Sample output from the Process Flow Forensics — Transaction Lineage Mining prompt in the Sonar AI Prompt Library, run against a NetSuite test account. Every name and number here is test data. Back to the post · The library
Process Mining · Transaction Lineage Forensics

Every Path an Order Actually Takes
reconstructed from 24,579 lineage links

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.

Account TD3016323 (Production · OneWorld) Window 2024-09-01 → 2026-09-01 Lineage links 24,579 → doc pairs ≈5,750 Sales orders traced 788 Generated 2026-08-27 by Sonar AI

01Executive Summary

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.

91.4%
Happy path — orders completing SO→IF→INV→PMT
720 / 788 · $1.98M (87.8% of value)
0 d
Median order-to-cash on the happy path
avg 1.2d · max 34d
11
Distinct path variants observed
10 exception variants · 68 orders · $273.7K
$769.9K
Unpaid standalone invoices — outside SO lineage entirely
21 invoices · median 157 days old
13 d
Median cash-sale → bank-deposit float
1,026 links · monthly batch on ~24th
0.3%
Return / rework rate on customer sales docs
4 RMAs + 1 cash refund / 1,865 docs
The headline finding All 32 invoices created without a sales order are currently unpaid or only partially paid ($790K open, median 157 days) — while SO-derived invoices collect in a median of 0 days. The order-to-cash pipeline works; revenue that bypasses it is where cash goes to die. These look like large manual/DC invoices (median $13.8K–$36K) with no fulfillment discipline behind them.

02Order-to-Cash — The Real Flow Map

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.

742 · 0d med 732 · 0d med (billing) 736 · 0d med 4 · 2.5d · returns 2 · 0d credit 2 · 0d receipt 1,026 · 13d med ⚠ float 1 · 0d refund 1 · 0d conv. 1 WO · 2 PO drop Estimate10 · 1 → SO Sales Order788$2.25M · 24 months Fulfillment742 from SOItemShip · 752 total Invoice732 from SOCustInvc · 764 total Payment736 pairsCustPymt · 748 Standalone Invoice32 docs · no SO · ALL unpaid · $790K Return Auth4 RMAs Credit Memo3 total · CM06 $155 Item Receipt2 return receipts Work Order1 special order PO (drop)2 drop-ship Cash Sale1,077POS · $176.6K Deposit49 batchesmonthly ~24th Cash Refund1 · CR01 $716 ORDER-TO-CASH LANE RETAIL / POS LANE
forward flow (thickness ∝ √volume) rework / return loop cash float bottleneck rare / ghost path

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.

03Path Variants — Happy Path vs. Everything Else

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 pathOrdersShareDistributionMedian $ / orderTotal valueMed. SO→cashReading
1SO→IF→INV→PMT72091.4%$305$1,978,7950 dHAPPY PATH
2SO (no downstream docs)455.7%$1,627$127,213—NOT STARTED 13 pending approval, 32 pending fulfillment
3SO→IF91.1%$151$2,206—SHIPPED, NOT BILLED revenue leakage risk
4SO→IF→INV70.9%$13,842$138,229—BILLED, UNPAID the 7 big-ticket DC invoices
5SO→IF→INV→PMT +RA10.1%$258$2583 dRETURN LOOP paid, then RMA opened
6SO→IF→INV→PMT +RA+CM10.1%$361$3613 dRETURN LOOP full loop: RMA → credit memo
7SO→IF +RA10.1%$361$361—RETURN, NEVER BILLED
8SO→INV→PMT (no IF)10.1%$1,085$1,0850 dBILL-ONLY non-ship order
9WO→SO→IF→INV→PMT10.1%$1,365$1,3652 dSPECIAL ORDER build-to-order, completed
10PO→SO→IF→INV→PMT10.1%$356$3564 dDROP-SHIP completed
11PO→SO→IF10.1%$2,271$2,271—DROP-SHIP, NOT BILLED
What's workingVariant concentration is exceptional: 1 variant covers 91.4% of orders (world-class process discipline is typically 60–80%). Fulfillment, billing, and payment fire same-day for the retail volume. Zero split shipments/invoices detected across all 788 orders.
What the variants hideException variants carry outsized value: the average happy-path order is $2.7K, but the average stuck order is $4.0K. Variant #4 alone (7 orders, invoiced-not-paid) holds $138K — 7% of total order value — in just 0.9% of order count.

04Where Cycle Time Dies

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.

StagePairsMedianAvgLag distribution (same-day / 1–7 / 8–30 / >30d)>30 dWorst case
SO→Fulfillment7420 d1.6
19365 d (SO3910)
SO→Invoice7320 d0.6
018 d
Invoice→Payment7360 d1.7
7272 d (INV787)
CashSale→Deposit1,02613 d13.1
024 d
PO→Item Receipt7220 d0.0
03 d
PO→Vendor Bill7051 d1.0
03 d
Bill→Vendor Pymt1,0053 d3.5
7345 d (VB01)

The four places time actually dies

① Standalone-invoice A/R — 157 days and counting21 invoices with no sales-order parent, $769,920 open, $0 collected, median 157 days old; 11 more are partially paid ($20.1K open). Because they bypass the SO pipeline, no fulfillment/billing cadence ever pushes them to closure. This dwarfs every other delay combined.
② Cash-sale deposit float — 13-day median, 100% of POS volumeAll 1,026 linked cash sales wait for a monthly deposit batch (~24th): receipts from the 1st wait 3+ weeks. 765 of 1,026 sit 8–30 days. On ~$88K/yr of POS takings this is a permanent ~$3–7K float and a daily-reconciliation blind spot.
③ Fulfillment stragglers — 19 orders >30 daysMedian SO→ship is same-day, but 19 orders took >30 days — SO3910 shipped after 365 days; a cluster of May–Jul 2026 orders (SO4270–SO4287) all took ~46–49 days, suggesting a specific stock-out or location backlog worth a look.
④ Big-ticket receivables tail — 7 invoices >30 days to payThe slow payers are the large ones: INV787 (272d, $364), INV781 (139d), INV776 (70d, $4.9K), INV779 (63d, $4.0K), INV773 (50d, $6.6K), INV770 (35d, $3.2K). Retail pays instantly; wholesale/DC terms do not.
P2P mirror imageProcure-to-pay is tight (receipt 0d, bill 1d, payment 3d) — but 7 vendor bills are >30 days old and unpaid: VB01 (345d, $22.5K), VB02 (280d, $22.6K), VB03 (254d, $30.1K), VB04 (225d, $31K), VB06 (165d, $55.7K), VB07 (139d, $69.7K). Open A/P: $180,934 on 9 bills. The same "documents outside the linked flow rot" pattern, on the payables side.

05Stuck in the Pipe Right Now

Orders that entered the funnel but have not exited, as of 2026-08-27 — the live work queue implied by the lineage graph.

BucketNS statusOrdersValueMedian ageOldestAction owner
Never started
no IF, no INV
Pending Fulfillment (B)32$71,32113 d2026-05-15Warehouse ops
Pending Approval (A)13$55,89216 d2026-08-01Sales management
Shipped, not invoiced
IF exists, no INV
Pending Fulfillment (B)7$1,3032 d2026-06-05Billing
Pending Billing (F)3$2,93720 d2026-08-01
Part. Fulfilled (E)1$59615 d2026-08-11
Invoiced, not paid
INV exists, no PMT
Billed (G) · invoice open7$138,22913 d2026-05-01Collections
Approval is the silent killer of August13 orders worth $55.9K have sat in Pending Approval a median of 16 days — all created in August. At the account's same-day operating tempo, approval is the only stage with a multi-week queue. An approval SLA (or auto-approval threshold) would release this value immediately.

06Rework Loops — The Red Edges

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.

Customer returns (5 loops)

ChainDaysValue
SO4266→RMA23→CM06 + IR11754$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

Vendor returns (4 loops)

ChainDaysValue
PO1155→VRMA21→IF5702 (ship-back)2$150
PO1156→VRMA221$120
PO1156→VRMA23 (2nd VRMA, same PO)9$30
PO1158→VRMA20→IF5701 + VendCred1$90
ReadingReturn rate ≈ 0.27% of customer sales documents and ≈ 0.5% of POs — negligible. Two mild signals: SO4268 and PO1156 each needed a second return document (first-touch resolution miss), and RMA24/25/26 have received goods but no credit memo issued yet — a small liability sitting unbooked.

07Procure-to-Pay — The Mirror Flow

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.

722 · 0d med 705 · 1d med (billing) 19 · 0d bill-from-rcpt 1,005 · 3d med 4 · 1.5d vend. return 2 · 0d ship-back 1 vend. credit Purchase Order740 Item Receipt722 from PO Vendor Bill705 from PO1,017 total Vendor Payment1,005 pairs Vendor RMA4 VRMAs Ship-back (IF)2 return shipments Vendor Credit1 · VRMA20 $90
forward flow vendor-return loop

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.

08Recommendations

Ranked by dollar impact per unit of effort, straight from the lineage evidence.

1

Collections sprint on standalone invoices $790K

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.

2

Release the approval queue and set an SLA $55.9K now

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.

3

Move deposits from monthly to weekly 13d → ~3d float

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.

4

Chase the shipped-not-invoiced tail weekly $4.8K + leakage risk

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.

5

Book the pending return credits liability hygiene

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.

6

Investigate the 46-day fulfillment cluster + the aging payables six diagnostic

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.

09Methodology, Queries & Assumptions

Everything below is reproducible in the SuiteQL Query Tool. All queries ran against production on 2026-08-27 under Tim Dietrich / Administrator.

Approach

Assumptions & caveats

Source queries

Q1Link-type inventory — what lineage exists at all
SELECT linktype, COUNT(*) AS cnt
FROM nexttransactionlinelink
GROUP BY linktype
ORDER BY COUNT(*) DESC
Q2Edge graph — doc-type transitions with volume + median lag (feeds the diagrams)
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
Q3Per-order variant reconstruction — the 11 paths (feeds the variant table)
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
Q4Stuck-order buckets — status, value, age (feeds §05)
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
Q5Stage lag distributions — same-day / 1–7 / 8–30 / >30 buckets (feeds §04)
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
Q6Worst-case outlier documents per slow edge (SO3910, INV787, VB01…)
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
Q7Rework-loop chains — every return/refund document (feeds §06)
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
Q8Open A/R split — SO-derived vs. standalone invoices (the headline finding)
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

Key raw results embedded in this report