# Shop · Team / specialists + shifts — Parity Matrix

Demo (canonical): `navagoo-app/src/portals/shop/Team.tsx`, `lib/specialists.ts`,
`store/store.ts`, `store/selectors.ts`, `types.ts`.
Ours: `frontend/controllers/AgentsController.php`, `frontend/views/agents/*`,
`common/models/UserShift.php`, `backend/models/UserForm.php`,
`frontend/controllers/AgentsWalletController.php`.

| # | Demo behavior | Demo ref | Our ref | Status | Note |
|---|---|---|---|---|---|
| 1 | Two-tab page: Specialists / Payroll | Team.tsx:71-84 | AgentsController.php:69-88 | partial | Only a specialists list; no Payroll tab. |
| 2 | Specialist list scoped to active shop | Team.tsx:47,101 | AgentSearch (frontend index) | done | Agents are shop-scoped Users. |
| 3 | List shows wage badge / fixed salary / commission % | Team.tsx:145-172 | index.php:56-196 | missing | No wage/salary/commission fields exist. |
| 4 | List shows status badge + active toggle | Team.tsx:116-126,174-203 | index.php:94-106, AgentsController.php:408-439 | done | Richer: status logs + suspension notice. |
| 5 | Add/Edit specialist (modal) | Team.tsx:458-899 | AgentsController.php:113-274, _form.php | partial | Full-page form, not modal; missing compensation. |
| 6 | Name required to save | Team.tsx:572 | UserForm rules | done | |
| 7 | Title defaults to "Specialist" | Team.tsx:435 | index.php:66 | partial | Display-only fallback; not persisted. |
| 8 | Profile fields: email, mobile, gender, dob, nationality, license | Team.tsx:635-678 | _form.php:749-824 | done | Plus MOL/Mostaqqel/locale/password (extra). |
| 9 | Languages (comma list) | Team.tsx:663-669,426-429 | — | missing | No field/storage (i18n label only). |
| 10 | Calendar-colour palette picker | Team.tsx:680-706, specialists.ts:16-32 | — | missing | No `color` field; day-calendar tint N/A. |
| 11 | Bio | Team.tsx:707-714 | _form.php:828-834 | done | |
| 12 | Photo upload (square) | Team.tsx:488-501 | AgentsController.php:48-62 | done | Server resize 215×215 vs client data-URL. |
| 13 | Compensation: wageType/payCycle/salary/commission%/basis | Team.tsx:717-772 | — | missing | No backing model or UI. |
| 14 | Service linking (specialist ↔ services) | Team.tsx:774-803, store.ts:1863-1879 | AgentsController.php:159-164,245-253 | done | Join table vs service.specialistIds. |
| 15 | Working hours: per-day enable + multiple shifts | Team.tsx:805-895, types.ts:129-133 | _form.php:866-984, UserShift | done | Stored as working_days CSV + dayId-packed UserShift. |
| 16 | Default week Sat–Thu 10:00–22:00, Fri off | specialists.ts:35-41 | _form.php:94-99,905-910 | partial | Ours seeds shop open→close instead. |
| 17 | Add-shift auto-split (~1h midday break) | Team.tsx:910-925 | _form.php:1107-1166 | partial | Ours chains to shop close instead of splitting. |
| 18 | Apply hours to all days | Team.tsx:534-541 | — | missing | No copy-to-all action. |
| 19 | Overnight shift `+1d` badge per shift | Team.tsx:848-855 | _form.php (shop-level only) | partial | Overnight handled at shop level, no per-shift badge. |
| 20 | Remove shift (keep ≥1) / add shift controls | Team.tsx:856-891 | _form.php:953-977,1137-1187 | done | |
| 21 | Delete guard: bookings → deactivate-instead modal | Team.tsx:106-114,220-257 | AgentsController.php:317-324 | missing | Hard delete, no booking-count guard. |
| 22 | Deactivate confirmation | Team.tsx:118-120 | index.php:104 (data-confirm) | done | |
| 23 | Payroll: completed bookings this month → serviced value | Team.tsx:265-274 | — | missing | No payroll page. |
| 24 | Commission base (service_value / net_of_fees) | Team.tsx:275-279, finance.ts:424 | — | missing | |
| 25 | Commission = base × pct/100; total = fixed + commission | Team.tsx:279-280 | — | missing | |
| 26 | Tips owed to specialist (unsettled) | selectors.ts:382-393 | AgentsWalletController.php:103-165 | partial | Separate tips page; not in a payroll total. |
| 27 | Grand totals (pay + tips owed) | Team.tsx:284-285 | — | missing | |
| 28 | Toasts on add/update/remove/activate | Team.tsx:113,122,540,552-556 | session flashes | done | Flash messages instead of toasts. |
| — | Shop-hours bounds enforcement + skip warning | (none) | AgentsController.php:346-406 | new (ours) | Not in demo. |
| — | Shift overlap / order validation | (none) | _form.php:270-300 | new (ours) | Not in demo. |
| — | Mobile uniqueness AJAX check | (none) | AgentsController.php:570-626 | new (ours) | Not in demo. |
| — | Show/Hide visibility flag | (none) | AgentsController.php:442-466 | new (ours) | Not in demo. |
| — | Status logs + suspension notification | (none) | AgentsController.php:428-432,420 | new (ours) | Not in demo. |

## Summary

The **profile + working-hours + service-linking + status** half is largely at parity (and our
shifts/validation are richer). The **compensation + payroll** half — wage type, salary,
commission %, commission basis, pay cycle, and the entire monthly payroll computation — is
**completely absent** on our side; only the tips-owed slice exists, on a separate wallet page.
Secondary gaps: languages field, calendar-colour, apply-to-all, the booking-aware delete guard,
and the per-shift `+1d` overnight badge.

**Area score: 52%**

## Verified verdict (adversarial)

Re-checked every "done"/high claim by opening both refs. Most hold; one "done" is a semantic
mismatch and is downgraded. The big compensation/payroll gaps are confirmed.

| # | Feature | Analyst | Verified | Evidence |
|---|---|---|---|---|
| 2 | Specialist list scoped to active shop | done | **done** | `AgentSearch.php:55-58` filters `user_type=AGENT AND shop_id=identity->shop->id AND status!=DELETED`. Confirmed shop-scoped. |
| 4 | Status badge + active toggle | done | **partial** | Demo toggle (`Team.tsx:116-126`) flips a specialist `status` whose only effect is "hidden from new bookings". Ours (`AgentsController.php:408-439`) flips the **account** `STATUS_ACTIVE/STATUS_NOT_ACTIVE`, fires `agentAccountSuspended` email + writes `UserStatusLogs`. Booking-availability in our app is a **separate** `hide` flag (`actionToggleShowHide:442-466`). So the single demo toggle maps to two different controls on our side with different semantics — not a 1:1 behavioral match. |
| 8/12 | Photo upload (square) | done | **done** | `AgentsController.php:52-57` server-resizes to 215×215 square (`->fit(215,215)`); demo `Team.tsx:488-501` builds a client square data-URL. Equivalent. |
| 10/14 | Service linking specialist↔services | done | **done** | `AgentsController.php:159-164,245-253` writes/replaces `UserShopService(user_id,service_id)` rows; mirrors demo `service.specialistIds` (`store.ts:1873-1879`). Join-table vs array, same effect. |
| 15 | Working hours: per-day enable + multi-shift | done | **partial** | UI + storage exist (`_form.php:866-982`, day toggle + add/remove shift). BUT only `from_time` is day-prefixed (`"$dayId:HH:MM"`, `AgentsController.php:385`); `to_time` is stored bare (`:386`). There is no `day` column on `user_shift`. Overnight reconstruction and any consumer that needs the shift's day from `to_time` cannot recover it — the day is encoded asymmetrically. Round-trips for the form's own display only. Downgrade to partial on storage fidelity. |
| 16 | Default week Sat–Thu 10:00–22:00, Fri off | partial | **partial (confirm)** | Confirmed: ours seeds shop `open_at→close_at` (`_form.php:94-99`), not the fixed 10:00–22:00 / Fri-off week from `specialists.ts:35-41`. |
| 17 | Add-shift auto-split (~1h midday break) | partial | **partial (confirm)** | Confirmed: `nextShiftDefaults` (`_form.php:1107-1134`) chains a new slot from the last end to shop-close; no `withAddedShift`-style midday split (`Team.tsx:910-925`). |
| 19 | Overnight `+1d` badge per shift | partial | **missing** | Demo renders a per-shift `+1d` badge whenever `end <= start` (`Team.tsx:848-855`). Ours has **no per-shift badge at all** in the slot template (`_form.php:932-958, 1144-1160`); overnight is only a shop-level note + JS window logic. No equivalent UI affordance → downgrade partial→missing. |
| 1/5 | Two-tab page / Add-Edit | partial | **partial (confirm)** | No Payroll tab; full-page form (not modal); no compensation section. Confirmed. |
| 3,9,10*,13,18,21,23,24,25,27 | wage badge / languages / colour / compensation / apply-to-all / delete-guard / payroll / commission math / grand totals | missing | **missing (confirm)** | Grep across `frontend/views/agents/`, `UserForm.php`, `UserProfile.php`, `User.php` found **zero** `wage/salary/commission/pay_cycle` props, no per-specialist languages field/storage, no `color`/palette, no apply-to-all action, no booking-count delete guard (`actionDelete:317-324` hard-deletes unconditionally), and no payroll page/computation. All confirmed absent. |
| 26 | Tips owed to specialist | partial | **partial (confirm)** | `AgentsWalletController::actionSpecialistTips` is a withdrawals/settlement-history grid (`:103-168`), not the demo's unsettled-tips-folded-into-payroll-total (`selectors.ts:382-393` + `Team.tsx:281-285`). Different model (`AgentWithdrawal`/`Withdrawal`) and not part of any payroll total. |

### Net effect
Two further downgrades vs the analyst: **#4 done→partial** (status-toggle semantics differ +
split across two controls) and **#19 partial→missing** (no per-shift badge exists), plus a
storage-fidelity downgrade on **#15 done→partial** (asymmetric day-encoding; `to_time` has no
day). Everything else verified as claimed. The compensation + payroll half remains entirely
absent, which is the dominant driver of the score.

**Adjusted area score: 46%**
</content>
