20 minlesson

Building an Order Lifecycle Flow

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:

1Draft
2 │ (manual: submit)
3
4Submitted
5 │ (auto: amount < 5000 → auto_approve)
6 │ (auto: amount >= 5000 → send_for_review)
7 ▼ ▼
8Approved UnderReview
9 │ │ (manual: approve_review)
10 │◄───────────────────┘
11 │ (manual: dispatch)
12
13InTransit [parent]
14 ├── AwaitingPickup
15 │ │ (auto: carrier confirms → mark_picked_up)
16 ▼ ▼
17 PickedUp
18 │ (auto: allItemsReceived → complete_delivery)
19
20 Delivered [final]
21
22 * → Cancelled [final] (manual: cancel — from any non-final state)

Key design decisions:

  • Draft is the initial state (new orders with null status resolve here).
  • InTransit is a parent — entities live in AwaitingPickup or PickedUp, not in InTransit directly.
  • Delivered and Cancelled are final — no further transitions.
  • The wildcard cancel transition uses from: "*" so it applies everywhere.
  • An allItemsReceived aggregation controls the PickedUp → Delivered auto-transition.

Step 2 — Scaffold the File

Always start from the CLI:

bash
1npx cxtms create workflow order-lifecycle-flow --template basic --feature orders
2# Creates: features/orders/workflows/order-lifecycle-flow.yaml

Open the generated file, then:

  1. Set workflowType: Flow in the workflow section.
  2. Remove the activities and triggers sections.
  3. Add entity, states, transitions, and aggregations sections.

Step 3 — Define the Entity Section

yaml
1entity:
2 name: Order
3 type: Brokerage
4 includes:
5 - Commodities
6 - Customer
7 - Charges
8 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

yaml
1states:
2 - name: Draft
3 stage: Entry
4 isInitial: true
5
6 - name: Submitted
7 stage: Processing
8
9 - name: UnderReview
10 stage: Processing
11
12 - name: Approved
13 stage: Processing
14
15 - name: InTransit
16 stage: Processing
17 onExit:
18 - task: "Utilities/Log@1"
19 inputs:
20 message: "Order leaving InTransit"
21
22 - name: AwaitingPickup
23 parent: InTransit
24
25 - name: PickedUp
26 parent: InTransit
27
28 - name: Delivered
29 stage: Complete
30 isFinal: true
31 requireConfirmation: true
32 onEnter:
33 - task: "Email/Send"
34 inputs:
35 template: order_delivered_notification
36 to: "{{ entity.customer.email }}"
37
38 - name: Cancelled
39 stage: Complete
40 isFinal: true
41 onEnter:
42 - task: "Utilities/Log@1"
43 inputs:
44 message: "Order cancelled"

Points to note:

  • Only Draft has isInitial: true.
  • InTransit is a parent — it is neither initial nor final. Its onExit fires when leaving any child state.
  • Delivered uses requireConfirmation: true — the UI prompts the user before entering it.
  • Both Delivered and Cancelled are isFinal: 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:

yaml
1aggregations:
2 - name: allItemsReceived
3 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

yaml
1transitions:
2 # Manual: customer submits the draft order
3 - name: submit
4 displayName: "Submit Order"
5 from: Draft
6 to: Submitted
7 trigger: manual
8
9 # Auto: low-value orders skip review
10 - name: auto_approve
11 displayName: "Auto Approve"
12 from: Submitted
13 to: Approved
14 trigger: auto
15 priority: 20
16 conditions:
17 - expression: "Order.Amount < 5000"
18
19 # Auto: high-value orders go to manual review (lower priority, catches remainder)
20 - name: send_for_review
21 displayName: "Send for Review"
22 from: Submitted
23 to: UnderReview
24 trigger: auto
25 priority: 10
26
27 # Manual: reviewer approves after inspecting the order
28 - name: approve_review
29 displayName: "Approve"
30 from: UnderReview
31 to: Approved
32 trigger: manual
33
34 # Manual: dispatcher sends approved order for pickup
35 - name: dispatch
36 displayName: "Dispatch"
37 from: Approved
38 to: AwaitingPickup
39 trigger: manual
40 steps:
41 - task: "Email/Send"
42 inputs:
43 template: carrier_pickup_request
44 to: "{{ entity.carrier.email }}"
45
46 # Auto: carrier confirms pickup
47 - name: mark_picked_up
48 displayName: "Mark Picked Up"
49 from: AwaitingPickup
50 to: PickedUp
51 trigger: auto
52 priority: 50
53 conditions:
54 - expression: "Order.CarrierConfirmed = true"
55
56 # Auto: all commodities received — complete the delivery
57 - name: complete_delivery
58 from: PickedUp
59 to: Delivered
60 trigger: auto
61 priority: 50
62 conditions:
63 - expression: "allItemsReceived = true"
64
65 # Manual wildcard: cancel from any non-final state
66 - name: cancel
67 displayName: "Cancel Order"
68 from: "*"
69 to: Cancelled
70 trigger: manual
71 steps:
72 - task: "Utilities/Log@1"
73 inputs:
74 message: "Order cancelled by user"

Step 7 — Validate the File

bash
1npx cxtms features/orders/workflows/order-lifecycle-flow.yaml

Fix any validation errors before deploying. Common mistakes:

  • Referencing a state name in from or to that is misspelled or not defined in states.
  • Setting to to a parent state (must be a leaf — no children).
  • Using from on a transition where the source is a final state.
  • Defining more than one isInitial: true state.

Step 8 — Completed Flow at a Glance

StateTypeStage
DraftInitial leafEntry
SubmittedLeafProcessing
UnderReviewLeafProcessing
ApprovedLeafProcessing
InTransitParentProcessing
AwaitingPickupChild of InTransit
PickedUpChild of InTransit
DeliveredFinal leafComplete
CancelledFinal leafComplete
TransitionFromToTrigger
submitDraftSubmittedmanual
auto_approveSubmittedApprovedauto (priority 20)
send_for_reviewSubmittedUnderReviewauto (priority 10)
approve_reviewUnderReviewApprovedmanual
dispatchApprovedAwaitingPickupmanual
mark_picked_upAwaitingPickupPickedUpauto
complete_deliveryPickedUpDeliveredauto (uses aggregation)
cancel*Cancelledmanual

Summary

In this lesson you:

  1. Planned the lifecycle diagram before writing YAML.
  2. Scaffolded the file with the CLI and added workflowType: Flow.
  3. Defined the entity section with includes for eager-loading.
  4. Created states — one initial, two final, two parent/child groups, and onEnter/onExit hooks.
  5. Added an allItemsReceived aggregation using the all() function.
  6. Wrote eight transitions covering manual, auto with priority, wildcard cancellation, and aggregation-based delivery completion.
  7. Validated with the CLI.

The same pattern applies to any entity lifecycle in CXTMS — Invoice status, Commodity tracking, and more.