# Children Module — Architecture Map

> **Last updated:** 2026-06-10
> **Status:** Living document — incrementally updated as new information is discovered

---

## 1. Purpose & Responsibilities

The Children module manages **dependent children** attached to working members. It handles:
- Adding children (biological sons/daughters, stepchildren) to a member record
- Fee calculation based on child order, age bracket, and relationship type
- Classification (included free, dependent with fee, temporary, frozen, separated)
- Freeze/separation lifecycle when children reach age 25
- Photo upload and NID validation for children
- Age-gap validation (min 15, max 60 years between parent and child)

It does **NOT** directly manage:
- Fee collection (Cashier/Payments modules process the payment_request)
- Annual subscription generation (Subscriptions module)
- Auto-freeze cron job (Members/AutoFreezeService handles age-25 separation)
- Member billing aggregation (Members/BillingService aggregates child fees)
- Carnets/ID cards (Carnets module)

---

## 2. Directory & File Structure

```
app/Modules/Children/
├── bootstrap.php                          # Permission registration (6 permissions)
├── Routes.php                             # Web routes (8 routes)
├── Controllers/
│   └── ChildController.php               # Main controller (CRUD, freeze, calculateFee API)
├── Models/
│   └── Child.php                         # Main model (soft delete, events, NID dedup)
├── Services/
│   └── ChildFeeCalculator.php            # Fee calculation engine
└── Views/
    ├── create.php                        # Add child form (NID, DOB, photo, relationship)
    ├── edit.php                          # Edit child (limited fields)
    ├── show.php                          # Child profile view
    └── _partials/
        └── children-table.php            # Embedded table in member profile
```

---

## 3. Database Schema (Production — Source of Truth)

### 3.1 `children` Table (105 rows as of 2026-06-10)

| Column | Type | Nullable | Key | Default | Notes |
|--------|------|----------|-----|---------|-------|
| id | bigint unsigned | NO | PRI | auto_increment | |
| member_id | bigint unsigned | NO | MUL | | FK to members |
| child_order | int unsigned | NO | | 1 | Sequential per member |
| full_name_ar | varchar(200) | NO | | | Arabic name (required) |
| full_name_en | varchar(200) | YES | | | English name (optional) |
| national_id | varchar(14) | YES | MUL | | Egyptian NID (required if age >= 16) |
| birth_certificate_number | varchar(50) | YES | | | For young children |
| date_of_birth | date | NO | MUL | | Required |
| age_years | int unsigned | YES | | | Stored at creation (STATIC) |
| age_months | int unsigned | YES | | | Stored at creation (STATIC) |
| gender | varchar(10) | NO | | | 'male' or 'female' |
| relationship | varchar(30) | NO | | son | 'son', 'daughter', 'stepchild' |
| school_faculty | varchar(200) | YES | | | Education info |
| nationality | varchar(100) | YES | | مصري | Default Egyptian |
| classification | varchar(50) | NO | MUL | included | Fee classification |
| addition_fee | decimal(15,2) | NO | | 0.00 | Calculated total fee |
| fee_breakdown_json | text | YES | | | JSON breakdown for display |
| fee_receipt_number | varchar(50) | YES | | | Receipt after payment |
| status | varchar(50) | NO | MUL | active | Lifecycle status |
| activated_by_payment_id | bigint unsigned | YES | | | FK to payments |
| join_date | date | YES | | | When formally joined |
| is_frozen | tinyint(1) | NO | MUL | 0 | Freeze flag |
| frozen_at | timestamp | YES | | | When frozen |
| frozen_reason | varchar(200) | YES | | | Reason for freeze |
| photo_path | varchar(500) | YES | | | Uploaded photo |
| remarks | text | YES | | | Free-text notes |
| is_archived | tinyint(1) | NO | MUL | 0 | Soft delete |
| archived_at | timestamp | YES | | | |
| archived_by | bigint unsigned | YES | | | FK to employees |
| created_at | timestamp | NO | | CURRENT_TIMESTAMP | |
| updated_at | timestamp | NO | | CURRENT_TIMESTAMP | On update |
| created_by | bigint unsigned | YES | | | FK to employees |
| updated_by | bigint unsigned | YES | | | FK to employees |

**Current data distribution:**
- active / included: 94
- active / regular: 2
- separated / separated: 3
- pending_payment / dependent_with_fee: 2
- pending_payment / regular: 2
- pending_payment / temporary: 1
- pending_payment / included: 1

### 3.2 Classification Values

| Classification | Meaning |
|---------------|---------|
| included | Free — covered by membership value |
| dependent_with_fee | Has a percentage-based fee |
| temporary | Age 21-24, temporary status |
| frozen | Manually or auto-frozen |
| separated | Aged out at 25, no longer a dependent |

### 3.3 Status Values

| Status | Meaning |
|--------|---------|
| active | Currently active dependent |
| inactive | Archived/removed |
| frozen | Frozen (can be manual or auto at age 25) |
| separated | Permanently separated at age 25 |
| pending_payment | Awaiting fee payment before activation |

---

## 4. Routes

| Method | Path | Handler | Middleware | Permission |
|--------|------|---------|------------|------------|
| GET | /members/{memberId}/children/create | ChildController@create | auth | child.add |
| POST | /members/{memberId}/children | ChildController@store | auth, csrf | child.add |
| GET | /members/{memberId}/children/{id} | ChildController@show | auth | child.view |
| GET | /members/{memberId}/children/{id}/edit | ChildController@edit | auth | child.edit |
| POST | /members/{memberId}/children/{id} | ChildController@update | auth, csrf | child.edit |
| POST | /members/{memberId}/children/{id}/archive | ChildController@archive | auth, csrf | child.remove |
| POST | /members/{memberId}/children/{id}/freeze | ChildController@freeze | auth, csrf | child.freeze |
| POST | /api/children/calculate-fee | ChildController@calculateFee | auth | child.add |

---

## 5. Core Business Flows

### 5.1 Add Child Flow

```
1. GET /members/{memberId}/children/create
   - Validate member exists, not archived
   - Check lock: if membership_number assigned && not super_admin → blocked
   - Show form with child_order pre-calculated

2. POST /members/{memberId}/children (store)
   - Validate: name, DOB, gender, relationship, photo (all required)
   - Parse NID if provided (auto-fills DOB, gender, age)
   - NID dedup check (members, spouses, children tables)
   - Age gap validation (15-60 years between member and child)
   - NID required if child age >= CHILD_NID_REQUIRED_AGE (default 16)
   - Max age check: CHILD_MAX_AGE (default 25)
   - Stepchild max age: STEPCHILD_MAX_AGE (default 25)
   - Calculate fee via ChildFeeCalculator
   - If classification = 'not_accepted' → reject
   - Create child record
   - Upload photo
   - Submit ADDITION_CHILD form via FormBridge (if exists)
   - Dispatch: child.added event
   - If fee > 0 AND member is activated → create payment_request via PaymentRequestService
   - If fee > 0 AND member NOT activated → fee included in aggregate bill
   - If fee = 0 → child is active immediately
```

### 5.2 Fee Calculation Logic (ChildFeeCalculator)

```
1. Get member's current membership value from pricing_configs
2. Determine child order (count of active children + 1)
3. If relationship = 'stepchild':
   → Apply STEPCHILD_FEE rule (always 10%, never free)
4. Else:
   → Delegate to PricingEngine::calculateChildFee(membershipValue, age, order)
   → First N children under 18 are free (INITIAL_FREE_CHILDREN_COUNT)
   → Age-bracketed percentages for older children
   → Age 25+ = 'not_accepted'
5. Add form fee (FormFeeService) if not on initial form
6. Add annual subscription if post-activation addition
7. Return: fee, classification, breakdown
```

### 5.3 Freeze Flow

```
POST /members/{memberId}/children/{id}/freeze
- Sets: is_frozen=1, frozen_at, frozen_reason, classification='frozen', status='frozen'
- Dispatches: child.frozen event
```

### 5.4 Archive (Remove) Flow

```
POST /members/{memberId}/children/{id}/archive
- Requires reason text
- Check lock (membership_number + super_admin)
- Sets: is_archived=1, archived_at, archived_by, status='inactive'
- Dispatches: child.removed event
```

---

## 6. Events Dispatched

| Event | Payload | Dispatched From |
|-------|---------|-----------------|
| `child.added` | `{member_id, child_id, classification, fee}` | ChildController::store |
| `child.removed` | `{member_id, child_id, reason}` | ChildController::archive |
| `child.frozen` | `{member_id, child_id, reason}` | ChildController::freeze |

---

## 7. Events Consumed

| Event | Source Module | Action |
|-------|--------------|--------|
| `child.fee_paid` | Cashier | Activates child (sets status='active', activated_by_payment_id) |

---

## 8. Cross-Module Dependencies

### 8.1 Children module IMPORTS FROM:

| Module | What it uses |
|--------|-------------|
| Members | NationalIdParser (NID parsing), FormFeeService (form fee + annual subscription) |
| Rules | RuleEngine (CHILD_NID_REQUIRED_AGE, CHILD_MIN_AGE_GAP, CHILD_MAX_AGE_GAP, STEPCHILD_MAX_AGE, CHILD_MAX_AGE, STEPCHILD_FEE) |
| Pricing | PricingEngine::calculateChildFee (age-bracket fee calculation) |
| Cashier | PaymentRequestService (creates payment requests for treasury) |
| Forms | FormBridge (submits ADDITION_CHILD form) |
| Shared | PhotoUploadService (photo upload/validation) |

### 8.2 Other modules that IMPORT FROM Children:

| Module | What it imports / references |
|--------|------------------------------|
| Members/BillingService | Aggregates child fees into member bill |
| Members/AutoFreezeService | Cron: freezes male children at age 25, reports near-threshold |
| Members/MembershipPaymentGuard | Activates children when member pays lump sum |
| Members/RetroactiveMembershipService | Creates children records during retroactive entry |
| Members/MemberController | Loads children for member profile display |
| Cashier/bootstrap | Listens for addition_fee payments → dispatches child.fee_paid |
| Payments/PaymentLifecycleService | Handles void/cancel of child-related payments |
| Subscriptions/SubscriptionGenerator | Generates annual subscriptions for active children |
| Death/DeathController | Reassigns children to surviving spouse on member death |
| Divorce/DivorceFeeCalculator | Checks children count (affects 5-year waiver rule) |
| Waiver/WaiverController | Counts children for waiver eligibility |
| Archive/ArchiveService | Includes children in member archive snapshot |
| Carnets/CarnetController | Issues carnets for children |

---

## 9. Permissions

| Key | Description (AR) |
|-----|-----------------|
| child.add | اضافة ابناء |
| child.edit | تعديل بيانات ابناء |
| child.remove | ازالة ابناء |
| child.view | عرض بيانات الابناء |
| child.freeze | تجميد عضوية ابن |
| child.separate | فصل ابناء |

**Super Admin gates:**
- Add/edit/remove children after member activation (membership_number assigned)
- No explicit super_admin permission check in bootstrap — enforced via `isLocked()` + `isSuperAdmin()` in controller

---

## 10. Background Processes (Cron)

Handled by `Members/Services/AutoFreezeService` (NOT in Children module):
- **processAutoFreeze()**: Male children aged 25+ with `status='active'` → sets `classification='separated'`, `status='separated'`
- **getChildrenNearingThreshold()**: Reports children within 6 months of age 25

---

## 11. Configuration Dependencies

| Rule Engine Key | Purpose | Default |
|----------------|---------|---------|
| CHILD_NID_REQUIRED_AGE | Min age requiring national ID | 16 |
| CHILD_MIN_AGE_GAP | Min years between parent and child | 15 |
| CHILD_MAX_AGE_GAP | Max years between parent and child | 60 |
| CHILD_MAX_AGE (or CHILD_AUTO_DELETE_AGE) | Max age for adding a child | 25 |
| STEPCHILD_MAX_AGE | Max age for stepchildren | 25 |
| STEPCHILD_FEE | Stepchild fee percentage | 10% |
| INITIAL_FREE_CHILDREN_COUNT | How many children are free | 2-3 |
| CHILD_INCLUDED_MAX_AGE | Max age for free inclusion | 18 |

| Service Catalog | Purpose |
|----------------|---------|
| SVC_ANNUAL_CHILD% | Annual subscription rate for children |
| SVC_ADDITION_FORM | Addition form fee (570 EGP) |

---

## 12. High-Risk Areas

### 12.1 Fee Calculation Complexity
- Multiple fee paths: stepchild (always 10%), age-bracketed (PricingEngine), free slots
- Uses current membership value from pricing_configs (not stored member value)
- If pricing_configs has no matching row, falls back to member's stored value
- bcmath used for all money — but classification logic has multiple branches

### 12.2 Post-Activation Lock
- Once `membership_number` is assigned to the parent member, children cannot be added/edited/removed without super_admin
- If lock is bypassed, fee calculation still runs but payment_request is created immediately
- If lock check has a bug: unauthorized users could modify dependents

### 12.3 NID Deduplication
- Checks members, spouses, AND children tables for duplicate NID
- If a child NID matches an existing record anywhere → blocked
- No check against temporary_members table (potential gap)

### 12.4 Age Storage is STATIC
- `age_years` and `age_months` are stored at creation time and NEVER updated
- AutoFreezeService uses `TIMESTAMPDIFF(YEAR, date_of_birth, CURDATE())` for live age
- Displayed age in views should compute from `date_of_birth`, not stored fields
- Recent commit (110d777) fixed this to compute dynamically from DOB

### 12.5 Payment Request Dual Path
- If member is activated (has membership_number): immediate payment_request to Cashier
- If member is NOT activated: fee is embedded in aggregate bill (BillingService)
- If aggregate bill already paid and a new child is added post-activation → new payment_request
- PaymentRequestService can cancel prior pending requests (used by Members when paying lump sum)

---

## 13. Known Patterns & Gotchas

1. **Stepchildren always pay**: Relationship='stepchild' is never counted toward free child slots — always 10%
2. **Child order is position-based**: `child_order` = count of active non-archived children + 1 at time of addition
3. **Photo required**: Cannot add a child without uploading a photo
4. **FormBridge optional**: If ADDITION_CHILD form definition doesn't exist, form submission is silently skipped
5. **classification vs status**: `classification` is the fee category; `status` is the lifecycle state — both exist independently
6. **Activation via Cashier**: When payment_request is processed, Cashier dispatches `child.fee_paid` which activates the child
7. **Activation via lump sum**: When parent pays membership fee, `MembershipPaymentGuard::activateIncludedDependents()` activates all pending children

---

## 14. Fee Calculation Reference

| Condition | Fee |
|-----------|-----|
| First 2-3 children under 18 | Free (included) |
| 4th+ child under 18 | 5% of membership value |
| Child age 18 | 10% |
| Child age 19 | 15% |
| Child age 20 | 20% |
| Child age 21-24 | 15% (temporary classification) |
| Stepchild (any age) | 10% (STEPCHILD_FEE) |
| Child age 25+ | Not accepted |
| Late addition form fee | 570 EGP (post-activation only) |
| Annual subscription | ~222 EGP (post-activation only) |

---

## Appendix: Files to Check Before Modifying Children

1. `app/Modules/Members/Services/BillingService.php` — Aggregates child fees into member bill
2. `app/Modules/Members/Services/AutoFreezeService.php` — Cron freeze logic for age 25
3. `app/Modules/Members/Services/MembershipPaymentGuard.php` — Activates children on lump-sum payment
4. `app/Modules/Cashier/bootstrap.php` — Payment processing → child.fee_paid event
5. `app/Modules/Payments/Services/PaymentLifecycleService.php` — Void/cancel for child payments
6. `app/Modules/Pricing/Services/PricingEngine.php` — calculateChildFee() logic
7. `app/Modules/Members/Services/FormFeeService.php` — Form fee + annual subscription amounts
8. `app/Modules/Death/Controllers/DeathController.php` — Child reassignment on death
9. `app/Modules/Subscriptions/Services/SubscriptionGenerator.php` — Annual subscriptions for children
