Accounts Receivable aging
Customer invoices outstanding, bucketed by days past due. The operational dashboard for chasing unpaid invoices.
1. What it shows
One row per open customer invoice (B2C or B2B sale). Invoices with
status = CANCELLED are excluded (e.g. B2B sales reversed by finance).
Zero-balance invoices are filtered out (only balanceDue > 0 rows
remain).
Buckets
Derived from daysPastDue (today − dueDate, or issuedAt when
dueDate is null):
bucket |
Days past due |
|---|---|
current |
≤ 0 (not yet due) |
1-30 |
1 to 30 |
31-60 |
31 to 60 |
61-90 |
61 to 90 |
90+ |
> 90 |
See src/lib/api.ts:25187-25193 for the bucket function.
Row shape
Each row carries (see src/lib/api.ts:25202-25223):
invoiceNumber,installmentNo,bookingNo/bookingId(null for B2B third-party sales).referenceType/referenceId— polymorphic pointer so downstream UIs can pick a sensible label for non-booking invoices.customerId,customerName,customerEmail.amountDue,amountPaid,balanceDue.dueDate,issuedAt,daysPastDue,bucket.
2. Endpoint
| Method | Path | Handler | Permission |
|---|---|---|---|
GET |
/finance/aging |
handleFinanceAging (src/lib/api.ts:25144) |
finance.reports.aging.view (UI button-level), RLS on Invoice defers to finance.view. |
The endpoint itself does not call requirePermission — it relies on the
parent Finance route permission (finance.view) plus the report-button
gate in Finance.tsx:6489. If you need the data from outside the UI,
gate the caller.
Aging summary (RPC-backed)
GET /finance/aging-report?asOf=YYYY-MM-DD returns bucketed totals
backed by the finance_aging_report RPC
(supabase/migrations/20260415180000_finance_aging_report_rpc.sql).
Returns { fallback: true, data: null } when the RPC is missing or
errors; the UI falls back to summarizeAging(agingRows) client-side
(src/lib/api.ts:25467-25493).
3. UI
Finance page → Reports tab → Aging Report button
(Finance.tsx:6489). Gated by finance.reports.aging.view.
View modes
Two toggles on the rendered report (Finance.tsx:7984-7992):
- Invoice view (
agingViewMode === 'invoice', default) — one row per open invoice. Drill-down opens the invoice detail. - Payer view (
agingViewMode === 'payer') — rolled up per customer with a sum per bucket. Drill-down expands to the invoice list for that payer.
Both views share filteredAgingRows — the filter pipeline (bucket
chips, search, payer search) works the same in both modes.
Summary cards
Bucket cards at the top of the tab show the bucket totals + the grand total outstanding. Clicking a card scopes the table to that bucket.
Export
Button in the tab header calls the same endpoint with ?format=csv and
triggers a browser download. Permission: finance.reports.aging.export.
4. Permission gate summary
| Action | Permission |
|---|---|
| View Aging Report button | finance.reports.aging.view |
| Export CSV | finance.reports.aging.export |
Read Invoice rows |
finance.view (RLS) |