# ParentPortal Module — Architecture Map

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

---

## 1. Purpose & Responsibilities

The ParentPortal module manages **parent/guardian accounts** and their relationship to players in the sports academy system. It provides:
- Parent account registration and management
- Parent-to-player linking (many-to-many with granular permissions)
- Player dashboard view for parents (attendance, evaluations, medical, financials, achievements)
- Notification system for parents (attendance, injuries, evaluations, achievements)
- Permission-gated data visibility per link (evaluations, attendance, medical, financials)

It does **NOT** directly manage:
- Players (PlayerAffairs/SportsActivity modules)
- Training sessions or attendance (SportsActivity module)
- Evaluations (Evaluations module)
- Achievements (Achievements module)
- SMS delivery (Notifications module)

---

## 2. Directory & File Structure

```
app/Modules/ParentPortal/
├── bootstrap.php                          # Menu + permission + event listener registration
├── Routes.php                             # Web routes (7 routes)
├── Controllers/
│   └── ParentController.php              # Main controller (CRUD, link/unlink, dashboard)
├── Models/
│   └── ParentAccount.php                 # Model with search, phone lookup, player queries
├── Services/
│   └── ParentService.php                 # Create parent, link/unlink players, notify parents, player dashboard
└── Views/
    ├── index.php                         # Paginated parent list with search
    ├── create.php                        # New parent form
    ├── show.php                          # Parent profile (linked players, notifications log)
    └── player_dashboard.php             # Player view for parent (attendance, evals, medical, etc.)
```

---

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

### 3.1 `parent_accounts` Table

| Column | Type | Nullable | Key | Default | Notes |
|--------|------|----------|-----|---------|-------|
| id | bigint unsigned | NO | PRI | auto_increment | |
| member_id | bigint unsigned | YES | MUL | NULL | FK to members (optional link) |
| full_name_ar | varchar(200) | NO | | | Arabic full name |
| full_name_en | varchar(200) | YES | | NULL | English full name |
| phone | varchar(30) | NO | MUL | | Primary contact |
| secondary_phone | varchar(30) | YES | | NULL | |
| email | varchar(200) | YES | | NULL | |
| national_id | varchar(20) | YES | | NULL | |
| relationship | varchar(20) | NO | | 'parent' | father/mother/guardian/parent |
| pin_hash | varchar(255) | YES | | NULL | For future mobile app auth |
| is_active | tinyint(1) | NO | MUL | 1 | Soft-active flag |
| last_login_at | datetime | YES | | NULL | |
| notification_preferences_json | json | YES | | NULL | Notification channel preferences |
| created_at | timestamp | NO | | CURRENT_TIMESTAMP | |
| updated_at | timestamp | NO | | CURRENT_TIMESTAMP | On update |

### 3.2 `parent_player_links` Table

| Column | Type | Nullable | Key | Default | Notes |
|--------|------|----------|-----|---------|-------|
| id | bigint unsigned | NO | PRI | auto_increment | |
| parent_id | bigint unsigned | NO | MUL | | FK to parent_accounts |
| player_id | bigint unsigned | NO | MUL | | FK to players |
| relationship | varchar(30) | NO | | 'parent' | |
| is_primary_contact | tinyint(1) | NO | | 0 | Primary parent for this player |
| can_view_evaluations | tinyint(1) | NO | | 1 | Permission flag |
| can_view_attendance | tinyint(1) | NO | | 1 | Permission flag |
| can_view_medical | tinyint(1) | NO | | 1 | Permission flag |
| can_view_financials | tinyint(1) | NO | | 1 | Permission flag |
| can_approve_group_change | tinyint(1) | NO | | 0 | Permission flag |
| created_at | timestamp | NO | | CURRENT_TIMESTAMP | |

### 3.3 `parent_notifications_log` Table

| Column | Type | Nullable | Key | Default | Notes |
|--------|------|----------|-----|---------|-------|
| id | bigint unsigned | NO | PRI | auto_increment | |
| parent_id | bigint unsigned | NO | MUL | | FK to parent_accounts |
| player_id | bigint unsigned | YES | MUL | NULL | FK to players |
| notification_type | varchar(50) | NO | MUL | | attendance/injury/evaluation/achievement |
| title_ar | varchar(300) | NO | | | Arabic title |
| body_ar | text | NO | | | Arabic body |
| is_read | tinyint(1) | NO | MUL | 0 | Read status |
| read_at | datetime | YES | | NULL | |
| channel | varchar(20) | NO | | 'sms' | Delivery channel |
| sent_at | datetime | YES | | NULL | |
| created_at | timestamp | NO | | CURRENT_TIMESTAMP | |

### 3.4 Related Tables (owned by other modules, queried by ParentPortal)

| Table | Owner Module | Relationship |
|-------|--------------|--------------|
| players | PlayerAffairs | player_id FK in links |
| members | Members | member_id FK in parent_accounts |
| session_attendance | SportsActivity | Queried via player_id |
| training_sessions | SportsActivity | Joined with attendance |
| player_evaluations | Evaluations | Queried via player_id |
| player_medical_records | Medical | Queried via player_id |
| player_injuries | Medical | Queried via player_id |
| activity_subscriptions | SportsActivity | Queried via player_id |
| player_achievements | Achievements | Queried via player_id |
| achievement_definitions | Achievements | Joined with achievements |
| group_memberships | SportsActivity | Queried via player_id |
| training_groups | SportsActivity | Joined with memberships |

---

## 4. Routes

| Method | Path | Handler | Middleware | Permission | Purpose |
|--------|------|---------|-----------|------------|---------|
| GET | /parents | ParentController@index | auth | parent.view | List parents |
| GET | /parents/create | ParentController@create | auth | parent.manage | Create form |
| POST | /parents | ParentController@store | auth, csrf | parent.manage | Store new parent |
| GET | /parents/{id} | ParentController@show | auth | parent.view | Parent profile |
| POST | /parents/{id}/link-player | ParentController@linkPlayer | auth, csrf | parent.manage | Link player |
| POST | /parents/{id}/unlink/{playerId} | ParentController@unlinkPlayer | auth, csrf | parent.manage | Unlink player |
| GET | /parents/{id}/player/{playerId} | ParentController@playerDashboard | auth | parent.view | Player dashboard |

---

## 5. Events Consumed (listened to in bootstrap.php)

| Event | Source Module | Action |
|-------|-------------|--------|
| `session_attendance.recorded` | SportsActivity | Notify parents of player attendance |
| `player.injury_reported` | Medical | Notify parents of player injury |
| `player.evaluation_submitted` | Evaluations | Notify parents of new evaluation |
| `player.achievement_earned` | Achievements | Notify parents of new achievement |

All listeners use `ParentService::notifyParents()` which:
1. Finds all active parent accounts linked to the player
2. Inserts a record into `parent_notifications_log` for each parent

---

## 6. Events Dispatched

| Event | Payload | Dispatched From |
|-------|---------|-----------------|
| `parent.created` | `{parent_id, name, phone}` | ParentService::createParent |
| `parent.player_linked` | `{parent_id, player_id}` | ParentService::linkPlayer |

---

## 7. Cross-Module Dependencies

### 7.1 ParentPortal IMPORTS FROM:

| Module | What it uses |
|--------|-------------|
| (none) | No direct PHP `use` imports from other modules |

**Note:** All cross-module data is accessed via raw SQL queries against shared tables (players, session_attendance, player_evaluations, etc.)

### 7.2 Other modules that IMPORT FROM ParentPortal:

| Module | What it imports |
|--------|----------------|
| Notifications | Queries `parent_accounts` and `parent_player_links` tables via SQL in SmsNotificationService |

---

## 8. Permissions

| Key | Description (AR) | Description (EN) |
|-----|------------------|------------------|
| parent.view | عرض أولياء الأمور | View Parents |
| parent.manage | إدارة أولياء الأمور | Manage Parents |

---

## 9. Sidebar Menu

| Label AR | Route | Permission | Order |
|----------|-------|------------|-------|
| أولياء الأمور | /parents | parent.view | 280 |
| قائمة أولياء الأمور | /parents | parent.view | 1 |
| إضافة ولي أمر | /parents/create | parent.manage | 2 |

---

## 10. Core Business Flows

### 10.1 Parent Registration

```
1. GET /parents/create — show form with relationship types
2. POST /parents — validate (name, phone, relationship required)
   - Create parent_accounts record
   - Optionally link to a player if player_id provided
   - Dispatch: parent.created
   - Redirect to /parents with success message
```

### 10.2 Link Player to Parent

```
1. POST /parents/{id}/link-player
   - Check if link already exists (idempotent)
   - If is_primary, reset other primary contacts for that player
   - Insert parent_player_links with default permissions (all viewing=1, approve=0)
   - Dispatch: parent.player_linked
```

### 10.3 Player Dashboard (Parent View)

```
1. GET /parents/{id}/player/{playerId}
   - Verify link exists in parent_player_links
   - Based on link permissions:
     - can_view_attendance → load recent 20 attendance records
     - can_view_evaluations → load recent 10 parent-visible evaluations
     - can_view_medical → load medical records + active injuries
     - can_view_financials → load recent 6 subscription months
   - Always load: achievements (10 recent), active groups
```

### 10.4 Parent Notification Flow

```
1. Event fires (e.g., session_attendance.recorded)
2. bootstrap.php listener extracts player_id
3. ParentService::notifyParents() called
4. Finds all active parents linked to player
5. Inserts notification log entry per parent (channel='sms')
```

---

## 11. High-Risk Areas

### 11.1 Notification Storm Potential
- Every attendance record, injury, evaluation, and achievement triggers notifications to ALL linked parents
- No rate limiting or batching — high-volume attendance days could create many log entries
- Currently only logs to DB (`parent_notifications_log`) — actual SMS delivery is handled by Notifications module

### 11.2 Permission-Gated Data Access
- `parent_player_links.can_view_*` flags gate dashboard sections
- If link permissions are wrong, parent sees data they shouldn't (or misses data they should)
- No audit trail for permission changes on links

### 11.3 Player Dashboard Raw SQL
- Dashboard queries multiple tables directly (no service abstraction from owning modules)
- If other modules rename columns or tables, ParentPortal breaks silently
- `player_evaluations.parent_visible` flag must be maintained by Evaluations module

### 11.4 No Authentication for Parents (Web Only)
- `pin_hash` column exists but is unused — no parent self-service portal yet
- Current access is staff-only via admin panel (auth middleware = employee login)
- Future mobile app would need parent authentication

---

## 12. Known Patterns & Gotchas

1. **Relationship types** are hardcoded in model: father, mother, guardian, parent
2. **is_primary_contact** — only one parent per player should be primary; enforced on link creation but not validated retroactively
3. **Notifications are write-only** — `notifyParents()` only inserts to `parent_notifications_log`; actual delivery depends on Notifications module reading this table
4. **member_id is optional** — parent account may or may not be linked to a club member
5. **No soft delete** — `ParentAccount` has `$softDelete = false`; deactivation is via `is_active = 0`
6. **Search is phone-first** — `findByPhone()` is the primary lookup method for parent accounts
