• Mahmoud Aglan's avatar
    feat(settlements): let the desk put a player back in a programme · 2b6e6997
    Mahmoud Aglan authored
    The console could tell an operator that a player had no enrolment and nothing
    in the system was billing him. It could not do anything about it: all ten
    settlement actions move money on an account that already exists, so the one
    account that had stopped existing was the one the wizard could not fix. The
    operator read the diagnosis and had no button.
    
    `restore_enrollment` is that button. It delegates to EnrollmentService rather
    than inserting a row, so a restored enrolment passes the same checks a new one
    does — capacity, group status, one-group-per-programme, the branch rule — and
    lands identical to every other row in the table. A repair that produces a
    slightly different shape of row is a second bug waiting.
    
    It raises no invoice. `next_billing_date` comes out as the 1st of next month,
    so the cycle resumes on its own and the current month stays a deliberate act:
    the operator adds `bill_month` for it if it is owed, with the amount in front
    of them. Restoring a subscription and charging for it are two decisions and the
    second is not ours to assume.
    
    The group is suggested, not chosen. The enrolment that knew the programme is
    gone; the only surviving record is the name on the player's last subscription
    invoice, and that programme was deleted and recreated under a new one — so it
    cannot be looked up, only matched. Matching is on the years in the name,
    because these programmes are birth-year cohorts: «اكاديمية 2017 -2018» and
    «أكاديمية (2017-2018)» share nothing as strings and are obviously one cohort to
    a human. Cohorts written in two digits match nothing and a retired squad has no
    successor, which is exactly where the person at the desk knows and this screen
    does not — so the suggestion is labelled as a guess and the full list is always
    there.
    
    Two bugs found while proving it works, both real rather than test-only:
    
    - EnrollmentService left `academy_id` to BelongsToAcademy, which fills it from
      the `current_academy` container binding — bound only inside a web request. An
      enrolment created from a command, a queued job or a service died on a NOT
      NULL violation. It is now stated from the participant, which is true under
      every caller and which the trait leaves alone.
    - The group list sorted with `sortBy([fn, fn])`. sortBy reads an array as
      [column, direction] pairs, so it sorted by neither and buried the suggestion
      mid-list, where an operator in a hurry never sees it.
    
    Verified end to end against the restored tenant: stage the restore, apply it,
    and the player is enrolled in the suggested group with next month's billing
    date and not one new invoice. 498 tests, 389 pass / 109 skip on Postgres and
    313 / 185 on SQLite.
    Co-Authored-By: 's avatarClaude Opus 5 (1M context) <noreply@anthropic.com>
    2b6e6997
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...