Wiki · NetSuite expansion

NS sales order lifecycle

Eleven beats from "SO intake" to "customer health signal". The full order-to-cash cycle, with the AR aging tail that feeds churn prediction. One STUB: EDI 850 auto-parse — today EDI lands in NS by hand.

Mixed · 10 real, 1 STUB
What this is

Order-to-cash, with the health tail

The NS sales order lifecycle is the canonical revenue flow — from "Driscoll emails an order" to "Driscoll pays the invoice" to "their churn risk score updates". Eleven phases, because we don't stop at payment; the AR aging and customer health signals are part of the same flow.

The platform's job here is mostly the bookends: intake validation and customer-health output. NS owns the middle — pick, pack, ship, invoice, payment. But every step writes events to the ledger, so consumers downstream (customer health watcher, anomaly detector, timeline UI) can react.

Diagram: ns-sales-order-lifecycle.html. One STUB: EDI 850 intake. We have customers (NYC DOE in particular) who can send orders via EDI 850, but the auto-parse isn't wired — the EDI file lands in a mailbox and an operator keys it into NS. The other three channels (manual NS UI, customer portal, email-to-order) are real.

When to use it

Trigger conditions

Approval gate

Auto-approve unless: margin < guardrail OR order_value > auto_threshold OR new customer. Otherwise status='Pending Approval' and a HITL card lands in Mike's queue.

Worked example

Driscoll weekly draw against B5875

Scenario

Thursday 14:00. Driscoll (NS customer #2147, 36.4% of revenue) emails the week's draw against the B5875 bid: 14 line items, $18,420. Inbound email triage workflow parses the body, matches lines against bid_lines, stages draft SO. CS desk confirms; SO posts. Customer validation: credit_limit $250K, current AR $42K, not on hold — pass. Inventory check: all 14 lines have sufficient on-hand at the NJ DC. Pricing applies the bid contract; margin rolls up at 18.4% — above guardrail. Order value over $15K threshold → Pending Approval. Mike taps Approve at 14:08 (knows Driscoll, knows the bid). Pick list prints at the warehouse 14:09.

Friday: pack + ship; itemfulfillment closes. CustInvc posted Friday EOD — $18,420 hits AR. Net 30 terms; payment expected June 22. Aging bucket: current. Customer health watcher consumes order.shipped and the 30-day rolling order_velocity stays flat (Driscoll's normal cadence). Health score unchanged.

Contrast scenario: payment doesn't arrive until July 8. AR aging bucket flips to "30-60". ar.bucket_moved event fires. Customer health watcher recomputes: order_velocity stable but DSO trending up; health score nudges from "stable" to "watch". Mike sees the watch flag on Monday's recap.

Step-by-step what happens

The eleven beats

  1. 01

    SO intake — four channels

    Sales orders arrive via four channels. Manual NS UI is most common; email/portal/EDI are expanding. STUB: EDI 850 auto-parse — today EDI files land in a mailbox and an operator keys them into NS. No EDI X12 parser is wired; no AS2 endpoint is configured.

    Channels NS UI, portal, email, EDI
    STUB EDI 850 auto-parse
  2. 02

    Customer validation

    Lookup the customers row, check credit_limit, check hold_status, enforce service hold rules. If on hold, SO stages with status='On Hold' and a HITL card stages for review.

    Reads customers
  3. 03

    Inventory check

    Per so_lines.item, validate quantityavailable at the fulfillment location. Short-pick flags raised; backorder logic surfaces. A short SO can split into shippable + backordered fragments.

    Reads inventory_balance
  4. 04

    Pricing application

    Lookup pricing_master + customer_programs to apply the right tier and discounts. Compute line margin and roll up to order margin. Bid customers pick up their bid_line price, not the list price.

    Reads pricing_master, customer_programs, bid_lines
  5. 05

    SO approval gate

    If margin < guardrail OR order_value > auto_threshold OR new customer, status='Pending Approval'. Otherwise auto-approve. The HITL card carries margin breakdown and customer recap for fast decisions.

    HITL conditional
    Writes so.status
  6. 06

    Pick

    Pick list generated at the warehouse. NS itemfulfillment record opens with status='In Progress'. Pickers walk the list, mark exceptions (damaged, short).

    Writes NS itemfulfillment
  7. 07

    Pack + ship

    Fulfillment record closes; itemfulfillment.status='Shipped'. Inventory decremented per shipped qty. BOL and ASN generated. The carrier-portal handoff is manual today.

    Writes itemfulfillment, inventory_balance--
  8. 08

    Invoice

    invoice_lines generated from so_lines using shipped qty, not ordered qty. NS CustInvc posted; AR balance updates. Invoice mirrored to D1 on the warm tier.

    Writes NS CustInvc, invoice_lines
  9. 09

    Payment

    Customer remits; NS CustPymt posted. payment_applications row matches the payment to one or more invoices. Short pays and overpays surface for manual reconciliation.

    Writes NS CustPymt, payment_applications
  10. 10

    AR aging

    v_customer_ar_aging view recomputes from invoice + payment activity. Buckets: current, 30, 60, 90+. ar.bucket_moved events fire when an invoice ages into a new bucket.

    View v_customer_ar_aging
    Emits ar.bucket_moved
  11. 11

    Customer health signal

    order_velocity_decline baseline updates. customer_health_predictor model recomputes churn risk. Signal surfaces to chat tools + admin-dashboard. Health changes (stable → watch → at-risk) emit customer.health_changed.

    Writes customer_health
    Emits customer.health_changed
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 for customers expecting same-day acknowledgment. Mitigation: limited EDI customers today (mostly NYC DOE). Long-term: AS2 endpoint + X12 parser as a Cloudflare Worker.

Short-pick — inventory drift caught here

Pick list says 50 cases, picker finds 48. This is usually where inventory drift surfaces. Recovery: ad-hoc cycle count on the affected SKU; short-ship the SO with customer notification.

Payment short-pay or over-pay

Customer pays an unexpected amount. payment_applications can't auto-match. Manual reconciliation queue surfaces these for Marie/Lewis.

SO approval HITL deferred past pick window

Mike doesn't approve in time; warehouse blocks. The CS desk has a fallback: revise the SO to under the auto-threshold by splitting, or escalate. Detection: pick_list_pending dashboard tile.

Related

Adjacent flows + diagrams

For developers

Code paths + invariants

ConcernWhere
SO mirror tabletransactions WHERE recordtype='salesorder'
Line mirrorso_lines (1:N from transactions)
Pricing sourcepricing_master, customer_programs, bid_lines
Approval thresholdsguardrails.so_margin_min, so_value_auto
AR viewv_customer_ar_aging
Health modelcustomer_health_predictor (R563)
Events emittedorder.* , ar.* , customer.health_changed
Sync tierhot (2 min) for SO/invoice/payment
STUB — EDI 850no AS2 endpoint, no X12 parser
// Approval gate (simplified) const needsApproval = margin < guardrails.so_margin_min || order_value > guardrails.so_value_auto || isNewCustomer(customer_id); if (needsApproval) { so.status = 'Pending Approval'; stageProposedAction({ action_type: 'so_approval', risk_level: 2, payload: { so_id, margin, order_value } }); }