@fluentcommerce/ai-skills 0.8.2 → 0.8.3

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.
Files changed (31) hide show
  1. package/README.md +79 -29
  2. package/bin/cli.mjs +278 -10
  3. package/content/cli/skills/fluent-cli-mcp-cicd/SKILL.md +6 -3
  4. package/content/cli/skills/fluent-connect/SKILL.md +1 -1
  5. package/content/dev/skills/fluent-e2e-test/SKILL.md +3 -1
  6. package/content/dev/skills/fluent-event-api/SKILL.md +1 -0
  7. package/content/dev/skills/fluent-feature-explain/SKILL.md +1 -0
  8. package/content/dev/skills/fluent-feature-plan/SKILL.md +1 -0
  9. package/content/dev/skills/fluent-mystique-builder/SKILL.md +2 -3
  10. package/content/dev/skills/fluent-mystique-scaffold/SDK_REFERENCE.md +2 -2
  11. package/content/dev/skills/fluent-mystique-scaffold/TEMPLATES.md +1 -2
  12. package/content/dev/skills/fluent-mystique-sdk-reference/SKILL.md +2 -2
  13. package/content/dev/skills/fluent-pre-deploy-check/SKILL.md +25 -0
  14. package/content/dev/skills/fluent-sourcing/SKILL.md +4 -0
  15. package/content/dev/skills/fluent-test-data/SKILL.md +4 -0
  16. package/content/dev/skills/fluent-trace/SKILL.md +16 -1
  17. package/content/dev/skills/fluent-workflow-builder/SKILL.md +11 -1
  18. package/content/dev/skills/fluent-workflow-deploy/SKILL.md +27 -0
  19. package/content/knowledge/index.md +2 -0
  20. package/content/knowledge/platform/create-order-reference.md +797 -0
  21. package/content/knowledge/platform/domain-model.md +1 -1
  22. package/content/knowledge/platform/workflow-design.md +18 -0
  23. package/content/mcp-extn/skills/fluent-mcp-core/SKILL.md +1 -1
  24. package/content/mcp-extn/skills/fluent-mcp-tools/SKILL.md +1 -1
  25. package/docs/dev-workflow.md +5 -2
  26. package/docs/fluent-ai-skills-reference.md +2 -2
  27. package/docs/getting-started.md +2 -2
  28. package/docs/use-cases.md +1 -1
  29. package/docs/workflow-reference.md +4 -2
  30. package/metadata.json +1 -1
  31. package/package.json +1 -1
@@ -0,0 +1,797 @@
1
+ ---
2
+ name: create-order-reference
3
+ scope: platform
4
+ description: "Schema-validated createOrder reference covering FulfilmentChoice linkage, ORDER::MULTI patterns, and readback query shapes."
5
+ load: on-demand
6
+ priority: high
7
+ relevant-skills: [fluent-test-data, fluent-e2e-test, fluent-trace, fluent-event-api, fluent-workflow-builder, fluent-feature-plan, fluent-feature-explain]
8
+ relevant-entities: [ORDER, FULFILMENT_CHOICE, FULFILMENT, CUSTOMER, PRODUCT, PAYMENT]
9
+ version: 1.1
10
+ last-updated: 2026-03-23
11
+ author: schema-introspection
12
+ source: live-schema + https://docs.fluentcommerce.com/by-type/create-order-using-graphql-mutation + https://docs.fluentcommerce.com/essential-knowledge/fulfilment-choice-entity
13
+ ---
14
+
15
+ # createOrder Mutation Reference
16
+
17
+ Complete reference for the `createOrder` GraphQL mutation. All input and readback examples were re-validated against the live Fluent Commerce schema on 2026-03-23, with official Fluent docs used as the behavioral cross-check for mixed-basket (`ORDER::MULTI`) patterns.
18
+
19
+ ## Mutation Signature
20
+
21
+ ```graphql
22
+ mutation($input: CreateOrderInput!) {
23
+ createOrder(input: $input) {
24
+ id ref status type
25
+ }
26
+ }
27
+ ```
28
+
29
+ ---
30
+
31
+ ## Type Tree
32
+
33
+ ### CreateOrderInput (root)
34
+
35
+ | Field | Type | Required | Notes |
36
+ |-------|------|:--------:|-------|
37
+ | `ref` | `String!` | YES | Must be unique across the account |
38
+ | `type` | `String!` | YES | Workflow routing key (e.g., `HD`, `CC`, `MULTI`) |
39
+ | `retailer` | `RetailerId!` | YES | **Wrapper object** `{ id: <ID> }` — NOT a bare ID or ref |
40
+ | `customer` | `CustomerId!` | YES | **Wrapper object** `{ id: <ID> }` — NOT a bare ID or ref |
41
+ | `items` | `[CreateOrderItemWithOrderInput]!` | YES | At least one item required |
42
+ | `fulfilmentChoice` | `CreateFulfilmentChoiceWithOrderInput` | no | **Singular** — use for single-FC orders |
43
+ | `fulfilmentChoices` | `[CreateFulfilmentChoiceWithOrderInput]` | no | **Plural** — use for split-shipment / mixed-FC orders |
44
+ | `totalPrice` | `Float` | no | Order total (excl. tax) |
45
+ | `totalTaxPrice` | `Float` | no | Tax total |
46
+ | `billingAddress` | `CreateOrderBillingAddressInput` | no | Billing address (all fields optional) |
47
+ | `payment` | `PaymentKey` | no | `{ ref: "PAY-REF" }` — links to existing Payment entity |
48
+ | `financialTransactions` | `[CreateFinancialTransactionWithOrderInput]` | no | Inline payment records |
49
+ | `attributes` | `[AttributeInput]` | no | Custom metadata |
50
+ | `ref2` | `String` | no | Secondary reference (must be unique if provided) |
51
+ | `tag1` / `tag2` / `tag3` | `String` | no | Searchable tags |
52
+
53
+ ### RetailerId / CustomerId (wrapper objects)
54
+
55
+ ```json
56
+ "retailer": { "id": "123" },
57
+ "customer": { "id": "456" }
58
+ ```
59
+
60
+ These are **wrapper input types** with a single `id: ID!` field. Reuse the exact `id` value returned by discovery and do **not** substitute `ref`. GraphQL `ID` accepts scalar ID values, and Fluent queries commonly return them as strings. Discover via:
61
+ - Retailer: `retailers(first:1) { edges { node { id ref tradingName } } }`
62
+ - Customer: `customers(first:1, username: {...}) { edges { node { id firstName lastName } } }`
63
+
64
+ ### CreateOrderItemWithOrderInput
65
+
66
+ | Field | Type | Required | Notes |
67
+ |-------|------|:--------:|-------|
68
+ | `ref` | `String!` | YES | Unique within the order |
69
+ | `productRef` | `String!` | YES | Must exist in the catalogue |
70
+ | `quantity` | `Int!` | YES | Must be >= 1 |
71
+ | `productCatalogueRef` | `String` | no | Which catalogue contains the product. Omit only if using the compatibility catalogue |
72
+ | `fulfilmentChoiceRef` | `String` | no | **Links this item to a specific FulfilmentChoice by ref**. Required for split-shipment orders |
73
+ | `paidPrice` | `Float` | no | Price paid (excl. tax) |
74
+ | `price` | `Float` | no | Unit price |
75
+ | `totalPrice` | `Float` | no | Line total |
76
+ | `taxPrice` | `Float` | no | Tax per unit |
77
+ | `totalTaxPrice` | `Float` | no | Line tax total |
78
+ | `taxType` | `String` | no | `GST`, `VAT`, or `EXCLTAX` |
79
+ | `currency` | `String` | no | ISO 4217 (e.g., `AUD`, `USD`, `EUR`) |
80
+ | `attributes` | `[AttributeInput]` | no | Item-level metadata |
81
+
82
+ ### CreateFulfilmentChoiceWithOrderInput
83
+
84
+ | Field | Type | Required | Notes |
85
+ |-------|------|:--------:|-------|
86
+ | `deliveryType` | `String!` | YES | `STANDARD`, `EXPRESS`, `OVERNIGHT`, `3HOURS`, `NONE` (CC) |
87
+ | `ref` | `String` | no | Recommended — items link via `fulfilmentChoiceRef` |
88
+ | `type` | `String` | no | `HD`, `CC`, `SFS` (ship-from-store), or custom |
89
+ | `fulfilmentType` | `String` | no | Additional classification |
90
+ | `pickupLocationRef` | `String` | no | **Required for CC** — ref of the pickup store/location |
91
+ | `deliveryAddress` | `CreateCustomerAddressInput` | no | **Required for HD** — where to ship |
92
+ | `deliveryFirstName` | `String` | no | Recipient first name |
93
+ | `deliveryLastName` | `String` | no | Recipient last name |
94
+ | `deliveryContact` | `String` | no | Recipient phone |
95
+ | `deliveryEmail` | `String` | no | Recipient email |
96
+ | `deliverAfter` | `DateTime` | no | Earliest delivery window |
97
+ | `deliverBefore` | `DateTime` | no | Latest delivery window |
98
+ | `dispatchOn` | `DateTime` | no | Planned dispatch date |
99
+ | `currency` | `String` | no | ISO 4217 |
100
+ | `fulfilmentPrice` | `Float` | no | Shipping/collection fee |
101
+ | `fulfilmentTaxPrice` | `Float` | no | Tax on shipping fee |
102
+ | `deliveryInstruction` | `String` | no | Max 250 chars |
103
+ | `attributes` | `[AttributeInput]` | no | FC-level metadata (sourcing hints, consignment prefix, etc.) |
104
+
105
+ ### CreateCustomerAddressInput (delivery address)
106
+
107
+ | Field | Type | Required | Notes |
108
+ |-------|------|:--------:|-------|
109
+ | `ref` | `String!` | YES | Unique address reference |
110
+ | `name` | `String` | no | Recipient name |
111
+ | `companyName` | `String` | no | Max 255 chars |
112
+ | `street` | `String` | no | Max 255 chars |
113
+ | `street2` | `String` | no | Max 255 chars |
114
+ | `city` | `String` | no | Max 255 chars |
115
+ | `state` | `String` | no | Max 200 chars |
116
+ | `postcode` | `String` | no | Max 100 chars |
117
+ | `region` | `String` | no | Max 250 chars |
118
+ | `country` | `String` | no | Max 100 chars (ISO 3166) |
119
+ | `latitude` | `Float` | no | |
120
+ | `longitude` | `Float` | no | |
121
+ | `timeZone` | `String` | no | Max 32 chars |
122
+ | `email` | `String` | no | Max 255 chars |
123
+
124
+ ### CreateFinancialTransactionWithOrderInput
125
+
126
+ | Field | Type | Required | Notes |
127
+ |-------|------|:--------:|-------|
128
+ | `ref` | `String!` | YES | Unique transaction ref |
129
+ | `type` | `String!` | YES | Max 25 chars. e.g., `PAYMENT`, `REFUND` |
130
+ | `amount` | `Float!` | YES | Transaction amount |
131
+ | `currency` | `String!` | YES | ISO 4217 (3 chars) |
132
+ | `paymentMethod` | `String!` | YES | e.g., `CREDIT_CARD`, `PAYPAL`, `CASH` |
133
+ | `scale` | `Int` | no | Decimal places for unscaled value |
134
+ | `unscaledValue` | `Int` | no | Amount without decimals (for precision) |
135
+ | `externalTransactionCode` | `String` | no | External payment gateway code |
136
+ | `externalTransactionId` | `String` | no | External payment gateway ID |
137
+ | `cardType` | `String` | no | e.g., `VISA`, `MASTERCARD` |
138
+ | `paymentProvider` | `String` | no | e.g., `STRIPE`, `ADYEN` |
139
+ | `attributes` | `[AttributeInput]` | no | |
140
+
141
+ ### AttributeInput
142
+
143
+ | Field | Type | Required | Notes |
144
+ |-------|------|:--------:|-------|
145
+ | `name` | `String!` | YES | Attribute key |
146
+ | `type` | `String!` | YES | `STRING`, `INTEGER`, `FLOAT`, `BOOLEAN`, `JSON` |
147
+ | `value` | `Json!` | YES | **Any valid JSON value** — see gotchas below |
148
+
149
+ ---
150
+
151
+ ## The Item-to-FulfilmentChoice Link
152
+
153
+ This is the most misunderstood relationship in the createOrder mutation.
154
+
155
+ ```
156
+ Order
157
+ |
158
+ +-- fulfilmentChoice (singular) OR fulfilmentChoices[] (plural)
159
+ | ref: "FC-HD-001" [{ ref: "FC-HD-001" }, { ref: "FC-CC-001" }]
160
+ |
161
+ +-- items[]
162
+ item[0].fulfilmentChoiceRef: "FC-HD-001" --> links to FC-HD-001
163
+ item[1].fulfilmentChoiceRef: "FC-CC-001" --> links to FC-CC-001
164
+ ```
165
+
166
+ **Rules:**
167
+ 1. Use `fulfilmentChoice` (singular) when ALL items share one fulfilment choice
168
+ 2. Use `fulfilmentChoices` (plural) when items have DIFFERENT fulfilment choices
169
+ 3. Each item's `fulfilmentChoiceRef` must match a `fulfilmentChoice.ref` — this is the join key
170
+ 4. If using singular `fulfilmentChoice`, items can omit `fulfilmentChoiceRef` (auto-linked)
171
+ 5. If using plural `fulfilmentChoices`, every item MUST have `fulfilmentChoiceRef`
172
+
173
+ ---
174
+
175
+ ## Input Contract vs Query Contract
176
+
177
+ The most common `ORDER::MULTI` mistake is mixing up the **mutation input join key** with the **query readback shape**.
178
+
179
+ ### Input-side linkage (`createOrder`)
180
+
181
+ - Use `items[].fulfilmentChoiceRef` to link an order item to a fulfilment choice by `ref`
182
+ - Use `fulfilmentChoice` for one-FC orders and `fulfilmentChoices[]` for mixed or split orders
183
+ - Use `items[].productRef` and `items[].productCatalogueRef` on input
184
+
185
+ ### Readback-side linkage (`orders` / `orderById`)
186
+
187
+ - Query `order.fulfilmentChoice { ... }` for the primary/singular FC
188
+ - Query `order.fulfilmentChoices { edges { node { ... } } }` for all FCs on mixed baskets
189
+ - Query `order.items { edges { node { fulfilmentChoice { ... } } } }` to verify which FC an item is linked to
190
+ - Query `order.items { edges { node { product { ... } } } }` to read the resolved product
191
+ - `Fulfilment.fulfilmentChoiceRef` remains queryable and is useful when tracing downstream fulfilment creation
192
+
193
+ **Important:** `fulfilmentChoiceRef` is a valid field on `CreateOrderItemWithOrderInput`, but on the validated readback schema `OrderItem` exposes `fulfilmentChoice { ... }`, not an `OrderItem.fulfilmentChoiceRef` scalar.
194
+
195
+ ### Validated linkage query
196
+
197
+ ```graphql
198
+ query orderLinkage($ref: [String!]) {
199
+ orders(ref: $ref, first: 1) {
200
+ edges {
201
+ node {
202
+ id
203
+ ref
204
+ type
205
+ status
206
+ fulfilmentChoice {
207
+ id
208
+ ref
209
+ type
210
+ status
211
+ deliveryType
212
+ }
213
+ fulfilmentChoices {
214
+ edges {
215
+ node {
216
+ id
217
+ ref
218
+ type
219
+ status
220
+ deliveryType
221
+ items {
222
+ edges {
223
+ node {
224
+ id
225
+ ref
226
+ quantity
227
+ order {
228
+ id
229
+ ref
230
+ }
231
+ fulfilmentChoice {
232
+ id
233
+ ref
234
+ type
235
+ status
236
+ }
237
+ }
238
+ }
239
+ }
240
+ fulfilments {
241
+ edges {
242
+ node {
243
+ id
244
+ ref
245
+ status
246
+ fulfilmentChoiceRef
247
+ }
248
+ }
249
+ }
250
+ }
251
+ }
252
+ }
253
+ items {
254
+ edges {
255
+ node {
256
+ id
257
+ ref
258
+ quantity
259
+ status
260
+ fulfilmentChoice {
261
+ id
262
+ ref
263
+ type
264
+ status
265
+ deliveryType
266
+ }
267
+ product {
268
+ id
269
+ ref
270
+ }
271
+ }
272
+ }
273
+ }
274
+ }
275
+ }
276
+ }
277
+ }
278
+ ```
279
+
280
+ Use this readback query after `createOrder` to prove the item-to-FC mapping actually materialized the way the mutation input intended.
281
+
282
+ ---
283
+
284
+ ## Pattern 1: Simple Home Delivery (HD)
285
+
286
+ Single fulfilment choice, all items shipped to one address.
287
+
288
+ ```graphql
289
+ mutation createHDOrder($input: CreateOrderInput!) {
290
+ createOrder(input: $input) {
291
+ id ref status type
292
+ }
293
+ }
294
+ ```
295
+
296
+ ```json
297
+ {
298
+ "input": {
299
+ "ref": "ORD-HD-20260323-001",
300
+ "type": "HD",
301
+ "retailer": { "id": 123 },
302
+ "customer": { "id": 456 },
303
+ "items": [
304
+ {
305
+ "ref": "ORD-HD-20260323-001-ITEM-1",
306
+ "productRef": "PROD-SKU-001",
307
+ "productCatalogueRef": "MASTER_CATALOGUE",
308
+ "quantity": 2,
309
+ "paidPrice": 29.99,
310
+ "currency": "AUD",
311
+ "taxType": "GST",
312
+ "taxPrice": 2.73
313
+ },
314
+ {
315
+ "ref": "ORD-HD-20260323-001-ITEM-2",
316
+ "productRef": "PROD-SKU-002",
317
+ "productCatalogueRef": "MASTER_CATALOGUE",
318
+ "quantity": 1,
319
+ "paidPrice": 49.99,
320
+ "currency": "AUD"
321
+ }
322
+ ],
323
+ "fulfilmentChoice": {
324
+ "ref": "ORD-HD-20260323-001-FC",
325
+ "type": "HD",
326
+ "deliveryType": "STANDARD",
327
+ "deliveryAddress": {
328
+ "ref": "ORD-HD-20260323-001-ADDR",
329
+ "name": "Jane Smith",
330
+ "street": "42 Wallaby Way",
331
+ "city": "Sydney",
332
+ "state": "NSW",
333
+ "postcode": "2000",
334
+ "country": "AU"
335
+ },
336
+ "attributes": [
337
+ { "name": "sourcingLocationRef", "type": "STRING", "value": "LOC-WH-001" },
338
+ { "name": "consignmentPrefix", "type": "STRING", "value": "A_" }
339
+ ]
340
+ },
341
+ "totalPrice": 109.97,
342
+ "totalTaxPrice": 10.00
343
+ }
344
+ }
345
+ ```
346
+
347
+ **When to use:** Standard ecommerce order. One delivery address, one or more items, all shipped together.
348
+
349
+ ---
350
+
351
+ ## Pattern 2: Click & Collect (CC)
352
+
353
+ Customer picks up from a store. No delivery address — `pickupLocationRef` instead.
354
+
355
+ ```json
356
+ {
357
+ "input": {
358
+ "ref": "ORD-CC-20260323-001",
359
+ "type": "CC",
360
+ "retailer": { "id": 123 },
361
+ "customer": { "id": 456 },
362
+ "items": [
363
+ {
364
+ "ref": "ORD-CC-20260323-001-ITEM-1",
365
+ "productRef": "PROD-SKU-001",
366
+ "productCatalogueRef": "MASTER_CATALOGUE",
367
+ "quantity": 1,
368
+ "paidPrice": 29.99
369
+ }
370
+ ],
371
+ "fulfilmentChoice": {
372
+ "ref": "ORD-CC-20260323-001-FC",
373
+ "type": "CC",
374
+ "deliveryType": "NONE",
375
+ "pickupLocationRef": "LOC-STORE-001"
376
+ }
377
+ }
378
+ }
379
+ ```
380
+
381
+ **Key differences from HD:**
382
+ - `deliveryType`: `"NONE"` (customer picks up, no shipping)
383
+ - `pickupLocationRef`: ref of the store/location (must exist)
384
+ - No `deliveryAddress`
385
+
386
+ ---
387
+
388
+ ## Pattern 3: Split Shipment (MULTI with multiple FCs)
389
+
390
+ Items fulfilled from different sources. Uses `fulfilmentChoices` (plural) and each item links via `fulfilmentChoiceRef`.
391
+
392
+ ```json
393
+ {
394
+ "input": {
395
+ "ref": "ORD-SPLIT-20260323-001",
396
+ "type": "MULTI",
397
+ "retailer": { "id": 123 },
398
+ "customer": { "id": 456 },
399
+ "items": [
400
+ {
401
+ "ref": "ORD-SPLIT-20260323-001-ITEM-1",
402
+ "productRef": "PROD-SKU-001",
403
+ "productCatalogueRef": "MASTER_CATALOGUE",
404
+ "quantity": 1,
405
+ "fulfilmentChoiceRef": "ORD-SPLIT-20260323-001-FC-HD"
406
+ },
407
+ {
408
+ "ref": "ORD-SPLIT-20260323-001-ITEM-2",
409
+ "productRef": "PROD-SKU-002",
410
+ "productCatalogueRef": "MASTER_CATALOGUE",
411
+ "quantity": 1,
412
+ "fulfilmentChoiceRef": "ORD-SPLIT-20260323-001-FC-HD"
413
+ },
414
+ {
415
+ "ref": "ORD-SPLIT-20260323-001-ITEM-3",
416
+ "productRef": "PROD-SKU-003",
417
+ "productCatalogueRef": "MASTER_CATALOGUE",
418
+ "quantity": 1,
419
+ "fulfilmentChoiceRef": "ORD-SPLIT-20260323-001-FC-CC"
420
+ }
421
+ ],
422
+ "fulfilmentChoices": [
423
+ {
424
+ "ref": "ORD-SPLIT-20260323-001-FC-HD",
425
+ "type": "HD",
426
+ "deliveryType": "STANDARD",
427
+ "deliveryAddress": {
428
+ "ref": "ORD-SPLIT-20260323-001-ADDR",
429
+ "name": "Jane Smith",
430
+ "street": "42 Wallaby Way",
431
+ "city": "Sydney",
432
+ "postcode": "2000",
433
+ "country": "AU"
434
+ }
435
+ },
436
+ {
437
+ "ref": "ORD-SPLIT-20260323-001-FC-CC",
438
+ "type": "CC",
439
+ "deliveryType": "NONE",
440
+ "pickupLocationRef": "LOC-STORE-001"
441
+ }
442
+ ]
443
+ }
444
+ }
445
+ ```
446
+
447
+ **Critical rules for split shipment:**
448
+ - Use `fulfilmentChoices` (plural array), NOT `fulfilmentChoice` (singular)
449
+ - Every item MUST have `fulfilmentChoiceRef` matching one of the FC refs
450
+ - Items 1 & 2 ship to home (HD), item 3 goes to store pickup (CC)
451
+
452
+ ### Variant: Multi-location HD split within ORDER::MULTI
453
+
454
+ `ORDER::MULTI` does **not** mean each fulfilment choice must have a different FC `type`. A common pattern is one mixed-basket order with multiple `HD` fulfilment choices, each routed to a different sourcing location.
455
+
456
+ ```json
457
+ {
458
+ "input": {
459
+ "ref": "ORD-MULTI-HD-20260323-001",
460
+ "type": "MULTI",
461
+ "retailer": { "id": "123" },
462
+ "customer": { "id": "456" },
463
+ "items": [
464
+ {
465
+ "ref": "ORD-MULTI-HD-20260323-001-ITEM-1",
466
+ "productRef": "PROD-SKU-001",
467
+ "productCatalogueRef": "MASTER_CATALOGUE",
468
+ "quantity": 1,
469
+ "fulfilmentChoiceRef": "ORD-MULTI-HD-20260323-001-FC-HD-WH1"
470
+ },
471
+ {
472
+ "ref": "ORD-MULTI-HD-20260323-001-ITEM-2",
473
+ "productRef": "PROD-SKU-002",
474
+ "productCatalogueRef": "MASTER_CATALOGUE",
475
+ "quantity": 1,
476
+ "fulfilmentChoiceRef": "ORD-MULTI-HD-20260323-001-FC-HD-WH2"
477
+ }
478
+ ],
479
+ "fulfilmentChoices": [
480
+ {
481
+ "ref": "ORD-MULTI-HD-20260323-001-FC-HD-WH1",
482
+ "type": "HD",
483
+ "deliveryType": "STANDARD",
484
+ "deliveryAddress": {
485
+ "ref": "ORD-MULTI-HD-20260323-001-ADDR-1",
486
+ "name": "Jane Smith",
487
+ "street": "42 Wallaby Way",
488
+ "city": "Sydney",
489
+ "postcode": "2000",
490
+ "country": "AU"
491
+ },
492
+ "attributes": [
493
+ { "name": "sourcingLocationRef", "type": "STRING", "value": "LOC-WH-001" }
494
+ ]
495
+ },
496
+ {
497
+ "ref": "ORD-MULTI-HD-20260323-001-FC-HD-WH2",
498
+ "type": "HD",
499
+ "deliveryType": "STANDARD",
500
+ "deliveryAddress": {
501
+ "ref": "ORD-MULTI-HD-20260323-001-ADDR-2",
502
+ "name": "Jane Smith",
503
+ "street": "42 Wallaby Way",
504
+ "city": "Sydney",
505
+ "postcode": "2000",
506
+ "country": "AU"
507
+ },
508
+ "attributes": [
509
+ { "name": "sourcingLocationRef", "type": "STRING", "value": "LOC-WH-002" }
510
+ ]
511
+ }
512
+ ]
513
+ }
514
+ }
515
+ ```
516
+
517
+ **Why this matters:**
518
+ - The order `type` stays `MULTI`
519
+ - Each FC can still be `HD`, `CC`, `SFS`, or another implementation-specific type
520
+ - The split is expressed by multiple FC refs plus item-level `fulfilmentChoiceRef`
521
+ - Source-specific routing belongs on the FC, commonly via attributes such as `sourcingLocationRef`
522
+
523
+ ---
524
+
525
+ ## Pattern 4: Order with Financial Transactions
526
+
527
+ Include payment records inline with the order.
528
+
529
+ ```json
530
+ {
531
+ "input": {
532
+ "ref": "ORD-PAY-20260323-001",
533
+ "type": "HD",
534
+ "retailer": { "id": 123 },
535
+ "customer": { "id": 456 },
536
+ "items": [
537
+ {
538
+ "ref": "ORD-PAY-20260323-001-ITEM-1",
539
+ "productRef": "PROD-SKU-001",
540
+ "productCatalogueRef": "MASTER_CATALOGUE",
541
+ "quantity": 1,
542
+ "paidPrice": 99.99
543
+ }
544
+ ],
545
+ "fulfilmentChoice": {
546
+ "ref": "ORD-PAY-20260323-001-FC",
547
+ "type": "HD",
548
+ "deliveryType": "EXPRESS",
549
+ "fulfilmentPrice": 12.00,
550
+ "deliveryAddress": {
551
+ "ref": "ORD-PAY-20260323-001-ADDR",
552
+ "street": "100 George St",
553
+ "city": "Sydney",
554
+ "postcode": "2000",
555
+ "country": "AU"
556
+ }
557
+ },
558
+ "totalPrice": 111.99,
559
+ "financialTransactions": [
560
+ {
561
+ "ref": "ORD-PAY-20260323-001-FT-1",
562
+ "type": "PAYMENT",
563
+ "amount": 111.99,
564
+ "currency": "AUD",
565
+ "paymentMethod": "CREDIT_CARD",
566
+ "cardType": "VISA",
567
+ "paymentProvider": "STRIPE",
568
+ "externalTransactionId": "pi_3abc123"
569
+ }
570
+ ],
571
+ "billingAddress": {
572
+ "name": "Jane Smith",
573
+ "street": "100 George St",
574
+ "city": "Sydney",
575
+ "postcode": "2000",
576
+ "country": "AU"
577
+ }
578
+ }
579
+ }
580
+ ```
581
+
582
+ ---
583
+
584
+ ## Pattern 5: Minimal Order (fewest possible fields)
585
+
586
+ Absolute minimum required fields. Useful for smoke tests.
587
+
588
+ ```json
589
+ {
590
+ "input": {
591
+ "ref": "ORD-MIN-20260323-001",
592
+ "type": "HD",
593
+ "retailer": { "id": 123 },
594
+ "customer": { "id": 456 },
595
+ "items": [
596
+ {
597
+ "ref": "ORD-MIN-20260323-001-ITEM-1",
598
+ "productRef": "PROD-SKU-001",
599
+ "quantity": 1
600
+ }
601
+ ],
602
+ "fulfilmentChoice": {
603
+ "deliveryType": "STANDARD"
604
+ }
605
+ }
606
+ }
607
+ ```
608
+
609
+ Note: `fulfilmentChoice.ref`, `productCatalogueRef`, and `deliveryAddress` are all optional. This creates a valid order but with minimal data — workflows may require attributes that aren't present.
610
+
611
+ ---
612
+
613
+ ## Return Fields
614
+
615
+ **Input field names differ from return field names.** The mutation input uses `amount` but the return type uses `total`. The input uses `productRef` but the return type uses `product { ... on VariantProduct { ref } }`.
616
+
617
+ ### Validated return field selection
618
+
619
+ ```graphql
620
+ mutation createOrder($input: CreateOrderInput!) {
621
+ createOrder(input: $input) {
622
+ # Order fields
623
+ id ref status type totalPrice totalTaxPrice
624
+
625
+ # Items — product is a UNION type, needs inline fragment
626
+ items {
627
+ edges {
628
+ node {
629
+ id ref quantity status
630
+ product { ... on VariantProduct { ref name } }
631
+ fulfilmentChoice { id ref type }
632
+ }
633
+ }
634
+ }
635
+
636
+ # Singular FC return
637
+ fulfilmentChoice {
638
+ id ref type deliveryType
639
+ deliveryAddress { ref name street city postcode country }
640
+ }
641
+
642
+ # Plural FC return (for split orders)
643
+ fulfilmentChoices {
644
+ edges {
645
+ node { id ref type deliveryType pickupLocationRef }
646
+ }
647
+ }
648
+
649
+ # Financial
650
+ financialTransactions {
651
+ edges {
652
+ node { id ref type total currency paymentMethod status }
653
+ }
654
+ }
655
+
656
+ # Billing
657
+ billingAddress { name street city postcode country }
658
+
659
+ # Related entities
660
+ customer { id firstName lastName primaryEmail }
661
+ retailer { id tradingName }
662
+ }
663
+ }
664
+ ```
665
+
666
+ ### Input vs Return field name differences
667
+
668
+ | Concept | Input field name | Return field name |
669
+ |---------|-----------------|-------------------|
670
+ | Product reference | `items[].productRef` (string) | `items[].product { ... on VariantProduct { ref } }` (union) |
671
+ | Transaction amount | `financialTransactions[].amount` (Float) | `financialTransactions[].total` (Float) |
672
+ | Fulfilment choices | `fulfilmentChoices[]` (flat array input) | `fulfilmentChoices { edges { node { ... } } }` (Relay connection) |
673
+ | Items | `items[]` (flat array input) | `items { edges { node { ... } } }` (Relay connection) |
674
+
675
+ ---
676
+
677
+ ## Gotchas
678
+
679
+ ### 1. RetailerId and CustomerId are wrapper objects
680
+
681
+ ```json
682
+ // WRONG — bare integer
683
+ "retailer": 123,
684
+ "customer": 456
685
+
686
+ // WRONG — string ref
687
+ "retailer": "RET-001",
688
+ "customer": "CUST-001"
689
+
690
+ // CORRECT — wrapper object with numeric ID
691
+ "retailer": { "id": 123 },
692
+ "customer": { "id": 456 }
693
+ ```
694
+
695
+ ### 2. AttributeInput.value is `Json!` scalar
696
+
697
+ The `value` field accepts **any valid JSON value** — string, number, boolean, object, array. When passing via GraphQL variables (JSON), just use the native JSON type:
698
+
699
+ ```json
700
+ { "name": "colour", "type": "STRING", "value": "red" }
701
+ { "name": "weight", "type": "FLOAT", "value": 2.5 }
702
+ { "name": "express", "type": "BOOLEAN", "value": true }
703
+ { "name": "tags", "type": "JSON", "value": ["fragile", "oversized"] }
704
+ { "name": "metadata", "type": "JSON", "value": { "source": "web", "channel": "AU" } }
705
+ ```
706
+
707
+ ### 3. fulfilmentChoice (singular) vs fulfilmentChoices (plural)
708
+
709
+ | Scenario | Use | fulfilmentChoiceRef on items? |
710
+ |----------|-----|:----------------------------:|
711
+ | All items → one delivery | `fulfilmentChoice` (singular) | Optional (auto-linked) |
712
+ | Items → different deliveries | `fulfilmentChoices` (plural array) | **Required on every item** |
713
+ | Mixed HD + CC | `fulfilmentChoices` (plural array) | **Required on every item** |
714
+
715
+ Never use both `fulfilmentChoice` and `fulfilmentChoices` on the same order.
716
+
717
+ ### 4. Item refs must be unique within the order
718
+
719
+ ```json
720
+ // WRONG — duplicate refs
721
+ { "ref": "ITEM-1", "productRef": "SKU-A", "quantity": 1 },
722
+ { "ref": "ITEM-1", "productRef": "SKU-B", "quantity": 1 }
723
+
724
+ // CORRECT — unique refs
725
+ { "ref": "ORD-001-ITEM-1", "productRef": "SKU-A", "quantity": 1 },
726
+ { "ref": "ORD-001-ITEM-2", "productRef": "SKU-B", "quantity": 1 }
727
+ ```
728
+
729
+ **Best practice:** Prefix item refs with the order ref to guarantee uniqueness.
730
+
731
+ ### 5. deliveryAddress.ref is required when providing a delivery address
732
+
733
+ ```json
734
+ // WRONG — ref missing
735
+ "deliveryAddress": { "street": "42 Wallaby Way", "city": "Sydney" }
736
+
737
+ // CORRECT
738
+ "deliveryAddress": { "ref": "ORD-001-ADDR", "street": "42 Wallaby Way", "city": "Sydney" }
739
+ ```
740
+
741
+ ### 6. productCatalogueRef should always be provided
742
+
743
+ While technically optional (falls back to compatibility catalogue), omitting it is error-prone. Always pair `productRef` with `productCatalogueRef` to ensure the product is found in the correct catalogue.
744
+
745
+ ### 7. The order type drives workflow routing
746
+
747
+ The `type` field (e.g., `HD`, `CC`, `MULTI`) determines which workflow processes the order. The Orchestration Engine matches `type` against workflow trigger conditions. Using an unrecognized type means no workflow picks it up.
748
+
749
+ Common types:
750
+ - `HD` — Home Delivery
751
+ - `CC` — Click & Collect
752
+ - `MULTI` — Mixed fulfilment (split shipment)
753
+ - Custom types defined by the implementation
754
+
755
+ ### 8. DateTime format
756
+
757
+ `deliverAfter`, `deliverBefore`, `dispatchOn` use ISO 8601: `"2026-03-23T10:00:00.000Z"`
758
+
759
+ ### 9. Relay connections on return but flat arrays on input
760
+
761
+ Input takes flat JSON arrays (`items: [...]`). Return fields use Relay connections (`items { edges { node { ... } } }`). Don't confuse the two.
762
+
763
+ ---
764
+
765
+ ## Discovery Queries (prerequisites)
766
+
767
+ Before calling `createOrder`, discover the required IDs and refs:
768
+
769
+ ```graphql
770
+ # Retailer ID
771
+ query { retailers(first: 1) { edges { node { id ref tradingName } } } }
772
+
773
+ # Customer ID
774
+ query { customers(first: 1) { edges { node { id firstName lastName primaryEmail } } } }
775
+
776
+ # Product refs + catalogue
777
+ query { variantProducts(first: 5, catalogue: { ref: "MASTER_CATALOGUE" }) {
778
+ edges { node { ref name status catalogue { ref } } }
779
+ } }
780
+
781
+ # Location refs (for CC pickup or sourcing)
782
+ query { locations(first: 10, type: "STORE", status: "ACTIVE") {
783
+ edges { node { id ref name type status } }
784
+ } }
785
+ ```
786
+
787
+ ---
788
+
789
+ ## Workflow Side Effects
790
+
791
+ Calling `createOrder` automatically fires a `CREATE` event on the new Order entity. The Orchestration Engine then:
792
+
793
+ 1. Matches the event to an ORDER workflow (by `type` or catch-all)
794
+ 2. Executes the `CREATED` status ruleset
795
+ 3. Typically triggers sourcing, fulfilment creation, inventory reservation
796
+
797
+ No manual event dispatch is needed after `createOrder` — the workflow starts automatically.