# SportsActivity Module — Architecture Map

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

---

## 1. Purpose & Responsibilities

The SportsActivity module is a **self-contained sports facility management system** within the ERP. It manages:

- **Disciplines** (sports types: swimming, football, tennis, etc.)
- **Facilities & Units** (courts, pools, fields with sub-units like lanes)
- **Coaches** (internal/external, academy-linked, multi-discipline)
- **Players** (member or non-member, with registration, medical, cards)
- **Academies** (external sports organizations contracted to use facilities)
- **Programs** (training programs under disciplines/academies)
- **Groups** (player cohorts enrolled in programs with schedules)
- **Bookings** (hourly facility rentals + recurring training sessions)
- **Scheduling** (weekly templates → generated training bookings)
- **Attendance** (per-session player check-in/out)
- **Subscriptions** (monthly player fees for group enrollment)
- **Pricing** (time-bracket-based hourly rates + academy pricing)
- **Gate Access** (card-based entry/exit with validation)
- **Player Cards** (QR-coded access cards with renewal lifecycle)
- **Pool Grid** (zone-based pool management with templates)
- **Pool Tickets** (single-use open-access pool entry)
- **Booking Passes** (multi-entry passes for hourly bookings)
- **Lockers** (physical locker inventory + rental management)
- **Waitlist** (automatic queue for full groups with offer system)
- **Mirror** (real-time facility occupancy display with drag-and-drop management)
- **Registration Wizard** (multi-step player onboarding flow)
- **Service Desk** (unified lookup + ticket issuance for front desk)

This module is **highly self-contained** — no other modules import from it. It imports from Members (validation), Cashier (payments), Payments, Carnets (QR), and Notifications (SMS).

---

## 2. Directory & File Structure

```
app/Modules/SportsActivity/
├── bootstrap.php                              # Menu, permissions, event listeners
├── Routes.php                                 # ~180 routes (web + API)
├── SaConstants.php                            # Status/type constants
├── Controllers/ (28 controllers)
│   ├── DashboardController.php                # Module home dashboard
│   ├── DisciplineController.php               # Sports type CRUD + activity browser
│   ├── FacilityController.php                 # Facility CRUD + time brackets + blackouts
│   ├── FacilityUnitController.php             # Sub-unit CRUD (lanes, courts)
│   ├── CoachController.php                    # Coach CRUD
│   ├── PlayerController.php                   # Player CRUD
│   ├── PlayerDocumentController.php           # Medical docs upload/approve/reject
│   ├── AcademyController.php                  # Academy CRUD
│   ├── AcademyContractController.php          # Contract CRUD + approval
│   ├── AcademyPricingController.php           # Academy pricing display + calculator
│   ├── ProgramController.php                  # Training program CRUD
│   ├── GroupController.php                    # Group CRUD + enrollment + transfer + schedule
│   ├── ScheduleController.php                 # Calendar views + bulk generation
│   ├── BookingController.php                  # Booking CRUD + checkin/checkout
│   ├── BookingWizardController.php            # Step-by-step booking flow
│   ├── BookingPassController.php              # Pass validation + usage
│   ├── PricingController.php                  # Pricing rule management
│   ├── SubscriptionController.php             # Sub generation + payment + exemption
│   ├── AttendanceController.php               # Record + report
│   ├── MirrorController.php                   # Real-time facility grid display
│   ├── GateController.php                     # Card scan + access log
│   ├── WaitlistController.php                 # Queue management + offers
│   ├── LockerController.php                   # Locker inventory CRUD
│   ├── LockerRentalController.php             # Rental lifecycle (create/renew/evict)
│   ├── PoolGridController.php                 # Pool zone grid management
│   ├── PoolTicketController.php               # Pool entry ticket issuance
│   ├── RegistrationWizardController.php       # Multi-step player registration
│   ├── ServiceDeskController.php              # Front desk unified search + tickets
│   ├── SaCardController.php                   # Card lifecycle management
│   └── Api/ (10 API controllers)
│       ├── ActivityBrowserApiController.php   # Discipline/program/coach browsing
│       ├── BookingApiController.php           # Price preview
│       ├── MemberLookupApiController.php      # Member validation for bookings
│       ├── MirrorApiController.php            # Facility state + quick actions (book/cancel/move/swap)
│       ├── PlayerSearchApiController.php      # Player search JSON
│       ├── PoolGridApiController.php          # Pool grid state + assign/clear
│       ├── PoolGridTemplateApiController.php  # Pool template CRUD + expand/rollback
│       ├── ScheduleApiController.php          # Availability + conflict checks
│       ├── SmartFilterApiController.php       # Groups/coaches/disciplines search
│       └── SubscriptionPreviewApiController.php # Sub preview + pause/unpause
├── Models/ (18 models)
│   ├── Academy.php
│   ├── AcademyContract.php
│   ├── Booking.php
│   ├── Coach.php
│   ├── Discipline.php
│   ├── Facility.php
│   ├── FacilityUnit.php
│   ├── Group.php
│   ├── GroupPlayer.php
│   ├── GroupSchedule.php
│   ├── Locker.php
│   ├── LockerRental.php
│   ├── Player.php
│   ├── PlayerDocument.php
│   ├── PricingRule.php
│   ├── Program.php
│   ├── Subscription.php
│   ├── TimeBracket.php
│   └── Waitlist.php
├── Services/ (23 services)
│   ├── AcademyPricingService.php              # Academy pricing engine
│   ├── AttendanceRuleService.php              # Absence threshold monitoring
│   ├── BookingPassService.php                 # Multi-entry pass lifecycle
│   ├── BookingService.php                     # Core booking creation/cancellation
│   ├── CardRenewalService.php                 # Card renewal + payment
│   ├── ConflictDetectionService.php           # Booking conflict checks
│   ├── DiscountCalculatorService.php          # Subscription discount rules
│   ├── EnrollmentService.php                  # Group enrollment lifecycle
│   ├── GateAccessService.php                  # Card-based gate validation
│   ├── GroupTransferService.php               # Inter-group player transfer
│   ├── MirrorStateService.php                 # Facility occupancy grid builder
│   ├── NumberGeneratorService.php             # Sequential ID generation
│   ├── PoolGridService.php                    # Pool zone grid operations
│   ├── PoolGridTemplateService.php            # Reusable pool templates
│   ├── PoolTicketService.php                  # Pool entry ticket lifecycle
│   ├── PricingCalculatorService.php           # Hourly booking pricing
│   ├── RegistrationWizardService.php          # Multi-step registration orchestrator
│   ├── SaEventListenerService.php             # Central payment event router
│   ├── SaPaymentService.php                   # Payment processing bridge
│   ├── ScheduleGeneratorService.php           # Weekly schedule → booking expansion
│   ├── SlotAvailabilityService.php            # Time slot capacity checker
│   ├── SubscriptionGeneratorService.php       # Batch monthly subscription creation
│   └── WaitlistAutoOfferService.php           # Auto-offer vacant spots
└── Views/ (20 view directories, ~60 view files)
    ├── dashboard.php
    ├── academies/ (7 views)
    ├── activities/ (1 view)
    ├── attendance/ (3 views)
    ├── bookings/ (5 views)
    ├── cards/ (2 views)
    ├── coaches/ (4 views)
    ├── disciplines/ (4 views)
    ├── facilities/ (6 views)
    ├── gate/ (2 views)
    ├── groups/ (4 views)
    ├── locker-rentals/ (5 views)
    ├── lockers/ (4 views)
    ├── mirror/ (3 views)
    ├── players/ (5 views)
    ├── pool-grid/ (2 views)
    ├── pool-tickets/ (1 view)
    ├── pricing/ (3 views)
    ├── programs/ (4 views)
    ├── registration/ (3 views)
    ├── schedule/ (2 views)
    ├── service-desk/ (1 view)
    ├── subscriptions/ (2 views)
    └── waitlist/ (1 view)
```

---

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

### 3.1 Core Entity Tables

#### `sa_players` (66 rows)
| Column | Type | Key | Notes |
|--------|------|-----|-------|
| id | bigint unsigned | PRI | |
| player_type | varchar(20) | MUL | 'member' or 'non_member' |
| member_id | bigint unsigned | MUL | FK to members (null for non-members) |
| registration_serial | varchar(50) | UNI | Generated serial |
| full_name_ar | varchar(300) | | |
| full_name_en | varchar(300) | | |
| national_id | varchar(20) | MUL | |
| date_of_birth | date | | |
| gender | varchar(10) | | |
| phone | varchar(30) | | |
| email | varchar(200) | | |
| guardian_name | varchar(300) | | For minors |
| guardian_phone | varchar(30) | | |
| guardian_national_id | varchar(20) | | |
| guardian_relationship | varchar(50) | | |
| photo_path | varchar(500) | | |
| medical_status | varchar(30) | MUL | pending/fit/conditional/expired/unfit |
| medical_expiry_date | date | | |
| card_status | varchar(20) | MUL | inactive/active/suspended/revoked/expired |
| registration_fee_paid | tinyint(1) | | |
| notes | text | | |
| branch_id | bigint unsigned | | |
| is_archived, created_at, updated_at, created_by, updated_by | | | Standard |

#### `sa_disciplines` (29 rows)
| Column | Type | Key | Notes |
|--------|------|-----|-------|
| id | bigint unsigned | PRI | |
| code | varchar(30) | UNI | e.g. 'swimming', 'football' |
| name_ar | varchar(200) | | |
| name_en | varchar(200) | | |
| category | varchar(30) | MUL | |
| icon | varchar(50) | | |
| description_ar | text | | |
| config_json | json | | Discipline-specific config |
| sort_order | int unsigned | | |
| is_active | tinyint(1) | MUL | |
| Standard soft-delete + audit columns | | | |

#### `sa_facilities` (27 rows)
| Column | Type | Key | Notes |
|--------|------|-----|-------|
| id | bigint unsigned | PRI | |
| code | varchar(30) | UNI | |
| name_ar | varchar(300) | | |
| facility_type | varchar(30) | MUL | court/pool/field/hall/gym |
| discipline_id | bigint unsigned | MUL | FK to sa_disciplines |
| operating_hours_json | json | | Opening/closing times |
| pool_grid_rows | tinyint unsigned | | For pool facilities |
| pool_grid_cols | tinyint unsigned | | For pool facilities |
| is_active | tinyint(1) | MUL | |
| branch_id | bigint unsigned | | |
| Standard soft-delete + audit columns | | | |

#### `sa_facility_units`
| Column | Type | Key | Notes |
|--------|------|-----|-------|
| id | bigint unsigned | PRI | |
| facility_id | bigint unsigned | MUL | FK to sa_facilities |
| code | varchar(30) | | |
| name_ar | varchar(200) | | |
| unit_type | varchar(30) | | lane/court/field/zone |
| booking_mode | varchar(20) | MUL | exclusive/shared |
| max_capacity | int unsigned | | |
| expected_capacity | int unsigned | | |
| sort_order | int unsigned | | |
| dimensions_json | json | | |
| config_json | json | | |
| is_active | tinyint(1) | | |

#### `sa_coaches` (25 rows)
| Column | Type | Key | Notes |
|--------|------|-----|-------|
| id | bigint unsigned | PRI | |
| code | varchar(30) | UNI | |
| full_name_ar | varchar(300) | | |
| national_id | varchar(20) | MUL | |
| employment_type | varchar(20) | | contract/permanent |
| coach_type | varchar(20) | MUL | independent/academy |
| academy_id | bigint unsigned | MUL | FK to sa_academies |
| employee_id | bigint unsigned | | FK to employees |
| payment_model | varchar(20) | | per_session/hourly/monthly |
| hourly_rate | decimal(10,2) | | |
| session_rate | decimal(10,2) | | |
| monthly_rate | decimal(10,2) | | |
| max_groups | int unsigned | | Default: 10 |
| is_active | tinyint(1) | MUL | |
| Standard soft-delete + audit columns | | | |

#### `sa_coach_disciplines` (junction)
| Column | Type | Notes |
|--------|------|-------|
| id | bigint unsigned PRI | |
| coach_id | bigint unsigned MUL | FK to sa_coaches |
| discipline_id | bigint unsigned MUL | FK to sa_disciplines |
| specialization_level | varchar(20) | primary/secondary |

### 3.2 Program & Group Tables

#### `sa_programs`
| Column | Type | Key | Notes |
|--------|------|-----|-------|
| id | bigint unsigned | PRI | |
| code | varchar(30) | UNI | |
| name_ar | varchar(300) | | |
| discipline_id | bigint unsigned | MUL | FK to sa_disciplines |
| academy_id | bigint unsigned | MUL | FK to sa_academies (nullable) |
| program_type | varchar(30) | | training/competition/camp |
| age_from / age_to | int unsigned | | Age restrictions |
| gender_restriction | varchar(10) | | male/female/null |
| skill_level | varchar(30) | | beginner/intermediate/advanced |
| session_duration_minutes | int unsigned | | Default: 60 |
| sessions_per_week | int unsigned | | Default: 3 |
| monthly_fee_member | decimal(10,2) | | |
| monthly_fee_nonmember | decimal(10,2) | | |
| max_capacity / min_capacity | int unsigned | | |
| is_active | tinyint(1) | MUL | |

#### `sa_groups` (33 rows)
| Column | Type | Key | Notes |
|--------|------|-----|-------|
| id | bigint unsigned | PRI | |
| code | varchar(30) | UNI | |
| name_ar | varchar(300) | | |
| program_id | bigint unsigned | MUL | FK to sa_programs |
| coach_id | bigint unsigned | MUL | FK to sa_coaches |
| min_capacity / max_capacity | int unsigned | | |
| current_count | int unsigned | | Live player count |
| is_full | tinyint(1) | | Derived from current_count >= max_capacity |
| monthly_fee_member | decimal(10,2) | | Override program fee |
| monthly_fee_nonmember | decimal(10,2) | | |
| min_age / max_age | tinyint unsigned | | |
| season_start / season_end | date | | |
| status | varchar(20) | MUL | active/paused/completed/cancelled |

#### `sa_group_players`
| Column | Type | Key | Notes |
|--------|------|-----|-------|
| id | bigint unsigned | PRI | |
| group_id | bigint unsigned | MUL | FK to sa_groups |
| player_id | bigint unsigned | MUL | FK to sa_players |
| enrolled_at | date | | |
| left_at | date | | Null if still active |
| status | varchar(20) | MUL | active/pending_payment/withdrawn/transferred |
| force_enrolled | tinyint(1) | | Bypassed capacity |
| transferred_to_group_id | bigint unsigned | | |
| transfer_reason | varchar(500) | | |
| paused_months | json | | Months to skip for subscription |
| payment_request_id | bigint unsigned | | FK to payment_requests |
| activated_by_payment_id | bigint unsigned | | FK to payments |

#### `sa_group_coaches` (multi-coach pivot)
| Column | Type | Key | Notes |
|--------|------|-----|-------|
| id | bigint unsigned | PRI | |
| group_id | bigint unsigned | MUL | FK to sa_groups |
| coach_id | bigint unsigned | MUL | FK to sa_coaches |
| role | varchar(20) | | primary/assistant |
| created_at | datetime | | |

**Note:** `sa_groups.coach_id` is kept as backward-compatible "primary coach" shortcut. The pivot table is the truth for all coaches. `EnrollmentService::enrollByCoach()` checks both fields when finding groups.

#### `sa_group_schedule`
| Column | Type | Key | Notes |
|--------|------|-----|-------|
| id | bigint unsigned | PRI | |
| group_id | bigint unsigned | MUL | FK to sa_groups |
| facility_unit_id | bigint unsigned | MUL | FK to sa_facility_units |
| day_of_week | tinyint unsigned | MUL | 0=Sun..6=Sat |
| start_time / end_time | time | | |
| is_active | tinyint(1) | | |

### 3.3 Booking & Scheduling Tables

#### `sa_bookings` (204 rows)
| Column | Type | Key | Notes |
|--------|------|-----|-------|
| id | bigint unsigned | PRI | |
| booking_number | varchar(50) | UNI | Sequential "BK-YYYY-NNNNNN" |
| facility_unit_id | bigint unsigned | MUL | |
| booking_type | varchar(20) | MUL | hourly/training |
| booking_date | date | MUL | |
| start_time / end_time | time | | |
| group_id | bigint unsigned | MUL | For training bookings |
| coach_id | bigint unsigned | MUL | |
| booker_type | varchar(20) | | member/guest/organization |
| booker_id | bigint unsigned | | |
| booker_name | varchar(300) | | |
| organization_name / organization_contact | | | For org bookings |
| booker_national_id | varchar(14) | MUL | |
| participant_count | int unsigned | | |
| participant_mode | enum | | full/partial/passes |
| passes_total / passes_used | int unsigned | | For pass-based bookings |
| spots_reserved | int unsigned | | |
| total_amount | decimal(10,2) | | |
| payment_status | varchar(20) | | unpaid/pending/paid/overdue/refunded |
| payment_id | bigint unsigned | MUL | FK to payments |
| receipt_id / receipt_number | | | |
| status | varchar(20) | MUL | confirmed/cancelled/no_show/completed |
| is_recurring | tinyint(1) | | |
| recurrence_pattern_json | json | | |
| parent_booking_id | bigint unsigned | MUL | For recurring series |

#### `sa_booking_participants`
| Column | Type | Notes |
|--------|------|-------|
| id | bigint unsigned PRI | |
| booking_id | bigint unsigned MUL | FK to sa_bookings |
| player_id | bigint unsigned MUL | Nullable (guest participants) |
| participant_name | varchar(300) | |
| is_member | tinyint(1) | |
| price_charged | decimal(10,2) | |
| checked_in_at / checked_out_at | timestamp | |

#### `sa_booking_passes`
| Column | Type | Notes |
|--------|------|-------|
| id | bigint unsigned PRI | |
| booking_id | bigint unsigned MUL | FK to sa_bookings |
| pass_number | varchar(30) UNI | Sequential pass ID |
| status | enum | issued/used/expired/cancelled |
| used_at | datetime | |
| used_by_name | varchar(200) | |
| gate_employee_id | bigint unsigned | |

### 3.4 Subscription & Financial Tables

#### `sa_subscriptions`
| Column | Type | Key | Notes |
|--------|------|-----|-------|
| id | bigint unsigned | PRI | |
| subscription_number | varchar(50) | UNI | Sequential "SUB-YYYY-NNNNNN" |
| player_id | bigint unsigned | MUL | FK to sa_players |
| group_id | bigint unsigned | MUL | FK to sa_groups |
| period_start / period_end | date | | Monthly period |
| amount | decimal(10,2) | | Base fee |
| discount_amount | decimal(10,2) | | Applied discount |
| final_amount | decimal(10,2) | | amount - discount |
| payment_status | varchar(20) | MUL | unpaid/pending/paid/overdue/refunded |
| paid_at | timestamp | | |
| paid_amount | decimal(10,2) | | |
| payment_id | bigint unsigned | MUL | FK to payments |
| receipt_id / receipt_number | | | |
| exemption_reason | text | | If exempted |
| exempted_by | bigint unsigned | | |

#### `sa_pricing_rules`
| Column | Type | Key | Notes |
|--------|------|-----|-------|
| id | bigint unsigned | PRI | |
| facility_unit_id | bigint unsigned | MUL | |
| time_bracket_id | bigint unsigned | MUL | |
| group_size_min / group_size_max | int unsigned | | |
| price_per_person_member | decimal(10,2) | | |
| price_per_person_nonmember | decimal(10,2) | | |
| effective_from / effective_to | date | | |
| is_active | tinyint(1) | MUL | |

#### `sa_time_brackets`
| Column | Type | Key | Notes |
|--------|------|-----|-------|
| id | bigint unsigned | PRI | |
| facility_id | bigint unsigned | MUL | |
| name_ar | varchar(100) | | e.g. "ذروة صباحية" |
| bracket_type | varchar(20) | MUL | peak/off_peak/evening |
| start_time / end_time | time | | |
| days_of_week_json | json | | Which days apply |
| is_active | tinyint(1) | | |

#### `sa_discount_rules`
| Column | Type | Key | Notes |
|--------|------|-----|-------|
| id | bigint unsigned | PRI | |
| rule_code | varchar(50) | UNI | |
| name_ar | varchar(200) | | |
| discount_type | varchar(20) | | percentage/fixed |
| discount_value | decimal(10,2) | | |
| applies_to | varchar(50) | MUL | subscription/booking |
| academy_code | varchar(50) | | Scope to academy |
| condition_type | varchar(50) | MUL | advance_payment/sibling/etc |
| condition_value | text | | JSON criteria |
| is_active | tinyint(1) | | |

#### `sa_academy_pricing`
| Column | Type | Key | Notes |
|--------|------|-----|-------|
| id | bigint unsigned | PRI | |
| academy_code | varchar(50) | MUL | |
| discipline_code | varchar(30) | MUL | |
| category | varchar(50) | MUL | subscription/session_card/lane_rental/etc |
| level_name_ar / level_name_en | varchar(200) | | |
| age_from / age_to | int unsigned | MUL | |
| gender | varchar(10) | | |
| sessions_per_week / sessions_per_month | int unsigned | | |
| session_duration_minutes | int unsigned | | |
| price_member / price_nonmember | decimal(10,2) | | |
| price_subscription | decimal(10,2) | | |
| billing_period | varchar(20) | | monthly/quarterly/yearly |
| season | varchar(20) | | e.g. '2025_2026' |
| effective_from / effective_to | date | | |
| is_active | tinyint(1) | MUL | |

### 3.5 Gate & Card Tables

#### `sa_player_cards`
| Column | Type | Key | Notes |
|--------|------|-----|-------|
| id | bigint unsigned | PRI | |
| card_number | varchar(30) | UNI | |
| player_id | bigint unsigned | MUL | FK to sa_players |
| registration_id | bigint unsigned | MUL | FK to sa_registrations |
| card_type | varchar(20) | | standard/temporary |
| status | varchar(20) | MUL | active/temporary/suspended/revoked/expired |
| qr_code_data | varchar(200) | | For scanning |
| valid_from / valid_until | date | MUL | Expiry tracked |
| photo_path | varchar(500) | | |
| print_count | int | | |
| suspend_reason / revoke_reason | varchar(500) | | |

#### `sa_gate_access_log`
| Column | Type | Key | Notes |
|--------|------|-----|-------|
| id | bigint unsigned | PRI | |
| player_id | bigint unsigned | MUL | |
| card_id | bigint unsigned | MUL | |
| access_type | varchar(10) | | entry/exit |
| access_point | varchar(100) | | |
| granted | tinyint(1) | MUL | |
| denial_reason | varchar(200) | | |
| scanned_data | varchar(200) | | |
| recorded_by | bigint unsigned | | |
| recorded_at | timestamp | MUL | |

### 3.6 Pool Management Tables

#### `sa_pool_zone_bookings`
| Column | Type | Key | Notes |
|--------|------|-----|-------|
| id | bigint unsigned | PRI | |
| facility_id | bigint unsigned | MUL | |
| booking_date | date | | |
| start_time / end_time | time | | |
| zone_row / zone_col | tinyint unsigned | | Grid coordinates |
| assignment_type | varchar(20) | | training/blocked/maintenance/hourly |
| access_mode | enum | | reserved/open_access |
| access_restriction | enum | | all/members_only |
| ticket_price_member / ticket_price_nonmember | decimal(10,2) | | For open_access zones |
| max_occupancy / current_occupancy | int unsigned | | |
| group_id | bigint unsigned | MUL | For training assignments |
| coach_id | bigint unsigned | MUL | |
| template_run_id | bigint unsigned | MUL | FK to sa_pool_zone_template_runs |
| status | varchar(20) | | active/cancelled |

#### `sa_pool_zone_templates`
| Column | Type | Notes |
|--------|------|-------|
| id | bigint unsigned PRI | |
| facility_id | bigint unsigned MUL | |
| name | varchar(200) | |
| description | text | |
| color | varchar(20) | Display color |
| is_active | tinyint(1) | |

#### `sa_pool_zone_template_entries`
| Column | Type | Notes |
|--------|------|-------|
| id | bigint unsigned PRI | |
| template_id | bigint unsigned MUL | FK to sa_pool_zone_templates |
| day_of_week | tinyint unsigned MUL | |
| start_time / end_time | time | |
| zone_selection_type | varchar(20) | |
| zone_selection_json | json | |
| assignment_type | varchar(20) | |
| group_id | bigint unsigned MUL | |
| coach_id | bigint unsigned MUL | |
| label | varchar(200) | |

#### `sa_pool_zone_template_runs`
| Column | Type | Notes |
|--------|------|-------|
| id | bigint unsigned PRI | |
| template_id | bigint unsigned MUL | |
| date_from / date_to | date | |
| generated_count / skipped_count | int unsigned | |
| status | varchar(20) | completed/rolled_back |
| rolled_back_at / rolled_back_by | | |

#### `sa_pool_tickets`
| Column | Type | Key | Notes |
|--------|------|-----|-------|
| id | bigint unsigned | PRI | |
| facility_id | int unsigned | MUL | |
| zone_booking_id | bigint unsigned | MUL | FK to sa_pool_zone_bookings |
| booking_date | date | | |
| start_time / end_time | time | | |
| member_id / player_id | int unsigned | | |
| guest_name / guest_national_id | | | For non-members |
| is_member | tinyint(1) | | |
| amount_paid | decimal(10,2) | | |
| payment_status | enum | | unpaid/paid/refunded |
| payment_request_id | int unsigned | | |
| entry_time / exit_time | datetime | | |
| status | enum | | issued/active/completed/cancelled |

### 3.7 Locker Tables

#### `sa_lockers`
| Column | Type | Key | Notes |
|--------|------|-----|-------|
| id | bigint unsigned | PRI | |
| code | varchar(30) | UNI | |
| name_ar | varchar(200) | | |
| facility_id | bigint unsigned | MUL | |
| discipline_id | bigint unsigned | MUL | |
| locker_type | varchar(30) | | standard/premium |
| location_note | varchar(300) | | |
| is_active | tinyint(1) | MUL | |

#### `sa_locker_rentals`
| Column | Type | Key | Notes |
|--------|------|-----|-------|
| id | bigint unsigned | PRI | |
| rental_number | varchar(30) | UNI | |
| locker_id | bigint unsigned | MUL | FK to sa_lockers |
| player_id | bigint unsigned | MUL | FK to sa_players |
| rental_type | enum | | monthly/6_months/yearly |
| start_date / end_date | date | MUL | |
| amount | decimal(15,2) | | |
| payment_status | enum | | unpaid/paid/partial |
| payment_id / receipt_id / receipt_number | | | |
| status | varchar(30) | MUL | active/expired/evicted |
| grace_started_at | date | | |
| evicted_at | date | | |
| eviction_reason | text | | |
| renewed_from_id | bigint unsigned | | Self-FK for renewal chain |

### 3.8 Other Tables

#### `sa_registrations`
| Column | Type | Key | Notes |
|--------|------|-----|-------|
| id | bigint unsigned | PRI | |
| registration_number | varchar(30) | UNI | |
| player_id | bigint unsigned | MUL | |
| player_type | varchar(20) | | member/non_member |
| member_id | bigint unsigned | | |
| status | varchar(20) | MUL | in_progress/pending_payment/completed/cancelled |
| registration_fee / card_fee / form_fee / total_fees | decimal(10,2) | | |
| group_id | bigint unsigned | | Selected group |
| subscription_amount / subscription_months | | | |
| photo_captured / form_printed / card_generated | tinyint(1) | | Wizard step flags |
| payment_request_id / form_payment_request_id | bigint unsigned | | |
| payment_status / form_payment_status | varchar(20) | MUL | |

#### `sa_attendance`
| Column | Type | Key | Notes |
|--------|------|-----|-------|
| id | bigint unsigned | PRI | |
| booking_id | bigint unsigned | MUL | FK to sa_bookings |
| player_id | bigint unsigned | MUL | FK to sa_players |
| attendance_date | date | MUL | |
| status | varchar(20) | MUL | present/absent/late/excused |
| check_in_time / check_out_time | time | | |
| recorded_by | bigint unsigned | | |

#### `sa_waitlist`
| Column | Type | Key | Notes |
|--------|------|-----|-------|
| id | bigint unsigned | PRI | |
| player_id | bigint unsigned | MUL | |
| group_id | bigint unsigned | MUL | |
| position | int unsigned | | Queue position |
| status | varchar(20) | MUL | waiting/offered/accepted/expired/cancelled |
| offered_at / expires_at / accepted_at | timestamp | | |

#### `sa_academies`
| Column | Type | Key | Notes |
|--------|------|-----|-------|
| id | bigint unsigned | PRI | |
| code | varchar(30) | UNI | |
| name_ar | varchar(300) | | |
| discipline_id | bigint unsigned | MUL | FK to sa_disciplines |
| academy_type | varchar(30) | | external/internal |
| contact_person / contact_phone / contact_email | | | |
| is_active | tinyint(1) | MUL | |

#### `sa_academy_contracts`
| Column | Type | Key | Notes |
|--------|------|-----|-------|
| id | bigint unsigned | PRI | |
| contract_number | varchar(50) | UNI | |
| academy_id | bigint unsigned | MUL | |
| contract_type | varchar(30) | | revenue_share/fixed_rent/hybrid |
| start_date / end_date | date | MUL | |
| club_commission_pct / academy_share_pct | decimal(5,2) | | |
| fixed_monthly_rent | decimal(10,2) | | |
| deposit_amount / deposit_status | | | |
| contract_pdf_path | varchar(500) | | |
| status | varchar(20) | MUL | draft/pending_approval/active/terminated |

#### `sa_blackout_dates`
| Column | Type | Notes |
|--------|------|-------|
| id | bigint unsigned PRI | |
| facility_id | bigint unsigned MUL | |
| facility_unit_id | bigint unsigned MUL | Optional (facility-wide if null) |
| blackout_date | date | |
| start_time / end_time | time | Optional (full day if null) |
| reason | varchar(500) | |

#### `sa_player_documents`
| Column | Type | Key | Notes |
|--------|------|-----|-------|
| id | bigint unsigned | PRI | |
| player_id | bigint unsigned | MUL | |
| document_type | varchar(30) | MUL | medical_certificate/fitness_test/etc |
| file_path / file_name | varchar | | |
| exam_date / expiry_date | date | MUL | |
| doctor_name / clinic_name | varchar(200) | | |
| approval_status | varchar(20) | MUL | pending/approved/rejected/conditional |
| approved_by | bigint unsigned | | |
| rejection_reason / conditional_notes | text | | |

#### `sa_mirror_templates`
| Column | Type | Notes |
|--------|------|-------|
| id | bigint unsigned PRI | |
| facility_id | int unsigned MUL | |
| name | varchar(100) | |
| template_data | json | Full grid snapshot |

#### `sa_rental_entity_pricing`
| Column | Type | Notes |
|--------|------|-------|
| id | bigint unsigned PRI | |
| facility_type | varchar(50) MUL | |
| entity_type | varchar(50) MUL | |
| usage_type | varchar(50) MUL | practice/competition/rental |
| time_period | varchar(20) | peak/off_peak |
| duration_minutes | int unsigned | |
| price_amount | decimal(10,2) | |
| max_players | int unsigned | |
| effective_from / effective_to | date | |

---

## 4. Core Business Flows

### 4.1 Player Registration Wizard

```
1. POST /sa/registration/lookup → Find existing player or create new
2. GET /sa/registration/{id} → Show current wizard step
3. POST /sa/registration/{id}/photo → Capture player photo
4. POST /sa/registration/{id}/pay-form → Submit form fee payment (505 EGP)
5. POST /sa/registration/{id}/activity → Select discipline/program/group
6. POST /sa/registration/{id}/pay → Submit registration + subscription payment
7. POST /sa/registration/{id}/generate-card → Create player card with QR
8. GET /sa/registration/{id}/print-form → Print registration form
9. GET /sa/registration/{id}/print-card → Print player card

Payment flows through PaymentRequestService → Cashier → on completion:
  SaEventListenerService::handlePaymentCompleted → activates enrollment + card
```

### 4.2 Booking Flow (Hourly)

```
1. Member/guest identifies via Booking Wizard or direct create
2. Select facility unit + date + time slot
3. PricingCalculatorService calculates price (time bracket + group size + member status)
4. SlotAvailabilityService checks capacity + ConflictDetectionService checks conflicts
5. BookingService::createHourlyBooking() creates sa_bookings record
6. If participant_mode = 'passes': BookingPassService::generatePasses() creates numbered passes
7. Payment: either immediate (PaymentService) or queued (PaymentRequestService)
8. Gate access: passes validated at entry via BookingPassController::usePass()
```

### 4.3 Training Schedule Flow

```
1. Create Program → Create Group → Assign Coach
2. GroupController::saveSchedule() saves sa_group_schedule (weekly template)
3. ScheduleGeneratorService::generateForGroup() expands template → sa_bookings
4. Each generated booking: type='training', group_id set, is_recurring=1
5. Attendance recorded per session via AttendanceController
6. SubscriptionGeneratorService::generateForMonth() creates monthly sa_subscriptions
```

### 4.4 Pool Grid Management

```
1. Pool facility has grid dimensions (pool_grid_rows × pool_grid_cols)
2. PoolGridService manages zone assignments (training/blocked/maintenance/hourly/open_access)
3. PoolGridTemplateService allows saving/loading reusable weekly patterns
4. Template expansion: PoolGridTemplateService::expandTemplate() → creates sa_pool_zone_bookings
5. Open-access zones: PoolTicketService issues tickets, tracks occupancy
6. Reserved zones: Standard booking flow for training groups
```

### 4.5 Gate Access Flow

```
1. Player card scanned (QR code)
2. GateAccessService::checkAccess() validates:
   - Card exists and is active
   - Card not expired
   - Player has active booking/training today
   - Medical status is valid
3. If valid → recordEntry() → sa_gate_access_log (granted=1)
4. If denied → recordDenial() → dispatch 'sa.gate.access_denied'
5. Exit: recordExit()
```

### 4.6 Subscription Lifecycle

```
1. SubscriptionGeneratorService::generateForMonth() → batch creates sa_subscriptions
   - One per active group_player per month
   - Proration for mid-month enrollments
   - Skips paused months (sa_group_players.paused_months)
2. Payment collection: SubscriptionController::pay() → PaymentRequestService
3. On payment complete: SaEventListenerService marks paid
4. Overdue notifications: event 'sa.subscription.overdue' → SMS
5. Exemption: SubscriptionController::exempt() (requires sa.subscription.exempt permission)
```

---

## 5. Events

### 5.1 Events Dispatched by SportsActivity

| Event | Dispatched By | Payload |
|-------|---------------|---------|
| `sa.card.issued` | RegistrationWizardService | {player_id, card_id, card_number} |
| `sa.card.renewed` | CardRenewalService | {player_id, card_id} |
| `sa.card.expiry_reminder` | (cron trigger) | {phone, player_name, expiry_date} |
| `sa.registration.completed` | RegistrationWizardService | {registration_id, player_id} |
| `sa.gate.access_denied` | GateAccessService | {player_name, reason, ...} |
| `sa.player.absence_threshold` | AttendanceRuleService | {phone, player_name, absence_count, group_name} |
| `sa.player.transferred` | GroupTransferService | {player_id, to_group_id} |
| `sa.subscription.overdue` | (notification trigger) | {phone, player_name, amount} |
| `sa.waitlist.offer` | WaitlistAutoOfferService | {phone, player_name, group_name} |

### 5.2 Events Consumed by SportsActivity

| Event | Handler | Action |
|-------|---------|--------|
| `payment_request.completed` | SaEventListenerService::handlePaymentCompleted | Routes by entity_type → activates enrollment/card/subscription |
| `payment.voided` | SaEventListenerService::handlePaymentVoided | Reverses activation |

### 5.3 Internal Event Listeners (bootstrap.php)

| Event | Action |
|-------|--------|
| `sa.card.expiry_reminder` | SMS notification |
| `sa.subscription.overdue` | SMS notification |
| `sa.player.transferred` | SMS notification to guardian |
| `sa.player.absence_threshold` | SMS notification |
| `sa.gate.access_denied` | Logger::warning |
| `sa.waitlist.offer` | SMS notification |

---

## 6. Cross-Module Dependencies

### 6.1 SportsActivity IMPORTS FROM:

| Module | What it uses | Used by services |
|--------|-------------|-----------------|
| Cashier | PaymentRequestService | Registration, Enrollment, CardRenewal, PoolTicket |
| Payments | PaymentService | BookingService, SaPaymentService |
| Members | MembershipValidationService, NationalIdParser | MemberLookupApi, RegistrationWizard |
| Carnets | QRCodeGenerator | RegistrationWizardService (card generation) |
| Notifications | SmsNotificationService | bootstrap.php event listeners |
| Shared | PhotoUploadService | RegistrationWizardService |

### 6.2 Other modules that IMPORT FROM SportsActivity:

**None.** This module is a leaf node in the dependency graph — nothing imports from it.

---

## 7. Permissions (56 total)

| Category | Permissions |
|----------|------------|
| Dashboard | sa.dashboard |
| Discipline | sa.discipline.view, sa.discipline.manage |
| Facility | sa.facility.view, sa.facility.manage |
| Coach | sa.coach.view, sa.coach.manage |
| Player | sa.player.view, sa.player.manage |
| Academy | sa.academy.view, sa.academy.manage |
| Contract | sa.contract.view, sa.contract.manage, sa.contract.approve |
| Program | sa.program.view, sa.program.manage |
| Group | sa.group.view, sa.group.manage, sa.group.enroll, sa.group.force_enroll |
| Schedule | sa.schedule.view, sa.schedule.manage |
| Booking | sa.booking_wizard.use, sa.booking.view, sa.booking.create, sa.booking.manage |
| Pricing | sa.pricing.view, sa.pricing.manage |
| Subscription | sa.subscription.view, sa.subscription.generate, sa.subscription.collect, sa.subscription.exempt |
| Attendance | sa.attendance.view, sa.attendance.manage |
| Mirror | sa.mirror.view |
| Gate | sa.gate.view, sa.gate.scan |
| Waitlist | sa.waitlist.view, sa.waitlist.manage |
| Pool Grid | sa.pool-grid.manage |
| Locker | sa.locker.view, sa.locker.manage |
| Locker Rental | sa.locker_rental.view, sa.locker_rental.create, sa.locker_rental.manage, sa.locker_rental.evict |
| Registration | sa.registration.view, sa.registration.manage |
| Card | sa.card.view, sa.card.manage, sa.card.print |
| Pool Ticket | sa.pool_ticket.view, sa.pool_ticket.issue, sa.pool_ticket.manage |
| Medical (external) | medical.board.approve (from MedicalBoard module) |

---

## 8. Constants (SaConstants.php)

| Category | Values |
|----------|--------|
| Enrollment status | active, pending_payment, withdrawn, transferred |
| Booking status | confirmed, cancelled, no_show, completed |
| Booking types | hourly, training |
| Payment status | unpaid, pending, paid, overdue, refunded |
| Player types | member, non_member |
| Booker types | member, guest, organization |
| Participant modes | full, partial, passes |
| Pass status | issued, used, expired, cancelled |
| Pool ticket status | issued, active, completed, cancelled |
| Pool access modes | reserved, open_access |
| Medical status | fit, conditional, pending, expired, unfit |
| Card status | active, temporary, suspended, revoked, expired |
| Pool grid actions | training, blocked, maintenance, hourly |
| Registration status | in_progress, pending_payment, completed, cancelled |
| Group status | active, paused, completed, cancelled |
| Payment types | sports_registration, hourly_booking, sa_form_fee, sports_subscription, sa_card_renewal |
| Entity types | sa_group_players, sa_subscriptions, sa_bookings, sa_registrations, sa_registration_form, sa_player_cards |

---

## 9. High-Risk Areas

### 9.1 SaEventListenerService (CRITICAL)
- Central payment completion router — if broken, registrations/enrollments/cards don't activate
- Routes by `related_entity_type` to different handlers
- Also handles payment voids (reversal logic)

### 9.2 Booking Conflicts & Slot Availability
- SlotAvailabilityService + ConflictDetectionService prevent double-booking
- Must check: facility unit capacity, exclusive mode, blackout dates, pool zone blocking
- If broken: double-bookings, overbooked facilities

### 9.3 Pool Grid State Management
- Complex zone×time×date grid with occupancy tracking
- Template expansion generates bulk records — rollback must be precise
- `current_occupancy` counter on pool_zone_bookings must stay in sync with tickets

### 9.4 Subscription Generation
- Batch process affects all active players
- Proration logic for mid-month enrollment
- Paused months feature (JSON array) must be correctly consulted
- If run twice: duplicate subscriptions

### 9.5 Group Enrollment Count
- `sa_groups.current_count` is manually maintained (not a computed column)
- Enrollment, withdrawal, transfer all must update this counter
- `is_full` flag derived from it — gates new enrollments
- If desynchronized: groups appear full when they aren't (or vice versa)

### 9.6 Card Validity & Gate Access
- Gate access depends on card status + validity dates + medical status
- Card renewal extends `valid_until` — if payment fails, card can expire mid-access
- Medical expiry checked at gate — expired medical = access denied

---

## 10. API Endpoints Summary

### Internal AJAX APIs (session auth)
| Endpoint | Purpose |
|----------|---------|
| GET /api/sa/schedule/availability | Check time slot availability |
| GET /api/sa/schedule/conflicts | Detect booking conflicts |
| GET /api/sa/bookings/price-preview | Calculate booking price |
| GET /api/sa/players/search | Player search JSON |
| POST /api/sa/member-lookup | Member validation for bookings |
| GET /api/sa/groups/search | Group search |
| GET /api/sa/coaches/search | Coach search |
| GET /api/sa/disciplines/list | Discipline list |
| GET /api/sa/mirror/{id}/state | Full facility grid state |
| GET /api/sa/mirror/{id}/groups | Groups using facility |
| POST /api/sa/mirror/{id}/quick-book | Create booking from mirror |
| POST /api/sa/mirror/{id}/cancel-booking | Cancel from mirror |
| POST /api/sa/mirror/{id}/move-booking | Move booking to different slot |
| POST /api/sa/mirror/{id}/swap-bookings | Swap two bookings |
| POST /api/sa/mirror/{id}/copy-day | Copy day schedule to another date |
| POST /api/sa/mirror/{id}/bulk-block | Block multiple slots |
| POST /api/sa/mirror/{id}/extend-booking | Extend booking duration |
| POST /api/sa/mirror/{id}/save-template | Save grid as template |
| POST /api/sa/mirror/{id}/apply-template | Apply saved template |
| GET/POST /api/sa/pool-grid/{id}/* | Pool grid CRUD operations |
| GET /api/sa/activities/* | Activity browser data |
| GET /api/sa/subscriptions/preview | Subscription preview with discounts |
| POST /api/sa/subscriptions/pause | Pause player subscription |
| POST /api/sa/subscriptions/unpause | Unpause player subscription |
| GET /api/sa/registration/disciplines | Disciplines for wizard |
| GET /api/sa/registration/programs/{id} | Programs for discipline |

---

## 11. Data Model Relationships

```
sa_disciplines
  ├── sa_programs (1:many)
  │     └── sa_groups (1:many)
  │           ├── sa_group_players (1:many) ──→ sa_players
  │           ├── sa_group_schedule (1:many) ──→ sa_facility_units
  │           └── sa_bookings (1:many, type=training)
  ├── sa_facilities (1:many)
  │     ├── sa_facility_units (1:many)
  │     │     ├── sa_bookings (1:many)
  │     │     ├── sa_pricing_rules (1:many)
  │     │     └── sa_blackout_dates (0:many)
  │     ├── sa_time_brackets (1:many)
  │     ├── sa_pool_zone_bookings (1:many) [pool facilities]
  │     ├── sa_pool_zone_templates (1:many)
  │     └── sa_lockers (1:many)
  ├── sa_academies (1:many)
  │     ├── sa_academy_contracts (1:many)
  │     └── sa_coaches (1:many, academy coaches)
  └── sa_coach_disciplines (many:many junction)

sa_players
  ├── sa_group_players (1:many)
  ├── sa_player_cards (1:many)
  ├── sa_player_documents (1:many)
  ├── sa_registrations (1:many)
  ├── sa_subscriptions (1:many)
  ├── sa_attendance (1:many)
  ├── sa_gate_access_log (1:many)
  ├── sa_locker_rentals (1:many)
  ├── sa_waitlist (1:many)
  └── sa_booking_participants (1:many)

sa_bookings
  ├── sa_booking_participants (1:many)
  ├── sa_booking_passes (1:many)
  └── sa_attendance (1:many)
```

---

## 12. Configuration Dependencies

| Config Source | Key | Purpose |
|---------------|-----|---------|
| system_config | sa.card_validity_months | Default card validity period |
| system_config | sa.card_renewal_fee | Card renewal fee amount |
| system_config | sa.form_fee | Registration form fee |
| system_config | sa.absence_threshold | Max absences before alert |
| system_config | sa.registration_number_prefix | Registration serial prefix |
| sa_pricing_rules | (table) | Hourly booking pricing by unit/bracket/size |
| sa_time_brackets | (table) | Peak/off-peak time definitions |
| sa_academy_pricing | (table) | Academy-specific fee structures |
| sa_discount_rules | (table) | Subscription/booking discounts |
| sa_rental_entity_pricing | (table) | Lane/facility rental pricing |

---

## 13. Cron/Background Processes

| Process | Triggered By | Action |
|---------|-------------|--------|
| Subscription generation | Manual (SubscriptionController::generate) | Batch create monthly subscriptions |
| Card expiry check | sa.card.expiry_reminder event | SMS to guardian/player |
| Absence monitoring | AttendanceRuleService::processMonthlyAbsenceReport | Flag players exceeding threshold |
| Waitlist auto-offer | WaitlistAutoOfferService::offerNextInLine | Offer spots when group has vacancy |

---

## Appendix: Files to Check Before Modifying SportsActivity

1. `app/Modules/Cashier/bootstrap.php` — Payment request completion → triggers SaEventListenerService
2. `app/Modules/Payments/Services/PaymentService.php` — Direct payment processing used by BookingService
3. `app/Modules/MedicalBoard/` — Approves/rejects player medical documents (permission: medical.board.approve)
4. `app/Modules/Notifications/Services/SmsNotificationService.php` — SMS gateway for all SA notifications
5. `app/Modules/Members/Services/MembershipValidationService.php` — Member status checks for booking wizard
