# Members Module — Architecture Map

## Module Purpose

Core module of the club ERP. Manages the complete membership lifecycle from application to termination, including:
- Member registration and form submission
- Membership activation via payment
- Dependent management (spouses, children, temporary members)
- Status transitions (active, frozen, suspended, dropped, expired, terminated)
- Membership transfers (waiver, death, divorce, child separation)
- Financial obligations (form fee, membership value, annual subscriptions, fines)
- Carnet eligibility and access control
- Retroactive data entry for legacy members

## System Responsibilities

1. **Member Registration** — Create potential members with form numbers, validate NID, collect photos
2. **Form Fee Collection** — Gate form-filling behind form fee payment (505 EGP)
3. **Membership Pricing** — Calculate membership value based on branch + qualification (150K/225K/300K EGP)
4. **Payment Orchestration** — Send payment requests to Cashier module, handle activation on completion
5. **Activation Guard** — Single source of truth for member/dependent activation/deactivation
6. **Dependent Lifecycle** — Manage spouses, children, and temporary members with fee calculations
7. **Status Enforcement** — Block operations (carnet printing, subscriptions) based on member status
8. **Age Monitoring** — Auto-separate children at 25, expire temporary members at age limits
9. **Transfers** — Coordinate waiver, death, divorce, and separation transfers
10. **Reporting** — Age reports, subscription status, unpaid debts, children aging out

---

## File Structure

```
app/Modules/Members/
├── bootstrap.php                    — Menu + permissions registration
├── Routes.php                       — Web routes
├── ApiRoutes.php                    — API routes (player portal)
├── Controllers/
│   ├── MemberController.php         — Main CRUD, payments, status changes
│   ├── MemberApiController.php      — Internal API (NID parse, search, debts)
│   ├── Api/MemberApiV1Controller.php — External API v1
│   ├── MemberArchiveController.php  — Archived members viewing
│   ├── ReportController.php         — Reports (age, subscriptions, debts, waivers)
│   └── RetroactiveWizardController.php — Bulk retroactive member entry
├── Models/
│   ├── Member.php                   — Main model (table: members)
│   └── MemberNote.php               — Notes model (table: member_notes)
├── Services/
│   ├── AutoFreezeService.php        — Age-based freezing/separation/expiry
│   ├── BillingService.php           — Calculate total bill (all items + dependents)
│   ├── BoardOfferService.php        — Board discount offers (cash/installment)
│   ├── FormFeeService.php           — Form fee calculations
│   ├── FormNumberGenerator.php      — Form number sequencing
│   ├── MemberNumberGenerator.php    — Membership number + form number assignment
│   ├── MemberSearchService.php      — Unified search across members/dependents
│   ├── MembershipPaymentGuard.php   — SOLE AUTHORITY for activation/deactivation
│   ├── MembershipRulesService.php   — All business rules (fees, eligibility, penalties)
│   ├── MembershipValidationService.php — Validate membership for access (active + subscription paid)
│   ├── NationalIdParser.php         — Parse Egyptian 14-digit NID (DOB, gender, governorate)
│   ├── RetroactiveMembershipService.php — Create members with historical data
│   └── WaiverService.php            — Waiver fee calculation and request creation
└── Views/
    ├── index.php, create.php, show.php, edit.php
    ├── fill-form.php, search.php, changelog.php
    ├── insurance-record.php, retroactive-wizard.php
    ├── archive_index.php, archive_show.php
    ├── _partials/ (profile-header, family-tab, documents-tab, financial-summary, activity-timeline)
    └── reports/ (index, age-report, children-aging, subscription-status, transfers, waivers, unpaid-debts)
```

---

## Database Schema (Primary Tables)

### `members` (main table)
| Column | Type | Notes |
|--------|------|-------|
| id | INT PK | |
| membership_number | VARCHAR UNIQUE | Assigned on activation |
| form_number | VARCHAR | Sequential at registration |
| form_date | DATE | |
| branch_id | INT FK→branches | |
| membership_type | ENUM | working/seasonal/sports/honorary/foreign |
| member_category | VARCHAR | |
| status | VARCHAR | See Status Flow below |
| full_name_ar / full_name_en | VARCHAR | |
| national_id | VARCHAR(14) UNIQUE | Egyptian NID |
| passport_number | VARCHAR | For foreigners |
| date_of_birth | DATE | |
| age_years / age_months | INT | |
| gender | VARCHAR | male/female |
| qualification_id | INT FK→qualifications | Determines pricing |
| membership_value | DECIMAL | Stored for reference |
| special_discount_id | INT FK→special_discounts | |
| discount_amount | DECIMAL | |
| payment_method | VARCHAR | cash/installment/check/visa/transfer |
| activated_by_payment_id | INT FK→payments | Links to activating payment |
| activated_at | DATETIME | |
| workflow_instance_id | INT FK→workflow_instances | |
| transferred_from_waiver_id | INT | |
| transferred_from_death_id | INT | |
| transferred_from_divorce_id | INT | |
| transferred_from_transfer_id | INT | |
| photo_path | VARCHAR | |
| is_archived | TINYINT | Soft delete |
| created_at / updated_at | DATETIME | |
| created_by | INT FK→employees | |

### Related Tables
- `spouses` — FK member_id, spouse_order, addition_fee, status, activated_by_payment_id
- `children` — FK member_id, child_order, classification, addition_fee, is_frozen, frozen_reason
- `temporary_members` — FK member_id, category, addition_fee, relationship_to_member
- `member_notes` — FK member_id, note text, created_by
- `subscriptions` — FK member_id, financial_year, person_type, person_id, status
- `payments` — FK member_id, payment_type, amount, receipt_id
- `payment_requests` — FK member_id, payment_type, status (pending→processing→completed)
- `installment_plans` — FK member_id, total_amount, down_payment, monthly_payment
- `installment_schedule` — FK installment_plan_id, installment_number, status
- `fines` — FK member_id, fine_type, penalty_type, amount, status
- `violations` — FK member_id, description, reported_by
- `waiver_requests` — source_member_id, target_member_id, status
- `death_cases` — FK member_id, transferred_to_member_id
- `transfer_requests` — source_member_id, child_id, target_member_id
- `divorce_cases` — FK member_id/original_member_id
- `board_offers` — is_active, effective_from/to, cash_discount_type/value
- `board_offer_tiers` — FK board_offer_id, tier_order, payment_type (cash/installment), cash_discount_pct, down_payment_pct, months, interest_rate
- `pricing_configs` — branch_id, qualification_id, membership_type, price

---

## Status Flow (Member Lifecycle)

```
potential → under_review → interview_scheduled → accepted → payment_pending → active
                                                          → rejected
                                               → payment_pending → pending_cheques → active
                                                                                   → dropped (installment default)
active → frozen (manual/auto)
active → suspended (violation)
active → dropped (5yr unpaid / installment default / board decision)
active → expired (honorary/seasonal end)
active → terminated (board decision / condition lost)
frozen → active (reinstatement)
dropped → active (within 1 year + board approval + payment)
```

### Activation Rules (MembershipPaymentGuard)
- **ONLY** `MembershipPaymentGuard::activateMember()` can set status = 'active'
- Requires non-voided payment of type: membership_fee, down_payment, foreign_membership_fee, sports_membership_fee, seasonal_fee
- Also recognizes: death_fee (death transfer), waiver_fee (waiver transfer), separation_fee
- On activation: assigns membership_number, sets activated_at + activated_by_payment_id
- On deactivation: clears membership_number, reverts to payment_pending, cascades to all dependents

### Dependent Activation
- Dependents are "included" (activated with parent's membership payment) OR "separate fee" (need own addition_fee)
- `activateIncludedDependents()` — auto-activates dependents without separate payment requests
- `activateDependent()` — activates specific dependent after their own fee is paid

---

## Business Rules (MembershipRulesService)

### Membership Types
| Type | Min Age | Dependents? | Pricing |
|------|---------|-------------|---------|
| working | 21 | Yes | By qualification (150K/225K/300K) |
| seasonal | 0 | Yes | Duration × rate × nationality |
| sports | 0 | Yes | 50% of working price |
| honorary | 0 | Yes | Free (board approval) |
| foreign | 21 | Yes | $10,000 USD |

### Fee Schedule
- **Form fee**: 505 EGP (500 form + 5 martyrs stamp), from ServiceCatalog SVC_NEW_FORM
- **Addition form fee**: 570 EGP + annual subscription (for adding dependents post-creation)
- **1st spouse**: Free (included)
- **2nd spouse**: 10% of membership value + 150/year retro
- **3rd spouse**: 20% + 200/year retro
- **4th+ spouse**: 30% + 300/year retro
- **First 3 children under 18**: Free (included)
- **4th child under 18**: 5% of membership value
- **Child 18**: 10%, **19**: 15%, **20**: 20%, **21+**: 15% (temporary until 25)
- **Temporary members**: 10% of membership value (default), 5% for nanny
- **Separation fee**: Tiered 30%→20%→15%→10%→5%→2.5% based on years since addition

### Annual Subscription Rates (Financial Year July→June)
| Year | Member | Spouse | Child | Temp | Dev Fee |
|------|--------|--------|-------|------|---------|
| 2023/2024 | 410 | 410 | 185 | 185 | 35 |
| 2024/2025 | 410 | 410 | 185 | 185 | 35 |
| 2025/2026+ | 492 | 492 | 222 | 222 | 35 |

### Installment Terms
- Min down payment: 25%
- Max months: 30
- Interest: 22% annual (flat)
- Default: 3+ items overdue by 90+ days → plan defaulted → member dropped

### Penalties & Violations
- Levels: attention → warning → fine (1K-10K) → suspension (≤6mo) → ban (≤6mo) → expulsion
- Appeal deadline: 15 days from penalty
- Fines accumulate for max 5 years → then membership dropped
- Reinstatement: within 1 year of drop, requires full payment + board approval

### Carnet Printing Eligibility
- Member status must be active (not frozen/suspended/dropped)
- Annual subscription paid for current FY
- No overdue installments
- No unpaid fines

---

## Cron Jobs (Automated Processes)

| Job | Schedule | Action |
|-----|----------|--------|
| AgeMonitorJob | Daily | Update children ages; alert at 18/25; auto-separate at 25; remove temps at 25 |
| SubscriptionGeneratorJob | July 1-7 | Generate annual subscriptions for all active members |
| OverdueFineJob | Daily | Mark past-FY subscriptions overdue; apply fines; drop members (5yr rule) |
| InstallmentDefaultJob | Daily | Default plans with 3+ items 90+ days overdue; drop member |
| HonoraryExpiryJob | Daily | Flag honorary memberships past end_date |
| SeasonalExpiryJob | Daily | Expire seasonal memberships past end_date |
| FormExpiryJob | Daily | Expire stale form submissions |
| WaiverAutoCompleteJob | Daily | Auto-complete waivers with fee_paid status |

---

## Event System

### Events Emitted
- `member.created` — after member insert (with retroactive flag if applicable)
- `member.dropped` — after member dropped (installment default / unpaid 5yr)
- `payment.completed` — after retroactive payment creation

### Events Consumed (via bootstrap.php)
- Listens via other module bootstraps (Accounting posts journal entries on payment events)

---

## Key Workflows

### 1. New Member Registration
```
1. Employee creates member (name, NID, phone, branch, type, photo) → status: potential
2. Pay form fee (505 EGP) → payment_request to Cashier
3. Cashier completes form_fee payment
4. Employee fills full form (qualification, address, employment) → status: under_review
5. [Optional] Interview → status: accepted
6. Pay membership value (cash/installment) → payment_request to Cashier
7. Cashier completes membership_fee/down_payment → MembershipPaymentGuard::activateMember()
8. Member assigned membership_number → status: active
9. Included dependents auto-activated
```

### 2. Dependent Addition (Post-Creation)
```
1. Add spouse/child/temp via form on member profile
2. System calculates addition_fee based on rules
3. Pay addition_fee → payment_request to Cashier
4. Cashier completes → MembershipPaymentGuard::activateDependent()
5. Dependent status → active
```

### 3. Child Separation (Age 25 / Voluntary)
```
1. AgeMonitorJob auto-separates male children at 25 OR
2. Manual transfer request created with separation fee calculation
3. Fee = tiered % of current membership value + form fee + annual subscription
4. Payment of separation_fee
5. New member created with transferred_from_transfer_id
6. Original child marked separated/frozen
```

### 4. Waiver Transfer
```
1. Source member requests waiver → WaiverService::processWaiver()
2. Board approval required
3. Fee = 30% of membership value
4. Target pays waiver_fee → Cashier
5. WaiverAutoCompleteJob processes: archives source, creates target with same number
6. Target activated via MembershipPaymentGuard
```

### 5. Death Transfer
```
1. Death case created for member
2. Membership transfers to spouse (same number, form fee + subscription only)
3. Payment of death_fee
4. New member created with transferred_from_death_id
5. Activated by MembershipPaymentGuard::reconcile()
```

### 6. Annual Subscription Cycle
```
1. July 1-7: SubscriptionGeneratorJob creates subscription records for all active members + dependents
2. Members pay via Cashier (subscription + dev fee)
3. Overdue after FY ends without payment
4. 5 consecutive unpaid years → membership dropped
5. Carnet printing blocked if current FY unpaid
```

### 7. Retroactive Entry
```
1. Super admin opens retroactive wizard
2. Enters member + dependents + payments + subscriptions + violations
3. All created in single transaction with historical dates
4. Payments create receipts; subscriptions auto-dedup by FY + person
```

---

## Integration Points

### Upstream Dependencies (consumed by Members)
| Module | Purpose |
|--------|---------|
| Rules (RuleEngine) | All configurable fees, rates, limits |
| Pricing | pricing_configs table → membership_value |
| ServiceCatalog | SVC_NEW_FORM price |
| Branches | Branch selection, branch-specific offers |
| Qualifications | Qualification-based pricing tiers |

### Downstream Dependencies (consumed from Members)
| Module | Purpose |
|--------|---------|
| Subscriptions | Annual subscription generation per member+dependents |
| Payments | Payment records, receipt generation |
| Cashier | PaymentRequestService → payment workflow |
| Installments | Installment plan management |
| Fines | Violation fines, overdue penalties |
| Carnets | Carnet eligibility check via canPrintCarnet() |
| Transfers | Separation/transfer request management |
| Waiver | Waiver processing and auto-completion |
| Death | Death case management and membership transfer |
| Divorce | Divorce-triggered membership transfer |
| Accounting | Journal entries on payment events |
| Notifications | SMS alerts for age milestones, overdue payments |
| Workflow | New membership workflow instance |
| Forms | Form submission tracking |
| Archive | Source data preservation on transfers |
| AccessMatrix | Membership validation for facility access |
| Children | Child records management |
| Spouses | Spouse records management |
| Temporary | Temporary member records |
| Foreign | Foreign member details (USD fee, exchange rate) |
| Honorary | Honorary member details (term dates) |
| Seasonal | Seasonal membership details (duration, nationality pricing) |
| Sports | Sports membership conversion details |

---

## Risk Areas

1. **MembershipPaymentGuard is the ONLY activation authority** — any bypass creates orphan states
2. **reconcile() runs on every show page** — performance concern with many members
3. **BillingService** queries across 6+ tables on every page load — no caching
4. **Installment default** → member drop is irreversible without board intervention
5. **Age monitoring** — if cron misses a day, children past 25 stay active until next run
6. **Financial year boundary** (July 1) — subscription generation only runs July 1-7
7. **Race condition** on membership_number assignment — handled with 3 retries but no locking
8. **Retroactive wizard** creates payments without Cashier flow — can mismatch accounting
9. **spouses.join_date is NOT NULL** — uses '1970-01-01' sentinel on deactivation

---

## Technical Debt

- No test coverage (no test framework configured)
- BillingService has 800 lines with significant duplication across membership types
- MemberController::show() loads 20+ queries per page view
- reconcile() on every page view is a hidden migration — should be event-driven
- Multiple financial year calculation functions duplicated across services
- Board offer logic tightly coupled to payment request creation
