-
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:Claude Opus 5 (1M context) <noreply@anthropic.com>
2b6e6997
| Name |
Last commit
|
Last update |
|---|---|---|
| .. | ||
| settlement-cart.blade.php |