Sample output from the Vendor Bill Approval & Payment Process 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

Vendor Bill Approval & Payment: Process Mining Analysis

NetSuite account TD3016323 (production, OneWorld, USD) · Objects: VendBill, VendPymt, VendCred, approver, terms · Scope: all history, Oct 2024 → Sep 2026 · Prepared 2026-09-04 · Revision 1 · Method: SuiteQL via runSql over transaction, nexttransactionlinelink, systemnote · Seventh study in the series; the approval-and-payment tail of Procure-to-Pay · Document numbers link to the source record inside NetSuite
  1. 0Executive summary
  2. 1Method and assumptions
  3. 2Structure — approval, then payment
  4. 3Approval — what can and cannot be seen
  5. 4Payment timing vs terms
  6. 5The cost of paying early; the discount window
  7. 6Late payments
  8. 7Open and pending bills
  9. 8Exception registers
  10. 9Data quality
  11. 10Findings and actions
  12. ASuiteQL used
  13. BHand-check

0Executive summary

1,017
Vendor bills, $3.40M — 1,004 paid, 9 open, 4 pending approval
25.9 d
Value-weighted days early on 995 of 1,005 bill payments
$1,810
Early-payment discount still available on 5 open bills — window closes 10–13 Sep
7
Late payments, all Generation N, 95–330 days, cleared in one $341,743 sweep

Bill payment in this account is disciplined to the point of being expensive. Of 1,005 bill-to-payment pairs, 995 (99.0%) were paid before the due date — 896 of them 10–30 days early, 99 more than 30 days early — and exactly one was paid on the due date. Weighted by value, $2.91M of bills were paid 25.9 days ahead of terms. Only 7 bills were paid materially late, and they are one story: seven Generation N bills on Net 15 terms dated Sep 2025 – May 2026 sat unpaid for 95–330 days past due and were all settled in a single $341,743.03 payment on 2026-09-07 — the only many-to-one payment in the account.

  1. Approval is configured but not exercised. Every one of 1,013 non-pending bills carries approvalstatus = Approved with no approver recorded and no approval system note; the only trace of a human approval step in 1,017 bills is 7 status-change notes written by the administrator in one sitting on 2026-01-07. Approval lead time therefore cannot be measured. Four bills ($3,114) are Pending Approval today; two have an approver (Frank Davenport, $50 total) and two have none — including the oldest, Bedline $1,500, pending since 1 July and 35 days past its due date.
  2. The early-payment discount is about to be missed for the first time, not the fifth. Five open bills carry 1%/2% 10 Net 30 terms with discount dates of 10–13 September — six to nine days from now. Paying them inside the window saves $1,810.40, $1,200 of it on a single Davidson Leasing bill for $120,000. No bill with discount terms has ever been paid in this account (all five are the first), so there is no history of capturing or missing it.
  3. Everything else pays 10–30 days early with no discount to show for it. 896 payments landed 10–30 days before Net 15 / Net 30 due dates — the pattern is "pay within 1–7 days of the bill date regardless of terms" (692 payments at 1–7 days lag, 302 same-day). At a 5% cost of funds that is roughly $10,300 a year of working capital given away against terms the vendors already granted.

1Method and assumptions

#AssumptionEffect
A1Case = vendor bill (1,017). Activities: Entered (trandate), Approved (approvalstatus, nextapprover, TRANDOC.KSTATUS system notes), Paid (VendPymt via nexttransactionlinelink), Credited (VendCred).Approval timestamp exists only where a system note exists (7 bills).
A2Links = distinct nexttransactionlinelink (previousdoc, nextdoc) pairs; VendBill→VendPymt counted once per pair even when the payment was later voided.1,005 bill-payment pairs incl. 1 voided-and-reissued.
A3Timing vs terms = payment trandate − bill duedate (negative = early). Lag = payment trandate − bill trandate. Day resolution.Bills with no duedate (6, no terms) use trandate as due.
A4Discount available = rate in terms name × bill total, if paid on or before discountdate.Only 5 bills carry discount terms.
A5Cost of early payment = Σ(amount × days early) / 365 × assumed 5% cost of funds. The rate is an assumption, shown so the reader can substitute their own.Illustrative, not a booked figure.
A6Vendor payments not linked to any bill (expense-report payments, unapplied) reported separately.3 payments.

2Structure — approval, then payment

Figure 1 — Bill lifecycle. Approval is a recorded state but not a recorded event; payment is 1:1 except for one 7-bill sweep. Labels: count · median days.

Multiplicity0127Note
Bills per vendor payment399801The 7-bill payment is Generation N 2026-09-07, $341,743.03. 3 payments link to no bill: 2 expense-report payments (Abby Kwan EXP05 $650, EXP03 $80), 1 unlinked Generation N $59.98 "Vendor Returns"
Payments/credits per bill121,0022—VB01: payment + vendor credit VRMA20 $89.97. VB05: payment voided ("Demo Ex") and reissued same day 2026-03-01, both $33,700
Bills with no payment link128 Open ($60,934) + 4 Pending Approval ($3,114). One Open bill (Davidson Leasing $120,000) links only to a RevRec journal JE159
Approvers named1,0143——Frank Davenport on 2 pending bills ($50); Larry Nelson on VB843 ($52,550, already Approved/Open)
Procurement in this account is one-to-one all the way down: one PO, one receipt, one bill, one payment. The single exception — seven bills swept into one payment — is also the only place payments ran late. Consolidation and delay arrived together.

3Approval — what can and cannot be seen

EvidenceBillsValueWhat it tells us
approvalstatus = Approved, status Paid In Full1,0043,218,685.23Approved at some point; no timestamp
approvalstatus = Approved, status Open9180,934.00Approved, awaiting payment
approvalstatus = Pending Approval43,114.00In the queue; 2 with approver, 2 without
TRANDOC.KSTATUS system notes (Open → Paid In Full)7—All 2026-01-07 by the administrator via UI — a status change, not an approval
Bills with any approval-field system note0—Approval routing (if any) leaves no trace in systemnote

Approval lead time — bill entered to bill approved — is unobservable on 1,013 of 1,017 bills. The state is set (Approved) but no event records when or by whom. The 4 pending bills are the only cases where the queue can be timed: Bedline 65 days (since 2026-07-01), Ad4tech, Lotion Co and Harris Technology future-dated. If the account intends bills to be approved by someone other than the entering user, the approval routing workflow is not running, or is running without writing to the record.

4Payment timing vs terms

Figure 2 — Payment date − due date, 1,005 pairs.

Figure 3 — Bill date → payment date, 1,005 pairs.

vs due datePairsShareValueLag same-day1–7 d8–30 d> 30 d
> 30 days early (31–32)999.9%320,100.5498100
8–30 days early (10–30)89689.2%2,588,601.6920369030
On due date10.1%350.001000
1–7 days late (2)20.2%1,500.000110
> 30 days late (95–330)70.7%341,833.000007
Total1,005100%3,252,385.2330269247

The lag columns explain the early payments: 302 bills were paid the day they were entered and 692 within a week, against terms that are Net 30 on 901 bills and Net 15 on 105. Payment is driven by bill arrival, not by due date. The 99 payments >30 days early are Net 30 bills paid on entry (lag 0, 31–32 days early because of month length).

Terms on billsBillsValueTermsBillsValue
Net 309012,508,395.971% 10 Net 303174,050.00
Net 15105712,959.262% 10 Net 3023,495.00
(none — due on entry)63,833.00

5The cost of paying early; the discount window

MeasureValueFormula
Pairs paid before due date995d = duedate − payment date > 0
Value paid early$2,908,702.23Σ bill total
Value-weighted days early25.9Σ(amount × d) / Σ amount
Working capital given up (illustrative)≈ $10,3002,908,702.23 × 25.9 / 365 × 5% = $10,320; over the 23-month window ≈ $5,400 / yr

Discount available now on open bills

BillVendorBill dateDiscount byDueTotalTermsDiscount if paid in windowDays left (from 09-04)
(no number)Davidson Leasing2026-09-032026-09-132026-10-02120,000.001% 10 Net 301,200.009
VB843Cloud Consulting2026-09-032026-09-132026-10-0252,550.001% 10 Net 30525.509
VB397Generation N2026-09-012026-09-112026-09-302,995.002% 10 Net 3059.907
VB385Witt & Anderson2026-08-312026-09-102026-09-301,500.001% 10 Net 3015.006
VB842Cloud Consulting2026-09-012026-09-112026-09-30500.002% 10 Net 3010.007
5 bills — the only discount-term bills ever entered177,545.001,810.40

Discount terms appear for the first time on bills dated 31 Aug – 3 Sep 2026. Given the account's habit of paying within a week of entry, all five would normally be paid inside the window — but none has been, and the Davidson bill is linked only to a revenue-recognition journal, suggesting it is a lease accrual rather than a payable in the usual sense.

6Late payments

BillVendorBill dateDue (Net 15)AmountPaidDays late
VB01Generation N2025-09-272025-10-1222,499.002026-09-07330
VB02Generation N2025-11-302025-12-1222,575.002026-09-07269
VB03Generation N2025-12-262026-01-1230,100.002026-09-07238
VB04Generation N2026-01-252026-02-0931,000.002026-09-07210
VB06Generation N2026-03-232026-04-0755,660.002026-09-07153
VB07Generation N2026-04-212026-05-0669,699.002026-09-07124
VB08Generation N2026-05-202026-06-04110,300.002026-09-0795
One payment, 2026-09-07 (VB01 net of credit VRMA20 $89.97)341,833.00weighted 165
LP- interco alloc 3Flexsteel2026-09-102026-09-251,000.002026-09-272
(no number)—2026-08-012026-08-01500.002026-08-032

The VB01–VB08 series (VB05 was paid on time, twice — see §8) is Generation N's monthly bill, Net 15, ignored for eleven months and then cleared in one sweep three days from now. Every other Generation N bill (81 of 88) was paid within 3 days of entry. This is not a slow vendor relationship; it is one numbered series of bills that was excluded from the normal payment run.

7Open and pending bills

BillVendorBill dateDuePast dueAmountStatusApproverNote
(none)Bedline2026-07-012026-07-31351,500.00Pending Approval—65 days in queue, nobody assigned
LP- interco allocFlexsteel2026-07-092026-07-24421,000.00Open—Intercompany allocation, Net 15
LP- interco alloc 2Flexsteel2026-08-102026-08-25101,000.00Open—Same series; alloc 3 was paid
INV-005Ad4tech Material LLC2026-09-112026-09-11−71,564.00Pending Approval—No terms
(none)Well2026-09-132026-09-13−91,000.00Open—No terms
(none)Lotion Co2026-09-152026-09-15−1130.00Pending ApprovalFrank Davenport
(none)Cable Plus Distributors2026-09-152026-09-15−11389.00Open—No terms
(none)Harris Technology2026-09-152026-10-15−4120.00Pending ApprovalFrank Davenport
Plus the 5 discount-term bills of §5 (VB385, VB397, VB842, VB843, Davidson $120,000), all Open, all not yet due.
13 bills · 9 Open $180,934.00 · 4 Pending $3,114.00184,048.003 past due ($3,500); 10 not yet due

8Exception registers

8-A · Duplicate / voided payment

VB05 (Generation N, 2026-02-28, $33,700) has two payments dated 2026-03-01 for $33,700: 00000005/1-12102024-181841 (memo "Demo Ex", status Voided, also linked to journal JE150) and 00000005/1 (status Undefined, live). Net effect is one payment; the audit trail shows a duplicate caught and voided the same day.

8-B · Vendor payments with no bill (3)

PaymentDatePayeeAmountApplied toReading
(none)2026-09-07Abby Kwan650.00Expense report EXP05Employee reimbursement via bill payment — legitimate, different object
3622026-09-24Abby Kwan80.00Expense report EXP03Same
1672026-09-08Generation N59.98nothingMemo "Vendor Returns - LP"; paid with no bill or credit applied — unapplied vendor payment

8-C · Bill with only a journal link

Davidson Leasing $120,000 (2026-09-03, 1% 10 Net 30) links forward only to journal JE159 "RevRec". It is the largest open bill and carries the largest available discount ($1,200); whether it is a payable at all should be confirmed before the 13 September discount date.

9Data quality

LimitationEvidenceEffect
Approval is a state, not an eventapprovalstatus populated on all 1,017; approval system notes on 0; nextapprover on 3Approval lead time unmeasurable; approver accountability unmeasurable
Status notes are one-off7 TRANDOC.KSTATUS notes, all 2026-01-07 13:xx by the administratorCannot distinguish system-driven from manual status changes
Voided payments remain linkedVB05 shows 2 payment links, one VoidedPair counts include 1 voided pair (1,005 vs 1,004 live)
Future-dated documentsPayment run 2026-09-07 and 10 open bills dated after 2026-09-04"Days left" and "past due" computed against SYSDATE; demo data is partly in the future
Un-numbered bills6 open/pending bills with blank tranidReferenced by internal id in this report

10Findings and recommended actions

  1. Take the $1,810 this week, and decide whether to want more of it. Pay VB385 by 10 Sep, VB397 and VB842 by 11 Sep, VB843 and Davidson by 13 Sep (after confirming Davidson is a real payable) — $1,810.40 for doing what the account already does with every other bill. Then ask vendors on Net 30 for 1%/2% 10 terms: this account pays in 1–7 days anyway, so today it forgoes ≈ $10K/yr of float for nothing; with discount terms the same behaviour would earn 1–2% of spend.
  2. Make approval leave a trace, or stop pretending it happens. 1,013 bills are "Approved" with no approver, no date and no note. Either enable an approval workflow that writes nextapprover and a system note (so lead time and segregation of duties become auditable), or set the preference to auto-approve openly. Meanwhile assign an approver to the two orphaned pending bills — Bedline ($1,500) has waited 65 days and is 35 days past due.
  3. Find out why VB01–VB08 skipped eleven payment runs. Seven Net-15 bills from one vendor, entered monthly, each ignored while 81 other bills from the same vendor were paid within 3 days, then cleared in a single $341,743 payment. Check whether the series was on payment hold, belonged to a different subsidiary/AP account, or was simply never selected in Pay Bills — and clear the unapplied $59.98 payment 167 to the same vendor while there.

AAppendix A — SuiteQL used

Q1 — Timing vs due date with lag columns

SELECT CASE WHEN d < -30 THEN 'a >30 early' WHEN d < -7 THEN 'b 8-30 early' WHEN d < 0 THEN 'c 1-7 early' WHEN d = 0 THEN 'd on due'
            WHEN d <= 7 THEN 'e 1-7 late' WHEN d <= 30 THEN 'f 8-30 late' ELSE 'g >30 late' END AS vs_due,
 COUNT(*) AS pairs, MIN(d), MAX(d), ROUND(SUM(amt),2) AS value,
 SUM(CASE WHEN lag=0 THEN 1 ELSE 0 END) AS lag0, SUM(CASE WHEN lag BETWEEN 1 AND 7 THEN 1 ELSE 0 END) AS lag1_7,
 SUM(CASE WHEN lag BETWEEN 8 AND 30 THEN 1 ELSE 0 END) AS lag8_30, SUM(CASE WHEN lag > 30 THEN 1 ELSE 0 END) AS lag_over30
FROM (SELECT DISTINCT b.id, p.id AS pid, TRUNC(p.trandate)-TRUNC(b.duedate) AS d, TRUNC(p.trandate)-TRUNC(b.trandate) AS lag, ABS(b.foreigntotal) AS amt
      FROM nexttransactionlinelink l JOIN transaction b ON b.id=l.previousdoc AND b.type='VendBill' JOIN transaction p ON p.id=l.nextdoc AND p.type='VendPymt')
GROUP BY [same CASE] ORDER BY 1

Q2 — Multiplicity and unlinked bills

SELECT 'bills_per_payment' AS metric, k AS n, COUNT(*) AS docs, ROUND(SUM(v),2) AS value FROM (SELECT l.nextdoc, COUNT(DISTINCT l.previousdoc) AS k, MAX(ABS(p.foreigntotal)) AS v
  FROM nexttransactionlinelink l JOIN transaction b ON b.id=l.previousdoc AND b.type='VendBill' JOIN transaction p ON p.id=l.nextdoc AND p.type='VendPymt' GROUP BY l.nextdoc) GROUP BY k
UNION ALL SELECT 'payments_per_bill', k, COUNT(*), ROUND(SUM(v),2) FROM (SELECT l.previousdoc, COUNT(DISTINCT l.nextdoc) AS k, MAX(ABS(b.foreigntotal)) AS v
  FROM nexttransactionlinelink l JOIN transaction b ON b.id=l.previousdoc AND b.type='VendBill' JOIN transaction p ON p.id=l.nextdoc AND p.type IN ('VendPymt','VendCred') GROUP BY l.previousdoc) GROUP BY k
UNION ALL SELECT 'bills_no_payment_link_' || BUILTIN.DF(b.status), 0, COUNT(*), ROUND(SUM(ABS(b.foreigntotal)),2) FROM transaction b
  WHERE b.type='VendBill' AND NOT EXISTS (SELECT 1 FROM nexttransactionlinelink l WHERE l.previousdoc=b.id) GROUP BY BUILTIN.DF(b.status) ORDER BY 1,2

Q3 — Approval state, terms census, weighted early days

SELECT 'approvalstatus ' || COALESCE(BUILTIN.DF(b.approvalstatus),'(null)') || ' / ' || BUILTIN.DF(b.status) AS k, COUNT(*) AS n, ROUND(SUM(ABS(b.foreigntotal)),2) AS v, NULL AS x
  FROM transaction b WHERE b.type='VendBill' GROUP BY COALESCE(BUILTIN.DF(b.approvalstatus),'(null)'), BUILTIN.DF(b.status)
UNION ALL SELECT 'early_pay_weighted', COUNT(*), ROUND(SUM(amt),2), ROUND(SUM(amt*dearly)/SUM(amt),1)
  FROM (SELECT DISTINCT b.id, p.id AS pid, ABS(b.foreigntotal) AS amt, TRUNC(b.duedate)-TRUNC(p.trandate) AS dearly FROM nexttransactionlinelink l
        JOIN transaction b ON b.id=l.previousdoc AND b.type='VendBill' JOIN transaction p ON p.id=l.nextdoc AND p.type='VendPymt') WHERE dearly > 0
UNION ALL SELECT 'bill_terms ' || COALESCE(BUILTIN.DF(b.terms),'(none)'), COUNT(*), ROUND(SUM(ABS(b.foreigntotal)),2), NULL FROM transaction b WHERE b.type='VendBill' GROUP BY COALESCE(BUILTIN.DF(b.terms),'(none)')
ORDER BY 1

Q4 — Open / pending register

SELECT b.id, b.tranid, TO_CHAR(b.trandate,'YYYY-MM-DD') AS bill_date, TO_CHAR(b.duedate,'YYYY-MM-DD') AS due, TRUNC(SYSDATE)-TRUNC(b.duedate) AS days_past_due,
 BUILTIN.DF(b.entity) AS vendor, ROUND(ABS(b.foreigntotal),2) AS total, ROUND(b.foreignamountunpaid,2) AS unpaid, BUILTIN.DF(b.status) AS status, BUILTIN.DF(b.terms) AS terms,
 BUILTIN.DF(b.nextapprover) AS approver, TO_CHAR(b.discountdate,'YYYY-MM-DD') AS discount_date, b.memo
FROM transaction b WHERE b.type='VendBill' AND b.status <> 'B' ORDER BY b.status, b.duedate

Q5 — Late payments

SELECT b.tranid, TO_CHAR(b.trandate,'YYYY-MM-DD') AS bill_date, TO_CHAR(b.duedate,'YYYY-MM-DD') AS due, ROUND(ABS(b.foreigntotal),2) AS amt, p.tranid AS pymt,
 TO_CHAR(p.trandate,'YYYY-MM-DD') AS paid, TRUNC(p.trandate)-TRUNC(b.duedate) AS days_late, BUILTIN.DF(b.terms) AS terms
FROM (SELECT DISTINCT previousdoc, nextdoc FROM nexttransactionlinelink) l JOIN transaction b ON b.id=l.previousdoc AND b.type='VendBill' JOIN transaction p ON p.id=l.nextdoc AND p.type='VendPymt'
WHERE TRUNC(p.trandate)-TRUNC(b.duedate) > 0 ORDER BY days_late DESC

Q6 — Residuals (payments without bills, bills with 2 payments, open bills with links)

SELECT 'pymt_no_bill' AS kind, p.tranid, TO_CHAR(p.trandate,'YYYY-MM-DD') AS d, BUILTIN.DF(p.entity) AS vendor, ROUND(ABS(p.foreigntotal),2) AS amt, BUILTIN.DF(p.status) AS status, p.memo
  FROM transaction p WHERE p.type='VendPymt' AND NOT EXISTS (SELECT 1 FROM nexttransactionlinelink l WHERE l.nextdoc=p.id)
UNION ALL SELECT 'bill_2_pymts', b.tranid, TO_CHAR(b.trandate,'YYYY-MM-DD'), BUILTIN.DF(b.entity), ROUND(ABS(b.foreigntotal),2), BUILTIN.DF(b.status),
  (SELECT LISTAGG(DISTINCT n.tranid || ' ' || TO_CHAR(n.trandate,'MM-DD') || ' $' || ROUND(ABS(n.foreigntotal),2), '; ') FROM nexttransactionlinelink l JOIN transaction n ON n.id=l.nextdoc WHERE l.previousdoc=b.id)
  FROM transaction b WHERE b.type='VendBill' AND (SELECT COUNT(DISTINCT l.nextdoc) FROM nexttransactionlinelink l JOIN transaction n ON n.id=l.nextdoc AND n.type IN ('VendPymt','VendCred') WHERE l.previousdoc=b.id) = 2
UNION ALL SELECT 'open_bill_with_link', b.tranid, TO_CHAR(b.trandate,'YYYY-MM-DD'), BUILTIN.DF(b.entity), ROUND(ABS(b.foreigntotal),2), BUILTIN.DF(b.status) || ' unpaid $' || ROUND(b.foreignamountunpaid,2),
  (SELECT LISTAGG(DISTINCT n.type || ' ' || n.tranid || ' ' || l.linktype, '; ') FROM nexttransactionlinelink l JOIN transaction n ON n.id=l.nextdoc WHERE l.previousdoc=b.id)
  FROM transaction b WHERE b.type='VendBill' AND b.status <> 'B' AND EXISTS (SELECT 1 FROM nexttransactionlinelink l WHERE l.previousdoc=b.id)
Query notes
VendBill status letters: A Open · B Paid In Full · D Pending Approval (label from BUILTIN.DF(status); filter on the letter). VendPymt status shows "Undefined" for live payments and "Voided" for voided — voided payments keep their nexttransactionlinelink rows. Bill payments to employees link to ExpRept, not VendBill. approvalstatus, nextapprover, discountdate, terms are all exposed on transaction.

BAppendix B — Hand-check

Bills 1,004 + 9 + 4 = 1,017 ✓ · Terms 901 + 105 + 6 + 3 + 2 = 1,017; 2,508,395.97 + 712,959.26 + 3,833 + 174,050 + 3,495 = 3,402,733.23 = 3,218,685.23 + 180,934 + 3,114 ✓ · Pairs by vs-due 99 + 896 + 1 + 2 + 7 = 1,005 = 998 × 1 + 1 × 7 ✓ · Lag columns 302 + 692 + 4 + 7 = 1,005 ✓ · Early pairs 99 + 896 = 995; value 320,100.54 + 2,588,601.69 = 2,908,702.23 ✓ · Cost 2,908,702.23 × 25.9 / 365 × 0.05 = 10,320 ✓ · Discount 1,200 + 525.50 + 59.90 + 15 + 10 = 1,810.40; totals 120,000 + 52,550 + 2,995 + 1,500 + 500 = 177,545 ✓ · Late sweep 22,499 + 22,575 + 30,100 + 31,000 + 55,660 + 69,699 + 110,300 = 341,833; less credit 89.97 = 341,743.03 = payment ✓ · Open 1,000 × 2 + 1,000 + 389 + 2,995 + 1,500 + 500 + 120,000 + 52,550 = 180,934 ✓ · Pending 1,500 + 1,564 + 30 + 20 = 3,114 ✓ · Unlinked bills 8 + 4 = 12; bills with links 1,002 + 2 + 1 (Davidson, journal only) = 1,005; 1,005 + 12 = 1,017 ✓.