1. 22 Jul, 2026 20 commits
  2. 21 Jul, 2026 20 commits
    • Fares's avatar
      fix(cashier): show wife's name (recipient) for death_fee payments, not deceased · 8f9a4b37
      Fares authored
      The cashier payment page was displaying the deceased husband's info
      (name, form number, membership number). For death_fee payments, the
      recipient is the wife — extracted from the notes JSON. Form/membership
      numbers are hidden since the wife doesn't have them yet.
      Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
      8f9a4b37
    • Fares's avatar
      fix(death): trustee fee percentage now reliably uses current plan price · 447dfbce
      Fares authored
      The board approval percentage calculation had fragile fallbacks that could
      miss the pricing_configs data and fall back to the old membership_value.
      Added cascading fallbacks: PricingEngine → branch+qual → branch-only →
      any active pricing → membership_value (last resort).
      
      Also fixed the JS preview in show.php which was using the stored
      membership_value (114,000) instead of the current plan price (150,000)
      for the real-time calculation hint.
      Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
      447dfbce
    • Fares's avatar
      fix(transfers): child separation gives NEW number, parent keeps theirs · 93215a48
      Fares authored
      Child separation is not a death/full transfer — the parent stays active
      with their original membership number. The separated child gets a brand
      new sequential number via MemberNumberGenerator::getNextMembershipNumber().
      
      Only full_transfer type archives the source member and passes the same
      number to the new member.
      Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
      93215a48
    • Fares's avatar
      feat(transfers): enhance child separation dependent inputs with full fields · 9ae45ad7
      Fares authored
      Form now collects for spouses: date_of_birth, nationality, marriage_date,
      join_date, payment_date. For children: date_of_birth, join_date.
      
      Validates spouse minimum age (SPOUSE_MIN_AGE rule, default 21) and
      requires marriage_date. Non-Egyptian spouses get addition_fee calculated
      as SPOUSE_FOREIGN_FEE% of current plan price at completion time.
      
      TransferProcessor now uses submitted dates instead of defaults and stores
      nationality and addition_fee on the spouse record.
      Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
      9ae45ad7
    • Fares's avatar
      fix(transfers): add missing marriage_date column to transfer_requests · ffbaa774
      Fares authored
      The column was in the model's fillable but never had a migration. Causes
      'Column not found' error when storing female child separation requests.
      Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
      ffbaa774
    • Fares's avatar
      fix(members): prevent empty string for decimal addition_fee in retroactive wizard · a4e9cc61
      Fares authored
      Empty form fields post as '' which ?? doesn't catch (only null). MySQL
      rejects '' for decimal columns. Switched to ?: so empty strings fall
      through to the '0.00' default.
      Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
      a4e9cc61
    • Fares's avatar
      docs(death): update architecture map with corrected fee logic · 9d2a1e86
      Fares authored
      Reflects that deceased is excluded from annual sub, trustee fee uses
      current plan price via PricingEngine, and Pricing module is now a dependency.
      Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
      9d2a1e86
    • Fares's avatar
      fix(death): exclude deceased from annual sub; use current plan price for trustee fee · d48cc140
      Fares authored
      The deceased member should not be charged annual subscription — the primary
      spouse becomes the new member. Trustee fee percentage now uses the current
      membership plan price from pricing_configs (e.g. 150,000) instead of the
      historical membership_value stored at enrollment time (e.g. 114,000).
      Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
      d48cc140
    • Fares's avatar
      fix(transfers): resolve NOT NULL field errors during child separation completion · a5b513bb
      Fares authored
      The TransferProcessor::execute() was failing with "Field 'date_of_birth'
      doesn't have a default value" because:
      1. Child's date_of_birth could be empty string (not null), bypassing ?? operator
      2. Children table has no phone_mobile column, causing NOT NULL violation on members
      3. Spouse creation was missing required fields (date_of_birth, marriage_date, join_date)
      4. Temporary member creation was missing required fields (date_of_birth, gender, category)
      
      Changes:
      - Sanitize DOB: treat empty string and '0000-00-00' as null before NID fallback
      - Add phone_mobile fallback chain: subject → source member → placeholder
      - Spouse inserts now include all NOT NULL fields with sensible defaults
      - Children inserts include child_order
      - Temporary member inserts include category, date_of_birth, gender with NID parsing
      - Death module: fix secondary spouse phone_mobile NULL issue
      Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
      a5b513bb
    • Fares's avatar
    • Fares's avatar
      feat(death,waiver): enhance death module with 6 changes; fix waiver post-completion issues · e4340b36
      Fares authored
      Death module:
      - Annual subscription now includes ALL family members (spouses, children, temps + dev fee)
      - Temporary members distribution UI added to create form
      - Payment receipt created in primary spouse's name (not deceased)
      - Form number field added to membership application
      - Deceased member name stored on new membership record
      - Full Arabic audit trail logging on completion
      
      Waiver module:
      - Mark ALL family subscriptions as paid after completion (not just member)
      - Set activated_by_payment_id on all dependents
      - Copy membership_value from source to target member
      - Add transferred_from_waiver_id and waived_from_member_name to members
      - Display waiver source badge and archive section on member profile
      Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
      e4340b36
    • Fares's avatar
      feat(pricing): enhance special discounts with conditions, bonuses, and date ranges · 9a8823b4
      Fares authored
      Expanded the special discounts system to support:
      - Discount types: percentage, fixed amount, or free subscription
      - Date range (effective_from/effective_to) for time-bounded offers
      - Conditions: none (direct), full_payment, or min_payment threshold
      - Bonus: free subscription years granted when discount activates
      - Applies-to targeting: membership_fee, subscription, or all
      
      Added SpecialDiscountService for evaluating conditional discounts and
      applying free subscription bonuses on member.activated event.
      
      Updated form UI with dynamic visibility and validation.
      
      Migration: Phase_98_003 adds new columns to special_discounts table.
      Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
      9a8823b4
    • Fares's avatar
      fix(transfers): include date_of_birth when creating dependent children · e0e2fdd8
      Fares authored
      The children table requires date_of_birth (NOT NULL). The dependent
      creation loop now derives DOB from national_id via NationalIdParser,
      falling back to a placeholder if neither is available.
      Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
      e0e2fdd8
    • Fares's avatar
      fix(transfers): handle missing date_of_birth when completing transfer · 67907b67
      Fares authored
      When creating the new member record, date_of_birth may be NULL on the
      child record. Now attempts to derive DOB and gender from national_id
      via NationalIdParser before falling back to a placeholder.
      Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
      67907b67
    • Fares's avatar
      fix(transfers): fix annual subscription calculation and marriage_date column error · 705bbcbe
      Fares authored
      1. isCurrentYearSubscriptionCovered() was comparing activation date against
         its own fiscal year (always true) — now correctly compares against the
         current fiscal year's July 1st.
      
      2. Annual subscription in calculate() now includes all family members
         (spouses, children, temps) not just the member alone.
      
      3. marriage_date and dep count columns are only included in INSERT when
         non-null, preventing column-not-found error if migration hasn't run.
      Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
      705bbcbe
    • Fares's avatar
      fix(waiver): fix 4 issues after waiver completion · 2e4f8575
      Fares authored
      1. Subscriptions now marked as paid: after waiver completes, sync
         subscriptions for target member and mark current FY as paid since
         the fee was already collected in the waiver payment.
      
      2. Membership date: set created_at to today on waiver completion so the
         member profile shows the actual membership start date.
      
      3. Total paid shows correctly: BillingService now recognizes
         activated_by_payment_id as proof that form_fee and membership_fee
         are paid (covers waiver_fee and separation_fee payment types).
      
      4. Waiver list shows names: fix $r['member_name'] → $r['source_name']
         to match the SQL alias from WaiverRequest::search().
      Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
      2e4f8575
    • Fares's avatar
      feat(subscriptions): add per-row payment audit trail (receipt_number, paid_by, paid_at) · 0a2372df
      Fares authored
      Each subscription row now independently stores:
      - receipt_number: denormalized from receipts table for quick audit access
      - paid_by: FK to employees — who processed the payment
      - paid_at: already existed
      
      Migration adds columns and backfills existing paid rows from payments/receipts.
      View shows "بيانات السداد" column with date, receipt number, and employee name.
      Model query now JOINs employees for paid_by_name display.
      Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
      0a2372df
    • Fares's avatar
      feat(transfers): gender-based separation logic — males use work/grad/age25,... · 316c58eb
      Fares authored
      feat(transfers): gender-based separation logic — males use work/grad/age25, females use marriage date
      
      - Males: effective_date = min(max(work_date, graduation_date), date_turned_25) — unchanged
      - Females: effective_date = marriage_date only (no work/graduation/age25 fields)
      - Add data-gender attribute to child select options for JS detection
      - Show male-specific fields (employment, graduation, work_date, date25) only for males
      - Show marriage_date field only for females
      - Females bypass age >= 25 filter in dropdown (eligible at any age via marriage)
      - Server-side age validation skipped for female children
      - Add marriage_date column to migration and model fillable
      - Frontend dynamically switches between male/female form based on selected child's gender
      Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
      316c58eb
    • Fares's avatar
      feat(transfers): complete child separation with dependents, fix effective date logic · 1c8ee16d
      Fares authored
      - Fix effective date: use min(max(work_date, graduation_date), date_turned_25) instead of min(work_date, date_turned_25)
      - Make graduation_date mandatory when child is employed
      - Annual subscription now includes all family members (member + spouses + children + temps)
      - Add dependents section to form: user specifies counts and details (name, national_id) for each person joining new membership
      - TransferProcessor creates dependent records (spouses, children, temporary_members) from notes JSON on completion
      - Migration adds target_spouses_count, target_children_count, target_temps_count columns
      - Updated architecture map
      Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
      1c8ee16d
    • Fares's avatar
      fix(subscriptions): prevent duplicate subscription rows with unique constraint · 5a5aba46
      Fares authored
      Root causes of duplicate members in yearly subscriptions:
      1. No DB-level unique constraint allowed race conditions between
         SubscriptionGenerator, SyncService, and RetroactiveMembershipService
      2. SyncService set person_id=NULL for member rows vs Generator's person_id=memberId
      3. RetroactiveMembershipService did blind INSERTs with no dedup check
      
      Fix:
      - Migration removes existing duplicates (keeps paid row, lowest ID tiebreak)
      - Normalizes NULL person_id on member rows
      - Adds UNIQUE INDEX (member_id, financial_year, person_type, person_id)
      - All insert paths catch Duplicate entry exceptions as race guard
      - SyncService now sets person_id=memberId matching Generator
      - RetroactiveMembershipService checks for existing row before INSERT
      Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
      5a5aba46