# PoolManagement Module — Architecture Map

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

---

## 1. Purpose & Responsibilities

The PoolManagement module handles **swimming pool configuration, lane-level booking, and occupancy management**. It manages:
- Pool physical configuration (dimensions, lanes, depths)
- Lane-level booking with time slot granularity
- Safety ratio enforcement per booking type
- Visual grid display (lanes x time slots)
- Lane availability checking with conflict detection
- Occupancy optimization (lane suggestion algorithm)
- Pool dashboard with utilization metrics
- Booking lifecycle (confirmed -> checked_in -> completed/cancelled)

It does **NOT** directly manage:
- Facility definitions (Facilities module — pool links via `facility_id`)
- Payments (relies on external `payment_id` reference)
- General facility reservations (Reservations module handles those)
- Pool schedules for training groups (previously handled here, now migrated to FacilityGrids module)

---

## 2. Directory & File Structure

```
app/Modules/PoolManagement/
├── bootstrap.php                          # Permission registration (3 permissions)
├── Routes.php                             # Web routes (11 routes)
├── Controllers/
│   ├── PoolConfigController.php           # Pool configuration CRUD
│   ├── PoolBookingController.php          # Booking CRUD + check-in
│   ├── PoolDashboardController.php        # Per-pool dashboard
│   ├── PoolGridController.php             # Grid visualization
│   └── PoolScheduleController.php         # Schedule management
├── Models/
│   ├── PoolConfiguration.php             # Pool config model (no soft delete)
│   ├── PoolBooking.php                   # Booking model (no soft delete)
│   └── PoolSchedule.php                  # Schedule model
├── Services/
│   ├── PoolGridService.php               # Grid state computation + lane availability
│   └── PoolOccupancyOptimizer.php        # Lane suggestion + utilization metrics
└── Views/
    ├── index.php                         # Pool list
    ├── config_create.php                 # Create pool config form
    ├── config_edit.php                   # Edit pool config form
    ├── grid.php                          # Lane x time slot grid visualization
    ├── book.php                          # Booking creation form
    ├── bookings.php                      # Booking list with filters
    ├── booking_show.php                  # Booking detail
    ├── dashboard.php                     # Pool dashboard with metrics
    └── schedules.php                     # Schedule view
```

---

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

### 3.1 `pool_configurations` Table

| Column | Type | Nullable | Key | Default | Notes |
|--------|------|----------|-----|---------|-------|
| id | bigint unsigned | NO | PRI | auto_increment | |
| facility_id | bigint unsigned | NO | MUL | | FK to facilities (type='pool') |
| name_ar | varchar(255) | NO | | | Arabic name |
| name_en | varchar(255) | YES | | | English name |
| length_meters | decimal(5,2) | NO | | | Pool length |
| width_meters | decimal(5,2) | NO | | | Pool width |
| depth_shallow | decimal(3,2) | YES | | | Shallow end depth |
| depth_deep | decimal(3,2) | YES | | | Deep end depth |
| total_lanes_lengthwise | int unsigned | NO | | 6 | Number of lanes (length direction) |
| total_lanes_widthwise | int unsigned | NO | | 0 | Lanes in width direction |
| lane_widths_json | json | NO | | | Array of lane widths in meters |
| max_total_swimmers | int unsigned | NO | | 50 | Total pool capacity |
| max_per_lane | int unsigned | NO | | 8 | Default max swimmers per lane |
| safety_ratio_json | json | YES | | | Max swimmers per lane by booking type |
| operating_hours_json | json | YES | | | Start, end, slot_minutes |
| is_active | tinyint(1) | NO | | 1 | |
| created_at | timestamp | NO | | CURRENT_TIMESTAMP | |
| updated_at | timestamp | NO | | CURRENT_TIMESTAMP | On update |
| created_by | bigint unsigned | YES | | | |
| updated_by | bigint unsigned | YES | | | |

### 3.2 `pool_lanes` Table

| Column | Type | Nullable | Key | Default | Notes |
|--------|------|----------|-----|---------|-------|
| id | bigint unsigned | NO | PRI | auto_increment | |
| pool_config_id | bigint unsigned | NO | MUL | | FK to pool_configurations |
| lane_number | int unsigned | NO | | | Sequential (1-based) |
| direction | varchar(10) | NO | | lengthwise | lengthwise/widthwise |
| width_meters | decimal(4,2) | NO | | | Individual lane width |
| label_ar | varchar(50) | YES | | | Arabic label (e.g., "حارة 1") |
| label_en | varchar(50) | YES | | | |
| max_swimmers | int unsigned | NO | | 8 | Lane-specific capacity |
| is_active | tinyint(1) | NO | | 1 | |
| sort_order | int unsigned | NO | | 0 | Display order |
| created_at | timestamp | NO | | CURRENT_TIMESTAMP | |

### 3.3 `pool_bookings` Table

| Column | Type | Nullable | Key | Default | Notes |
|--------|------|----------|-----|---------|-------|
| id | bigint unsigned | NO | PRI | auto_increment | |
| booking_code | varchar(50) | NO | UNI | | Format: PB-YYMMDD-XXXXXX (hex) |
| pool_config_id | bigint unsigned | NO | MUL | | FK to pool_configurations |
| booking_date | date | NO | MUL | | |
| start_time | time | NO | | | |
| end_time | time | NO | | | |
| booking_type | varchar(30) | NO | | | lap_swimming/lessons/free_swim/academy_session/competition/maintenance |
| selection_mode | varchar(20) | NO | | | lane/multi_lane/full_pool |
| lanes_json | json | NO | | | Array of selected lane numbers |
| booker_type | varchar(20) | NO | | member | member/non_member/academy/staff |
| booker_member_id | bigint unsigned | YES | | | FK to members |
| carnet_guest_entry_id | bigint unsigned | YES | | | FK to carnet guest entries |
| booker_id | bigint unsigned | YES | | | Generic booker FK |
| booker_name | varchar(300) | YES | | | |
| expected_swimmers | int unsigned | NO | | 1 | |
| actual_swimmers | int unsigned | YES | | | Set on check-in |
| unit_rate | decimal(15,2) | NO | | 0.00 | |
| total_amount | decimal(15,2) | NO | | 0.00 | |
| payment_status | varchar(20) | NO | | pending | pending/paid/waived |
| payment_id | bigint unsigned | YES | | | FK to external payment |
| status | varchar(20) | NO | MUL | confirmed | confirmed/checked_in/completed/cancelled/no_show |
| cancelled_by | bigint unsigned | YES | | | FK to employees |
| cancelled_at | timestamp | YES | | | |
| cancel_reason | varchar(500) | YES | | | |
| notes | text | YES | | | |
| created_at | timestamp | NO | | CURRENT_TIMESTAMP | |
| updated_at | timestamp | NO | | CURRENT_TIMESTAMP | On update |
| created_by | bigint unsigned | YES | | | |
| updated_by | bigint unsigned | YES | | | |

---

## 4. Routes

| Method | Path | Handler | Permission |
|--------|------|---------|------------|
| GET | /pool | PoolConfigController@index | pool.view |
| GET | /pool/config/create | PoolConfigController@create | pool.manage |
| POST | /pool/config | PoolConfigController@store | pool.manage |
| GET | /pool/config/{id}/edit | PoolConfigController@edit | pool.manage |
| POST | /pool/config/{id} | PoolConfigController@update | pool.manage |
| GET | /pool/{id}/book | PoolBookingController@create | pool.book |
| POST | /pool/{id}/book | PoolBookingController@store | pool.book |
| GET | /pool/bookings | PoolBookingController@index | pool.view |
| GET | /pool/bookings/{id} | PoolBookingController@show | pool.view |
| POST | /pool/bookings/{id}/cancel | PoolBookingController@cancel | pool.manage |
| POST | /pool/bookings/{id}/checkin | PoolBookingController@checkin | pool.manage |
| GET | /pool/{id}/dashboard | PoolDashboardController@show | pool.view |

**Note:** Grid and schedule routes have been migrated to the FacilityGrids module (redirect controllers handle backward compat).

---

## 5. Core Business Flows

### 5.1 Pool Configuration

```
1. Select facility (type='pool')
2. Set physical dimensions: length, width, depth range
3. Set lane count (lengthwise lanes)
4. System auto-calculates: lane_widths = width / total_lanes
5. Set safety ratios per booking type (max swimmers per lane)
6. Set operating hours (start, end, slot duration in minutes)
7. On save: creates pool_configurations + individual pool_lanes records
```

**Safety ratios (swimmers per lane by type):**
- lap_swimming: 6
- lessons: 4
- free_swim: 8
- academy_session: 5
- competition: 4
- maintenance: 0

### 5.2 Pool Booking Flow

```
1. Select pool, date
2. Choose booking_type, lanes (via grid visualization)
3. PoolGridService::checkLaneAvailability() verifies no conflicts
4. Generate booking_code: PB-YYMMDD-XXXXXX (random hex)
5. Determine selection_mode: lane / multi_lane / full_pool
6. Create booking with status='confirmed'
7. Redirect to grid view
```

### 5.3 Grid State Computation (PoolGridService)

```
For each lane:
  For each time slot (based on operating_hours):
    Check all non-cancelled bookings for overlap
    Determine cell status: available / booked / in_progress / maintenance
    Attach booking details if occupied
```

### 5.4 Lane Suggestion (PoolOccupancyOptimizer)

```
1. Calculate lanes_needed = ceil(swimmer_count / max_per_lane_for_type)
2. Get all available lanes for the requested time
3. If not enough lanes → return failure
4. Score lane combinations:
   - Adjacent lanes get +20 bonus
   - Lane width suitability per booking type
   - Return top 3 suggestions
```

### 5.5 Utilization Metrics

Calculates for a date range:
- Total bookings count
- Total lane-slot usage (via JSON_LENGTH on lanes_json)
- Total swimmers
- Total revenue
- Occupancy percentage (used slots / total capacity)

---

## 6. Events Dispatched

The PoolManagement module does **NOT** dispatch any events currently.

---

## 7. Events Consumed

The PoolManagement module does **NOT** listen to any external events.

---

## 8. Cross-Module Dependencies

### 8.1 PoolManagement module IMPORTS FROM:

| Module | What it uses |
|--------|-------------|
| Facilities | Queries `facilities` table directly (type='pool', for config dropdowns) |

### 8.2 Other modules that IMPORT FROM PoolManagement:

No other modules directly import from PoolManagement.

**Note:** The FacilityGrids module has taken over grid/schedule routes with redirect controllers pointing back here.

---

## 9. Permissions

| Key | Description |
|-----|-------------|
| pool.view | View pool configurations, bookings, grid, dashboard |
| pool.manage | Create/edit pool configs, cancel bookings, check-in |
| pool.book | Create pool bookings |

---

## 10. High-Risk Areas

### 10.1 Lane Conflict Detection
- `PoolGridService::checkLaneAvailability()` checks for overlapping bookings on the SAME pool + date + time + lanes
- Uses JSON decode of `lanes_json` column to find lane intersection
- If `lanes_json` contains invalid JSON, `json_decode` returns null (treated as empty array)
- No cross-system check against reservations or training sessions

### 10.2 Safety Ratio Enforcement
- Safety ratios (max swimmers per lane) are stored as JSON in `pool_configurations`
- The PoolOccupancyOptimizer uses them to calculate required lanes
- **However**, the booking store action does NOT enforce safety ratios — only lane availability is checked
- A booking could be created with 50 swimmers in 1 lane without error

### 10.3 Booking Code Generation
- Format: `PB-YYMMDD-XXXXXX` using `random_bytes(3)` for hex suffix
- No uniqueness check in code (relies on DB UNIQUE constraint on `booking_code`)
- Collision probability is very low (16^6 = 16.7M possibilities per day) but non-zero

### 10.4 No Soft Delete
- Neither `pool_configurations` nor `pool_bookings` use soft delete
- Cancellation sets `status='cancelled'` but record persists
- Deleted configurations cannot be recovered

### 10.5 Grid/Schedule Route Migration
- Grid and schedule routes are noted as "migrated to FacilityGrids module"
- But PoolGridController and PoolScheduleController still exist in the module
- Potential routing conflict or dead code

### 10.6 Occupancy Metrics Use JSON_LENGTH
- `PoolOccupancyOptimizer::getUtilizationMetrics()` uses MySQL `JSON_LENGTH(lanes_json)` in SQL
- This counts lane-slots per booking but does NOT account for slot duration differences
- A 1-hour booking and a 3-hour booking both count as the same number of lane-slots

---

## 11. Configuration Dependencies

All configuration is stored within the pool's own JSON columns:

| JSON Field | Location | Purpose |
|------------|----------|---------|
| safety_ratio_json | pool_configurations | Max swimmers per lane by booking type |
| operating_hours_json | pool_configurations | Operating start/end time, slot duration |
| lane_widths_json | pool_configurations | Array of lane widths |
| lanes_json | pool_bookings | Which lanes are booked (array of lane numbers) |

No external `system_config` or `business_rules` dependencies.

---

## 12. Booking Types

| Type | Arabic | Safety Ratio (swimmers/lane) |
|------|--------|------------------------------|
| lap_swimming | سباحة حرة (لفات) | 6 |
| lessons | دروس سباحة | 4 |
| free_swim | سباحة ترفيهية | 8 |
| academy_session | حصة أكاديمية | 5 |
| competition | مسابقة / تدريب فريق | 4 |
| maintenance | صيانة | 0 (blocks lane) |

---

## 13. Known Patterns & Gotchas

1. **Lane-level granularity**: Unlike Reservations (facility-level), PoolManagement operates at lane level within a pool
2. **JSON columns for flexible config**: Safety ratios, operating hours, lane widths, and selected lanes all stored as JSON
3. **Booking starts as 'confirmed'**: Unlike Reservations which start as 'pending', pool bookings go directly to 'confirmed'
4. **No pricing calculation**: `unit_rate` and `total_amount` are set to 0 in the store action — pricing is not implemented
5. **Carnet integration**: `carnet_guest_entry_id` column exists for linking pool bookings to carnet guest entries
6. **Pool lanes created on config store**: Individual `pool_lanes` rows are created in the same controller action as the configuration
7. **Grid view is the primary navigation target**: Creating a config or booking redirects to the grid view
8. **`selection_mode` is derived**: Calculated from lane selection count vs total lanes (lane/multi_lane/full_pool)
9. **No event integration**: Module operates independently — no events dispatched or consumed
10. **No sidebar menu registration**: Only permissions registered in bootstrap.php — navigation must be handled elsewhere
