Building an Order Lifecycle Flow
This lesson walks through designing and implementing a complete Flow state machine for a Brokerage order. You will plan the lifecycle, define states with hierarchy, configure manual and auto transitions, write aggregations, and add notifications on key state entries.
Step 1 — Plan the Lifecycle
Before writing any YAML, sketch the states and transitions on paper (or a whiteboard). A Brokerage order lifecycle might look like this:
1Draft2 │ (manual: submit)3 ▼4Submitted5 │ (auto: amount < 5000 → auto_approve)6 │ (auto: amount >= 5000 → send_for_review)7 ▼ ▼8Approved UnderReview9 │ │ (manual: approve_review)10 │◄───────────────────┘11 │ (manual: dispatch)12 ▼13InTransit [parent]14 ├── AwaitingPickup15 │ │ (auto: carrier confirms → mark_picked_up)16 ▼ ▼17 PickedUp18 │ (auto: allItemsReceived → complete_delivery)19 ▼20 Delivered [final]2122 * → Cancelled [final] (manual: cancel — from any non-final state)
Key design decisions:
Draftis the initial state (new orders with null status resolve here).InTransitis a parent — entities live inAwaitingPickuporPickedUp, not inInTransitdirectly.DeliveredandCancelledare final — no further transitions.- The wildcard
canceltransition usesfrom: "*"so it applies everywhere. - An
allItemsReceivedaggregation controls thePickedUp → Deliveredauto-transition.
Step 2 — Scaffold the File
Always start from the CLI:
bash1npx cxtms create workflow order-lifecycle-flow --template basic --feature orders2# Creates: features/orders/workflows/order-lifecycle-flow.yaml
Open the generated file, then:
- Set
workflowType: Flowin theworkflowsection. - Remove the
activitiesandtriggerssections. - Add
entity,states,transitions, andaggregationssections.
Step 3 — Define the Entity Section
yaml1entity:2 name: Order3 type: Brokerage4 includes:5 - Commodities6 - Customer7 - Charges8 query: "{ commodities { id, receivedQuantity, quantity }, customer { name, email } }"
The includes list tells the engine to eager-load Commodities, Customer, and Charges so they are available in conditions and step template expressions without additional queries.
Step 4 — Define States
yaml1states:2 - name: Draft3 stage: Entry4 isInitial: true56 - name: Submitted7 stage: Processing89 - name: UnderReview10 stage: Processing1112 - name: Approved13 stage: Processing1415 - name: InTransit16 stage: Processing17 onExit:18 - task: "Utilities/Log@1"19 inputs:20 message: "Order leaving InTransit"2122 - name: AwaitingPickup23 parent: InTransit2425 - name: PickedUp26 parent: InTransit2728 - name: Delivered29 stage: Complete30 isFinal: true31 requireConfirmation: true32 onEnter:33 - task: "Email/Send"34 inputs:35 template: order_delivered_notification36 to: "{{ entity.customer.email }}"3738 - name: Cancelled39 stage: Complete40 isFinal: true41 onEnter:42 - task: "Utilities/Log@1"43 inputs:44 message: "Order cancelled"
Points to note:
- Only
DrafthasisInitial: true. InTransitis a parent — it is neither initial nor final. ItsonExitfires when leaving any child state.DeliveredusesrequireConfirmation: true— the UI prompts the user before entering it.- Both
DeliveredandCancelledareisFinal: true— the engine prevents defining transitions from them.
Step 5 — Define the Aggregation
Before writing transitions, define the aggregation that checks whether all commodities are received:
yaml1aggregations:2 - name: allItemsReceived3 expression: "all(Order.Commodities, item.ReceivedQuantity >= item.Quantity)"
This evaluates to true when every commodity's ReceivedQuantity is at least its Quantity. It will be referenced by name in the PickedUp → Delivered auto-transition.
Step 6 — Define Transitions
yaml1transitions:2 # Manual: customer submits the draft order3 - name: submit4 displayName: "Submit Order"5 from: Draft6 to: Submitted7 trigger: manual89 # Auto: low-value orders skip review10 - name: auto_approve11 displayName: "Auto Approve"12 from: Submitted13 to: Approved14 trigger: auto15 priority: 2016 conditions:17 - expression: "Order.Amount < 5000"1819 # Auto: high-value orders go to manual review (lower priority, catches remainder)20 - name: send_for_review21 displayName: "Send for Review"22 from: Submitted23 to: UnderReview24 trigger: auto25 priority: 102627 # Manual: reviewer approves after inspecting the order28 - name: approve_review29 displayName: "Approve"30 from: UnderReview31 to: Approved32 trigger: manual3334 # Manual: dispatcher sends approved order for pickup35 - name: dispatch36 displayName: "Dispatch"37 from: Approved38 to: AwaitingPickup39 trigger: manual40 steps:41 - task: "Email/Send"42 inputs:43 template: carrier_pickup_request44 to: "{{ entity.carrier.email }}"4546 # Auto: carrier confirms pickup47 - name: mark_picked_up48 displayName: "Mark Picked Up"49 from: AwaitingPickup50 to: PickedUp51 trigger: auto52 priority: 5053 conditions:54 - expression: "Order.CarrierConfirmed = true"5556 # Auto: all commodities received — complete the delivery57 - name: complete_delivery58 from: PickedUp59 to: Delivered60 trigger: auto61 priority: 5062 conditions:63 - expression: "allItemsReceived = true"6465 # Manual wildcard: cancel from any non-final state66 - name: cancel67 displayName: "Cancel Order"68 from: "*"69 to: Cancelled70 trigger: manual71 steps:72 - task: "Utilities/Log@1"73 inputs:74 message: "Order cancelled by user"
Step 7 — Validate the File
bash1npx cxtms features/orders/workflows/order-lifecycle-flow.yaml
Fix any validation errors before deploying. Common mistakes:
- Referencing a state name in
fromortothat is misspelled or not defined instates. - Setting
toto a parent state (must be a leaf — no children). - Using
fromon a transition where the source is a final state. - Defining more than one
isInitial: truestate.
Step 8 — Completed Flow at a Glance
| State | Type | Stage |
|---|---|---|
Draft | Initial leaf | Entry |
Submitted | Leaf | Processing |
UnderReview | Leaf | Processing |
Approved | Leaf | Processing |
InTransit | Parent | Processing |
AwaitingPickup | Child of InTransit | — |
PickedUp | Child of InTransit | — |
Delivered | Final leaf | Complete |
Cancelled | Final leaf | Complete |
| Transition | From | To | Trigger |
|---|---|---|---|
submit | Draft | Submitted | manual |
auto_approve | Submitted | Approved | auto (priority 20) |
send_for_review | Submitted | UnderReview | auto (priority 10) |
approve_review | UnderReview | Approved | manual |
dispatch | Approved | AwaitingPickup | manual |
mark_picked_up | AwaitingPickup | PickedUp | auto |
complete_delivery | PickedUp | Delivered | auto (uses aggregation) |
cancel | * | Cancelled | manual |
Summary
In this lesson you:
- Planned the lifecycle diagram before writing YAML.
- Scaffolded the file with the CLI and added
workflowType: Flow. - Defined the
entitysection withincludesfor eager-loading. - Created states — one initial, two final, two parent/child groups, and
onEnter/onExithooks. - Added an
allItemsReceivedaggregation using theall()function. - Wrote eight transitions covering manual, auto with priority, wildcard cancellation, and aggregation-based delivery completion.
- Validated with the CLI.
The same pattern applies to any entity lifecycle in CXTMS — Invoice status, Commodity tracking, and more.