# Shop · Team / specialists + shifts — Business Rules

Numbered, implementable rules the demo encodes. "Ours" notes whether we honour each.
Refs: demo `Team.tsx`, `lib/specialists.ts`, `lib/finance.ts`, `store/*.ts`, `types.ts`.

## Identity & scoping
1. **Shop scoping** — a specialist belongs to exactly one shop; lists/payroll only show
   `specialist.shopId === activeShopId` (`Team.tsx:47,101,266`). Ours: agents are
   `User(user_type=AGENT, shop_id=owner shop)`, scoped via `AgentSearch`. **Honoured.**
2. **Name required** — save is disabled until `name.trim()` is non-empty (`Team.tsx:572`).
   Ours: `full_name` required in `UserForm` rules. **Honoured.**
3. **Title defaults to "Specialist"** when left blank (`Team.tsx:435`). Ours: role/title is free;
   index falls back to "Specialist" label only for display (`index.php:66`). **Partial** (display-only).
4. **Languages** = comma-split, trimmed, empties dropped, undefined if none (`Team.tsx:426-429,445`).
   Ours: **not stored** (no field). **Missing.**

## Compensation (entirely missing on our side)
5. **Wage type ∈ {fixed, commission, both}** (`types.ts:107`). Default `both` (`Team.tsx:414`).
6. **When wageType = commission**, fixed salary is forced to 0; **when = fixed**, commission % is
   forced to 0 (`Team.tsx:430-431,450-451`).
7. **Commission basis ∈ {service_value, net_of_fees}**, default `service_value`
   (`types.ts:111,118`).
8. **Pay cycle ∈ {monthly, weekly, biweekly}**, default `monthly` (`types.ts:112,119`).
9. **Salary/commission % coerce to Number, fallback 0** (`Team.tsx:450-451`).
   Rules 5–9 have **no backing model, form, or storage on our side. All MISSING.**

## Payroll math (missing on our side)
10. **Payroll window = current month** of `simNow` (`Team.tsx:265,272`).
11. **Only `completed` bookings** for the specialist count toward serviced value (`Team.tsx:267-273`).
12. **Serviced value** = Σ `bookingValue` of those bookings, rounded to 2 (`Team.tsx:274`).
13. **Commission base** = serviced value, OR `Σ(bookingValue − bookingFeesIncurred)` when basis is
    `net_of_fees` (`Team.tsx:275-279`; fees = marketing + payment-processing, `lib/finance.ts:424`).
14. **Commission** = `round2(base × commissionPct/100)` (`Team.tsx:279`).
15. **Total pay** = `round2(fixedSalary + commission)` (`Team.tsx:280`).
16. **Tips owed** = Σ unsettled (`specialistTipSettled !== true`) positive `specialistTip`
    (`selectors.ts:382-393`). Ours: a tips summary exists in `agents-wallet/specialist-tips`
    (`AgentsWalletController.php:103-165`) — **only this rule is partially covered**; rules 10–15
    are **MISSING**.
17. **Grand totals** for pay and tips owed across specialists (`Team.tsx:284-285`). **Missing.**

## Status / lifecycle
18. **Status ∈ {active, inactive}**, default active (`types.ts:140`, `Team.tsx:403`).
    Ours: `User.status` ACTIVE / NOT_ACTIVE. **Honoured.**
19. **Deactivating requires confirmation** ("hidden from new bookings until reactivated")
    (`Team.tsx:118-120`). Ours: index toggle uses `data-confirm` + POST (`index.php:104`);
    writes a status log and fires a suspension notification (`AgentsController.php:418-432`).
    **Honoured (richer).**
20. **Delete guard** — if the specialist has ≥1 booking, do NOT hard-delete; offer
    "deactivate instead" vs "delete anyway" (`Team.tsx:106-114,220-257`). Ours: `actionDelete`
    hard-deletes unconditionally (`AgentsController.php:317-324`). **MISSING.**

## Working hours / shifts
21. **Default week** for a new specialist = open Sat–Thu 10:00–22:00 (single shift), Friday off
    (`specialists.ts:35-41`). Ours: seeds first shift as shop open→close, days from
    `working_days` CSV (`_form.php:94-99,905-910`). **Divergent default.**
22. **A day may have multiple shifts** (breaks). Adding to a single ≥2h block splits it around a
    ~1h midday break; otherwise appends a 1h block (`Team.tsx:910-925`). Ours: supports multiple
    shifts but the add-shift chains from the previous end to shop close (`_form.php:1107-1166`).
    **Multi-shift honoured; auto-split logic divergent.**
23. **Overnight shift** when `end <= start` → conceptually +1 day, shown as `+1d`
    (`Team.tsx:848-855`). Ours: no per-shift `+1d`; overnight is a **shop-level** property used
    only in bounds math. **Partial / divergent.**
24. **Calendar colour** chosen from a fixed 10-hue palette, falls back to a stable
    by-index tint (`specialists.ts:16-32`, `Team.tsx:680-706`). Ours: **no color field.** **Missing.**

## Rules NEW on our side (not in the demo — keep, but record)
25. **Shop-hours bounds**: each shift must fall within the shop's `open_at`/`close_at`
    (overnight-aware); out-of-bounds shifts are silently dropped on save and a warning flash shown
    (`AgentsController.php:346-406`, client mirror `_form.php:219-301`).
26. **No overlapping shifts** on the same day; **from < to** ordering enforced client-side
    (`_form.php:270-300`).
27. **Mobile uniqueness** — AJAX-checked, blocks submit if a duplicate `966…` number exists
    (`AgentsController.php:570-626`, `_form.php:104-195`).
28. **Birth date must be in the past** (`AgentsController.php:290-294`).
29. **Show/Hide visibility** flag independent of active status (`AgentsController.php:442-466`).
30. **Avatar** server-resized to 215×215 (`AgentsController.php:55`).
</content>
