# MatchCenter Module — Architecture Map

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

---

## 1. Purpose & Responsibilities

The MatchCenter module provides **real-time match tracking and live event entry** for sports matches at the club. It handles:
- Scheduling matches (team vs team, with date/time)
- Starting and ending matches (live status management)
- Live pitch-side event entry (goals, fouls, cards, substitutions, injuries)
- Automatic score tracking (goals increment team scores)
- Match dashboard (live, scheduled, and finished matches)
- Direct score override capability

It is designed as a **standalone live-tracking tool** distinct from Tournaments. While it can optionally link to a tournament match via `tournament_match_id`, it operates independently with its own match records and event timeline.

It does **NOT** manage:
- Tournament bracket logic (Tournaments module)
- Player profiles or eligibility (Players module)
- Facility booking (Reservations module)
- Match statistics aggregation or historical analytics

---

## 2. Directory & File Structure

```
app/Modules/MatchCenter/
├── bootstrap.php                              # Permission registration only (no menu)
├── Routes.php                                 # 9 routes (dashboard, CRUD, live operations)
├── Controllers/
│   └── MatchCenterController.php             # Single controller for all operations
├── Models/
│   ├── LiveMatch.php                         # Match model (status groups for dashboard)
│   └── MatchEvent.php                        # Event model (timeline entries, type labels/icons)
└── Views/
    ├── dashboard.php                         # Live/scheduled/finished match overview
    ├── create.php                            # Schedule a new match
    ├── show.php                              # Match detail with event timeline
    └── live_entry.php                        # Pitch-side live event entry interface
```

---

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

### 3.1 `live_matches` Table

| Column | Type | Nullable | Key | Default | Notes |
|--------|------|----------|-----|---------|-------|
| id | bigint unsigned | NO | PRI | auto_increment | |
| facility_id | bigint unsigned | YES | | NULL | FK to facilities |
| discipline_id | bigint unsigned | YES | | NULL | FK to sport_disciplines |
| tournament_match_id | bigint unsigned | YES | | NULL | FK to tournament_matches (optional link) |
| match_title_ar | varchar(200) | NO | | | Arabic match title |
| team_a_name | varchar(100) | NO | | | First team name |
| team_b_name | varchar(100) | NO | | | Second team name |
| team_a_logo_path | varchar(500) | YES | | NULL | Logo file path |
| team_b_logo_path | varchar(500) | YES | | NULL | Logo file path |
| score_a | int | NO | | 0 | Team A current score |
| score_b | int | NO | | 0 | Team B current score |
| match_date | date | NO | MUL | | Scheduled date |
| start_time | time | NO | | | Scheduled start time |
| status | enum('scheduled','live','finished','cancelled') | NO | MUL | scheduled | Match lifecycle status |
| started_at | datetime | YES | | NULL | Actual start time |
| finished_at | datetime | YES | | NULL | Actual end time |
| referee_name | varchar(100) | YES | | NULL | |
| entry_employee_id | bigint unsigned | YES | | NULL | FK to employees (who created the match) |
| notes | text | YES | | NULL | Internal notes |
| created_at | timestamp | YES | | CURRENT_TIMESTAMP | |
| updated_at | timestamp | YES | | CURRENT_TIMESTAMP | on update |

**Status lifecycle:** `scheduled` -> `live` -> `finished` (or `cancelled`)

### 3.2 `match_events` Table

| Column | Type | Nullable | Key | Default | Notes |
|--------|------|----------|-----|---------|-------|
| id | bigint unsigned | NO | PRI | auto_increment | |
| match_id | bigint unsigned | NO | MUL | | FK to live_matches |
| minute | int unsigned | NO | | 0 | Match minute when event occurred |
| event_type | enum(...) | NO | | | goal/foul/yellow_card/red_card/substitution/penalty/injury/half_time/full_time/other |
| team | enum('a','b') | YES | | NULL | Which team the event belongs to (null for neutral events) |
| player_name | varchar(100) | YES | | NULL | Free-text player name |
| description_ar | text | YES | | NULL | Arabic event description |
| created_at | timestamp | YES | | CURRENT_TIMESTAMP | |

### 3.3 Related Tables (owned by other modules)

| Table | Owner Module | Relationship |
|-------|--------------|--------------|
| facilities | SportsActivity | facility_id FK on live_matches |
| sport_disciplines | SportsActivity | discipline_id FK on live_matches |
| tournament_matches | Tournaments | tournament_match_id FK (optional link to bracket match) |
| employees | HR | entry_employee_id FK |

---

## 4. Match Status Lifecycle

```
scheduled --> live --> finished
    \                    /
     \--> cancelled <---/  (manual, any state)
```

| Transition | Guard | Action |
|-----------|-------|--------|
| scheduled -> live | `status === 'scheduled'` | Sets `started_at = now()` |
| live -> finished | `status === 'live'` | Sets `finished_at = now()` |

---

## 5. Routes

| Method | Path | Handler | Permission |
|--------|------|---------|------------|
| GET | `/match-center` | MatchCenterController@dashboard | match_center.view |
| GET | `/match-center/create` | MatchCenterController@create | match_center.manage |
| POST | `/match-center` | MatchCenterController@store | match_center.manage |
| GET | `/match-center/{id}` | MatchCenterController@show | match_center.view |
| POST | `/match-center/{id}/start` | MatchCenterController@startMatch | match_center.live_entry |
| POST | `/match-center/{id}/end` | MatchCenterController@endMatch | match_center.live_entry |
| GET | `/match-center/{id}/live` | MatchCenterController@liveEntry | match_center.live_entry |
| POST | `/match-center/{id}/event` | MatchCenterController@addEvent | match_center.live_entry |
| POST | `/match-center/{id}/score` | MatchCenterController@updateScore | match_center.live_entry |

---

## 6. Events Dispatched

None. The MatchCenter module does **not dispatch any events** via EventBus. It is a self-contained operational tool.

---

## 7. Events Consumed

None. The MatchCenter module does **not listen to any events**.

---

## 8. Cross-Module Dependencies

### 8.1 MatchCenter IMPORTS FROM:

| Module/Table | What it uses |
|--------------|-------------|
| Tournaments | `tournament_match_id` FK — optional link to a tournament bracket match |
| SportsActivity | `facilities` and `sport_disciplines` — stored as FKs but not queried in controller logic |
| HR/Auth | Current employee for `entry_employee_id` |

### 8.2 Other modules that IMPORT FROM MatchCenter:

None identified. No other module references `live_matches` or `match_events` tables. The MatchCenter is a **leaf module** with no downstream consumers.

---

## 9. Permissions

| Key | Description |
|-----|-------------|
| match_center.view | View dashboard and match details |
| match_center.manage | Schedule new matches |
| match_center.live_entry | Start/end matches, add events, update scores |

**Note:** No sidebar menu is registered — accessed via direct URL or links from other modules.

---

## 10. Event Types

| Code | Arabic Label | Icon | Color | Auto-Score |
|------|-------------|------|-------|-----------|
| goal | هدف | circle-dot | #10B981 | Yes (+1 to team) |
| foul | خطأ | alert-triangle | #F59E0B | No |
| yellow_card | بطاقة صفراء | square | #EAB308 | No |
| red_card | بطاقة حمراء | square | #DC2626 | No |
| substitution | تبديل | repeat | #6366F1 | No |
| penalty | ركلة جزاء | target | #8B5CF6 | No |
| injury | إصابة | heart-pulse | #EF4444 | No |
| half_time | نهاية الشوط الأول | pause | #6B7280 | No |
| full_time | نهاية المباراة | flag-checkered | #6B7280 | No |
| other | أخرى | info | #9CA3AF | No |

**Auto-score behavior:** When a `goal` event is added with a team value ('a' or 'b'), the corresponding `score_a` or `score_b` is automatically incremented via direct SQL update.

---

## 11. Key Business Flows

### 11.1 Schedule a Match
```
1. Validate: title, team names, date, time required
2. Create live_matches record (status='scheduled', scores=0/0)
3. Record entry_employee_id from current session
4. Redirect to match detail
```

### 11.2 Start Match
```
1. Verify status === 'scheduled'
2. Update status to 'live', set started_at = now()
3. Redirect to match detail
```

### 11.3 Live Event Entry
```
1. Select event type from predefined enum
2. Enter minute, team (optional), player name (optional), description
3. Validate event_type against allowed list
4. Insert into match_events
5. If event_type === 'goal' AND team is 'a' or 'b':
   - Direct SQL: UPDATE live_matches SET score_{team} = score_{team} + 1
6. Redirect back to live entry view
```

### 11.4 End Match
```
1. Verify status === 'live'
2. Update status to 'finished', set finished_at = now()
3. Redirect to match detail
```

### 11.5 Direct Score Update
```
1. Accept score_a and score_b values (non-negative integers)
2. Overwrite both scores directly on match record
3. This can be used to correct auto-accumulated goal scores
```

---

## 12. Dashboard Grouping

The dashboard (`getDashboardMatches()`) shows matches in three groups:
1. **Live** (`status = 'live'`): ordered by started_at DESC (most recent first)
2. **Scheduled** (`status = 'scheduled'`): ordered by match_date ASC, start_time ASC — limit 20
3. **Finished** (`status = 'finished'`): ordered by finished_at DESC — limit 20

---

## 13. High-Risk Areas

### 13.1 Goal Score Auto-Increment is Not Atomic
- Adding a goal event and incrementing the score are separate operations (INSERT + UPDATE)
- No transaction wrapping
- If the score update fails, the event exists but score is wrong
- If the event insert fails after score update (unlikely order), score is inflated

### 13.2 No Score Decrement on Event Deletion
- There is no route to delete or edit events
- If a goal event is incorrectly added, the score stays incremented
- The only fix is using the direct score override (`/score` endpoint)
- **Impact:** Accidental goals can only be corrected manually

### 13.3 No Match Status Validation for Events
- Events can be added regardless of match status (no check that status='live')
- A goal could theoretically be added to a 'scheduled' or 'finished' match
- **Impact:** Score could change on finished matches

### 13.4 Player Names are Free Text
- `player_name` in match_events is a free-form string, not an FK to players
- No validation against actual team rosters
- Typos and inconsistencies in player names will exist

### 13.5 Tournament Integration is One-Way
- `tournament_match_id` links a live match to a tournament bracket match
- However, MatchCenter does NOT update tournament_matches scores/status
- Tournament results must be separately recorded in the Tournaments module
- **Impact:** Scores in MatchCenter and Tournaments can diverge

---

## 14. Known Patterns & Gotchas

1. **No sidebar menu**: Module has no MenuRegistry entry — must be accessed via direct URL
2. **No soft delete**: `live_matches` has no `is_archived` column
3. **Status is ENUM**: Unlike most modules that use varchar for status, `live_matches.status` and `match_events.event_type` are MySQL ENUMs — adding new types requires a migration
4. **Team logos**: `team_a_logo_path` and `team_b_logo_path` columns exist but the create form does not populate them
5. **No edit route**: There is no route to edit a match after creation (only start/end/events/score)
6. **Event ordering**: Events are sorted by `minute ASC, created_at ASC` — multiple events at the same minute maintain insertion order
7. **Discipline and facility stored but not used**: The create form does not ask for `discipline_id` or `facility_id`, yet the model lists them as fillable
8. **Penalty event vs penalty goal**: A "penalty" event does NOT auto-increment score — only "goal" does. A successful penalty should be recorded as a "goal" event
