# Accounts Receivable Aging Report

**Date:** 2026-05-31  
**Status:** Draft  
**Spec:** 140  
**Tier:** All tiers (Business+ for export)  
**Depends on:** `invoices-core`, `customers-module`, `reports-analytics`, `partial-payment-recording`, `foundation-auth-rbac`  
**Referenced by:** `reports-analytics`, `invoices-core`

---

## Overview

Spec 20 (`reports-analytics`) shows "Open invoices aging" as one bar chart in the financial dashboard. This is insufficient for operations: collecting overdue payments requires knowing which customer owes what, for how long, and the total exposure per aging bucket. This spec defines a dedicated AR aging report at `/reports/ar-aging`.

---

## Route

`/reports/ar-aging` — accessible from the reports navigation hub (spec 81, `reports-navigation-hub`).

---

## Report Layout

```
┌──────────────────────────────────────────────────────────────┐
│  Reports > Accounts Receivable Aging                         │
│                                                              │
│  As of: [2026-06-01 ▾]        [Export PDF]  [Export CSV]    │
│                                                              │
│  Summary                                                     │
│  ┌──────────┬──────────┬──────────┬──────────┬──────────┐   │
│  │ Current  │ 1–30 d   │ 31–60 d  │ 61–90 d  │  90+ d   │   │
│  │ ₪12,400  │ ₪8,200   │ ₪3,500   │ ₪1,200   │ ₪2,800   │   │
│  │  43.7%   │  28.9%   │  12.4%   │   4.2%   │  9.9%    │   │
│  └──────────┴──────────┴──────────┴──────────┴──────────┘   │
│  Total outstanding: ₪28,100                                  │
│                                                              │
│  Customer          Current  1–30d   31–60d  61–90d  90+d  Total│
│  ─────────────────────────────────────────────────────────  │
│  Acme Corp        ₪4,500   ₪2,300   —       —       —    ₪6,800│
│  [▾ 3 invoices]                                              │
│  Beta Ltd         ₪3,200   ₪1,800   ₪1,200  —       —    ₪6,200│
│  [▾ 4 invoices]                                              │
│  Gama Inc         —        ₪2,100   —       ₪1,200  ₪2,800 ₪6,100│
│  [▾ 5 invoices]  ⚠ 90+ days overdue                         │
│  Delta Corp       ₪4,700   —        ₪2,300  —       —    ₪7,000│
│                                                              │
│  [Send statements]  [Export all]                             │
└──────────────────────────────────────────────────────────────┘
```

---

## Aging Calculation

**Invoice age** = `as_of_date - invoice.due_date` (days). If `due_date` is null, uses `sent_at + tenant_settings.default_payment_terms_days` as the effective due date.

**Bucket assignment** per invoice:

| Bucket | Condition |
|--------|-----------|
| Current | `due_date >= as_of_date` (not yet due) |
| 1–30 days | 1 ≤ age ≤ 30 |
| 31–60 days | 31 ≤ age ≤ 60 |
| 61–90 days | 61 ≤ age ≤ 90 |
| 90+ days | age > 90 |

**Included statuses:** `SENT`, `APPROVED`, `TAX_ISSUED`, `PARTIALLY_PAID`. Excludes `DRAFT`, `PAID`, `VOID`, `REJECTED`.

**Amount shown:** for `PARTIALLY_PAID` invoices, shows `invoices.total - SUM(payments.amount)` (outstanding balance).

---

## Customer Row Drill-Down

**[▾ expand]** on a customer row reveals invoice-level detail:

```
│  Acme Corp                                           ₪6,800  │
│  ▾ 3 invoices                                                 │
│                                                              │
│    INV-0041  Website Redesign    due 2026-05-15  ₪4,500  Current │
│    INV-0038  SEO Services        due 2026-04-30  ₪2,300  2 days  │
│    [View invoice]  [Send reminder]                           │
```

**[View invoice]** → opens invoice detail in new tab.
**[Send reminder]** → payment reminder email (spec 58 `invoice-payment-reminders`).

---

## Send Statements

**[Send statements]** → multi-select dialog:

```
┌──────────────────────────────────────────────────────────────┐
│  Send account statements                                     │
│                                                              │
│  ☑ Acme Corp — dana@acme.com — ₪6,800 outstanding           │
│  ☑ Beta Ltd — yossi@beta.com — ₪6,200 outstanding           │
│  ☑ Gama Inc — roni@gama.com — ₪6,100 outstanding  ⚠ 90+d   │
│  ☐ Delta Corp — alex@delta.com — ₪7,000 outstanding         │
│                                                              │
│  Pre-selected: customers with outstanding > 30 days          │
│                                                              │
│  Subject: [Account statement — {business_name}___________]   │
│  Message: [Please find attached a summary of your           │
│            outstanding invoices as of {date}._____________]  │
│                                                              │
│  [Cancel]        [Send to 3 customers]                       │
└──────────────────────────────────────────────────────────────┘
```

Statement email includes:
- PDF attachment: customer's outstanding invoices (same data as drill-down)
- Each invoice: number, date, due date, amount, days overdue

---

## Customer Statement

The single-customer account statement (כרטסת לקוח) is **owned by spec 183 (`customer-statement`)** at `/customers/:id/statement` — one canonical statement document, not a second one defined here. The AR-aging report links to it:
- AR aging row → **[Statement]** → opens `/customers/:id/statement` (spec 183) for that customer.
- The customer-detail **[Statement]** header action is defined by spec 9 / spec 183, not here.

This spec keeps only the **bulk** send (below), which fans out spec 183's statement document to multiple overdue customers from the aging view.

---

## API

```
GET /api/reports/ar-aging
    → aging report data
      query: { as_of?: date (default today), currency?: string }
      Returns: {
        as_of: date,
        summary: { current, d1_30, d31_60, d61_90, d90plus, total },
        customers: [{
          customer_id, customer_name, contact_email,
          current, d1_30, d31_60, d61_90, d90plus, total,
          invoices: [{ id, number, description, due_date, age_days, total, balance_due, status }]
        }]
      }
      Requires: invoices:read

POST /api/reports/ar-aging/statements
     → bulk-send statements to selected overdue customers (renders each via spec 183's statement document)
       body: { customer_ids: string[], subject?: string, message?: string, as_of: date }
       Returns: { sent: number }
       Requires: invoices:read, admin
```

> Single-customer statement generation (`GET /api/customers/:id/statement` + `POST /api/customers/:id/statement/send`) is owned by spec 183 (`customer-statement`); this report does not redefine it.

---

## Architecture Decisions

| Decision | Choice | Reason |
|----------|--------|--------|
| `as_of` date parameter | Not always today | Auditors and accountants need historical aging snapshots; invoices get paid and buckets shift; historical view is necessary for reconciliation |
| Outstanding balance for `PARTIALLY_PAID` | Not face value | Showing full invoice amount for partially-paid invoices overstates exposure; customer owes the difference |
| Statement as PDF email | Not portal-only | Many customers don't have portal access; email PDF reaches all customers regardless of portal setup |
| Pre-select >30d for statement send | Not all or none | Sending statements to current-balance customers is unnecessary and creates noise; overdue-focused default is what accountants actually want |
