1. 30 Aug, 2026 4 commits
    • Mahmoud Aglan's avatar
      fix(members,subscriptions): drop due-date column from family tables, collect... · 67196669
      Mahmoud Aglan authored
      fix(members,subscriptions): drop due-date column from family tables, collect annual subscription via الخزنة
      
      - Remove the "تاريخ الاستحقاق" column from the spouses/children/temporary
        members tables on the member show page (display-only, no schema change).
      - Annual subscription payments no longer post directly from the
        اشتراك سنوي page. SubscriptionController::payYear() now queues a
        payment_request instead of calling PaymentService::processPayment()
        directly; the subscription rows are only marked paid once خزنة العضويات
        (Membership Treasury / Cashier) actually collects it, via a new
        payment_request.completed listener
        (SubscriptionSyncService::completeFamilyYearPayment()).
      - Closed the same bypass on the legacy generic /payments/process/{id}
        page, which had its own divergent partial-payment, oldest-year-first
        logic for annual_subscription that skipped the treasury entirely and
        violated the all-or-nothing-per-family rule; that path now redirects to
        the member's subscriptions page instead.
      Co-Authored-By: 's avatarClaude Sonnet 5 <noreply@anthropic.com>
      67196669
    • Mahmoud Aglan's avatar
      fix(pricing): require board decision for member special discounts, allow... · 570cb709
      Mahmoud Aglan authored
      fix(pricing): require board decision for member special discounts, allow fixed-amount down payment on board offers
      
      - special_discounts gains board_decision_number/board_decision_date; the
        member-facing "special discount" dropdown (fill-form, edit, show, apply)
        now only lists/accepts discounts backed by a board decision instead of
        the full unaudited special_discounts catalog, per client request that the
        dropdown should only read board-approved discounts.
      - board_offers gains inst_down_payment_type (percentage/fixed_amount) so a
        board-approved offer's installment down payment (المقدم) can be a fixed
        cash amount, not only a percentage. Wired through BoardOfferService,
        InstallmentCalculator (new min_down_amount override) and
        PaymentLifecycleService so the fixed amount is actually enforced at
        billing time, not just in the admin form.
      Co-Authored-By: 's avatarClaude Sonnet 5 <noreply@anthropic.com>
      570cb709
    • Mahmoud Aglan's avatar
      fix(auth): resolve route/menu/role permission drift causing phantom 403s · d39d9293
      Mahmoud Aglan authored
      Users saw sidebar links that returned 403. Root cause was drift between four
      independently-authored declaration sets that nothing reconciles: the permission
      catalogue (bootstrap.php), the route gate (Routes.php), the menu gate
      (MenuRegistry) and the role grants (seeds).
      
      Route shadowing (Router::dispatch is first-match-wins over a sorted module glob):
      - GET /reports was declared by both Members and Reports; Members won and enforced
        member.reports while the sidebar gated on report.view_membership. Members'
        report routes moved to /members/reports/*.
      - GET /sports-dashboard[/export] was declared by three modules, so the dashboard
        index and its drill-downs were served by different modules. Disciplines ->
        /disciplines/dashboard, PlaygroundAdmin -> /playgrounds/dashboard[/export];
        /sports-dashboard is now wholly owned by SportsDashboard.
      - Members/Routes.php used unconstrained {id} in 15 routes, so /members/<anything>
        was swallowed by MemberController@show. Constrained to {id:\d+}, matching every
        other module. All 25 affected links updated.
      
      Gate alignment:
      - Six menu entries gated on a different permission than the route they link to
        (/members/search, /sports, /carnets, /rentals/entities,
        /notifications/templates, /reports).
      
      Authorization bypasses:
      - RetroactiveWizardController hardcoded a role_code = 'super_admin' query,
        throwing "هذه الأداة متاحة فقط لمدير النظام". Replaced with a registered
        member.retroactive permission enforced by the route and grantable via the
        Roles UI.
      - report_definitions.required_permission was stored and displayed but never
        checked, so report.view_membership was enough to open ANY report by code,
        including financial ones. Now enforced on view/export/print; the listing
        filters to what the viewer can actually run.
      
      Role grants (Phase_105_001, idempotent):
      - Closes the reported gaps for report_viewer, general_manager, receptionist,
        sports_officer, academy_manager and membership_director; grants the sports
        report keys to board_member/auditor so enforcing the per-report permission
        does not silently remove reports; revokes member.view/member.search from
        facilities_manager, who keeps bookings and reservations.
      
      Data correctness:
      - SaFinanceReportService read base_price from sa_pricing_rules, a facility
        booking table with neither that column nor activity_type, and derived revenue
        as headcount x a rate-card price. Now sums actual sa_registrations
        .registration_fee, matching how subscription and booking revenue are computed.
      
      Regression guard:
      - php cli.php permissions:audit reconciles all four declaration sets, reproduces
        the router's load order, and exits non-zero on drift. Run it after touching any
        Routes.php, menu block or role seed.
      
      Docs: new docs/architecture-maps/Authorization.md; cross-module authorization
      section added to DEPENDENCY-GRAPH.md.
      
      Note: the live DB was unreachable from the dev environment, so role grants were
      verified by replaying the seeds and schema came from migrations, not the live DB.
      PHPUnit is not installed locally; all changed files lint clean.
      Co-Authored-By: 's avatarClaude Opus 5 <noreply@anthropic.com>
      d39d9293
    • Mahmoud Aglan's avatar
      fix(sports-activity): resolve broken queries, fee/threshold drift, and billing gaps · e139a8a2
      Mahmoud Aglan authored
      - ActivitySubscriptions: fix generate/calculateRate querying nonexistent
        `enrollments` table; use `academy_enrollments` with correct columns
      - SportsDashboard: fix queries against nonexistent `disciplines` table;
        use `sport_disciplines`
      - Sports: unify conversion-fee percentage to a single source
        (MembershipRulesService::getAthleticMemberConversionRules), preventing
        the eligibility preview from drifting from what's actually charged
      - SportsActivity: align absence-threshold fallback defaults between
        AttendanceRuleService and TrainingAttendanceService via a shared constant
      - SportsActivity: auto-bill first month on Registration Wizard completion,
        matching the direct-enrollment path so wizard-registered players aren't
        left uncharged until the monthly batch runs
      - ActivitySubscriptions: guard paySubscription() to only transition
        pending/overdue -> paid, making it idempotent against the (currently
        unreachable) payment.completed listener path
      - ActivitySubscriptions: dispatch academy.enrollment_created from the
        enroll wizard so PlayerAffairs' auto-billing listener actually fires
      
      Also adds/updates Architecture Maps for Sports, SportsActivity,
      SportsDashboard, ActivitySubscriptions and the cross-module Dependency
      Graph, per this repo's mandatory architecture-map workflow.
      Co-Authored-By: 's avatarClaude Opus 5 <noreply@anthropic.com>
      Co-Authored-By: 's avatarClaude Sonnet 5 <noreply@anthropic.com>
      e139a8a2
  2. 29 Aug, 2026 2 commits
    • Mahmoud Aglan's avatar
      feat(dashboard): role-aware dashboards + super-admin command center · f46e7a77
      Mahmoud Aglan authored
      Every user previously saw the same dashboard: DashboardDataService::getData()
      returned one fixed payload with no reference to the current employee. A cashier
      got membership stats they could not act on; an HR manager got revenue instead of
      headcount.
      
      Each role now gets a curated dashboard. Role presets pick the layout, permissions
      gate every widget (mirroring MenuRegistry::getVisible), and multi-role users get
      the deduped union of their presets. Super admin gets a 5-KPI, 16-widget command
      center across six sections.
      
      Wires up WidgetRegistry, which existed fully written but was used by nothing.
      
      144 widgets, all SQL executed and verified against the live schema — 46 were
      corrected during verification, including a month-to-date figure compared against
      a full prior month (a fake collapse every month), spouse counts missing their
      status filter, and receivables that included debt owed by archived deceased
      members.
      
      Only the headline plus first six widgets query on load; the rest hydrate through
      GET /dashboard/widget/{key}, which re-checks permission server-side and renders
      via the same partial as the eager path. Employees with no mapped role fall back
      to the previous shared dashboard, preserved verbatim.
      
      Also loads Chart.js, which PlayerAffairs has always called behind a
      `typeof Chart !== 'undefined'` guard while the library was loaded nowhere —
      those evaluation charts were silently dead and now render.
      Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
      f46e7a77
    • Mahmoud Aglan's avatar
      commit all · 7b74997d
      Mahmoud Aglan authored
      7b74997d
  3. 28 Aug, 2026 2 commits
  4. 27 Aug, 2026 1 commit
    • Mahmoud Aglan's avatar
      feat(sa-reports, discounts): sports activity reports + membership discount fixes · 40f1c0c0
      Mahmoud Aglan authored
      Sports Activity Reports: player reports with filters (discipline/program/group/
      player type/medical/payment status/branch) and finance reports (revenue/costs/
      profit with daily/weekly/monthly/yearly/3yr/5yr/custom periods). CSV and PDF
      export for both. Role-based access with 3 new permissions.
      
      Membership Discounts: fix BillingService to include regulatory discount as bill
      line item, add regulatory discount section to edit page, add FYI discount guide
      to show page covering all 3 discount types (special, regulatory, board offers),
      handle regulatory discount in update controller with document upload.
      Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
      40f1c0c0
  5. 26 Aug, 2026 4 commits
    • Mahmoud Aglan's avatar
    • Mahmoud Aglan's avatar
      feat(hr): work schedules, job description cards, org hierarchy · 9342a5ee
      Mahmoud Aglan authored
      Phase 3: Weekly work schedules
      - Migration: hr_weekly_schedules table (per-employee per-day shift times + rest days)
      - Seed: import 59 employees' weekly schedules from club's work schedule sheet
      - Seed: 13 shift definitions (9-5, 3-11, 10-6, 12-8, etc.)
      
      Phase 7B: Job description cards
      - Migration: hr_job_descriptions table (purpose, authority, duties per job title)
      - View: printable بطاقة الوصف الوظيفي matching club's official template
      - Route: GET /hr/job-titles/{id}/description-card
      
      Phase 8: Org structure hierarchy
      - Migration sets parent_id=1 (EXEC) for all other departments
      Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
      9342a5ee
    • Mahmoud Aglan's avatar
      fix(hr-seeds): fix department type constraint + create employee login accounts · f61ceb27
      Mahmoud Aglan authored
      - Phase_05_001: changed department_type from 'edara' to 'idara' (CHECK constraint only allows 'idara'/'qism')
      - Phase_05_002: create employees table records before hr_employee_profiles to satisfy FK constraint
        - Generates unique usernames (emp0001, emp0002, ...)
        - Sets default password 'Club@2026' with force_password_change=1
        - Links hr_employee_profiles.employee_id to newly created employees.id
      Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
      f61ceb27
    • Mahmoud Aglan's avatar
      feat(hr): 2025 Labour Law compliance + employee import + payroll alignment · 23dd0208
      Mahmoud Aglan authored
      Phase 1 - Leave System (2025 Law):
      - Annual leave: 15d (1st year) → 21d (after 1y) → 30d (10y+/age 50+) → 45d (disability)
      - Maternity: 90 → 120 days (4 months), hajj: now PAID with 5y service min
      - New leave types: childcare (unpaid, 3 career, 24mo gap), paternity (1d, 3 career), exam
      - Casual leave: enforce max 2 consecutive days
      - Childcare gap enforcement, service months validation
      
      Phase 2 - Employee Import:
      - Extract 75 employees from HR registry Excel → JSON seed data
      - 34 Bank of Alexandria accounts extracted for salary transfers
      - 14 departments + all job titles auto-created from registry
      - Migration adds variable_salary + total_allowances columns
      
      Phase 4 - Payroll Alignment (July 2026 format):
      - Solidarity fund: 0.25% of gross (صندوق التكافل)
      - Stamp duty: 3% of net (دمغة عادية وإضافية) - now percentage-based
      - Emergency fund + VAT config keys added
      - calculation_json includes full breakdown
      
      Phase 5 - Bank Transfer Export:
      - BankTransferService generates transfer data from payroll runs
      - CSV export with BOM for Arabic compatibility
      - View + route: /hr/payroll/periods/{id}/bank-transfer
      
      Phase 6 - Performance Evaluation:
      - Seed 10-criteria template matching club's official form (100 points)
      - Dual evaluator support (direct manager + general supervisor)
      - 5-tier rating labels (ضعيف → ممتاز)
      
      Phase 7 - Reports & Forms:
      - Work receipt printable form (إقرار استلام العمل)
      - Workforce statement report (بيان القوة الفعلية)
      Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
      23dd0208
  6. 23 Aug, 2026 3 commits
    • Mahmoud Aglan's avatar
      ui(members): add FYI hints table showing all available regulatory discounts · 9585e99f
      Mahmoud Aglan authored
      Always-visible reference table on the membership form showing:
      - All 7 articles with their discount percentages and conditions
      - Which branches each applies to
      - What proof is needed (auto-verify vs document upload)
      - Non-stacking rule note
      Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
      9585e99f
    • Mahmoud Aglan's avatar
      feat(members): integrate regulatory discounts into membership form · b86c24f6
      Mahmoud Aglan authored
      - Add regulatory_discount_id/amount/document columns to members table
      - Add eligibility type selector with conditional sections per type
      - Auto-verification for cross-branch (checks members DB) and club employee (HR)
      - Document upload for types requiring manual proof (gov, ministry, board, group)
      - AJAX eligibility check button calls /pricing/regulatory-discounts/check-eligibility
      - Creates audit trail application record on form submit (status: pending)
      - Dynamic UI: shows/hides relevant fields based on selected type
      Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
      b86c24f6
    • Mahmoud Aglan's avatar
      feat(pricing): implement regulatory discounts (Articles 97-102, 110) · 3f42fa01
      Mahmoud Aglan authored
      Full-cycle implementation of club bylaw discount rules:
      - Art 97: Cross-branch member discounts (50% Sheraton6th Oct, 25% →Admin Capital)
      - Art 98: Government employees 50%, Ministry of Youth 62.5% at Admin Capital
      - Art 99: Ministry of Youth 25% at Sheraton/6th Oct
      - Art 100: Board of Trustees 50% + 2yr interest-free installment
      - Art 101: Ministry employees installment-only (no discount)
      - Art 102: Club employees (5+ yrs) up to 15%
      - Art 110: Group membership tiered (5-10→3%, 11-20→7%, 21+→10%)
      
      Includes: migration, seed data, model, service with eligibility engine,
      controller (CRUD + eligibility check API + application workflow),
      views (index, form, applications), routes, permissions, menu entry,
      and PricingEngine integration.
      Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
      3f42fa01
  7. 20 Aug, 2026 2 commits
    • Mahmoud Aglan's avatar
    • Mahmoud Aglan's avatar
      feat(pricing+subscriptions): multi-tier board offers + fix first-year subscription logic · cb63de72
      Mahmoud Aglan authored
      Board Offers:
      - Add board_offer_tiers table for multiple payment plans per offer
      - Each offer now supports 4 tiers: cash (10% discount), 24mo/0%, 40mo/15%, 60mo/15%
      - View shows all tiers as selectable cards with pre-calculated breakdowns
      - Controller accepts offer_tier_id and uses tier-specific terms
      - Seed populates tiers for all active board offers
      - Legacy fallback preserved when no tiers are configured
      
      Subscription Fix:
      - First-year members no longer get subscription rows (membership fee covers current FY)
      - First-year dependents no longer get subscription rows (addition fee covers current FY)
      - MembershipValidationService bypasses subscription check for first-year members
      - AutoFreezeService.checkSubscriptionBlock bypasses for first-year members
      - MembershipRulesService.canPrintCarnet bypasses for first-year members
      - SubscriptionGenerator skips first-year members/dependents entirely
      - Individual subscription pay() now redirects to payYear() (all-or-nothing family payment)
      Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
      cb63de72
  8. 19 Aug, 2026 3 commits
  9. 18 Aug, 2026 9 commits
  10. 16 Aug, 2026 3 commits
  11. 12 Aug, 2026 7 commits