Almost every collections process is driven by an aged debt report, which sorts by how overdue an invoice is. That is a reasonable proxy for urgency and a poor one for recoverability. The data to do better is already in your ledger.
An aged debt report tells you what is late. Payment behaviour tells you what is likely to stay late. The first is a list; the second is a priority order — and only one of them makes a collections team more effective.
Aged debt gets reviewed monthly. Payment behaviour changes daily. That mismatch is most of why DSO is difficult to move: the artefact everyone works from is a snapshot of a slow variable, and it says nothing about the fast one.
Worse, the aged debt report actively misleads on priority. It puts the oldest invoices at the top, and the oldest invoices are frequently the least collectable — a disputed £4,000 sitting at 120 days will absorb an afternoon and yield nothing, while a £90,000 invoice that just tipped past terms with a customer who always pays at 45 days needs one email.
The standard formula:
-- Simple DSO for a period.
DSO = (Accounts Receivable ÷ Credit Sales) × Days in Period
That works when sales are stable and misleads badly when they aren't. If you had a strong final month, your AR balance is inflated relative to the quarter's average sales and DSO looks worse than reality. For seasonal businesses, use the countback method — work backwards month by month, subtracting each month's sales from the AR balance until it's exhausted, and count the days consumed.
-- Monthly AR balance and credit sales, for DSO by either method. WITH sales AS ( SELECT TRUNC(t.trandate, 'MM') AS month, SUM(ABS(t.foreigntotal)) AS credit_sales FROM transaction t WHERE t.type = 'CustInvc' AND t.voided = 'F' AND t.trandate >= ADD_MONTHS(CURRENT_DATE, -13) GROUP BY TRUNC(t.trandate, 'MM') ), open_ar AS ( SELECT SUM(ABS(NVL(t.foreignamountunpaid, 0))) AS ar_balance FROM transaction t WHERE t.type = 'CustInvc' AND t.voided = 'F' AND NVL(t.foreignamountunpaid, 0) > 0 ) SELECT s.month, ROUND(s.credit_sales, 0) AS credit_sales, (SELECT ROUND(ar_balance, 0) FROM open_ar) AS ar_now FROM sales s ORDER BY s.month
DSO conflates two things: the terms you agreed (a commercial decision) and the lateness against them (a collections issue). Days Beyond Terms — DSO minus your weighted average agreed terms — isolates the part you can actually fix. A DSO of 47 on 30-day terms is 17 days of collection gap. A DSO of 47 on 45-day terms is essentially fine, and chasing harder won't help.
Consider three overdue invoices, all at 45 days past due:
| Customer X | Customer Y | Customer Z | |
|---|---|---|---|
| Value | £82,000 | £9,400 | £3,100 |
| Usual days to pay | 34 | 71 | 38 |
| Payments in last 12m | 26 | 19 | 11 |
| Open disputes | 0 | 0 | 2 |
| Trend last 6m | Stable | Stable | Deteriorating |
The aged debt report ranks these identically. But X is a reliable payer who is uncharacteristically late on a large sum — that's either an administrative problem you can fix with one call, or an early warning worth knowing about immediately. Y is behaving exactly as Y always behaves; chasing at day 45 achieves nothing except irritation, and the real issue is that terms were never enforced. Z is small, disputed and deteriorating — genuinely at risk, but the value doesn't justify front-of-queue.
Three completely different actions. One report that can't distinguish them.
This builds the behavioural profile the aged debt report lacks: how each customer actually pays, how consistently, and whether it's changing.
-- Payment behaviour profile by customer, last 24 months. WITH paid AS ( SELECT t.entity AS cust, t.id, t.trandate, t.duedate, t.closedate, (t.closedate - t.trandate) AS days_to_pay, (t.closedate - t.duedate) AS days_beyond_terms, ABS(t.foreigntotal) AS invoice_value FROM transaction t WHERE t.type = 'CustInvc' AND t.voided = 'F' AND t.closedate IS NOT NULL AND t.trandate >= ADD_MONTHS(CURRENT_DATE, -24) ) SELECT BUILTIN.DF(cust) AS customer, COUNT(*) AS invoices_paid, ROUND(AVG(days_to_pay), 1) AS avg_days_to_pay, ROUND(MEDIAN(days_to_pay), 1) AS median_days_to_pay, ROUND(STDDEV(days_to_pay), 1) AS consistency, ROUND(AVG(days_beyond_terms), 1) AS avg_beyond_terms, ROUND(AVG(CASE WHEN trandate >= ADD_MONTHS(CURRENT_DATE, -6) THEN days_to_pay END), 1) AS recent_6m, ROUND(AVG(CASE WHEN trandate < ADD_MONTHS(CURRENT_DATE, -6) THEN days_to_pay END), 1) AS prior_18m, ROUND(SUM(invoice_value), 0) AS value_paid FROM paid GROUP BY cust HAVING COUNT(*) >= 6 ORDER BY (AVG(CASE WHEN trandate >= ADD_MONTHS(CURRENT_DATE, -6) THEN days_to_pay END) - AVG(CASE WHEN trandate < ADD_MONTHS(CURRENT_DATE, -6) THEN days_to_pay END)) DESC
Sorted by deterioration — the customers whose recent behaviour is worst relative to their own history appear first. That column is the early warning; a customer drifting from 38 to 55 days matters long before they show up in the 90-day bucket.
The consistency column (standard deviation) is underrated. A customer who always pays at
exactly 60 days is entirely predictable and can be planned around. One averaging 45 days with a
standard deviation of 30 is unpredictable, and unpredictability is what actually costs you, because it
is what you have to hold working capital against.
| Profile | Signature | Approach |
|---|---|---|
| Reliable | Low days beyond terms, low variance | Don't chase. Automated reminder only. Investigate immediately if they break pattern. |
| Consistently late | High days beyond terms, low variance | Not a collections problem — a terms problem. Renegotiate or price the funding in. |
| Erratic | Moderate average, high variance | Usually process friction on their side: wrong PO reference, invoice going to the wrong inbox. Fixable and worth fixing. |
| Deteriorating | Recent materially worse than history | The one that matters. Could be distress. Escalate early, tighten limits, get a person on the phone. |
Only two of these four warrant chasing at all, and one of them isn't a collections activity. That's the practical payoff: a collections team of two working the right accounts will outperform a team of four working an aged debt report top to bottom.
Combining current exposure with behavioural risk gives an actionable daily list.
-- Open invoices ranked by value at risk, not by age. WITH behaviour AS ( SELECT t.entity AS cust, AVG(t.closedate - t.trandate) AS typical_days FROM transaction t WHERE t.type = 'CustInvc' AND t.voided = 'F' AND t.closedate IS NOT NULL AND t.trandate >= ADD_MONTHS(CURRENT_DATE, -24) GROUP BY t.entity HAVING COUNT(*) >= 6 ) SELECT BUILTIN.DF(t.entity) AS customer, t.tranid AS invoice, t.trandate, t.duedate, ROUND(ABS(t.foreignamountunpaid), 2) AS outstanding, ROUND(CURRENT_DATE - t.duedate) AS days_overdue, ROUND(b.typical_days, 0) AS usually_pays_in, ROUND((CURRENT_DATE - t.trandate) - b.typical_days) AS days_off_pattern, ROUND(ABS(t.foreignamountunpaid) * LEAST(((CURRENT_DATE - t.trandate) / NULLIF(b.typical_days, 0)), 3), 0) AS value_at_risk FROM transaction t JOIN behaviour b ON b.cust = t.entity WHERE t.type = 'CustInvc' AND t.voided = 'F' AND NVL(t.foreignamountunpaid, 0) > 0 AND CURRENT_DATE > t.duedate ORDER BY value_at_risk DESC
value_at_risk weights the outstanding amount by how far past the customer's own normal
payment point the invoice has travelled, capped at 3× so a single extreme case can't dominate. Crude,
transparent, and a substantial improvement on sorting by age.
Give a collections team the top 20 rows of this each morning rather than a 400-line aged debt report, and the same effort collects materially more.
Here's the part that gets missed. A meaningful share of DSO accrues before anyone is late, and no amount of chasing recovers it:
Work out how many days sit in each of these before investing in more chasing capacity. The answer frequently reallocates the whole effort.
Four numbers, tracked as trends rather than levels:
None of these appear on a standard aged debt report, and all four come from data NetSuite is already recording. That's the recurring theme: the constraint is almost never data availability. It's that nobody has asked the ledger a question in this shape.
Revaion Cash scores payment risk from behaviour rather than age, ranks the chase list by recoverable value, and flags the accounts that are drifting before they become aged debt.
See how Revaion Cash works →Book a 30-minute discovery call. We'll walk through how Revaion ranks your collections effort by payment behaviour — and what that does to your DSO.
Or email [email protected]
We use your email only to reply to this enquiry. See our privacy policy.