feat(portal): notification preferences, language, and transfer reconciliation
Three P1 items, and a latent bug each of them depended on.
SetLocale was registered nowhere. The middleware has existed since early on
and no middleware group ever included it, so `app()->getLocale()` returned the
config default on every request and the bilingual half of an Arabic-first
product was dead code. Worse, that default was 'en' — so every page announced
`lang="en"` while being marked `dir="rtl"`, telling a screen reader two
contradictory things about the same text. The default is now 'ar', the
middleware runs, and the portal's direction follows the locale instead of being
hardcoded.
notification_preferences had per-event, per-channel columns and no interface
anywhere: it was written to by the deleted API and by nothing else, so every
member received everything on every channel with no way to say otherwise. The
preferences screen defaults an unset choice to ON — the member has not asked
for less, and silently defaulting to off means a missed instalment nobody was
told about.
The device list is on the same screen, because push is the one channel whose
recipients a member cannot otherwise see: an old phone, a browser at work, a
device someone else now owns, all receiving silently until the token rotates.
Transfer reconciliation is the control that makes proof approval honest. A
screenshot is not evidence; matching the day's total against the academy's own
statement is. The report is per branch per day with deliberately blank
statement and signature columns, because a report that cannot be signed is not
a control. It also surfaces ageing proofs — a member told "we will check" who
heard nothing — and transfers recorded with no proof behind them, which are
legitimate but which a reconciler needs to expect.
One column asserts a database constraint rather than a number: an approved
proof with no payment is made unrepresentable by
payment_proofs_approved_payment_check, so a non-zero count there means the
constraint is gone, and the screen says exactly that. A constraint nobody ever
looks at is one you find out about the hard way.
notification_preferences.academy_id added to the model's fillable — S1 added
the column and the model was still writing rows that belonged to no tenant.
Verified on the restored tenant: 12 portal screens and 6 staff screens render
200; suite 76 pass on SQLite.
Co-Authored-By:
Claude Opus 5 (1M context) <noreply@anthropic.com>
Showing
Please register or sign in to comment