Wiki · Sales order · PATH 1 · R592

Path 1 · fulfill from inventory

The simplest path. Items already on hand at the fulfillment location — no production, no PO. Reserve qty against the SO, warehouse picks/packs/stages, Item Fulfillment carries createdfrom = so.id and inherits the customer PO# (otherrefnum) from the SO via NS standard linkage.

PATH 1 REAL ★ Finance alert STUB (NS auto pending)
What this is

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.

When to use it

Trigger conditions

★ The handoff

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.

Worked example

Driscoll 10 inventory lines, $13,200

Scenario

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.

Step-by-step what happens

Reserve → pick → pack → ship → Finance

  1. 01

    Inventory check

    Verify inventory_balance.quantityavailable - reserved_qty ≥ so_line.qty per location.

  2. 02

    Reserve qty

    UPDATE inventory_balance SET reserved_qty = reserved_qty + qty against the SO.

  3. 03

    Generate pick list

    Per warehouse zone. Surface: warehouse pick form.

  4. 04

    Pick · pack · palletize · stage

    Physical pull + scan + label + shrink + dock-door assignment.

  5. 05

    Item Fulfillment created

    NS ItemFulfill: createdfrom = so.id, otherrefnum inherits from SO via NS standard linkage, status = "Shipped". Inventory decrements.

    PO# threads SO.otherrefnum → ItemFulfill.otherrefnum
  6. 06

    ★ Automation alert → Finance (STUB)

    Fires events.so.finance_alert_fired. NS workflow build pending next round.

  7. 07

    Customer Invoice posted

    NS CustInvc: otherrefnum = "72622", createdfrom = 1217, total from shipped qty.

    PO# threads CustInvc.otherrefnum = SO.otherrefnum
  8. 08

    Monitor terms · dunning loopback if past due

    View: v_customer_ar_aging. If past due → ar_aging_action_plan.

  9. 09

    Payment applied · SO closed

    CustPymt; payment_applications; UPDATE transactions SET status='Closed'; events.order.closed.

Outcomes

What's different after the cycle

SO
Closed
paid + applied
Inventory
Decremented
by shipped qty
PO# trace
3 records
SO · IF · Invoice
Events
5+
per cycle
Failure modes

What can go wrong

Short pick

Pick list 50, picker finds 48 → short-ship + customer note. Invoice still posts on shipped qty.

Inventory overcommit (concurrent SO race)

Two SOs reserve the same units. Resolve by reconciling inventory_balance.reserved_qty against open IFs.

Finance alert never delivered

IF closed, events.so.finance_alert_fired present but Finance review queue empty — NS workflow stub not yet deployed. Fall back: manually notify Finance.

otherrefnum threading broken on CustInvc

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 = ?.

Related

Adjacent flows + diagrams

For developers

Code paths + invariants

ConcernWhere
Workflow contractso_lifecycle_inventory_path (workflow_definitions)
Master dispatchersales_order_lifecycle
ReservationUPDATE inventory_balance SET reserved_qty += qty
IF linkageitem_fulfillments.createdfrom = transactions.id (SO)
PO# thread invariantCustInvc.otherrefnum == SO.otherrefnum
Finance alertevents.so.finance_alert_fired (STUB; NS workflow pending)
Sync tierhot (2 min) for IF / Invoice / Payment
// Path 1 threading invariant — verify in workflow_verify_results SELECT ci.otherrefnum, so.otherrefnum FROM customer_invoices ci JOIN transactions so ON ci.so_id = so.id WHERE ci.id = ? // expected: ci.otherrefnum = so.otherrefnum (same customer PO# threads through)
Changelog

Dated trail · spot stale claims

Dated trail of when this doc was last touched.

DateRoundChangeTouched by
2026-05-26R592Split into master + 3 path-detail docs; added Customer PO# threading schema.Mike + Claude
Schema · data contract

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 recordFieldSample value
Sales OrderbodyFields.otherrefnum (thread origin)"72622"
Item Fulfillmentinherits via NS std linkage — createdfrom = so.idcarries otherrefnum = "72622"
Customer InvoicebodyFields.otherrefnum"72622"
Customer Paymenttraces via appliedto = invoice.id (no direct PO# field)n/a (use invoice as proxy)

Inputs

FieldTypeDescription
so_idintegerParent SO. Required.
customer_po_numberstringFrom SO.otherrefnum. Required.
so_linesarrayLines where itemtype=InvtPart.

D1 tables touched

TableOperation
inventory_balanceUPDATE (reserved_qty, quantityavailable)
item_fulfillmentsINSERT (createdfrom, otherrefnum)
customer_invoicesINSERT (otherrefnum, createdfrom)
invoice_linesINSERT per shipped line
customer_paymentsINSERT
payment_applicationsINSERT (payment_id, invoice_id, amount)
transactionsUPDATE (status='Closed' on SO)
eventsINSERT (so.finance_alert_fired, order.closed)
Runbook · when it breaks

It broke at 2am — what now

Logs, recovery steps, escalation.

Scenario · CustInvc.otherrefnum mismatch with SO.otherrefnum

Customer PO# threading invariant broken.

  1. Verify: SELECT ci.id, ci.otherrefnum, so.otherrefnum FROM customer_invoices ci JOIN transactions so ON ci.so_id=so.id WHERE ci.id=?
  2. Patch: UPDATE customer_invoices SET otherrefnum = ? WHERE id = ? with SO.otherrefnum value.
  3. 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.

  1. Check events: SELECT * FROM events WHERE event_type='so.finance_alert_fired' AND entity_id=?
  2. Force: manually insert the event row and notify Finance with the IF id while NS workflow build is pending.

Logs to check

Backlog · open questions

What's not done