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).
Trigger conditions
- Customer emails an order to
orders@globalfoodsolutions.coordanielle@globalfoodsolutions.co(the dominant intake channel). - Customer transmits EDI 850 — today lands in a mailbox and is keyed into NS by an operator (auto-parse STUB).
- Customer self-serves via the customer portal upload form.
- CS desk enters directly into NS UI from phone or fax.
- Bid award triggers a release order against a bid contract (e.g. B5875 weekly draws).
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.
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).
Driscoll weekly mixed-path order
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.
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)
-
01
Receive customer order
Most orders arrive by email at
orders@globalfoodsolutions.coordanielle@globalfoodsolutions.co. Other channels: NS UI direct entry, customer portal, EDI 850 (manual keying today, auto-parse STUB), phone keyed by CS. -
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.
-
03
Enter SO in NetSuite
SO posted as system-of-record. D1 mirror lands on the next 2-min hot-tier sync.
-
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.
Path 1 — Fulfill from inventory
-
1.1
Allocate from inventory
Check
inventory_balance.quantityavailable≥ line qty at the fulfillment location. Reserve qty against the SO. -
1.2
Pick, pack, stage
Warehouse executes the pick list (scan + label), palletizes, and stages for carrier pickup.
-
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)
-
2.1
Create Work Orders FROM the SO
For each assembly-build line, a Work Order is created with
parent_so_idlinking 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. -
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. -
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)
-
3.1
Create Purchase Order FROM the SO
For each drop-ship line, a PO is created with
parent_so_idlinking it back to the originating Sales Order. PO is transmitted to the vendor. -
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.
-
3.3
SO fulfilled to customer · Item Fulfillment completed
★ Automation alert fires to Finance. Continues to the Finance steps below.
-
3.4
Vendor bill cycle (parallel)
Separate from the customer-side invoice cycle: vendor bill entered against the PO, paid per vendor terms.
★ Finance steps (every path converges here)
-
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).
-
F2
Finance reviews fulfillment
Finance checks fulfilled qty matches expected, pricing carried through, customer terms + tax handling correct, short-ships flagged.
-
F3
Invoice customer
Finance posts
CustInvcusing shipped qty (not ordered qty). AR balance updates. -
F4
Invoice open · monitor payment terms
Invoice remains open until payment received.
v_customer_ar_agingtracks the bucket (current / 30 / 60 / 90+). -
F5
If past due → dunning fires
The
ar_aging_action_planworkflow 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. -
F6
Payment received · apply · close
Customer remits;
CustPymtposted;payment_applicationsmatches payment to invoice(s). SO closes;events.order.closedfires for the customer health watcher.
What's different after the cycle
- Customer received goods; we received payment (eventually).
- AR aging buckets reflect the current state of every invoice.
- Customer health score updated with new velocity + DSO data.
- Events ledger has a full audit trail per SO — replayable for any consumer.
What can go wrong
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.
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.
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.
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).
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.
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.
Adjacent flows + diagrams
Code paths + invariants
| Concern | Where |
|---|---|
| Intake mailboxes | orders@globalfoodsolutions.co, danielle@globalfoodsolutions.co |
| Inbound email pipeline | src/email.ts |
| SO mirror table | transactions WHERE type='SalesOrd' |
| Line mirror | so_lines / transaction_lines |
| Path 2 link | workorder.parent_so_id → transactions.id |
| Path 3 link | purchaseorder.parent_so_id → transactions.id |
| Convergence | itemfulfillment (all 3 paths) |
| Finance alert (STUB verify) | NS workflow on itemfulfillment.status='Shipped' |
| Invoice + AR | CustInvc, invoice_lines, v_customer_ar_aging |
| Dunning trigger | ar_aging_action_plan (workflow_definitions) |
| Events emitted | order.* , ar.* , customer.health_changed |
| Sync tier | hot (2 min) for SO / WO / PO / fulfillment / invoice / payment |
| STUB — EDI 850 | no AS2 endpoint, no X12 parser wired to /api/so/create |
| STUB — auto path classifier | operator decides today; no automated routing |
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.
| Date | Round | Change | Touched by |
|---|---|---|---|
2026-05-26 | R592 | Split 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-26 | R589 | Rewrote 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-26 | R586 | Added CHANGELOG · SCHEMA · RUNBOOK · BACKLOG sections — wiki became best-in-class operating documentation. | Mike + Claude |
2026-05-25 | R584/R585 | Wiki originally shipped — 8-section structure (hero / what / when / steps / outcomes / failure-modes / related / for-developers). | Mike + Claude |
workflow_definitions WHERE workflow_type='sales_order_lifecycle' before acting on these claims.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 record | Field carrying customer PO# | Sample value |
|---|---|---|
| Sales Order | bodyFields.otherrefnum (thread origin) | "72622" |
| Purchase Order (Path 3) | bodyFields.memo + lineFields.links[].tranid | "72622" and "1217 / 72622" |
| Invoice | bodyFields.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 Fulfillment | inherits from SO via NS standard linkage; otherrefnum carries through | "72622" |
| Item Receipt (Path 3) | inherits from PO via NS standard linkage | traces 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)
| Source | Kind | Status |
|---|---|---|
orders@globalfoodsolutions.co | email mailbox · primary customer order intake | REAL |
danielle@globalfoodsolutions.co | email mailbox · secondary customer order intake | REAL |
| NS UI | direct manual entry by CS desk | REAL |
| customer portal | portal upload form | REAL |
| EDI 850 | X12 transmission | STUB auto-parse (manual keying today) |
Inputs (required + optional)
| Field | Type | Description |
|---|---|---|
customer_id | integer | NS customer internal ID. Required. |
items[] | array | Order lines: item_code, qty, rate, requested_ship_date. Required. |
shipping_requirements | object | Ship-to, carrier, delivery window. Required. |
special_instructions | string | Notes the team needs to honor. |
email_thread_id | string | Optional. Trace back to inbound email. |
D1 tables touched (across the 3 paths)
| Table | Path | Operation |
|---|---|---|
transactions (SalesOrd) | common | UPSERT · hot-tier 2min sync |
so_lines / transaction_lines | common | DELETE + INSERT · delete_by_parent |
work_orders | Path 2 only | INSERT with parent_so_id |
assembly_builds | Path 2 only | INSERT on build completion |
purchase_orders | Path 3 only | INSERT with parent_so_id |
item_receipts | Path 3 only | INSERT when vendor ships |
vendor_bills | Path 3 sidecar | INSERT · separate cycle |
item_fulfillments | convergence | INSERT · all 3 paths land here |
customer_invoices | Finance | INSERT (CustInvc) after Finance review |
invoice_lines | Finance | INSERT per shipped line |
customer_payments | Finance | INSERT (CustPymt) on remit |
payment_applications | Finance | INSERT · payment → invoice match |
v_customer_ar_aging | Finance | VIEW · dunning trigger source |
events | all | so.created, fulfillment.completed, finance.alerted, order.closed |
Endpoints called
| Method | Path | Purpose |
|---|---|---|
POST | /api/so/create | SO orchestration (today manual; EDI auto-parse STUB) |
GET | /api/salesorder/:id | Live SO lookup |
POST | /api/sync/run?table=transactions | Force hot-tier sync |
POST | /api/proposed-actions/bulk-decide | HITL approve assembly build / dunning email |
Events fired
| event_type | When | Subscribers |
|---|---|---|
so.created | SO posted in NS, mirrored to D1 | customer_health, classifier |
workorder.created | Path 2: WO created FROM SO | production_admin, inventory |
purchaseorder.created | Path 3: PO created FROM SO | vendor_admin, inventory |
itemfulfillment.completed | All 3 paths converge — ★ triggers Finance alert | Finance |
finance.alerted | NS workflow notification delivered | Finance review queue |
invoice.posted | Finance posts CustInvc | AR + analytics + customer_health |
ar.bucket_moved | Invoice ages into new bucket | ar_aging_action_plan |
order.closed | Payment applied, SO closed | customer_health, analytics |
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.
- Check NS workflow execution: NS workflow studio → sales_order_fulfillment_alert → execution log for this fulfillment.
- Check sync_log:
SELECT * FROM sync_log WHERE table_name='itemfulfillment' AND created_at > datetime('now','-1 hour') - Check ns_pending_pushes:
SELECT * FROM ns_pending_pushes WHERE record_type='itemfulfillment_alert' ORDER BY created_at DESC LIMIT 10 - 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".
- Verify the link:
SELECT id, parent_so_id, status FROM transactions WHERE type='WorkOrd' AND id=? - If parent_so_id is NULL: patch with
UPDATE transactions SET parent_so_id=? WHERE id=?and force resync. - Force SO resync:
POST /api/sync/run?tier=hot&table=transactions&id=<so_id> - 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.
- Check PO status:
SELECT id, status, trandate FROM transactions WHERE type='PurchOrd' AND parent_so_id=? - Vendor outreach: contact vendor with PO number, confirm ship date or escalate to alternate source.
- Check vendor bill prematurely entered:
SELECT * FROM vendor_bills WHERE po_id=?— if present, that's a sign of receipt confusion. Pause and reconcile. - 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.
- 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 - Check definition enabled:
SELECT workflow_type, enabled FROM workflow_definitions WHERE workflow_type='ar_aging_action_plan' - Check cron lock:
SELECT * FROM cron_locks WHERE cron_name LIKE '%ar_aging%'— release if stuck. - Manual fire:
POST /api/workflow/run?workflow_type=ar_aging_action_plan&customer_id=?
Logs to check
workflow_run_log· top-level run auditworkflow_step_log· per-step traceworkflow_verify_results· post-window verify outcomescron_locks· stuck cron lock detectionevents· workflow.completed / workflow.failed event trailreflexion_log· per-run narrative (if reflexion_enabled)npx wrangler tail· live Worker logs
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:
kill:ns_writes· stops every NS push platform-widekill:proposed_apply· stops HITL approvals from executing fan-outkill:high_risk_ops· stops risk_level >= 4 fan-out
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.
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.
-
STUB
EDI 850 auto-intake
EDI orders today land in a mailbox and are keyed into NS by hand. No AS2 endpoint, no X12 parser wired to
/api/so/create. Wire as a Cloudflare Worker once volume justifies. -
STUB
Auto-classify which path applies (inventory / assembly / dropship)
Today operators classify each line by item type mentally. Classifier could surface mixed-path orders earlier and auto-stage WO + PO from the SO via a single fan-out.
-
STUB
Verify the Finance automation alert in code
The "automation alert → Finance" is a documented concept. The NS workflow trigger on
itemfulfillment.status='Shipped'needs code-level confirmation. Check NS workflow studio config and walk one fulfillment end-to-end. -
DECISION
Add
finance_invoice_reviewas the 27th v2 workflow contract?The Finance steps (review → invoice → monitor → dunning → payment apply → close) currently live as fan-out targets of
sales_order_lifecycle. Could be promoted to a standalone workflow contract that bridges fulfillment and AR. Pending Mike's call. -
DECISION
Credit-check precondition: keep block or keep warn?
Mike's documented process does not show an explicit credit-check gate. The current contract has
under_credit_limitas a warn precondition. Confirm with Mike whether NS-side enforces this or whether it should be removed. -
OPEN
NS webhook for real-time SO sync
Today 2min polling. Webhook would shrink the email-to-D1-mirror gap.
-
DEFER
SO profitability snapshot at SO creation
Capture margin at SO creation time, not just at month-end. Useful for Finance review step.