• Mahmoud Aglan's avatar
    feat(settlements): settle a member's account instead of editing around it · e45bd6d7
    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: 's avatarClaude Opus 5 (1M context) <noreply@anthropic.com>
    e45bd6d7
Name
Last commit
Last update
..
activities Loading commit data...
admin Loading commit data...
assignments Loading commit data...
attendance Loading commit data...
audit Loading commit data...
auth Loading commit data...
branches Loading commit data...
cash-sessions Loading commit data...
components Loading commit data...
dashboard Loading commit data...
documents Loading commit data...
enrollments Loading commit data...
evaluations Loading commit data...
events Loading commit data...
facilities Loading commit data...
financial Loading commit data...
groups Loading commit data...
guardian Loading commit data...
hr Loading commit data...
inventory Loading commit data...
invoices Loading commit data...
messaging Loading commit data...
notifications Loading commit data...
parent Loading commit data...
participants Loading commit data...
people Loading commit data...
portal Loading commit data...
pos Loading commit data...
pricing Loading commit data...
profile Loading commit data...
programs Loading commit data...
public Loading commit data...
receptionist Loading commit data...
reports Loading commit data...
requests Loading commit data...
roles Loading commit data...
schedule Loading commit data...
settings Loading commit data...
trainer Loading commit data...
training Loading commit data...
users Loading commit data...
wallets Loading commit data...
website Loading commit data...
whatsapp Loading commit data...
wizards Loading commit data...
branch-switcher.blade.php Loading commit data...
dashboard.blade.php Loading commit data...
global-search.blade.php Loading commit data...