- 18 Jul, 2026 21 commits
-
-
Mahmoud Aglan authored
- ChequeService: add nextChequeNumberForPlan(planId) — returns next number scoped to the specific plan, not globally across all plans - ChequeController::index(): pass nextChequeNumForPlan to view - ChequeController::storeBatch(): load existing cheques before validation; check against existing numbers for duplicates; guard against exceeding requiredCount; coverage check uses existingTotal + batchTotal; only enforce full-coverage on the final batch - cheques.php JS: generator uses REMAINING_COUNT (not amount-math) for count, NEXT_NUM_FOR_PLAN for sequence start — correctly continues from cheque 6 if 5 already exist; preview shows "تكملة من #N" context note - cheques.php UI: yellow info banner when existing cheques present, showing count, remaining, and starting cheque number Co-Authored-By:Claude Sonnet 4.6 <noreply@anthropic.com>
-
Mahmoud Aglan authored
The runner was calling splitStatements(string) directly on migration['up'] without checking whether it was a Closure, causing a fatal type error on all idempotent closure-based migrations (Phase_94_001, _002, Phase_96_001). Now checks is_callable() first and invokes the closure, falling back to string SQL splitting for plain-string migrations. Co-Authored-By:Claude Sonnet 4.6 <noreply@anthropic.com>
-
Mahmoud Aglan authored
## Schema (Phase_96_001) - escalation_type / escalation_rate / escalation_tiers_json — flat or tiered annual rent increases - utilities_mode / utilities_rent_pct / utilities_facility_pct / facility_monthly_cost — support rent%, facility-cost%, or both - payment_due_day — configurable per-contract (default day 5) - late_fee_bank_rate — annual bank rate for daily penalty calculation - grace_period_months / early_termination_months — additional contract terms - Data migration: backfills utilities_rent_pct from utilities_percentage ## Service layer - RentalContractService: computes escalated total_amount across flat/tiered modes; handles all utilities modes; recalculates VAT and grand_total - RentalInvoiceService: calcBase() now escalation-aware (by period); bulkGenerate skips grace months and uses payment_due_day; calcLateFee supports bank-rate daily formula - Seeds RENTAL_LATE_FEE_BANK_RATE business rule (27.25% annual) ## UI - contract_form: new sections for escalation (dynamic tiers table), utilities mode, payment terms, extra contract conditions; year-by-year preview - contract_show: mode-aware utilities display, escalation card, grace/termination info Co-Authored-By:Claude Opus 4.8 <noreply@anthropic.com>
-
Mahmoud Aglan authored
- Installments: controller now computes pendingCount before passing to show view, fixing Undefined variable crash at show.php:4 - Transfers: add فصل أبناء employment-status workflow - Migration Phase_94_002 adds is_employed, graduation_date, work_date, date_turned_25, effective_transfer_date columns to transfer_requests - SeparationFeeCalculator: new calculateYearsFloor() (floor, never rounds up), computeChildSeparationDates() (effective = min(work_date, date_25) or date_25), calculateForChildSeparation() (uses floor years + current subscription price) - TransferController store(): reads employment fields, computes effective date, routes to new calculator for child_separation; saves all new fields - calculateFee API: supports transfer_type=child_separation with employment fields - create.php: new فصل أبناء section with employment radio, graduation date, work date (conditional), auto-computed date_turned_25, effective date display, live elapsed-years preview via AJAX Co-Authored-By:Claude Sonnet 4.6 <noreply@anthropic.com>
-
Mahmoud Aglan authored
Converts static ALTER TABLE to closure with information_schema checks per column so re-runs don't fail with "Duplicate column name". Co-Authored-By:Claude Opus 4.8 <noreply@anthropic.com>
-
Mahmoud Aglan authored
- VAT % now reads default from RENTAL_VAT_PCT business rule (seeded at 1%); contract form pre-fills with live rule value instead of hardcoded 1 - Deposit row in contract_show now shows payment reference (receipt number) when deposit_payment_id is set - Bulk invoice generation: POST /contracts/{id}/invoices/bulk-generate generates all monthly invoices from start to end date, skipping existing; button added to page_actions and invoices table header with JS confirm Co-Authored-By:Claude Opus 4.8 <noreply@anthropic.com>
-
Mahmoud Aglan authored
- Contracts now store vat_percentage (1%), utilities_percentage, late_fee_type (none/daily/weekly/monthly), late_fee_rate, and grand_total - New rental_invoices table with per-invoice breakdown: base, utilities, VAT, late_fee, total; late fee is calculated at payment time based on days overdue - RentalInvoiceService handles generation, late-fee calc, and mark-paid - Accounting auto-posts on rental.invoice_paid: Dr. Cash, Cr. RentalRevenue (410521) + ServiceRevenue (410515) + TaxPayable (230804) + FineRevenue (410512) - contract_form has live preview calculator for monthly invoice totals - contract_show shows full financial breakdown and invoices table - Migrations: Phase_95_001 (alter contracts), Phase_95_002 (create invoices) Co-Authored-By:Claude Sonnet 4.6 <noreply@anthropic.com>
-
Mahmoud Aglan authored
Root cause: after WaiverProcessor::execute() completed, visiting /members/{id} triggered MembershipPaymentGuard::reconcile(), which did not recognise waiver_fee as a valid activation payment. It stripped the membership_number and then called deactivateAllDependents(), which crashed on spouses.join_date NOT NULL constraint. Fixes: - MembershipPaymentGuard::reconcile(): add waiver_fee path — looks up completed waiver_requests where target_member_id matches, preventing false deactivation - MembershipPaymentGuard::deactivateAllDependents(): spouses.join_date is NOT NULL; use sentinel date '1970-01-01' instead of NULL to avoid constraint violation - MembershipPaymentGuard::deactivateDependent(): same sentinel fix for spouses - WaiverProcessor::execute(): set activated_by_payment_id + activated_at on the target member so the existing fallback check in reconcile() catches future cases Also restored member #136 directly in DB (membership_number='1600', status='active', activated_by_payment_id=773) which was the live victim of this bug. Co-Authored-By:Claude Sonnet 4.6 <noreply@anthropic.com>
-
Mahmoud Aglan authored
- New status flow: board_review → board_approved → pending_form_fill → completed - Mandatory document uploads at case creation (death certificate + inheritance notice) - Board approval step: configurable trustee fee (% of membership_value or flat amount) - Payment request created only after board approval (not at case creation) - Cashier bootstrap fixed: death_fee for primary_member now sets pending_form_fill - Pre-completion validations: board approval, payment, both docs, wife form filled - Children transfer bug fixed: sweep remaining children to primary + renumber child_order - Source tracking: transferred_from_death_id on new member rows - Death-origin badge in member show page - Migration Phase_94_001: new columns on death_cases + members Co-Authored-By:Claude Sonnet 4.6 <noreply@anthropic.com>
-
Mahmoud Aglan authored
- AccountingIntegrationService: read payment_type key (dispatched) not type key (wrong fallback) that was silently routing every payment journal to the default catch-all account - InstallmentController: fire installment.plan_created (correct) not installment_plan.created so AR journal entry is created when a plan is made from the installments UI - AccountCodes: remap all constants to accounts that actually exist in chart_of_accounts DB - form_fee → 410103 (استمارات عضويات), membership_fee → 410101 (عضويات جديدة) - addition_fee → 410102 (إضافة عضويات) - installment → 410510 (الاقساط), down_payment → 410503 (مقدم عضويه) - waiver_fee/death_fee → 410515 (إيرادات متنوعه) - sports_registration/sa_form_fee → 410516 (استمارات نشاط) - fine → 410512 (غرامة تاخير), SERVICE_REVENUE → 410515 (exists, was 4110 which did not exist) - DB: corrected treasury 3 account_code from 12060103 (EUR label) to 12060101 (EGP) which was causing form_fee journals to debit the wrong sub-treasury cash account Co-Authored-By:Claude Sonnet 4.6 <noreply@anthropic.com>
-
Mahmoud Aglan authored
Co-Authored-By:Claude Sonnet 4.6 <noreply@anthropic.com>
-
Mahmoud Aglan authored
- InstallmentCalculator: calculateEarlySettlement() sums all pending principal and interest separately; settlement_amount = principal only, all interest waived - InstallmentController: earlySettlement() (confirmation page), processEarlySettlement() (executes: single PaymentService call, zeros interest on each settled row, marks plan completed + is_cash_settled=1, dispatches installment.early_settled), settlementReceipt() (print view) - Routes: GET/POST /installments/{id}/early-settlement, GET /installments/{id}/settlement-receipt/{receiptId} - show.php:⚡ تسوية مبكرة button (requires installment.pay permission, only when active + pending > 0) - early_settlement.php: breakdown table (original due / interest waived / principal to pay), pending items preview, mandatory confirmation checkbox, submit disabled until checked - settlement_receipt.php: print-ready receipt showing original balance, interest waived, amount paid, settled items list, amount in words, stamp/signature area - PaymentService: early_settlement payment type label - Architecture Map: section 5.6, new route rows, new event row Co-Authored-By:Claude Sonnet 4.6 <noreply@anthropic.com>
-
Mahmoud Aglan authored
- ChequeService: add nextChequeNumber() — sequential from last numeric cheque in DB - ChequeController: index() passes planTotal, uploadedTotal, nextChequeNum; storeBatch() validates total coverage + dedup + activates member; store() validates total coverage on final cheque - Routes: add POST /installments/{planId}/cheques/batch - cheques.php: full rewrite — KPI row, auto-generator panel (JS generates N editable rows from amount+bank+start-date), live total validation bar, submit disabled until total covered, editable-row table with per-row delete, existing cheques table with coverage status, single-upload form retained Co-Authored-By:Claude Sonnet 4.6 <noreply@anthropic.com>
-
Mahmoud Aglan authored
Co-Authored-By:Claude Sonnet 4.6 <noreply@anthropic.com>
-
Mahmoud Aglan authored
- Add syncForMember(memberId) to SubscriptionSyncService: creates the member's own FY subscription row + syncs all currently-active dependents in one call. Uses same rate/discount/dedup logic as the batch generator. - Wire member.activated EventBus listener in Subscriptions bootstrap so any newly-activated member (and their dependents) gets a subscription row immediately, without waiting for the next annual batch generation. - Existing syncForDependent is unchanged; syncForMember delegates to it. Co-Authored-By:Claude Sonnet 4.6 <noreply@anthropic.com>
-
Mahmoud Aglan authored
- Installment panel in member show page now uses flat simple interest: remaining × (rate/100) × (months/12) instead of hardcoded 22% × 30mo - Live breakdown table updates in real-time as user changes down payment or months: shows سعر العضوية, المقدم, المبلغ المتبقي, الفائدة, الإجمالي مع الفائدة, القسط الشهري - Pass installInterestRate and installMaxMonths from RuleEngine to show view - Fix pay-membership action to respect RuleEngine max months (not hardcoded 30) Co-Authored-By:Claude Sonnet 4.6 <noreply@anthropic.com>
-
Mahmoud Aglan authored
The last pending row was absorbing full rounding relative to (pendingCount-1), but the plan may have 30 paid rows already. Now computes the pending interest pool (total - paid interest), distributes flat per row, and absorbs only the pending rounding on the final pending row. Co-Authored-By:Claude Sonnet 4.6 <noreply@anthropic.com>
-
Mahmoud Aglan authored
- Complete rewrite of show.php: 6 KPI cards (original amount, down payment, remaining balance, interest, monthly payment, member's outstanding balance), progress bar showing paid vs total installments, financial breakdown panels (paid vs remaining split), grand total formula ribbon, overdue alerts, next-due-date card with countdown, enhanced schedule table with status highlighting and per-row pay forms - Add recalculate() action to InstallmentController: corrects pending-only installment rows to flat simple-interest formula without touching paid rows - Register POST /installments/{id}/recalculate route Co-Authored-By:Claude Sonnet 4.6 <noreply@anthropic.com>
-
Mahmoud Aglan authored
SubscriptionSyncService::syncForDependent() inserts a pending subscription row for the current financial year whenever a spouse, child, or temporary member becomes active. Triggered via two paths in Subscriptions bootstrap: - Cashier path: spouse.fee_paid / child.fee_paid / temporary.fee_paid - Zero-fee path: *.added events where fee = 0 (immediate activation) Guards: member must be active + non-exempt type; dedup prevents duplicates. Rates resolved identically to SubscriptionGenerator (year-specific catalog code → generic code → hard fallback). Never throws — safe in event context. Co-Authored-By:Claude Sonnet 4.6 <noreply@anthropic.com>
-
Mahmoud Aglan authored
All calculation sites now use: interest = remaining × (rate/100) × (months/12) instead of diminishing-balance amortization. Changes across all 6 sites: - InstallmentCalculator: flat interest, equal monthly instalments - PricingEngine: same formula - RetroactiveMembershipService: same formula - retroactive-wizard JS: updated preview + shows المبلغ المتبقي in summary - Members/show.php: preview panel now includes months factor (was missing) - Installments/create.php: added live المبلغ المتبقي preview panel Co-Authored-By:Claude Sonnet 4.6 <noreply@anthropic.com>
-
Mahmoud Aglan authored
Native HTML5 constraint validation fires on hidden fields (display:none panels), causing "not focusable" errors on inst_months. Custom validateStep() handles all validation — novalidate disables the duplicate browser check. Co-Authored-By:Claude Sonnet 4.6 <noreply@anthropic.com>
-
- 09 Jul, 2026 1 commit
-
-
Mahmoud Aglan authored
- Show loading spinner on submit button while saving - Display error in a prominent red banner at top of wizard on failure - Error message is now shown directly (not buried in generic alerts) Co-Authored-By:Claude Opus 4.6 <noreply@anthropic.com>
-
- 07 Jul, 2026 1 commit
-
-
Mahmoud Aglan authored
Co-Authored-By:Claude Opus 4.6 <noreply@anthropic.com>
-
- 06 Jul, 2026 1 commit
-
-
Mahmoud Aglan authored
When enabled, all active members and dependents get subscriptions from 2023/2024 (system start) regardless of their actual join date. Toggle via business_rules table — set enabled:false to revert to join-date logic. Co-Authored-By:Claude Opus 4.6 <noreply@anthropic.com>
-
- 04 Jul, 2026 6 commits
-
-
Mahmoud Aglan authored
The column is 'notes', not 'description'. Co-Authored-By:Claude Opus 4.6 <noreply@anthropic.com>
-
Mahmoud Aglan authored
1. Added phase to SubscriptionDataMigration that removes unpaid subscriptions for dependents who are archived or no longer active (e.g., temporary members who became inactive, frozen children). 2. Fixed WaiverProcessor referencing non-existent 'reason' column in fines table — correct column is 'notes'. Co-Authored-By:Claude Opus 4.6 <noreply@anthropic.com>
-
Mahmoud Aglan authored
Co-Authored-By:Claude Opus 4.6 <noreply@anthropic.com>
-
Mahmoud Aglan authored
The fallback `$sub['person_id'] ?? $memberId` silently assigned member's own ID to spouse/child/temporary subscription rows when person_index didn't map. Now resolves by name from the dependent table, or skips the row entirely if no match is found. Co-Authored-By:Claude Opus 4.6 <noreply@anthropic.com>
-
Mahmoud Aglan authored
Co-Authored-By:Claude Opus 4.6 <noreply@anthropic.com>
-
Mahmoud Aglan authored
- Remove unpaid subscriptions before 2023/2024 (system didn't exist) - Apply 50% discount to all 2023/2024 subscriptions - Fix amounts/discounts for all years based on historical rate table - Reset and recalculate all fines from scratch - Delete orphaned subscription rows with no matching dependent Co-Authored-By:Claude Opus 4.6 <noreply@anthropic.com>
-
- 03 Jul, 2026 9 commits
-
-
Mahmoud Aglan authored
Documents the SubscriptionDataMigration service: phases, rate table, production results, and root cause of orphaned person_ids. Co-Authored-By:Claude Opus 4.6 <noreply@anthropic.com>
-
Mahmoud Aglan authored
When matching orphaned person_ids, also search archived/inactive records since subscriptions may reference people who were active at creation time. Validate method also checks ALL records, not just active ones. Co-Authored-By:Claude Opus 4.6 <noreply@anthropic.com>
-
Mahmoud Aglan authored
Adds SubscriptionDataMigration service that: - Fixes orphaned person_id references (matched wrong ID to dependent) - Removes exact duplicate subscription rows - Corrects zero/wrong amounts using historical rate table - Creates missing subscriptions for all members from join date to present - Applies overdue fines proportionally per SubscriptionCalculator rules CLI commands: php cli.php subscriptions:migrate Run the migration php cli.php subscriptions:migrate --dry-run Preview changes php cli.php subscriptions:validate Check data integrity Co-Authored-By:Claude Opus 4.6 <noreply@anthropic.com>
-
Mahmoud Aglan authored
Documents the PHP truthiness bug (0.00 ?: fallback), the fix applied, production data corrections, and new Docker cron infrastructure. Co-Authored-By:Claude Opus 4.6 <noreply@anthropic.com>
-
Mahmoud Aglan authored
Docker cached the apt-get layer from before cron was added. ARG before RUN forces layer rebuild. Co-Authored-By:Claude Opus 4.6 <noreply@anthropic.com>
-
Mahmoud Aglan authored
Previous deploy used cached layers without cron installed. Co-Authored-By:Claude Opus 4.6 <noreply@anthropic.com>
-
Mahmoud Aglan authored
PHP's ?: operator treats '0.00' as truthy, preventing fallback to generic service codes. Now returns empty string for missing/zero rows, with hard fallback defaults as last resort. Co-Authored-By:Claude Opus 4.6 <noreply@anthropic.com>
-
Mahmoud Aglan authored
1. Install cron package in Dockerfile and start cron daemon in entrypoint (hourly: php cli.php cron). This was missing — cron jobs never ran. 2. SubscriptionGeneratorJob now delegates to SubscriptionGenerator service which handles exemptions, year-specific rates/discounts, per-person dedup. 3. Financial year 2026/2027 subscriptions will generate on next cron run (July 1-7 window still active). Co-Authored-By:Claude Opus 4.6 <noreply@anthropic.com>
-
Mahmoud Aglan authored
1. Debt check now covers ALL financial types: subscriptions, fines, installments, payment requests, unpaid sales, and seasonal memberships — for member + all dependents. 2. Board approval screen shows ALL target dependents (not just excess) with full details (name, age, DOB, category, within-allowance flag). Board decides per-person fees independently. 3. Receipt/payment request includes per-individual itemized breakdown with calculation method, person type, age category — stored as structured JSON for audit trail. Co-Authored-By:Claude Opus 4.6 <noreply@anthropic.com>
-
- 01 Jul, 2026 1 commit
-
-
Mahmoud Aglan authored
- Added GET /api/members/{id}/debts endpoint returning comprehensive debt check as JSON (clear, debts array, total) - Create page now fetches and displays target member debts live after selection via AJAX — shows detailed debt table or green "clear" badge - Form submission blocked if target has outstanding debts, with error message and direct link to payment page - Made waiver request document upload required (HTML validation) Co-Authored-By:Claude Opus 4.6 <noreply@anthropic.com>
-