# Page Review — Booking Calendar (Day / List / Month)

- **Route(s):** `/booking-calendar/index?view=day|list|month`
- **Views:** `frontend/views/booking-calendar/{index,_list,_day,_month,_new_booking,_block_time,_detail,...}.php`
- **Controller / services:** `frontend/controllers/BookingCalendarController.php` · `frontend/components/{BookingListService,BookingScheduleService,BookingCalendarPresenter,BookingCalendarViewModel}.php`
- **Demo reference:** `../Navagoo_MI` `#/shop/bookings` · parity `ai_specs/07_DEMO_PARITY/shop-booking-detail/`, `logic-schedule/`
- **Reviewed:** 2026-07-01 · **Status:** 🔍

## 1. Element inventory (Map)
| Ref | Element | Type | Expected action | Leads to |
|-----|---------|------|-----------------|----------|
| toolbar | List / Day / Month | tabs | switch view (preserves date) | `?view=` |
| toolbar | New group | button | open group-booking iframe modal | `/booking-calendar/group-booking` |
| toolbar | New walk-in | button | open walk-in iframe modal | `/booking-calendar/walk-in` |
| toolbar | Search bookings | input | filter rows client-side | — |
| toolbar | All/Scheduled/Completed/No-show/Cancelled | filter chips | filter list by status | — |
| list | booking row | row → iframe modal | open styled detail | `/booking-calendar/detail?id=` |
| list | group row → child rows | expander + child detail | open per-guest detail | `/booking-calendar/detail?id=` |
| detail | Collect & complete / Cancel / Reschedule / No-show | actions in modal | FSM transitions + finance | controller actions |
| day | empty slot click | hover-create | open walk-in pre-seeded slot | `/booking-calendar/walk-in` |
| day | block drag | reschedule/reassign | move booking | `/booking-calendar/reschedule|reassign` |
| day | Block time | button | block-time iframe modal | `/booking-calendar/block-time` |

## 2. Design findings
| # | What | Expected | Actual | Severity | Evidence |
|---|------|----------|--------|----------|----------|
| D1 | Calendar date headers in Arabic UI | localized Arabic date (top bar already shows "الأربعاء ١ يوليو") | English: Day "Wednesday, July 1, 2026", Month "July 2026", walk-in picker "Wed, Jul 01, 2026" | MEDIUM | ss_1658o43sk, ss_39009drnf, ss_4981zy771 |
| D2 | Day time-axis labels | "9:00 ص" Arabic, AM/PM after time | English "AM 9:00" with AM before number (looks bidi-reversed) | LOW | ss_1658o43sk |
| D3 | Day view RTL layout, specialist columns, status chips | match demo | ✅ correct (sidebar right, Rawan/Rola columns, 30-min grid, 5 status chips) | — | ss_1658o43sk |
| D4 | Month grid RTL, Arabic weekdays, today highlight, legend | match demo | ✅ correct | — | ss_39009drnf |
| D5 | Walk-in modal (iframe_aurora) styling/RTL | faithful to demo NewBookingModal | ✅ correct (customer existing/new toggle, services, specialist, payment, time, footer) | — | ss_4981zy771 |

## 3. Logic findings
| # | Action | Steps | Expected | Actual | Severity | Evidence |
|---|--------|-------|----------|--------|----------|----------|
| L1 | List row → detail | click row #974 | styled detail modal | ✅ renders full aurora detail (header, status chips, details, services, timeline) | — | ss_7911v22fz |
| L2 | Day view load | nav ?view=day | grid + specialist columns | ✅ loads; 1 Jul has 0 bookings so chips all 0 (correct) | — | ss_1658o43sk |
| L3 | New walk-in modal | click "حجز فوري جديد" → submit empty | open styled + gated submit | ✅ opens (iframe_aurora); empty submit is a safe no-op (gated until customer+service+specialist) | — | ss_4981zy771, ss_53928cjlj |
| L4 | Month view load | nav ?view=month | grid overview, today highlight | ✅ correct | — | ss_39009drnf |
| L5 | group-booking + block-time modals | — | open styled forms | ⚠️ NOT live-triggered this pass (share iframe_aurora w/ walk-in which works); covered by code-review agent. Live happy-path create/persist also deferred (no real bookings created while user away). | note | — |

## 4. Code trace
- List `detailUrl` built in `BookingListService::soloRow()` / `groupRow()`; Day blocks re-pointed in `BookingCalendarViewModel`. Detail rendered by `BookingCalendarController::actionDetail` on `iframe_aurora` layout (scoped to `resolveShopId()`).

**Code-review agent (BookingCalendarController + BookingController + finance/FSM services) — 0 CRIT, 2 HIGH, 4 MED, 3 LOW:**
| Sev | File:line | Issue | Status |
|---|---|---|---|
| HIGH | BookingController.php:1147 actionCancel | query by id+status w/o shop scope → cross-shop cancellable-state probe (IDOR info-leak) | ✅ FIXED (R-H002) |
| HIGH | BookingController.php:310 actionUpdate | `loadAll()` then no pin of status/refund_value/completed_at… → mass-assignment | ✅ FIXED (R-H001) |
| MED | FinanceLedgerService.php:787 tips() | reads `tipping_amount` only; in-store `specialist_tip` excluded from earnings/payout | ⚠️ LOGGED — finance math, needs user decision |
| MED | FinanceLedgerService.php:286 buildMarketingFee | minFee floor applied before `inGrace` zero → charge row stamps non-zero base on grace booking (data-quality) | ⚠️ LOGGED |
| MED | GroupBookingService.php:462 outstandingBalance | ignores `in_store_collected` + `refund_value` (2-field vs 4-field) → overstated group outstanding | ⚠️ LOGGED — finance math |
| MED | BookingController.php:1053 actionSlots | next-available scan up to 60 days, no timeout/rate cap (DoS-ish) | ⚠️ LOGGED |
| LOW | BookingController.php:897 moveBooking | `findOne` w/o shop scope (defence-in-depth; checkOwnership follows) | ⚠️ LOGGED |
| LOW | BookingController.php:749 actionTransition | `validate(['status'])` omits `no_show_at` | ⚠️ LOGGED |
| LOW | BookingCalendarController.php:208,348 | misleading guest-`?real=1` docblocks (actions not in GUEST_ALLOWED_ROUTES) | ⚠️ LOGGED |

> Finance-math MEDIUMs (tips, group outstanding) are deliberately NOT auto-fixed while the user is away — they change money owed to specialists and deserve human sign-off.

## 5. Fixes applied
| Issue | File:line | Change | Re-verified |
|-------|-----------|--------|-------------|
| R-001 List rows → bare `/booking/detail-partial` | `frontend/components/BookingListService.php:152,212` | re-point to `/booking-calendar/detail` | ✅ live (22 rows, 0 legacy); modal screenshot ss_7911v22fz |
| R-H001 actionUpdate mass-assignment | `frontend/controllers/BookingController.php:~326` | restore server-owned finance/lifecycle fields from `getOldAttribute()` after `loadAll()` (UPDATE-safe — NOT the create zero-pin, which would corrupt completed bookings) | ✅ php -l clean |
| R-H002 actionCancel cross-shop IDOR probe | `frontend/controllers/BookingController.php:~1147` | scope query by `shop_id` (via `currentShopId()`) before status filter, mirroring findModel | ✅ php -l clean |

## 6. Sign-off
- [x] List → detail modal renders styled
- [ ] Day view + drag move + block-time
- [ ] Month view structure
- [ ] New walk-in / New group / detail FSM actions (success + error)
- [ ] Search + status filters
- [ ] Both languages / breakpoints
