# Facilio Script Reference — Complete Language Reference > Complete reference for the Facilio scripting language, built on ANTLR. Use this document to generate, validate, and repair Facilio scripts. ## Agent Instructions When the user asks to generate or modify a Facilio script: - Return only one complete Facilio function. - Put the script in a single fenced code block. - Do not include explanations unless explicitly requested. - Do not invent module names or field names. - Use only supported syntax, data types, methods, operators, modules, and namespace functions from this document. - If required module names, field names, IDs, or business rules are missing, ask for them instead of guessing. When the user asks to explain, debug, review, or validate a script, explanations are allowed unless the user specifically requests script-only output. ## Key Facts (READ FIRST) **Language Rules** - **Variable declarations must NOT include a data type** — the type is auto-detected by the system. - **One function per file** — all logic must be inside a single function block. - **Always return a function block** — the script must be a complete function definition. - **Reserved keywords** cannot be used as variable names: `criteria`, `limit`, `log`, `Module`, `Reading`. - **Do not invent module names or field names** — use only module names and field names provided by the user, documentation, or the module/field CSV. - Field names must come from the correct module schema. Inside Criteria expressions, use only the field name, without module prefixes. **Data Types** - Return types: `void`, `String`, `Number`, `Boolean`, `Map`, `List`, `Criteria`. - Parameter types: `String`, `Number`, `Boolean`, `Map`, `List`, `Criteria`. - Data types are **only used in the function signature** — return type and parameters — **never** inside the function body. **Data Formats** - All date/datetime values: **milliseconds since epoch**. - All records are represented as Maps: `{"id": 1234, "name": "JCB Workorder", "safetyPlan": {"id": 4321}}`. - Lookup fields hold only `{id}` — fetch the full record separately via `Module("moduleName").fetchFirst([id == record.lookupField.id])`. **Fetch and Null Handling** - `fetch` and `fetchFirst` return `null` when no records match — not an empty list/map. - Always null-check the result of `fetch` before looping. - Always null-check the result of `fetchFirst` before accessing fields. - For empty criteria, use `[id > 0]`. **Special Fields** - Special fields `id` and `siteId` exist in all modules by default. - `siteId` is a Number that refers to the Site module. --- ## Minimal Valid Script ```facilio void run() { log "Hello Facilio"; } ``` Rules: - The file must contain exactly one function. - All executable logic must be inside the function. - Return type and parameter types appear only in the function signature. - Variables inside the body are assigned without data types. --- ## Generated Script Output Format When generating a Facilio script, output exactly one fenced code block: ```facilio void functionName() { // script statements } ``` Do not include Markdown explanations around the script unless the user explicitly asks for an explanation. ## Function Syntax **Signature**: ` ( , ...) { ... }` ```javascript // Void function with all parameter types void test(String a, Number b, Boolean c, Map d, List e, Criteria f) { // statements } // Function with return value String greet(String name) { return "Hello " + name; } ``` --- ## Data Types and Initialization IMPORTANT: Variable initialization inside the function body must NOT have a data type prefix. Types are auto-detected. Wrong: ```javascript String name = "Test"; Number count = 10; Boolean active = true; ``` Correct: ```javascript name = "Test"; count = 10; active = true; ``` ### String **Init**: `varName = "text";` ```javascript a = "hello"; ``` | Method | Description | Example | |:---|:---|:---| | **equals** | Compares with another string, returns Boolean | `stringVar.equals("Work Order")` | | **contains** | Returns true if string contains the search string | `stringVar.contains("Order")` | | **length** | Returns character count (including spaces) | `stringVar.length()` | | **subString** | Returns substring between start and end index | `stringVar.subString(5, 10)` | | **indexOf** | Returns first occurrence index of search string | `stringVar.indexOf("Order")` | | **lastIndexOf** | Returns last occurrence index of search string | `stringVar.lastIndexOf("Order")` | | **trim** | Removes leading and trailing spaces | `stringVar.trim()` | | **split** | Splits string into a List by delimiter | `stringVar.split("&")` | | **toUpperCase** | Returns string in upper case | `stringVar.toUpperCase()` | | **toLowerCase** | Returns string in lower case | `stringVar.toLowerCase()` | | **replace** | Replaces first occurrence of old with new | `stringVar.replace("Facilio", "FacilioTutorial")` | | **replaceAll** | Replaces all occurrences of old with new | `stringVar.replaceAll("Facilio", "FacilioTutorial")` | | **addDoubleQuotes** | Adds `"` to end of string | `stringVar.addDoubleQuotes()` | | **encloseDoubleQuotes** | Wraps string in `"..."` | `stringVar.encloseDoubleQuotes()` | | **match** | Returns array of regex/string matches, or null | `text.match("ing")` | | **toNumber** | Casts string to Number | `stringVar.toNumber()` | No other String methods are available. ### Number **Init**: `varName = ;` ```javascript a = 10; b = 10.35; ``` | Method | Description | Example | |:---|:---|:---| | **intValue** | Casts to integer | `num.intValue()` | | **longValue** | Casts to long | `num.longValue()` | | **doubleValue** | Casts to double | `num.doubleValue()` | **Formatting**: Use `new NameSpace("number").format(n)` — same as Java NumberFormat. ### Boolean **Init**: `varName = true;` or `varName = false;` **IMPORTANT**: Boolean operations in expressions use single `&` and `|`. In **if statements**, use `&&` and `||`. ```javascript a = true; b = false; c = a & b; if (a && b) { // ... } ``` ### Map **Init**: `varName = {};` ```javascript a = {}; a["name"] = "GenAI"; // Nested maps use dot notation map = {}; map.a.b.c = "test"; log map.a.b.c; ``` | Method | Description | Example | |:---|:---|:---| | **put** | Adds key-value pair | `map.put("key", "value")` | | **putAll** | Merges another map into this one | `map.putAll(map2)` | | **get** | Returns value for key | `map.get("key")` | | **remove** | Removes key-value pair by key | `map.remove("a")` | | **removeAll** | Removes multiple keys (takes List) | `map.removeAll(keysList)` | | **size** | Returns count of key-value pairs | `map.size()` | | **keys** | Returns all keys as a List | `map.keys()` | | **toString** | Returns JSON formatted string | `map.toString()` | | **clear** | Removes all key-value pairs | `map.clear()` | No other Map methods are available. ### List **Init**: `varName = [];` ```javascript a = []; a.add("GenAI"); ``` **IMPORTANT**: No direct nested list access. Always assign inner list to a variable first. ```javascript list1 = ["hello"]; list2 = [list1]; // list2[0][0] — WRONG a = list2[0]; log a[0]; // CORRECT ``` | Method | Description | Example | |:---|:---|:---| | **add** | Adds element to end of list | `list.add("name")` | | **addAll** | Adds all elements from another list | `list.addAll(otherList)` | | **get** | Returns element at index | `list.get(5)` | | **set** | Updates element at index | `list.set(2, "Alarms")` | | **remove** | Removes element at index | `list.remove(1)` | | **removeElement** | Removes first matching element by value | `list.removeElement("Asset")` | | **removeAll** | Removes all elements present in another list | `list.removeAll(removeList)` | | **contains** | Returns true if element exists in list | `list.contains("Visitor")` | | **size** | Returns element count | `list.size()` | | **sort** | Sorts list; optional boolean for ascending (default) or descending | `list.sort()` or `list.sort(false)` | | **allMatch** | Returns true if all elements are the same | `list.allMatch()` | | **join** | Joins elements with delimiter, returns String | `list.join(",")` | | **clear** | Removes all elements | `list.clear()` | No other List methods are available. --- ## Control Flow ### Conditional Statements Consists of `if`, `else if`, and `else`. Conditional statements must be standalone blocks — **no inline lambdas**. ```javascript time = 12; greeting = ""; if (time < 10) { greeting = "Good morning"; } else if (time < 20) { greeting = "Good day"; } else { greeting = "Good evening"; } ``` ### Relational Operators | Operator | Description | |:---|:---| | `==` | Equals | | `!=` | Not equals | | `<` | Less than | | `>` | Greater than | | `<=` | Less than or equal to | | `>=` | Greater than or equal to | ### Logical Operators Combine two or more conditions with `&&` (AND) or `||` (OR). ```javascript if (a > 10 && b > 20) { // ... } ``` --- ## Arithmetic Operators | Operator | Description | |:---|:---| | `+` | Add (numbers) or concatenate (strings) | | `-` | Subtract | | `*` | Multiply | | `/` | Divide | | `%` | Modulo (remainder) | No other arithmetic operations are available. ```javascript a = 5; b = 5; c = a + b; // 10 First_Name = "John"; Last_Name = "Smith"; Name = First_Name + Last_Name; // "JohnSmith" ``` --- ## Loops **Only `for each` is available.** There is no `for`, `while`, or range-based iteration. To repeat N times, create a list with N elements and iterate over it. There is no `continue` statement. ### Iterating over a List `for each , in { ... }` — first variable is the index, second is the element. ```javascript list = [1, 2, 3, 4, 5]; for each index, value in list { log index; log value; } ``` ### Iterating over a Map `for each , in { ... }` — first variable is the key, second is the value. ```javascript data = {}; data.name = "user"; data.age = 30; data.exp = 5; for each key, value in data { log key; log value; } ``` --- ## Criteria Object **Syntax**: `varName = [ ]` **IMPORTANT**: Field names must be a single value — no module name or other prefix. Supported operators: `==`, `!=`, `>=`, `>`, `<=`, `<` — **strictly no other operators**. Field names must still belong to the module being fetched or evaluated. ```javascript myCriteria = [user > 20 && user == "USA"]; ``` ### Criteria Methods | Method | Params | Returns | Example | |:---|:---|:---|:---| | **evaluate** | `(Map data)` | Boolean — true if data matches criteria | `criteriaObj.evaluate(mapObj)` | | **and** | `(Criteria b)` | New criteria: `a AND b` | `criteriaObj.and([name == "Card"])` | | **or** | `(Criteria b)` | New criteria: `a OR b` | `criteriaObj.or([name == "Card"])` | ### Criteria Namespace Functions | Function | Params | Returns | Example | |:---|:---|:---|:---| | **get** | `(Map mapVal)` | Criteria object from map | `new NameSpace("criteria").get(mapVal)` | | **getContainsCriteria** | `(String fieldName, String value)` | Criteria object for contains match | `new NameSpace("criteria").getContainsCriteria("id", "1297239")` | --- ## DB Fetch Map A Map used to configure advanced record fetching from a module. Pass it to `Module.fetch()` or `Module.fetchFirst()` instead of a plain criteria. | Parameter | Description | Example Value | |:---|:---|:---| | **criteria** | Criteria object for filtering | `[id > 10]` | | **sortByFieldName** | Field name to sort by | `"id"` | | **sortOrder** | `"asc"` (default) or `"desc"` | `"desc"` | | **limit** | Maximum records to return (non-negative integer) | `5` | | **fieldNames** | List of specific field names to fetch | `["id", "name"]` | ```javascript // Full example with all parameters fetchMap = {}; fetchMap.put("criteria", [id > 0]); fetchMap.put("sortByFieldName", "id"); fetchMap.put("sortOrder", "desc"); fetchMap.put("limit", 5); fetchMap.put("fieldNames", ["id", "name"]); records = Module("workorder").fetch(fetchMap); ``` --- ## Modules and Fields Modules are entities similar to database tables, uniquely identified by name. A module can be standalone or a child of a parent module (e.g., "Work Order" is a child of "Ticket"). Fetching a child module retrieves fields from both child and parent. Fields are like table columns, each with a specific data type (String, Number, DateTime, Lookup, etc.). Lookup fields capture relationships to other modules (similar to foreign keys). **Module and field names** are provided in a CSV file. Access modules only via `MODULE_NAME` and fields only via `FIELD_NAME` from that CSV. Agents must not invent module names or field names. If the required module or field name is missing, ask the user for the CSV value instead of guessing. **Special fields**: `id` (unique record identifier, Number) and `siteId` (site reference, Number) exist in all modules by default. `siteId` is a Number that refers to the Site module. **Records as Maps**: All records are Map objects. Lookup fields hold only `{id}` — to get the full lookup record, fetch it separately. ```javascript // Lookup field access pattern wo = Module("workorder").fetchFirst([id == 1234]); if (wo.safetyPlan != null) { safetyPlanRecord = Module("safetyPlan").fetchFirst([id == wo.safetyPlan.id]); } ``` ### CRUD Operations All methods below are available on every module via `Module("moduleName")`. **IMPORTANT**: `fetch` and `fetchFirst` return `null` when no records match — not an empty list or map. Always provide a criteria object or DB Fetch Map. For "all records" use `[id > 0]`. #### fetch **Params**: `(Criteria)` or `(DB Fetch Map)` **Returns**: List of Maps (records), or `null` if none found ```javascript wos = Module("workorder").fetch([name != null]); // Fetch by lookup: query the lookup module first, then use its ID statusRecord = Module("moduleState").fetchFirst([name == "open"]); openStatusId = statusRecord.id; openWorkOrders = Module("workorder").fetch([createdTime >= startTime && createdTime <= endTime && status == openStatusId]); serviceRequests = Module("serviceRequest").fetch([sysCreatedTime >= startTime && sysCreatedTime <= endTime]); ``` #### fetchFirst **Params**: `(Criteria)` or `(DB Fetch Map)` **Returns**: Single Map (record), or `null` if none found ```javascript wo = Module("workorder").fetchFirst([name != null]); ``` #### add **Params**: `(Map record)` or `(List of Maps)` for bulk insert **Returns**: Created record ```javascript building = {}; building.name = "HQ at New York"; building.description = "Company headquarters"; building.totalArea = 200000; building.siteId = 20; Module("building").add(building); ``` #### update **Params**: `(Criteria, Map updateData)` — updates all records matching criteria **Returns**: void ```javascript floorId = 1; updateFloor = {}; updateFloor.totalArea = 300000; Module("floor").update([id == floorId], updateFloor); ``` #### delete **Params**: `(Criteria)` — deletes all records matching criteria **Returns**: void ```javascript spaceId = 1; Module("space").delete([id == spaceId]); ``` ### Other Module Methods | Method | Params | Returns | Example | |:---|:---|:---|:---| | **export** | `(String viewName, Criteria?)` | Exported file URL | `Module("quote").export("all", [id == siteId])` | | **exportAsFileId** | `(String viewName, Criteria?)` | Exported file ID | `Module("meter").exportAsFileId("all", [siteId == 120])` | | **getViewCriteria** | `(String viewName)` | Criteria object for that view | `Module("asset").getViewCriteria("all")` | | **getAllStates** | none | List of state Maps (`{id, status, displayName, type, ...}`) | `Module("floor").getAllStates()` | | **addNote** | `(Number recordId, String note)` | void | `Module("workorder").addNote(1234, "This is a note")` | | **addAttachments** | `(Number recordId, List fileIds)` | void; optional 3rd param `duplicateFiles` | `Module("workorder").addAttachments(1234, [10,20,30])` | | **getEnumFieldValue** | `(String fieldName, Number enumNum)` | Enum name string | `Module("announcement").getEnumFieldValue("category", 1)` | ### Module-Specific Methods #### workorder | Method | Params | Description | |:---|:---|:---| | **addTask** | `(Number recordId, List taskList)` | Adds tasks to a work order | | **reschedulePreOpenWorkOrder** | `(Number recordId, Number timeMillis)` | Updates the opening time of a pre-open work order | | **updatePreOpenWorkOrders** | `(Number recordId, Map recordMap)` | Updates field values of a pre-open work order | ```javascript taskList = []; task = {}; task.name = "Task 1"; task.description = "This is a task"; taskList.add(task); Module("workorder").addTask(1234, taskList); ``` #### users | Method | Params | Returns | |:---|:---|:---| | **getMyTeams** | none | List of Maps (team details) | | **getUsersForPeople** | `(Number peopleId)` | List of Maps (user details) | #### groups (Team/Group) | Method | Params | Returns | |:---|:---|:---| | **getTeamUsers** | `(String teamName)` | List of Maps (user details) | | **getMembersByTeamId** | `(Number teamId)` | List of Maps (user details) | #### serviceTask | Method | Params | Description | |:---|:---|:---| | **startTask** | `(Number taskId)` | Starts all tasks for the service task | | **pauseTask** | `(Number taskId)` | Pauses all tasks | | **resumeTask** | `(Number taskId)` | Resumes all tasks | | **completeTask** | `(Number taskId)` | Completes all tasks | #### rawAlarm | Method | Params | Description | |:---|:---|:---| | **addRawAlarm** | `(List alarmMaps)` | Adds raw alarms from a list of Maps | #### itemTransactions | Method | Params | Description | |:---|:---|:---| | **addOrUpdateItemTransactions** | `(Map transactionMap)` | Adds or updates item transactions | #### inspection | Method | Params | Description | |:---|:---|:---| | **addInspections** | `(Number templateId, List resourceIds)` | Generates inspections for given resources | #### induction | Method | Params | Description | |:---|:---|:---| | **addInductions** | `(String templateName)` | Produces induction response from template | #### plannedmaintenance | Method | Params | Description | |:---|:---|:---| | **addAssetsToPPMPlanner** | `(Map resourcePlannerObj)` | Adds assets to a PPM planner | | **publishAssetsOfPlanner** | `(Number pmId, Number plannerId, List resourcePlannerIds)` | Publishes asset planners | | **publish** | `(List pmIds)` | Publishes planned maintenance records | | **unPublish** | `(List pmIds)` | Unpublishes planned maintenance records | #### people | Method | Params | Description | |:---|:---|:---| | **getPeopleWithRoles** | `(Number peopleId)` | Returns people with their roles | | **getPeopleForRoles** | `(List roleIds)` | Returns people matching given role IDs | | **updateScopingForPeople** | `(String scopingName, List peopleIds)` | Updates scoping for specified people | | **updatePermissionSetsForPeople** | `(List permSetNames, List peopleIds)` | Updates permission sets | | **addAccessibleSpacesForPeople** | `(List peopleIds, List spaceIds)` | Adds accessible spaces | | **deleteAccessibleSpacesForPeople** | `(List peopleIds, List spaceIds)` | Deletes accessible spaces | | **updateAccessibleSpacesForPeople** | `(List peopleIds, List spaceIds)` | Updates accessible spaces | | **getAccessibleSpacesForPeople** | `(Number peopleId)` | Returns accessible spaces for a person | | **getAccessibleSpacesForUser** | `(Number userId)` | Returns accessible spaces for a user | | **getAppAccessForPeople** | `(Number peopleId)` | Returns app access for a person | --- ## Date Range Object Contains a start time and end time (in milliseconds). Used for date-based operations. **Create**: `new NameSpace("dateRange").create(startTimeMillis, endTimeMillis)` | Method | Returns | Example | |:---|:---|:---| | **getStartTime** | Start time in milliseconds | `dateRange.getStartTime()` | | **getEndTime** | End time in milliseconds | `dateRange.getEndTime()` | ```javascript obj = new NameSpace("dateRange").create(1640975400000, 1643653799999); log obj.getStartTime(); // 1640975400000 log obj.getEndTime(); // 1643653799999 ``` --- ## Schedule Object Used for frequency-based scheduled operations. ### create **Params**: `(Map dataMap)` — creates a schedule object from a configuration map. **Frequency types**: | frequencyType | Description | values field | monthValue field | |:---|:---|:---|:---| | `1` | Daily | — | — | | `2` | Weekly | Days of week (1=Mon..7=Sun) | — | | `3` | Monthly Date | Days of month (1–31) | — | | `4` | Monthly Week | Days of week (1=Mon..7=Sun) | — | | `7` | Quarterly Date | Days of month (1–31) | Start month (1=Jan..3=Mar) | | `8` | Quarterly Week | Days of week (1=Mon..7=Sun) | Start month (1=Jan..3=Mar) | | `9` | Half-Yearly Date | Days of month (1–31) | Start month (1=Jan..6=Jun) | | `10` | Half-Yearly Week | Days of week (1=Mon..7=Sun) | Start month (1=Jan..6=Jun) | **Common fields**: `times` (List of "HH:mm" strings, mandatory), `frequency` (repeat interval), `skipEvery` (skip cycle count), `endDate` (end timestamp in milliseconds). ```javascript // Daily schedule dataMap = {}; dataMap["frequencyType"] = 1; dataMap["times"] = ["00:00", "00:15", "00:30"]; dataMap["frequency"] = 2; dataMap["skipEvery"] = 3; dataMap["endDate"] = 1748629800000; scheduleObject = new NameSpace("schedule").create(dataMap); // Weekly schedule dataMap = {}; dataMap["frequencyType"] = 2; dataMap["values"] = [1, 2, 3]; dataMap["times"] = ["00:00", "00:15", "00:30"]; dataMap["frequency"] = 2; dataMap["skipEvery"] = 3; dataMap["endDate"] = 1748629800000; scheduleObject = new NameSpace("schedule").create(dataMap); // Monthly Date dataMap = {}; dataMap["frequencyType"] = 3; dataMap["values"] = [1, 2, 3]; dataMap["times"] = ["00:00", "00:15", "00:30"]; dataMap["frequency"] = 2; dataMap["skipEvery"] = 3; dataMap["endDate"] = 1748629800000; scheduleObject = new NameSpace("schedule").create(dataMap); // Quarterly Date dataMap = {}; dataMap["frequencyType"] = 7; dataMap["monthValue"] = 1; dataMap["values"] = [1, 2, 3]; dataMap["times"] = ["00:00", "00:15", "00:30"]; dataMap["endDate"] = 1748629800000; scheduleObject = new NameSpace("schedule").create(dataMap); ``` ### nextExecutionTime **Params**: `(scheduleObject, Number startTimeSeconds, Number endTimeSeconds)` — note: times are in **seconds**, not milliseconds. **Returns**: List of dates (in seconds) that fall within the schedule. ```javascript dateList = new NameSpace("schedule").nextExecutionTime(scheduleObject, 1746037800, 1746815400); ``` --- ## Reading Object Used for CRUD operations on readings (sensor data). **Create**: `Reading(fieldId, resourceId)` — both params are Numbers. | Method | Params | Returns | Example | |:---|:---|:---|:---| | **getLastValue** | none | Last received reading value | `reading.getLastValue()` | | **get** | `(Criteria)` | List of readings matching criteria | `reading.get([id > 100])` | | **getEnumMap** | none | Enum list as Map | `reading.getEnumMap()` | | **getRDM** | none | RDM value of the reading | `reading.getRDM()` | | **add** | `(value, Number timeMillis)` | void — adds a reading at specified time | `reading.add(100, 1751826600000)` | ```javascript reading = Reading(1, 2); lastValue = reading.getLastValue(); reading.add(100, 1751826600000); ``` --- ## XML Builder Used to build and parse XML documents. **Create**: `new NameSpace("xml").create("rootElement")` — returns an XML builder object with start/end tags. **Parse**: `new NameSpace("xml").parse(xmlString)` — parses an XML string into an XML builder object. | Method | Params | Returns | Example | |:---|:---|:---|:---| | **element** | `(String name)` | XML builder with child element added | `xmlObj.element("book")` | | **text** | `(String content)` | XML builder with text content | `xmlObj.text("7Habits")` | | **attribute** | `(String key, String value)` | XML builder with attribute set | `xmlObj.attribute("name", "value")` | | **getAllElements** | `(String name)` | List of XML builder objects for matching children | `xmlObj.getAllElements("book")` | | **getElement** | `(String name)` | First matching child XML builder object | `xmlObj.getElement("book")` | | **getAttribute** | `(String key)` | Attribute value string | `xmlObj.getAttribute("a")` | | **getText** | none | Text content string | `xmlObj.getText()` | | **parent** | none | Parent XML builder object | `xmlObj.element("book").parent()` | | **getXMLString** | none | XML string representation | `xmlObj.getXMLString()` | ```javascript // Build XML xmlObject = new NameSpace("xml").create("bookstore"); xmlObject2 = xmlObject.element("book").text("7Habits").parent(); log xmlObject.getXMLString(); // Parse XML xmlString = "Pankaj25Developer"; xmlObj = new NameSpace("xml").parse(xmlString); log xmlObj.getElement("name").getText(); // Pankaj ``` --- ## Namespace Functions All functions are invoked via: `new NameSpace("").functionName(params)` ### default | Function | Params | Returns | Description | |:---|:---|:---|:---| | **getUserName** | none | String | Current user's name | | **getUserEmail** | none | String | Current user's email | | **getUserNameForId** | `(Number userId)` | String | Username for given user ID | | **isUserInSiteScope** | `(Number siteId, Number userId)` | Boolean | Whether user has access to site | | **getDelegations** | `(Number userId)` | delegation settings | Retrieves delegation settings | | **convertUnit** | `(Number value, Number fromUnit, Number toUnit)` | Number | Converts between units | | **getUnit** | `(String fieldName, String moduleName)` | String | Display unit for a field | | **mergeJson** | `(Map map1, Map map2)` | Map | Merges two maps | | **getTimeIntervals** | `(Number startMillis, Number endMillis, Number intervalSec)` | List of DateRange | Splits time range into intervals | | **exportURL** | `(String url)` | String (PDF URL) | Exports URL as PDF, returns PDF URL | | **exportURLAsFile** | `(String url, String fileName)` | String (download URL) | Exports URL content as downloadable file | | **exportURLWithFileReturn** | `(String url)` | File | Exports URL content and returns file | | **getOrgDownloadUrl** | `(Number fileId)` | String (URL) | Org file URL for a file ID | | **getFileName** | `(Number fileId)` | String | File name for a file ID | | **encodeFileToBase64Binary** | `(Number fileId)` | String (base64) | Base64-encoded file content | | **getTinyUrl** | `(String longUrl)` | String (short URL) | Generates short URL | | **encodeUrl** | `(String url)` | String (encoded URL) | URL-encodes a string | | **getLatLngForSite** | `(Number siteId)` | String (Google Maps URL) | Map URL with lat/lng for site | | **tagAssetAsRotating** | `(Number assetId)` | void | Tags asset as rotating equipment | | **updateStatus** | `(Number recordId, String moduleName, String status)` | void | Updates record status | | **getBlobFile** | `(String url, Map headers, Map params, String fileName, String assetId)` | File | Fetches blob file | | **getPermaLinkUrl** | `(String url, String token, Number startDate, Number endDate, String user)` | String (permalink) | Generates permanent link URL | | **getMaintenancePermaLinkUrl** | `(String url, Number startDate, Number endDate, Number operatorId, String user, String dashboard)` | String (permalink) | Permanent link for maintenance records | | **updateVendorPortalAccess** | `(Number vendorContactId, Number vendorId)` | void | Updates vendor portal access | | **revokeVendorPortalAccess** | `(Number vendorContactId, Number vendorId)` | void | Revokes vendor portal access | | **updateTenantPortalAccess** | `(Number tenantContactId, Number tenantId)` | void | Updates tenant portal access | | **revokeTenantPortalAccess** | `(Number tenantContactId, Number tenantId)` | void | Revokes tenant portal access | ### csv | Function | Params | Returns | Description | |:---|:---|:---|:---| | **build** | `(List data)` | String (CSV) | Builds CSV string from list | | **parse** | `(String csvString)` | List | Parses CSV string to list | | **parseAsMap** | `(String csvString)` | Map | Parses CSV string to map | | **buildWithMap** | `(List of Maps)` | String (CSV) | Builds CSV string from list of maps | ```javascript csvString = new NameSpace("csv").build(list); parsedList = new NameSpace("csv").parse(csvString); csvMap = new NameSpace("csv").parseAsMap(csvString); csvFromMap = new NameSpace("csv").buildWithMap(mapList); ``` ### notification #### sendMail **Params**: `(Map emailParams)` — sends email to specified recipients. Map keys: `to` (email address), `subject`, `message` (HTML supported), `mailType` (`"html"`), `attachments` (optional Map of `fileName: fileUrl`) ```javascript // Simple email emailParams = {}; emailParams["to"] = "some@email.com"; emailParams["subject"] = "Work orders that need your attention"; emailParams["message"] = "Dear User
Here is your report.
Regards,
Team Facilio."; emailParams["mailType"] = "html"; new NameSpace("notification").sendMail(emailParams); // Email with attachments attachments = {}; exportedFileDownloadURL = Module("workorder").export("open", [subject != null]); attachments["User-Guide.pdf"] = exportedFileDownloadURL; emailAttachmentParams = {}; emailAttachmentParams["to"] = "some@email.com"; emailAttachmentParams["subject"] = "Work orders that need your attention"; emailAttachmentParams["message"] = "Dear User
Please find the attached report."; emailAttachmentParams["attachments"] = attachments; emailAttachmentParams["mailType"] = "html"; new NameSpace("notification").sendMail(emailAttachmentParams); ``` #### sendNotification **Params**: `(Number userId, Map notificationParams)` — sends push notification. ```javascript userId = 1070141; map = {}; maindata = {}; maindata.text = "message"; maindata.title = "Final"; map.notification = maindata; map.data = maindata; new NameSpace("notification").sendNotification(userId, map); ``` ### math | Function | Params | Returns | Description | |:---|:---|:---|:---| | **abs** | `(Number value)` | Number | Absolute value | | **ceil** | `(Number value)` | Number | Nearest largest integer | | **floor** | `(Number value)` | Number | Nearest smallest integer | | **pow** | `(Number base, Number power)` | Number | base raised to power | | **cbrt** | `(Number value)` | Number | Cube root | | **sqrt** | `(Number value)` | Number | Square root | | **random** | `(Number maxLimit)` | Number | Random number in range 0 to maxLimit | | **setPrecision** | `(Number value, Number decimals)` | Number | Rounds to specified decimal places | | **setPrecisionWithCeiling** | `(Number value, Number decimals)` | Number | Precision with ceiling rounding | | **exp** | `(Number value)` | Number | e^value (Euler's number = 2.71828) | | **ln** | `(Number value)` | Number | Natural logarithm (base e) | | **getLog** | `(Number value)` | Number | Logarithm (base 10) | ```javascript new NameSpace("math").abs(-20); // 20 new NameSpace("math").ceil(100.23); // 101 new NameSpace("math").floor(100.23); // 100 new NameSpace("math").pow(100, 2); // 10000 new NameSpace("math").sqrt(100); // 10 new NameSpace("math").setPrecision(3.14159, 2); // 3.14 ``` ### http #### get **Params**: `(String url, Map params, Map headers)` **Returns**: String — response content as text ```javascript url = "http://"; paramsMap = {}; paramsMap.fetchLimit = 100; headerMap = {}; headerMap.Authorization = "Bearer token"; response = new NameSpace("http").get(url, paramsMap, headerMap); log response; ``` #### post **Params**: `(String url, Map params, Map headers, String body)` **Returns**: String — response content as text ```javascript url = "http://"; paramsMap = {}; paramsMap.fetchLimit = 100; headerMap = {}; headerMap.Authorization = "Bearer token"; body = "Some dummy content to be posted"; response = new NameSpace("http").post(url, paramsMap, headerMap, body); log response; ``` ### date All date/time operations use **milliseconds**. | Function | Params | Returns | Description | |:---|:---|:---|:---| | **now** | none | Number (millis) | Current time | | **getFormattedTime** | `(Number millis, String format)` | String | Formatted date/time (Java DateFormat patterns) | | **getMilliSecondFromFormatedDateString** | `(String dateStr, String format)` | Number (millis) | Parses formatted date string to millis | | **getDayStartTime** | `(Number? millis)` | Number (millis) | Start of day; current day if no param | | **getDayEndTime** | `(Number? millis)` | Number (millis) | End of day; current day if no param | | **getMonthStartTime** | `(Number? millis)` | Number (millis) | Start of month; current month if no param | | **getMonthEndTime** | `(Number? millis)` | Number (millis) | End of month; current month if no param | | **getPreviousMonthStartDate** | none | Number (millis) | Previous month start | | **getPreviousMonthEndDate** | none | Number (millis) | Previous month end | | **getPreviousQuarterStartDate** | `(Number? millis)` | Number (millis) | Previous quarter start; current if no param | | **getPreviousQuarterEndDate** | `(Number? millis)` | Number (millis) | Previous quarter end; current if no param | | **getCurrentMonthDays** | `(Number? millis)` | Number | Days in current month or specified date's month | | **getLastMonthDays** | none | Number | Days in last month | | **getPreviousMonthName** | none | String | Previous month name with year | | **getPreviousLastMonthName** | none | String | Month before previous month name with year | | **addMonths** | `(Number millis, Number n)` | Number (millis) | Adds n months (negative to subtract) | | **addYears** | `(Number millis, Number n)` | Number (millis) | Adds n years (negative to subtract) | ```javascript currentTime = new NameSpace("date").now(); formatted = new NameSpace("date").getFormattedTime(currentTime, "MMM dd, yyyy hh:mm a"); millis = new NameSpace("date").getMilliSecondFromFormatedDateString("20-01-2001 11:04:02", "dd-MM-yyyy HH:mm:ss"); dayStart = new NameSpace("date").getDayStartTime(); monthEnd = new NameSpace("date").getMonthEndTime(); future = new NameSpace("date").addMonths(currentTime, 3); past = new NameSpace("date").addYears(currentTime, -2); ``` --- ## System Variables Read-only variables predefined by the system. Values are assigned automatically and cannot be changed. | Variable | Properties | |:---|:---| | **currentUser** | `name`, `email`, `id`, and other user fields | | **currentOrg** | `name` and other org fields | ```javascript log currentUser.name; log currentUser.email; log currentUser.id; log currentOrg.name; ``` --- ## Scenario-Based Examples ### Lookup null check Lookup fields hold only the ID, not the full object. Check null on the lookup field itself. ```javascript record = Module("serviceRequest").fetchFirst([id != null]); if (record.requester != null) { log "requester is present in the service request"; } else { log "requester is not present in the service request"; } ``` ### Fetching full lookup object To get the complete lookup record, fetch it by ID from the lookup's module. ```javascript record = Module("serviceRequest").fetchFirst([id != null]); if (record.urgency != null) { urgencyRecord = Module("servicerequestpriority").fetchFirst([id == record.urgency.id]); } else { log "urgency is not present in the service request"; } ``` ### Null check before loop Always check for null before iterating — `fetch` returns null, not an empty list. ```javascript allAssetBreakdowns = Module("assetbreakdown").fetch([id > 0]); if (allAssetBreakdowns != null) { for each index, breakdown in allAssetBreakdowns { log "breakdown id is : " + breakdown.id; } } ``` ### Fetching user lookup The user module requires special handling: module name is `"users"` and always use `ouid` as the ID field. ```javascript user = Module("users").fetchFirst([ouid == serviceRequest.sysModifiedBy.id]); if (user != null) { log "user name is : " + user.name; log "user email is : " + user.email; } else { log "user not found"; } ``` ### Fetching site lookup All modules have `siteId` as a Number field. Query site records using `siteId` directly. ```javascript asset = Module("asset").fetchFirst([id == 1234]); if (asset.siteId != null) { site = Module("site").fetchFirst([id == asset.siteId]); log "site name is : " + site.name; } else { log "site not found"; } ``` --- ## Grammar Reference (ANTLR) The following is the complete `.g4` grammar file for the scripting language: ```antlr grammar WorkflowV2; parse : function_block EOF ; function_block : data_type function_name_declare OPEN_PARANTHESIS (function_param)*(COMMA function_param)* CLOSE_PARANTHESIS OPEN_BRACE block CLOSE_BRACE ; function_name_declare: VAR ; function_param: data_type VAR ; data_type : op=(VOID | DATA_TYPE_STRING | DATA_TYPE_NUMBER | DATA_TYPE_BOOLEAN | DATA_TYPE_MAP | DATA_TYPE_LIST | DATA_TYPE_CRITERIA | DATA_TYPE_OBJECT) ; block : statement* ; try_catch : TRY OPEN_BRACE try_statement* CLOSE_BRACE CATCH OPEN_BRACE catch_statement* CLOSE_BRACE ; try_statement :statement ; catch_statement :statement ; statement : assignment | if_statement | for_each_statement | log | stand_alone_expr SEMICOLON | function_return | try_catch | OTHER {System.err.println("unknown char: " + $OTHER.text);} ; assignment : assignment_var ASSIGN expr SEMICOLON ; assignment_var : VAR #assignSingleVar | VAR (OPEN_BRACKET expr CLOSE_BRACKET) #assignSingleBracketVar | VAR(DOT VAR)+ #assignMultiDotVar ; if_statement : IF condition_block (ELSE IF condition_block)* (ELSE statement_block)? ; condition_block : boolean_expr statement_block ; statement_block : OPEN_BRACE block CLOSE_BRACE | statement ; for_each_statement : FOR_EACH VAR (COMMA) VAR 'in' expr statement_block ; log : LOG expr SEMICOLON ; function_return : RETURN expr SEMICOLON ; boolean_expr : OPEN_PARANTHESIS boolean_expr_atom CLOSE_PARANTHESIS ; boolean_expr_atom : expr #exprForBoolean | boolean_expr_atom op=(AND | OR) boolean_expr_atom #booleanExprCalculation | OPEN_PARANTHESIS boolean_expr_atom CLOSE_PARANTHESIS #boolExprParanthesis ; expr : MINUS expr #unaryMinusExpr | NOT expr #notExpr | expr op=(MULT | DIV | MOD) expr #arithmeticFirstPrecedenceExpr | expr op=(PLUS | MINUS) expr #arithmeticSecondPrecedenceExpr | expr op=(LTEQ | GTEQ | LT | GT | EQ | NEQ) expr #relationalExpr | expr op=(SINGLE_AND | SINGLE_OR) expr #booleanExpr | atom #atomExpr | stand_alone_expr #standAloneStatements | criteria #criteriaInitialization ; stand_alone_expr : atom (recursive_expression)+ #recursive_expr ; recursive_expression : '.' VAR OPEN_PARANTHESIS (expr)*(COMMA expr)* CLOSE_PARANTHESIS | '.' VAR | OPEN_BRACKET atom CLOSE_BRACKET ; atom : OPEN_PARANTHESIS expr CLOSE_PARANTHESIS #paranthesisExpr | (INT | FLOAT) #numberAtom | (TRUE | FALSE) #booleanAtom | STRING #stringAtom | NULL #nullAtom | VAR #varAtom | 'new ' VAR OPEN_PARANTHESIS (expr)*(COMMA expr)* CLOSE_PARANTHESIS #newKeywordIntitialization | 'Module' OPEN_PARANTHESIS expr CLOSE_PARANTHESIS #customModuleInitialization | 'Reading' OPEN_PARANTHESIS expr ',' expr CLOSE_PARANTHESIS #readingInitialization | VAR OPEN_PARANTHESIS (expr)*(COMMA expr)* CLOSE_PARANTHESIS #moduleAndSystemNameSpaceInitialization | list_opperations #listOpp | map_opperations #mapOpps ; list_opperations : (OPEN_BRACKET CLOSE_BRACKET)+ #listInitialisation | OPEN_BRACKET (atom)+(COMMA atom)* CLOSE_BRACKET #listInitialisationWithElements ; map_opperations : (OPEN_BRACE CLOSE_BRACE)+ #mapInitialisation ; criteria : OPEN_BRACKET condition CLOSE_BRACKET ; condition : condition_atom | condition op=(AND | OR) condition | OPEN_PARANTHESIS condition CLOSE_PARANTHESIS ; condition_atom : VAR op=(LTEQ | GTEQ | LT | GT | EQ | NEQ) expr ; VOID : 'void'; DATA_TYPE_STRING : 'String'; DATA_TYPE_NUMBER : 'Number'; DATA_TYPE_BOOLEAN : 'Boolean'; DATA_TYPE_MAP : 'Map'; DATA_TYPE_LIST : 'List'; DATA_TYPE_CRITERIA : 'Criteria'; DATA_TYPE_OBJECT : 'Object'; RETURN : 'return'; TRY : 'try'; CATCH : 'catch'; OR : '||'; SINGLE_OR : '|'; DOT : '.'; AND : '&&'; SINGLE_AND : '&'; EQ : '=='; NEQ : '!='; GT : '>'; LT : '<'; GTEQ : '>='; LTEQ : '<='; PLUS : '+'; MINUS : '-'; MULT : '*'; DIV : '/'; MOD : '%'; POW : '^'; NOT : '!'; COMMA : ','; SEMICOLON : ';'; COLON : ':'; ASSIGN : '='; OPEN_PARANTHESIS : '('; CLOSE_PARANTHESIS : ')'; OPEN_BRACE : '{'; CLOSE_BRACE : '}'; OPEN_BRACKET : '['; CLOSE_BRACKET : ']'; TRUE : 'true'; FALSE : 'false'; NULL : 'null'; IF : 'if'; ELSE : 'else'; FOR_EACH : 'for each'; LOG : 'log'; VAR : [a-zA-Z_] [a-zA-Z_0-9]*; INT : [0-9]+; FLOAT : [0-9]+ '.' [0-9]* | '.' [0-9]+; fragment ESCAPED_QUOTE : '\\"'; STRING : '"' ( ESCAPED_QUOTE | ~('\n'|'\r') )*? '"'; COMMENT : '//' ~[\r\n]* -> skip ; BLOCKCOMMENT : '/*' .*? '*/' -> skip ; SPACE : [ \t\r\n] -> skip ; OTHER : . ; ```