# Navagoo Demo ↔ Portal Parity Re-Analysis

**Date:** 2026-06-28
**Scope:** Full demo (`../Navagoo_MI`) ↔ portal (Yii2/Tailwind "aurora") parity, reflecting state after waves 8–13.
**Method:** 13 domain agents each read both the demo React source and the current portal PHP and reported feature-by-feature.

## Executive summary

The port is in strong overall health: of **224** features analyzed, **103 are at parity (done)**, **45 partial**, **41 intentionally/structurally different**, and **35 missing**. Roughly **66%** of features are either fully done or a deliberate, documented superset (different-but-richer). The portal's financial engine (`FinanceLedgerService` ↔ `finance.ts`), calendar scheduling engine (`BookingScheduleService` ↔ `lib/schedule.ts`), onboarding gate (`nextOnboardingScreen` ↔ `onboarding.ts`), and the entire admin side are faithful or better-than-demo, frequently wired to real DB tables rather than mocks.

Since the last analysis, waves 8–13 closed substantial ground: **Settings** gained the 12h/24h toggle, per-method walk-in toggles, and deposit-cap gating; **Specialist display fields** (compensation, calendar colour, languages, reports_to column) landed; **Commercial Config** singleton form shipped; **Notifications** got usage tiles + a full admin trigger catalogue with timing modes; the admin-finance **SettleModal** (dual-upload, FormulaStrip, mismatch detection) and the **Finance hub** (5-tab shell + ShopBalances + TransferRequests) were ported. The remaining work concentrates in five places: (1) a **client/server contract bug** breaking calendar drag-to-reschedule/reassign; (2) the **solo bookings list** lacking an inline drawer / quick-actions / live search; (3) the **Notifications template + approval lifecycle** (no template columns in DB at all); (4) three **Finance tabs** (Charges filters, Invoices Pay/ZATCA flow, Subscription) being thin or stubbed; and (5) the **Team Structure** org-chart being read-only with no manager field. None of the admin-side gaps are high priority — the shop-owner surfaces are where the value is.

---

## Scorecard

| Domain | Overall | Done | Partial | Different | Missing | Verdict |
|---|---|---:|---:|---:|---:|---|
| Shop Dashboard | mostly_done | 11 | 2 | 2 | 0 | Faithful, real-ledger; subscription banner substitutes contract lifecycle |
| Shop Bookings list + Group Bookings | partial | 4 | 6 | 7 | 3 | Group half strong; solo half lacks drawer/search/columns + Pay-now |
| Day/Month Calendar + Time-off | mostly_done | 16 | 1 | 4 | 0 | Near-1:1 engine; drag POST contract is broken |
| Booking Detail + Timeline + Slot picker | mostly_done | 10 | 4 | 1 | 2 | Core faithful; payment rows + terminal timeline node missing |
| Services / Catalogue | partial | 5 | 4 | 4 | 5 | List strong; forms lack variants/cropper/freebies/descriptions |
| Team / Specialists / Structure | partial | 8 | 3 | 4 | 3 | Specialist form excellent; Structure is read-only, no manager field |
| Shop Settings (all tabs) | partial | 9 | 6 | 3 | 2 | Shell + payments solid; Notifications tab shallow/static |
| Notifications (3 surfaces) | partial | 6 | 5 | 3 | 6 | Shells ported; template + approval lifecycle entirely absent |
| Shop Finance 5-tab hub | partial | 4 | 8 | 4 | 5 | Engine + detail strong; Charges/Invoices/Subscription tabs incomplete |
| Marketing / Promo / Packages | mostly_done | 11 | 1 | 3 | 0 | Real persisted CRUD, superset of demo |
| Customers + Onboarding/Auth | mostly_done | 13 | 3 | 2 | 0 | Near-1:1, prod-grade OTP superset; login still Bootstrap |
| Admin Finance | partial | 7 | 5 | 3 | 4 | ShopBalances/Settle faithful; no AdminInvoices/AdminCharges screens |
| Admin core (8 screens) | mostly_done | 15 | 0 | 3 | 0 | Most mature area; Activate modal + Geography not re-skinned |
| **Total** | — | **103** | **45** | **41** | **35** | — |

---

## Prioritized gap backlog

Every feature with status ≠ `done`, sorted Priority (high → med → low) then Effort (S → M → L). "Different" entries are included where the divergence is a real behavioral gap, not a documented superset.

### HIGH priority

| Domain | Feature | Status | Gap | Effort |
|---|---|---|---|---|
| Day/Month Calendar | Drag-to-reschedule (same specialist) | different | JS posts `{datetime}` but server reads `date`+`from`+`to` → every drop fails "Invalid date or time." Confirm body also generic. | S |
| Day/Month Calendar | Drag-to-reassign (cross specialist) | different | Same fatal `datetime`-vs-`date/from/to` POST mismatch; server can't read the move. | S |
| Booking Detail | Payment breakdown rows (discount/online/in-person/tip/refund) | partial | Renders only Outstanding + Total; view-model already returns discountLabel/collectedOnlineLabel/inStoreLabel/tipLabel/refundValueLabel — pure view work. | S |
| Booking Detail | Status timeline — terminal branches (Cancelled / No-Show) | different | Cancelled/no-show show 3 ghosted happy-path steps instead of a terminal node w/ timestamp + refund/reason lines. | M |
| Shop Bookings | Live client-side search (booking#/customer/mobile) | different | Submit-based not live; status/ID/name filter ignores party rows entirely. | M |
| Shop Bookings | Solo row inline expand → BookingDrawer | missing | No inline solo drawer at all; start/complete/reschedule/cancel/timeline only via separate /view page. Biggest solo-side gap. | L |
| Shop Bookings | New group modal: Pay-now collection sub-flow | different | "Pay now" opens no payment-capture step (no method/tip/due); on_visit collects nothing. | M |
| Services / Catalogue | Service variant / image-tier picker (VariantPicker) | missing | `service_image_variant` table + model exist but fully unwired; no UI, no persistence. | L |
| Services / Catalogue | Routine-before/after/free-addon attachments on a service | missing | `service_freebie_link` table migrated but unwired; no picker, no +N min roll-up. | L |
| Team / Structure | Structure tab — interactive org-chart editing | missing | Read-only tree; NO add-child/rename/reparent/delete AND no manager/reports_to field in form despite DB column+FK. Domain headline gap. | L |
| Team / Specialists | Delete-with-bookings → deactivate-instead guard | missing | `actionDelete` hard-deletes with no booking-count check; risk of deleting specialist with live bookings. | M |
| Shop Settings | Notifications — usage stat tiles (used/free/overage) | different | Usage hardcoded 0; free limits 100/50 ≠ demo 50/20; no overage "N billed · $X each"; extra In-app tile. | M |
| Shop Settings | Notifications — paid-channel confirmation dialog | missing | Clicking SMS/WhatsApp submits immediately, no cost disclosure (a deliberate spend gate in demo). | M |
| Shop Settings | Notifications — channel approval gating (locked/pending) | missing | Buttons always enabled; no per-channel approved/pending concept; posted values unvalidated. Data-model gap. | L |
| Notifications | Admin: per-channel TEMPLATE authoring (SMS/WA/in-app bodies) | missing | `notification_trigger` has no template/channel columns; no template textareas anywhere. | L |
| Notifications | Admin: variable catalogue ([bookingNum]…) click-to-insert | missing | No template area → no variable chips/insert/token catalogue. | M |
| Notifications | Admin: template APPROVAL status + Approve control | missing | No status column/badges/Approve action; central admin governance concept absent. | L |
| Notifications | Shop: per-trigger paid-channel picker not gated by approval | different | Always offers SMS+WhatsApp; shop can pick a channel admin never approved. | M |
| Notifications | Send-time channel resolution + NotifSent emission | missing | No resolver/sender pipeline; configured paid_channel has no behavioral effect (usage stays 0). Engine behind the feature. | L |
| Shop Finance | Earnings table columns (per-row finance state) | partial | Missing payment-timing pill, Net Earnings, Navagoo Payout, Eligible-verdict + hold countdown, Collection-status. | M |
| Shop Finance | Charges filters (status Segmented + type Select) | missing | Controller computes filterStatus/filterType then ignores them; no filter UI rendered. ChargeQuery scopes already exist. | M |
| Shop Finance | Invoice Pay flow (card vs bank-transfer slip) | missing | No Pay action, method modal, slip upload, or payInvoice mutation. Invoices read-only. | L |
| Shop Finance | Subscription tab (plans, activate/cancel, status) | missing | Coming-soon stub; SubscriptionPackage/UserCard models exist but nothing wired. | L |
| Admin Finance | AdminInvoices — platform-wide list + status filter | missing | No screen/action; Invoice model lacks period/issued_at/due_at/paid_at/documentStatus. Biggest admin-finance gap. | L |
| Admin Finance | AdminInvoices — verify-payment + upload-document | missing | No verifyInvoice/attachInvoiceDocument; pending_verification unreachable from UI. | M |
| Admin Finance | AdminCharges — platform-wide ledger + filters | missing | No platform-wide screen; only per-shop drill-down; no rate-label column or status/shop/type filters. | M |
| Admin Finance | CommercialConfig values actually drive the charge engine | different | Form saves to `commercial_config` but `FinanceLedgerService` reads `Settings.taxes` + shop columns/hardcoded defaults — config is write-only w.r.t. ledger. | M |

### MEDIUM priority

| Domain | Feature | Status | Gap | Effort |
|---|---|---|---|---|
| Shop Dashboard | Costs-to-date donut (legend dot colours) | partial | Legend dots all `bg-brand-300` instead of per-category `$p['color']` — minor bug. | S |
| Shop Bookings | Status filter options parity | different | Dropdown vs segmented; any-guest-matches party rule moot since filter never touches party rows. | S |
| Shop Bookings | Group reschedule modal (SlotPicker) | partial | Raw date+time inputs, no open-slot grid; clash only surfaces after submit. | M |
| Shop Bookings | New group modal: organiser + guest repeater | partial | No "New" organiser path (existing-customer only) → blocks brand-new walk-in parties. | M |
| Shop Bookings | New group modal: inline per-guest conflict | partial | `.grp-conflict` banner is dead UI; no client-side placement check at chosen slot. | M |
| Shop Bookings | New group modal: live slot picker | different | Blind date/time entry; "add a guest first to see open times" non-functional. | M |
| Shop Bookings | Group expand drawer: nested per-guest detail | partial | Guest rows don't expand to BookingDetailBody; single-guest in-drawer actions lost (eye-link only). | L |
| Shop Bookings | Collection status column (solo + party) | missing | No Collection column; party shows "{amt} due" sub-line, no collected/pending badge. | M |
| Booking Detail | Per-row actions menu (3-dot) | missing | No quick-actions menu on list rows; every transition needs the detail modal first. | M |
| Booking Detail | Per-booking notifications bell | partial | Title-only sent list; no channel badge/body/Failed badge/Mark-failed, not per-row bell. | M |
| Day/Month Calendar | Empty-slot click → pre-filled walk-in create | partial | JS appends `?datetime=`, actionWalkIn reads `?from=` → snapped TIME dropped. | S |
| Services / Catalogue | Image cropper on all image pickers | different | filekit auto-resize only, no interactive crop step for square thumbnails. | L |
| Services / Catalogue | Bilingual service description | missing | ShopService has no description field at all (demo treats it as first-class on every item). | M |
| Services / Catalogue | Total-duration roll-up (+N min) | missing | Depends on freebie attachments; card chip + form hint need the +N. | M |
| Services / Catalogue | Routines/add-ons as first-class items (Freebie CRUD) | partial | No bilingual name/description (no name_ar/description_ar), no image picker. | M |
| Services / Catalogue | Subscription packages tab placement | partial | Lives on own route, not surfaced in unified catalogue; legacy package/_form coexists. | M |
| Services / Catalogue | Unified catalogue Packages tab | partial | 4 tabs only; Packages (5th) split to package/subscriptions route. | M |
| Services / Catalogue | Admin Catalogue women/men branch segmentation | missing | No gender-branch filter; admins can't separate female/male catalogues. | M |
| Team / Structure | Structure — colour modes + zoom-pan-fullscreen | missing | No tint modes (specialist colour ignored), no zoom/pan/fit/fullscreen toolbar. | L |
| Team / Payroll | Payroll tab — monthly breakdown | partial | net_of_fees flattened to gross (over-pays); "Tips Earned" sums booking tipping not the demo's owed-liability selector. | M |
| Team / Structure | Structure help popover | partial | Describes a set-reporting-line-via-profile capability the form doesn't provide. | S |
| Shop Settings | Scheduling — opening hours TimePicker | partial | Native time input ignores time_format toggle (always 24h, no 15-min snap). | M |
| Shop Settings | Scheduling — time format toggle drives pickers | partial | Toggle persists but does NOT drive open/close picker display format. | M |
| Shop Settings | Commercials — read-only terms table | partial | Missing Next-billing date + Payment-processing-fee rows; min-withdrawal not money()-formatted. | S |
| Shop Settings | Notifications — customer triggers table | partial | Omits template preview, "N sent this month", schedule detail; In-app badge always "On · free". | M |
| Shop Settings | Notifications — template preview (expand/collapse) | missing | No template text/preview/expand; shops can't see what an alert will say. | M |
| Notifications | Admin: live preview + char/SMS-segment count | missing | No preview box/char count/smsSegments helper (needs template authoring first). | M |
| Notifications | Admin: editing SMS/WA template re-pends it | missing | No re-approval-on-edit semantics (blocked on template+approval model). | M |
| Notifications | Admin: "re-approval needed" confirm (N shops paused) | missing | No blast-radius confirm before re-pending a template. | M |
| Notifications | Admin: eventBasis selector ("Fires") | different | No enumerated eventBasis column/selector; firing event implied by free-text event_key; timing always shown. | M |
| Notifications | Shop Settings: usage tiles (in-app/SMS/WA) | partial | In-app real; SMS/WA hardcoded 0; "/100","/50" static literals, no real quota model. | M |
| Notifications | Shop in-app inbox: feed list (data source) | different | Wired to legacy `notifications` table, not the demo's trigger-named NotifSent feed; the two systems are decoupled. | L |
| Shop Finance | Earnings KPI tiles (P&L quartet) | different | Doesn't surface Revenue/Navagoo fees/Net earnings/Withdrawable exactly; no threshold-aware caption; uses ShopEarning sums not the engine. | M |
| Shop Finance | Charges ledger table | partial | Missing Booking ref, Rate (rate_snapshot/fixed_snapshot exist but unshown), Invoice/TR ref, reversal annotation. | S |
| Shop Finance | Invoices table | partial | No Period column, no Issued/Due pair, no action cell. | S |
| Shop Finance | Invoice document viewer (ZATCA) | missing | No invoice document modal/printable view; earningsApplied credit line not surfaced. | M |
| Shop Finance | Settlement eligibility table (per-booking) | partial | Missing customer/specialist/status/value/tips/per-booking charges/Held-days columns. | M |
| Shop Finance | Request transfer / Approve & send action | different | Delegates to agents-wallet/create; no inline requestTransfer bundling eligible bookings → settlement tab; no "nothing eligible" warning. | M |
| Shop Finance | Payment cards management | missing | No card list/add-card modal/make-default; UserCard model exists. | M |
| Marketing | Create deal modal (description/title field) | different | Deals keyed by redemption CODE, no free-text description/title for marketing labels; blank cap = Unlimited vs demo 100. | M |
| Admin Finance | Finance hub: 5-tab layout + badge counts | different | Fragmented into 3 separate menu items; no requested/to-verify/owing badge counts. | M |
| Admin Finance | InvoiceDocument — printable render | missing | No generated invoice doc with line items/period/issued-due/earnings-applied. | M |
| Admin Finance | CommercialConfig — messaging sub-fields | partial | Missing per-channel fail-refund + free-monthly allowance + live margin chip. | S |
| Admin core | Shops — Activate modal | different | Only Shops surface NOT re-skinned to aurora; still legacy AdminLTE/Kartik/fancybox RTL chrome. | M |
| Admin core | Geography — cities & districts | different | Two flat CRUD tables, not per-city card grid w/ inline district chips + enable/disable toggle (no city on/off switch). | M |
| Customers/Auth | Login screen | different | Functional + richer (remember-me/locale/reset) but legacy Bootstrap shell, not aurora card. | M |
| Customers/Auth | Sign-up form (chips + inline validation) | partial | Categories/type as Select2/dropdown not chips; no live "passwords don't match" or submit-disable-until-valid gate. | M |

### LOW priority

| Domain | Feature | Status | Gap | Effort |
|---|---|---|---|---|
| Shop Dashboard | Page header + quick actions | done* | Header uses user name vs plain "Welcome back"; cosmetic. | S |
| Shop Dashboard | Subscription banner (plan/trial/billing/card) | different | No Subscription/Plan model; substitutes contract/verification lifecycle. | L |
| Shop Dashboard | KPI grid selection (4 vs 6 cards) | different | 6 KPIs vs demo's 4; adds in_progress+cancelled counts; data correct. | S |
| Shop Dashboard | Costs-to-date breakdown population set | partial | Uses ->outstanding() (unpaid only); demo counts ALL non-pending so paid fees + reversals included. | M |
| Shop Dashboard | Action cards (Invite/Detailed charges) | partial | Standalone "Detailed charges" card missing (CTA lives in donut); Invite copy generic. | S |
| Shop Bookings | Unified table: parties + solos merged | different | Parties stacked at top, not interleaved chronologically by slot. | M |
| Shop Bookings | View segmented control (List/Day/Month) | partial | List-only here; Day/Month on separate /booking-calendar route. | L |
| Shop Bookings | Settlement status column | missing | Settlement state not surfaced in list (wide-screen-only secondary in demo). | M |
| Shop Bookings | Per-row notification bell | different | Navigation link, not in-place BookingNotifBell popover/indicator. | M |
| Shop Bookings | New walk-in: modal vs full page | different | Navigates to full create page, not the demo's inline modal. | L |
| Shop Bookings | Empty state | partial | Not query-aware (no 'match "q"' variant). | S |
| Day/Month Calendar | Specialist columns muted badge | partial | Hardcoded "Off"; demo distinguishes "Inactive" vs "Off". | S |
| Day/Month Calendar | Group-booking badge on a block | different | Shows party SIZE instead of the group id the demo prints. | S |
| Booking Detail | Action buttons No-Show grace state | done* | Hides No-Show button until grace vs demo's disabled+tooltip "available in N min". | S |
| Booking Detail | Cancel overlay fee-disposition text | done* | "Reversed (partial)" vs demo's exact "Reversed {pct}%". | S |
| Booking Detail | Reschedule overlay conflict feedback | done* | No live pre-submit banner (relies on slot-list filtering). | S |
| Booking Detail | SlotPicker Book-now/Earliest dedupe | done* | bookNow not removed from grid; earliest can appear twice. | S |
| Booking Detail | Inline expandable detail drawer in list | missing | Modal-only; no inline expand-in-place drawer. | M |
| Booking Detail | Group/guest-label detail row | partial | guestLabel not shown alongside group id. | S |
| Booking Detail | Status strip Settlement chip | done* | 2-state heuristic vs demo's fuller settlementStateFor(). | S |
| Services / Catalogue | 3-panel pricing discount control | partial | Plain <select> vs demo's 3 icon radios; logic faithful. | S |
| Services / Catalogue | Free add-ons attached to a bundle | missing | Bundles can't attach freebies; no FREE chips on cards. | M |
| Services / Catalogue | Assign-agents section | different | Editable multi-select vs demo's read-only (two source-of-truth places). | S |
| Services / Catalogue | Per-shop category image override | different | Listing-page customiser not in-form; no crop/reset-to-default. | M |
| Services / Catalogue | Admin category default-image upload | different | Full-page CRUD not in-list modal+cropper. | M |
| Team | Calendar colour swatch | partial | No Auto/clear control; 8 brand hexes vs 10 muted hues. | S |
| Team | Copy-to-all-days + overnight badge | different | Copy source fixed to first enabled day, no toast, won't seed closed days; +1d strict < vs <=. | S |
| Team | Spoken languages | different | Fixed 6-language pills vs free-text; not shown as chips on card. | S |
| Team | Specialists list inline actions | different | Card grid vs demo DataTable; no per-card delete. | M |
| Team | Photo upload UX | different | filekit widget chrome vs demo's tailored upload/change/remove/uploading. | S |
| Shop Settings | General — Bank/IBAN display | different | Separate BankAccount card + CTA (richer); divergent placement. | S |
| Shop Settings | Scheduling — "+1d" midnight hint | missing | No conditional +1d hint; past-midnight explanatory copy dropped. | S |
| Shop Settings | Scheduling — extra "scheduling window" field | different | Portal-only field (intentional superset). | S |
| Shop Settings | Payments — App-vs-Walk-in copy | partial | Richer explainer footnote dropped. | S |
| Notifications | Admin: bilingual trigger name | partial | name_ar REQUIRED in portal vs optional in demo. | S |
| Notifications | Admin: delete trigger confirm | partial | Native confirm() vs aurora ngConfirm convention. | S |
| Notifications | Shop in-app inbox: per-row mark-read | partial | Mark-all-read done; per-row click-to-mark missing. | S |
| Shop Finance | In-store cash collected split line | missing | No in-store (off-rail) cash split caption. | S |
| Shop Finance | Per-booking earnings detail (drawer vs page) | different | Navigated page not in-context drawer; richer ledger but lacks classification badge/online-vs-in-person split/tip line. | M |
| Shop Finance | Invoice locked/awaiting states | partial | Pending-verification shown as colour only; no "Being settled via TR" lock. | S |
| Shop Finance | Settlement FormulaStrip notifications term | partial | notifFees computed but never rendered in the strip. | S |
| Shop Finance | Settlement hold/min-withdrawal chips | partial | Missing "settlement hold N days" chip; min-withdrawal conditional. | S |
| Shop Finance | Transfer/withdrawal requests history | different | Table vs demo's request cards; lacks bookings-count + settled/pending icons. | S |
| Marketing | Usage progress visualization | different | Plain "uses/max" text vs demo's progress Bar. | S |
| Marketing | Empty state | partial | Plain row vs demo's iconified EmptyState w/ copy. | S |
| Customers/Auth | Read-only Classifications table | partial | Column "Source" plain text vs demo's "Reason" slate Badge. | S |
| Customers/Auth | Set-password inline match hint | partial | No live "passwords don't match" + disable-until-match (server-side only). | S |
| Customers/Auth | Activation email screen | different | Real emailed token link vs demo's simulated inbox (by design). | S |
| Admin Finance | TransferRequests queue breakdown columns | partial | Per-row Earned/Tips/Marketing/Processing/FeeVAT/AmountPaid collapsed into modal. | S |
| Admin Finance | SettleModal Tips term + attached confirms | done* | Tips term dropped (hardcoded 0); no per-step CheckCircle "attached" state. | S |
| Admin Finance | SettleModal associated-invoices preview | partial | External-link to PDF, no inline InvoiceDocument render. | M |
| Admin Finance | Charge type badges (CHARGE_META) | partial | Plain text labels, no coloured per-type badge; only in shop-ledger. | S |
| Admin core | Admin Dashboard 6-KPI set | done* | Reorganised into status+finance+shop tiles; "Recent bookings" folded in. | S |

\* `done*` = scored done in the source analysis with a noted cosmetic nuance; listed here for completeness only.

---

## Per-domain detail

### Shop Dashboard — `mostly_done`
Strong faithful port of `Dashboard.tsx` wired to the real Booking/Charge/`FinanceLedgerService` ledger: page header, finance KPIs, upcoming-bookings table with per-booking value + settlement badges, costs donut + legend, deep-link copy card, invite card. Divergences are intentional and documented.
- **Subscription banner** — demo shows plan/trial/next-billing/card; portal drives off shop contract/verification (no Subscription model exists).
- **Costs-to-date breakdown** — portal uses `->outstanding()` (unpaid only); demo counts ALL non-pending charges so paid fees + reversals net in.
- **Costs donut legend** — dot colours all `bg-brand-300` instead of per-category `$p['color']` (minor bug).
- **Action cards** — standalone "Detailed charges" card missing (CTA lives inside the donut widget).
- Files: `frontend/views/site/index.php`, `frontend/controllers/SiteController.php`.

### Shop Bookings list + Group Bookings — `partial`
Group ("party") half is a strong port (unified-ish table, expand drawer, whole-party action bar, atomic finance-invariant collect/reschedule via `GroupBookingService`). Solo half is where parity breaks.
- **Solo inline drawer** (missing, high) — solo rows only link to a separate /view page; no in-list detail/actions.
- **Live search** (different, high) — submit-based, and crucially never filters the party rows.
- **Pay-now sub-flow on group create** (different, high) — "Pay now" opens no method/tip/due capture; on_visit collects nothing.
- **Unified merge** — parties stacked at top instead of interleaved by slot.
- **Collection / Settlement columns**, **per-guest nested detail**, **inline conflict banner**, **live slot pickers** — all missing/partial.
- Files: `frontend/views/agents-bookings/index.php`, `_group_drawer.php`, `_group_modal.php`, `frontend/controllers/AgentsBookingsController.php`, `common/components/GroupBookingService.php`.

### Day/Month Calendar + Time-off — `mostly_done`
`BookingScheduleService` mirrors `lib/schedule.ts` almost line-for-line; views + `booking-calendar.js` replicate geometry, zoom rail, now-line, hover-create, time-off, block-time modal.
- **Drag-to-reschedule / reassign** (different, high) — JS posts `{datetime}`, server reads `date`+`from`+`to` → every drop fails. A background task was already spawned for this.
- **Empty-slot click create** — `?datetime=` vs `?from=` drops the snapped time (specialist+day still pre-fill).
- **Muted badge** always "Off" (never "Inactive"); **group badge** shows party-size not group id.
- Files: `frontend/components/BookingScheduleService.php`, `frontend/web/js/booking-calendar.js`, `frontend/controllers/BookingController.php`, `frontend/views/booking-calendar/*`.

### Booking Detail + Timeline + Slot picker + modals — `mostly_done`
3-column body, status strip, happy-path timeline, FSM-gated actions, all three overlays (Collect/Cancel/Reschedule), and the full SlotPicker are faithful.
- **Payment breakdown rows** (partial, high) — only Outstanding + Total render; discount/online/in-person/tip/refund rows omitted even though the view-model computes every one. Pure view work.
- **Terminal timeline node** (different, high) — cancelled/no-show show 3 ghosted happy-path steps instead of a Cancelled/No-Show node with timestamp + refund/reason lines.
- **3-dot per-row actions menu** + **inline list drawer** + **per-booking notif bell** — missing/partial.
- Files: `frontend/views/booking-calendar/_detail.php`, `_new_booking.php`, `_list.php`, `frontend/web/js/booking-new.js`.

### Services / Catalogue — `partial`
Unified catalogue listing (4 tabs, showInactive, lifecycle clusters, discount feedback) is a strong port; the gaps are inside the create/edit FORMS.
- **VariantPicker** (missing, high) — `service_image_variant` table+model exist, fully unwired.
- **Routine/freebie attachments** (missing, high) — `service_freebie_link` migrated but unwired; blocks the +N-min roll-up.
- **Bilingual service description** (missing) — ShopService has no description field at all.
- **Image cropper**, **freebie bilingual+image**, **Packages tab placement**, **admin women/men branch** — partial/different/missing.
- Files: `frontend/views/shop-service/{index,_form,_freebie_form,_bundle_form}.php`, `common/models/ServiceImageVariant.php`, `backend/views/shop-category/catalogue.php`.

### Team / Specialists / Structure — `partial`
Specialist form nails compensation, calendar colour, languages, multi-shift hours (with copy-to-all + +1d + bounds/overlap validation), services-can-perform.
- **Structure org-chart editing** (missing, high) — read-only tree; NO add/rename/reparent/delete AND no manager/reports_to field in the form despite the DB column+FK+model rule. Hierarchy uneditable end-to-end.
- **Delete-with-bookings guard** (missing, high) — `actionDelete` hard-deletes with no booking-count check.
- **Structure colour-modes + zoom/pan/fullscreen** (missing) and **Payroll net_of_fees** (partial — flattened to gross).
- Files: `frontend/views/agents/{index,_form,_payroll,_structure}.php`, `frontend/controllers/AgentsController.php`, `common/models/UserProfile.php`.

### Shop Settings (all tabs) — `partial`
5-tab shell + the recent high-value work (12h/24h toggle, walk-in per-method toggles, deposit cap + over-cap warning, commercials read-only card) are solid. Divergences concentrate in Notifications.
- **Notifications usage tiles** (different, high) — hardcoded 0, wrong free limits (100/50 vs 50/20), no overage state, extra In-app tile.
- **Paid-channel confirmation dialog** (missing, high) — selecting SMS/WhatsApp persists immediately with no cost disclosure.
- **Channel approval gating** (missing, high) — buttons always enabled; no per-channel approved/pending model; posted values unvalidated.
- **Template preview** (missing), **Scheduling TimePicker / format toggle** (partial), **Commercials missing 2 rows** (partial).
- Files: `frontend/controllers/SettingsController.php`, `frontend/views/settings/{index,_scheduling,_payments,_commercials,_notifications}.php`.

### Notifications (admin triggers + shop settings + in-app inbox) — `partial`
Visual shells ported well, but the portal collapses the demo's entire per-channel TEMPLATE + provider TEMPLATE-APPROVAL dimension.
- **Template authoring** (missing, high) — `notification_trigger` has no template/channel columns at all.
- **Variable catalogue**, **approval status + Approve**, **send-time channel resolution + NotifSent emission** (all missing, high) — the engine behind the feature is unported, so configured paid_channel has no behavioral effect.
- **Shop paid-channel picker not approval-gated** (different, high); **shop inbox wired to legacy `notifications` table** not the trigger-named NotifSent feed (different, decoupled).
- Timing modes, two trigger tables, optional/active toggles, bilingual names, auto-key derivation are faithfully done.
- Files: `backend/controllers/NotificationTriggerController.php`, `backend/views/notification-trigger/*`, `common/models/{NotificationTrigger,ShopNotificationSetting}.php`, `frontend/views/notifications/index.php`.

### Shop Finance 5-tab hub — `partial`
5-tab shell, the immutable-charge ledger engine (`FinanceLedgerService` ↔ `finance.ts`, a faithful 1:1 port), and the per-booking detail page (`view.php`, actually richer than the demo's drawer) are in place. Three tabs are materially incomplete.
- **Charges filters** (missing, high) — controller computes the params then ignores them; no filter UI.
- **Invoice Pay flow** (missing, high) + **ZATCA document viewer** (missing) + **locked/awaiting states** (partial).
- **Subscription tab** (missing, high) — coming-soon stub despite SubscriptionPackage/UserCard models.
- **Earnings table per-row columns** (partial, high) and **KPI quartet** (different) diverge from the engine-driven demo views.
- Files: `frontend/controllers/EarningsController.php`, `common/components/FinanceLedgerService.php`, `frontend/views/earnings/{index,_charges,_invoices,_settlement,_subscription,view}.php`.

### Marketing / Promo codes / Packages / Deals — `mostly_done`
Portal MORE than covers the demo's small DealsManager with real persisted PromoCode CRUD (shop + platform scopes) and a metrics-driven backend Marketing hub. Functionally richer (VAT-aware fixed discounts, remaining_uses, status enum, search/filter).
- **Create deal modal** (different) — keyed by redemption CODE; no free-text description/title for marketing labels; blank cap flips meaning (Unlimited vs demo 100).
- **Usage progress bar** (different) and **empty state** (partial) are cosmetic.
- Files: `frontend/controllers/PromoCodeController.php`, `backend/controllers/MarketingController.php`, `common/models/PromoCode.php`.

### Customers + Onboarding/Auth — `mostly_done`
One of the most faithful areas: Customers screen (booking-link/QR, 3-tab switcher, directory, read-only classifications w/ admin override, grace banner, freeze CRUD) is near-1:1 on real DB tables; OTP/activation is a production-grade superset (real SMS, rate-limiting, expiry); `nextOnboardingScreen` mirrors `onboarding.ts` exactly.
- **Login screen** (different) — functional+richer but legacy Bootstrap shell, not aurora card.
- **Sign-up form** (partial) — categories/type as Select2/dropdown not chips; no live password-match hint or submit-disable gate.
- **Classifications "Source" vs "Reason"** and **set-password inline match** are minor.
- Files: `frontend/controllers/CustomersController.php`, `frontend/views/sign-in/{login,signup,set_password}.php`, `frontend/controllers/SignInController.php`.

### Admin Finance — `partial`
CommercialConfig, ShopBalances, and the admin-finance TransferRequests/SettleModal are faithful, well-engineered ports on a real charge/invoice ledger.
- **AdminInvoices screen** (missing, high) — no list/filter/verify/upload; Invoice model lacks period/issued_at/due_at/paid_at/documentStatus. Biggest gap.
- **AdminCharges platform screen** (missing, high) — only per-shop drill-down; no rate-label column or filters.
- **CommercialConfig doesn't drive the engine** (different, high) — saves to `commercial_config` but `FinanceLedgerService` reads `Settings.taxes` + shop columns/hardcoded defaults. Either wire it or document the split.
- **5-tab hub + badge counts** fragmented into separate menu items.
- Files: `backend/controllers/{AdminFinanceController,CommercialConfigController}.php`, `backend/views/admin-finance/*`, `common/components/FinanceLedgerService.php`, `common/models/{Charge,Invoice}.php`.

### Admin core (Dashboard/Bookings/People/Shops/Geography/Settings/Support/Marketing) — `mostly_done`
The most mature part: every one of the 8 demo admin screens has a real DB-backed counterpart, several exceeding the demo (Settings FSM/colours/versioned policies, Shops plans/requests/subscriptions, Dashboard analytics). Admin-side and explicitly secondary.
- **Shops Activate modal** (different, med) — the only Shops surface still on legacy AdminLTE/Kartik/fancybox RTL chrome, not aurora.
- **Geography** (different, med) — two flat CRUD tables, not the demo's per-city card grid with inline district chips + enable/disable toggle (no city on/off switch).
- **Marketing hub** structural divergence (real-metrics landing deep-linking out vs one inline tabbed screen) — behaviorally a superset.
- Files: `backend/controllers/{SiteController,UserController,SettingsController,MarketingController}.php`, `backend/views/shop/{activate,plans,subscriptions,requests}.php`, `backend/views/city/index.php`.

---

## "Already at parity" appendix (done features)

- **Shop Dashboard:** Page header + quick actions, Net-available + withdrawal-min gating, upcoming-bookings table columns, per-booking value, settlement badge, booking-status chip, deep-link copy card, empty/zero states, DemoClock range anchoring.
- **Shop Bookings:** Party row summary, whole-party action bar (Start/Collect/Complete/Reschedule/Cancel — server-enforced collect gate), group collect modal (tip-on-first-guest rule).
- **Day/Month Calendar:** Business-day/overnight model, availability interval algebra, lane packing, checkPlacement + rejection order, appointment block rendering, hover-slot band, time-off block + hover-remove, block-time modal, day toolbar + status chips, zoom rail + client relayout, now-line, fullscreen, empty state, Month view, Week view (correctly absent in both).
- **Booking Detail:** 3-column layout, happy-path timeline, FSM action buttons, Collect overlay, Cancel overlay w/ live refund preview, Reschedule overlay, SlotPicker (Book-now/Earliest/next-available/past-time), conflict reasons, New-booking modal.
- **Services:** Unified tabs + counts, showInactive + lifecycle, Inactive/Hidden badges, bilingual service name, admin Catalogue two-level hierarchy.
- **Team:** 3-tab hub, compensation block, multi-shift hours, shift validation (stronger than demo), profile fields, services-can-perform, structure orientation toggle.
- **Shop Settings:** 5-tab shell, General editor, slot-size lock, payment method toggles, per-method walk-in toggle, deposit-cap + over-cap warning, shop in-app triggers list.
- **Notifications:** Two admin trigger tables, timing modes (offset/calendar), auto-key derivation, optional/active toggles, shop in-app read-only list.
- **Shop Finance:** Tab shell, immutable-ledger engine (`finance.ts` 1:1).
- **Marketing:** Shop + platform deals lists, create modal, discount rendering (VAT-aware), usage cap + remaining, expiry/expired, delete, status control, search/filter, hub landing, per-shop scoping, Packages CRUD.
- **Customers/Auth:** Booking-link/QR card, 3-tab switcher, directory, admin classification override (audited + re-derives), grace banner, freeze CRUD, sign-up confirmation, OTP entry/verification, onboarding gate FSM, password strength meter, classification badge.
- **Admin Finance:** SettleModal (FormulaStrip + dual-upload gate + mismatch), ShopBalances ledger + issue-invoice CTA + auto-bill reconcile, money/signed-balance/id-mono presentation.
- **Admin core:** Dashboard KPIs + finance rail, Bookings list + filters, People tabs + classification override, Shops directory + Plans + Subscription dashboard + Onboarding requests, Settings workflow FSM / colour system / policies, Support FAQs + Contact, Commercial Config singleton, Notification Triggers catalogue.

---

## Recommended next waves

**Wave 14 — Calendar drag fix + booking-detail fidelity (quick, high-impact).**
Fix the `datetime`-vs-`date/from/to` POST contract for drag-to-reschedule, drag-to-reassign, and empty-slot create (one root cause, all S effort), then add the missing payment-breakdown rows and the terminal Cancelled/No-Show timeline node — both data already exists in the view-model. Highest value-per-hour in the report.

**Wave 15 — Solo bookings list parity.**
Reuse `_detail.php` as an inline solo drawer, add the 3-dot quick-actions menu, make search live + apply it to party rows, and add the Collection column. Closes the single biggest cluster of high-priority gaps on a daily-driver screen.

**Wave 16 — Notifications engine (template + approval + send pipeline).**
Add template/channel/approval columns to `notification_trigger` (+ migration), build admin template authoring with variable catalogue + preview + segment count + Approve/re-pend, gate the shop picker to approved channels, and implement the send-time resolver that emits NotifSent and increments real SMS/WhatsApp usage. This unlocks the Settings notifications tiles and the shop picker at once.

**Wave 17 — Finance tab completion.**
Wire the Charges status/type filters (controller already computes them), add per-row Net Earnings/Payout/Eligible/Collection columns to Earnings, build the Invoice Pay flow (card + bank-slip) with a ZATCA InvoiceDocument viewer, and ship the Subscription tab (plans + activate/cancel + cards) off the existing models.

**Wave 18 — Services catalogue forms.**
Wire the already-migrated `service_image_variant` (VariantPicker) and `service_freebie_link` (routine-before/after + free add-ons) tables into the service form, add the +N-min total-duration roll-up, add a bilingual service description field, and give freebies bilingual name/description + image.

**Wave 19 — Team Structure + admin-finance screens + commercial-config wiring.**
Add a manager/reports_to field to the specialist form and make the org chart editable (add/rename/reparent/delete + colour modes + zoom/pan); add the deactivate-instead-of-delete guard. In parallel on the admin side: build AdminInvoices (list + verify + upload, plus the missing Invoice columns) and AdminCharges (platform list + filters + rate label), and decide whether to wire `FinanceLedgerService` to `CommercialConfig` or formally document the Settings/Shop-column split.
