1. 04 Sep, 2026 1 commit
    • Mahmoud Aglan's avatar
      build(proposal): add PDF export with print stylesheet · 14972ee2
      Mahmoud Aglan authored
      The microsite is built for scrolling, so a naive print lost most of it:
      reveals start invisible, the phone shows one prototype screen at a time,
      the portal shows one admin screen, and <details> print collapsed.
      
      - @media print in styles.css: force reveals visible, drop the fixed nav
        and prototype tools, start each section on a fresh page, and mark cards,
        timeline items, tables and price blocks break-inside:avoid so none is
        split across a page boundary
      - generate-pdf.mjs (puppeteer): expands both prototypes before printing —
        the single phone frame becomes a labelled 3x3 grid of nine real app
        screens, and all seven portal screens are stacked. 1240x1754 pages (A4
        proportion at 150dpi), backgrounds on.
      - Adds a "تحميل العرض PDF" button to the hero; the print stylesheet hides
        .btn-row so it does not appear inside the PDF itself
      
      Output is 14 pages with a real text layer: Arabic extracts correctly and
      figures stay searchable. Ghostscript compression reaches 2.3MB but its
      font re-embedding drops Arabic strings from the text layer, so the
      uncompressed 8.5MB file is kept instead.
      Co-Authored-By: 's avatarClaude Opus 5 (1M context) <noreply@anthropic.com>
      14972ee2
  2. 03 Sep, 2026 5 commits
    • Mahmoud Aglan's avatar
      content(proposal): reprice to 500k, 2-4 weeks, add club news, trim tech detail · deefac32
      Mahmoud Aglan authored
      Commercial terms:
      - Price 900,000 -> 500,000 EGP, breakdown rebased to sum exactly
        (230k app + 160k portal/CMS + 60k gate/invites + 50k launch)
      - Payments simplified from three milestones to two: 360,000 on signing,
        140,000 on delivery
      - Timeline 4-6 weeks -> 2-4 weeks, timeline recompressed from five
        milestones to four
      - Early-signing discount recalculated: 5% = 25,000 (was 45,000)
      - "غير شاملة ضريبة القيمة المضافة" now stated in the price hero, the
        totals row, under the payment schedule, in the hero stat and footer,
        and bolded in the FAQ
      
      New scope — club news / blog:
      - Fourth axis added; news moved out of the deferred list into phase one
      - Live prototype gains a news feed, article page and a fifth tab, with
        home showing the two latest items
      - Portal gains a news management screen: article list with reach stats,
        editor with category, image drop and publish-notification toggle
      - Article artwork is a branded crest placeholder, not stock photography —
        the club supplies real images at launch and inventing them would
        misrepresent what has been approved
      
      Reduced technical detail per request:
      - Dropped the per-template column-name lists (six of them) and the three
        import-engine cards, replaced with one plain note
      - Removed API/OTP/RTL/iOS-14/Android-8 jargon throughout; rewrote the
        security and extensibility cards in business terms
      - Simplified the in-scope list wording
      
      Also makes .tbl scroll rather than clip: a table too wide for its column
      silently lost its last cells instead of becoming reachable.
      Co-Authored-By: 's avatarClaude Opus 5 (1M context) <noreply@anthropic.com>
      deefac32
    • Mahmoud Aglan's avatar
      feat(proposal): replace static mockup with a working app prototype · cf882849
      Mahmoud Aglan authored
      The prototype in the proposal was eight inline <div>s toggled with
      display:none — screenshots in a phone bezel. It now embeds a real
      single-page app from app/, so the screens shown in the proposal are the
      screens that ship, and the board can actually use it.
      
      app/ — hash routed, so any screen is linkable (#/dues):
        splash, login, otp, home, dues, pay, paying, success, receipts, qr,
        invites, activities, activity/:id, schedule, notifications, profile
      
      State is live, not scripted. Selecting dues recomputes the total before
      paying; paying clears those dues, creates a receipt and posts a
      notification; issuing an invite decrements the balance; subscribing to an
      activity adds the subscription, consumes a place and generates the first
      invoice into المستحقات. The QR regenerates every 60s against a countdown
      ring. Dark/light theme persists. Verified end to end by driving the app in
      headless Chrome, not just by rendering it.
      
      Chose an SPA over the multi-page pattern used by the older Proposal/
      prototype: no white flash between screens, real forward/back transitions,
      and shared state across screens, which is the whole point of showing a
      collection flow.
      
      Proposal integration:
      - Phone hosts <iframe src="app/">; the side list drives it over
        postMessage and the prototype reports its route back, so the list and
        the annotation stay in sync when someone navigates inside the phone
      - Theme toggle, reset, and open-fullscreen controls
      - Annotation panel rewritten per screen
      
      Removed 409 lines of static screen markup and the 93 lines of CSS that
      served it (.sbar/.app-hd/.mcard/.tabbar/.sport-item/.otp-row/.qr-*),
      verified dead by checking real class= usage, not substring matches.
      
      Adds .dockerignore: the image is built with `COPY . /usr/share/nginx/html`,
      so any stray working file in this folder would be served publicly.
      Co-Authored-By: 's avatarClaude Opus 5 (1M context) <noreply@anthropic.com>
      cf882849
    • Mahmoud Aglan's avatar
      design(proposal): replace all emoji with an inline SVG icon set · ff106889
      Mahmoud Aglan authored
      Emoji render as platform-specific colour cartoons (Apple/Windows/Android
      each differ) which reads informal in a document going to a club board, and
      they cannot inherit brand colour. Replaced every one with a 33-symbol
      inline SVG sprite: 24x24, stroke-based, currentColor, sized in em so each
      existing icon slot keeps its own scale.
      
      Removed: swimmer, bell, receipt, credit card, mobile, bank, football,
      martial-arts, tennis, cartwheel, page, lock, floppy, plug, envelope, gear,
      up/down arrows, and the EG regional-indicator flag pair.
      
      Also converted the geometric glyphs sitting in icon slots (fisheye,
      diamonds, house, quadrant-circle, square-fill, clock) plus the list
      check/x/arrow marks, so the icon layer is uniformly SVG rather than a mix
      of text glyphs and emoji. A source scan for emoji ranges now returns clean.
      
      Sprite is hidden with position/width/height rather than display:none,
      which can break <use> resolution in some engines.
      
      Fixes an unrelated pre-existing contrast bug found while verifying: .card
      h4 is declared after .dark h4 at equal specificity, so the three cards in
      the dark flow section rendered navy-on-navy. Added .dark .card h4.
      Co-Authored-By: 's avatarClaude Opus 5 (1M context) <noreply@anthropic.com>
      ff106889
    • Mahmoud Aglan's avatar
      content(proposal): de-AI the Arabic, add crest watermark, rescope to 4-6 weeks · 783a14e6
      Mahmoud Aglan authored
      Copy — the previous register read as marketing/AI boilerplate to a board
      audience. Removed the tells and rewrote in institutional MSA:
      - Drop staccato fragment headlines and their trailing periods
        ("ثلاثة محاور. لا أكثر." -> "نطاق المرحلة الأولى: ثلاثة محاور")
      - Drop rhetorical punchlines ("الاحتكاك يقتل الاشتراك."،
        "ليست صورًا تخيلية."، "من ينسى، لا يدفع.") for descriptive prose
      - Drop the «مصنع البيانات» metaphor and the pitch-deck eyebrow "لماذا الآن"
      - Rewrite all 8 prototype screen annotations from slogans to labels
        ("البوابة تعرف من يدخل" -> "الدخول بكود QR")
      - Reduce rhetorical em-dashes; keep structural ones only
      
      Schedule — project is 4-6 weeks, not 12. Timeline recompressed from six
      milestones over 12 weeks to five over 6, with the 4-week case stated as
      conditional on data and accounts landing in week 1. Updated hero stat,
      scope lede, price card, plan heading and footer badge to match.
      
      Design — elegant crest watermark:
      - Oversized club crest bleeding off the inline-start edge of the hero,
        gold-tinted, offset so it does not double with the hero logo
      - Alternating-side crest ghosts on problem/scope/flow/price sections
      - Crest in the price card and footer, plus a gold hairline on the footer
      - Uses background-image + filter, not mask-image: masks give a cleaner
        silhouette but do not paint in headless Chrome, so this variant is the
        one that can actually be verified before shipping
      - Logical properties throughout (inset-inline, margin-inline) so the
        watermark mirrors correctly in RTL
      - Added a prefers-reduced-motion guard for the scroll reveal
      Co-Authored-By: 's avatarClaude Opus 5 (1M context) <noreply@anthropic.com>
      783a14e6
    • Mahmoud Aglan's avatar
      content(proposal): rewrite Arabic copy, reprice to 900k, drop support scope · a91250e3
      Mahmoud Aglan authored
      Proposal microsite for Nady El-Seid mobile app (sayd-mobile).
      
      Arabic copy:
      - Fix agreement/tamyiz errors: اكتملت العدد -> اكتمل العدد,
        متأخر يومان -> يومين, "92 يوم" -> "92 يومًا", منها 214 متأخر -> متأخرًا,
        يومان تدريب -> يوما تدريب (dual mudaf drops nun)
      - Fix ambiguous/wrong forms: فيتحدث رصيده -> فيُحدَّث رصيده فورًا,
        الحمام الأولمبي -> حمام السباحة الأولمبي
      - Remove translationese: comma-lists rewritten with و, passives given
        back their agents, Egyptian تشتغلون -> تبدأون التشغيل
      - Unify register to plural address (اضغط -> اضغطوا, شاهد -> شاهدوا)
      - Apply reviewed headline/lede/ROI rewrites with corrected orthography
      
      Commercial terms:
      - Price 700,000 -> 900,000 EGP; breakdown rebased to sum exactly
        (400k app + 290k portal + 110k gate/invites + 100k integration)
      - Remove all post-launch support: annual maintenance contract, 6 free
        months, 1-year warranty, 99.5% SLA, first-year store fees. Delivery
        and release only, stated explicitly in a "غير مشمول" block and FAQ
      - Payments 40/30/20/10 -> 40/25/35: signing, data-entry plan approved
        + club opens required accounts and services, completed delivery
      - Timeline week 1-2 renamed to cover data-entry planning and names the
        account-opening as a parallel club obligation
      Co-Authored-By: 's avatarClaude Opus 5 (1M context) <noreply@anthropic.com>
      a91250e3
  3. 01 Sep, 2026 3 commits
    • Mahmoud Aglan's avatar
      dxfdgfnzdfh · 8a8d4428
      Mahmoud Aglan authored
      8a8d4428
    • Mahmoud Aglan's avatar
      docs: condense architecture-map protocol in CLAUDE.md · 2dd20e37
      Mahmoud Aglan authored
      Pre-existing uncommitted working-tree change, not part of the member search
      work. Committed separately so the 478-line reduction stays visible in history.
      Co-Authored-By: 's avatarClaude Opus 5 (1M context) <noreply@anthropic.com>
      2dd20e37
    • Mahmoud Aglan's avatar
      feat(members): unified people search across members and dependents · b7522599
      Mahmoud Aglan authored
      Overhaul /members/search so one bar finds anyone in the club — the member,
      a spouse, a child or a temporary member — and add an advanced filter panel.
      
      MemberSearchService becomes the single source of truth for people search:
      
      - UNIONs members + spouses + children + temporary_members into one normalised
        row per matched PERSON (person_type, relation, parent membership, rank).
      - Token AND matching on names, so word order no longer matters:
        "محمود احمد" finds "أحمد سيد محمود".
      - Arabic orthographic folding (أ إ آ ٱ→ا, ى→ي, ة→ه, ؤ→و, ئ→ي) applied to both
        the query and the column, so "احمد" matches "أحمد".
      - Arabic-Indic and Persian digits folded to ASCII before identifier matching.
      - Relevance ranking: exact membership number / national id, then name prefix,
        then substring. LIKE wildcards in user input are escaped.
      
      Scopes (member/spouse/child/temporary) and fields (name, membership number,
      national id, phone, form number, passport) are selectable; branch, membership
      status and membership type filter on the parent membership. A scope whose table
      lacks the requested field is skipped rather than matching nothing.
      
      The legacy search() keeps its exact signature and output shape, so the three
      existing API consumers are untouched.
      
      Also:
      - Split Member::getStatusOptions() (statuses an employee may ASSIGN) from
        getAllStatusLabels() (every status, for display/filtering). deceased,
        transferred and waived exist in live data but were missing from the list, so
        they could not be filtered on; they are deliberately kept out of the
        assignable set because the Death, Transfer and Waiver workflows own those
        transitions.
      - Dependent deep links honour spouse.view / child.view / temp.view and fall
        back to the membership file when denied.
      - Map children.relationship (son/daughter) and temporary_members.category
        (nanny/parent/unmarried_daughter) to Arabic for display.
      - The search form submitted to /members, dropping most of what was typed; it
        now posts back to /members/search.
      - Sidebar declared member.search while the route requires member.view; aligned.
      
      Architecture Map and Dependency Graph updated per project protocol, including
      the placeholder-ordering constraint in buildScopeQuery() and the three inline
      member-search SQL blocks that remain unconsolidated.
      Co-Authored-By: 's avatarClaude Opus 5 (1M context) <noreply@anthropic.com>
      b7522599
  4. 31 Aug, 2026 2 commits
    • Mahmoud Aglan's avatar
      docs(proposal): add Nady El-Seid mobile-app + data-portal proposal microsite · c39af584
      Mahmoud Aglan authored
      Phase-1 scope only: membership renewals/installments, sports activity
      invoices, QR gate entry + invitations. Interactive HTML prototypes for
      8 mobile screens and 6 portal screens, 6 downloadable CSV import
      templates, 12-week plan, 700,000 EGP commercial offer.
      
      Deployed to CapRover as app 'sayd-mobile'.
      Co-Authored-By: 's avatarClaude Opus 5 <noreply@anthropic.com>
      c39af584
    • Mahmoud Aglan's avatar
      fix(pricing,members): merge board-offer discounts into the member special-discount dropdown · 01e3b4ef
      Mahmoud Aglan authored
      Client clarified their earlier request: the member "special discount"
      dropdown should show board-approved special_discounts AND active
      عروض مجلس الإدارة (board_offers) cash discounts side by side, not one
      instead of the other — they'd stopped seeing anything they'd added
      under Board Offers.
      
      - Add members.special_discount_source ('special_discount'|'board_offer')
        and drop the hard FK on special_discount_id (a single column can no
        longer FK exactly one table). Integrity is now validated in
        MemberController::parseDiscountSelection().
      - BoardOffer::allActiveWithCashDiscount() surfaces board offers that
        define a cash discount as selectable options.
      - SpecialDiscountService::resolveAssignedDiscount()/amountForType() give
        one place that normalizes "the member's assigned discount" across both
        source tables — used by BillingService's invoice line item, the show
        page's applied-discount banner, and the dropdown's own validation.
      - fill-form/edit/show views render two <optgroup>s ("عروض مجلس الإدارة" /
        "الخصومات الخاصة") with prefixed option values (bo:<id> / sd:<id>) so a
        single form field can select from either table; edit.php's live
        discount-amount preview now handles fixed-amount discounts too, not
        just percentage.
      Co-Authored-By: 's avatarClaude Sonnet 5 <noreply@anthropic.com>
      01e3b4ef
  5. 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
  6. 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
  7. 28 Aug, 2026 2 commits
  8. 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
  9. 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
  10. 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
  11. 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
  12. 19 Aug, 2026 3 commits
  13. 18 Aug, 2026 8 commits