# Branches Module — Architecture Map

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

---

## 1. Purpose & Responsibilities

The Branches module manages the **multi-branch structure** of the club/facility. It is a foundational lookup module that:
- Provides CRUD for branch records (create, view, edit)
- Serves as a foreign key target for members, employees, pricing, accounting, and many other modules
- Displays branch statistics (employee count, member count)
- Registers the "General Settings" sidebar menu (which also houses Audit Log, Alerts, Appearance, Branding)
- Registers permissions for settings and audit access

It does **NOT** directly manage:
- Branch-specific pricing (Pricing module owns `pricing_configs` with `branch_id`)
- Branch-specific business rules (Rules module owns `business_rules` with `branch_id`)
- Employee assignment to branches (HR module manages this)
- Member assignment to branches (Members module manages this via `branch_id`)

---

## 2. Directory & File Structure

```
app/Modules/Branches/
├── bootstrap.php                    # Menu ("General Settings") + permissions (settings + audit)
├── Routes.php                       # 6 web routes
├── Controllers/
│   └── BranchController.php        # Standard CRUD (index, create, store, show, edit, update)
├── Models/
│   └── Branch.php                  # Model with allActive(), getByCode(), member/employee counts
└── Views/
    ├── index.php                   # Branch list with employee count
    ├── create.php                  # New branch form
    ├── edit.php                    # Edit branch form
    └── show.php                    # Branch detail with statistics
```

---

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

### 3.1 `branches` Table (4 rows)

| Column | Type | Nullable | Key | Notes |
|--------|------|----------|-----|-------|
| id | bigint unsigned | NO | PRI | auto_increment |
| branch_code | varchar(50) | NO | UNI | Unique code identifier |
| name_ar | varchar(200) | NO | | Arabic branch name |
| name_en | varchar(200) | YES | | English branch name |
| address | text | YES | | Physical address |
| phone | varchar(20) | YES | | Branch phone |
| manager_name | varchar(200) | YES | | Branch manager name |
| is_active | tinyint(1) | NO | MUL | Default: 1 |
| member_count_cache | int unsigned | NO | | Default: 0 (cache, computed on demand) |
| created_at | timestamp | NO | | |
| updated_at | timestamp | NO | | |
| created_by | bigint unsigned | YES | | FK to employees |
| updated_by | bigint unsigned | YES | | FK to employees |

**Current data:**
| ID | Code | Name |
|----|------|------|
| 1 | sheraton | فرع الشيراتون / مصر الجديدة |
| 2 | sadis | فرع السادس من اكتوبر |
| 3 | new_capital | فرع العاصمة الإدارية الجديدة |
| 4 | x-003 | فرع التجمع |

---

## 4. Routes (6)

| Method | Path | Handler | Permission |
|--------|------|---------|------------|
| GET | /branches | BranchController@index | settings.view |
| GET | /branches/create | BranchController@create | settings.edit |
| POST | /branches | BranchController@store | settings.edit |
| GET | /branches/{id} | BranchController@show | settings.view |
| GET | /branches/{id}/edit | BranchController@edit | settings.edit |
| POST | /branches/{id} | BranchController@update | settings.edit |

---

## 5. Events Dispatched

| Event | Dispatched From | Notes |
|-------|-----------------|-------|
| branch.created | Model (via `$dispatchEvents = true`) | Auto-dispatched by Model::create() |
| branch.updated | Model (via `$dispatchEvents = true`) | Auto-dispatched by Model::save()/update() |

---

## 6. Events Consumed

None.

---

## 7. Cross-Module Dependencies

### 7.1 Branches module IMPORTS FROM:

| Module | What it uses |
|--------|-------------|
| Core | Model, Controller, App, Request, Response |

No external module dependencies.

### 7.2 Other modules that IMPORT FROM Branches (branch_id FK or Branch model):

This is one of the most widely-referenced tables in the system:

| Module | How it uses branches |
|--------|---------------------|
| **Members** | `members.branch_id` FK — every member belongs to a branch |
| **Accounting** | `journal_entries.branch_id`, `cost_centers.branch_id`, `daily_transactions.branch_id`, `account_balances.branch_id`, etc. — full multi-branch accounting |
| **Cashier** | `payment_requests.branch_id` — payment processing per branch |
| **HR** | `employees.branch_id`, `hr_departments.branch_id` — employee/department assignment |
| **Pricing** | `pricing_configs.branch_id` — branch-specific membership pricing |
| **Rules** | `business_rules.branch_id` — branch-overridable business rules |
| **Inventory** | `warehouses.branch_id` — warehouse assignment |
| **Carnets** | Branch lookup for carnet issuance |
| **Death** | Branch context for death case processing |
| **Divorce** | Branch context for divorce/transfer processing |
| **Foreign** | Foreign member branch assignment |
| **Children** | Fee calculation uses branch_id for pricing lookup |
| **Dashboard** | Branch filter on all dashboard widgets |
| **Members (API)** | Branch filter on API member listing |
| **Members (Reports)** | Branch filter on all reports |
| **Members (Retroactive)** | Branch selection in retroactive wizard |
| **Members (Search)** | Branch filter in search |
| **Members (Billing)** | Branch for pricing config lookup |
| **Members (NumberGen)** | Branch-specific numbering format |
| **Payments** | Payment recording per branch |

### 7.3 Branch::allActive() consumers:

Many controllers call `Branch::allActive()` to populate branch dropdowns in forms:
- Member create/edit forms
- Employee create/edit forms
- Journal entry forms
- Cost center forms
- Report filters
- Dashboard filters

---

## 8. Permissions

| Key | Description |
|-----|-------------|
| settings.view | View settings and branch list |
| settings.edit | Create/edit branches and settings |
| settings.backup | Backup operations |
| report.view_audit | View audit log (registered here, used by Audit module) |

---

## 9. Menu Registration

The Branches module registers the **"General Settings"** parent menu, which includes sub-items for:
1. Branches (`/branches`) — permission: `settings.view`
2. Settings (`/settings`) — permission: `settings.view`
3. Appearance (`/settings/appearance`) — permission: `settings.edit`
4. Branding (`/settings/branding`) — permission: `settings.edit`
5. Audit Log (`/audit`) — permission: `report.view_audit`
6. Alerts (`/alerts`) — permission: `alert.view`

This makes Branches the "infrastructure" module that owns the settings sidebar section.

---

## 10. Background Processes (Cron)

None.

---

## 11. High-Risk Areas

### 11.1 Wide FK Dependency (CRITICAL)
- `branch_id` is referenced by 15+ tables across the system
- Deactivating or deleting a branch without checking dependents could break queries
- No cascade delete — only `is_active` flag (soft deactivation)
- The `BranchController::update()` allows setting `is_active = 0` but does NOT check if active members/employees exist

### 11.2 member_count_cache is Never Updated
- The `member_count_cache` column exists (default 0) but is never written to by any code
- `Branch::getMemberCount()` always queries the members table directly
- The cache field is listed in `$fillable` but no code populates it

### 11.3 No Deletion Route
- There is no DELETE route or archive functionality
- Branches cannot be removed once created — only deactivated via `is_active = 0`
- This is correct behavior (referential integrity) but means test/junk branches persist forever

### 11.4 branch_code Immutability
- `branch_code` can only be set at creation (store) — the update method does not include it
- This is correct (codes are referenced in configs/rules) but not explicitly enforced at the model level

### 11.5 No Branch-Level Access Control
- Any user with `settings.view` can see ALL branches
- Any user with `settings.edit` can modify ANY branch
- No concept of "branch admin" who can only manage their own branch
- Data filtering by branch is done at the consuming module level (e.g., members WHERE branch_id = X)

---

## 12. Model Capabilities

```php
Branch extends Model {
    $table = 'branches'
    $timestamps = true
    $softDelete = false        // No is_archived column
    $dispatchEvents = true     // Events fired on create/update
    $fillable = ['branch_code', 'name_ar', 'name_en', 'address', 'phone', 
                 'manager_name', 'is_active', 'member_count_cache']

    allActive(): array         // All branches WHERE is_active = 1
    getByCode(string): ?Branch // Lookup by branch_code
    getMemberCount(): int      // Live COUNT from members table
    getEmployeeCount(): int    // Live COUNT from employees table
}
```

---

## 13. Known Patterns & Gotchas

1. **No soft delete**: Unlike most modules, Branch uses `is_active` flag instead of `is_archived`. The Model has `$softDelete = false`.
2. **Settings menu ownership**: Despite being called "Branches", this module owns the entire "General Settings" sidebar section including audit, alerts, appearance, and branding links.
3. **Validation on create**: branch_code uniqueness is checked manually (not via a unique constraint violation handler).
4. **getMemberCount() fallback**: If the members table doesn't exist (early migration state), returns `member_count_cache` value as fallback.
5. **Index shows employee count**: The index query includes a subquery to count active employees per branch.
6. **No branch switching**: There is no concept of "current branch" in the session — branch selection is per-entity (member, employee, transaction).
7. **branch_code format**: No strict format enforced — existing codes are lowercase strings (sheraton, sadis, new_capital, x-003).
