Wiki · NetSuite expansion · R592 master · 3 path subdocs

NS sales order — master

The high-altitude master view. Customer orders arrive by email at orders@ and danielle@. Team reviews, keys into NS (capturing customer PO# as otherrefnum), then dispatches into 3 path sub-contracts. Customer PO# threads through every connected NS record — one trace field across the whole SO chain. Path-detail docs cover field-level depth.

Master · dispatcher + 3 path subdocs otherrefnum threading invariant
What this is

Email intake, 3 fulfillment paths, Finance handoff

The NS sales order lifecycle is the canonical revenue flow — the way orders actually move at GFS. Most customer orders arrive by email at orders@globalfoodsolutions.co or danielle@globalfoodsolutions.co. The team reads each order against the customer file, confirms item details / shipping / pricing / special instructions, then keys it into NetSuite as a Sales Order.

From there the SO branches by item type into three parallel fulfillment paths: fulfill from inventory (already on hand), assembly build (Work Orders are created FROM the SO — records stay linked), or drop ship (Purchase Orders are created FROM the SO and sent to a vendor). A single SO routinely spans multiple paths because items often differ in type.

All three paths converge at Item Fulfillment. That fulfillment fires an automated alert to Finance — the single named handoff between Operations and Finance. Finance reviews, invoices the customer, monitors payment terms, and triggers dunning if the invoice goes past due. On the drop-ship path, the vendor-bill cycle runs in parallel.

Diagram: ns-sales-order-master.html. Path detail docs: Path 1 inventory · Path 2 assembly · Path 3 dropship. Open STUBs: EDI 850 auto-intake, auto path classifier, NS workflow build for the Finance alert (next round). TBD: Work Order customer PO# field — pending Mike's review (expected pattern bodyFields.memo).

When to use it

Trigger conditions

Branch decision

After SO entry, each line is classified by item type. Inventory items → Path 1 (pull from stock). Assembly items → Path 2 (Work Order created FROM the SO; or fulfill from inventory + close WO if already on hand). Dropship / special-order items → Path 3 (PO created FROM the SO). Mixed-path orders are common.

★ The handoff

After every Item Fulfillment, an automation alert fires to Finance. This is the single named bridge between Operations and Finance. Finance owns everything from review onward (invoice, terms, dunning, payment apply, close).

Worked example

Driscoll weekly mixed-path order

Scenario

Thursday 14:00. Driscoll (NS customer #2147, 36.4% of revenue) emails the week's order to orders@globalfoodsolutions.co: 14 line items, $18,420. Mix of inventory items (10), one assembly build item (Melt Mates kit), and 3 drop-ship items from a vendor partner.

Team opens the email, reviews against Driscoll's file (terms, ship-to, bid contract), confirms pricing, and keys the SO into NetSuite at 14:08. Each line is mentally classified: 10 lines → Path 1 (inventory pull); 1 line → Path 2 (Work Order created FROM the SO for the Melt Mates assembly); 3 lines → Path 3 (PO created FROM the SO, sent to vendor).

Friday morning: warehouse picks the 10 inventory lines. Production crew completes the Melt Mates assembly build (HITL approval at /assembly-build.html, Mike taps Approve). Vendor ships the 3 drop-ship lines directly to Driscoll's location, posting an Item Receipt against the PO. By Friday afternoon all paths converge: the Item Fulfillment record closes.

★ Automation alert fires to Finance. Finance reviews the fulfillment (qty, pricing, terms), then posts the CustInvc for $18,420. Net 30 terms; expected June 22. AR aging bucket: current.

Path 3 sidecar: the next week, Finance enters the vendor bill for the 3 drop-ship lines and pays it per vendor terms.

Contrast: if Driscoll's payment doesn't arrive by July 8, the AR aging bucket flips to "30-60" and the ar_aging_action_plan dunning workflow fires. Loops at 30 / 60 / 90+ until paid or escalated to service hold.

Step-by-step what happens

Intake → SO entry → 3 paths → Finance

Common steps run first. Then the SO branches into one of three paths (a single SO can hit multiple). All paths converge at Item Fulfillment, which fires the automation alert to Finance.

Common steps (every SO)

  1. 01

    Receive customer order

    Most orders arrive by email at orders@globalfoodsolutions.co or danielle@globalfoodsolutions.co. Other channels: NS UI direct entry, customer portal, EDI 850 (manual keying today, auto-parse STUB), phone keyed by CS.

    Mailboxes orders@, danielle@
  2. 02

    Review against customer file

    Team reads the order against the customer record: ship-to address, pack/UOM details, pricing alignment (contract / program / list), special instructions, delivery windows.

    Reads customers, pricing_master, customer_programs, contracts
  3. 03

    Enter SO in NetSuite

    SO posted as system-of-record. D1 mirror lands on the next 2-min hot-tier sync.

    Writes NS SalesOrd, transactions, so_lines
  4. 04

    Classify each line by item type

    Operator (today) sorts each line into Path 1 / 2 / 3. A single SO can fan out into multiple paths. Auto-classification on intake is a STUB.

    Decision inventory · assembly · dropship

Path 1 — Fulfill from inventory

  1. 1.1

    Allocate from inventory

    Check inventory_balance.quantityavailable ≥ line qty at the fulfillment location. Reserve qty against the SO.

  2. 1.2

    Pick, pack, stage

    Warehouse executes the pick list (scan + label), palletizes, and stages for carrier pickup.

  3. 1.3

    Item Fulfillment completed

    Fulfillment record closes (status='Shipped'). ★ Automation alert fires to Finance. Continues to the Finance steps below.

Path 2 — Assembly build (Work Orders FROM SO)

  1. 2.1

    Create Work Orders FROM the SO

    For each assembly-build line, a Work Order is created with parent_so_id linking it back to the originating Sales Order. Records stay linked end-to-end. Alternate: if the assembly is already in inventory, fulfill from inventory and close the Work Order.

    Writes NS workorder with parent_so_id
  2. 2.2

    Complete the assembly build

    Production crew executes the build on the floor — capture qty + waste + scrap, consume raw components, credit finished goods. Mike approves at /assembly-build.html.

    Writes assemblybuild, inventory_balance
  3. 2.3

    SO fulfilled to customer · Item Fulfillment completed

    Once the assembly is on hand, the SO ships. ★ Automation alert fires to Finance. Continues to the Finance steps below.

Path 3 — Drop ship (Purchase Orders FROM SO)

  1. 3.1

    Create Purchase Order FROM the SO

    For each drop-ship line, a PO is created with parent_so_id linking it back to the originating Sales Order. PO is transmitted to the vendor.

    Writes NS purchaseorder with parent_so_id
  2. 3.2

    Vendor ships · Item Receipt posted

    Vendor ships product (directly to customer or to GFS for crossdock). Item Receipt posted against the PO; PO line closes.

    Writes NS itemreceipt
  3. 3.3

    SO fulfilled to customer · Item Fulfillment completed

    ★ Automation alert fires to Finance. Continues to the Finance steps below.

  4. 3.4

    Vendor bill cycle (parallel)

    Separate from the customer-side invoice cycle: vendor bill entered against the PO, paid per vendor terms.

    Writes NS vendorbill, vendorpayment

★ Finance steps (every path converges here)

  1. F1

    ★ Automation alert → Finance

    On every Item Fulfillment completion (across all 3 paths), an automated alert fires to Finance. This is the single named handoff between Operations and Finance. The actual NS workflow trigger config still needs code-level verification (STUB on confirmation, not on existence).

    Trigger itemfulfillment.status='Shipped'
  2. F2

    Finance reviews fulfillment

    Finance checks fulfilled qty matches expected, pricing carried through, customer terms + tax handling correct, short-ships flagged.

  3. F3

    Invoice customer

    Finance posts CustInvc using shipped qty (not ordered qty). AR balance updates.

    Writes NS CustInvc, invoice_lines
  4. F4

    Invoice open · monitor payment terms

    Invoice remains open until payment received. v_customer_ar_aging tracks the bucket (current / 30 / 60 / 90+).

  5. F5

    If past due → dunning fires

    The ar_aging_action_plan workflow kicks in. Dunning email drafted at escalating tone for 30 / 60 / 90+ buckets. Email send remains HITL-confirmed. Loops until paid or escalated to service hold.

    Workflow ar_aging_action_plan
  6. F6

    Payment received · apply · close

    Customer remits; CustPymt posted; payment_applications matches payment to invoice(s). SO closes; events.order.closed fires for the customer health watcher.

    Writes NS CustPymt, customer_payments, payment_applications
Outcomes

What's different after the cycle

SO
Shipped
itemfulfillment closed
Invoice
Posted
AR balance updated
Health
Recomputed
per cycle
Events
Emitted
8+ per cycle
Failure modes

What can go wrong

EDI 850 STUB — manual entry burden

EDI files land in a mailbox; operator keys into NS. Risk: transcription errors, missed orders, slow turnaround. Mitigation: limited EDI customers today (mostly NYC DOE). Long-term: AS2 endpoint + X12 parser as a Cloudflare Worker.

Finance never got the alert (the handoff failed)

Item Fulfillment closed but Finance's review queue is empty. Likely the NS workflow trigger fired but the alert email/scheduled-script delivery failed. See runbook scenario below — check NS workflow execution log, sync_log, and ns_pending_pushes.

Assembly build done but SO never fulfilled (WO/SO link broken)

Production completed the assembly build, finished goods are credited, but the SO still shows "Pending Fulfillment". The parent_so_id link likely got dropped. Resync the WO; rebuild the link manually if needed.

Drop-ship PO sent but vendor never shipped

PO transmitted, no Item Receipt after the expected lead time. Customer-side SO blocked. Vendor outreach required; check whether a vendor bill was prematurely entered (sometimes a sign of receipt confusion).

Invoice 60+ days past due, dunning hasn't fired

Aging bucket flipped but no dunning email staged. Check ar_aging_action_plan workflow status in workflow_run_log — likely either the cron didn't run or the workflow definition is disabled.

Short-pick / payment mismatch

Pick list 50, picker finds 48 → short-ship + customer note. Or customer pays unexpected amount → payment_applications can't auto-match → manual reconciliation queue for Marie/Lewis.

Related

Adjacent flows + diagrams

For developers

Code paths + invariants

ConcernWhere
Intake mailboxesorders@globalfoodsolutions.co, danielle@globalfoodsolutions.co
Inbound email pipelinesrc/email.ts
SO mirror tabletransactions WHERE type='SalesOrd'
Line mirrorso_lines / transaction_lines
Path 2 linkworkorder.parent_so_id → transactions.id
Path 3 linkpurchaseorder.parent_so_id → transactions.id
Convergenceitemfulfillment (all 3 paths)
Finance alert (STUB verify)NS workflow on itemfulfillment.status='Shipped'
Invoice + ARCustInvc, invoice_lines, v_customer_ar_aging
Dunning triggerar_aging_action_plan (workflow_definitions)
Events emittedorder.* , ar.* , customer.health_changed
Sync tierhot (2 min) for SO / WO / PO / fulfillment / invoice / payment
STUB — EDI 850no AS2 endpoint, no X12 parser wired to /api/so/create
STUB — auto path classifieroperator decides today; no automated routing
// Path classification (intent — today operator-driven) function classifyLine(line) { const item = items[line.item_code]; if (item.itemtype === 'assembly') return 'path_2_assembly'; if (item.dropship || item.special_order) return 'path_3_dropship'; return 'path_1_inventory'; } // Records linked end-to-end via parent_so_id // Path 2: workorder.parent_so_id = so.id // Path 3: purchaseorder.parent_so_id = so.id // ★ THE handoff — fires on every Item Fulfillment if (itemfulfillment.status === 'Shipped') { fireFinanceAlert({ fulfillment_id, so_id, customer_id, total }); }
Changelog

Dated trail · spot stale claims

Dated trail of when this doc was last touched, what changed, and what to look at if it feels stale.

DateRoundChangeTouched by
2026-05-26R592Split into master + 3 path-detail docs; added Customer PO# threading schema. Master is now the dispatcher; so_lifecycle_inventory_path, so_lifecycle_assembly_path, so_lifecycle_dropship_path sub-contracts each have their own deep diagram + wiki. WO customer PO# field flagged TBD pending Mike's review.Mike + Claude
2026-05-26R589Rewrote to match Mike's actual 3-path SO process. Replaced generic 11-phase "intake → credit check → EDI" with email intake (orders@ + danielle@) → SO review/entry → branch by item type (inventory / assembly / dropship) → Item Fulfillment converge → ★ automation alert to Finance → invoice / dunning / close. Records linked end-to-end via parent_so_id on WO + PO.Mike + Claude
2026-05-26R586Added CHANGELOG · SCHEMA · RUNBOOK · BACKLOG sections — wiki became best-in-class operating documentation.Mike + Claude
2026-05-25R584/R585Wiki originally shipped — 8-section structure (hero / what / when / steps / outcomes / failure-modes / related / for-developers).Mike + Claude
If today is more than 60 days past the latest changelog row, treat live system behavior as the source of truth. The doc may have drifted — verify against the workflow contract in workflow_definitions WHERE workflow_type='sales_order_lifecycle' before acting on these claims.
Schema · data contract

The machine-readable spec

Canonical fields, table names, endpoint signatures. What code should match, what tests should assert. Master workflow_type · sales_order_lifecycle · risk_level · 2. Sub-contracts: so_lifecycle_inventory_path (risk 2), so_lifecycle_assembly_path (risk 3), so_lifecycle_dropship_path (risk 3).

Customer PO# threading (THE trace thread across every connected NS record)

The customer PO# (entered as otherrefnum on the SO) threads through every connected NS record. Grep this single field across the whole lifecycle to find every record for a given customer order.

NS recordField carrying customer PO#Sample value
Sales OrderbodyFields.otherrefnum (thread origin)"72622"
Purchase Order (Path 3)bodyFields.memo + lineFields.links[].tranid"72622" and "1217 / 72622"
InvoicebodyFields.otherrefnum"72622"
Vendor Bill (Path 3)bodyFields.memo, bodyFields.tranid, bodyFields.initialtranid"72622", "1217 / 72622"
Work Order (Path 2)TBD — Mike review pending; expected pattern bodyFields.memo"72622" (TBD)
Item Fulfillmentinherits from SO via NS standard linkage; otherrefnum carries through"72622"
Item Receipt (Path 3)inherits from PO via NS standard linkagetraces via createdfrom = po.id

Trace recipe: grep "72622" across transactions.otherrefnum + purchase_orders.memo + customer_invoices.otherrefnum + vendor_bills.memo + vendor_bills.tranid + item_fulfillments.otherrefnum lights up every record in the SO chain. WO field TBD pending Mike's review.

Inbound sources (where orders come from)

SourceKindStatus
orders@globalfoodsolutions.coemail mailbox · primary customer order intakeREAL
danielle@globalfoodsolutions.coemail mailbox · secondary customer order intakeREAL
NS UIdirect manual entry by CS deskREAL
customer portalportal upload formREAL
EDI 850X12 transmissionSTUB auto-parse (manual keying today)

Inputs (required + optional)

FieldTypeDescription
customer_idintegerNS customer internal ID. Required.
items[]arrayOrder lines: item_code, qty, rate, requested_ship_date. Required.
shipping_requirementsobjectShip-to, carrier, delivery window. Required.
special_instructionsstringNotes the team needs to honor.
email_thread_idstringOptional. Trace back to inbound email.

D1 tables touched (across the 3 paths)

TablePathOperation
transactions (SalesOrd)commonUPSERT · hot-tier 2min sync
so_lines / transaction_linescommonDELETE + INSERT · delete_by_parent
work_ordersPath 2 onlyINSERT with parent_so_id
assembly_buildsPath 2 onlyINSERT on build completion
purchase_ordersPath 3 onlyINSERT with parent_so_id
item_receiptsPath 3 onlyINSERT when vendor ships
vendor_billsPath 3 sidecarINSERT · separate cycle
item_fulfillmentsconvergenceINSERT · all 3 paths land here
customer_invoicesFinanceINSERT (CustInvc) after Finance review
invoice_linesFinanceINSERT per shipped line
customer_paymentsFinanceINSERT (CustPymt) on remit
payment_applicationsFinanceINSERT · payment → invoice match
v_customer_ar_agingFinanceVIEW · dunning trigger source
eventsallso.created, fulfillment.completed, finance.alerted, order.closed

Endpoints called

MethodPathPurpose
POST/api/so/createSO orchestration (today manual; EDI auto-parse STUB)
GET/api/salesorder/:idLive SO lookup
POST/api/sync/run?table=transactionsForce hot-tier sync
POST/api/proposed-actions/bulk-decideHITL approve assembly build / dunning email

Events fired

event_typeWhenSubscribers
so.createdSO posted in NS, mirrored to D1customer_health, classifier
workorder.createdPath 2: WO created FROM SOproduction_admin, inventory
purchaseorder.createdPath 3: PO created FROM SOvendor_admin, inventory
itemfulfillment.completedAll 3 paths converge — ★ triggers Finance alertFinance
finance.alertedNS workflow notification deliveredFinance review queue
invoice.postedFinance posts CustInvcAR + analytics + customer_health
ar.bucket_movedInvoice ages into new bucketar_aging_action_plan
order.closedPayment applied, SO closedcustomer_health, analytics
Runbook · when it breaks

It broke at 2am — what now

Different from "how do I use this." This is the page Mike pulls up when something is wrong: logs to check, recovery steps, who to escalate to.

Scenario · SO entered but Finance never got the alert (the handoff)

Item Fulfillment closed, but Finance's review queue is empty. The automation alert didn't deliver.

  1. Check NS workflow execution: NS workflow studio → sales_order_fulfillment_alert → execution log for this fulfillment.
  2. Check sync_log: SELECT * FROM sync_log WHERE table_name='itemfulfillment' AND created_at > datetime('now','-1 hour')
  3. Check ns_pending_pushes: SELECT * FROM ns_pending_pushes WHERE record_type='itemfulfillment_alert' ORDER BY created_at DESC LIMIT 10
  4. Fall back: manually notify Finance with the fulfillment ID; investigate workflow trigger config in NS afterwards.

Scenario · Assembly build completed but SO never fulfilled (WO/SO link broken)

Path 2 broke: production finished the build, finished goods are credited, but the SO still shows "Pending Fulfillment".

  1. Verify the link: SELECT id, parent_so_id, status FROM transactions WHERE type='WorkOrd' AND id=?
  2. If parent_so_id is NULL: patch with UPDATE transactions SET parent_so_id=? WHERE id=? and force resync.
  3. Force SO resync: POST /api/sync/run?tier=hot&table=transactions&id=<so_id>
  4. Trigger fulfillment manually in NS: from the SO, hit "Fulfill" with the assembly already on hand.

Scenario · Drop-ship PO sent but vendor didn't ship

Path 3 stalled: PO transmitted, no Item Receipt after the expected lead time, customer waiting.

  1. Check PO status: SELECT id, status, trandate FROM transactions WHERE type='PurchOrd' AND parent_so_id=?
  2. Vendor outreach: contact vendor with PO number, confirm ship date or escalate to alternate source.
  3. Check vendor bill prematurely entered: SELECT * FROM vendor_bills WHERE po_id=? — if present, that's a sign of receipt confusion. Pause and reconcile.
  4. If shipped from alternate source: close original PO, post Item Receipt against the new source.

Scenario · Invoice past due 60+ days, dunning hasn't fired

AR aging shows the bucket flipped, but no dunning email is staged.

  1. Check workflow status: SELECT * FROM workflow_run_log WHERE workflow_type='ar_aging_action_plan' AND created_at > datetime('now','-7 days') ORDER BY created_at DESC
  2. Check definition enabled: SELECT workflow_type, enabled FROM workflow_definitions WHERE workflow_type='ar_aging_action_plan'
  3. Check cron lock: SELECT * FROM cron_locks WHERE cron_name LIKE '%ar_aging%' — release if stuck.
  4. Manual fire: POST /api/workflow/run?workflow_type=ar_aging_action_plan&customer_id=?

Logs to check

Kill switch · emergency stop

If this workflow is misbehaving in a high-impact way (creating bad proposed_actions in volume, pushing wrong things to NS), flip a kill switch:

See kill-switches-state-machine.html for the full state machine + recovery procedure.

Escalation

Primary: Mike Levine (single-admin) · mikelevine@globalfoodsolutions.co. For prolonged outage during business hours, notify warehouse lead + accounting lead so they can defer dependent work.

Backlog · open questions

What's not done · what's uncertain

What's not done, what's uncertain, what we punted. Captured so it survives context switches and doesn't die in someone's head.