# Shop Portal — Execution Plan (Sprints)

> PM artifact. Turns the shop-portal parity gaps from `PARITY_REANALYSIS_2026_06_28.md`
> into a sequenced, executable backlog. Scope: **frontend / shop portal only**.
> Sequencing = value-per-effort (HIGH→MED→LOW, S→M→L), safety-bugs pulled forward.

## 1. Objective

Close the high-value shop-owner gaps in ≤6 sprints, starting with the cheapest
high-impact fixes (broken interactions + safety bugs where the data/engine already
exists), then the daily-driver bookings list, then party UX, finance depth, and
notifications. Every sprint is **behavior-preserving where it touches shared code**
and ends with the same verification we just used (smoke + targeted live test).

## 2. Prioritization (RICE-lite, HIGH backlog)

Score = Impact × Confidence ÷ Effort. Effort: S=1, M=2, L=3. (Reach assumed
shop-wide for all.) Sorted by score.

| # | Gap | Domain | Eff | Why now | Score |
|---|-----|--------|-----|---------|------:|
| 1 | Drag-to-reschedule + reassign POST contract | Calendar | S | One root-cause bug breaks every drag; data exists | ★★★★★ |
| 2 | Delete-with-bookings → deactivate guard | Team | M | **Safety**: hard-deletes a specialist with live bookings | ★★★★★ |
| 3 | Payment breakdown rows (detail) | Booking Detail | S | View-model already returns the labels; pure view work | ★★★★☆ |
| 4 | Empty-slot click → pre-filled walk-in | Calendar | S | Same datetime-vs-from root cause as #1 | ★★★★☆ |
| 5 | Terminal timeline node (Cancelled/No-Show) | Booking Detail | M | Data exists; correctness of the status story | ★★★☆☆ |
| 6 | Live search (applies to party rows) | Bookings | M | Daily-driver friction | ★★★☆☆ |
| 7 | Solo inline drawer (reuse `_detail.php`) | Bookings | L | Biggest solo-side gap; reuses existing component | ★★★☆☆ |
| 8 | Collection status column | Bookings | M | Needed for daily reconciliation | ★★★☆☆ |
| 9 | New-group Pay-now sub-flow | Bookings | M | Party money capture | ★★★☆☆ |
| 10 | Charges filters (scopes already exist) | Finance | M | Controller computes filters then ignores them | ★★★☆☆ |
| 11 | Earnings table columns / KPI quartet | Finance | M | Per-row finance truth | ★★★☆☆ |
| 12 | Notifications usage tiles + paid-channel confirm | Settings | M+M | Spend gate; currently hardcoded | ★★☆☆☆ |
| 13 | Channel approval gating | Settings | L | Data-model gap (per-channel approved/pending) | ★★☆☆☆ |
| 14 | Service VariantPicker / freebie attachments | Services | L+L | Tables exist, fully unwired | ★★☆☆☆ |
| 15 | Structure org-chart editing | Team | L | Read-only today; big build | ★★☆☆☆ |
| 16 | Invoice Pay flow / Subscription tab | Finance | L+L | Needs new flows/models | ★★☆☆☆ |

---

## 3. Sprints

### Sprint 1 — "Stop the bleeding" (quick wins + safety)
**Goal:** fix every cheap high-impact defect where the data/engine already exists.
**Scope:**
- Calendar drag-to-reschedule (same specialist) — fix `{datetime}` → `date`+`from`+`to` POST contract.
- Calendar drag-to-reassign (cross specialist) — same root cause.
- Empty-slot click → pre-filled walk-in (`?datetime=` → `?from=`).
- Booking Detail payment breakdown rows (discount/online/in-person/tip/refund).
- Team: delete-with-bookings → deactivate guard (booking-count check before `actionDelete`).

**Dependencies:** none — all data/view-model fields already exist.
**Definition of Done:**
- Drag-drop reschedule & reassign persist (no "Invalid date or time."); confirm dialog body uses the same contract.
- Empty-slot create pre-fills the snapped time.
- Detail card renders all 5 payment lines from the existing view-model labels.
- Deleting a specialist with live bookings is blocked and offers deactivate instead.
- `php -l` clean + smoke (`render-smoke.sh`) 15/15 + manual drag/delete check.

**Risks:** the calendar POST action must not be on the shared `api/` tier — **verify the endpoint is frontend-only before changing the contract** (cross-tier rule). Keep the confirm-body fix in the same change as the drag fix (one root cause).

---

### Sprint 2 — Solo bookings list parity (daily driver)
**Goal:** bring the solo bookings list to demo parity without a separate `/view` round-trip.
**Scope:**
- Solo row inline expand → BookingDrawer (reuse `views/booking/_detail.php`).
- Per-row 3-dot quick-actions menu (start/complete/reschedule/cancel from the row).
- Live client-side search (booking#/customer/mobile) — **also filter party rows**.
- Collection status column (solo + party): collected / pending / not-required badge.

**Dependencies:** reuses `_detail.php` (already used by the calendar modal); `BookingFinanceMath::collectionStatusKey()` (exists) for the badge.
**Definition of Done:**
- Solo row expands in place to the full detail body with working transitions + timeline.
- 3-dot menu fires the same transitions as the detail modal.
- Search is live (no submit) and party rows respect it.
- Collection column matches `BookingFinanceMath` buckets.
- Smoke 15/15 + live POST test of one transition via the row menu.

**Risks:** drawer reuse must not regress the calendar modal (shared partial) — verify both render. Search perf on large lists (client-side filter only).

---

### Sprint 3 — Group / party UX completeness
**Goal:** finish the party create/manage flows on top of the Wave-1 `GroupBookingService` (now owner of the whole party lifecycle after the refactor).
**Scope:**
- New-group modal: organiser + guest repeater **"New" organiser path** (verify `GroupBookingService::resolveNewOrganiser` is wired to the UI — service support already landed in the refactor).
- New-group modal: Pay-now collection sub-flow (method/tip/due capture).
- New-group modal: inline per-guest conflict check (client-side placement at chosen slot).
- New-group modal: live slot picker (open-times grid).
- Group reschedule modal: SlotPicker (replace raw date/time inputs).
- Group expand drawer: nested per-guest detail (guest rows → `BookingDetailBody`).

**Dependencies:** SlotPicker component (exists in booking detail); Sprint 2 drawer; `GroupBookingService` (refactored).
**Definition of Done:**
- Brand-new walk-in party can be created (organiser find-or-create by mobile).
- Pay-now captures a real collection; on_visit collects nothing (unchanged).
- Conflicts surface before submit; reschedule uses the slot grid.
- Finance invariance preserved (all money via `FinanceLedgerService`).
- Live test: create a party end-to-end + collect + complete (then clean up test rows).

**Risks:** finance invariance — do **not** fork money math; route through `FinanceLedgerService` exactly as the refactor does.

---

### Sprint 4 — Finance hub depth (per-row truth)
**Goal:** make the 5 finance tabs reflect the immutable ledger per row (no sums/stubs).
**Scope:**
- Earnings table columns + KPI quartet (Revenue / Navagoo fees / Net / Withdrawable) from the engine.
- Charges filters (status Segmented + type Select) — `ChargeQuery` scopes already exist; just wire UI.
- Settlement eligibility table (per-booking columns + held-days).
- Request transfer / Approve & send (inline, bundling eligible bookings).
- Invoices table columns (Period / Issued / Due).

**Deferred to Sprint 6:** Invoice Pay flow (L), Subscription tab (L), Payment cards (M), ZATCA invoice viewer (M) — need new flows/models.
**Dependencies:** `FinanceLedgerService` (done), `ChargeQuery` scopes (exist).
**Definition of Done:** every finance table column is populated from the engine and reconciles; charges filters work; request-transfer creates a real bundled request.
**Risks:** numbers must reconcile with the engine — assert against `FinanceLedgerService`, never re-sum.

---

### Sprint 5 — Notifications gating ⚠️ (data-model work)
**Goal:** real quota + approval UX for paid channels.
**Scope:**
- Usage stat tiles (used/free/overage) wired to a real quota model.
- Paid-channel confirmation dialog (cost disclosure before SMS/WhatsApp).
- Channel approval gating (per-channel approved/pending; server-enforced).
- Customer triggers table + template preview.

**Dependencies:** **new DB columns** (template/channel/approval, quota). 
**Definition of Done:** usage reflects real counts; paid channels gated by approval + cost confirm; server rejects un-approved channels.
**Risks:** **DATA-MODEL CHANGE — flag mobile-API impact first.** Notification tables may be read by the shared `api/` tier; per the cross-tier rule, surface any schema/contract change for sign-off **before** the migration. This is why it's late, not early.

---

### Sprint 6 — Larger builds (catalogue + structure + finance flows)
**Goal:** the remaining L-effort features, lower urgency.
**Scope:**
- Services: VariantPicker (`service_image_variant`) + freebie attachments (`service_freebie_link`) + total-duration roll-up + bilingual description.
- Team: Structure org-chart editing (add/rename/reparent/delete + `reports_to` field) + colour/zoom/fullscreen.
- Finance: Invoice Pay flow + Subscription tab + Payment cards + ZATCA invoice viewer.

**Dependencies:** the unwired tables already migrated; UI + persistence only.
**Definition of Done:** each feature persists and round-trips; no orphaned tables remain.
**Risks:** largest surface; split into per-feature PRs; keep each behind its own verification.

---

## 4. Fast-follow polish backlog (LOW — batch between sprints)

Cosmetic/`done*` items: KPI grid 6-vs-4, donut legend colours, muted "Off" vs "Inactive"
badge, group-badge size-vs-id, slot-picker dedupe, no-show grace tooltip, marketing
usage progress bar, empty states, login/sign-up aurora re-skin, classifications "Reason"
badge, settings copy/hints. Pick 3–5 S-items as filler at the end of any sprint.

## 5. Cross-cutting guardrails

1. **Shared `api/` tier:** any data-model or API-contract change (esp. Sprint 5 notifications, any new column the mobile app might read) must be flagged for sign-off **before** implementing.
2. **Behavior-preserving on shared code:** controllers/services touched by both surfaces (calendar partial, `GroupBookingService`, `FinanceLedgerService`) keep identical responses; verify with the live smoke + targeted POST tests.
3. **Verification ritual per sprint:** `php -l` on changed files → `render-smoke.sh` (15/15) → one targeted live POST/flow test → clean up any test rows created.
4. **Test debt:** the full `codecept run unit` (DB-backed) couldn't run from this host (DB unreachable + docker-exec hangs). Run it inside the app container per sprint to keep the suite green.

## 6. Cadence & tracking

- One sprint = one cohesive PR cluster; ship Sprint 1 first (highest value/hour).
- Track live status in `WORKLOG_BOARD.html`; track feature state in `SHOP_FEATURES_BOARD.html`.
- Re-score the HIGH backlog after each sprint (new info changes priorities).

**Recommended start:** Sprint 1 — it removes two user-facing breakages (calendar drag) and a data-loss safety bug for the cost of mostly S-effort work.
