How the three processes work together — SO + PO + WO

Customer side (SO) · Vendor side (PO) · Production side (WO) · all three converge at the Finance handoff

Three transaction lifecycles run the business. Sales Order is the customer side — starts with the customer order and ends with fulfillment + invoice + payment + close. Purchase Order is the vendor side — used when we buy product, ingredients, packaging, components, finished goods, or drop-ship items; ends with Item Receipt + vendor bill + vendor payment + PO closure. Work Order is the production side — used when we assemble or build finished goods; ends when the Assembly Build is completed and finished goods land back in inventory. Two cross-lane bridges keep records linked: SO Path 2 creates a WO from the SO (WO.createdfrom = SO.id), and SO Path 3 creates a PO from the SO (PO.createdfrom = SO.id). All three converge at the ★ Finance handoff post-fulfillment / post-receipt. This is the canonical "system of three" view; the per-lane masters and path-detail docs hold the field-level depth.

D1-mirrored from NS HITL on every write-back Customer side · SO Vendor side · PO Production side · WO ★ Finance handoff (convergence)

4-lane swimlane — customer / vendor / production / finance

idle Driscoll orders 50 cases RS-12: 30 inv + 20 WO + 1 dropship line
How the three NetSuite transaction lifecycles work together. Lane 1 (customer / Sales Order) runs top. Lane 2 (vendor / Purchase Order) runs middle. Lane 3 (production / Work Order) runs below. Cross-lane bridges show SO Path 2 spawning a WO from the SO, and SO Path 3 spawning a PO from the SO. Lane 4 (Finance) spans the bottom as the universal handoff convergence — fulfilled Sales Orders alert it for customer-side invoicing, received Purchase Orders alert it for vendor-side billing, completed Work Orders are reported to it for cost roll-up. Lane 1 · Customer side · Sales Order lifecycle · customer order → fulfillment → invoice → payment → close Lane 2 · Vendor side · Purchase Order lifecycle · need to buy → PO → vendor ships → Item Receipt → Vendor Bill → pay Lane 3 · Production side · Work Order lifecycle · need to build → WO → release → consume BOM → Assembly Build → FG in inventory Lane 4 · ★ Finance handoff · universal convergence · receives alerts from all 3 lifecycles · invoices, bills, payments, cost roll-up SO ENTRY — Customer order keyed in NetSuite as a Sales Order. ACTION intake: email at orders@ / danielle@ enter: NS SalesOrd · capture bodyFields.otherrefnum = customer PO# (e.g. "72622") capture: tranid = NS-assigned SO# (e.g. "1217") TABLES write: NS SalesOrd · D1 transactions · so_lines STATUS: REAL · operator-driven SO entry customer order keyed otherrefnum = "72622" LANE 1 / SO · step 1 i CLASSIFY EACH LINE — A single SO routinely spans multiple paths. LOGIC per so_line.item.type: inventory_item → Path 1 (fulfill from inventory) assembly_item → Path 2 (create WO from SO) dropship_item OR special_order → Path 3 (create PO from SO) STATUS: REAL (operator-driven today) · STUB (auto-classify on /api/so/create) classify each line inv · assembly · dropship one SO → multi paths LANE 1 / SO · step 2 i PATH 1 INVENTORY — Lines fulfilled directly from stock. ACTION reserve · pick · pack · stage TABLES write: itemfulfillment (open) · inventory_balance.reserved_qty DETAIL see ns-sales-order-path-1-inventory.html for field-level depth STATUS: REAL Path 1 · fulfill from inventory reserve · pick · pack no WO · no PO LANE 1 / SO · step 3 i ITEM FULFILLMENT (CONVERGE) — Where all 3 SO paths land. ACTION status: itemfulfillment → 'Shipped' inventory decrement · BOL/ASN generated LINKAGE itemfulfillment.createdfrom = so.id inherits otherrefnum from SO STATUS: REAL Item Fulfillment all 3 paths land here createdfrom = so.id LANE 1 / SO · step 4 (converge) i INVOICE CUSTOMER — Finance-driven step on the SO side. ACTION post: CustInvc using shipped qty (not ordered qty) AR balance updates TABLES write: CustInvc · invoice_lines · v_customer_ar_aging STATUS: REAL invoice customer CustInvc · AR open otherrefnum threads LANE 1 / SO · step 5 i PAYMENT + CLOSE — Customer remits; SO closed. ACTION receive: CustPymt · apply: payment_applications SO → Closed · fire events.order.closed STATUS: REAL payment · close CustPymt · SO Closed order.closed event LANE 1 / SO · step 6 i NEED TO BUY — PO trigger has two sources. SOURCES PO Path 1 (SO-connected): drop-ship line from SO Path 3 → PO created FROM SO PO Path 2 (stock replen): inventory low or scheduled buy → standalone PO PO Path 3 (special order): one-off ingredient/packaging/component STATUS: REAL need to buy SO-connected OR replen PO Path 1 / 2 / 3 LANE 2 / PO · step 1 i PO ENTRY — Purchase Order keyed in NetSuite. ACTION enter: NS PurchOrd if SO-connected: createdfrom = so.id · bodyFields.memo = customer PO# if standalone: no createdfrom; vendor + items + terms LINKAGE (Path 1 only) purchaseorder.createdfrom = so.id PO.lineFields.links[].tranid = " / " STATUS: REAL PO entry PurchOrd posted createdfrom = so.id (P1) LANE 2 / PO · step 2 i PO TRANSMITTED — PO sent to vendor (email / EDI / portal). ACTION email-to-vendor or EDI 850 vendor confirms · ship date negotiated STATUS: REAL · vendor confirmation often manual transmit to vendor email / EDI / portal vendor confirms LANE 2 / PO · step 3 i ITEM RECEIPT — Vendor ships product; we post receipt against the PO. ACTION status: itemreceipt → posted PO line → 'Fully Received' LINKAGE itemreceipt.createdfrom = po.id inherits memo (customer PO# threads through on Path 1) TABLES write: itemreceipt · inventory_balance (credit) STATUS: REAL Item Receipt vendor ships · IR posted createdfrom = po.id LANE 2 / PO · step 4 i VENDOR BILL — Finance enters the vendor bill against the PO. ACTION enter: NS VendBill · createdfrom = po.id bill matches 3-way: PO + IR + bill qty + price LINKAGE vendorbill.memo · tranid · initialtranid = customer PO# (Path 1) STATUS: REAL vendor bill VendBill posted · AP open 3-way match LANE 2 / PO · step 5 i PAY + CLOSE PO — Vendor paid per terms; PO closed. ACTION pay: VendPymt per vendor terms (Net 30 / Net 45) PO → Closed · fire events.po.closed STATUS: REAL pay vendor · close PO VendPymt · AP cleared po.closed event LANE 2 / PO · step 6 i NEED TO BUILD — WO trigger has two sources. SOURCES WO Path 1 (SO-connected): assembly line on SO Path 2 → WO created FROM SO WO Path 2 (stock build): low FG inventory or scheduled production → standalone WO ALTERNATE (Path 1) if FG already on hand → fulfill from inventory + close WO marked 'unused' STATUS: REAL need to build SO-connected OR stock WO Path 1 / 2 LANE 3 / WO · step 1 i WO ENTRY — Work Order keyed in NetSuite. ACTION enter: NS WorkOrd · assembly item + qty + BOM ref if SO-connected: createdfrom = so.id LINKAGE workorder.createdfrom = so.id (Path 1) WO customer PO# field: TBD — Mike review pending; expected bodyFields.memo STATUS: REAL · WO PO# field TBD WO entry WorkOrd posted createdfrom = so.id (P1) LANE 3 / WO · step 2 i WO RELEASED — Production committed; components reserved. ACTION status: WO → 'Released' reserve BOM components against the WO STATUS: REAL WO released components reserved production scheduled LANE 3 / WO · step 3 i ASSEMBLY BUILD — Production crew completes the build. ACTION enter: AssemblyBuild · consume BOM components credit: finished-goods qty back into inventory HITL /assembly-build.html → proposed_actions → Mike approves TABLES write: assemblybuild · inventory_balance (credit FG, debit components) STATUS: REAL · HITL on cost variance Assembly Build BOM consume · FG credit HITL via /assembly-build LANE 3 / WO · step 4 (end-state) i FG IN INVENTORY — Finished goods land back in inventory. ACTION inventory_balance for the assembly item increments by build qty scrap / waste captured as variance DOWNSTREAM if Path 1 WO: triggers Lane 1 SO fulfillment (now stock is available) STATUS: REAL FG in inventory FG available to SO scrap / waste captured LANE 3 / WO · step 5 i WO CLOSED — Work Order completes. ACTION WO → 'Closed' · build variance booked EVENTS fire: events.wo.closed · events.assembly.built (with cost variance) DOWNSTREAM Finance: cost roll-up for COGS + variance analysis STATUS: REAL WO closed variance booked wo.closed event LANE 3 / WO · step 6 i ★ FINANCE ALERT — SO SIDE. Fires on every Item Fulfillment. TRIGGER itemfulfillment.status → 'Shipped' ACTION NS workflow notifies Finance review queue emit: events.so.finance_alert_fired STATUS: STUB — platform-side event fires (REAL); NS workflow build pending (R594) ★ SO finance alert itemfulfillment → Shipped LANE 4 / Finance · from SO i ★ FINANCE ALERT — PO SIDE. Fires on every Item Receipt. TRIGGER itemreceipt posted → 3-way match queued ACTION NS workflow queues bill entry · Finance reviews emit: events.po.finance_alert_fired STATUS: STUB — platform-side event fires (REAL); NS workflow build pending (R594) ★ PO finance alert itemreceipt posted LANE 4 / Finance · from PO i ★ FINANCE ROLL-UP — WO SIDE. Cost roll-up on every Assembly Build close. TRIGGER events.wo.closed · events.assembly.built (with variance) ACTION Finance updates COGS · variance reporting · cost-method refresh STATUS: STUB — events fire (REAL); GL posting still manual in Finance review ★ WO cost roll-up wo.closed · variance LANE 4 / Finance · from WO i FINANCE RECONCILES — Universal convergence. ACTION SO side: invoice → payment apply → close PO side: bill → vendor pay → AP clear WO side: variance → COGS adjustment → margin walk monthly close ties all three together STATUS: REAL · Finance-driven Finance reconciles SO + PO + WO · monthly close ties all three LANE 4 / Finance · convergence i SO Path 2: spawns WO from SO WO.createdfrom = SO.id SO Path 3: spawns PO from SO PO.createdfrom = SO.id WO completes → FG available → SO can fulfill Path 3 IR → SO IF (dropship) ★ alert ★ alert ★ alert LEGEND Lane 1 SO (customer) Lane 2 PO (vendor) Lane 3 WO (production) Lane 4 Finance (★ convergence) cross-lane bridge (createdfrom) ★ Finance handoff alert CROSS-RECORD LINKAGE · how the 3 lifecycles stay connected SO → WO (Path 2) WO.createdfrom = SO.id preserves SO ↔ WO link SO → PO (Path 3) PO.createdfrom = SO.id preserves SO ↔ PO link SO → Item Fulfillment IF.createdfrom = SO.id inherits otherrefnum PO → Item Receipt IR.createdfrom = PO.id inherits memo PO → Vendor Bill · Customer PO# thread Bill.createdfrom = PO.id · memo / tranid universal trace thread "72622"

The three lifecycles — Mike's framing

SO Sales Order — the customer side customer REAL

Starts with the customer order and ends with customer fulfillment, invoicing, payment, and order closure.
Start
customer email at orders@ / danielle@
End
SO Closed · events.order.closed fires
Master doc
ns-sales-order-master.html + 3 path-detail diagrams
Wiki

PO Purchase Order — the vendor side vendor REAL

Used when the company needs to buy product, ingredients, packaging, components, finished goods, or drop-ship items. Ends with Item Receipt, vendor bill entry, vendor payment, and PO closure.
Start
need-to-buy signal (SO Path 3 dropship, stock replen, or special order)
End
PO Closed · events.po.closed fires
Bridge
PO Path 1 (SO-connected) is the consuming side of SO Path 3
Linkage
PO.createdfrom = SO.id on Path 1

WO Work Order — the production side production REAL

Used when the company needs to assemble or build finished goods. Ends when Assembly Build is completed and finished goods are added back into inventory.
Start
need-to-build signal (SO Path 2 assembly, or stock build)
End
WO Closed · FG inventory credited · events.wo.closed fires
Bridge
WO Path 1 (from-SO) is the consuming side of SO Path 2
Linkage
WO.createdfrom = SO.id on Path 1 (customer PO# field TBD)

FIN Finance handoff — the universal convergence ★ convergence REAL (concept) NS workflow build pending R594

All three lifecycles fire a Finance alert at their post-fulfillment / post-receipt moment. Finance reconciles SO + PO + WO at monthly close.
SO trigger
itemfulfillment.status → 'Shipped' → events.so.finance_alert_fired
PO trigger
itemreceipt posted → events.po.finance_alert_fired
WO trigger
events.wo.closed + events.assembly.built (with variance)
Reconcile
CustInvc / VendBill / COGS variance — monthly close

Cross-record linkage — the bridge table

Cross-record linkageFieldCarries
SO → WO (Path 2)WO.createdfrom = SO.idpreserves the SO ↔ WO link
SO → PO (Path 3)PO.createdfrom = SO.idpreserves the SO ↔ PO link
SO → Item FulfillmentIF.createdfrom = SO.idinherits otherrefnum (customer PO#)
PO → Item ReceiptIR.createdfrom = PO.idinherits memo (customer PO# threads on Path 1)
PO → Vendor BillBill.createdfrom = PO.idinherits memo, tranid, initialtranid
Customer PO# thread (universal)otherrefnum (SO, Inv) ↔ memo (PO, Bill) ↔ memo (WO — TBD)universal trace thread "72622"

Open gaps — honest punch list

  • WO customer PO# field TBD: WO has no canonical place for the customer PO# yet. Expected pattern is bodyFields.memo, but Mike's review is pending.
  • Finance alert NS automation: SO/PO/WO alerts to Finance fire as platform events today; the NS workflow build that delivers them into Finance's review queue is planned for R594.
  • Auto-classification (which path/combination applies): operators decide today. A classifier on intake would surface mixed-path orders earlier and auto-stage WO + PO from the SO via one fan-out.
  • Duplicate Finance alerts (Path 3 dropship): Path 3 fires both a PO-side alert (Item Receipt) and an SO-side alert (Item Fulfillment) for the same line. Design intent — not a bug — but Finance UI should de-duplicate visually.
  • Monthly close reconciliation ties SO + PO + WO together; today this is Finance-manual. Variance roll-up is a candidate for the 27th v2 workflow contract.

Related docs