# Sports Module — Architecture Map

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

---

## 1. Purpose & Responsibilities

The Sports module manages **sports membership records** for club members. As of 2026-06-11, sports membership is a **primary membership type** selected at creation (not a post-creation conversion). It handles:
- Registering a sports membership detail record for members already typed as 'sports'
- Tracking federation registration, years of service, and competitive level
- Checking conversion eligibility to working membership (minimum years of service rule)
- Calculating conversion fee (percentage of membership value based on qualification)
- Listing all active sports members
- Validating member type before allowing access (must be `membership_type = 'sports'`)

This is a **small, focused module** — NOT the same as SportsActivity (which manages the sports booking/scheduling/academy system). This module manages the sports membership detail record and conversion eligibility.

It does **NOT** handle:
- Sports activities/bookings (SportsActivity module)
- Academies/training (Academies, Training modules)
- Player management (PlayerAffairs)
- Disciplines catalog (Disciplines module)

---

## 2. Directory & File Structure

```
app/Modules/Sports/
├── bootstrap.php                              # Permissions only (no menu, no events)
├── Routes.php                                 # 4 routes
├── Controllers/
│   └── SportsController.php                  # Index, create, store, checkConversion
├── Models/
│   └── SportsMember.php                      # Sports membership model (soft delete)
├── Services/
│   └── SportsConversionCalculator.php        # Eligibility check + fee calculation
└── Views/
    ├── index.php                             # List all active sports members
    └── create.php                            # Register sports membership form
```

---

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

### 3.1 `sports_members` Table (0 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 |
| sport_name | varchar(200) | NO | | | Name of sport practiced |
| federation_name | varchar(200) | YES | | NULL | Federation/governing body |
| federation_registration | varchar(100) | YES | | NULL | Registration number |
| registration_date | date | YES | | NULL | When registered with federation |
| years_of_service | int unsigned | NO | | 0 | Total years in the sport |
| highest_competitive_level | varchar(200) | YES | | NULL | e.g. national, regional |
| is_conversion_eligible | tinyint(1) | NO | | 0 | Computed: years >= min_years |
| conversion_requested | tinyint(1) | NO | | 0 | Has requested conversion |
| conversion_date | date | YES | | NULL | When conversion was processed |
| conversion_fee | decimal(15,2) | YES | | NULL | Fee paid for conversion |
| conversion_receipt | varchar(50) | YES | | NULL | Receipt number |
| status | varchar(50) | NO | MUL | active | active, converted, inactive |
| notes | text | YES | | NULL | |
| is_archived | tinyint(1) | NO | | 0 | Soft delete flag |
| archived_at | timestamp | YES | | NULL | |
| archived_by | bigint unsigned | YES | | NULL | |
| created_at | timestamp | NO | | CURRENT_TIMESTAMP | |
| updated_at | timestamp | NO | | CURRENT_TIMESTAMP | On update |
| created_by | bigint unsigned | YES | | NULL | FK to employees |
| updated_by | bigint unsigned | YES | | NULL | FK to employees |

### 3.2 Related Tables (not owned but used)

| Table | Owner Module | Relationship |
|-------|--------------|--------------|
| members | Members | member_id FK — the club member this record belongs to |
| qualifications | (lookup) | Via members.qualification_id — determines pricing tier |
| sport_disciplines | (lookup) | Referenced in create form for discipline list |

---

## 4. Routes

| Method | Path | Handler | Permission |
|--------|------|---------|------------|
| GET | /sports | SportsController@index | sports.view |
| GET | /members/{memberId}/sports/create | SportsController@create | sports.add |
| POST | /members/{memberId}/sports | SportsController@store | sports.add |
| POST | /members/{memberId}/sports/check-conversion | SportsController@checkConversion | sports.convert |

---

## 5. Events Dispatched

| Event | Payload | Dispatched From |
|-------|---------|-----------------|
| `sports.registered` | `{member_id, sports_id}` | SportsController::store |

**Note:** No module currently listens for `sports.registered` — it is dispatched but has no consumers.

---

## 6. Events Consumed

None. This module does not listen to any events.

---

## 7. Cross-Module Dependencies

### 7.1 Sports IMPORTS FROM:

| Module | What it uses |
|--------|-------------|
| Rules | `RuleEngine::get('SPORTS_MIN_YEARS')` — minimum years for conversion eligibility (default: 8) |
| Rules | `RuleEngine::get('SPORTS_CONVERSION_FEE')` — conversion fee percentage (default: 50%) |
| Pricing | `PricingEngine::getMembershipPrice()` — membership value by branch + qualification |

### 7.2 Other modules that IMPORT FROM Sports:

| Module | What it imports |
|--------|----------------|
| (none) | No other module imports from Sports |

---

## 8. Permissions

| Key | Description |
|-----|-------------|
| sports.view | View list of sports members |
| sports.add | Register a new sports membership |
| sports.convert | Check conversion eligibility (JSON endpoint) |

---

## 9. Core Business Flow: Sports-to-Working Conversion

### 9.1 Registration

```
GET /members/{id}/sports/create
  ↓
Verify: member exists, not archived, no existing sports record
  ↓
Show form: sport name, federation, years_of_service, competitive level
  ↓
POST /members/{id}/sports
  ↓
Validate: sport_name required, years_of_service required
  ↓
Get SPORTS_MIN_YEARS from RuleEngine (default: 8)
  ↓
Create sports_members record with is_conversion_eligible = (years >= min_years)
  ↓
Dispatch sports.registered
  ↓
Flash success message (includes eligibility note if eligible)
```

### 9.2 Conversion Eligibility Check

```
POST /members/{id}/sports/check-conversion
  ↓
SportsConversionCalculator::checkEligibility()
  ↓
Verify: sports record exists, years >= SPORTS_MIN_YEARS
  ↓
Get member's qualification (high/medium/none)
  ↓
PricingEngine::getMembershipPrice(branch, qualification) → membership value
  ↓
Get SPORTS_CONVERSION_FEE percentage (default: 50%)
  ↓
Calculate fee = membership_value * percentage / 100
  ↓
Return JSON: {eligible, years, min_years, new_membership_value, fee_percentage, conversion_fee}
```

### 9.3 Conversion Fee Reference

| Qualification | Membership Value | Conversion Fee (50%) |
|--------------|-----------------|---------------------|
| High (عالى) | 150,000 EGP | 75,000 EGP |
| Medium (متوسط) | 225,000 EGP | 112,500 EGP |
| None (بدون) | 300,000 EGP | 150,000 EGP |

---

## 10. High-Risk Areas

### 10.1 No Conversion Execution Logic
- The module can CHECK eligibility and CALCULATE the fee, but there is no endpoint to actually PROCESS the conversion
- `conversion_requested`, `conversion_date`, `conversion_fee`, `conversion_receipt` columns exist but are never written to
- The actual conversion (changing member status, creating payment request) is not implemented

### 10.2 One Record Per Member Constraint
- Enforced in application code (`getForMember()` returns single record, `create` checks for existing)
- No unique index on `member_id` in the DB — race condition could create duplicates

### 10.3 Static Years of Service
- `years_of_service` is a static integer set at registration time — not computed from `registration_date`
- Will not auto-increment over time; requires manual update to reflect additional years

### 10.4 Rule Engine Dependency
- Both rule keys (`SPORTS_MIN_YEARS`, `SPORTS_CONVERSION_FEE`) are seeded in Phase 5
- If rules are missing, defaults are used (8 years, 50%), but behavior may be unexpected

### 10.5 Table Empty (0 rows)
- Module is deployed but unused in production

---

## 11. Menu Registration

**No sidebar menu registered.** The module is accessed via:
- `/sports` route (index listing)
- Links from member profile pages (`/members/{id}/sports/create`)

Only permissions are registered in `bootstrap.php`.

---

## 12. Configuration Dependencies

| Config Source | Key | Purpose |
|--------------|-----|---------|
| Rules (business_rules table) | SPORTS_MIN_YEARS | Minimum years of service for conversion eligibility |
| Rules (business_rules table) | SPORTS_CONVERSION_FEE | Percentage of membership value as conversion fee |
| Pricing (pricing_configs table) | Branch + qualification | Determines membership value for fee calculation |
