@cxtms/cx-schema 1.9.67 → 1.9.241
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +43 -9
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
- package/schemas/actions/all.json +5 -1
- package/schemas/actions/reload.json +17 -0
- package/schemas/actions/setSessionStorage.json +38 -0
- package/schemas/actions/sound.json +39 -0
- package/schemas/actions/vibrate.json +48 -0
- package/schemas/components/avatar.json +1 -0
- package/schemas/components/badge.json +79 -0
- package/schemas/components/barcodeScanner.json +28 -0
- package/schemas/components/camera.json +177 -0
- package/schemas/components/card.json +78 -0
- package/schemas/components/dashboard-widget.json +39 -0
- package/schemas/components/dashboard.json +46 -0
- package/schemas/components/dataGrid.json +15 -0
- package/schemas/components/field-collection.json +86 -2
- package/schemas/components/field.json +39 -4
- package/schemas/components/form.json +4 -0
- package/schemas/components/index.json +17 -1
- package/schemas/components/infoLine.json +1 -0
- package/schemas/components/map.json +167 -0
- package/schemas/components/module.json +32 -0
- package/schemas/components/planner.json +1 -0
- package/schemas/components/progressBar.json +1 -0
- package/schemas/components/timeline.json +216 -74
- package/schemas/components/timelineGrid.json +5 -1
- package/schemas/fields/autocomplete-googleplaces.json +2 -2
- package/schemas/fields/datetime.json +2 -2
- package/schemas/fields/index.json +4 -0
- package/schemas/fields/number-select.json +88 -0
- package/schemas/fields/rangedatetime.json +10 -2
- package/schemas/fields/select-async.json +2 -2
- package/schemas/fields/toggle.json +91 -0
- package/schemas/schema.graphql +11016 -12171
- package/schemas/schemas.json +131 -4
- package/schemas/workflows/flow/entity.json +3 -1
- package/schemas/workflows/input.json +1 -1
- package/schemas/workflows/output.json +1 -1
- package/schemas/workflows/tasks/all.json +9 -0
- package/schemas/workflows/tasks/commodity.json +8 -0
- package/schemas/workflows/tasks/contact-address.json +25 -0
- package/schemas/workflows/tasks/dispatch-routes.json +64 -0
- package/schemas/workflows/tasks/edi.json +15 -0
- package/schemas/workflows/tasks/export.json +21 -0
- package/schemas/workflows/tasks/httpRequest.json +4 -0
- package/schemas/workflows/tasks/import.json +5 -0
- package/schemas/workflows/tasks/order-move.json +38 -0
- package/schemas/workflows/tasks/order.json +6 -0
- package/schemas/workflows/tasks/organization-config.json +101 -0
- package/schemas/workflows/tasks/tracking-event.json +11 -3
- package/schemas/workflows/tasks/workflow-execute.json +4 -0
- package/schemas/workflows/variable.json +2 -2
- package/schemas/workflows/workflow.json +39 -2
- package/skills/cxtms-developer/SKILL.md +5 -2
- package/skills/cxtms-developer/ref-cli-auth.md +2 -0
- package/skills/cxtms-developer/ref-entity-commodity.md +41 -5
- package/skills/cxtms-developer/ref-entity-contact.md +46 -0
- package/skills/cxtms-developer/ref-entity-dispatch-routing.md +195 -0
- package/skills/cxtms-developer/ref-entity-geography.md +26 -1
- package/skills/cxtms-developer/ref-entity-job.md +6 -2
- package/skills/cxtms-developer/ref-entity-order-sub.md +1 -1
- package/skills/cxtms-developer/ref-entity-order.md +36 -3
- package/skills/cxtms-developer/ref-entity-organization.md +4 -2
- package/skills/cxtms-developer/ref-entity-shared.md +79 -3
- package/skills/cxtms-developer/ref-graphql-query.md +246 -4
- package/skills/cxtms-module-builder/SKILL.md +33 -6
- package/skills/cxtms-module-builder/ref-components-data.md +85 -0
- package/skills/cxtms-module-builder/ref-components-display.md +282 -8
- package/skills/cxtms-module-builder/ref-components-forms.md +386 -21
- package/skills/cxtms-module-builder/ref-components-interactive.md +2 -0
- package/skills/cxtms-module-builder/ref-components-layout.md +288 -29
- package/skills/cxtms-module-builder/ref-components-specialized.md +168 -20
- package/skills/cxtms-workflow-builder/SKILL.md +45 -6
- package/skills/cxtms-workflow-builder/ref-entity.md +100 -4
- package/skills/cxtms-workflow-builder/ref-expressions-ncalc.md +35 -2
- package/skills/cxtms-workflow-builder/ref-expressions-template.md +15 -1
- package/skills/cxtms-workflow-builder/ref-flow.md +6 -1
- package/skills/cxtms-workflow-builder/ref-query.md +16 -0
- package/skills/cxtms-workflow-builder/ref-utilities.md +32 -2
- package/templates/workflow-public-api.yaml +1 -0
|
@@ -18,8 +18,9 @@ Core tenant entity. Each organization represents a company/tenant in the system.
|
|
|
18
18
|
| `faxNumber` | `string?` | |
|
|
19
19
|
| `uniqueId` | `Guid` | Immutable external identifier |
|
|
20
20
|
| `slug` | `string?` | URL-friendly identifier for Public API routes (lowercase, hyphenated) |
|
|
21
|
+
| `organizationType` | `OrganizationType` | `Regular` (0) or platform-managed `SystemAdministration` (1) |
|
|
21
22
|
| `isDeleted` | `bool` | Soft delete |
|
|
22
|
-
| `customValues` | `Dictionary?` | jsonb, merged on update |
|
|
23
|
+
| `customValues` | `Dictionary?` | jsonb, merged on update; incoming keys overwrite existing keys while unrelated keys are preserved |
|
|
23
24
|
| `created` / `lastModified` | `DateTime` | Audit fields (from `AuditableEntity`) |
|
|
24
25
|
| `createdBy` / `lastModifiedBy` | `string` | Audit fields |
|
|
25
26
|
|
|
@@ -37,5 +38,6 @@ Used by `PublicApiOrganizationResolver` to resolve org by slug or GUID in Public
|
|
|
37
38
|
|
|
38
39
|
- `ChangeSlug(string?)` — trims and lowercases
|
|
39
40
|
- `ChangeCompanyName(string)`, `ChangeAddressLine(string?)`, etc.
|
|
40
|
-
- `ChangeCustomValues(Dictionary?)` — merges into existing
|
|
41
|
+
- `ChangeCustomValues(Dictionary?)` — initializes `CustomValues` when needed, merges incoming values into the existing dictionary, and overwrites matching keys without dropping unrelated keys
|
|
41
42
|
- `DeleteOrganization()` — sets `IsDeleted = true`
|
|
43
|
+
- `ChangeOrganizationType(OrganizationType)` — platform-only; ignored by dynamic update mutations
|
|
@@ -1,15 +1,37 @@
|
|
|
1
1
|
# Shared Entity Reference
|
|
2
2
|
|
|
3
3
|
## Contents
|
|
4
|
+
- EntityField
|
|
4
5
|
- Tag
|
|
5
6
|
- Attachment
|
|
6
7
|
- Division
|
|
7
8
|
- EquipmentType
|
|
9
|
+
- Equipment
|
|
10
|
+
- EquipmentStatus
|
|
8
11
|
- PackageType
|
|
9
12
|
- NoteThread
|
|
10
13
|
- Note
|
|
11
14
|
|
|
12
|
-
Tag, Attachment, Division, EquipmentType, PackageType, Note/NoteThread.
|
|
15
|
+
EntityField, Tag, Attachment, Division, EquipmentType, Equipment, EquipmentStatus, PackageType, Note/NoteThread.
|
|
16
|
+
|
|
17
|
+
## EntityField
|
|
18
|
+
|
|
19
|
+
App-module field metadata used by grids, forms, filters, and entity extensions.
|
|
20
|
+
|
|
21
|
+
| Field | Type | Notes |
|
|
22
|
+
|-------|------|-------|
|
|
23
|
+
| `entityFieldId` | `int` | PK |
|
|
24
|
+
| `organizationId` | `int` | |
|
|
25
|
+
| `entityTypeId` | `int` | FK to EntityType |
|
|
26
|
+
| `name` | `string?` | Field name / path |
|
|
27
|
+
| `fieldDefinition` | `Dictionary` | Original module field definition |
|
|
28
|
+
| `isCustomField` | `bool` | True for custom field definitions |
|
|
29
|
+
| `isInactive` | `bool` | Inactive fields are excluded when resolving fields for an `entityName` |
|
|
30
|
+
| `priority` | `int` | Override precedence; if multiple active module fields have the same entity/name, highest priority wins |
|
|
31
|
+
|
|
32
|
+
**GraphQL:** `entityFields(organizationId, entityName, filter, search, orderBy)` exposes `priority`. Supplying `entityName` applies inactive filtering and priority-based duplicate resolution.
|
|
33
|
+
|
|
34
|
+
---
|
|
13
35
|
|
|
14
36
|
## Tag
|
|
15
37
|
|
|
@@ -103,6 +125,54 @@ No customValues. Linked to carriers via `CarrierEquipment` join.
|
|
|
103
125
|
|
|
104
126
|
---
|
|
105
127
|
|
|
128
|
+
## Equipment
|
|
129
|
+
|
|
130
|
+
Physical equipment units (trailers, containers, trucks) tracked within an organization.
|
|
131
|
+
|
|
132
|
+
| Field | Type | Notes |
|
|
133
|
+
|-------|------|-------|
|
|
134
|
+
| `equipmentId` | `int` | PK |
|
|
135
|
+
| `organizationId` | `int` | |
|
|
136
|
+
| `unitNumber` | `string` | Required; unique per org |
|
|
137
|
+
| `equipmentTypeId` | `int` | FK to EquipmentType |
|
|
138
|
+
| `equipmentStatusId` | `int` | FK to EquipmentStatus |
|
|
139
|
+
| `notes` | `string?` | |
|
|
140
|
+
| `customValues` | `Dictionary` | jsonb |
|
|
141
|
+
|
|
142
|
+
**Navigation:** `organization`, `equipmentType` (EquipmentType), `equipmentStatus` (EquipmentStatus)
|
|
143
|
+
|
|
144
|
+
**Constraints:** `(organizationId, unitNumber)` unique index.
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
## EquipmentStatus
|
|
149
|
+
|
|
150
|
+
User-defined statuses for equipment lifecycle management.
|
|
151
|
+
|
|
152
|
+
| Field | Type | Notes |
|
|
153
|
+
|-------|------|-------|
|
|
154
|
+
| `equipmentStatusId` | `int` | PK |
|
|
155
|
+
| `organizationId` | `int` | |
|
|
156
|
+
| `statusName` | `string` | Required |
|
|
157
|
+
| `statusDescription` | `string?` | |
|
|
158
|
+
| `statusStage` | `EquipmentStatusStage` enum | Required |
|
|
159
|
+
| `priority` | `int` | Required; controls display/sort order |
|
|
160
|
+
|
|
161
|
+
**Navigation:** `organization`
|
|
162
|
+
|
|
163
|
+
No customValues.
|
|
164
|
+
|
|
165
|
+
### EquipmentStatusStage Enum
|
|
166
|
+
|
|
167
|
+
| Value | Int |
|
|
168
|
+
|-------|-----|
|
|
169
|
+
| `Available` | 1 |
|
|
170
|
+
| `InUse` | 2 |
|
|
171
|
+
| `Maintenance` | 3 |
|
|
172
|
+
| `OutOfService` | 4 |
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
106
176
|
## PackageType
|
|
107
177
|
|
|
108
178
|
| Field | Type | Notes |
|
|
@@ -172,5 +242,11 @@ Encrypted key-value store for sensitive configuration (API keys, tokens, credent
|
|
|
172
242
|
**Table:** `secrets` (snake_case columns)
|
|
173
243
|
**Provider:** `PostgresSecretManager` (default) or `AzureKeyVault` via `SecretManager:Provider` config.
|
|
174
244
|
|
|
175
|
-
**GraphQL mutations:** `setSecret(organizationId, secretName, secretValue)
|
|
176
|
-
**Org scoping:** Commands validate user org membership; qualified name `org/{orgId}/{name}` is built by the command handler.
|
|
245
|
+
**GraphQL mutations:** org-scoped secrets use input-wrapper mutations: `setSecret(input: { organizationId, secretName, secretValue }) { setSecretResult { secretName } }` and `deleteSecret(input: { organizationId, secretName }) { boolean }`.
|
|
246
|
+
**Org scoping:** Commands validate user org membership; qualified name `org/{orgId}/{name}` is built by the command handler. Module `secret` fields must pass the current organization ID in the mutation input.
|
|
247
|
+
|
|
248
|
+
---
|
|
249
|
+
|
|
250
|
+
## EntityKind enum
|
|
251
|
+
|
|
252
|
+
Known entity-kind values include `Order`, `Contact`, `OrderEntity`, `AccountingTransaction`, `Calendar`, `CalendarEvent`, `CalendarAvailabilityBlock`, `Other`, and `AuditChangeEntry`. Use `AuditChangeEntry` for audit-history result rows/change-log entries rather than a primary business entity.
|
|
@@ -28,7 +28,24 @@ orders(
|
|
|
28
28
|
Entity-specific extras:
|
|
29
29
|
- `orders` — `includeDraft: Boolean` (default false)
|
|
30
30
|
|
|
31
|
-
Available root queries: `orders`, `contacts`, `commodities`, `accountingTransactions`, `jobs`, and others.
|
|
31
|
+
Available root queries: `orders`, `contacts`, `commodities`, `accountingTransactions`, `jobs`, `entityFields`, and others.
|
|
32
|
+
|
|
33
|
+
`entityFields` returns field metadata. When `entityName` is supplied, the API filters out inactive fields and duplicate field names resolve to the active definition with the highest `priority`.
|
|
34
|
+
|
|
35
|
+
`getEntityFieldsLastModified(organizationId, entityName, filter)` returns the newest matching field timestamp (or `null`) for cheap saved-view reconciliation without fetching every definition. Entity-name matching is case-insensitive.
|
|
36
|
+
|
|
37
|
+
Order quick search (`orders.search` and `orderGroupBy.search`) matches order number, bill-to, carriers, tracking numbers, order entities, commodity text/tracking numbers, and InventoryItem values linked through order commodities or child/container commodities. Inventory item matches include `sku`, `productName`, `description`, `modelNumber`, and JSON `customValues` such as color or size.
|
|
38
|
+
|
|
39
|
+
## App Module Metadata Visibility
|
|
40
|
+
|
|
41
|
+
GraphQL metadata queries hide rows attached to soft-deleted app modules:
|
|
42
|
+
|
|
43
|
+
- `appComponents` / `appComponent` require the parent `AppModule.IsDeleted != true`
|
|
44
|
+
- `appRoutes` and public route queries require the parent module to be active
|
|
45
|
+
- `appPermissions` require the parent module to be active
|
|
46
|
+
- `entityFields` exclude fields whose `EntityType.AppModule` is deleted
|
|
47
|
+
|
|
48
|
+
Do not add extra client-side `isDeleted` filters for these metadata screens unless the UI also needs to expose deleted-module diagnostics.
|
|
32
49
|
|
|
33
50
|
## Filter Syntax (Lucene Query)
|
|
34
51
|
|
|
@@ -82,11 +99,61 @@ filter: "customValues.fieldName:\"exact match\""
|
|
|
82
99
|
filter: "customValues.fieldName:NULL"
|
|
83
100
|
```
|
|
84
101
|
|
|
102
|
+
### CustomValue join expressions
|
|
103
|
+
|
|
104
|
+
When a custom value stores a foreign key to a supported entity, use `customValues.key->entity.property` to filter by a property on the joined row. Supported join aliases include `contact`, `order`, `modeOfTransportation`, `country`, `terminal`, `contactAddress`, `port`, and `vessel`. Vessel keys are integer `VesselId` values.
|
|
105
|
+
|
|
106
|
+
```
|
|
107
|
+
filter: "customValues.carrierId->contact.name:Acme*"
|
|
108
|
+
filter: "customValues.terminalId->terminal.name:Chicago*"
|
|
109
|
+
filter: "customValues.portId->port.name:Los Angeles*"
|
|
110
|
+
filter: "customValues.vesselId->vessel.name:Ever*"
|
|
111
|
+
filter: "customValues.deliveryLocationId->contactAddress.name:Warehouse*"
|
|
112
|
+
filter: "NOT customValues.returnLocationId->contactAddress.contactAddressId:NULL"
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
For list-valued scalar columns, a negated match includes rows where the list column is null; a positive match requires a non-null list. For example, `NOT tags:Priority` matches null tag lists as well as lists that do not contain `Priority`.
|
|
116
|
+
|
|
85
117
|
### Filtered collections (bracket notation)
|
|
86
118
|
```
|
|
87
119
|
filter: "children[category:CatA].name:test"
|
|
120
|
+
filter: "contactAddresses[addressType:Billing|Shipping].contactAddressId:[1 TO *]"
|
|
121
|
+
filter: "dispatchRouteStopOrders.dispatchRouteStop.dispatchRouteStopStatus.statusStage:Completed"
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Inside `[path:value]`, pipe-separated values are OR-combined after trimming and normal type conversion. Use this for exact multi-value bracket filters in both `filter` and `orderBy`, e.g. `children[category:CatA|CatB].name` or `lastTrackingEvent[eventDefinition.eventName:Departed|Delivered].eventDate`.
|
|
125
|
+
|
|
126
|
+
## Mutations
|
|
127
|
+
|
|
128
|
+
### `importContacts`
|
|
129
|
+
|
|
130
|
+
Bulk import contacts from an uploaded CSV, JSON, or XLSX file.
|
|
131
|
+
|
|
132
|
+
```graphql
|
|
133
|
+
mutation ImportContacts($input: ImportContactsInput!) {
|
|
134
|
+
importContacts(input: $input) {
|
|
135
|
+
added
|
|
136
|
+
updated
|
|
137
|
+
errors
|
|
138
|
+
}
|
|
139
|
+
}
|
|
88
140
|
```
|
|
89
141
|
|
|
142
|
+
`ImportContactsInput` fields:
|
|
143
|
+
|
|
144
|
+
| Field | Type | Notes |
|
|
145
|
+
|-------|------|-------|
|
|
146
|
+
| `organizationId` | `Int!` | Tenant scope |
|
|
147
|
+
| `fileUploadUrl` | `String!` | Uploaded file URL/path |
|
|
148
|
+
| `contactType` | `ContactType` | Optional default for new rows; row-level `ContactType` wins |
|
|
149
|
+
| `columnMappings` | `MapOfString` | Map internal field paths to inbound column headers |
|
|
150
|
+
|
|
151
|
+
Behavior notes:
|
|
152
|
+
- Matches existing contacts by `ContactId` first.
|
|
153
|
+
- Ignores primary key fields during patching.
|
|
154
|
+
- Skips nulls and empty strings on updates so blank cells do not clear existing values.
|
|
155
|
+
- Resolves nested `division.divisionName` / `Division.DivisionName` to `DivisionId` when possible.
|
|
156
|
+
|
|
90
157
|
## OrderBy Syntax
|
|
91
158
|
|
|
92
159
|
```
|
|
@@ -97,25 +164,93 @@ orderBy: "customValues.fieldName" # Custom field sort
|
|
|
97
164
|
orderBy: "orderNumber~ToInt32" # Type conversion during sort
|
|
98
165
|
```
|
|
99
166
|
|
|
100
|
-
|
|
167
|
+
Join expressions are also valid in `orderBy`, including terminal and contact-address custom-value references:
|
|
101
168
|
|
|
102
|
-
|
|
169
|
+
```
|
|
170
|
+
orderBy: "customValues.terminalId->terminal.name"
|
|
171
|
+
orderBy: "-customValues.returnLocationId->terminal.name"
|
|
172
|
+
orderBy: "customValues.deliveryLocationId->contactAddress.name"
|
|
173
|
+
orderBy: "customValues.portId->port.name"
|
|
174
|
+
orderBy: "customValues.vesselId->vessel.name"
|
|
175
|
+
orderBy: "orderSummary.totalPieces"
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### Tracking-event synthetic sort paths (Order / Commodity)
|
|
179
|
+
|
|
180
|
+
Sort the result list by the winning tracking event of each order or commodity. Use `lastTrackingEvent` for the newest matching event and `firstTrackingEvent` for the oldest matching event. Winners are selected using `COALESCE(EventDate, Created)` plus `TrackingEventId` as a tie-breaker, so SQL-level ordering and per-row resolution stay consistent.
|
|
103
181
|
|
|
104
182
|
```
|
|
105
183
|
orderBy: "-lastTrackingEvent.eventDate" # Latest event first (DESC)
|
|
106
|
-
orderBy: "lastTrackingEvent.eventDate" #
|
|
184
|
+
orderBy: "lastTrackingEvent.eventDate" # Newest-event winner, sorted ASC
|
|
185
|
+
orderBy: "firstTrackingEvent.eventDate" # Oldest-event winner, sorted ASC
|
|
107
186
|
```
|
|
108
187
|
|
|
109
188
|
Filter to a specific event type using bracket notation before sorting:
|
|
110
189
|
|
|
111
190
|
```
|
|
112
191
|
orderBy: "-lastTrackingEvent[eventDefinition.eventName:Departed].eventDate"
|
|
192
|
+
orderBy: "-lastTrackingEvent[eventDefinition.eventName:Departed|Delivered].eventDate"
|
|
193
|
+
orderBy: "firstTrackingEvent[eventDefinition.eventName:Yard Scan|Archive Yard Scan].eventDate"
|
|
113
194
|
```
|
|
114
195
|
|
|
115
196
|
- The bracket predicate (`[path:value]`) filters the `TrackingEvents` collection before the winner is picked.
|
|
197
|
+
- Pipe-separated bracket values are OR-combined, so the winner can be selected from several event definitions.
|
|
116
198
|
- Only `.eventDate` is supported as the sub-path. The expression resolves to `COALESCE(winner.EventDate, winner.Created)`, so null `EventDate` values fall back to `Created`.
|
|
117
199
|
- Works on both `orders` and `commodities` top-level queries.
|
|
118
200
|
|
|
201
|
+
### Commodity `getContact` resolver
|
|
202
|
+
|
|
203
|
+
Commodities expose `getContact(idPropertyName: String!)` to resolve a contact ID stored in `customValues`. The lookup is scoped to the commodity organization and returns `null` when the custom value is missing or not a valid integer contact ID.
|
|
204
|
+
|
|
205
|
+
```graphql
|
|
206
|
+
{
|
|
207
|
+
commodities(organizationId: 1, take: 1) {
|
|
208
|
+
items {
|
|
209
|
+
commodityId
|
|
210
|
+
getContact(idPropertyName: "shipperContactId") {
|
|
211
|
+
contactId
|
|
212
|
+
name
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
### Commodity related dispatch routes
|
|
220
|
+
|
|
221
|
+
Commodities expose `getRelatedDispatchRoutes(filter, orderBy)` for dispatch routes linked through non-draft orders attached to the commodity or any descendant commodity.
|
|
222
|
+
|
|
223
|
+
```graphql
|
|
224
|
+
commodities(organizationId: 1, filter: "commodityId:5001") {
|
|
225
|
+
items {
|
|
226
|
+
commodityId
|
|
227
|
+
getRelatedDispatchRoutes(orderBy: "routeDate") {
|
|
228
|
+
dispatchRouteId
|
|
229
|
+
name
|
|
230
|
+
routeDate
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
## Order Custom-Value Resolvers
|
|
237
|
+
|
|
238
|
+
Orders expose `getContactAddress(idPropertyName: String!)` to resolve a contact-address id stored in `customValues` into a `contactAddress` object. The lookup is organization-scoped and returns null/empty when the custom-value key is missing.
|
|
239
|
+
|
|
240
|
+
```graphql
|
|
241
|
+
orders(organizationId: 1, take: 1) {
|
|
242
|
+
items {
|
|
243
|
+
orderId
|
|
244
|
+
getContactAddress(idPropertyName: "pickupContactAddressId") {
|
|
245
|
+
contactAddressId
|
|
246
|
+
name
|
|
247
|
+
addressLine
|
|
248
|
+
cityName
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
```
|
|
253
|
+
|
|
119
254
|
## Pagination
|
|
120
255
|
|
|
121
256
|
```graphql
|
|
@@ -197,6 +332,21 @@ Arguments: `organizationId: Int!`, `filter: String`, `search: String`, `take: In
|
|
|
197
332
|
|
|
198
333
|
Filter fields: `entityName`, `primaryKey`, `userId`, `state`
|
|
199
334
|
|
|
335
|
+
### Root-level: `auditEntityHistory` grid query filters
|
|
336
|
+
|
|
337
|
+
The paged audit-history query accepts DataGrid/Lucene filter strings:
|
|
338
|
+
|
|
339
|
+
- `entityName:"Order*"` — normalized to the entity path segment; quotes, trailing `*`, and trailing `~` are stripped.
|
|
340
|
+
- `primaryKey:"123"` — narrows the S3 path to one entity key.
|
|
341
|
+
- `timestamp:["2026-05-01T00:00:00Z" TO "NOW"]` — filters parsed audit file timestamps. ISO timestamps and date-math bounds (`NOW-7DAYS`, `NOW/DAY+1DAY`) are supported.
|
|
342
|
+
- `user.fullName:"Jane*"` — resolves users by full name, first/last name, username, or email and filters by persisted `userId`.
|
|
343
|
+
|
|
344
|
+
Sorting supports `timestamp` and `-timestamp`; descending timestamp is the default.
|
|
345
|
+
|
|
346
|
+
### Entity fields lookup
|
|
347
|
+
|
|
348
|
+
`entityFields(organizationId, entityName, filter, search, orderBy)` matches `entityName` with case-insensitive SQL `ILIKE`, so exact names and `ILIKE` patterns are both accepted. `search` matches field names by substring.
|
|
349
|
+
|
|
200
350
|
### Root-level: `auditChangeSummaries` query
|
|
201
351
|
|
|
202
352
|
High-level summary of changes (grouped by change event):
|
|
@@ -283,6 +433,75 @@ High-level summary of changes (grouped by change event):
|
|
|
283
433
|
| `userName` | `String` |
|
|
284
434
|
| `email` | `String` |
|
|
285
435
|
|
|
436
|
+
## Workflow Execution Queries
|
|
437
|
+
|
|
438
|
+
### `executeWorkflow` idempotency
|
|
439
|
+
|
|
440
|
+
The `executeWorkflow` mutation accepts an optional `executionId` GUID. Use one stable ID per retryable business operation, especially offline or mobile sync actions. Replays with the same organization, workflow, and `executionId` return the stored result instead of executing the workflow again; using the same ID for another workflow or organization is rejected.
|
|
441
|
+
|
|
442
|
+
### `workflowExecutions` — paginated execution history
|
|
443
|
+
|
|
444
|
+
Returns paginated `WorkflowExecutionLog` records. Supports Lucene filter scoping, free-text search, and sorting.
|
|
445
|
+
|
|
446
|
+
```graphql
|
|
447
|
+
{
|
|
448
|
+
workflowExecutions(
|
|
449
|
+
organizationId: 1
|
|
450
|
+
filter: "workflowId:abc123 AND status:Failed"
|
|
451
|
+
search: "user@example.com"
|
|
452
|
+
orderBy: "-executedAt"
|
|
453
|
+
take: 50
|
|
454
|
+
skip: 0
|
|
455
|
+
) {
|
|
456
|
+
items {
|
|
457
|
+
executionId
|
|
458
|
+
workflowId
|
|
459
|
+
status
|
|
460
|
+
executedAt
|
|
461
|
+
userId
|
|
462
|
+
}
|
|
463
|
+
totalCount
|
|
464
|
+
pageInfo { hasNextPage hasPreviousPage }
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
```
|
|
468
|
+
|
|
469
|
+
**Arguments:**
|
|
470
|
+
|
|
471
|
+
| Argument | Type | Notes |
|
|
472
|
+
|----------|------|-------|
|
|
473
|
+
| `organizationId` | `Int!` | Required |
|
|
474
|
+
| `filter` | `String` | Lucene query syntax — scope by any field |
|
|
475
|
+
| `search` | `String` | Free-text: matches `userId`, `executionId`, `workflowId` |
|
|
476
|
+
| `orderBy` | `String` | Default: `-executedAt` (newest first) |
|
|
477
|
+
| `take` / `skip` | `Int` | Pagination |
|
|
478
|
+
|
|
479
|
+
**`filter` examples:**
|
|
480
|
+
```
|
|
481
|
+
filter: "workflowId:abc123"
|
|
482
|
+
filter: "status:Failed"
|
|
483
|
+
filter: "userId:user@example.com AND status:Completed"
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
**`search` behavior:** case-insensitive match across `userId`, `executionId` (UUID string), and `workflowId` (UUID string).
|
|
487
|
+
|
|
488
|
+
### `workflowExecution` — single execution by ID
|
|
489
|
+
|
|
490
|
+
```graphql
|
|
491
|
+
{
|
|
492
|
+
workflowExecution(
|
|
493
|
+
organizationId: 1
|
|
494
|
+
executionId: "00000000-0000-0000-0000-000000000000"
|
|
495
|
+
) {
|
|
496
|
+
executionId
|
|
497
|
+
workflowId
|
|
498
|
+
status
|
|
499
|
+
executedAt
|
|
500
|
+
userId
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
```
|
|
504
|
+
|
|
286
505
|
## Discovering Fields
|
|
287
506
|
|
|
288
507
|
**Always discover field names before building a query.** Do not guess field names — use the tools below.
|
|
@@ -326,3 +545,26 @@ GraphQL error messages reveal valid field/type names:
|
|
|
326
545
|
|
|
327
546
|
- `npx cxtms schema <name>` — shows workflow/module JSON schema fields (not GraphQL)
|
|
328
547
|
- Entity reference files (`ref-entity-*.md`) — document common fields, computed properties, and enums
|
|
548
|
+
|
|
549
|
+
|
|
550
|
+
## Contact Nested Address Resolver
|
|
551
|
+
|
|
552
|
+
Contacts expose `getContactAddresses(filter, orderBy)` for loading only the addresses needed by a selection set. Prefer this resolver over raw `contactAddresses` when the UI/workflow needs server-side filtering or deterministic sorting.
|
|
553
|
+
|
|
554
|
+
```graphql
|
|
555
|
+
query {
|
|
556
|
+
contacts(organizationId: 1, filter: "contactId:123") {
|
|
557
|
+
items {
|
|
558
|
+
contactId
|
|
559
|
+
name
|
|
560
|
+
getContactAddresses(filter: "addressType:Shipping", orderBy: "cityName") {
|
|
561
|
+
contactAddressId
|
|
562
|
+
addressType
|
|
563
|
+
cityName
|
|
564
|
+
stateCode
|
|
565
|
+
countryCode
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
```
|
|
@@ -172,10 +172,10 @@ Read the relevant category ref file when building specific component types:
|
|
|
172
172
|
|----------|-----------|------|
|
|
173
173
|
| **Layout & Structure** | `layout`, `row`, `col`, `header`, `tabs`, `toolbar`, `card`, `line`, `slot` | `skills/cxtms-module-builder/ref-components-layout.md` |
|
|
174
174
|
| **Forms & Input** | `form`, `field`, `field-collection`, `barcodeScanner` | `skills/cxtms-module-builder/ref-components-forms.md` |
|
|
175
|
-
| **Data Display** | `dataGrid`, `text`, `markup`, `badge`, `icon`, `image`, `photo`, `summary`, `diff`, `viewer`, `embed` | `skills/cxtms-module-builder/ref-components-display.md` |
|
|
175
|
+
| **Data Display** | `dataGrid`, `text`, `markup`, `badge`, `avatar`, `infoLine`, `progressBar`, `icon`, `image`, `photo`, `summary`, `diff`, `viewer`, `embed` | `skills/cxtms-module-builder/ref-components-display.md` |
|
|
176
176
|
| **Interactive & Nav** | `button`, `dropdown`, `menuButton`, `link`, `redirect`, `navbar`, `navbarItem`, `navbarLink`, `navDropdown` | `skills/cxtms-module-builder/ref-components-interactive.md` |
|
|
177
177
|
| **Data & Collections** | `collection`, `list`, `listItem`, `datasource`, `script` | `skills/cxtms-module-builder/ref-components-data.md` |
|
|
178
|
-
| **Specialized** | `calendar`, `notes`, `dashboard`, `dashboard-widget`, `widget`, `timeline`, `timeline-grid`, `oauth2` | `skills/cxtms-module-builder/ref-components-specialized.md` |
|
|
178
|
+
| **Specialized** | `calendar`, `notes`, `dashboard`, `dashboard-widget`, `widget`, `timeline`, `timeline-grid`, `planner`, `oauth2` | `skills/cxtms-module-builder/ref-components-specialized.md` |
|
|
179
179
|
|
|
180
180
|
### Templates
|
|
181
181
|
|
|
@@ -224,9 +224,10 @@ entities:
|
|
|
224
224
|
fieldType: text | number | date | boolean | ...
|
|
225
225
|
displayName: { en-US: "..." }
|
|
226
226
|
isCustomField: false
|
|
227
|
+
priority: 0 # Optional override precedence; highest active duplicate field wins
|
|
227
228
|
props:
|
|
228
229
|
allowOrderBy: true
|
|
229
|
-
allowFilter: true
|
|
230
|
+
allowFilter: true # Boolean or template expression, e.g. "{{ canFilterField }}"
|
|
230
231
|
filter: # Optional filter selector
|
|
231
232
|
component: "Contacts/Select"
|
|
232
233
|
props:
|
|
@@ -276,6 +277,7 @@ onClick:
|
|
|
276
277
|
- navigateBack: { fallback: "/home" } # Go back in history
|
|
277
278
|
- navigateBackOrClose: { fallback: "/home" } # Go back or close dialog
|
|
278
279
|
- refresh: "componentName" # Refresh a component
|
|
280
|
+
- reload: "formName" # Refetch and replace a form's initial values
|
|
279
281
|
- notification: { message: { en-US: "Saved!" }, type: success } # success|error|warning|info
|
|
280
282
|
- confirm: { title: { en-US: "Delete?" }, message: { en-US: "Are you sure?" } }
|
|
281
283
|
- mutation:
|
|
@@ -290,6 +292,7 @@ onClick:
|
|
|
290
292
|
onError: [...]
|
|
291
293
|
- setFields: { "fieldName": "{{ value }}" } # Set form field values
|
|
292
294
|
- setStore: { "key": "{{ value }}" } # Set store values
|
|
295
|
+
- setSessionStorage: { key: "isDevMode", value: "true" } # Set tab-scoped sessionStorage
|
|
293
296
|
- validateForm: {} # Trigger form validation
|
|
294
297
|
- dialog:
|
|
295
298
|
name: "dialogName"
|
|
@@ -312,6 +315,8 @@ onClick:
|
|
|
312
315
|
- consoleLog: { message: "debug info" }
|
|
313
316
|
- openBarcodeScanner: { onScan: [...] }
|
|
314
317
|
- resetDirtyState: {}
|
|
318
|
+
- sound: success # success|error|warning|scan
|
|
319
|
+
- vibrate: light # success|error|warning|light|medium|heavy
|
|
315
320
|
```
|
|
316
321
|
|
|
317
322
|
## Common Patterns
|
|
@@ -328,6 +333,7 @@ displayName:
|
|
|
328
333
|
value: "{{ fieldName }}" # Simple variable
|
|
329
334
|
value: "{{ format date L }}" # Format helper
|
|
330
335
|
value: "{{ number quantity }}" # Type cast
|
|
336
|
+
value: "{{ sessionStorage isDevMode }}" # Current-tab sessionStorage
|
|
331
337
|
value: "{{ eval items.length > 0 }}" # JavaScript expression
|
|
332
338
|
isHidden: "{{ eval !canEdit }}" # Conditional visibility
|
|
333
339
|
```
|
|
@@ -400,7 +406,7 @@ Reusable select components (e.g., `Countries/Select`, `Ports/Select`) follow thi
|
|
|
400
406
|
entity(...) { entityId name }
|
|
401
407
|
}
|
|
402
408
|
variables: { entityId: "{{number entityId}}" }
|
|
403
|
-
onEditClick: # Edit action on selected item
|
|
409
|
+
onEditClick: # Edit action on selected item (also supported on text fields)
|
|
404
410
|
- dialog:
|
|
405
411
|
component: { layout: { component: layout, children: [{ component: Entity/UpdateEntity }] } }
|
|
406
412
|
```
|
|
@@ -473,5 +479,26 @@ npx cxtms app release -m "Add warehouse locations module" --org 42
|
|
|
473
479
|
6. **Set proper entityKind** when defining entities (Order, Contact, OrderEntity, AccountingTransaction, Calendar, CalendarEvent, Other)
|
|
474
480
|
7. **DataGrid options** requires ALL properties: query, rootEntityName, entityKeys, navigationType, enableDynamicGrid, enableViews, enableSearch, enablePagination, enableColumns, enableFilter, defaultView, onRowClick
|
|
475
481
|
8. **Form component** requires `validationSchema` in props
|
|
476
|
-
9. **
|
|
477
|
-
|
|
482
|
+
9. **Adaptive (mobile/tablet/desktop) design by default** — every form and layout must work on phones (`xs`), tablets (`sm`/`md`), and desktops (`lg+`):
|
|
483
|
+
- Wrap form fields in a `layout` child with `itemDefaults.size: { xs: 12, md: 6 }` (1 col on phone, 2 col on tablet+)
|
|
484
|
+
- Long fields (`textarea`, `quill`, `attachment`, multi-line `notes`) → `size: { xs: 12 }` (always full width)
|
|
485
|
+
- Cap visible columns: max 2 on `sm`, 3 on `md`, 4 on `lg`
|
|
486
|
+
- Group large forms into `card` sections that reflow with `size: { xs: 12, lg: 6 }`
|
|
487
|
+
- **Use modern MUI Grid v2 spacing**: `columnSpacing` + `rowSpacing` (both accept responsive maps `{ xs, sm, md, lg, xl }`) per https://mui.com/material-ui/react-grid/#row-and-column-spacing. Use `spacing` only when both axes should match.
|
|
488
|
+
- For details, see `skills/cxtms-module-builder/ref-components-layout.md` ("Adaptive / Responsive Design") and `skills/cxtms-module-builder/ref-components-forms.md` ("Mobile-Friendly Form Layout")
|
|
489
|
+
10. **Do NOT use these legacy / forbidden layout patterns:**
|
|
490
|
+
- **No `cols: N`** on `layout` or `form` — non-responsive, ignored on mobile. Use `itemDefaults: { size: { xs: 12, md: <12/N> } }` instead.
|
|
491
|
+
- **No `row` component** in new code — use `layout` with responsive `columns` and `itemDefaults.size`. Existing `row` usages can stay.
|
|
492
|
+
- **No CSS class names (`className`) for layout** — neither on `layout`, `row`, nor `col`. Layouts must control sizing and spacing through `size`, `columns`, `spacing`/`rowSpacing`/`columnSpacing`, and `sx` / `containerSx` / `itemDefaults.sx`. Bootstrap-style classes (`col-md-6`, `row`, `container-fluid`) do not affect MUI Grid and have no effect.
|
|
493
|
+
- **No bare `spacing: <number>` for adaptive layouts** — use a breakpoint map (`spacing: { xs: 1, md: 3 }`) or split into `columnSpacing`/`rowSpacing`.
|
|
494
|
+
11. **Do not change `appModuleId` or `filePath`** — set correctly by CLI scaffold
|
|
495
|
+
12. **Always validate** the final YAML: `npx cxtms <file.yaml>`
|
|
496
|
+
|
|
497
|
+
|
|
498
|
+
## Reactive page titles
|
|
499
|
+
|
|
500
|
+
Route `props.title` and root component `displayName` may be template expressions. App screens parse them against the app module store and update the page header/browser title when store values change. Unauthorized-route labels intentionally use route name/path instead of parsing store-backed title templates.
|
|
501
|
+
|
|
502
|
+
## Screen route variables
|
|
503
|
+
|
|
504
|
+
Matched route parameters are raw strings and are available on the component's first render, including when a cached component definition mounts. Query-string values that look numeric are converted to numbers. When names collide, the matched route parameter wins over the query string. Screen variables also include `organizationId`, `locale`, and `currentUser`.
|
|
@@ -7,6 +7,91 @@
|
|
|
7
7
|
- Datasource component
|
|
8
8
|
- Script component
|
|
9
9
|
|
|
10
|
+
## dataGrid export identity keys
|
|
11
|
+
|
|
12
|
+
Saved views reconcile automatically with newer entity-field schemas before their first data load. Removed fields and filters are dropped, nested reference columns are resolved, and YAML-declared computed columns are preserved.
|
|
13
|
+
|
|
14
|
+
`rowStyles.conditions` accepts a semantic `variant` or template-resolved hex `color` for theme-aware row and hover tints. Matching conditions apply in order; the last value wins.
|
|
15
|
+
|
|
16
|
+
DataGrid table views support `includeEntityKeysInExport` on each view. It defaults to `true` and forces entity key/primary key fields into exports so rows can be matched by ID on re-import. Set it to `false` only when exports must omit internal IDs.
|
|
17
|
+
|
|
18
|
+
On phone layouts, DataGrid pagination already reduces inline padding and the page-size select width below the MUI `sm` breakpoint. When placing grids in dialogs or cards, avoid adding extra horizontal padding around the grid unless it is required by the surrounding design.
|
|
19
|
+
|
|
20
|
+
```yaml
|
|
21
|
+
component: dataGrid
|
|
22
|
+
name: contactsGrid
|
|
23
|
+
props:
|
|
24
|
+
options:
|
|
25
|
+
query: contacts
|
|
26
|
+
rootEntityName: Contact
|
|
27
|
+
entityKeys: [contactId]
|
|
28
|
+
views:
|
|
29
|
+
- name: allContacts
|
|
30
|
+
includeEntityKeysInExport: true
|
|
31
|
+
columns:
|
|
32
|
+
- name: name
|
|
33
|
+
- name: emailAddress
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
## dataGrid static items inherit parent variables
|
|
38
|
+
|
|
39
|
+
When `options.items` is a template expression, the grid parses it with local variables, store values, and parent component variables. Nested grids can reference parent-scope values such as collection items or datasource results:
|
|
40
|
+
|
|
41
|
+
```yaml
|
|
42
|
+
component: dataGrid
|
|
43
|
+
name: commodityLines
|
|
44
|
+
props:
|
|
45
|
+
options:
|
|
46
|
+
items: "{{ order.commodities }}"
|
|
47
|
+
entityKeys: [commodityId]
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Use `options.items` for already-loaded arrays; use `options.query` for server-side search/sort/pagination.
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## dataGrid conditional filter columns
|
|
55
|
+
|
|
56
|
+
DataGrid column `props.allowFilter` accepts either a boolean or a template expression. The filter column picker evaluates the expression with the grid's current variables before deciding whether the column can be added as a filter.
|
|
57
|
+
|
|
58
|
+
```yaml
|
|
59
|
+
component: dataGrid
|
|
60
|
+
name: ordersGrid
|
|
61
|
+
props:
|
|
62
|
+
views:
|
|
63
|
+
- name: operations
|
|
64
|
+
columns:
|
|
65
|
+
- name: orderStatusName
|
|
66
|
+
props:
|
|
67
|
+
allowFilter: "{{ currentUser.canFilterStatus }}"
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Dynamic entity fields use the same rule through `fieldDefinition.props.allowFilter`.
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## dataGrid sticky column state
|
|
75
|
+
|
|
76
|
+
DataGrid table headers let users pin columns left or right. The active view stores pinned column names under `stickyColumns.left[]` and `stickyColumns.right[]`; left-pinned columns render first, normal columns render in their original order, and right-pinned columns render last.
|
|
77
|
+
|
|
78
|
+
```yaml
|
|
79
|
+
component: dataGrid
|
|
80
|
+
name: ordersGrid
|
|
81
|
+
props:
|
|
82
|
+
views:
|
|
83
|
+
- name: operations
|
|
84
|
+
stickyColumns:
|
|
85
|
+
left: [orderNumber]
|
|
86
|
+
right: [orderStatusName]
|
|
87
|
+
columns:
|
|
88
|
+
- name: orderNumber
|
|
89
|
+
- name: customerName
|
|
90
|
+
- name: orderStatusName
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
10
95
|
## collection
|
|
11
96
|
|
|
12
97
|
Iterates over data items and renders children as templates. Supports drag-and-drop reordering.
|