---
description: Audit logs are immutable. Notifications via events only. Templates for all messages.
globs: ["app/Domain/Shared/**", "app/Domain/Notification/**", "app/Domain/CMS/**"]
---

# Audit & Notifications

## Audit Log — IMMUTABLE

- No `updated_at`, no `deleted_at` on audit_logs table
- Created via `Auditable` trait (model observer on created/updated/deleted)
- Stores: who, what, when, old_values, new_values, ip_address
- EXCLUDED from logging: password, remember_token, api_key
- Financial audit logs: retained FOREVER
- Non-financial: retained 1 year (configurable)
- Nobody can edit or delete audit logs (no UI for it, no API endpoint)

## Notification Flow

```
Event → Listener → NotificationService → Channel Handler → Log
```

### Rules

1. Notifications are triggered by EVENTS — never called directly from services/controllers
2. Every notification is LOGGED (notification_logs table) — success or failure
3. In-app notifications CANNOT be disabled by user
4. Email/SMS CAN be disabled per user per notification type (preferences)
5. High-priority notifications ignore digest mode (always immediate)
6. SMS rate limit: 100/hour per academy
7. Max 1 email per recipient per event (no duplicates)

## Template Rendering

1. Templates stored in `notification_templates` table
2. Resolution: academy-specific first, then platform default
3. ALL template variables MUST be provided (throw on missing — never render raw `{{brackets}}`)
4. Variable replacement: simple str_replace (no Blade in templates)
5. SMS templates: respect 70-char Arabic limit per segment

## Notification Preferences Resolution

1. Check `notification_preferences` for user + event type
2. If no record exists → ALL channels enabled (default)
3. In-app: always on regardless of preference
4. If `digest_mode = true` AND priority is 'normal' → queue for daily digest at configured time

## Phone Number Formatting (for SMS)

- Strip spaces and dashes
- Ensure starts with `+20` (Egypt country code)
- `01012345678` → `+201012345678`
- Validate: must be 12 digits after +20
