# Shop · Settings — Parity Matrix

Demo: `/private/tmp/Navagoo_MI_dev/navagoo-app/src/portals/shop/Settings.tsx` (+ `types.ts`,
`store/selectors.ts`, `lib/schedule.ts`).
Ours: `frontend/controllers/{ShopSettingsController,PaymentSettingsController}.php`,
`frontend/views/{shop-settings,payment-settings}/index.php`, `common/models/ShopPaymentSettings.php`.

| Demo behavior | Demo ref | Our ref | Status | Note |
|---|---|---|---|---|
| Unified Settings page, 5 tabs (Segmented) | Settings.tsx:43 | — | missing | Ours split into 2 routes; no commercials/notifications surface |
| General: edit commercial name | Settings.tsx:54 | views/shop-settings/index.php:193 (`title`) | done | commercialName≈title |
| General: edit email | Settings.tsx:62 | — | missing | No email field on shop settings |
| General: edit owner mobile | Settings.tsx:65 | views/shop-settings/index.php:206 (`mobile`) | done | |
| General: bank name (read-only) | Settings.tsx:69 | — | missing | No bank field |
| General: IBAN (read-only) | Settings.tsx:72 | — | missing | No IBAN field |
| Save General + confirmation | Settings.tsx:73 | ShopSettingsController.php:126 + flash | done | Real DB save vs in-memory |
| Opening hours (open/close time) | Settings.tsx:101/113 | views/shop-settings/index.php:227/231 | done | kartik TimePicker |
| Overnight (close≤open) "+1d" hint | Settings.tsx:96; schedule.ts:93 | Shop::isOvernight(); view:234, JS:713 | done | Server + JS replicated |
| Calendar slot size {5,10,15,30} | Settings.tsx:120 | view:323; Shop::getSlotSizeOptions() | done | |
| Slot size locked by admin policy | Settings.tsx:118 | view:318; Shop::isSlotTimeStepLocked() | done | Ours adds admin/manager bypass |
| Time format 12h/24h toggle | Settings.tsx:131; types.ts:70 | — | missing | No per-shop time format setting |
| Payments: Pay 100% Online (app) | Settings.tsx:159 | ShopPaymentSettings.php:39; pmt view:114 | done | |
| Payments: Pay Deposit (app) | Settings.tsx:172 | ShopPaymentSettings.php:38; pmt view:123 | done | |
| Payments: Pay on Visit (app) | Settings.tsx:186 | ShopPaymentSettings.php:37; pmt view:174 | done | |
| At least one method enabled | (implicit) | ShopPaymentSettings.php:72 | done | Ours enforces server-side |
| Deposit % required when deposit on | types.ts:47 | ShopPaymentSettings.php:83 | done | |
| Deposit % effective cap (shop override else global) | Settings.tsx:198 | ShopPaymentSettings.php:112 | done | Override→global→100 |
| Deposit % live clamp on input | Settings.tsx:200 | pmt view:101 (warn) + submit validate | partial | Ours validates on submit, no live clamp |
| Walk-in per-method toggles | Settings.tsx:165/179/193; types.ts:53 | — | missing | No walkin* columns/toggles |
| App-vs-Walk-in explanatory footnote | Settings.tsx:213 | — | missing | |
| Commercials: subscription plan badge | Settings.tsx:218; selectors.ts:40 | — | missing | No plan field surfaced |
| Commercials: subscription status badge | Settings.tsx:222 | — | missing | |
| Commercials: next billing date | Settings.tsx:237 | — | missing | |
| Commercials: marketing fee rate | Settings.tsx:240; types.ts:26 | — | missing | No column on Shop |
| Commercials: processing fee (rate+fixed) | Settings.tsx:241; types.ts:27 | — | missing | |
| Commercials: VAT registered | Settings.tsx:246; types.ts:25 | — | missing | No vat_registered field |
| Commercials: min withdrawal | Settings.tsx:247; types.ts:29 | — | missing | Exists in earnings logic, not as setting |
| Commercials: settlement hold days | Settings.tsx:248; types.ts:31 | — | missing | Exists in earnings logic, not as setting |
| Commercials read-only ("contact Navagoo") | Settings.tsx:250 | — | missing | |
| Notifications: SMS/WhatsApp/Email/Push toggles | Settings.tsx:257–293 | — | missing | Demo itself local-only/unpersisted |
| Notifications: per-message fee descriptions | Settings.tsx:259/266; types.ts:533 | — | missing | |
| Active-shop scoping | Settings.tsx:21 | controllers L124 / L49 | done | |
| Auth required (shop owner) | (portal) | PaymentSettingsController.php:27 | done | |
| Flash / error summary on save | toast | both views (flash + errorSummary) | done | Stronger than demo |
| Bilingual ar/en profile + about/terms | (commercialNameAr only) | shop-settings view:127, bilingual fields | done | Ahead of demo |

### Extra in OURS, absent in demo Settings page
- Media (cover image + gallery upload), Google Maps location picker (lat/lng + reverse geocode),
  city/district dependent dropdowns, refund window (start/end/partial value), no-show threshold,
  website link, bilingual about + cancellation terms. (Some of these live in the demo's *onboarding*
  flow, not its Settings page.)

---

Area score: 48%

Scoring rationale: Scheduling and Payment-methods cores (the genuinely interactive tabs) are at
strong parity (overnight, slot lock, deposit cap, at-least-one validation all ✅). But three of the
demo's five tabs are entirely missing on our side — **Commercials** (8 read-only fields, no backing
Shop columns), **Notifications** (4 channel toggles), and the **Walk-in** payment dimension — plus
General's email/bank/IBAN and the time-format toggle. Roughly 18 of 38 tracked behaviors are done.

---

## Verified verdict (adversarial)

Re-checked every "done"/high claim by opening both refs. Confirmed the scheduling +
payment-methods cores hold up; downgraded two scoping/contact claims that map to the
wrong field or skip an auth gate. Net effect on score is small (the area was already
dominated by the 3 missing tabs), so the adjusted score lands a touch below the analyst's.

| Demo behavior | Claimed | Verdict | Evidence |
|---|---|---|---|
| General: edit commercial name | done | **done (caveat)** | View binds `Shop.title` (shop-settings/index.php:193). A dedicated `commercial_name` column also exists and at signup `title = commercial_name` (SignupForm.php:146-147); display falls back `commercial_name ?: title` (agents-wallet/_form.php:86). Editing `title` is a valid analog but the two can diverge post-signup. |
| General: edit owner mobile | done | **partial** | View edits `Shop.mobile` — the customer-facing shop contact number (label "رقم جوال التواصل للمتجر (يظهر للعملاء)", index.php:205-206). Demo field is `ownerMobile`. Our model has a SEPARATE `owner_mobile` column (Shop.php:294, label "رقم الجوال لصاحب المتجر") that the settings view never surfaces. Wrong field → not a clean match. |
| Opening/closing hours | done | **done** | kartik TimePicker → `open_at`/`close_at` (index.php:227/231); real model attrs. |
| Overnight (close≤open) +1d hint | done | **done** | `Shop::isOvernight()` = `strtotime(close) <= strtotime(open)` (base/Shop.php:906-912) matches demo `closeTime <= openTime` (schedule.ts:93). Server flag + JS `refreshOvernightHint` both replicate it. |
| Calendar slot size {5,10,15,30} | done | **done** | `Shop::getSlotSizeOptions()` returns exactly {5,10,15,30} (base/Shop.php:720-727); range-validated (base/Shop.php:220). |
| Slot size admin lock | done | **done** | Real `slot_time_step_locked` boolean column (base/Shop.php:214, `isSlotTimeStepLocked()` L1127). View disables + lock hint, with admin/manager bypass (index.php:318). Demo only disables on `slotStepLocked` (Settings.tsx:123) — bypass is an enhancement, not a regression. |
| Payment methods: 3 app toggles | done | **done** | `pay_online/deposit/on_visit_enabled` columns (ShopPaymentSettings.php:60); rendered as pill switches over real checkboxes (payment-settings/index.php:114/123/174). |
| At least one method enabled | done | **done** | `validateAtLeastOneEnabled` server rule (ShopPaymentSettings.php:72-77). Stronger than demo (demo has no enforcement). |
| Deposit % required when deposit on | done | **done** | `validateDepositPercentage`, skipOnEmpty=false (ShopPaymentSettings.php:83-91). |
| Deposit % effective cap (override else global) | done | **done** | `getEffectiveMaxDeposit()`: shop `max_deposit_percent_override` → `Settings.max_deposit_percent` → hard cap 100 (ShopPaymentSettings.php:112-124). Matches demo `shop.maxDepositPct ?? config.maxDepositPct` (Settings.tsx:197). |
| Deposit % live clamp on input | partial | **partial (confirmed)** | Ours: HTML `min/max` attrs + submit-time validation + warn banner (payment-settings/index.php:101-109,157-164), no JS clamp. Demo clamps on every keystroke `Math.min(max, Math.max(0,n))` (Settings.tsx:200-207). HTML `max` does not hard-clamp typed input → analyst's "partial" stands. |
| Active-shop scoping + auth | done | **partial** | PaymentSettingsController has real `AccessControl roles=>['@']` (L26-29) ✓. BUT (a) ShopSettingsController has NO AccessControl behavior — guests hit a fatal on `identity->shop` (controller L124) rather than a clean redirect; (b) scoping is single-shop-per-user (`User::getShop()` hasOne via `user_id`, User.php:477), so there is no demo-style `activeShopId` switch. Acceptable for the single-shop case, but not a full match for the demo's active-shop model + uneven auth across the two controllers. |
| Flash + error summary on save | done | **done** | Both views render `errorSummary` + flash banner with success/warn tone (index.php:118-124 / payment-settings:93-99). Real DB persistence. |
| Bilingual ar/en profile + about/terms | done | **done** | MyMultiLanguageActiveField on title/address + about/about_en, cancel_terms/_en (index.php:193,265,369,372,380,383). Ahead of demo (demo has commercialNameAr only). |

### Downgrades
- **Owner mobile**: done → **partial** (binds customer-facing `mobile`, not the `owner_mobile` column the demo's `ownerMobile` maps to).
- **Active-shop scoping + auth**: done → **partial** (ShopSettingsController lacks an explicit auth gate; no active-shop selector — single hasOne shop).

### Score adjustment
The analyst's 48% already reflects the three wholly-missing tabs (Commercials, Notifications,
Walk-in) plus General's email/bank/IBAN and the 12h/24h toggle. The two downgrades above shave a
couple of points off the "done" column without changing the structural picture. The interactive
cores (scheduling overnight/slot, payment cap/validation) genuinely match.

**Adjusted area score: 45%**
