Skip to content

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)

  • AP aging — creditor-side mirror of this report.
  • Reports — full report catalog.
  • Client helper: summarizeAging() (used by fallback + tests).