• Mahmoud Aglan's avatar
    feat(terminology): let each branch call its people what it actually calls them · e8def3f5
    Mahmoud Aglan authored
    The product calls a person who belongs to the organisation عضو and
    everyone else غير عضو. That is only true for a club. The same software
    runs inside residential compounds (مقيم / غير مقيم), on beaches and in
    resorts (مشترك / زائر), and in hotels (نزيل / زائر), where every screen
    read as though it had been written for somebody else.
    
    The data model does not move: participants.membership_type still holds
    'member' and 'non_member', every query and every enum is untouched, and
    nothing about pricing or membership logic changes. Only the words shown
    to a human do, and they are chosen per branch — one academy can run a
    club and a compound at the same time.
    
    Eight Arabic forms are stored per branch rather than derived, because
    Arabic will not let you derive them: ال prefixes the noun in العضو but
    the second word in غير العضو, and the plural of عضو is أعضاء while the
    plural of مقيم is مقيمين. Everything a screen needs beyond those eight —
    نوع العضوية, رقم العضوية, سعر العضو — composes from them in
    Identity\Support\Terminology. Plurals are stored in the ـين form because
    almost every site prints them after a preposition or in an idafa.
    
    TerminologyService takes an explicit ?int $branchId like every other
    domain service, so it stays callable from a queued notification or an
    artisan report; terms()/term()/membership_label() in app/Helpers are the
    adapter that reads BranchContext for a Blade file. Reads go through the
    query builder rather than the model, because the sidebar consults this on
    every page and BranchSetting's branch scope would filter a settings
    screen editing branch B, viewed from branch A, down to nothing. The
    service is bound scoped, so its memo lives exactly one request.
    
    Presets (club, compound, resort, hotel, gym) fill the settings screen and
    stay editable afterwards; the fields are stored in the existing
    branch_settings table, so there is no schema change. An unconfigured
    branch — and the public website, and a user in all-branches mode — gets
    the default preset rather than blank labels. term() throws on an unknown
    key rather than echoing it back, because a typo that printed
    "membershipp_type" onto a receipt would survive review.
    
    62 call sites converted across the sidebar, participant screens, the
    registration wizard, the portal, reports, messaging, imports and the
    printed card. New screen at /settings/terminology behind
    permission:settings.manage.
    
    Verified: 413 tests green, no failures.
    Co-Authored-By: 's avatarClaude Opus 5 (1M context) <noreply@anthropic.com>
    e8def3f5