# Phase 7: Cross-cutting Concerns

## Objective
Deliver all cross-cutting features that span multiple phases: notifications, real-time events via Pusher, translations for all user-facing text, cart cleanup jobs, and reservation reminders.

---

## Tasks

### 7.1 Notifications

#### Reservation Confirmed (Customer)
**File:** `app/Notifications/Restaurant/CustomerReservationConfirmedNotification.php`

- Channels: `sendinblue_email`, `expo` (push), `database`
- Triggered when: reservation status changes to CONFIRMED
- Content: restaurant name, date, time, party size, tables (if assigned)
- Sendinblue template with reservation details

#### Reservation Reminder (Customer)
**File:** `app/Notifications/Restaurant/CustomerReservationReminderNotification.php`

- Channels: `expo` (push), `database`
- Triggered by: scheduled job, 2 hours before reservation start_at
- Content: reminder with restaurant name, time, any pre-order status

#### Reservation Cancelled (Customer)
**File:** `app/Notifications/Restaurant/CustomerReservationCancelledNotification.php`

- Channels: `sendinblue_email`, `expo` (push), `database`
- Triggered when: reservation cancelled (by customer or manager)
- Content: cancellation confirmation, reason if provided

#### Pre-order Confirmed (Customer)
**File:** `app/Notifications/Restaurant/CustomerPreorderConfirmedNotification.php`

- Channels: `sendinblue_email`, `expo` (push), `database`
- Triggered when: pre-order payment completed
- Content: items ordered, total paid, reservation details

#### New Reservation (Restaurant Manager)
**File:** `app/Notifications/Restaurant/ManagerNewReservationNotification.php`

- Channels: `expo` (push), `database`
- Triggered when: customer creates a reservation
- Content: customer name, date, time, party size, special requests

#### Daily Summary (Restaurant Manager)
**File:** `app/Notifications/Restaurant/ManagerDailySummaryNotification.php`

- Channels: `sendinblue_email`
- Triggered by: scheduled job at end of day (after last service period)
- Content: day's total revenue, covers, reservations, no-shows, top items

---

### 7.2 Pusher Real-time Events

#### Table Status Changed
**File:** `app/Events/Restaurant/TableStatusChanged.php`

- Channel: `private-restaurant.{restaurant_id}`
- Event: `table.status.changed`
- Data: `{ table_id, table_number, zone, old_status, new_status, reservation_id? }`
- Fired when: table status updated (any transition)

#### New Reservation Created
**File:** `app/Events/Restaurant/NewReservationCreated.php`

- Channel: `private-restaurant.{restaurant_id}`
- Event: `reservation.created`
- Data: `{ reservation_id, customer_name, party_size, start_at, status }`

#### Reservation Status Updated
**File:** `app/Events/Restaurant/ReservationStatusUpdated.php`

- Channel: `private-restaurant.{restaurant_id}`
- Event: `reservation.status.updated`
- Data: `{ reservation_id, old_status, new_status }`

#### Order Paid
**File:** `app/Events/Restaurant/RestaurantOrderPaid.php`

- Channel: `private-restaurant.{restaurant_id}`
- Event: `order.paid`
- Data: `{ order_id, total, table_numbers, customer_name }`

---

### 7.3 Scheduled Jobs

#### Reservation Reminder Job
**File:** `app/Jobs/Restaurant/SendReservationRemindersJob.php`

- Runs every 15 minutes via Laravel scheduler
- Finds reservations starting in 2 hours (± 15 min window)
- Sends `CustomerReservationReminderNotification`
- Marks reservation as reminded (prevent duplicates)

#### Cart Cleanup Job
**File:** `app/Jobs/Restaurant/CleanupExpiredCartsJob.php`

- Runs every hour
- Finds restaurant orders with:
  - status = WAITING_PAYMENT
  - created_at > 4 hours ago (restaurant carts expire after 4h, longer than shop's 24h)
  - restaurant_id IS NOT NULL
- Frees assigned tables → AVAILABLE
- Deletes expired orders

#### Daily Summary Job
**File:** `app/Jobs/Restaurant/SendDailySummaryJob.php`

- Runs at configurable time (default: 23:00 local time)
- Compiles day's stats for each restaurant
- Sends `ManagerDailySummaryNotification` to restaurant managers

#### Dashboard Cache Warm Job
**File:** `app/Jobs/Restaurant/WarmDashboardCacheJob.php`

- Runs at midnight
- Pre-computes all dashboard widgets for all restaurants
- Uses `RestaurantDashboardCacheService::warmCache()`

---

### 7.4 Translations

All translatable fields must work across 4 languages (en, es, fr, zh):

| Model | Translatable Fields |
|---|---|
| Restaurant | name, description |
| RestaurantMenuCategory | name, description |
| RestaurantMenuItem | name, description |
| RestaurantMenu | name, description |
| RestaurantMenuSection | name |

**Enum labels** (in `storage/languages/{lang}.json`):
- All RestaurantServicePeriod values
- All RestaurantReservationStatus values
- All RestaurantTableZone values
- All RestaurantTableStatus values
- All Allergen values (with common name per language)
- All DietaryLabel values
- All RestaurantMenuCategoryType values
- OrderableType new values (RESTAURANT_MENU_ITEM, RESTAURANT_RESERVATION)
- AdminRole: RESTAURANT_MANAGER label

After translations added: `php artisan translations:clear-cache`

---

### 7.5 Event Listeners Registration

**File:** `app/Providers/EventServiceProvider.php`

Register listeners:
```php
RestaurantOrderPaid::class => [
    FreeTablesWhenRestaurantOrderPaid::class,
    GenerateInvoiceWhenOrderPaid::class, // existing
],
TableStatusChanged::class => [
    // Broadcast via Pusher (handled by ShouldBroadcast)
],
NewReservationCreated::class => [
    NotifyRestaurantManagerOfNewReservation::class,
],
```

---

### 7.6 Observer (Optional)

**File:** `app/Observers/RestaurantReservationObserver.php`

- `updated()`: If status changed → fire ReservationStatusUpdated event
- `created()`: Fire NewReservationCreated event

---

## Tests

**File:** `tests/Feature/Controllers/RestaurantManager/CrossCuttingTest.php`

- [ ] Reservation confirmation notification sent to customer
- [ ] Reservation cancellation notification sent to customer
- [ ] Pre-order confirmed notification sent to customer
- [ ] New reservation notification sent to manager
- [ ] Pusher events broadcast on table status change
- [ ] Pusher events broadcast on new reservation
- [ ] Cart cleanup job expires old restaurant carts
- [ ] Cart cleanup job frees tables
- [ ] Reservation reminder job sends notification 2h before
- [ ] Reservation reminder job doesn't duplicate
- [ ] Daily summary job compiles correct stats
- [ ] Dashboard cache warm job pre-computes widgets
- [ ] All translations exist for 4 languages
- [ ] All enum labels translated

---

## Files Created/Modified

| Action | File |
|---|---|
| Create | 6 notification classes in `app/Notifications/Restaurant/` |
| Create | 4 event classes in `app/Events/Restaurant/` |
| Create | 4 job classes in `app/Jobs/Restaurant/` |
| Create | 2-3 listener classes in `app/Listeners/Restaurant/` |
| Create | `app/Observers/RestaurantReservationObserver.php` |
| Modify | `app/Providers/EventServiceProvider.php` |
| Modify | `app/Console/Kernel.php` — register scheduled jobs |
| Modify | `storage/languages/{en,es,fr,zh}.json` — all new translation keys |
| Create | `tests/Feature/Controllers/RestaurantManager/CrossCuttingTest.php` |

---

## Acceptance Criteria

- [ ] All 6 notification types send via correct channels
- [ ] Pusher events broadcast on table/reservation/order changes
- [ ] Cart cleanup expires restaurant carts after 4h and frees tables
- [ ] Reservation reminder sent 2h before, no duplicates
- [ ] Daily summary compiled and sent at end of day
- [ ] Dashboard cache warmed at midnight
- [ ] All translations available in en, es, fr, zh
- [ ] All tests pass
- [ ] `vendor/bin/pint` passes on all new/modified files
- [ ] `vendor/bin/phpstan --level=7` passes on all new/modified files
