The live formulas behind the POS — drawers, settlement, refunds, discounts, tax, splits —
as operable calculators. Everything runs in cents internally; the exact algorithms mirror
src/ as of fix PRs #1082–#1086. Companion prose: docs/architecture/money-math.md.
The full pipeline: line gross = (unit + modifiers) × qty → item discounts → capped comps →
ticket-level discount (exact subtraction; the exact tax scales proportionally, rounded once) →
whole-ticket comp (collapses subtotal + tax, never surcharge/service charges) →
+ surcharge + SC → total − payments = balance due.
Voided items contribute nothing. The comp cap is
max(0, min(comp, gross − discounts)) so a line can never go negative.
The pricing mode drives the cash side: STRAIGHT (cash = credit), DUAL (per-item stored
cash price rows), CASH_DISCOUNT (ticket-level blended-rate transform, tax on the unrounded base).
balance = openingAmount + Σ signed(txn). One sign convention rules every drawer and checkout balance. Four ad-hoc ledgers diverged from it and were all wrong (fixed in #1086) — toggle the ghosts to see what the buggy code reported for the same session.
variance = countedTotal − expectedAmount, guarded with != null —
an expected amount of exactly $0.00 is a real expectation. The old truthy guard silently recorded
variance 0 on empty-expected drawers, so overages vanished. Manager approval gates at
$10.00 for spot counts, $5.00 for closing/blind counts.
net = owedToHouse − owedToEmployee − tipPoolNet. Cash collected goes to the house; credit-card tips come back to the server; a pool contribution is negative pool-net, so it adds back to the turn-in. Dropping the contribution was the stale-test bug — money must be conserved.
“Cash collected” is attributed by ticket owner — non-voided cash payments joined through tickets.shiftId, never the server-cash-bank ledger. The bank credits whoever pressed the button, so ringing cash on another server’s ticket makes the two keys diverge by equal-and-opposite amounts (a real $125.75 walked between two checkouts this way on 2026‑07‑16). The bank stays as a custody audit trail only; cross-collected cash surfaces as informational “hand off” lines on both servers’ checkouts. One source feeds the sidebar, the cash reconciliation, and the persisted settlement — same round-once philosophy, applied to attribution.
remaining = max(0, payment − Σ refunds in PENDING∪COMPLETED). PENDING reserves headroom before the gateway confirms; FAILED and VOIDED never count. The storage guard rejects non-positive amounts and anything past remaining — atomically, inside the same transaction as the insert, after the idempotency lookup.
discounted = round(subtotal × (1 − rate)); tax = round(unrounded-discounted × taxRate). Tax is computed on the unrounded discounted base — rounding the subtotal first masks cents (the ticket-0039 lesson). Tip base is the post-discount credit total. Preview must equal persisted, exactly.
target = round(Σ exact shares) — one rounding at the boundary. Rounding each tax group independently can disagree with the target by a cent or more; the difference is distributed one cent at a time by largest fractional remainder (ties → alphabetical). Watch the penny move.
The law: Σ parts === amount, exactly — never a cent created or lost. Floor every share, then hand out the residual one cent at a time by largest fractional remainder; ties break to the lowest index. Enforced by 1,300+ randomized property runs in the suite.
The TipAdjust wire amount is the tip alone — never the new captured total. Verified against the iPOSpays portal 2026-05-08. The audit row stores the resulting captured total for readers. Retries carrying the same idempotency key skip the gateway and the audit insert entirely.