# Admin · Shops — Logic & Flows Parity

Demo (canonical): `/private/tmp/Navagoo_MI_dev/navagoo-app/src/portals/admin/Shops.tsx`
Store: `store/store.ts` · Selectors: `store/selectors.ts` · Finance: `lib/finance.ts` · Types: `types.ts`

Ours: `backend/controllers/ShopController.php`, `backend/views/shop/*`, `common/models/Shop.php` (+ `common/models/base/Shop.php`).

---

## 1. Page composition (4 tabs)

Demo renders a single `AdminShops` page with a `Segmented` switcher over **four** views (`Shops.tsx:26,49-63`):

1. `shops` — directory `DataTable` (`ShopsTable`, `Shops.tsx:89`)
2. `requests` — onboarding queue (`RequestsQueue`, `Shops.tsx:351`)
3. `plans` — subscription plan cards + create (`PlansView`, `Shops.tsx:543`)
4. `subscriptions` — per-shop subscription dashboard (`SubscriptionDashboard`, `Shops.tsx:660`)

**Ours:** only the directory exists. `ShopController::actionIndex` (`backend/controllers/ShopController.php:125`) renders one flat table (`backend/views/shop/index.php`). There is **no** requests tab, **no** plans tab, **no** subscriptions dashboard. The onboarding "queue" is approximated by clicking the status chip of a NEW/RE_ORDER/AWAITING_CONTRACT shop, which links to `actionActivate` (`index.php:302-306`). Subscription/plan logic does **not exist anywhere** in the backend (no model, controller, or migration — confirmed `ls common/models | grep -iE plan|subscri` → none).

## 2. Create shop

Demo `createShop` (`store/store.ts:1086`): generates `NSH-…` id (`uniqueShopId`), computes a **grace window** = `simNow + config.graceWindowDays` (`store.ts:1089-1091`), builds a **deep link** `https://navagoo.app/s/<slug>` (`store.ts:1113`) and a **shopToken** (`store.ts:1114`), seeds payment toggles + `depositPct: 30`, sets `status:'inactive'`, `verificationStatus:'requested'`, and creates an owner `User` with `passwordSet:false` (`store.ts:1137-1140`). Toast advertises "deep link + grace window issued" (`Shops.tsx:236`). After create the UI **immediately opens the Activate modal** (Path 2, `Shops.tsx:33-35,239-243`).

**Ours:** `actionCreate` (`backend/controllers/ShopController.php:183`) builds a `Shop` + `User` + `UserProfile` via `signup()`. Admin-created shops are forced `STATUS_ACTIVE` + `VERIFICATION_STATUS_VERIFIED` (`ShopController.php:206-207`) — the opposite of the demo's `inactive/requested`. Hard-codes Arabic/English cancel_terms (`ShopController.php:208-223`), assigns categories, syncs service-category assignments, handles document uploads, emails the owner. **No grace window, no deep link, no shop token** are generated (none of these columns/fields exist — `grep grace|deep_link|shop_token` → none). No auto-open of an activation step after create.

## 3. Update / edit shop

Demo `updateShop` (`store.ts:1146`) is a shallow patch merge; the same `ShopFormModal` serves create & edit (`Shops.tsx:204`). Save is gated on `commercialName && email` (`Shops.tsx:255`).

**Ours:** `actionUpdate` (`ShopController.php:331`) loads `Shop` + owner + profile via `loadAll`/`saveAll`, handles doc uploads. Far richer (addresses, districts, schedule, payment settings) but driven by Yii model validation rules, not the demo's 2-field gate.

## 4. Activate / approve onboarding

Demo `activateShop` (`store.ts:1185`): writes uploaded `onboardingDocs`, `vatRegistered`, and a **commercials block** (marketingFeeRatePct, minWithdrawalAmount, settlementHoldDays, maxDepositPct, carryForwardThreshold, bankName, iban), sets `verificationStatus:'activated_pending_auth'` (NOT active yet), and **queues an email** with link `#/activate?shop=<id>` for the owner to authenticate (`store.ts:1196-1205`). The owner then does OTP (`sendActivationOtp`/`verifyOtp`, `store.ts:1211-1228`) and accepts terms (`acceptShopTerms`, `store.ts:1242-1245`) which flips to `verificationStatus:'active' + status:'active'`.

**Ours:** `actionActivate` (`ShopController.php:947`) saves the model, handles doc uploads, sets `verification_status = VERIFIED`, then a branch decides status: if a contract file exists and `isContractAccepted()` → `STATUS_ACTIVE`; else `STATUS_AWAITING_CONTRACT`; no contract → `STATUS_ACTIVE` directly (`ShopController.php:983-1010`). Sends an authentication email + activation email. Conceptually close (admin activates → owner must authenticate), but our **commercials block is incomplete**: no `marketingFeeRatePct`, no `settlementHoldDays`/`carryForwardThreshold` captured in the activate flow (only the edit `_form` has the elapsed-period + min-withdrawal + deposit-override fields). The verification state machine differs (demo: requested→activated_pending_auth→active/rejected; ours: NEW/RE_ORDER/AWAITING_CONTRACT/ACTIVE/REJECTED/NOT_ACTIVE numeric statuses + a separate `verification_status` 0/1 + `verification_contract_status` 0/1).

## 5. Reject request

Demo `rejectShopRequest` (`store.ts:1209`) simply sets `verificationStatus:'rejected'` with a confirm dialog + toast (`Shops.tsx:403-419`).

**Ours:** `actionStatus` (`ShopController.php:736`) / `actionToggleApprovalStatus` (`ShopController.php:795`) sets `STATUS_REJECTED`, captures a **reason**, and emails the owner the reason. Ours is richer (reason + email) — demo has no reason field.

## 6. Activate / deactivate toggle (live shops)

Demo (`Shops.tsx:138-167`): in the Status column, if not active → "Pending" badge; if in grace window → "Grace" badge (`isInGraceWindow`, `lib/finance.ts:293`); else a `Toggle` flips `status` active↔inactive. Deactivate shows a confirm ("hidden from customer app; existing bookings not affected"); activate is immediate. Both toast.

**Ours:** the status cell is a link to `actionStatus`/`actionActivate` (`index.php:299-308`), plus a separate `actionToggleApprovalStatus`. There is a `actionToggleDemo` (`ShopController.php:847`) for Live/Demo mode (an extra concept the demo lacks). **No grace-window concept** at all — `isInGraceWindow` has no equivalent (no `graceWindowEndsAt` column).

## 7. Plans (subscription) logic

Demo `PlansView` (`Shops.tsx:543`): cards from `state.plans`; `addPlan` (`store.ts:1784`) auto-derives 6-mo = `monthly*6*0.9` and 12-mo = `monthly*12*0.8` (10%/20% discounts, `Shops.tsx:608-610`); `deletePlan` (`store.ts:1787`). `subscriptionPrice` (`lib/finance.ts:648`) returns price by period.

**Ours:** **MISSING entirely.** No plan model/table/UI.

## 8. Subscription dashboard logic

Demo `SubscriptionDashboard` (`Shops.tsx:660`): per shop resolves `subscriptionForShop` (`selectors.ts:41`) + `planById` (`selectors.ts:40`), shows plan name, status badge (active/free_period/cancelled/flagged), next billing date + `relativeDays`, masked `cardLast4`.

**Ours:** **MISSING entirely.** No subscription model/table/UI.

## 9. Requests-awaiting-review selector

Demo `shopsAwaitingReview` (`selectors.ts:456`) = shops with `verificationStatus === 'requested'`; count drives the tab badge (`Shops.tsx:55`).

**Ours:** approximated by `ShopSearch[status_new]` filter / the "New Shops" stat card counting `STATUS_NEW|RE_ORDER|AWAITING_CONTRACT` (`index.php:38`). No dedicated queue list/selector.

---

### Logic gaps summary
- Grace window, deep link, shop token: **not modeled**.
- Per-shop `marketingFeeRatePct` and `carryForwardThreshold`: **not modeled** (marketing fee is per-booking on `ShopEarning`, not a shop rate).
- Subscription plans + subscriptions: **entirely absent**.
- Activate flow captures fewer commercials than the demo.
- Demo extras we DON'T have: 4-tab IA, auto-open activate after create, grace badge.
- We have extras the demo lacks: Live/Demo mode toggle, rejection reason + email, full address/district/schedule editing.
