# Group Bookings — Parity Matrix

Area: **Shop · Group ("party") bookings** — NEW in the dev demo (M2). Demo at
`/private/tmp/Navagoo_MI_dev/navagoo-app/src`; ours = Yii2 advanced template (cwd).

**Verdict: the feature is entirely MISSING on our side.** No DB column, model, service,
controller, view, API route, or i18n string for groups/parties exists. Confirmed by grep
across `frontend/ backend/ common/ api/` for group/party/guest/`group_booking` (no hits)
and `common/models/base/Booking.php` (no group columns).

| # | Demo behavior | Demo ref | Our ref | Status | Note |
|---|---------------|----------|---------|--------|------|
| 1 | `groupBookingId` + `guestLabel` fields on Booking | `types.ts:351-357` | — | missing | No `group_booking_id`/`guest_label` on `booking`; `common/models/base/Booking.php` has none |
| 2 | Organiser is shared customer-of-record across children | `store.ts:1549,1569` | — | missing | — |
| 3 | Default guest label `"Guest N"`; organiser auto-labelled | `store.ts:1569`, `GroupBookingModal.tsx:182-188` | — | missing | — |
| 4 | Create party — `createGroupBooking` action | `store.ts:1459-1612` | — | missing | No multi-child create path; `api/models/BookingForm.php` is solo |
| 5 | ≥1 guest (store) / ≥2 (UI) required | `store.ts:1463`, `GroupBookingModal.tsx:146` | — | missing | — |
| 6 | Every guest needs ≥1 service | `store.ts:1464-1465`; test `:99-107` | — | missing | — |
| 7 | Distinct specialist per guest (shared start) | `store.ts:1468-1473`; test `:67-77` | — | missing | — |
| 8 | Each child placement-checked (overlap/hours/time-off/skill) | `store.ts:1574-1586` | partial | partial | Solo placement logic exists (`frontend/components/BookingScheduleService.php`, `api/models/BookingForm.business-rules.md`) but no party orchestration |
| 9 | Atomic create — all-or-nothing rollback | `store.ts:1526-1592`; test `:79-97` | — | missing | — |
| 10 | Party slot = max of per-guest durations (parallel) | `selectors.ts:254-263,290`; test `:182` | — | missing | — |
| 11 | Party value = Σ child value; per-guest discount | `store.ts:1536-1538`; `selectors.ts:287` | — | missing | — |
| 12 | Whole-party payment timing (online/deposit/on_visit) | `store.ts:1541-1545`; `GroupBookingModal.tsx:55-68` | — | missing | Solo booking has timing; not applied party-wide |
| 13 | Finance invariance — child charges == solo | `store.ts:1587`; test `:185-229` | partial | partial | Our charge/earning pipeline exists (`m251019_213708_add_shop_earning_tables.php`) but no group child feeds it |
| 14 | Attribution computed once on organiser, shared | `store.ts:1489-1519` | — | missing | — |
| 15 | Reschedule whole party (atomic re-validate) | `store.ts:1614-1657`; test `:113-138` | — | missing | — |
| 16 | Cancel whole party (per-child transition + refund) | `store.ts:1659-1665`; test `:140-144` | — | missing | — |
| 17 | Collect whole party (settle each balance) | `store.ts:1667-1684` | — | missing | — |
| 18 | Card tip attaches to first guest with balance only | `store.ts:1672-1683` | — | missing | — |
| 19 | Complete all (collect-to-complete gate) | `store.ts:1686-1693`; test `:156-167` | — | missing | — |
| 20 | Group selectors (`groupOf`/`summariseGroup`/`groupsForShop`) | `selectors.ts:240-306` | — | missing | `BookingSearch.php` aggregates solo only |
| 21 | Aggregate status incl. `'mixed'` | `selectors.ts:285` | — | missing | — |
| 22 | "New group" button + unified solo/party table | `Bookings.tsx:337-339,103-135` | — | missing | `frontend/views/booking/index.php` solo only |
| 23 | Party row: stacked avatars + GRP tag + "Party of N" | `Bookings.tsx:42,153-205` | — | missing | — |
| 24 | `NewGroupBookingModal` (organiser + guest repeater) | `GroupBookingModal.tsx` (full) | — | missing | `frontend/views/booking/_form.php` is single-customer |
| 25 | Per-guest inline conflict + dup-specialist banners | `GroupBookingModal.tsx:433-459` | — | missing | — |
| 26 | "Organiser is also getting a service" toggle | `GroupBookingModal.tsx:346-354` | — | missing | — |
| 27 | Specialist filtered by skill + not-taken-by-sibling | `GroupBookingModal.tsx:356-358` | — | missing | — |
| 28 | Pay-now vs pay-after-service create flows | `GroupBookingModal.tsx:214-241,251-284` | — | missing | — |
| 29 | `GroupExpanded` drawer + whole-party action bar | `GroupBookingsView.tsx:29-208` | — | missing | — |
| 30 | `GroupRescheduleModal` (shared slot, anchored) | `GroupBookingsView.tsx:210-272` | — | missing | — |
| 31 | `GroupCollectModal` ("collect from organiser") | `GroupBookingsView.tsx:274-325` | — | missing | — |
| 32 | Per-guest single-booking actions inside drawer | `GroupBookingsView.tsx:132-141` | partial | partial | Solo reschedule/cancel/collect exist standalone, not inside a party drawer |
| 33 | Shop-scoping of organiser list + party list | `GroupBookingModal.tsx:49-52`; `selectors.ts:295-306` | — | missing | Must be enforced server-side in any port |
| 34 | Sources walk_in/app/deep_link drive attribution+timing | `store.ts:329-337,1502-1506` | partial | partial | Solo bookings track a source channel; no party-level use |
| 35 | Bilingual (ar/en) group strings | n/a (demo en + RTL logical props) | — | missing | No group keys in `common/messages/{ar,en}/*` |

**Area score: 3%**

(Three rows are *partial* only because adjacent solo-booking infrastructure — placement
checks, the charge/earning pipeline, standalone reschedule/cancel/collect, and a source
channel — already exists and could be reused. No group-specific behavior is implemented
anywhere.)

## Verified verdict (adversarial)

Re-checked every row against BOTH the demo and our tree. The analyst's matrix is
**accurate** — the group/party feature is genuinely absent on our side. Independent
confirmation:

- **No group surface anywhere.** `grep -rn "group_booking_id|guest_label|guestLabel|groupBooking|createGroupBooking"` across `common/ frontend/ backend/ api/ console/` → **zero hits**. `common/models/base/Booking.php` (the real attribute home) has no `group_booking_id`/`guest_label`. No migration adds them (`common/migrations/db/` has only social-media migrations for this window).
- **No group i18n.** Only `'Guest' => 'ضيف' / 'Guest'` in `common/messages/{ar,en}/backend.php` — an unrelated customer-type label, not party strings. Row 35 confirmed *missing*.
- **Demo really has the full feature** (so "missing" is a true gap, not a non-feature): `src/portals/bookings/GroupBookingModal.tsx`, `src/portals/shop/GroupBookingsView.tsx`, `createGroupBooking`/`groupBookingId`/`guestLabel` in `store/`, and a dedicated `store/groupBooking.test.ts`.

### Corrected / tightened rows

| # | Analyst status | Verified status | Evidence |
|---|----------------|-----------------|----------|
| 8 | partial | partial (no change) | `frontend/components/BookingScheduleService.php:273` `checkPlacement()` is solo only — single agent/date/duration; no party/sibling orchestration (grep `group|party|sibling` → only an unrelated "grouped by agent" comment at :498). |
| 13 | partial | partial (no change) | `common/migrations/db/m251019_213708_add_shop_earning_tables.php` exists; `common/models/base/ShopEarning.php:259-285` is `booking_method`-aware — but nothing produces group **child** charges to feed it. |
| 32 | partial | partial (no change) | `frontend/views/booking/view.php:74-75` has standalone cancel/reschedule/collect; no party drawer. |
| 34 | partial | partial (no change) — *ourRef corrected* | Source channel IS real, but lives in `common/models/base/Booking.php:86-88` (`BOOKING_METHOD_MOBILE / WALK_IN_SHOP_PORTAL / WALK_IN_SPECIALIST_APP`), not `common/models/Booking.php` as the analyst cited. It drives solo attribution at `ShopEarning.php:285`; no party-level use. Pointer fixed; status unchanged. |

**No overclaims found.** Every "partial" understates rather than overstates group parity: each
describes reusable *solo* substrate, while the group dimension itself is 0% implemented. The
three partials must not be read as partial *group* parity.

**Adjusted area score: 3%** (unchanged — defensible given the reusable solo substrate; the
group feature contributes nothing on its own).
