# RULE FILE 11 — Cross-Module Integration Contract

This is the glue. Every module must respect these integration points. If you build a module in isolation and ignore this file, the system breaks.

---

## The Golden Rule of Integration

**Modules communicate through:**
1. Service calls (synchronous, within same transaction)
2. Events + Listeners (asynchronous, after commit)
3. Shared traits (BelongsToAcademy, HasUuid, Auditable)
4. Shared models (Person, User, Organization, Branch)

**Modules NEVER communicate through:**
- Direct DB queries to another module's tables
- Reaching into another module's private methods
- Global state or session manipulation
- File-system signals or temp files

---

## The Complete Data Flow — Every Major User Action

### 1. New Participant Registration (POS/Reception)

```
Actor: Receptionist
Trigger: Walk-in or phone registration

Flow:
1. [PersonService] Create person record (name, phone, national_id, birth_date)
   → Parses national_id → extracts birth_date, gender, governorate
   → Creates people record

2. [ParticipantService] Create participant record (person_id, status='registered')
   → Links to person
   → Sets branch_id from current user's branch

3. [GuardianService] Create guardian (if child) → link via pivot
   → Guardian is also a Person (create person record for guardian too)
   → guardian_participant pivot: is_primary=true, financial_responsibility_percent=100

4. [UserService] Optionally create login for guardian
   → user record with guardian's email + generated password
   → Link: guardian.user_id = new user.id

5. Dispatch: ParticipantRegistered event
   → Listener: send welcome notification to guardian
   → Listener: audit log
```

### 2. Program Enrollment

```
Actor: Receptionist or Admin
Trigger: Participant wants to join a program

Pre-conditions:
- Participant status must be 'registered' or 'active' or 'inactive' (re-enrollment)
- Participant NOT already enrolled in this group (active enrollment)
- Group NOT at max capacity (or goes to waitlist)
- Participant meets age/skill requirements

Flow:
1. [PricingService] Calculate enrollment fee
   → Input: participant, program, branch, today's date
   → Output: PriceResult {original_amount, final_amount, applied_rules}

2. [EnrollmentService] Create enrollment record
   → status = 'pending' (until paid) or 'active' (if fee=0)
   → fee_amount = PriceResult.final_amount
   → start_date = group.start_date or today (whichever is later)
   → end_date = start_date + program.duration_months (or null for ongoing)

3. [InvoiceService] Create invoice for enrollment fee (if fee > 0)
   → billable = participant (or guardian, based on setting)
   → invoice_items: [{description: program_name, quantity: 1, unit_price: fee_amount}]
   → Link: enrollment.invoice_id = new invoice.id

4. [ParticipantService] If participant was 'registered' → change status to 'active'
   → Only on FIRST enrollment activation

5. [TrainingGroupService] Increment group.current_count

6. [AttendanceGenerationService] Generate attendance expectations
   → Find all upcoming sessions for this group
   → Create attendance_records(status='expected') for this participant for each session

7. Dispatch: EnrollmentCreated event
   → Listener: notify guardian (enrollment confirmation)
   → Listener: notify trainer (new student joined)
   → Listener: audit log
```

### 3. POS Sale (Complete Transaction)

```
Actor: Receptionist
Trigger: Items in cart, payment submitted

Flow (all within ONE DB::transaction):
1. [POSService] Validate cart
   → Check POS session is open
   → For each product item: verify stock (if tracked)
   → For each program item: verify enrollment prerequisites
   → For kit items: check all component availability

2. [PricingService] Calculate final prices per item
   → Each item goes through pricing engine individually
   → Coupon applied to subtotal after individual pricing

3. [POSService] Create pos_transaction record
   → Link to pos_session
   → Calculate: subtotal, discount, tax, total

4. [POSService] Create pos_transaction_items
   → One per cart item, with snapshot of price at time of sale

5. [InvoiceService] Create invoice + items (mirrors POS transaction)
   → This is the FINANCIAL record
   → pos_transaction.invoice_id = new invoice.id

6. [PaymentService] Record payment(s)
   → For each payment method used:
     → Create payment record
     → Create double-entry transactions
   → If wallet: [WalletService] deduct balance
   → Update invoice: paid_amount, balance_due, status

7. [InventoryService] Deduct stock (for physical products)
   → For each product with track_inventory=true:
     → createMovement(type='sale', direction='out', quantity)
   → For kit: deduct EACH component
     → createMovement(type='kit_assembly', direction='out') per component

8. [EnrollmentService] If program was purchased:
   → Create enrollment (follow flow #2 above, minus invoice creation since POS already created it)

9. [POSService] Update POS session totals
   → Increment transactions_count, add to total_sales

10. [CashSessionService] If cash payment → update expected_closing

Dispatch (after commit):
- POSTransactionCompleted → listeners handle notifications, low stock checks
- If enrollment: EnrollmentCreated → listeners handle attendance generation
```

### 4. Payment on Existing Invoice

```
Actor: Receptionist or Self-Service (future)
Trigger: Guardian pays outstanding balance

Flow:
1. [PaymentService] Validate
   → invoice.status NOT in ('cancelled', 'draft')
   → amount > 0 AND amount <= invoice.balance_due
   → If wallet: available_balance >= amount

2. [PaymentService] Record payment
   → Create payment record (amount, method, received_by)
   → If wallet: [WalletService] lockForUpdate → deduct → wallet_transaction

3. [TransactionService] Double-entry
   → Debit: Cash/Card/Wallet account
   → Credit: Accounts Receivable

4. [InvoiceService] Update invoice
   → paid_amount += payment.amount
   → balance_due = total_amount - paid_amount
   → If balance_due == 0: status='paid', paid_at=now()
   → Else if paid_amount > 0: status='partially_paid'

5. [CashSessionService] If cash → add to session tracking

Dispatch: PaymentReceived
- Listener: notify guardian (receipt)
- Listener: if invoice now fully paid AND enrollment was pending → activate enrollment
- Listener: audit log
```

### 5. Taking Attendance (Trainer)

```
Actor: Trainer
Trigger: Session time arrives, trainer opens attendance screen

Pre-state: attendance_records already exist (status='expected') — generated by AttendanceGenerationService

Flow:
1. [AttendanceService] Load session's attendance records
   → All records for this session (participants + staff)
   → Show current status (all 'expected' initially)

2. [AttendanceService] For each person marked by trainer:
   → Validate transition: 'expected' can go to any status
   → If 'present': just set status
   → If 'late': calculate late_minutes from check_in time vs session start + grace
   → If 'absent': set status (will trigger notifications)
   → If 'excused': only if excuse already approved (otherwise remains 'absent' until approved)

3. [AttendanceService] After all marked:
   → Update session.actual_participants_count = count of (present + late + partial)
   → If all marked: session.status = 'completed'

4. For each ABSENT participant:
   → Dispatch: ParticipantAbsent event
   → Listener: [NotificationService] notify guardian
   → Listener: [AttendanceService] check consecutive count
     → If consecutive >= threshold: [ParticipantService].changeStatus('suspended')

5. For session completion:
   → Dispatch: AttendanceMarked event (batch)
   → Listener: update stats, check thresholds
```

### 6. Session Cancellation

```
Actor: Admin or Branch Manager
Trigger: Facility issue, trainer unavailable, weather, etc.

Flow:
1. [SessionService] Cancel session
   → Validate: session.status must be 'scheduled' (can't cancel 'completed')
   → Set: status='cancelled', cancelled_at=now(), cancelled_by, cancellation_reason

2. [AttendanceService] Cancel all attendance records for this session
   → All records WHERE attendable_id = session.id → status = 'cancelled'
   → These do NOT count toward absence tracking

3. [SpaceReservationService] Free the reservation
   → reservation.status = 'cancelled'
   → Segments are now available for other bookings

4. Dispatch: SessionCancelled
   → Listener: [NotificationService] notify ALL enrolled participants' guardians + assigned trainers
   → Listener: if makeup policy → create future makeup session option
   → Listener: audit log
```

### 7. Refund Processing

```
Actor: Admin or Accountant
Trigger: Guardian requests refund

Flow:
1. [RefundService] Validate refund request
   → Find original payment
   → requested_amount <= payment.amount - payment.refunded_amount
   → Invoice is not 'cancelled'
   → If amount > refund_approval_threshold setting → require approval step

2. [RefundService] If requires approval:
   → Create refund record with status='pending_approval'
   → Notify academy_owner/admin
   → STOP HERE — flow resumes on approval

3. [RefundService] Process approved refund (or auto-approved if below threshold):
   → Update payment: refunded_amount += refund.amount
   → If fully refunded: payment.status = 'refunded'
   → Else: payment.status = 'partially_refunded'

4. [InvoiceService] Recalculate invoice
   → paid_amount -= refund.amount
   → balance_due = total_amount - paid_amount
   → If was 'paid' → now 'partially_paid' (or 'sent' if fully refunded)

5. [TransactionService] Reversing double-entry
   → Debit: Accounts Receivable (or Refund Expense)
   → Credit: Cash/Card/Wallet (where money goes back)

6. [WalletService] If refund-to-wallet:
   → Deposit refund amount to participant/guardian wallet
   → Create wallet_transaction(type='refund')

7. Dispatch: PaymentRefunded
   → Listener: notify guardian
   → Listener: audit log
```

### 8. Freeze Request

```
Actor: Guardian request → Admin approves (or Admin directly)
Trigger: Participant needs temporary pause

Flow:
1. [ParticipantService] Validate freeze eligibility
   → Participant must be 'active'
   → No overdue balance (or admin override)
   → Freeze count this year < max_freezes_per_year setting
   → Requested duration <= max_freeze_days setting

2. [ParticipantService] Create participant_freeze record
   → status='active', start_date, end_date

3. [ParticipantService] Change status: 'active' → 'frozen'
   → Dispatches: ParticipantStatusChanged

4. [EnrollmentService] If extends_enrollment = true:
   → For each active enrollment: end_date += freeze_duration_days

5. [AttendanceGenerationService] Remove future attendance records during freeze period
   → DELETE WHERE person_id AND date BETWEEN freeze.start_date AND freeze.end_date AND status='expected'

6. [FinancialService] Pause subscription billing during freeze (no new invoices generated for frozen period)

Dispatch: ParticipantStatusChanged → notify guardian, audit log
```

---

## Column Name Consistency Across Modules — THE REFERENCE

When two modules reference the same concept, they MUST use the exact same column name:

| Concept | Column Name | Used In |
|---------|-------------|---------|
| The tenant | `academy_id` | ALL tenant tables |
| The branch | `branch_id` | groups, sessions, cash_sessions, pos_sessions, facilities, warehouses, users |
| The person (any human) | `person_id` | participants, guardians, assignments, attendance_records |
| The participant (enrolled person) | `participant_id` | enrollments, waitlists, evaluations, guardian_participant |
| The group | `training_group_id` | sessions, enrollments, evaluations, attendance (via morph) |
| The program | `training_program_id` | groups, base_prices (via morph) |
| The session | `training_session_id` | space_reservations, attendance (via morph) |
| The invoice | `invoice_id` | payments, enrollments, pos_transactions, payment_plans |
| The facility | `facility_id` | space_layouts, sessions, space_reservations, operating_hours |
| The user who did it | `created_by` | ALL user-created records |
| The user who approved | `approved_by` | refunds, stock_counts, excuses |
| The product | `product_id` | kit_items, inventory_levels, inventory_movements, purchase_order_items |
| The warehouse | `warehouse_id` | inventory_levels, inventory_movements |
| Who is billed | `billable_type` + `billable_id` | invoices (morph to Participant or Guardian) |
| What is assigned to | `assignable_type` + `assignable_id` | assignments (morph) |
| What attendance is for | `attendable_type` + `attendable_id` | attendance_records (morph) |
| What was referenced | `reference_type` + `reference_id` | transactions, movements |
| Target of pricing | `priceable_type` + `priceable_id` | base_prices (morph) |
| Target of pricing rule | `applicable_to_type` + `applicable_to_id` | pricing_rules (morph, nullable) |
| What reservation is for | `reserved_for_type` + `reserved_for_id` | space_reservations (morph) |

**ENFORCEMENT**: Before writing a migration, grep existing migrations for the column name. If you're referencing the same entity, use the EXACT same name.

---

## Denormalized Fields — When Updated & By Whom

| Table.Column | Source of Truth | Updated By | When |
|-------------|----------------|-----------|------|
| `training_groups.current_count` | COUNT of active enrollments | EnrollmentService | On enrollment create/cancel |
| `invoices.paid_amount` | SUM of confirmed payments | PaymentService | On payment record/refund |
| `invoices.balance_due` | total_amount - paid_amount | PaymentService | On payment record/refund |
| `wallets.balance` | SUM of wallet_transactions | WalletService | On deposit/withdrawal |
| `products.total_sold` (on kits) | COUNT of POS items sold | POSService | On sale |
| `suppliers.total_purchases` | SUM of purchase_orders | PurchaseOrderService | On PO completion |
| `pos_sessions.total_sales` | SUM of transaction totals | POSService | On each transaction |
| `pos_sessions.transactions_count` | COUNT of transactions | POSService | On each transaction |
| `inventory_levels.quantity_available` | on_hand - reserved | InventoryService | On any movement |
| `training_sessions.actual_participants_count` | COUNT of present+late+partial | AttendanceService | After attendance taken |
| `promotions.usage_count` | COUNT of coupon_usages | POSService/EnrollmentService | On coupon use |

**RULE**: Denormalized fields are CONVENIENCE for display/queries. The source of truth is ALWAYS the related records. If they ever drift, a reconciliation job fixes the denormalized value — not the source.

---

## Migration Execution Order

Migrations MUST be created in dependency order. A table with a foreign key can only be created AFTER the referenced table exists.

```
Level 0 (no FKs to domain tables):
  001_create_organizations_table
  002_create_users_table (FK: organizations)

Level 1 (FK to level 0):
  003_create_branches_table (FK: organizations, users)
  004_create_roles_table (FK: organizations)
  005_create_permissions_table
  006_create_people_table (FK: organizations, users)
  007_create_activities_table (FK: organizations)
  008_create_financial_accounts_table (FK: organizations)
  009_create_system_settings_table (FK: organizations)

Level 2 (FK to level 1):
  010_create_participants_table (FK: people, branches)
  011_create_guardians_table (FK: people, users)
  012_create_training_programs_table (FK: organizations, activities)
  013_create_facilities_table (FK: organizations, branches)
  014_create_wallets_table (morph: people/guardians)
  015_create_product_categories_table (FK: organizations)
  016_create_suppliers_table (FK: organizations)
  017_create_warehouses_table (FK: organizations, branches)
  018_create_notification_templates_table (FK: organizations)

Level 3 (FK to level 2):
  019_create_guardian_participant_table (FK: guardians, participants)
  020_create_training_groups_table (FK: programs, branches)
  021_create_space_layouts_table (FK: facilities)
  022_create_facility_operating_hours_table (FK: facilities)
  023_create_products_table (FK: organizations, categories)
  024_create_base_prices_table (morph, FK: branches)
  025_create_pricing_rules_table (FK: organizations)
  026_create_promotions_table (FK: organizations)
  027_create_invoices_table (morph: participants/guardians)
  028_create_assignments_table (FK: people, morph)

Level 4 (FK to level 3):
  029_create_enrollments_table (FK: participants, groups, invoices)
  030_create_group_schedules_table (FK: groups, facilities)
  031_create_space_segments_table (FK: space_layouts)
  032_create_training_sessions_table (FK: groups, facilities, people)
  033_create_invoice_items_table (FK: invoices)
  034_create_payments_table (FK: invoices)
  035_create_payment_plans_table (FK: invoices)
  036_create_transactions_table (FK: financial_accounts)
  037_create_wallet_transactions_table (FK: wallets)
  038_create_inventory_levels_table (FK: products, warehouses)
  039_create_purchase_orders_table (FK: suppliers, branches)
  040_create_kits_table (FK: organizations)

Level 5 (FK to level 4):
  041_create_space_reservations_table (FK: facilities, space_layouts, sessions)
  042_create_attendance_records_table (FK: people, assignments, morph: sessions)
  043_create_installments_table (FK: payment_plans, payments)
  044_create_purchase_order_items_table (FK: purchase_orders, products)
  045_create_inventory_movements_table (FK: products, warehouses, morph)
  046_create_kit_items_table (FK: kits, products)
  047_create_pos_sessions_table (FK: branches, users)
  048_create_waitlists_table (FK: participants, groups)
  049_create_evaluations_table (FK: participants, groups, people)
  050_create_participant_freezes_table (FK: participants)
  051_create_holidays_table (FK: organizations)

Level 6 (FK to level 5):
  052_create_pos_transactions_table (FK: pos_sessions, participants, invoices)
  053_create_pos_transaction_items_table (FK: pos_transactions)
  054_create_evaluation_scores_table (FK: evaluations)
  055_create_stock_counts_table (FK: warehouses)
  056_create_stock_count_items_table (FK: stock_counts, products)
  057_create_notification_logs_table (FK: users)
  058_create_audit_logs_table (FK: users)
  059_create_notification_preferences_table (FK: users)
  060_create_coupon_usages_table (FK: promotions, participants)

Final:
  061_create_indexes_migration (composite indexes that span tables)
  062_seed_financial_accounts
  063_seed_default_roles_and_permissions
  064_seed_default_activities
  065_seed_default_templates
```

**RULE**: This order is BINDING. You cannot skip ahead. If writing migration 030, all migrations 001-029 must already exist and work.

---

## Testing Integration Points (Validation Checklist)

Before declaring any module "done", verify these cross-module paths:

| Path | What to verify |
|------|---------------|
| Registration → Enrollment → Invoice → Payment | Full flow produces correct financial records |
| Enrollment → Session Generation → Attendance Generation | Records exist for all sessions |
| POS Sale → Inventory Deduction → Low Stock Alert | Stock level decreases, alert fires at threshold |
| Participant Freeze → Attendance Removal → Enrollment Extension | No attendance during freeze, dates extended |
| Payment → Invoice Status → Enrollment Activation | Pending enrollment becomes active on full payment |
| Session Cancel → Attendance Cancel → Reservation Free → Notifications | All downstream effects propagate |
| Refund → Payment Update → Invoice Recalculate → Transaction Reverse | Financial integrity maintained |
| Auto-Absent → Consecutive Check → Auto-Suspend → Guardian Notify | Automation chain completes end-to-end |
| Kit Sale → Component Stock → Multiple Movements → Level Update | Each component tracked individually |
| Pricing Rule Change → Does NOT affect existing invoices | Snapshot principle holds |
