# Admin · People — Business Rules (numbered, implementable)

Derived from `src/portals/admin/People.tsx`, `store/store.ts`, `lib/finance.ts`, `types.ts`.

## Classification & marketing-fee rules
1. **Classification is per (customer-mobile, shop).** One `CustomerClassification` row keyed
   by `(customerMobile, shopId)` (`types.ts:282`, `store.ts:1719`). Override upserts that row.
2. **Two classification values:** `shop_owned` (no marketing fee) and `navagoo_sourced`
   (marketing fee applies) (`types.ts:275`).
3. **Default = shop_owned.** When no classification row exists, treat as `shop_owned`
   (`store.ts:351 classificationOf`).
4. **Auto-classification at booking time** (`lib/finance.ts:308`): walk-in → shop_owned/
   `shop_admin_walkin`; deep-link → shop_owned/`deep_link`; freeze-list → shop_owned/
   `freeze_list`; app first booking → navagoo_sourced/`app_first_booking`; else shop_owned.
5. **Marketing fee fires only for `navagoo_sourced`** (`lib/finance.ts:132`) at the shop's
   `marketingFeeRatePct` on the chosen basis.
6. **Admin override requires a mandatory, non-empty reason.** Apply button disabled until the
   reason is provided (`People.tsx:207`); empty reason aborts (`People.tsx:166`).
7. **Override is audited.** Sets `overriddenBy = 'Navagoo Admin'` and `overrideReason` on the
   row (`store.ts:1722`) and appends an activity-log entry (`store.ts`).
8. **Override re-derives charges.** Every booking for that customer at that shop is re-run
   through `rederiveCharges` so marketing-fee charges are added/reversed to match the new
   classification (`store.ts:1750`). Classifications are otherwise "immutable once set"
   (`People.tsx:140`) — override is the only mutation path.

## Specialist (people listing) rules
9. **Wage type ∈ {fixed, commission, both}** (`types.ts:107`); compensation also carries
   `commissionBasis` (`service_value` default | `net_of_fees`) and `payCycle`
   (`monthly`/`weekly`/`biweekly`) (`types.ts:113`).
10. **Specialist status ∈ {active, inactive}** (`types.ts:108`); listing badges teal/slate.
11. **Specialist is shop-scoped** via `shopId` (`types.ts:136`).

## Platform user / role rules
12. **User role ∈ {owner, manager, admin, support}** (`types.ts:567`).
13. **Shop scoping:** a user with no `shopId` is a **platform** user; otherwise scoped to one
    shop (`People.tsx:330`). Owner/manager are shop-scoped; admin/support are platform.

## Derived display rules
14. **Customer "Bookings" = count of bookings with `customerId === c.id`** (`People.tsx:69`).
15. **Customer "Shops" = distinct `shopId` over that customer's bookings** (`People.tsx:76`).
16. **Customer gender = {male, female}** (`types.ts:266`), shown title-cased.

## Our-side mapping / divergences
- Rules 1–8 (classification + override + re-derivation + audit): **not implemented in admin.**
  Partial shop-side analog is `CustomerFreeze` (binary fee exemption; `frontend`), which
  covers rule-4's `freeze_list` path only, with no two-value classification and no admin
  override/audit.
- Rules 9–11: specialist data exists on `common\models\User` (`USER_TYPE_AGENT`) but wage
  type/compensation is not surfaced in the admin people grid (`agent/index.php`).
- Rules 12–13: our roles are `user|manager|administrator|shopOwner` (`User.php:84`) — a
  vocabulary mismatch; assignment writes a CSV `roles` string and the live RBAC `assign()`
  is commented out (`ManagerForm.php:149,164-169`) — verify auth_assignment is actually
  written.
- Rules 14–16: customer counts (14,15) not rendered in our grid; gender (16) is shown.
