# Fines Module — Architecture Map

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

---

## 1. Purpose & Responsibilities

The Fines module manages the **disciplinary system** for club members. It handles:
- Violation reporting (incident documentation by staff)
- Penalty imposition (6 penalty types from warning to termination)
- Fine collection (monetary penalties)
- Member suspension/ban enforcement (status changes)
- Appeal submission and adjudication (with time window)
- Fine waiver (with reason tracking)
- Workflow integration (violation_penalty workflow definition)

It does **NOT** directly manage:
- Subscription late fines (handled by Subscriptions module's OverdueFineApplicator)
- Payment processing (delegates to Payments module's PaymentService)
- Member reinstatement after suspension ends
- HR attendance violations (separate `hr_attendance_violations` table in HR module)

---

## 2. Directory & File Structure

```
app/Modules/Fines/
├── bootstrap.php                              # Permission registration only (menu via Members)
├── Routes.php                                 # 9 web routes
├── Controllers/
│   ├── ViolationController.php               # Violation reporting (create, list)
│   └── FineController.php                    # Penalty imposition, payment, appeal, waive
├── Models/
│   ├── Violation.php                         # Violation model (no soft delete, with events)
│   └── Fine.php                              # Fine/penalty model (no soft delete, with events)
└── Views/
    ├── violations/
    │   ├── index.php                         # Violation list with filters
    │   └── create.php                        # Report new violation form
    ├── fines/
    │   └── index.php                         # Fines/penalties list with filters
    └── appeal.php                            # Appeal submission form
```

---

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

### 3.1 `violations` Table (2 rows as of 2026-06-10)

| Column | Type | Nullable | Key | Default | Notes |
|--------|------|----------|-----|---------|-------|
| id | bigint unsigned | NO | PRI | auto_increment | |
| member_id | bigint unsigned | NO | MUL | | FK to members |
| violation_date | date | NO | MUL | | When violation occurred |
| description | text | NO | | | Free-text description |
| reported_by | bigint unsigned | YES | | | FK to employees |
| evidence_notes | text | YES | | | Supporting evidence |
| status | varchar(50) | NO | MUL | reported | reported/under_review/penalty_imposed/dismissed |
| created_at | timestamp | NO | | CURRENT_TIMESTAMP | |
| updated_at | timestamp | NO | | CURRENT_TIMESTAMP | on update |
| created_by | bigint unsigned | YES | | | FK to employees |
| updated_by | bigint unsigned | YES | | | FK to employees |

**Current data distribution (status):**
- reported: 2

### 3.2 `fines` Table (0 rows as of 2026-06-10)

| Column | Type | Nullable | Key | Default | Notes |
|--------|------|----------|-----|---------|-------|
| id | bigint unsigned | NO | PRI | auto_increment | |
| member_id | bigint unsigned | NO | MUL | | FK to members |
| violation_id | bigint unsigned | YES | MUL | | FK to violations (nullable for non-violation fines) |
| fine_type | varchar(50) | NO | MUL | violation_fine | Type classification |
| amount | decimal(15,2) | NO | | | Monetary penalty (0 for non-financial penalties) |
| penalty_type | varchar(50) | NO | MUL | fine | warning/caution/fine/suspension/ban/termination |
| suspension_from | date | YES | MUL | | Start date (suspension/ban) |
| suspension_to | date | YES | | | End date (suspension/ban) |
| appeal_submitted | tinyint(1) | NO | | 0 | Whether appeal filed |
| appeal_date | date | YES | | | When appeal was filed |
| appeal_notes | text | YES | | | Member's appeal text |
| appeal_decision | varchar(50) | YES | | | upheld/modified/cancelled |
| appeal_decided_by | bigint unsigned | YES | | | FK to employees |
| appeal_decided_at | timestamp | YES | | | When decision was made |
| status | varchar(50) | NO | MUL | imposed | See status lifecycle below |
| paid_amount | decimal(15,2) | NO | | 0.00 | Amount paid toward fine |
| payment_id | bigint unsigned | YES | MUL | | FK to payments |
| paid_at | timestamp | YES | | | When fine was paid |
| notes | text | YES | | | Internal notes |
| created_at | timestamp | NO | | CURRENT_TIMESTAMP | |
| updated_at | timestamp | NO | | CURRENT_TIMESTAMP | on update |
| created_by | bigint unsigned | YES | | | FK to employees |
| updated_by | bigint unsigned | YES | | | FK to employees |

---

## 4. Routes

| Method | Path | Handler | Middleware | Permission |
|--------|------|---------|------------|------------|
| GET | /violations | ViolationController@index | auth | fine.view |
| GET | /violations/create/{memberId} | ViolationController@create | auth | fine.impose |
| POST | /violations/store/{memberId} | ViolationController@store | auth, csrf | fine.impose |
| GET | /fines | FineController@index | auth | fine.view |
| POST | /fines/impose/{violationId} | FineController@impose | auth, csrf | fine.impose |
| POST | /fines/{id}/pay | FineController@pay | auth, csrf | fine.collect |
| POST | /fines/{id}/appeal | FineController@submitAppeal | auth, csrf | fine.view |
| POST | /fines/{id}/appeal-decide | FineController@decideAppeal | auth, csrf | fine.impose |
| POST | /fines/{id}/waive | FineController@waive | auth, csrf | fine.waive |

---

## 5. Core Business Flows

### 5.1 Violation Reporting

```
1. GET /violations/create/{memberId}
   - Show form with member info
   - Fields: description, violation_date, evidence_notes

2. POST /violations/store/{memberId}
   - Validate: description required
   - Create violation: status='reported', reported_by=current employee
   - Create workflow instance (if 'violation_penalty' definition exists)
   - Dispatch: violation.reported event
```

### 5.2 Penalty Imposition

```
1. POST /fines/impose/{violationId}
   - Validate:
     a. penalty_type must be: warning/caution/fine/suspension/ban/termination
     b. If fine: amount between VIOLATION_FINE_MIN and VIOLATION_FINE_MAX (200-10000 EGP)
     c. If suspension/ban: start and end dates required
   - Create fine record with appropriate fields
   - Update violation status to 'penalty_imposed'
   - SIDE EFFECTS on member status:
     * suspension/ban → member status = 'suspended'
     * termination → member status = 'terminated'
   - Create workflow instance for fine
   - Transition violation workflow: 'impose_penalty'
   - Dispatch: fine.imposed event
```

### 5.3 Fine Payment

```
1. POST /fines/{id}/pay
   - Validate:
     a. Fine must have penalty_type = 'fine' (monetary)
     b. Status must not be 'paid'
     c. Check for existing non-voided payment (prevent double payment)
   - Calculate remaining: amount - paid_amount
   - Call PaymentService::processPayment():
     * payment_type = 'fine'
     * related_entity_type = 'fines'
   - Update fine: paid_amount, payment_id, status='paid', paid_at
   - Transition workflow: 'pay'
   - Dispatch: fine.paid event
```

### 5.4 Appeal Process

```
Submit Appeal:
1. POST /fines/{id}/appeal
   - Check appeal window: APPEAL_WINDOW days (default 15) from created_at
   - Require appeal_notes (reason text)
   - Update fine: appeal_submitted=1, appeal_date, appeal_notes, status='appealed'
   - Transition workflow: 'appeal'
   - Dispatch: fine.appealed event

Decide Appeal:
2. POST /fines/{id}/appeal-decide
   - Only for fines with status='appealed'
   - decision must be: upheld/modified/cancelled
   - Status mapping:
     * upheld → 'appeal_upheld' (fine stands)
     * modified → 'appeal_modified' (amount may change)
     * cancelled → 'cancelled' (fine voided)
   - If modified: new_amount replaces original amount
   - Transition workflow: 'uphold_appeal' / 'modify_appeal' / 'cancel_appeal'
```

### 5.5 Fine Waiver

```
1. POST /fines/{id}/waive
   - Require waive_reason
   - Update fine: status='waived', append reason to notes
   - Transition workflow: 'waive'
```

---

## 6. Penalty Types

| Type | Arabic | Effect on Member | Monetary |
|------|--------|------------------|----------|
| warning | لفت نظر | None | No |
| caution | إنذار | None | No |
| fine | غرامة مالية | None (until unpaid blocks services) | Yes (200-10,000 EGP) |
| suspension | إيقاف نشاط | status → 'suspended' | No |
| ban | منع دخول | status → 'suspended' | No |
| termination | إنهاء عضوية | status → 'terminated' | No |

---

## 7. Fine Status Lifecycle

```
imposed → paid                (monetary fine paid)
imposed → appealed            (member submits appeal within window)
imposed → waived              (admin waives the fine)

appealed → appeal_upheld      (appeal rejected, fine stands)
appealed → appeal_modified    (appeal partially accepted, amount changed)
appealed → cancelled          (appeal fully accepted, fine voided)

appeal_upheld → paid          (fine must still be paid after failed appeal)
```

**Violation Status Lifecycle:**
```
reported → under_review → penalty_imposed
reported → under_review → dismissed
```

---

## 8. Events Dispatched

| Event | Payload | Dispatched From |
|-------|---------|-----------------|
| `violation.reported` | `{violation_id, member_id}` | ViolationController::store |
| `fine.imposed` | `{fine_id, member_id, penalty_type, amount}` | FineController::impose |
| `fine.paid` | `{fine_id, member_id, amount}` | FineController::pay |
| `fine.appealed` | `{fine_id, member_id}` | FineController::submitAppeal |

---

## 9. Events Consumed (by other modules)

| Event | Listener Module | Action |
|-------|----------------|--------|
| `fine.imposed` | Accounting | Create AR journal entry for monetary fine |
| `fine.imposed` | Accounting (StatementIntegrationService) | Update member statement |
| `fine.paid` | Accounting | Settle AR, recognize revenue |

---

## 10. Cross-Module Dependencies

### 10.1 Fines module IMPORTS FROM:

| Module | What it uses |
|--------|-------------|
| Payments | PaymentService::processPayment() — fine payment processing |
| Rules | RuleEngine — VIOLATION_FINE_MIN, VIOLATION_FINE_MAX, APPEAL_WINDOW |
| Workflow | WorkflowEngine — violation_penalty workflow, transitions |

### 10.2 Other modules that IMPORT FROM Fines:

| Module | What it imports |
|--------|----------------|
| Carnets | Fine::isSuspended() — blocks carnet issuance for suspended members |
| Carnets | Queries fines table for unpaid fine count |
| Payments | BalanceCalculator queries fines for unpaid totals (blocks services) |
| Members | MembershipRulesService checks unpaid fines count |
| Members | Member show page links to violation creation |
| Members | RetroactiveMembershipService creates retroactive violations+fines |
| Reports | ReportEngine generates fine status reports |
| Accounting | GLSyncService reads fines table for journal sync |
| Archive | ArchiveService includes fines in member archive |
| Audit | AuditService translates 'fines' table name to Arabic |

---

## 11. Permissions

| Key | Description |
|-----|-------------|
| fine.view | View violations and fines lists |
| fine.impose | Report violations and impose penalties |
| fine.collect | Process a fine payment |
| fine.waive | Waive a fine (with reason) |

---

## 12. Background Processes (Cron)

The Fines module has **no dedicated cron jobs**. However:
- Suspension/ban end dates are NOT automatically enforced — no cron job reinstates members when suspension_to passes
- This is a known gap: members remain 'suspended' until manually changed

---

## 13. Configuration Dependencies

| Config Source | Key | Purpose |
|--------------|-----|---------|
| Rules | VIOLATION_FINE_MIN | Minimum fine amount (default 200.00 EGP) |
| Rules | VIOLATION_FINE_MAX | Maximum fine amount (default 10,000.00 EGP) |
| Rules | APPEAL_WINDOW | Days allowed to submit appeal (default 15) |
| Workflow | violation_penalty | Workflow definition for violation/fine lifecycle |

---

## 14. Workflow Integration

The Fines module deeply integrates with the Workflow module:

**Violation workflow transitions:**
- `impose_penalty` — when penalty is imposed on a violation

**Fine workflow transitions:**
- `pay` — when fine is paid
- `appeal` — when appeal is submitted
- `uphold_appeal` — appeal rejected
- `modify_appeal` — appeal partially accepted
- `cancel_appeal` — appeal fully accepted
- `waive` — fine waived by admin

All workflow transitions are wrapped in try/catch — workflow failures are logged but do NOT block the operation.

---

## 15. High-Risk Areas

### 15.1 Member Status Changes (CRITICAL)
- Imposing suspension/ban directly updates member status to 'suspended'
- Imposing termination directly updates member status to 'terminated'
- These are **immediate and irreversible** without manual intervention
- No confirmation step — single POST request changes member status
- Status change is limited to currently 'active' (suspension) or 'active'/'suspended' (termination)

### 15.2 No Automatic Reinstatement
- When `suspension_to` date passes, member remains 'suspended'
- No cron job checks for expired suspensions
- Manual status change required to reactivate
- Risk: members stay suspended indefinitely if staff forgets

### 15.3 Double Payment Prevention
- Fine payment checks for existing non-voided payment on the same fine
- Uses `FOR UPDATE` row lock during payment processing
- But the check happens at payment time — if two users click simultaneously, both may pass the check before either payment is committed

### 15.4 Appeal Window Enforcement
- Window calculated from `created_at` of the fine, not from the violation date
- Uses seconds-based calculation (may be off by timezone)
- If appeal window rule is changed, already-imposed fines are affected retroactively

### 15.5 Fine Amount Validation
- Only validated for `penalty_type = 'fine'`
- Non-financial penalties (warning, caution, etc.) set amount to 0.00
- If penalty_type is changed after creation: no revalidation occurs

### 15.6 Waive Appends to Notes
- Waive reason is appended to existing notes with newline separator
- Original notes may contain sensitive information
- No separate audit trail for the waive action (only notes field)

---

## 16. Known Patterns & Gotchas

1. **No soft delete**: Neither violations nor fines use soft delete — status tracks lifecycle
2. **violation_id is nullable**: Fines can exist without a violation (e.g., subscription-related fines imposed by other modules)
3. **No services layer**: Unlike Subscriptions/Installments, Fines has no service classes — all logic is in controllers
4. **Menu via Members module**: Violations & Fines menu item appears under the Members sidebar group
5. **Suspension uses member status**: Fine::isSuspended() checks active fines with suspension dates, but Carnets also checks member status — dual check
6. **Appeal modifies amount in-place**: If appeal_decision='modified', the original amount is overwritten with new_amount — no history of original
7. **Unpaid fines block services**: BalanceCalculator includes unpaid fines in member debt; Carnets checks for unpaid fines before printing
8. **HR violations are separate**: The HR module has its own `hr_attendance_violations` table — completely unrelated to this module's `violations` table
9. **Retroactive entries bypass workflow**: RetroactiveMembershipService inserts directly into violations/fines tables without triggering workflows or events
10. **fine_type field**: Currently always 'violation_fine' — suggests future types were planned but not implemented

---

## 17. Static Helper Methods (Fine Model)

```php
Fine::getForMember(int $memberId)        // All fines for a member with violation desc
Fine::getUnpaidForMember(int $memberId)  // Sum of unpaid financial fines
Fine::isSuspended(int $memberId)         // Active suspension/ban check (date-aware)
Fine::search(array $filters, ...)        // Paginated search with member join
Fine::getPenaltyTypeLabel(string $type)  // Arabic label for penalty type
```

---

## Appendix: Files to Check Before Modifying Fines

1. `app/Modules/Accounting/bootstrap.php` — Listens to `fine.imposed` and `fine.paid`
2. `app/Modules/Accounting/Services/AccountingIntegrationService.php` — Journal entries for fines
3. `app/Modules/Accounting/Services/GLSyncService.php` — GL sync from fines table
4. `app/Modules/Payments/Services/BalanceCalculator.php` — Queries fines for unpaid totals
5. `app/Modules/Carnets/Services/CarnetPrintService.php` — Checks Fine::isSuspended() and unpaid fines
6. `app/Modules/Members/Services/MembershipRulesService.php` — Checks unpaid fine count
7. `app/Modules/Members/Services/RetroactiveMembershipService.php` — Creates retroactive violations/fines
8. `app/Modules/Members/Views/show.php` — Links to violation creation
9. `app/Modules/Reports/Services/ReportEngine.php` — Fine reports
10. `app/Modules/Workflow/` — violation_penalty workflow definition (if it exists)
