Inventory pull, no production, no PO
Path 1 covers SO lines where the item is already on hand at a fulfillment location. The workflow sub-contract so_lifecycle_inventory_path handles the entire cycle: reserve → pick → pack → stage → Item Fulfillment → ★ Finance alert → Customer Invoice → Payment Apply → Close.
This is the cleanest path from a customer-PO# threading standpoint: SO.otherrefnum threads through the Item Fulfillment (inherited via NS standard linkage from createdfrom = so.id) and lands on the Customer Invoice as CustInvc.otherrefnum. Trace recipe: grep "72622" across transactions.otherrefnum + item_fulfillments.otherrefnum + customer_invoices.otherrefnum lights up every Path 1 record in this SO chain.
Diagram: ns-sales-order-path-1-inventory.html. Master view: ns-sales-order-master.html.
Trigger conditions
- SO entered in NS; one or more lines have
items.itemtype = "InvtPart"anditems.dropship = false. inventory_balance.quantityavailable ≥ so_line.qtyat the fulfillment location.- Customer is not on hold (
customers.hold_status = 'active',service_hold = 0). - Master
sales_order_lifecycledispatches intoso_lifecycle_inventory_path.
After Item Fulfillment, an automation alert fires to Finance. Currently a STUB step emitting events.so.finance_alert_fired at the platform layer; the NS workflow that delivers the alert is the next-round build.
Driscoll 10 inventory lines, $13,200
Driscoll Foods (NS customer #2147) emails an order with customer PO# 72622. Of 14 lines, 10 are inventory items totaling $13,200. SO #1217 entered in NS with otherrefnum = "72622".
For each inventory line: inventory_balance.quantityavailable checked, reserved against SO. Warehouse pulls picks, packs, palletizes, stages. Item Fulfillment record created with createdfrom = 1217 and otherrefnum = "72622" (inherits from SO).
★ Finance alert fires. Finance posts CustInvc with otherrefnum = "72622" and createdfrom = 1217. Net 30. Driscoll pays on day 24. CustPymt.appliedto = "INV-9981". SO closes; events.order.closed fires.
Reserve → pick → pack → ship → Finance
- 01
Inventory check
Verify
inventory_balance.quantityavailable - reserved_qty ≥ so_line.qtyper location. - 02
Reserve qty
UPDATE inventory_balance SET reserved_qty = reserved_qty + qtyagainst the SO. - 03
Generate pick list
Per warehouse zone. Surface: warehouse pick form.
- 04
Pick · pack · palletize · stage
Physical pull + scan + label + shrink + dock-door assignment.
- 05
Item Fulfillment created
NS
ItemFulfill:createdfrom = so.id,otherrefnuminherits from SO via NS standard linkage,status = "Shipped". Inventory decrements. - 06
★ Automation alert → Finance (STUB)
Fires
events.so.finance_alert_fired. NS workflow build pending next round. - 07
Customer Invoice posted
NS
CustInvc:otherrefnum = "72622",createdfrom = 1217, total from shipped qty. - 08
Monitor terms · dunning loopback if past due
View:
v_customer_ar_aging. If past due →ar_aging_action_plan. - 09
Payment applied · SO closed
CustPymt;payment_applications;UPDATE transactions SET status='Closed';events.order.closed.
What's different after the cycle
What can go wrong
Pick list 50, picker finds 48 → short-ship + customer note. Invoice still posts on shipped qty.
Two SOs reserve the same units. Resolve by reconciling inventory_balance.reserved_qty against open IFs.
IF closed, events.so.finance_alert_fired present but Finance review queue empty — NS workflow stub not yet deployed. Fall back: manually notify Finance.
CustInvc.otherrefnum is NULL or differs from SO.otherrefnum. Trace recipe doesn't light up the invoice. Resync transactions or patch UPDATE customer_invoices SET otherrefnum = ?.
Adjacent flows + diagrams
Code paths + invariants
| Concern | Where |
|---|---|
| Workflow contract | so_lifecycle_inventory_path (workflow_definitions) |
| Master dispatcher | sales_order_lifecycle |
| Reservation | UPDATE inventory_balance SET reserved_qty += qty |
| IF linkage | item_fulfillments.createdfrom = transactions.id (SO) |
| PO# thread invariant | CustInvc.otherrefnum == SO.otherrefnum |
| Finance alert | events.so.finance_alert_fired (STUB; NS workflow pending) |
| Sync tier | hot (2 min) for IF / Invoice / Payment |
Dated trail · spot stale claims
Dated trail of when this doc was last touched.
| Date | Round | Change | Touched by |
|---|---|---|---|
2026-05-26 | R592 | Split into master + 3 path-detail docs; added Customer PO# threading schema. | Mike + Claude |
The machine-readable spec
Canonical fields, table names, endpoint signatures. workflow_type · so_lifecycle_inventory_path · risk_level · 2.
Customer PO# threading (Path 1)
| NS record | Field | Sample value |
|---|---|---|
| Sales Order | bodyFields.otherrefnum (thread origin) | "72622" |
| Item Fulfillment | inherits via NS std linkage — createdfrom = so.id | carries otherrefnum = "72622" |
| Customer Invoice | bodyFields.otherrefnum | "72622" |
| Customer Payment | traces via appliedto = invoice.id (no direct PO# field) | n/a (use invoice as proxy) |
Inputs
| Field | Type | Description |
|---|---|---|
so_id | integer | Parent SO. Required. |
customer_po_number | string | From SO.otherrefnum. Required. |
so_lines | array | Lines where itemtype=InvtPart. |
D1 tables touched
| Table | Operation |
|---|---|
inventory_balance | UPDATE (reserved_qty, quantityavailable) |
item_fulfillments | INSERT (createdfrom, otherrefnum) |
customer_invoices | INSERT (otherrefnum, createdfrom) |
invoice_lines | INSERT per shipped line |
customer_payments | INSERT |
payment_applications | INSERT (payment_id, invoice_id, amount) |
transactions | UPDATE (status='Closed' on SO) |
events | INSERT (so.finance_alert_fired, order.closed) |
It broke at 2am — what now
Logs, recovery steps, escalation.
Scenario · CustInvc.otherrefnum mismatch with SO.otherrefnum
Customer PO# threading invariant broken.
- Verify:
SELECT ci.id, ci.otherrefnum, so.otherrefnum FROM customer_invoices ci JOIN transactions so ON ci.so_id=so.id WHERE ci.id=? - Patch:
UPDATE customer_invoices SET otherrefnum = ? WHERE id = ?with SO.otherrefnum value. - NS push back: stage proposed_action to update NS CustInvc.otherrefnum so the system of record matches.
Scenario · Item Fulfillment created but Finance alert never fired
IF closed, but no events.so.finance_alert_fired row.
- Check events:
SELECT * FROM events WHERE event_type='so.finance_alert_fired' AND entity_id=? - Force: manually insert the event row and notify Finance with the IF id while NS workflow build is pending.
Logs to check
workflow_run_log· top-level run auditworkflow_step_log· per-step traceevents· so.finance_alert_fired + order.closednpx wrangler tail· live Worker logs
What's not done
-
STUB
Finance automation alert — NS workflow build
Platform fires
events.so.finance_alert_fired. NS workflow that surfaces this in Finance's queue is the next-round build (Mike to deploy). -
OPEN
Cross-warehouse rebalance on shortfall
If primary location is short, auto-propose pull from secondary location instead of falling back to dropship/PO.