-
Mahmoud Aglan authored
A club that ran on paper for years does not arrive in the system as a clean ledger. On the first academy live, 33 players were registered with an invoice raised and the "pay now" toggle left off — 25 of them in two data-entry evenings — and 27 of those are now carrying an unpaid registration month plus an unpaid September renewal. Nine paid for the federation card in instalments typed into free-text lines. Eight invoices were issued at zero because no price existed yet. Three people exist twice. None of that is a bug in one screen; it is a whole class of file that reality got ahead of. The desk had four tools that each did a slice: collect a payment, correct one invoice's amount, back-fill missing invoices, register someone who started months ago. None of them answers the question an operator has in front of a parent — this file is wrong in several ways at once, what do we do about all of it — so corrections were made wherever a screen allowed them and the ledger drifted further. SettlementService applies a reviewed set of corrections as one transaction and one record: money taken and never entered (on the day it was actually taken), a month closed for less than it was billed because the player joined halfway through, a month dropped entirely, a month nobody billed, a card or kit sold outside the system, a free-text line linked to the product it was really paying for, an agreed instalment plan, a payment sitting on the wrong month, and an overpayment held as wallet credit. Money moves through PaymentService so the ledger, the balance and the receipt all happen; stock through InventoryService; a waiver is written as the admin_override the roster already knows how to explain, leaving subtotal_amount alone so "650 of 900, discounted" still reads. Nothing calls auth() or session(): actor, branch and amounts are parameters. AccountAnomalyScanner finds the files rather than waiting for an argument at the desk — seven cases, worst first, each with the sentence that says what to check. SettlementWorklist lists them with a CSV export; AccountSettlementWizard puts one account on a page, proposes the corrections that fit what it found, shows exactly what will be collected, waived and billed, and demands a written reason before it writes anything. Both screens are gated on a new settlements.manage permission — waiving a month is the academy's call, and an owner should not need a platform administrator to make it — delivered by migration as well as seeder, since db:seed only runs on a first deploy. Two things the tests caught rather than production: Postgres refuses FOR UPDATE on an aggregate, so numbering settlements from max(id) would have rolled back a whole settlement the operator had already confirmed; and payment_plans_status_check has no 'partial', so a part-paid plan is active with the count saying how far along it is. Verified against a restored oc-sport tenant: 20 settlement cases and 7 render/permission cases pass, including cross-participant access, a future date, an oversized payment, and a failing second action rolling the first one back. Full suite 298 tests, no failures. Co-Authored-By:Claude Opus 5 (1M context) <noreply@anthropic.com>
e45bd6d7