# Order to cash (AR)

> The complete receivables stack: quote → order → fulfilment → invoice → collection → cash application → revenue.

Source: https://lopifi.com/docs/domain/05-order-to-cash
Part of: Lopifi — US GAAP books on autopilot, reviewed by a CPA (https://lopifi.com)

---

# Order to cash (AR)

The complete receivables stack: quote → order → fulfilment → invoice → collection → cash application → revenue.

The rule that governs the whole family:

> **An invoice is a claim, a payment is cash, and revenue is recognition. They are three different events on three different dates, and a UI that conflates any two of them will misstate something.**

---

## 1. The lifecycle

```
Quote → Sales order → Fulfilment → Invoice → Dunning → Payment → Application → Cash
  │         │            │            │                  │           │
  │         │            │            └─ Dr AR / Cr Revenue          │
  │         │            └─ revenue recognition trigger (point-in-time)
  │         └─ backlog; no GL impact
  └─ no GL impact
                                                         └─ Dr Cash / Cr AR
```

**No GL impact until the invoice.** Quotes and orders are commitments, not transactions. Showing them in a revenue figure is the most common way a pipeline gets mistaken for performance. `SalesOrderTable` renders backlog and is visually separated from anything ledger-derived.

### States

| State | Meaning | GL |
|---|---|---|
| `draft` | Being prepared | None |
| `sent` | Delivered to the customer | Dr AR / Cr Revenue (or deferred) |
| `viewed` | Customer opened it | None — but it kills the "never received it" excuse |
| `partially-paid` | Some cash applied | Dr Cash / Cr AR, partial |
| `paid` | Fully applied | — |
| `overdue` | Past due date, unpaid | None; a presentation state |
| `disputed` | Customer contests it | None; blocks dunning |
| `credited` | Offset by a credit note | Dr Revenue (+ Dr Sales tax payable) / Cr AR |
| `written-off` | Uncollectible | Dr Allowance / Cr AR |
| `void` | Cancelled before it was ever valid | Reversal of the original |

**`void` and `credited` are different and the distinction matters.** Voiding says the invoice should never have existed; crediting says it existed and is being reversed for a reason. A voided invoice with a payment against it is an error state. `InvoiceDetail` offers void only while unpaid and within the open period.

---

## 2. The invoice

```ts
type Invoice = {
  number: string;                  // sequential and unique — many VAT regimes require it
  customerId: string;
  issueDate: Date;
  dueDate: Date;                   // derived from terms; overridable with a reason
  terms: PaymentTerms;
  currency: string;
  lines: InvoiceLine[];
  subtotal: Money;
  discounts: Discount[];
  taxes: TaxLine[];                // per jurisdiction, per rate — never one lump
  total: Money;
  amountPaid: Money;
  amountCredited: Money;
  balanceDue: Money;               // total − paid − credited
  exchangeRate?: { rate: number; asOf: Date };   // if not in functional currency
  poNumber?: string;               // customer's PO — required by many AP departments
  revenueSchedule?: PerformanceObligation[];
};
```

**UI rules:**

- **Invoice numbers are sequential and unique.** Many VAT regimes require it, and gaps are a standard audit red flag. The UI never lets a user pick a number, and a gap is an audit finding surfaced in `InvoiceRegister`.
- **`balanceDue` is the number that matters**, and it's the one shown largest. Total is context; balance is the ask.
- **Taxes are itemized by jurisdiction and rate**, never a single "Tax" line. A customer disputing tax needs to see which authority and which rate.
- **The customer PO number is prominent when present.** An invoice missing the PO an AP department requires will be rejected and the user needs to see that before sending.
- **Foreign-currency invoices show the rate and date used.** The receivable is recorded at the transaction-date rate, revalued at period end, and settled at the payment-date rate — three rates, two FX gain/loss events. See [multi-entity & FX](09-multi-entity-and-fx.md).

### Payment terms
```ts
type PaymentTerms =
  | { kind: "net"; days: number }                             // Net 30
  | { kind: "eom"; days: number }                             // Net 30 EOM
  | { kind: "discount"; discountPct: number; discountDays: number; netDays: number }  // 2/10 net 30
  | { kind: "due-on-receipt" }
  | { kind: "installments"; schedule: { date: Date; amount: Money }[] };
```

**`2/10 net 30` must be rendered in full, not as a code.** "2% discount if paid within 10 days, otherwise due in 30 days" — and the component computes and shows both the discounted amount and the discount deadline, because that's the decision the customer is making.

---

## 3. Aging

The core AR view, and the one that ties to the balance sheet.

| Bucket | Definition | Typical treatment |
|---|---|---|
| **Current** | Not yet due | Normal |
| **1–30** | Past due up to a month | Reminder |
| **31–60** | | Escalation |
| **61–90** | | Collections |
| **90+** | | Allowance candidate, possible write-off |

**UI rules:**

- **Aging is by due date, not invoice date.** Aging from the invoice date makes Net 60 terms look delinquent on day 31. This is a common and embarrassing bug.
- **The aging total ties to the AR control account.** Displayed, asserted. A difference means either the aging or the ledger is wrong.
- **Row totals and column totals both foot, and the grand total is proven both ways.**
- **Bucket severity uses a sequential lightness ramp**, not a red gradient — it survives greyscale and the signal palette. Age is also stated numerically ("47 days past due"), never color alone.
- **Credit balances in AR are shown, not hidden.** A customer with a net credit (overpayment, unapplied cash) appears as a negative in the aging and should arguably be reclassified to a liability at period end. The component flags it rather than burying it.
- **Disputed invoices are excluded from dunning but included in aging.** They are still receivables.

---

## 4. Cash application

Where AR actually breaks. Money arrives; matching it to invoices is the work.

```ts
type PaymentApplication = {
  paymentId: string;
  amountReceived: Money;
  applications: { invoiceId: string; amount: Money }[];
  unapplied: Money;              // received − sum(applied)
  writeOff?: { amount: Money; reason: string; account: string };
};
```

**The cases the component must handle — all of them, not just the happy path:**

| Case | Treatment |
|---|---|
| **Exact match** | One payment, one invoice, same amount. Auto-applied. |
| **Partial payment** | Applies to one invoice, leaves a balance. Invoice stays `partially-paid`. |
| **Overpayment** | Excess becomes **unapplied cash** — never revenue; a liability once it leaves the customer in a net credit. |
| **Lump sum, many invoices** | The common case. Apply across invoices; the UI suggests a combination that sums exactly. |
| **Short pay** | Customer deducts. Either a dispute, an unrecorded credit, or a write-off. **Requires a reason.** |
| **Payment with no invoice** | Unapplied cash. Sits on the balance sheet until applied. |
| **Duplicate payment** | Flagged by amount + customer + date proximity. Refund or apply forward. |
| **Bank fee deducted in transit** | Received is less than remitted. The difference is a bank charge expense, not a short pay. |
| **FX difference on settlement** | Realized FX gain/loss. Posted, not written off. |

**UI rules:**
- **Unapplied cash is never revenue.** A payment from a customer with open invoices reduces that customer's AR even before it's matched; only a net credit balance (or a prepayment with no receivable) is a liability, reclassified at period end. Products that book it to revenue, or leave a credit balance inside AR, misstate both.
- **The suggested-combination feature is the highest-value interaction in AR.** Given a lump sum, propose the set of invoices that sums exactly to it. When multiple combinations exist, show them ranked by oldest-first.
- **A remaining difference always requires a disposition** — write-off (with reason and account), leave unapplied, or dispute. The UI never lets a difference silently disappear.
- **Write-off thresholds enforce approval.** Small differences auto-write-off to a configured account; anything above the threshold requires an approver.
- **Applying is reversible** (unapply), and both actions are audited.

---

## 5. Collections and dunning

```ts
type DunningStep = {
  offsetDays: number;              // relative to due date; negative = pre-due reminder
  channel: "email" | "letter" | "task" | "call";
  template: string;
  escalateTo?: string;
  pauseIf: ("disputed" | "payment-plan" | "manual-hold")[];
};
```

**UI rules:**
- **Disputes pause dunning automatically.** Chasing a customer for an invoice they've formally disputed damages the relationship and is the single most common complaint about automated collections.
- **The full communication history is on the customer**, not scattered per invoice. A collector needs one timeline.
- **Promise-to-pay is a first-class object** with a date and an amount, and a broken promise escalates. "Customer said they'd pay Friday" is the most actionable fact in collections and it belongs in the data model.
- **Payment plans** convert one receivable into an installment schedule without changing the total. The aging reflects the plan's due dates, not the original.

---

## 6. Credit notes and refunds

Three different things, constantly conflated:

| | What it does | GL |
|---|---|---|
| **Credit note** | Reduces what the customer owes | Dr Revenue (or contra-revenue) + Dr Sales tax payable / Cr AR |
| **Refund** | Returns cash already received | Dr AR (or liability) / Cr Cash |
| **Write-off** | Abandons an uncollectible claim | Dr Allowance / Cr AR |

**A write-off is not a credit note.** A credit note says the customer doesn't owe it; a write-off says they owe it and won't pay. The revenue treatment and the tax treatment differ, and so does what it tells you about the business. The UI names each distinctly and never offers one where the other is meant.

**Credit notes reference their original invoice** and cannot exceed its balance without an explicit override.

---

## 7. Customer master

```ts
type Customer = {
  id: string; name: string; legalName?: string;
  billingAddress: Address; shippingAddress?: Address;
  taxId?: string; taxExemptCertificate?: Document;
  defaultTerms: PaymentTerms;
  creditLimit?: Money;
  currency: string;
  parentCustomerId?: string;       // hierarchy for group-level exposure
  contacts: Contact[];
  status: "active" | "on-hold" | "inactive";
};
```

**UI rules:**
- **Credit limit and current exposure shown together**, with exposure including unbilled orders. A limit without exposure is decoration.
- **Customer hierarchy matters for exposure.** Three subsidiaries of one group are one credit risk, and `CustomerStatement` supports rolling up.
- **Tax exemption certificates expire.** An expired certificate on an active customer is a flagged exception, because invoicing them tax-free after expiry is a liability.
- **On-hold blocks new orders but not collections.**

---

## 8. Revenue recognition, separated from billing

The most important structural point in this document.

**Billing and recognition are independent.** You can bill annually and recognize monthly, deliver first and bill later, or bill milestones that don't match delivery. Every combination is normal.

```
Received/due > Recognized  → contract liability (deferred revenue)
Recognized > Billed        → contract asset (right still conditional)
                             or unbilled receivable (right unconditional)
```

**UI rules:**
- **An invoice does not automatically recognize revenue.** It creates a receivable. What it credits — revenue or deferred revenue — depends on the performance obligations. `InvoiceForm` shows which, per line, before posting.
- **The revenue waterfall is derived from obligations, not invoices.**
- **Never net contract assets against contract liabilities across contracts.** Within a contract the position is presented net (required); across contracts it is not.
- Full model in [schedules & accruals](04-schedules-and-accruals.md#4-deferred-revenue-asc-606--ifrs-15).

---

## 9. AR metrics

| Metric | Formula | Watch out |
|---|---|---|
| **DSO** | `(AR ÷ credit sales) × days in period` | Distorted by seasonality — prefer the countback method for volatile revenue |
| **DSO (countback)** | Subtract each period's revenue from AR until exhausted; count the days | More robust, harder to explain — show the working |
| **Best possible DSO** | `(Current AR ÷ credit sales) × days` | The floor DSO could reach with perfect collection |
| **CEI** | `(Opening AR + credit sales − closing total AR) ÷ (opening AR + credit sales − closing current AR)` | Collections effectiveness; better than DSO for team performance |
| **ADD** | `DSO − best possible DSO` | Average days delinquent — the actual lateness |
| **Bad debt rate** | `Write-offs ÷ credit sales` | |
| **% past due** | `Past-due AR ÷ total AR` | The simplest health signal |

**Every metric displays its formula, period, and inputs** via `MetricDefinition`. DSO computed two different ways gives two different numbers, and a dashboard that doesn't say which is not comparable to anything.

---

## 10. AR component set

| Component | Purpose |
|---|---|
| `InvoiceForm` | Create/edit with lines, tax, terms, and the recognition preview |
| `InvoiceDetail` | Full invoice with payment history, credits, and the audit trail |
| `InvoiceRegister` | All invoices, filterable, with the gapless-numbering check |
| `AgingTable` | By bucket, tied to the control account |
| `CashApplication` | The matching workspace — the highest-value screen in AR |
| `PaymentSuggestion` | Proposed invoice combinations for a lump sum |
| `UnappliedCashPanel` | Outstanding customer credits awaiting application |
| `CreditNoteForm` | Referenced to an invoice, capped at its balance |
| `CollectionsWorklist` | Prioritized by amount × age × risk, with promise-to-pay |
| `DunningTimeline` | Communication history per customer |
| `CustomerStatement` | Printable, per-customer, open items or full activity |
| `CustomerCreditPanel` | Limit, exposure, hierarchy roll-up |
| `RevenueWaterfall` | Deferred balance recognizing by future period |
