# Admin · People — Parity Matrix

Demo = React `src/portals/admin/People.tsx` (canonical). Our = Yii2 backend.

| Demo behavior | Demo ref | Our ref (file:line) | Status | Note |
|---|---|---|---|---|
| Single "People" page with 4-tab Segmented (Customers/Classifications/Specialists/Users) | People.tsx:17-44 | — | missing | We have 3 separate admin grids (User/Agent/Managers controllers), no unified page or tab switch |
| Customers list (name+avatar, mobile, gender) | People.tsx:48-67 | backend/controllers/UserController.php:65 · backend/views/user/index.php:120 | partial | Columns present except avatar in name cell; adds Mode/Status |
| Customer "Bookings" derived count | People.tsx:69 | — | missing | No bookings-count column in our customer grid |
| Customer "Shops" distinct count | People.tsx:76 | — | missing | No distinct-shops column |
| Classifications tab (per customer×shop row) | People.tsx:88-149 | — | missing | No admin classifications surface at all |
| Classification badge + reason + set-on date | People.tsx:111-127 | — | missing | No ClassificationBadge / reason rendering on admin |
| Admin **override** classification w/ mandatory reason | People.tsx:152-216 · store.ts:1717 | — | missing | No `overrideClassification` analog in backend |
| Override stamps `overriddenBy` + `overrideReason` (audit) | store.ts:1722 | — | missing | No audit trail for classification on our side |
| Override **re-derives marketing-fee charges** | store.ts:1750 · lib/finance.ts:132 | — | missing | No admin trigger to re-derive charges |
| Classification default = shop_owned | store.ts:351 | common/models/CustomerFreeze.php (shop-side, frontend) | partial | Freeze list = binary fee exemption only; not a 2-value classification |
| Auto-classify at booking (walk-in/deep-link/freeze/app) | lib/finance.ts:308-312 | frontend CustomerFreeze (freeze_list path only) | partial | Only the freeze-list exemption exists; other paths not modeled here |
| Specialists list (name, shop, title, wage type, status) | People.tsx:218-285 | backend/controllers/AgentController.php:58 · backend/views/agent/index.php | partial | Missing **wage type** + **title** columns; adds Mode/Status toggles |
| Specialist status active/inactive badge | People.tsx:262-272 | backend/views/agent/index.php:138 · common/models/User.php:64 | done | Status surfaced + toggle (richer than demo) |
| Users list (name, email, role badge, shop/Platform, last active) | People.tsx:289-339 | backend/controllers/ManagersController.php:45 · backend/views/managers/index.php | partial | Role vocab differs; no "Platform" label / last-active match |
| Role enum owner/manager/admin/support | types.ts:567 | common/models/User.php:84 (user/manager/administrator/shopOwner) | partial | Vocabulary mismatch |
| Role assignment (RBAC) | (store seeds users) | backend/models/ManagerForm.php:147-169 | partial | Writes CSV `roles`; live `authManager->assign()` is **commented out** — verify auth_assignment rows are written |
| Users tab read-only | People.tsx:289 | ManagersController create/update/delete:68,111,205 | done | We exceed demo: full CRUD on platform users |
| RTL / bilingual | src/i18n | Yii::t('backend'/'frontend') across views | done | Bilingual strings present throughout |
| DataTable empty state | shared Table.tsx | aurora card-table views | partial | Our tables render rows; empty-state styling not verified |

## Summary
Our app is strong on the **Users/Managers** (full CRUD + RBAC scaffolding) and **Specialists/
Customers** listings (with extra Mode/Status toggles and export). It is weak on the demo's
**derived count columns** (bookings/shops, wage type) and **entirely missing the
Classifications tab** — the admin-side, reason-logged, charge-re-deriving classification
override is the single biggest gap. The classification concept only half-exists shop-side as
`CustomerFreeze` (binary fee exemption), and RBAC role assignment appears partly disabled
(commented-out `assign()`), needing verification.

Area score: 45%

## Verified verdict (adversarial)

Re-checked every "done"/high claim against demo `src/portals/admin/People.tsx` and our
backend. Two overclaims found; the analyst's RBAC *note* is factually inverted.

| Claim (analyst) | Analyst status | Verified status | Evidence |
|---|---|---|---|
| Platform user CRUD | done | **partial** | CRUD actions + views all exist (ManagersController.php:68/111/205; views/managers/{create,update,view,_form}.php). BUT the *role you pick is never applied to RBAC*: `ManagerForm::save()` (backend/models/ManagerForm.php:163-166) does `revokeAll()` then `assign($auth->getRole(session 'UserRole'))`, and `UserRole` is hard-set to `"manager"` (ManagersController.php:51, ManagerForm.php:131). So every created/updated platform user is assigned the **manager** role regardless of the `role` dropdown (administrator/shopOwner/user). Create/Update mechanically succeed, but you cannot actually provision a non-manager platform user. Demo Users tab is read-only, so we exceed it on surface area but the write path is defective → partial, not done. |
| Role assignment (RBAC) | partial | partial (kept) — **note corrected** | Analyst note says "live `authManager->assign()` commented out — verify auth_assignment is written." That is **wrong**: the loop over `$this->roles` IS commented out (ManagerForm.php:148-150), but a live `assign()` runs unconditionally at ManagerForm.php:166. auth_assignment *is* written — but always to `"manager"` (see above), never to the selected role. The CSV `user.roles` column gets the selected values (ManagerForm.php:152) while auth_assignment diverges → real RBAC integrity gap. Status stays partial; severity is higher than the analyst implied. |
| Specialist active/inactive status | done | done (confirmed) | Toggle chip in agent/index.php:359-369 → `AgentController::actionToggleStatus` (AgentController.php:131-162): flips STATUS_ACTIVE↔STATUS_NOT_ACTIVE, persists, writes a `UserStatusLogs` audit row (admin_id + status), POST-guarded (behaviors:47). Demo only renders a read-only badge (People.tsx:250-257). We genuinely exceed the demo here. |
| Customers list (name, mobile, gender) | partial | partial (confirmed) | user/index.php headers: Customer ID, Full Name, Email, Mobile, Gender, Mode, Status (lines 286-290). Demo uses split `name`+avatar (People.tsx:48-57); ours = `full_name`, no avatar. Demo's derived **Bookings** (People.tsx:65-69) and distinct **Shops** (71-76) count columns are absent in ours — confirmed missing. |
| Specialists list (name, shop, title, wage type, status) | partial | partial (confirmed) | agent/index.php headers (292-302): ID, Full Name, Gender, Mobile, Email, Shop, Mode, Status, Created, Updated, Actions. **No Title column, no Wage-type column** — demo has both (People.tsx:239-248). Confirmed missing. |
| Users list (name, email, role badge, shop/Platform, last active) | partial | partial (confirmed) | managers/index.php headers (164-170): #, Username, Mobile, Email, Role, Created At. No shop/**Platform** column (People.tsx:304-312) and no **last-active** column — ours shows Created At instead. Role badge renders raw `user.role` string (no demo's tone-mapped enum). Confirmed partial. |
| Unified People page / Classifications tab / override / re-derive charges | missing | missing (confirmed) | No unified tabbed People page; three separate controllers/grids. `grep` finds no `overrideClassification`, no `classificationReason`/`overriddenBy` modelling anywhere in backend/common. CustomerFreeze (frontend, shop-side) is a binary fee-exemption list, not the demo's 2-value shop_owned/navagoo_sourced classification with logged override + charge re-derivation. All confirmed missing. |

### Adjusted area score

The analyst's 45% slightly *over*-credits the area: "Platform user CRUD" was scored as a full
"done" but its write path mis-assigns RBAC roles (every platform user becomes a manager), so a
headline strength is actually a defect. The classification engine — the single largest demo
surface (Classifications tab + logged override + marketing-fee re-derivation) — is entirely
absent, and the two list tabs are each missing demo columns. Downgrading Platform-user-CRUD
done→partial and accounting for the RBAC integrity gap:

**Adjusted area score: 40%**
