• Mahmoud Aglan's avatar
    feat(portal): notification preferences, language, and transfer reconciliation · 8d392c04
    Mahmoud Aglan authored
    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: 's avatarClaude Opus 5 (1M context) <noreply@anthropic.com>
    8d392c04
NotificationPreference.php 996 Bytes