# Roles Module — Architecture Map

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

---

## 1. Purpose & Responsibilities

The Roles module manages the **role-based access control (RBAC) definitions** for the ERP:
- Create and edit roles (named permission groups)
- Assign permissions to roles (bulk sync)
- Role hierarchy support (parent_role_id inheritance)
- Display all registered permissions from PermissionRegistry (grouped by module)
- Provide role data for Users module (assignment to employees)

It does **NOT** manage:
- Assigning roles to employees (Users module)
- Direct employee permission grants/denials (Users module)
- Permission enforcement at runtime (framework PermissionMiddleware)
- Permission audit logging (Users/PermissionAuditService)

---

## 2. Directory & File Structure

```
app/Modules/Roles/
├── bootstrap.php                          # Shared menu + permission registration (with Users module)
├── Routes.php                             # Web routes (5 routes)
├── Controllers/
│   └── RoleController.php                # CRUD: index, create, store, edit, update
├── Models/
│   ├── Role.php                          # Role model with syncPermissions() + allActive()
│   └── RolePermission.php                # Simple model for role_permissions pivot
└── Views/
    ├── index.php                         # Role list with permission/employee counts
    ├── create.php                        # New role form with permission checkboxes
    ├── edit.php                          # Edit role form with permission checkboxes
    └── _form.php                         # Shared form partial for create/edit
```

---

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

### 3.1 `roles` Table (29 rows in production)

| Column | Type | Nullable | Key | Notes |
|--------|------|----------|-----|-------|
| id | bigint unsigned | NO | PRI | auto_increment |
| role_code | varchar(50) | NO | UNI | Unique identifier (e.g., 'super_admin') |
| name_ar | varchar(200) | NO | | Arabic display name |
| name_en | varchar(200) | YES | | English display name |
| description_ar | varchar(500) | YES | | Arabic description |
| description_en | varchar(500) | YES | | English description |
| is_system | tinyint(1) | NO | | Default: 0. System roles cannot be deleted |
| is_active | tinyint(1) | NO | | Default: 1. Inactive roles are ignored |
| parent_role_id | bigint unsigned | YES | MUL | FK to roles (self-ref for inheritance) |
| has_all_branches | tinyint(1) | NO | | Default: 0. Access all branches |
| category | varchar(50) | YES | | Role grouping category |
| level | tinyint unsigned | NO | | Default: 0. Hierarchy level |
| is_template | tinyint(1) | NO | | Default: 0. Template roles for cloning |
| created_at | timestamp | NO | | |
| updated_at | timestamp | NO | | |
| created_by | bigint unsigned | YES | | FK to employees |
| updated_by | bigint unsigned | YES | | FK to employees |

**Active roles in production (29 total):**
- super_admin, board_member, membership_director, membership_officer
- treasury_manager, treasury_officer, cashier_operator, main_cashier, membership_cashier, sports_cashier
- sales_agent, security_officer, report_viewer, auditor
- general_manager, hr_manager, hr_officer
- accountant, sports_coordinator, academy_manager, sports_coach
- facilities_manager, receptionist, gate_guard, it_admin
- department_head, sports_officer, sports_director

### 3.2 `role_permissions` Table

| Column | Type | Nullable | Key | Notes |
|--------|------|----------|-----|-------|
| id | bigint unsigned | NO | PRI | auto_increment |
| role_id | bigint unsigned | NO | MUL | FK to roles |
| permission_key | varchar(100) | NO | MUL | e.g., 'member.view' |
| granted_at | timestamp | NO | | When permission was granted |
| granted_by | bigint unsigned | YES | | FK to employees |

**Note:** `super_admin` role holds the wildcard permission key `*` which grants access to everything.

---

## 4. Role Hierarchy (Inheritance)

The `parent_role_id` column enables role inheritance:
- A child role inherits all permissions from its parent role (recursively)
- Implemented in `Employee::collectParentPermissions()` (Users module)
- Cycle protection: visited array prevents infinite loops
- Parent must be `is_active = 1` for permissions to flow

```
Example:
  hr_manager (parent)
    └── hr_officer (child) — inherits all hr_manager permissions + its own
```

---

## 5. Permission Sync Mechanism

`Role::syncPermissions(array $permissionKeys)`:
1. DELETE all existing role_permissions for this role
2. INSERT new rows for each permission key
3. Records `granted_by` (current employee) and `granted_at`

This is a full replacement (not incremental) — every save overwrites all permissions.

---

## 6. Events Dispatched

None. The Roles module does not dispatch any events.

---

## 7. Events Consumed

None. The Roles module does not listen to any events.

---

## 8. Cross-Module Dependencies

### 8.1 Roles module IMPORTS FROM:

| Module | What it uses |
|--------|-------------|
| Core | PermissionRegistry (getAllGrouped() for form rendering) |

### 8.2 Other modules that IMPORT FROM Roles:

| Module | What it imports |
|--------|----------------|
| Users | Role::allActive() for employee create/edit forms |
| Users | Role model in UserPermissionController for permission preview |
| Auth | (indirect) employee_roles + role_permissions tables read at auth time |

---

## 9. Routes

| Method | Path | Handler | Middleware | Permission |
|--------|------|---------|------------|------------|
| GET | /roles | RoleController@index | auth | role.view |
| GET | /roles/create | RoleController@create | auth | role.create |
| POST | /roles | RoleController@store | auth, csrf | role.create |
| GET | /roles/{id}/edit | RoleController@edit | auth | role.edit |
| POST | /roles/{id} | RoleController@update | auth, csrf | role.edit |

---

## 10. Permissions

Registered in `Roles/bootstrap.php` (shared registration block with Users module):

| Key | Description |
|-----|-------------|
| role.view | View roles list |
| role.create | Create new roles |
| role.edit | Edit existing roles |

---

## 11. Sidebar Menu

Registered as a shared group with Users module (`users_roles`):
- Parent: "المستخدمون والصلاحيات" (Users & Roles)
- Child: "الأدوار" (Roles) → /roles (requires role.view)

---

## 12. High-Risk Areas

### 12.1 Full Permission Sync (DELETE + INSERT)
- `syncPermissions()` deletes ALL role_permissions then re-inserts
- NOT wrapped in a transaction — partial failure could leave role with no permissions
- If browser sends empty permissions array, role loses all permissions silently

### 12.2 Super Admin Wildcard
- The `*` permission key in `role_permissions` grants access to EVERYTHING
- Any role assigned `*` becomes equivalent to super_admin
- No UI guard prevents granting `*` to non-system roles

### 12.3 No Role Deletion
- Roles can only be deactivated (is_active = 0), not deleted
- But there's no deactivation endpoint — only edit can set is_active
- System roles (is_system = 1) should not be editable, but no guard exists in controller

### 12.4 role_code Uniqueness
- Validated on create (check existing), but NOT on edit (role_code is not editable)
- If someone edits the database directly, duplicates could cause issues

### 12.5 Parent Role Infinite Loops
- Circular parent references could cause infinite recursion
- Mitigated by `visited` array in Employee::collectParentPermissions()
- But no validation prevents setting circular parent_role_id in the DB

---

## 13. Known Patterns & Gotchas

1. **Bootstrap is shared with Users**: The Roles `bootstrap.php` is empty (just a comment) — all menu/permission registration is in the `Roles/bootstrap.php` file registered under the group `users_roles`
2. **Permission display**: Forms render ALL permissions from PermissionRegistry grouped by module — this means adding permissions to any module automatically shows them in role edit forms
3. **No soft delete**: The Role model has `$softDelete = false` — it relies on `is_active` flag instead
4. **Index query aggregates**: The index page uses subqueries to count permissions and assigned employees per role
5. **allActive() is used by Users module**: Role::allActive() returns all roles with `is_active = 1`, ordered by name_ar
