Skip to main content

0054 — Remittance line amounts cap per-invoice allocation (short-pay support)

Status: accepted (owner sign-off 2026-08-20) · Date: 2026-08-20

Context

allocateCheck spends a check down the targeting order greedily, paying each invoice's full outstanding balance until the check is exhausted. Remittance line amounts influenced matching (which invoice a line names) but never application (how much lands on it). That was invisible while payers paid exact balances. Check #5085 (Westchester, 2026-08-20) broke the assumption: the stub put $2,440.00 on INV-0973 against a $2,552.70 balance — a deliberate short-pay — and greedy saturation applied the full $2,552.70, taking $112.70 from INV-0977, which the payer had paid in full. Same total money, wrong invoices: AR chasing targets an invoice the facility's books show as paid, and the short-pay dispute disappears from the invoice it belongs to.

Decision

When a matched remittance line carries a positive amount, that amount caps the application to its invoice: applied = min(check remainder, outstanding, line amount). Everything else is unchanged — uncapped (fallback) invoices still saturate oldest-first per decisions.md D1, a capped invoice is partial, never "paid" (paid compares against the true outstanding), and unapplied remainder behaves as before (spread for single-facility payers, held for review for management-company payers). Zero/illegible line amounts cap nothing. The cap definition lives once in src/lib/reconcile (lineAmountCaps) and is used by both the on-screen preview and the server confirm, which refuses on any mismatch.

Consequences

  • A short-paid invoice keeps its unpaid remainder on the invoice the payer disputed, matching the facility's own books; statements and AR chasing point at the right place.
  • A capped invoice is never revisited by the same check's remainder — the payer said how much goes there. If the payer under-states every line, more money flows to the fallback (visible in the preview before confirm).
  • Confirms in flight across the deploy are refused as stale and need a page refresh (by design of the reviewed-allocation binding).