---
title: "Order to cash (AR)"
description: "The receivables stack from quote to revenue: quote → order → fulfillment → invoice → collection → cash application → revenue."
canonical: "https://lopifi.com/docs/domain/order-to-cash"
---

# Order to cash (AR)

> The receivables stack from quote to revenue: quote → order → fulfillment → invoice → collection → cash application → revenue.

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

---

# Order to cash (AR)

The receivables stack from quote to revenue: quote → order → fulfillment → invoice → collection → cash application → revenue.

One rule governs all of it:

> **An invoice is a claim, a payment is cash, and revenue is recognition. Those are three events on three dates, and a UI that merges any two of them misstates 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. Put them in a revenue figure and pipeline starts passing for performance, which is the usual way it happens. `SalesOrderTable` shows backlog and keeps it visually apart from anything that comes from the ledger.

### 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.** 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, so `InvoiceDetail` offers void only while the invoice is unpaid and its period is open.

---

## 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 auditors look for gaps. Users never pick a number, and `InvoiceRegister` surfaces any gap as a finding.
- **`balanceDue` gets the largest type.** The total is context. The balance is what you're asking the customer to pay.
- **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 AP department rejects an invoice that's missing the PO it requires, and the user should find that out 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](https://lopifi.com/md/docs/domain/multi-entity-and-fx).

### 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 }[] };
```

**Spell out `2/10 net 30`.** Write "2% discount if paid within 10 days, otherwise due in 30 days," and show the discounted amount and the discount deadline next to it. 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. It's a common bug, and an embarrassing one.
- **The aging total ties to the AR control account.** The check is displayed and asserted. If the two differ, 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, so it survives grayscale and doesn't fight 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.
- **Disputed invoices are excluded from dunning but included in aging.** They are still receivables.

---

## 4. Cash application

This is where AR breaks. Money arrives, and 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 component handles every one of these cases:**

| 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.
- **Suggested combinations are the most valuable interaction in AR.** Given a lump sum, propose the invoices that add up to it exactly. If several sets work, rank them oldest-first.
- **A remaining difference always needs a disposition:** write it off (with reason and account), leave it unapplied, or dispute it. No difference is allowed to vanish.
- **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 over an invoice they've formally disputed damages the relationship. It's the 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 that get mixed up constantly:

| | 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

**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](https://lopifi.com/md/docs/domain/schedules-and-accruals#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`. Two DSO methods give two different numbers, and a dashboard that doesn't say which one it used can't be compared 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 |
