You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix(payments): report payout overpayments instead of clamping them away (#516) (#524)
* fix(payments): report payout overpayments instead of clamping them away (#516)
`netOwed = Math.max(grossOwed - alreadyPaid, 0)` hid the size of an
overpayment, and `disabled={netOwed <= 0}` on the Mark-as-Paid button removed
the only control that writes a payout row. The platform could neither see nor
act on "we paid this school more than we owed".
Add `overpaid` (max(alreadyPaid - grossOwed, 0)) alongside `netOwed`, which
keeps its floor so the metric cards, the mismatch guard and the school-facing
view are unaffected. Surface it as a table column and a banner.
On the issue's suggested fix: a negative/adjusting payout row is not available
— `payouts.amount` carries CHECK (amount > 0) from the original revenue
migration, untouched by the manual-payouts migration, and `markPayoutPaid`
rejects non-positive amounts. Recording one means a migration plus a sign
convention running through `alreadyPaid`, `clawback` and the school-facing
history.
The other half of the ask — carry the overpayment forward — already works:
`alreadyPaid` is an all-time sum, so the next cycle starts in the hole and
absorbs the excess with no operator action. What was missing was any way to
see that happening, which is what this change fixes. Tests pin the
carry-forward across three successive states.
§2 of the issue (mismatch guard skipped on retry) does not reproduce: the
amount input has cleared `mismatch` on every change since the dialog was
written in 3a36763, so an edited amount re-submits with confirmMismatch=false
and is re-validated. Documented both sides of that contract in place, since
reading `mismatch !== null` in isolation is what made it look bypass-prone.
* fix(payments): translate the platform payouts screen and correct the overpaid copy (#516)
Review follow-up on top of the overpayment fix. Four changes, none of them to
the arithmetic:
- Copy. The banner promised that an overpayment "recovers itself". That only
holds while the school keeps selling: a dormant or departed school never
generates the sales that absorb the excess, and neither does a mis-keyed
payout row, so the page was telling the operator to do nothing about real
money. It now says the excess comes off the next payout automatically and
that a school with no further sales has to be settled outside the platform.
Same caveat added to the module doc in payouts-owed.ts.
- Overlap. A refund drops a sale out of grossOwed while its payout stays inside
alreadyPaid, so the same money can show up as both clawback and overpaid, in
adjacent columns, under two banners that each said "nothing to do". The
overpaid banner now names that overlap when both are present.
- i18n. /platform was English-only, unlike the school-facing payouts page. The
page and the Mark-as-Paid dialog now read from a new platform.payouts
namespace in en.json and es.json, and amounts format in the request locale
rather than a hardcoded en-US. A unit test asserts en/es parity for that
namespace (missing keys fall back silently, so nothing else would catch it).
- Composition. The Overpaid cell was a near-verbatim copy of the clawback cell.
Both are now explicit variants over one ReportedAmountCell, and each carries
an icon so the two columns aren't distinguished by colour alone (WCAG 1.4.1).
Overpaid moves off purple, which is the brand primary hue, onto sky.
Verified against local Supabase with a seeded overpaid tenant: both banners,
both cells, the ICU plural and the mismatch guard render correctly in English
and Spanish. Spanish copy needs a native review pass before merge.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TAaTqrkXy9kdWn5yk9eu5F
---------
Co-authored-by: Guillermo Marin <52298929+guillermoscript@users.noreply.github.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
0 commit comments