Actual process flows mined from nexttransactionlinelink and transactionline.createdfrom — not the process as designed, but the process as executed. Edge thickness = document volume · edge labels = median days between documents · red = rework loops.
Of 788 sales orders, 720 (91.4%) completed the textbook happy path SO→IF→INV→PMT — and did so astonishingly fast (median 0 days order-to-cash). The process problem is not the flow that runs; it's what never enters it: $769,920 of A/R sits on standalone invoices created outside any sales order (median age 157 days, zero payments), and cash-sale receipts float undeposited for a median 13 days because deposits are batched monthly.
Node = document type (with count). Edge thickness ∝ √volume of distinct document pairs; label = pairs · median days. NetSuite links both the fulfillment and the invoice back to the sales order, so both thick edges originate at SO. Rework loops in red; the amber edge is the deposit-float problem.
NetSuite records lineage from the sales order to both the fulfillment (ShipRcpt/PickPack) and the invoice (OrdBill + OrdRvCom) — invoices do not link to fulfillments, so both thick edges fan out of SO. Payment links (Payment linktype) connect invoice → customer payment. The retail lane is a separate stream: 1,077 POS cash sales settle straight to Undeposited Funds and wait for a monthly batch deposit.
Each of the 788 sales orders was assigned a variant signature from its full downstream document set (two lineage hops). Eleven distinct variants emerged. One dominates.
| # | Variant path | Orders | Share | Distribution | Median $ / order | Total value | Med. SO→cash | Reading |
|---|---|---|---|---|---|---|---|---|
| 1 | SO→IF→INV→PMT | 720 | 91.4% | $305 | $1,978,795 | 0 d | HAPPY PATH | |
| 2 | SO (no downstream docs) | 45 | 5.7% | $1,627 | $127,213 | — | NOT STARTED 13 pending approval, 32 pending fulfillment | |
| 3 | SO→IF | 9 | 1.1% | $151 | $2,206 | — | SHIPPED, NOT BILLED revenue leakage risk | |
| 4 | SO→IF→INV | 7 | 0.9% | $13,842 | $138,229 | — | BILLED, UNPAID the 7 big-ticket DC invoices | |
| 5 | SO→IF→INV→PMT +RA | 1 | 0.1% | $258 | $258 | 3 d | RETURN LOOP paid, then RMA opened | |
| 6 | SO→IF→INV→PMT +RA+CM | 1 | 0.1% | $361 | $361 | 3 d | RETURN LOOP full loop: RMA → credit memo | |
| 7 | SO→IF +RA | 1 | 0.1% | $361 | $361 | — | RETURN, NEVER BILLED | |
| 8 | SO→INV→PMT (no IF) | 1 | 0.1% | $1,085 | $1,085 | 0 d | BILL-ONLY non-ship order | |
| 9 | WO→SO→IF→INV→PMT | 1 | 0.1% | $1,365 | $1,365 | 2 d | SPECIAL ORDER build-to-order, completed | |
| 10 | PO→SO→IF→INV→PMT | 1 | 0.1% | $356 | $356 | 4 d | DROP-SHIP completed | |
| 11 | PO→SO→IF | 1 | 0.1% | $2,271 | $2,271 | — | DROP-SHIP, NOT BILLED |
Stage-by-stage lag distribution across every measured document pair. The happy path is same-day at every hop — cycle time dies in four specific places, none of them on the main line.
| Stage | Pairs | Median | Avg | Lag distribution (same-day / 1–7 / 8–30 / >30d) | >30 d | Worst case |
|---|---|---|---|---|---|---|
| SO→Fulfillment | 742 | 0 d | 1.6 | 19 | 365 d (SO3910) | |
| SO→Invoice | 732 | 0 d | 0.6 | 0 | 18 d | |
| Invoice→Payment | 736 | 0 d | 1.7 | 7 | 272 d (INV787) | |
| CashSale→Deposit | 1,026 | 13 d | 13.1 | 0 | 24 d | |
| PO→Item Receipt | 722 | 0 d | 0.0 | 0 | 3 d | |
| PO→Vendor Bill | 705 | 1 d | 1.0 | 0 | 3 d | |
| Bill→Vendor Pymt | 1,005 | 3 d | 3.5 | 7 | 345 d (VB01) |
Orders that entered the funnel but have not exited, as of 2026-08-27 — the live work queue implied by the lineage graph.
| Bucket | NS status | Orders | Value | Median age | Oldest | Action owner |
|---|---|---|---|---|---|---|
| Never started no IF, no INV | Pending Fulfillment (B) | 32 | $71,321 | 13 d | 2026-05-15 | Warehouse ops |
| Pending Approval (A) | 13 | $55,892 | 16 d | 2026-08-01 | Sales management | |
| Shipped, not invoiced IF exists, no INV | Pending Fulfillment (B) | 7 | $1,303 | 2 d | 2026-06-05 | Billing |
| Pending Billing (F) | 3 | $2,937 | 20 d | 2026-08-01 | ||
| Part. Fulfilled (E) | 1 | $596 | 15 d | 2026-08-11 | ||
| Invoiced, not paid INV exists, no PMT | Billed (G) · invoice open | 7 | $138,229 | 13 d | 2026-05-01 | Collections |
Every backward-flowing document chain in the account, sales and purchasing side. Volume is tiny (rework is not this account's problem) — but each loop is fully traceable and all completed within 5 days.
| Chain | Days | Value |
|---|---|---|
| SO4266→RMA23→CM06 + IR1175 | 4 | $155 |
| SO4267→RMA24→IR1178 (no credit yet) | 1 | $103 |
| SO4268→RMA25 (open) | 1 | $155 |
| SO4268→RMA26 (open · 2nd RMA, same order) | 5 | $52 |
| CS1072→CR01 (cash refund) | 0 | $716 |
| Chain | Days | Value |
|---|---|---|
| PO1155→VRMA21→IF5702 (ship-back) | 2 | $150 |
| PO1156→VRMA22 | 1 | $120 |
| PO1156→VRMA23 (2nd VRMA, same PO) | 9 | $30 |
| PO1158→VRMA20→IF5701 + VendCred | 1 | $90 |
The purchasing side runs the same lineage discipline, tighter: 97.6% of POs are received, 95.3% billed, and bills pay in a median 3 days.
Anomaly worth noting: Bill→Payment shows 1,005 pairs against 740 POs because vendor payments frequently settle bills in batches and some bills (standalone, non-PO) enter the payment flow without PO lineage — 312 of 1,017 vendor bills have no PO parent. The 6 giant aging bills (VB01–VB07, $231K combined) are all in that standalone set — the payables mirror of the standalone-invoice finding.
Ranked by dollar impact per unit of effort, straight from the lineage evidence.
21 fully-unpaid + 11 partially-paid invoices with no SO parent, median 157 days. They are invisible to order-to-cash dashboards because they never enter the SO funnel. Work the list top-down by amount; then require SO linkage (or an explicit exemption reason code) for future manual invoices.
13 August orders pending approval, median 16 days, in an account where everything else happens same-day. Add an auto-approval threshold (e.g. <$1K, existing customer, in-terms) and a 48-hour SLA alert on the rest.
All POS cash waits for one batch deposit around the 24th. Weekly (or daily-sweep) deposits cut the median float ~10 days, tighten bank reconciliation, and shrink the undeposited-funds balance that currently spikes to ~$6.6K per cycle.
11 orders shipped but never billed (variants 3 and 11). Small dollars today, but this variant is pure revenue leakage — a weekly saved search on fulfilled lines where quantitybilled < quantityshipped keeps it at zero.
RMA24/25/26: goods received, no credit memo issued. Small amounts ($310 total) but they are an unbooked customer liability and will distort the return-cycle metric as they age.
SO4270–SO4287 (May–Jul 2026) all shipped in 46–49 days — likely one stock-out event; confirm and post-mortem. Separately, decide deliberately on VB01–VB07 ($231K, 139–345 days old): dispute, schedule, or pay — aging silently is the worst option.
Everything below is reproducible in the SuiteQL Query Tool. All queries ran against production on 2026-08-27 under Tim Dietrich / Administrator.
previoustype/nexttype/previousdate/nextdate/linktype). The header-level nexttransactionlink table holds only 22 rows in this account — it is not a usable volume source here.transactionline.createdfrom counts (752 ItemShip, 736 CustPymt, 732 CustInvc, 727 ItemRcpt, 705 VendBill…) corroborate the link-table doc-pair counts within a few documents. Note transaction.createdfrom is not exposed to SuiteQL at the header level in this account — the line-level column is the queryable one.MEDIAN(); day lags are calendar days between transaction dates.foreignamountpaid confirmed only 39 invoices are genuinely open, matching the lineage view.foreigntotal = base).SELECT linktype, COUNT(*) AS cnt FROM nexttransactionlinelink GROUP BY linktype ORDER BY COUNT(*) DESC
SELECT previoustype || '>' || nexttype || '|' || linktype AS edge, COUNT(*) AS doc_pairs, MEDIAN(lag_days) AS median_days, ROUND(AVG(lag_days), 1) AS avg_days, MAX(lag_days) AS max_days FROM ( -- collapse line-level links to distinct document pairs SELECT previoustype, nexttype, linktype, previousdoc, nextdoc, MIN(nextdate - previousdate) AS lag_days FROM nexttransactionlinelink GROUP BY previoustype, nexttype, linktype, previousdoc, nextdoc ) GROUP BY previoustype, nexttype, linktype ORDER BY COUNT(*) DESC
WITH edges AS ( SELECT previousdoc, nexttype, nextdoc, MIN(previousdate) AS pd, MIN(nextdate) AS nd FROM nexttransactionlinelink GROUP BY previousdoc, nexttype, nextdoc ), per_so AS ( SELECT so.id, so.trandate AS so_date, so.status AS so_status, ABS(so.foreigntotal) AS order_total, COUNT(DISTINCT CASE WHEN e1.nexttype = 'ItemShip' THEN e1.nextdoc END) AS if_cnt, COUNT(DISTINCT CASE WHEN e1.nexttype = 'CustInvc' THEN e1.nextdoc END) AS inv_cnt, COUNT(DISTINCT CASE WHEN e1.nexttype = 'RtnAuth' THEN e1.nextdoc END) AS ra_cnt, COUNT(DISTINCT CASE WHEN e1.nexttype = 'WorkOrd' THEN e1.nextdoc END) AS wo_cnt, COUNT(DISTINCT CASE WHEN e1.nexttype = 'PurchOrd' THEN e1.nextdoc END) AS po_cnt, COUNT(DISTINCT CASE WHEN e1.nexttype = 'CustInvc' AND e2.nexttype IN ('CustPymt','DepAppl') THEN e2.nextdoc END) AS pmt_cnt, COUNT(DISTINCT CASE WHEN e1.nexttype = 'RtnAuth' AND e2.nexttype = 'CustCred' THEN e2.nextdoc END) AS cm_cnt, MIN(CASE WHEN e1.nexttype = 'CustInvc' AND e2.nexttype IN ('CustPymt','DepAppl') THEN e2.nd END) - so.trandate AS lag_so_cash FROM transaction so LEFT JOIN edges e1 ON e1.previousdoc = so.id LEFT JOIN edges e2 ON e2.previousdoc = e1.nextdoc WHERE so.type = 'SalesOrd' GROUP BY so.id, so.trandate, so.status, ABS(so.foreigntotal) ) SELECT 'SO' || CASE WHEN wo_cnt > 0 THEN '+WO' ELSE '' END || CASE WHEN po_cnt > 0 THEN '+PO' ELSE '' END || CASE WHEN if_cnt > 0 THEN '>IF' ELSE '' END || CASE WHEN inv_cnt > 0 THEN '>INV' ELSE '' END || CASE WHEN pmt_cnt > 0 THEN '>PMT' ELSE '' END || CASE WHEN ra_cnt > 0 THEN '+RA' ELSE '' END || CASE WHEN cm_cnt > 0 THEN '+CM' ELSE '' END AS variant, COUNT(*) AS orders, MEDIAN(lag_so_cash) AS med_so_cash, ROUND(MEDIAN(order_total), 0) AS med_value, ROUND(SUM(order_total), 0) AS total_value FROM per_so GROUP BY /* same signature expression */ ... ORDER BY COUNT(*) DESC
WITH edges AS ( -- as in Q3 ), per_so AS ( -- if_cnt / inv_cnt / pmt_cnt per SO, as in Q3 ) SELECT CASE WHEN if_cnt = 0 AND inv_cnt = 0 THEN 'A_not_started' WHEN if_cnt > 0 AND inv_cnt = 0 THEN 'B_shipped_not_invoiced' WHEN inv_cnt > 0 AND pmt_cnt = 0 THEN 'C_invoiced_not_paid' END AS bucket, status, COUNT(*) AS orders, ROUND(SUM(order_total), 0) AS value, MEDIAN(TRUNC(SYSDATE) - trandate) AS med_age_days FROM per_so WHERE (if_cnt = 0 AND inv_cnt = 0) OR inv_cnt = 0 OR pmt_cnt = 0 GROUP BY bucket_expr, status ORDER BY 1, COUNT(*) DESC
WITH pairs AS ( SELECT previoustype, nexttype, previousdoc, nextdoc, MIN(nextdate - previousdate) AS lag_days FROM nexttransactionlinelink WHERE (previoustype, nexttype) IN ( -- the 7 core stages ('SalesOrd','ItemShip'), ('SalesOrd','CustInvc'), ('CustInvc','CustPymt'), ('CashSale','Deposit'), ('PurchOrd','ItemRcpt'), ('PurchOrd','VendBill'), ('VendBill','VendPymt') ) GROUP BY previoustype, nexttype, previousdoc, nextdoc ) SELECT previoustype || '>' || nexttype AS stage, COUNT(*) AS pairs, MEDIAN(lag_days) AS p50, SUM(CASE WHEN lag_days = 0 THEN 1 ELSE 0 END) AS same_day, SUM(CASE WHEN lag_days BETWEEN 1 AND 7 THEN 1 ELSE 0 END) AS d1_7, SUM(CASE WHEN lag_days BETWEEN 8 AND 30 THEN 1 ELSE 0 END) AS d8_30, SUM(CASE WHEN lag_days > 30 THEN 1 ELSE 0 END) AS over_30, MAX(lag_days) AS max_d FROM pairs GROUP BY previoustype, nexttype ORDER BY COUNT(*) DESC
WITH pairs AS ( -- as Q5, filtered to the 3 slow edges ), ranked AS ( SELECT p.*, ROW_NUMBER() OVER (PARTITION BY p.previoustype, p.nexttype ORDER BY p.lag_days DESC) AS rn FROM pairs p WHERE p.lag_days > 30 ) SELECT r.previoustype || '>' || r.nexttype AS edge, tp.tranid, TO_CHAR(tp.trandate,'YYYY-MM-DD') AS prev_date, tn.tranid AS next_tranid, r.lag_days, ROUND(ABS(tp.foreigntotal),0) AS prev_total FROM ranked r JOIN transaction tp ON tp.id = r.previousdoc JOIN transaction tn ON tn.id = r.nextdoc WHERE r.rn <= 6 ORDER BY r.previoustype, r.lag_days DESC
SELECT l.previoustype, l.linktype, l.nexttype, tp.tranid AS prev_tranid, tn.tranid AS next_tranid, TO_CHAR(tp.trandate,'YYYY-MM-DD') AS prev_date, TO_CHAR(tn.trandate,'YYYY-MM-DD') AS next_date, ROUND(ABS(tn.foreigntotal),0) AS next_total FROM nexttransactionlinelink l JOIN transaction tp ON tp.id = l.previousdoc JOIN transaction tn ON tn.id = l.nextdoc WHERE l.linktype IN ('SaleRet','CostRtrn','PurchRet') OR l.nexttype IN ('RtnAuth','CustCred','CashRfnd','VendCred','VendAuth') OR l.previoustype IN ('RtnAuth','VendAuth','CustRfnd') GROUP BY /* all selected cols */ ... ORDER BY l.previoustype, tp.tranid
SELECT CASE WHEN l.previousdoc IS NOT NULL THEN 'from_SO' ELSE 'standalone' END AS origin, CASE WHEN t.foreignamountpaid > 0 THEN 'partially_paid' ELSE 'no_payment' END AS pay_state, COUNT(DISTINCT t.id) AS invoices, ROUND(SUM(t.foreignamountunpaid),0) AS open_amt, MEDIAN(TRUNC(SYSDATE) - t.trandate) AS med_age_days FROM transaction t LEFT JOIN (SELECT DISTINCT nextdoc, previousdoc FROM nexttransactionlinelink WHERE previoustype = 'SalesOrd' AND nexttype = 'CustInvc') l ON l.nextdoc = t.id WHERE t.type = 'CustInvc' AND t.status = 'A' GROUP BY origin_expr, pay_state_expr ORDER BY open_amt DESC -- Result: standalone/no_payment 21 · $769,920 · 157d -- from_SO/no_payment 7 · $138,229 · 10d -- standalone/partial 11 · $20,098 · 57d