# Reports Module — Architecture Map

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

---

## 1. Purpose & Responsibilities

The Reports module is the **centralized reporting engine** for the ERP. It provides:

- A report catalog (41 active report definitions across 6 categories)
- Dynamic report generation from SQL with configurable filters (date range, branch, status)
- CSV export with Arabic UTF-8 BOM support
- Print-friendly report view
- Specialized service-based reports for sports/academy (revenue, coach utilization, player retention)

Report categories:
- **membership**: Member register, new memberships, children (all, approaching 18/25), spouses, foreign, seasonal, temporary, potential/rejected, movement
- **financial**: Revenue, outstanding debts, installments, subscriptions (current + overdue), fines, daily cash, voided receipts, group discounts
- **operations**: Carnet prints, transfers, death cases, divorce cases, expired forms, separations, waivers
- **audit**: Employee activity, SMS log
- **sports**: Attendance, enrollment, facility utilization, player totals, by academy/sport, rental revenue, reservations, quarterly, subscription collection/overdue
- **general**: Branch comparison

It does **NOT**:
- Schedule reports automatically (no cron-based report generation)
- Store generated report results (regenerated on every request)
- Provide real-time dashboard metrics (that is the Dashboard module)

---

## 2. Directory & File Structure

```
app/Modules/Reports/
├── bootstrap.php                          # Menu + permission registration (7 permissions)
├── Routes.php                             # 4 routes (index, view, export, print)
├── Controllers/
│   └── ReportController.php              # Report catalog, generation, export, print
├── Models/
│   └── ReportDefinition.php              # Report definitions model (report_definitions table)
├── Services/
│   ├── ReportEngine.php                  # Main engine — 18 hardcoded report generators + generic fallback
│   ├── ReportExporter.php                # CSV generation from report data
│   ├── AcademyRevenueReportService.php   # Academy revenue vs. minimum guarantee analysis
│   ├── CoachUtilizationReportService.php # Coach sessions, hours, players, utilization rate
│   └── PlayerRetentionReportService.php  # Player enrollment churn and retention analysis
└── Views/
    ├── index.php                          # Report catalog grouped by category
    ├── view.php                           # Report results with filters
    ├── print.php                          # Print-optimized layout
    └── builder.php                        # (unused?) Report builder UI
```

---

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

### 3.1 `report_definitions` Table (41 rows)

| Column | Type | Nullable | Key | Default | Notes |
|--------|------|----------|-----|---------|-------|
| id | bigint unsigned | NO | PRI | auto_increment | |
| report_code | varchar(50) | NO | UNI | | Unique code (e.g., RPT_MEMBERSHIP_REGISTER) |
| name_ar | varchar(200) | NO | | | Arabic display name |
| name_en | varchar(200) | YES | | NULL | English name |
| description_ar | text | YES | | NULL | Arabic description |
| category | varchar(50) | NO | MUL | general | membership/financial/operations/audit/sports/general |
| query_definition_json | json | YES | | NULL | (not used by ReportEngine — hardcoded) |
| column_definitions_json | json | YES | | NULL | (not used — columns defined in ReportEngine) |
| default_filters_json | json | YES | | NULL | Default filter values |
| required_permission | varchar(100) | YES | | NULL | Permission override (not enforced in code) |
| is_system | tinyint(1) | NO | | 1 | System report vs. custom |
| is_active | tinyint(1) | NO | MUL | 1 | Active/disabled toggle |
| 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 Active Report Definitions (41 total)

| Category | Reports |
|----------|---------|
| membership (13) | RPT_MEMBERSHIP_REGISTER, RPT_NEW_MEMBERSHIPS, RPT_CHILDREN, RPT_CHILDREN_18, RPT_CHILDREN_25, RPT_SPOUSES, RPT_FOREIGN_MEMBERS, RPT_MEMBER_MOVEMENT, RPT_POTENTIAL_REJECTED, RPT_SEASONAL, RPT_TEMPORARY |
| financial (11) | RPT_REVENUE, RPT_OUTSTANDING, RPT_INSTALLMENTS, RPT_SUBSCRIPTIONS, RPT_OVERDUE_SUBSCRIPTIONS, RPT_FINES, RPT_DAILY_CASH, RPT_VOIDED_RECEIPTS, RPT_GROUP_DISCOUNTS |
| operations (7) | RPT_CARNET_PRINTS, RPT_TRANSFERS, RPT_DEATH, RPT_DIVORCE, RPT_EXPIRED_FORMS, RPT_SEPARATIONS, RPT_WAIVERS |
| audit (2) | RPT_EMPLOYEE_ACTIVITY, RPT_SMS_LOG |
| sports (11) | RPT_ATTENDANCE, RPT_ENROLLMENT_STATUS, RPT_FACILITY_UTILIZATION, RPT_PLAYER_TOTALS, RPT_PLAYERS_BY_ACADEMY, RPT_PLAYERS_BY_SPORT, RPT_RENTAL_REVENUE, RPT_RESERVATIONS, RPT_SPORTS_QUARTERLY, RPT_SUB_COLLECTION, RPT_SUB_OVERDUE |
| general (1) | RPT_BRANCH_COMPARISON |

---

## 4. Routes

| Method | Path | Handler | Middleware | Permission |
|--------|------|---------|------------|------------|
| GET | /reports | ReportController@index | auth | report.view_membership |
| GET | /reports/view/{code} | ReportController@viewReport | auth | report.view_membership |
| GET | /reports/export/{code} | ReportController@export | auth | report.export |
| GET | /reports/print/{code} | ReportController@printReport | auth | report.view_membership |

---

## 5. Events Dispatched

None.

---

## 6. Events Consumed

None.

---

## 7. Cross-Module Dependencies

### 7.1 Reports module IMPORTS FROM (via direct SQL in ReportEngine):

| Module / Table | Reports That Use It |
|----------------|---------------------|
| Members (`members`) | RPT_MEMBERSHIP_REGISTER, RPT_NEW_MEMBERSHIPS, RPT_OUTSTANDING, and as JOIN target for most reports |
| Children (`children`) | RPT_CHILDREN, RPT_CHILDREN_18, RPT_CHILDREN_25 |
| Spouses (`spouses`) | RPT_SPOUSES |
| Temporary (`temporary_members`) | RPT_TEMPORARY |
| Payments (`payments`) | RPT_REVENUE, RPT_DAILY_CASH, AcademyRevenueReportService |
| Receipts (`receipts`) | RPT_DAILY_CASH, RPT_VOIDED_RECEIPTS |
| Installments (`installment_plans`, `installment_schedule`) | RPT_INSTALLMENTS |
| Subscriptions (`subscriptions`) | RPT_SUBSCRIPTIONS, RPT_OVERDUE_SUBSCRIPTIONS, RPT_OUTSTANDING |
| Fines (`fines`) | RPT_FINES, RPT_OUTSTANDING |
| Branches (`branches`) | Filter options, RPT_MEMBERSHIP_REGISTER |
| Transfers (`transfer_requests`) | RPT_TRANSFERS |
| Carnets (`carnets`) | RPT_CARNET_PRINTS |
| Audit (`audit_trail`) | RPT_EMPLOYEE_ACTIVITY |
| Employees (`employees`) | RPT_EMPLOYEE_ACTIVITY, RPT_VOIDED_RECEIPTS, RPT_DAILY_CASH |
| Academies (`academies`, `academy_contracts`) | AcademyRevenueReportService |
| Coaches (`coaches`, `coach_payments`) | CoachUtilizationReportService |
| SportsActivity (`training_sessions`, `session_attendance`, `training_groups`, `training_group_players`) | CoachUtilizationReportService, PlayerRetentionReportService |
| Players (`players`) | CoachUtilizationReportService, PlayerRetentionReportService |

### 7.2 Other modules that IMPORT FROM Reports:

None. No other module imports from the Reports module.

### 7.3 Reports Used by Other Modules (via links)

The Dashboard module links to `/reports/view/RPT_CHILDREN_25` from its alerts section.

---

## 8. Permissions

| Key | Description |
|-----|-------------|
| report.view_membership | View membership reports (also gates report catalog access) |
| report.view_financial | View financial reports |
| report.view_operations | View operations reports |
| report.view_audit | View audit reports (also used by Archive module) |
| report.view_sports | View sports activity reports |
| report.view_sports_financial | View sports financial reports |
| report.export | Export reports to CSV |

**Note**: Routes use only `report.view_membership` and `report.export`. The other permissions are registered but NOT enforced in the controller — all authenticated users with `report.view_membership` can access ALL report categories. The `required_permission` column in `report_definitions` is also not checked.

**Menu placement**: Top-level menu item, order 600.

---

## 9. Background Processes (Cron)

None. Reports are generated on-demand only.

---

## 10. High-Risk Areas

### 10.1 No Per-Report Permission Enforcement

Despite registering 7 granular permissions and having a `required_permission` column in the database, **all reports are accessible to anyone with `report.view_membership`**. Financial reports, audit logs, and sports data are not permission-gated in the controller.

### 10.2 Unimplemented Reports (23 of 41)

The `ReportEngine::generate()` switch statement only handles 18 report codes. The remaining 23 reports fall through to `genericReport()` which returns:
```php
['columns' => [], 'rows' => [], 'total' => 0, 'message' => 'التقرير غير مُعرّف بعد: ' . $code]
```

Unimplemented reports include: RPT_FOREIGN_MEMBERS, RPT_MEMBER_MOVEMENT, RPT_POTENTIAL_REJECTED, RPT_SEASONAL, RPT_DEATH, RPT_DIVORCE, RPT_WAIVERS, RPT_SEPARATIONS, RPT_EXPIRED_FORMS, RPT_GROUP_DISCOUNTS, RPT_SMS_LOG, RPT_BRANCH_COMPARISON, and all 11 sports reports.

### 10.3 No Pagination on Report Results

All report queries use unbounded SELECT. For large datasets:
- `RPT_MEMBERSHIP_REGISTER` returns all members (76+ and growing)
- `RPT_SUBSCRIPTIONS` returns all subscription records (potentially thousands)
- `RPT_DAILY_CASH` is date-bounded (safe)

As data grows, memory exhaustion is likely for membership-wide reports.

### 10.4 Export Response Handling

The `export()` method uses `echo` + `exit` instead of returning a proper Response object:
```php
echo "\xEF\xBB\xBF";
echo $csv;
exit;
```

This bypasses the framework's response lifecycle (no middleware post-processing, no logging).

### 10.5 SQL in Service Layer

All report SQL is hardcoded in `ReportEngine.php` (a 200+ line file). The `query_definition_json` and `column_definitions_json` columns in `report_definitions` are unused — the system does not support dynamic/user-defined reports despite having the schema for it.

### 10.6 Academy/Coach/Retention Services Unreachable

`AcademyRevenueReportService`, `CoachUtilizationReportService`, and `PlayerRetentionReportService` exist but are **never called** from `ReportEngine::generate()` or the controller. They appear to be prepared for future sports report codes but have no integration point.

---

## 11. Known Patterns & Gotchas

1. **Report code convention**: All codes follow `RPT_*` prefix pattern
2. **Column definitions in code, not DB**: Despite `column_definitions_json` in the schema, column headers are defined in `ReportEngine` return arrays
3. **CSV export uses BOM**: The `\xEF\xBB\xBF` byte-order-mark ensures Arabic text renders correctly in Excel
4. **Filter params are uniform**: Every report receives the same 4 filters (`date_from`, `date_to`, `branch_id`, `status`) even if irrelevant to that report
5. **Print view**: Uses same data as view but with a different template (presumably CSS print styles)
6. **builder.php view exists**: A report builder view exists in the Views directory but no route points to it — likely a planned feature
7. **Outstanding report uses HAVING**: `RPT_OUTSTANDING` uses HAVING without GROUP BY in the outer query (relies on MySQL's non-standard behavior)

---

## 12. Specialized Report Services

### AcademyRevenueReportService
- Compares actual academy revenue (from payments where `payable_type = 'academy'`) against minimum guarantee
- Pro-rates guarantee based on months in period
- Includes monthly revenue trend per academy
- Calculates surplus/shortfall

### CoachUtilizationReportService
- Counts completed sessions per coach in a date range
- Calculates total hours from session start/end times
- Counts unique and total players served via session_attendance
- Pulls coach payment data from `coach_payments`
- Computes utilization rate (sessions per working day, excluding Fridays)

### PlayerRetentionReportService
- Calculates player churn rate: dropped / active_at_start
- Tracks new enrollments vs. departures
- Monthly breakdown of enrollment movement
- Per-academy breakdown of retention
- Uses `training_group_players.joined_at` and `left_at` for lifecycle tracking

---

## 13. Configuration Dependencies

None. The Reports module has no system_config, rule engine, or service catalog dependencies.

Reports are controlled entirely by the `report_definitions` table (which reports appear in the catalog) and the `ReportEngine` class (which reports actually generate data).
