# Settings Module — Architecture Map

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

---

## 1. Purpose & Responsibilities

The Settings module is the **system-wide configuration store** for the ERP. It manages:
- Key-value configuration storage (the `system_config` table)
- Grouped settings UI (view/edit settings by group)
- Appearance/theme customization (brand color, accent color, sidebar, fonts, density, animations)
- Branding management (club logo, name, subtitle, carnet design, receipt design)
- CSS variable override generation for theming
- Smoke test tool (system health check endpoint)

It does **NOT** directly manage:
- Business rules (Rules module — different table, different purpose)
- Service pricing (ServiceCatalog module)
- User preferences (no per-user settings — all settings are system-wide)
- Branch configuration (Branches module)

---

## 2. Directory & File Structure

```
app/Modules/Settings/
├── bootstrap.php                      # Empty — menu/permissions registered in Branches/bootstrap.php
├── Routes.php                         # Web routes (10 routes)
├── Controllers/
│   ├── SettingsController.php        # Generic group-based settings CRUD
│   ├── AppearanceController.php      # Theme/appearance customization + CSS generation
│   ├── BrandingController.php        # Logo, club name, carnet design, receipt design
│   └── SmokeTestController.php       # System health check (delegates to tools/smoke_test.php)
├── Models/
│   └── SystemConfig.php              # system_config model (getAllGrouped, get, set, getByGroup)
├── Services/
│   └── BrandingService.php           # Cached branding data access (logo, names, designs)
└── Views/
    ├── index.php                     # Settings groups overview
    ├── edit-group.php                # Edit all settings in a group
    ├── appearance.php                # Appearance customization UI
    └── branding.php                  # Branding management UI
```

---

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

### 3.1 `system_config` Table (77 rows)

| Column | Type | Nullable | Key | Notes |
|--------|------|----------|-----|-------|
| id | bigint unsigned | NO | PRI | auto_increment |
| config_key | varchar(255) | NO | UNI | Unique key (dot-notation: group.name) |
| config_value | mediumtext | YES | | Value (string, number, or JSON) |
| config_type | enum('string','integer','float','boolean','json') | NO | | Default: 'string' |
| group_name | varchar(100) | NO | MUL | Default: 'general' |
| description_ar | varchar(500) | YES | | Arabic description |
| description_en | varchar(500) | YES | | English description |
| is_editable | tinyint(1) | NO | | Default: 1 |
| created_at | timestamp | NO | | |
| updated_at | timestamp | NO | | On update cascade |

**Group distribution (77 total rows):**

| Group | Count | Purpose |
|-------|-------|---------|
| appearance | 10 | Theme/visual settings |
| branding | 7 | Logo, club name, carnet/receipt designs |
| general | 5 | Sports activity fees and temp card days |
| hr | 36 | HR module configuration (tax, insurance, leave, overtime, discipline, payroll) |
| membership | 1 | Form start number |
| procurement | 5 | Procurement workflow settings |
| seasonal | 5 | Seasonal membership discounts |
| sports_pricing | 8 | Sports facility pricing (JSON configs) |

---

## 4. Routes (10 endpoints)

| Method | Path | Handler | Permission |
|--------|------|---------|------------|
| GET | /settings | SettingsController@index | settings.view |
| GET | /settings/group/{group} | SettingsController@editGroup | settings.edit |
| POST | /settings/group/{group} | SettingsController@updateGroup | settings.edit |
| GET | /settings/appearance | AppearanceController@index | settings.edit |
| POST | /settings/appearance | AppearanceController@update | settings.edit |
| GET | /settings/branding | BrandingController@index | settings.edit |
| POST | /settings/branding/logo | BrandingController@updateLogo | settings.edit |
| POST | /settings/branding/carnet | BrandingController@updateCarnetDesign | settings.edit |
| POST | /settings/branding/receipt | BrandingController@updateReceiptDesign | settings.edit |
| GET | /tools/smoke-test | SmokeTestController@run | settings.edit |

---

## 5. SystemConfig Model — API Methods

### 5.1 `get(key)`
- Returns full row array (`{config_key, config_value, config_type, ...}`) or null
- Used system-wide for reading configuration values

### 5.2 `set(key, value)`
- Upsert pattern: updates if key exists, inserts if not
- Auto-derives `group_name` from dot-notation key (first segment)
- New keys default to: config_type='string', is_editable=1

### 5.3 `getAllGrouped()`
- Returns all config rows grouped by group_name
- Used by the settings index page

### 5.4 `getByGroup(group)`
- Returns all config rows for a specific group
- Used by the edit-group page

### 5.5 `getGroups()`
- Returns distinct group names

---

## 6. Appearance System

### 6.1 Theme Variables

| Setting Key | Default | Options |
|-------------|---------|---------|
| appearance.brand_color | #0D7377 | Hex color (8 presets available) |
| appearance.accent_color | #6366f1 | Hex color |
| appearance.sidebar_style | dark | dark / light / gradient |
| appearance.sidebar_width | normal | narrow (240px) / normal (270px) / wide (300px) |
| appearance.card_radius | rounded | sharp (4px) / rounded (10px) / pill (18px) |
| appearance.font_size | normal | compact (13px) / normal (14px) / large (15px) |
| appearance.animation | full | none / subtle / full |
| appearance.header_style | clean | clean |
| appearance.table_style | striped | striped / bordered / minimal |
| appearance.density | comfortable | compact / comfortable / spacious |

### 6.2 CSS Generation (`AppearanceController::getCssOverrides()`)
- Called from `app/Shared/Layout/main.php` on every page load
- Generates `:root` CSS custom properties based on current settings
- Handles sidebar style variants (light, gradient)
- Handles density adjustments (padding, margins)
- Handles animation disabling
- Returns raw CSS string injected into `<style>` tag

### 6.3 Color Presets
- 8 named color presets: Teal Classic, Royal Blue, Elegant Purple, Emerald Green, Modern Pink, Warm Orange, Modern Gray, Luxury Gold

---

## 7. Branding System

### 7.1 BrandingService (cached per-request)

| Method | Returns | Fallback |
|--------|---------|----------|
| logo() | Base64 data URI or file path | null |
| clubNameAr() | Arabic club name | 'نادي النادي شيراتون' |
| clubNameEn() | English club name | 'THE CLUB Sheraton' |
| subtitle() | Club subtitle | 'SPORTS CITY' |
| carnetDesign() | Carnet design config array | Defaults merged |
| receiptDesign() | Receipt design config array | Defaults merged |

### 7.2 Logo Storage
- Stored as base64 data URI in `system_config` (survives Docker rebuilds)
- Fallback: legacy file path for backward compatibility
- Max 2MB, allowed types: PNG, JPG, SVG, WebP
- Can be removed (set to empty string)

### 7.3 Carnet Design Configuration
| Key | Type | Default |
|-----|------|---------|
| front_bg_color | color | #0D7377 |
| front_text_color | color | #ffffff |
| front_show_subtitle | bool | true |
| front_show_english_name | bool | true |
| front_show_member_type | bool | true |
| front_show_qr | bool | true |
| front_qr_position | string | bottom-left |
| front_logo_position | string | top-center |
| back_show_branch_strip | bool | true |
| back_strip_color | color | #0D7377 |
| back_show_instructions | bool | true |
| back_instructions_text | string | (Arabic instructions) |
| back_fields | array | [full_name_ar, membership_number, branch_name, issued_at] |

### 7.4 Receipt Design Configuration
| Key | Type | Default |
|-----|------|---------|
| show_logo | bool | true |
| show_watermark | bool | true |
| watermark_text | string | 'نادي النادي شيراتون' |
| watermark_opacity | float | 0.06 |
| header_color | color | #0D7377 |
| show_footer_print_info | bool | true |

---

## 8. Events Dispatched

None — Settings does not dispatch events. Configuration changes take effect immediately on next read.

---

## 9. Events Consumed

None — Settings does not listen for events.

---

## 10. Cross-Module Dependencies

### 10.1 Settings module IMPORTS FROM:

| Module | What it uses |
|--------|-------------|
| (None) | Settings is self-contained — only uses App core (DB) |

### 10.2 Other modules that IMPORT FROM Settings:

| Module | What it imports | Usage |
|--------|----------------|-------|
| App Core (App.php) | system_config table | Boot: loads all config overrides into memory |
| App Core (Config.php) | system_config table | Configuration resolution |
| Shared/Layout (main.php) | AppearanceController::getCssOverrides() | Theme CSS injection |
| Shared/Layout (main.php) | BrandingService::logo(), clubNameAr() | Header branding |
| Shared/Layout (auth.php) | BrandingService | Login page branding |
| Shared/Layout (print.php) | BrandingService | Print layout branding |
| Shared/Components (sidebar.php) | BrandingService | Sidebar logo |
| Carnets | BrandingService::carnetDesign(), logo() | Carnet printing |
| Receipts | BrandingService::receiptDesign(), logo() | Receipt printing |
| SportsActivity | BrandingService | Card printing, registration forms |
| Members | SystemConfig (form_start_number) | Form number sequencing |
| HR | SystemConfig (hr.* group — 36 settings) | Tax, insurance, leave, overtime, discipline, payroll |
| Procurement | SystemConfig (procurement.* group) | Approval workflows, matching tolerances |
| Reservations | SystemConfig (system_config reads) | Facility pricing rules |
| SportsActivity | SystemConfig (general.* group) | Card fees, registration fees |

---

## 11. Permissions

Settings permissions are registered in **Branches/bootstrap.php** (shared menu group):

| Key | Description |
|-----|-------------|
| settings.view | View settings groups |
| settings.edit | Edit settings, appearance, branding |
| settings.backup | Backup functionality |

---

## 12. Menu Structure

Settings appears in the **Branches & Settings** menu group (registered in `Branches/bootstrap.php`):
1. Branches (/branches) — settings.view
2. System Settings (/settings) — settings.view
3. Appearance (/settings/appearance) — settings.edit
4. Branding (/settings/branding) — settings.edit

---

## 13. High-Risk Areas

### 13.1 App Boot Dependency — system_config Loaded at Boot
- `App::boot()` loads ALL system_config values into memory during application startup
- If the system_config table is corrupt or missing, the entire app fails to boot
- Changes via `SystemConfig::set()` update the DB but do NOT update the in-memory boot cache
- Some modules read from boot cache (via `config()` helper), others query DB directly

### 13.2 No Validation on Generic Settings
- `SettingsController@updateGroup` applies NO type-specific validation
- Integer/float/boolean types stored in config_type are not enforced on write
- A user could save "abc" into an integer field
- Only appearance settings have explicit validation (color hex regex)

### 13.3 BrandingService Cache vs SystemConfig::set()
- BrandingService has its own `$cache` array (per-request)
- After updating branding, `BrandingService::clearCache()` must be called
- If forgotten, stale branding data persists for the rest of the request
- BrandingController correctly calls clearCache() after updates

### 13.4 CSS Override Performance
- `AppearanceController::getCssOverrides()` is called on EVERY page load (from Layout/main.php)
- Queries system_config table for 10 appearance keys each time
- No caching between requests — hits DB on every page
- Generated CSS is inline (not a cached file) — adds to page weight

### 13.5 Logo as Base64 in Database
- Logo stored as base64 data URI in mediumtext column
- A 2MB image becomes ~2.7MB base64 string stored in DB
- Loaded from DB on every page that shows the logo (sidebar, prints)
- BrandingService per-request cache mitigates repeated queries within same request

### 13.6 HR Configuration Sprawl
- 36 of 77 config rows belong to the HR group
- These include complex JSON (tax brackets), calculation multipliers, date ranges
- Changes to HR config immediately affect payroll calculations
- No version history for config changes (unlike business_rules which has rule_versions)

### 13.7 Smoke Test Endpoint
- `/tools/smoke-test` delegates to `tools/smoke_test.php` via require + output buffering
- Protected only by `settings.edit` permission — no additional security
- May expose internal system state (DB connectivity, table counts, etc.)

---

## 14. Known Patterns & Gotchas

1. **Empty bootstrap.php**: Settings module's bootstrap is a no-op — menu/permissions registered by Branches module
2. **Dot notation for keys**: `group.subkey` format — first segment auto-becomes group_name on insert
3. **is_editable flag**: Settings with `is_editable = 0` are skipped in updateGroup() — cannot be changed via UI
4. **mediumtext for values**: Supports large JSON (sports pricing, tax brackets, carnet designs)
5. **No audit trail**: Unlike business_rules (which has rule_versions), system_config has no change history
6. **Mixed ownership**: The system_config table stores data for many modules (HR, procurement, seasonal, sports) — not just Settings module
7. **Base64 logo survives Docker rebuilds**: This is by design — file system is ephemeral in the Docker deployment
8. **Appearance settings are global**: No per-user theme — all users see the same appearance
9. **settings.backup permission exists but**: No backup controller found in Settings module — may be handled elsewhere

---

## 15. Configuration Keys Reference

### General Group (5 keys)
| Key | Type | Description |
|-----|------|-------------|
| sa.card_fee | string | Sports activity card fee |
| sa.form_fee | string | Sports activity form fee |
| sa.registration_fee_member | string | Registration fee (members) |
| sa.registration_fee_nonmember | string | Registration fee (non-members) |
| sa.temp_card_days | string | Temporary card validity days |

### Membership Group (1 key)
| Key | Type | Description |
|-----|------|-------------|
| membership.form_start_number | integer | Starting form number for sequencing |

### Appearance Group (10 keys)
All listed in Section 6.1 above.

### Branding Group (7 keys)
| Key | Type | Description |
|-----|------|-------------|
| branding.club_name_ar | string | Arabic club name |
| branding.club_name_en | string | English club name |
| branding.club_subtitle | string | Club subtitle |
| branding.logo_data | string | Base64 data URI of logo |
| branding.logo_path | string | Legacy file path (deprecated) |
| branding.carnet_design | json | Carnet design configuration |
| branding.receipt_design | json | Receipt design configuration |

### HR Group (36 keys)
Major subcategories:
- `hr.tax.*` — Income tax brackets and exemptions
- `hr.insurance.*` — Social insurance rates and ceilings
- `hr.leave.*` — Annual leave entitlements
- `hr.overtime.*` — Overtime multipliers and night hours
- `hr.eos.*` — End of service calculations
- `hr.discipline.*` — Disciplinary action rules
- `hr.payroll.*` — Payment day, currency
- `hr.probation.*` — Probation period
- `hr.ramadan.*` — Ramadan dates (updated yearly)
- `hr.working_hours.*` — Standard/night/ramadan hours

### Procurement Group (5 keys)
| Key | Type | Description |
|-----|------|-------------|
| procurement.auto_approve_matched_invoices | integer | Auto-approve matched invoices |
| procurement.auto_create_pr_on_reorder | integer | Auto-create PR at reorder point |
| procurement.default_payment_terms_days | integer | Default payment terms |
| procurement.match_tolerance_pct | float | 3-way match tolerance % |
| procurement.require_grn_before_invoice | integer | Require GRN before invoice |

### Seasonal Group (5 keys)
| Key | Type | Description |
|-----|------|-------------|
| SEASONAL_FAMILY_DISCOUNT_COUPLE | json | Couple discount rules |
| SEASONAL_FAMILY_DISCOUNT_FAMILY_5 | json | Family of 5 discount |
| SEASONAL_FAMILY_DISCOUNT_PARENT_CHILD | json | Parent+child discount |
| SEASONAL_NO_QUALIFICATION_DISCOUNT | json | No-qualification discount |
| SEASONAL_VAT_RATE | json | VAT rate for seasonal |

### Sports Pricing Group (8 keys)
| Key | Type | Description |
|-----|------|-------------|
| entry_tickets_non_member | json | Non-member entry ticket prices |
| general_fees | json | General facility fees |
| locker_pricing | json | Locker rental prices |
| pool_institutional | json | Institutional pool pricing |
| sports_pricing_member | json | Member sports pricing |
| sports_pricing_non_member | json | Non-member sports pricing |
| table_rentals | json | Table rental prices |
| track_pricing | json | Running track pricing |

---

## Appendix: Files to Check Before Modifying Settings

1. `app/Core/App.php` — Loads system_config at boot, populates in-memory config
2. `app/Shared/Layout/main.php` — Calls AppearanceController::getCssOverrides() and BrandingService
3. `app/Shared/Layout/auth.php` — Uses BrandingService for login page
4. `app/Shared/Components/sidebar.php` — Uses BrandingService for sidebar logo
5. `app/Modules/Carnets/Views/print.php` — Uses BrandingService for carnet printing
6. `app/Modules/Receipts/Views/print.php` — Uses BrandingService for receipt printing
7. `app/Modules/HR/Services/*.php` — 6+ services read from hr.* config keys
8. `app/Modules/Members/Services/FormNumberGenerator.php` — Reads membership.form_start_number
9. `app/Modules/Members/Services/MemberNumberGenerator.php` — Reads membership config
10. `app/Modules/Branches/bootstrap.php` — Registers Settings permissions and menu
