# Screens Inventory — Retail Module

> **Verified accurate:** 2026-05-02 — doc shape, screen-card schema, façade→API cut-over plan, and per-screen entries hold. **One drift:** `front/retail/` is now 53 files (not 50) — the +3 are screen splits added during batch work (`retail-screens-batch5.js` and the splits in retail-inventory). Net new screens, no replaced surfaces. Per-screen content remains accurate.
> **Status:** active source-of-truth doc; the canonical Retail screen catalog.

**Audience:** integration engineers and product owners doing the cut-over of the Retail module to the real backend.
**Scope:** every consumer-visible screen, modal, drawer, and sub-tab under `front/retail/`. 50 source files, ~28 top-level screens, ~85 modals/drawers/sub-tabs, ~12 long-running flows.
**Companion docs:** `MODULE-MAP.md`, `DESIGN-SYSTEM.md`, `API-USAGE-MAP.md`, `INTEGRATION-NOTES.md`.

---

## How to read this doc

Every screen entry has the same shape:

> **<Route key> · <Screen name>** — one-line purpose
> **File(s):** the source files that own this surface
> **Mounts at:** the route key in `DALSEEN_NAV` (or "embedded — opens from <X>")
> **Owns:** the React components defined in those files
> **Composes:** other screens/components it embeds
> **States:** loading / empty / data / error / read-only / edit / locked
> **Actions:** verbs the user can take (and where they go)
> **Data:** the FE hook calls used today (in mock world) and the canonical `window.API.<ns>.<resource>` calls they'll resolve to after cut-over
> **Permissions:** roles / capabilities required (`DalseenCan('x.y')`)
> **Modals & drawers:** child surfaces, indented under the parent
> **Known quirks:** subtle behaviour the integration team must preserve

When the FE today goes through a synchronous façade (`window.RetailHooks.products.list()` etc.), I list **both** the façade call and the underlying `API` it will resolve to after cut-over. The cut-over plan is to keep the façade and have it `await` instead of return synchronously — see §1.4.

---

## 1 · Module overview

### 1.1 File census (50 files)

| Group                                       | Files | Notes                                                           |
|---------------------------------------------|------:|-----------------------------------------------------------------|
| **Hubs (top-level screens)**                |     5 | `pos-hub`, `inventory-hub`, `purchasing-hub`, `customers-hub`, `ops-hub` |
| **POS surface**                             |    11 | `pos-shell`, `pos-cart`, `pos-pay`, `pos-checkout`, `pos-customer-search`, `pos-flow-ui`, `cfd`, `cfd-mock`, plus 3 sub-files |
| **Inventory tabs & flows**                  |    11 | `inventory-tabs-a/b/c/d`, `inventory-overview`, `add-product-v2*`, `stocktake-flow`, `expiry-flow`, `write-offs-flow` |
| **Purchasing tabs & flows**                 |     6 | `purchasing-overview`, `purchasing-suppliers`, `purchasing-receiving`, `retail-purchasing-flow`, `retail-transfers`, `retail-receiving` |
| **Ops & access**                            |     5 | `ops-tab-audit`, `ops-tab-shifts`, `ops-tab-tasks`, `ops-tab-incidents`, `users-access` |
| **Customers & loyalty**                     |     3 | `customers-list`, `customer-detail`, `loyalty` |
| **Hooks / data layer**                      |     2 | `retail-hooks` (the synchronous façade), `product-kinds` |
| **Returns + receipts + sub-screens**        |     3 | `retail-returns-flow`, `retail-receipts`, `retail-flow` (legacy compound) |
| **Growth ops (audit, bundles, gift cards, layaway)** | 1 | `retail-growth-ops` (4 screens in one file) |
| **Helpers & one-offs**                      |     3 | `retail-cfd`, `product-thumb`, `retail-complete` |

### 1.2 Top-level routes (Retail section)

The Retail module contributes these route keys to `DALSEEN_NAV` (see `front/common/nav.js`):

| Route key             | Section in sidebar       | Component / hub                |
|-----------------------|--------------------------|--------------------------------|
| `retail.pos`          | Operate                  | `RetailPOSHub`                 |
| `retail.inventory`    | Operate                  | `RetailInventoryHub`           |
| `retail.purchasing`   | Operate                  | `RetailPurchasingHub`          |
| `retail.customers`    | Sell                     | `RetailCustomersHub`           |
| `retail.ops`          | Run                      | `RetailOpsHub`                 |
| `retail.access`       | Settings                 | `UsersAccessScreen`            |
| `retail.zfflow`       | Demos (hidden in prod)   | `ZFFlowTab` (zero-friction onboarding) |

Everything else in this module is reached **inside** one of these hubs (sub-tabs, modals, drawers, fullscreen flows). There are no orphan routes.

### 1.3 Hub anatomy (shared shape)

All five Retail hubs follow the **same** layout pattern:

```
┌──────────────────────────────────────────────────────────────────────┐
│  HEADER STRIP (sticky)                                                │
│  ┌─────────────┬───────────────┬─────────────┬─────────────────────┐ │
│  │ Title + KPI │ Tab strip     │ Search      │ Action bar (CTAs)   │ │
│  └─────────────┴───────────────┴─────────────┴─────────────────────┘ │
├──────────────────────────────────────────────────────────────────────┤
│  ACTIVE TAB BODY (scrollable)                                         │
│   - Filter toolbar (per tab)                                          │
│   - Table or grid (with empty state)                                  │
│   - Footer: pagination + bulk-action bar (when rows selected)         │
└──────────────────────────────────────────────────────────────────────┘
   FAB / floating tweaks button (bottom right)
```

Hubs differ only in tab list, KPI strip content, and CTA buttons. Building a sixth hub means filling those three slots — the chrome is already a pattern.

### 1.4 The synchronous façade (`retail-hooks.js`)

**Why it exists:** the Retail screens were authored before the `API` layer existed and they call `window.RetailHooks.products.list()` synchronously, returning rows immediately. This works today because all data lives in `localStorage`; the façade is a thin wrapper over `window.DALSEEN_DATA.*`.

**At cut-over** (see `INTEGRATION-NOTES.md §C2`): the plan is to **keep the façade**, switch its internals to call `window.API.retail.<resource>.<verb>` and **`await`** the response, and update consumer screens from sync to async one tab at a time. The façade today exposes:

- `RetailHooks.products` — list / get / create / update / remove / stockByBranch / search / lowStock
- `RetailHooks.customers` — list / get / create / update / remove / loyaltyTier
- `RetailHooks.suppliers` — list / get / create / update / remove
- `RetailHooks.branches` — list / get / current
- `RetailHooks.sales` — list / create / get / void / refund / today
- `RetailHooks.purchaseOrders` — list / get / create / approve / receive / cancel
- `RetailHooks.receivings` — list / create / postToStock
- `RetailHooks.transfers` — list / create / approve / dispatch / receive / cancel
- `RetailHooks.shifts` — list / open / close / current / byBranch
- `RetailHooks.counts` — list / get / create / saveLine / submit / post / cancel (stocktakes)
- `RetailHooks.audit` — list / record / byActor / byAction
- `RetailHooks.approvals`, `RetailHooks.tasks`, `RetailHooks.incidents` — Ops tab support
- `RetailHooks.zeroFriction` — the demo onboarding pseudo-resource

Every hub references this façade. Cut-over wins are concentrated here: rewrite once, all screens benefit.

---

## 2 · Hub: POS (`retail.pos`)

### 2.1 RetailPOSHub — POS shell

**File:** `pos-shell.compiled.js` (+ `pos-cart`, `pos-pay`, `pos-checkout`, `pos-customer-search`, `cfd`)
**Mounts at:** `retail.pos`
**Owns:** `RetailPOSHub`, `POSCart`, `POSPay`, `POSCheckout`, `POSCustomerSearch`
**Composes:** `<ProductThumb>` (catalogue tiles), `<Icon>`, `RetailFlowMini` (post-sale receipt drawer)

**Layout (full-screen, no sidebar):**

```
┌── Header: branch name · cashier · shift state · device pill ────────┐
├──────────────────────────────────────────────────────────────────────┤
│ 2-pane:                                                               │
│   LEFT  · Catalogue tiles (grid) + category strip + search           │
│   RIGHT · Cart + customer card + totals + Pay button                 │
└──────────────────────────────────────────────────────────────────────┘
   Footer: Hold sale · Discount · Open drawer · Receipts · Returns
```

**States:**
- **Shift not open** — entire POS body is dimmed, modal locks user into "Open shift" flow (see §2.2).
- **Empty cart** — right pane shows customer search + "Scan or tap to add."
- **Items in cart** — totals strip at bottom of right pane is sticky.
- **Awaiting payment** — `POSPay` modal blocks input; cart is read-only.
- **Sale completed** — `POSCheckout` summary + auto-print receipt + 3-second auto-clear.
- **Offline mode** — banner across header; `localStorage` queue + sync indicator.

**Actions:**
- Add product (scan / tap tile / search) → `RetailHooks.sales.addLine(saleId, line)`
- Modify line (qty, price override, discount, note) → `sales.updateLine(...)`
- Apply cart-level discount → opens **Discount modal** (manager override required if > threshold)
- Set customer → opens `<POSCustomerSearch>` panel (slides in from right)
- Hold sale (parking) → `sales.park(saleId)`; reappears in "Held sales" drawer
- Pay → opens `<POSPay>` (multi-tender support)
- Return → routes to `<ReturnsFlow>` (`retail-returns-flow.compiled.js`)
- Open cash drawer (no sale) → audit log entry, manager override required

**Data (today → after cut-over):**
- Catalogue: `RetailHooks.products.list()` → `API.retail.products.list({ branchId })`
- Cart state: in-memory React state + `localStorage` mirror; sale created on first line via `sales.create({ branchId, channel: 'pos' })`
- Customer search: `RetailHooks.customers.list({ q })` → `API.retail.customers.list({ q, limit: 20 })`
- Tender: capture happens inside `POSPay` (see §2.4), which `await`s `API.retail.sales.update(id, { status: 'paid', tenders: [...] })` plus `API.pay` calls if a card terminal is used.

**Permissions:**
- `pos.sell` — required to render at all
- `pos.discount.manual` — for manual discount lines
- `pos.priceOverride` — for changing line price
- `pos.refund` — to reach Return action
- `pos.openDrawer` — to open drawer without a sale
- Manager override prompt (PIN modal) is shown when missing capability (see §2.5)

**Modals & drawers:**

#### 2.1.1 Discount modal
**Triggered by:** "Discount" button in cart footer
**File:** inline in `pos-cart.compiled.js`
**Modes:** percentage (0–100) · fixed amount · coupon code (validated via `RetailHooks.coupons.validate`)
**Caps:** above tenant threshold (`tenant.policy.maxManualDiscountPct`), prompts ManagerOverride.

#### 2.1.2 Held sales drawer
**Triggered by:** "Hold" button or top-bar pill ("3 held")
**File:** inline in `pos-shell`
**Lists:** all parked sales with timestamp + customer + total. Resume / discard per row.

#### 2.1.3 Quick-add product modal (zero-friction)
**Triggered by:** scanning an unknown barcode in `POSFlowScreen`'s Scene 2 (`pos-flow-ui.compiled.js`)
**Behaviour:** inline 3-field form (name, price, kind) → creates skeleton product on the fly via `RetailHooks.products.create({ minimal: true })`. Pushes a "needs full setup" task into the Ops queue (`RetailHooks.tasks.add`).

### 2.2 Open Shift / Close Shift flow

**File:** `pos-shell.compiled.js` (Open) + dedicated `<CloseShiftModal>`
**Triggered by:** entering POS without an active shift (Open) · "End shift" button in shift pill (Close)

**Open shift modal:**
- Counts: opening float by denomination (configurable per branch)
- Captures: cashier ID (defaults to logged-in user), opening cash photo (optional, future)
- Side effects: `RetailHooks.shifts.open({ branchId, openingFloat })` → blocks until persisted
- Permission: `pos.shift.open`

**Close shift modal:**
- Counts: closing cash by denomination + non-cash totals (auto-filled from sales)
- Calculates: expected vs counted variance; flag if `|variance| > tenant.policy.varianceThresholdSAR`
- Captures: notes (required if variance flagged)
- On submit: `RetailHooks.shifts.close({ shiftId, closingCount, notes })` → triggers `audit.record('shift.close', {...})` and prints Z-report
- Permission: `pos.shift.close`

### 2.3 POSCart — cart line management

**File:** `pos-cart.compiled.js`
**Embedded in:** RetailPOSHub right pane; not a standalone screen.
**Owns:** cart line list, line-level edit popovers (qty, price, discount, note), totals strip, customer chip
**Quirks:**
- Line edit is **inline** for qty (steppers) but **popover** for price override / discount / note
- Hitting Enter on a quantity input commits + advances focus to next line
- Removing the last line auto-resets the sale (status → `draft` in `sales.create` returns to fresh)

### 2.4 POSPay — payment capture modal

**File:** `pos-pay.compiled.js`
**Triggered by:** Pay button when cart total > 0
**Layout:** large numpad (left) + tender list (right) + denominations strip (cash quick-keys)

**Tender modes:**
- **Cash** — numpad amount; auto-calculates change due
- **Card (mada / Visa / Mastercard / Amex)** — picks a terminal from `API.pay.terminals.list({ branchId })`, sends amount, polls until approved/declined; supports tip on top
- **Apple Pay / STC Pay / mada Pay (NFC)** — same terminal flow, different rail
- **Wallet credit** — applies customer's stored credit (requires customer set on cart)
- **Loyalty redeem** — converts points to discount (requires customer with loyalty tier)
- **Split** — multi-tender; user adds rows until `sum(tenders) >= total`

**States:**
- Awaiting tender selection
- Awaiting amount entry
- Awaiting terminal response (spinner + "Tap or insert card" copy on CFD)
- Approved → auto-advances to checkout
- Declined → red banner + retry / change tender
- Partial (split) — running balance displayed; "Pay now" button enables only when balance ≤ 0

**Data (after cut-over):**
- Terminal list: `API.pay.terminals.list({ branchId })` filtered by `online: true`
- Capture: terminal-side flow returns `{ rrn, last4, brand, approvalCode }` — these become the tender's `meta`
- Sale finalisation: `API.retail.sales.update(saleId, { status: 'paid', tenders: [...], paidAt: ... })` with `Idempotency-Key: \`sale-${saleId}\``

**Quirks:**
- The CFD (customer-facing display) on a second monitor mirrors this modal in real-time via `window.CFDBus` (see §7.5). Keep this contract — pickers, store layouts and self-checkout rely on it.
- If terminal poll exceeds 90 seconds, modal shows "Cancel transaction" button which sends a void to the terminal AND posts `audit.record('pos.terminal.timeout', {...})`.

### 2.5 ManagerOverride modal (shared)

**File:** inline in `pos-shell.compiled.js` (used elsewhere too — Returns, Discounts, Stock writes)
**Triggered by:** any action where the active user lacks a capability AND the tenant policy permits override
**Captures:** manager PIN
**On approve:** the action proceeds *as the manager*, but the audit log records both actors:

```js
audit.record('pos.discount.manual', { actorId: cashierId, overrideBy: managerId, ... });
```

**Permission:** the manager being PIN'd in must have the missing capability (validated via `DalseenCan(cap, { userId: managerId })`).
**At cut-over:** PIN validation MUST go server-side (`POST /auth/manager-override` with PIN + capability + context, returns short-lived approval token attached to the next mutation's headers). Today it's a client-side hash check — **insecure, replace.**

### 2.6 POSFlowScreen — zero-friction demo

**File:** `pos-flow-ui.compiled.js` + `zf-flow.jsx`
**Mounts at:** `retail.zfflow` (hidden in prod via NAV flag)
**Purpose:** scripted, scene-by-scene walkthrough of the unknown-barcode → quick-add → priced → sold flow. Used in sales demos. Not a production screen but **don't delete** — sales engineering still uses it.

---

## 3 · Hub: Inventory (`retail.inventory`)

### 3.1 RetailInventoryHub — Inventory shell

**File:** `inventory-hub.jsx`
**Mounts at:** `retail.inventory`
**Owns:** `RetailInventoryHub`, `InventoryTweaksButton`, `InventoryEmptyState`
**Tabs:** Overview · Items · Receive · Transfers · Counts · Expiry · Write-offs · Reorder

**Header KPIs (computed live):**
- Active SKUs · Total stock value (SAR) · Low-stock alerts · Expiring within 30d · Pending receivings

**Action bar:** `+ Add product` (opens `<AddProductV2>`) · `Import CSV` · Tweaks gear

**States:**
- **Empty (no products)** — full-pane `<InventoryEmptyState>` with three guided CTAs: Add first product / Import CSV / Watch demo
- **Has data** — header + active tab body
- **Tweaks open** — popover (anchored to gear button) with: density toggle, show low-stock first, show out-of-stock items, group-by-category

**Permissions:**
- `inventory.view` — to render
- `inventory.write` — to see action bar CTAs
- `inventory.import` — for Import CSV button

#### 3.1.1 InventoryTweaksButton popover

**Toggles:** `density` (compact / cosy), `lowStockFirst` (sort), `showOutOfStock` (filter), `groupByCategory` (table grouping)
**Persists to:** `localStorage['dalseen.retail.inventory.tweaks']`
**Quirks:** density change cascades into `<style>` overrides — see `DESIGN-SYSTEM.md §B.7`.

### 3.2 Inventory · Overview tab

**File:** `inventory-overview.jsx`
**Body composition:**
- KPI tiles row (4 tiles) — same numbers as header but expanded with deltas vs last week
- "Stock heat map" — per-branch × top-20-categories grid; cell colour = stock health
- "Top movers" — sparkline list (top 10 by units sold this week)
- "At risk" — list of low-stock + expiring items, click → opens corresponding tab pre-filtered

**Data:** `RetailHooks.products.list()` aggregated client-side; will move to `GET /retail/dashboard` (already registered) at cut-over.
**Empty:** "No data yet — start adding products" with link to Items tab.

### 3.3 Inventory · Items tab

**File:** `inventory-tabs-a.jsx` (`InvTabItems`)
**Body:** sortable table — Image · SKU · Name · Category · Branch stock · Cost · Sell · Margin · Actions
**Filters:** Branch · Category · Stock status (in / low / out / all) · Kind (regular / variant / batch / serial / weighed / bundle / recipe / service / gift / digital)
**Bulk actions** (when rows selected): Print labels · Adjust prices · Move category · Archive

**Row actions:**
- Edit → opens `<AddProductV2>` in edit mode
- Adjust stock → `<StockAdjustmentModal>` (per-branch, with reason code)
- View movements → drawer with transaction history (sales, transfers, counts, write-offs)
- Print label → opens label preview + print dialog (browser print)
- Archive → soft delete (`status: 'archived'`); requires `inventory.archive`

**Data:** `RetailHooks.products.list({ filters })` → `API.retail.products.list({ ...filters })`
**Quirks:**
- The "Branch stock" column shows the **active branch's** stock by default; click it to reveal a per-branch breakdown popover.
- Bulk price adjust supports % or absolute, applied to either cost or sell, with optional rounding rules ("end in 9", "round to 5"). All bulk writes share one `Idempotency-Key`.

#### 3.3.1 Add Product (V2) — multi-step flow

**File:** `add-product-v2.jsx` + `add-product-v2-fields.jsx` + `add-product-v2-preview.jsx`
**Triggered by:** `+ Add product` button anywhere in Inventory hub
**Layout:** full-pane modal (escape closes only after confirmation if dirty)

**Step 1 — Pick kind** (`PickKindStep`):
- 10 kind cards: Regular · Variant · Batch · Serial · Weighed · Bundle · Recipe · Service · Gift card · Digital
- Each card shows: icon, en/ar name, 1-line description, "best for…" example
- Tenant archetype (set in onboarding) reorders/highlights kinds: a salon defaults to Service first, a grocery defaults to Weighed.
- See `product-kinds.js` for the full 10-kind metadata (each kind defines its required field groups)

**Step 2 — Form + live preview** (`FormStep`):
- LEFT pane: section tabs (Core, Variant/Batch/Serial as applicable, Pricing, Inventory, Tax/ZATCA, Media)
- RIGHT pane: `<LivePreview>` — the product as it would appear in: catalogue tile · POS line · receipt
- Field validation runs on blur AND on Save; errors appear inline + summary banner
- Generate barcode button (`genBarcode`) — produces an EAN-13 in the tenant's reserved range
- Save modes: "Save & close" / "Save & add another" (resets to Step 1)

**Field groups by kind** (defined in `product-kinds.js`):
| Kind     | Field groups                                                       |
|----------|--------------------------------------------------------------------|
| Regular  | core · pricing · inventory · zatca · media                         |
| Variant  | core · variants · pricing-per-variant · inventory-per-variant · zatca · media |
| Batch    | core · batches · pricing · inventory · expiry-policy · zatca · media |
| Serial   | core · serials · pricing · inventory · zatca · media               |
| Weighed  | core · weighed · pricing-per-unit · inventory · zatca · media      |
| Bundle   | core · bundle-components · pricing · zatca · media                 |
| Recipe   | core · recipe-ingredients · yield · pricing · zatca · media        |
| Service  | core · duration · resources · pricing · zatca                      |
| Gift     | core · denomination · expiry · pricing · zatca                     |
| Digital  | core · delivery · pricing · zatca                                  |

**Data:** `RetailHooks.products.create(payload)` → `API.retail.products.create(payload)`
**Permissions:** `inventory.write` (any kind); `inventory.write.serial` for serial; etc.
**Quirks:**
- The form is canvas-large (often 1600px tall) — the preview pane is sticky, the form pane scrolls.
- Saving from edit mode is `update`, not `create`. The component reuses the same form; only the title and the submit verb change.

#### 3.3.2 Stock Adjustment modal

**Triggered by:** "Adjust stock" row action in Items
**Captures:** branch · current qty (read-only) · new qty (or delta) · reason code (count, damage, theft, recipe-consumption, transfer-in, transfer-out, return-to-supplier, other) · note
**Permission:** `inventory.adjust` — manager override available
**Audit:** every adjustment writes `audit.record('inventory.adjust', {...})` with before/after qty
**At cut-over:** maps to a write-off or stock count entry depending on reason code (see §3.7 for write-offs).

### 3.4 Inventory · Receive tab

**File:** `inventory-tabs-a.jsx` (`InvTabReceive`)
**Body:** table of past receivings — Date · PO# (or Direct) · Supplier · Branch · Lines · Total · Status (draft / received / posted)
**Action bar:** `+ Receive stock` (opens `<ReceiveFlow>` mode picker)

**Mode picker (3 choices):**
1. **Against PO** — pick an open PO; lines pre-fill; user counts received qty per line and notes shortages
2. **Direct** — no PO; enter supplier, lines free-form (good for ad-hoc cash purchases)
3. **Return to supplier** — negative receiving; pick lines from past receivings

**ReceiveFlow** (`ReceiveFlow` component, ~400 lines):
- Step 1: pick mode + supplier + branch
- Step 2: line entry (with barcode scan, search, or paste-from-CSV)
- Step 3: summary + post (writes to stock + creates AP bill if "auto-bill on receive" tenant setting is on)
- Each line captures: qty received, qty expected, unit cost (override allowed), batch/serial if applicable, expiry date if applicable
- Variance row appears when qty received ≠ qty expected — requires reason

**Data (cut-over):**
- `RetailHooks.purchaseOrders.list({ status: 'open' })` → `API.retail.purchaseOrders.list({ status: 'open' })`
- `RetailHooks.receivings.create({ poId, branchId, lines })` → `API.retail.receivings.create(...)`
- `RetailHooks.receivings.postToStock(id)` → `API.retail.receivings.update(id, { status: 'posted' })`
- AP bill creation: server-side side effect — FE doesn't call accounting directly

**Permissions:** `inventory.receive`
**Quirks:**
- "Auto-bill on receive" is a tenant setting; if off, the AP bill is created in `Accounting · Bills` as a draft requiring confirmation
- Posting to stock is **irreversible** in the FE — to undo, user creates a return-to-supplier receiving (negative)

### 3.5 Inventory · Transfers tab

**File:** `retail-transfers.compiled.js` (`InvTabTransfers`)
**Body:** table — Date · From branch · To branch · Lines · Status (requested / approved / dispatched / received / cancelled) · Discrepancy flag
**Action bar:** `+ Request transfer`

**Imperative API exposed (per file):** `window.DALSEEN_TRANSFERS_API.openRequestModal(initial)` — used by Reorder tab to deep-link with prefilled lines.

**TransferRequest modal:**
- Pick from branch + to branch
- Add lines (search products at "from" branch, qty up to available)
- Note (optional)
- Submit → status: requested

**Transfer detail drawer (per row):**
- Timeline: requested → approved → dispatched → received
- Per-line: requested qty · dispatched qty · received qty · discrepancy
- Actions: Approve (manager) · Dispatch (warehouse) · Receive (destination) · Cancel
- Each transition has its own permission and writes audit

**Data (cut-over):**
- `RetailHooks.transfers.list()` → `API.retail.transfers.list()`
- `RetailHooks.transfers.create(...)` → `API.retail.transfers.create(...)`
- Transitions: `API.retail.transfers.update(id, { status: 'approved' | ... })` — each idempotency-keyed by transition

**Permissions:**
- `inventory.transfer.request` — to create
- `inventory.transfer.approve` — to advance from requested → approved
- `inventory.transfer.dispatch` — to advance from approved → dispatched
- `inventory.transfer.receive` — to advance from dispatched → received

### 3.6 Inventory · Counts tab (Stocktake)

**File:** `stocktake-flow.jsx`
**Body:** three sections (Open · Review · Recent)
- **Open sessions** — sessions with `lines.counted < lines.expected` (in progress); CTA "Continue counting"
- **Review** — sessions submitted but not posted; CTA "Review variance"
- **Recent (posted)** — last 30 days

**Action bar:** `+ New count` (opens `<StockTakeWizard>`)

**StockTakeWizard** (4 steps):
1. **Method** — Full count · By location · By category · ABC analysis · Cycle count
2. **Scope** — pick branch(es), zones, categories (depends on method)
3. **Policy** — "lock stock during count" toggle, expected-qty source (current vs frozen snapshot), tolerance %
4. **Review** — preview lines + start count

**CountRunnerPro** (the counting screen — fullscreen):
- Per-zone tabs (when method = By location)
- Scan / search / type to add a line
- Per-line: expected, counted (input), variance (auto), recount button
- Flag rows where counted ≠ expected
- Submit when all lines counted (or "Submit with N uncounted" if policy allows)

**VarianceReview** (post-submit):
- Lists every variance row with: SKU, expected, counted, diff, value impact
- Per-row decision: Accept · Recount · Investigate (creates incident in Ops)
- "Post to stock" button — only enabled when all variances decided
- On post: stock is adjusted, audit log entry per line, and (if any "investigate") an incident appears in Ops Incidents tab

**PostedConfirm:** summary screen — total adjustments, value impact, link to view in audit log, "Start new count" CTA.

**Data (cut-over):**
- `RetailHooks.counts.list()` → `API.retail.stocktakes.list()`
- `RetailHooks.counts.create({ method, scope, policy })` → `API.retail.stocktakes.create(...)`
- `RetailHooks.counts.saveLine(id, line)` → `API.retail.stocktakes.update(id, { lines: [...] })` — debounced batch
- `RetailHooks.counts.submit(id)` / `.post(id)` → status transitions

**Permissions:**
- `inventory.count.create` · `inventory.count.run` · `inventory.count.review` · `inventory.count.post`

**Quirks:**
- "Lock stock during count" suspends transfers and write-offs from the scope (POS sales still allowed but logged as "during count" in audit)
- Cycle count method auto-suggests next products to count based on last-counted date and ABC class
- The counter input is a fully separate screen (not a modal) because count sessions can run for hours — the user navigates away and back. State is persisted to `localStorage` AND server on every line.

### 3.7 Inventory · Expiry tab

**File:** `expiry-flow.compiled.js` (referenced as `InvTabExpiry`)
**Body:** table — SKU · Name · Branch · Batch · Expiry date · Days remaining · Qty · Action suggestion
**Color coding:** red (expired), amber (≤7d), yellow (≤30d), grey (>30d)
**Action bar:** Bulk markdown · Bulk write-off · Print labels (FEFO)

**Per-row actions:**
- Markdown — opens `<MarkdownModal>` with suggested discount based on days-to-expiry
- Write-off — opens write-off flow (§3.8) pre-filled with this row's batch
- Move (transfer) — opens TransferRequest pre-filled

**Data:** `RetailHooks.products.list().filter(hasBatchesNearExpiry)` → after cut-over, dedicated `API.retail.products.expiringSoon({ branchId, days })` route (NOT YET REGISTERED — see `INTEGRATION-NOTES.md §A.4`).
**Permissions:** `inventory.expiry.view`, `inventory.expiry.act` (for markdown/write-off)

### 3.8 Inventory · Write-offs tab

**File:** `write-offs-flow.compiled.js`
**Body:** table — Date · Branch · Lines · Total cost · Reason category · Status (draft / posted)
**Action bar:** `+ Write off` (opens flow)

**Write-off flow:**
- Step 1 — Pick branch + reason category (Damage · Theft · Expiry · Recipe-consumption · Other)
- Step 2 — Add lines (scan/search), per-line note
- Step 3 — Approval (manager override if value > threshold)
- Step 4 — Post → stock adjusts down + GL entry in Accounting (server-side)

**Permissions:** `inventory.writeoff.create`, `inventory.writeoff.approve` (above threshold)
**Quirks:** every line writes audit; the GL entry is server-generated, not FE.

### 3.9 Inventory · Reorder tab

**File:** `inventory-tabs-d.jsx` (`InvTabReorder`)
**Body:** suggestions table — SKU · Branch · Current · Reorder point · Reorder qty · Suggested PO supplier · Suggested transfer source
**Action bar:** Bulk create POs · Bulk create transfers

**Logic (FE-side today):**
- Pulls products where `stock < reorderPoint`
- For each: if a preferred supplier exists → suggest PO; if another branch has surplus → suggest transfer
- "Create PO" / "Create transfer" buttons deep-link into Purchasing (PO modal) or Transfers (`window.DALSEEN_TRANSFERS_API.openRequestModal`) with prefilled lines

**Data:** entirely synthesised client-side today; **at cut-over** this should move server-side as `GET /retail/reorder/suggestions?branchId=` because the algorithm depends on data the FE doesn't have (full sales velocity, supplier lead times). Add this to `INTEGRATION-NOTES.md §C2`.

---

## 4 · Hub: Purchasing (`retail.purchasing`)

### 4.1 RetailPurchasingHub — Purchasing shell

**File:** `retail-purchasing-flow.compiled.js` + `purchasing-overview`, `purchasing-suppliers`, `purchasing-receiving`
**Mounts at:** `retail.purchasing`
**Tabs:** Overview · Purchase Orders · Suppliers · Receiving · Returns to supplier

**Header KPIs:** Open POs · Pending receipts · Overdue payments · Top suppliers (this month)
**Action bar:** `+ New PO` · `+ New supplier`

### 4.2 Purchasing · Overview tab

**File:** `purchasing-overview.jsx`
**Composition:** KPI strip · Pending approvals list · Recent activity feed · Spend-by-category chart · Top 10 suppliers by spend
**Data:** assembled client-side from PO + supplier + receiving lists; server-side aggregate at cut-over (`GET /retail/purchasing/dashboard` — not yet registered, add it).

### 4.3 Purchasing · Purchase Orders tab

**File:** `retail-purchasing-flow.compiled.js`
**Body:** table — PO# · Supplier · Branch · Date · Lines · Total · Status (draft / approved / sent / partially received / received / cancelled)
**Filters:** Status · Supplier · Branch · Date range
**Row click → PO Detail drawer**

**+ New PO modal:**
- Pick supplier (search) · branch (defaults to active) · expected date
- Add lines (search products, qty, unit cost — defaults to last cost from this supplier)
- Auto-totals (subtotal, VAT, total)
- Submit as draft OR submit for approval (depending on user permission + tenant policy)

**PO Detail drawer:**
- Header: PO#, supplier, status, total
- Tabs: Lines · Receiving history · Comments · Audit
- Actions (depend on status + permission):
  - Draft: Edit, Approve, Cancel
  - Approved: Send to supplier (email/print/WhatsApp), Cancel
  - Sent: Receive (opens receive flow with this PO pre-picked), Cancel
  - Partially received: Receive again, Cancel remaining
  - Received: View bill (link to Accounting)
  - Cancelled: Reopen (manager override required)

**Permissions:**
- `purchasing.po.view` · `purchasing.po.create` · `purchasing.po.approve` · `purchasing.po.send` · `purchasing.po.cancel`

**Data:** `RetailHooks.purchaseOrders.*` → `API.retail.purchaseOrders.*`

### 4.4 Purchasing · Suppliers tab

**File:** `purchasing-suppliers.jsx`
**Body:** table — Name · Contact · Phone · Email · Total spend YTD · Open balance · Last PO · Status
**Row click → Supplier Detail drawer**

**Supplier Detail drawer tabs:**
- Overview (totals, contact card)
- Products (catalogue this supplier provides, with last cost + lead time)
- POs (history)
- Bills (link to Accounting AP)
- Notes

**Modals:**
- `+ New supplier` — captures: legal name, trade name, VAT#, contact, payment terms, default branch, currency
- Edit supplier — same fields, with "deactivate" CTA

**Data:** `RetailHooks.suppliers.*` → `API.retail.suppliers.*`
**Permissions:** `purchasing.supplier.view`, `purchasing.supplier.write`

### 4.5 Purchasing · Receiving tab

**File:** `purchasing-receiving.jsx`
**Body:** same table as Inventory · Receive tab — they're two views on the same dataset.
**Why two tabs?** Different muscle-memory paths (procurement folks live in Purchasing, warehouse folks live in Inventory). Same data and same `<ReceiveFlow>` modal. Keep both.

### 4.6 Purchasing · Returns to supplier tab

**File:** within `retail-purchasing-flow.compiled.js`
**Body:** table of negative receivings — same chrome as Receiving, filtered to `direction: 'out'`
**+ New return:** opens ReceiveFlow in "Return to supplier" mode — pick a past receiving, select lines + qty, reason, post

---

## 5 · Hub: Customers (`retail.customers`)

### 5.1 RetailCustomersHub — Customers shell

**File:** `customers-list.jsx` (hub + list), `customer-detail.jsx` (drawer), `loyalty.jsx` (loyalty programme tab)
**Mounts at:** `retail.customers`
**Tabs:** All customers · Loyalty programme · Segments · Communications

### 5.2 Customers · All customers tab

**File:** `customers-list.jsx`
**Body:** table — Avatar · Name · Phone · Email · Total spent · Last visit · Loyalty tier · Status
**Filters:** Tier · Branch (preferred) · Last visit (within X days) · Segment
**Action bar:** `+ Add customer` · `Import CSV` · `Bulk message`

**Row click → Customer Detail drawer:**

**Customer Detail drawer tabs (`customer-detail.jsx`):**
- **Overview** — header card · KPIs (total spent, AOV, visits, last visit) · favourite categories
- **Purchases** — full sale history with receipts (each row links to `<RetailReceipts>` detail)
- **Loyalty** — points balance, tier history, redemptions
- **Communications** — sent messages (SMS/WhatsApp/Email)
- **Notes** — staff notes, pinned + threaded
- **Files** — uploads (ID copy, contracts) — uploads not yet wired (placeholder)

**Data:** `RetailHooks.customers.get(id)` + sales filter → `API.retail.customers.get(id)` + `API.retail.sales.list({ customerId: id })`
**Permissions:** `customers.view`, `customers.write`, `customers.delete`

### 5.3 Customers · Loyalty tab

**File:** `loyalty.jsx`
**Body:** programme overview — tier definitions · earn rules · redeem rules · sample CSV exports
**Action:** Edit programme (single config form — multi-tier, with tier thresholds + perks)
**Data:** `RetailHooks.loyalty.programme()` (from `tenant.config.loyalty`); cut-over: `API.retail.loyalty.get` / `.update` (NOT YET REGISTERED — flag in INTEGRATION-NOTES).

### 5.4 Customers · Segments + Communications

**Files:** within `customers-list.jsx`
**State today:** placeholder tabs with empty states. Real implementation deferred — segments will become a query builder; communications will integrate with the Comms gateway. Documented here so they're not mistaken for done.

---

## 6 · Hub: Ops (`retail.ops`)

### 6.1 RetailOpsHub — Ops shell

**File:** `ops-hub.jsx`
**Mounts at:** `retail.ops`
**Tabs:** Today · Shifts · Audit · Tasks · Incidents · Reports

**Today tab (`OpsTodayTab`):**

Layout — three rows:
1. **KPI tiles** (4) — Sales today · Open shifts · Pending approvals · Open incidents
2. **Branch live cards** — one per branch: open shift count, sales velocity bar, last sale time, open drawer (CTA to remote-monitor)
3. **Two-column** — Pending approvals (left) | Incidents + Tasks rollup (right)

**Atoms:**
- `<KpiTile>` — clickable, with optional pulse animation when value crosses threshold
- `<BranchLiveCard>` — branch name, open shifts pill, sales bar (relative to top-performing branch), CTA
- `<ApprovalRow>` — one approval pending; Approve/Reject inline (with optional reason)
- `<IncidentRow>` — severity-colored (high=red, med=warn, low=muted), click to open detail drawer
- `<TasksRollup>` — per-branch completion %, click to drill into Tasks tab

**Empty states:** every section has its own `<EmptyBlock>` with icon + en/ar copy + dense option for cards

**Data:** assembled from `RetailHooks.shifts/approvals/incidents/tasks` lists; cut-over → single aggregate `GET /retail/ops/today` (recommend, not yet registered).

### 6.2 Ops · Shifts tab

**File:** `ops-tab-shifts.jsx`
**Body:** table — Branch · Cashier · Opened · Closed · Duration · Sales · Variance · Status
**Filters:** Branch · Status (open / closed) · Date range
**Row click → Shift detail drawer**

**Shift detail drawer:**
- Header (cashier, branch, times)
- Tabs: Sales (every transaction in this shift) · Drawer counts · Audit
- Actions: Force-close (manager override) · Print Z-report · Export CSV

**Data:** `RetailHooks.shifts.list({ filters })` → `API.retail.shifts.list({ ...filters })`
**Permissions:** `ops.shifts.view`, `ops.shifts.forceClose` (override)

### 6.3 Ops · Audit tab

**File:** `ops-tab-audit.jsx`
**Body:** filterable audit log — Time · Actor · Action · Resource · Branch · Risk
**Filters:** Free-text search · Risk (low/med/high) · Action category · Actor · Date range
**Row click → expand inline:** before/after JSON, related resource link, override info if any

**Risk colors:** low=muted, med=warn, high=danger

**Atoms:** `<AuditRow>`, `<Pill>`, `<StTile>` (stat tile in header strip)

**Data:** `RetailHooks.audit.list({ filters })` — entirely client-side today (writes go to `localStorage`); **at cut-over this is critical** — audit must be server-side append-only and queryable. New endpoint required: `GET /retail/audit?...&q=&risk=&from=&to=` paginated. (Currently NO `audit` resource in `api-seeds.js` — flag this in INTEGRATION-NOTES.)

**Permissions:** `audit.view` (operator), `audit.export` (export to CSV/PDF)

#### 6.3.1 Audit · Reports sub-grid (`ReportsGrid`)

**Layout:** left rail of report names · right pane of report body
**Reports defined:** Daily sales · Shift reconciliation · Variance report · Approvals summary · Tasks summary · Incidents summary · Audit summary
**Range picker:** Today / This week / This month / Custom
**Body renderers:** `RDailySales`, `RShiftRecon`, `RVariance`, `RApprovals`, `RTasks`, `RIncidents`, `RAuditSummary`, plus `RPlaceholder` for not-yet-implemented IDs

### 6.4 Ops · Tasks tab

**File:** `ops-tab-tasks.jsx`
**Body:** Kanban board (Backlog / In progress / Done) OR table view (toggle)
**Card:** title, branch, assignee, due, priority pill
**Actions:** Create task, edit, complete, archive

**Data:** `RetailHooks.tasks.*` → would require `API.retail.tasks.*` (not yet registered; see INTEGRATION-NOTES — note the `owner.tasks` resource exists but it's a separate dataset for HR; ops tasks need their own).

### 6.5 Ops · Incidents tab

**File:** `ops-tab-incidents.jsx`
**Body:** incident list — Severity · Branch · Title · Reporter · Opened · Status (open / investigating / resolved / closed)
**Row click → Incident detail drawer**

**Incident detail drawer:**
- Header (severity, status)
- Timeline (status changes + comments)
- Linked resources (which sale / count / transfer triggered it)
- Resolution (cause, action taken, prevention)

**Data:** `RetailHooks.incidents.*` — needs API parity (not yet registered).

### 6.6 Ops · Reports tab

Same `ReportsGrid` as inside Audit, mounted as its own tab for reach. Two entry points, one component.

---

## 7 · Settings: Users & Access (`retail.access`)

### 7.1 UsersAccessScreen

**File:** `users-access.compiled.js` (1300+ lines — substantial)
**Mounts at:** `retail.access`
**Body sections (all on one page):**

1. **Locations** — branches and warehouses as separate first-class entities. Per-row: name (en/ar), city, type (branch / warehouse), active toggle. CRUD inline. Stored at `window.DALSEEN_LOCATIONS`.

2. **Users** — table: avatar · name · role · scope · last active · status. Row click opens edit drawer.
   **User edit drawer:**
   - Identity: name, email, phone
   - Auth: password (set / reset / require change next login)
   - Role: pick one (Owner / Manager / Cashier / Warehouse / Auditor / Custom)
   - Scope: which branches / warehouses this user can act in
   - Capability overrides: explicit grant/deny per capability (advanced)
   - PIN: for manager override

3. **Roles** — list of roles with capability matrix; CRUD a custom role

4. **Capability map** — read-only reference: every capability key (e.g. `pos.discount.manual`) with its description and which roles include it by default.

**Imperative API exposed (per file):** `window.DalseenCan(capability, ctx)` — the runtime permission check used everywhere. Signature:

```js
DalseenCan('stock.adjust');                                // current user, default scope
DalseenCan('stock.adjust', { location: 'WH-CENTRAL' });    // scope-checked
DalseenCan('pos.sell', { userId: 'U-05', location: 'BR-OLAYA' });  // arbitrary user
```

**Data:** entirely `localStorage`-backed today (`window.DALSEEN_USERS`, `DALSEEN_ACCESS_USERS` v4 schema). At cut-over: `API.platform.users.*` (NOT YET REGISTERED — likely needs its own resource; see `INTEGRATION-NOTES.md §C2.1`).

**Permissions:**
- `access.view` to see this screen
- `access.write.users` · `access.write.roles` · `access.write.locations`

**Quirks:**
- The capability map is the **canonical list** of capabilities — when adding a new feature elsewhere, add the capability here first.
- Custom roles can be exported / imported as JSON for tenant-to-tenant cloning (in-page only today).

---

## 8 · Cross-cutting flows (referenced by hubs)

### 8.1 ReturnsFlow

**File:** `retail-returns-flow.compiled.js`
**Triggered by:** Returns CTA in POS · Sale row → "Refund" action · Receipt detail → "Refund this receipt"
**Stages (linear, with back nav):**

1. **Find** (`ReturnsFind`) — search by receipt number, customer phone/name, or scan receipt barcode. Empty: "Scan receipt or search."
2. **Select** (`ReturnsSelect`) — pick lines and qty; per-line "restock" toggle (default on; off for damaged returns)
3. **Refund** (`ReturnsRefund`) — refund method: original tender (default), cash, store credit, exchange. Reason code required (Defective / Wrong item / Customer changed mind / Other). Manager override if amount > threshold.
4. **Receipt** (`ReturnsReceipt`) — print refund receipt, ZATCA-clear (if applicable), customer signature optional, "New refund" / "Done" CTAs

**Atoms:** `<RrLine>` (refund line — qty stepper, restock toggle, line subtotal), `rfStepBtn`, `rfIconBtn`

**Data:**
- Find: `RetailHooks.sales.search({ q })`
- Submit: `RetailHooks.sales.refund({ saleId, lines, method })` → `API.retail.sales.create({ kind: 'refund', parentId, lines, ... })` with `Idempotency-Key: \`refund-${saleId}-${ulid}\``
- ZATCA clearance: server-side side effect

**Permissions:** `pos.refund` · `pos.refund.cashOver500` · `pos.refund.exchange`

### 8.2 RetailReceipts (history view)

**File:** `retail-receipts.compiled.js` (`RetailReceipts`)
**Mounts as:** sub-screen reachable from POS footer "Receipts" button
**Body:** table — Receipt # · Date · Cashier · Customer · Total · Tender · Status (paid / refunded / void) · ZATCA cleared
**Row click → Receipt detail drawer:** full sale lines, tenders, ZATCA QR, reprint, refund.

**Data:** `RetailHooks.sales.list({ ...filters })`

### 8.3 RetailReceiving / RetailTransfers / RetailReturns (legacy flow file)

**File:** `retail-flow.compiled.js`
**Status:** **legacy** — early monolithic file with `RetailReceiving`, `RetailTransfers`, `RetailReceipts`, `RetailReturns`. Most of these are now superseded by the per-tab dedicated files. Still loaded, still referenced as fallbacks.
**Recommendation (cut-over):** keep loading order; once the new tabs are battle-tested, delete this file. Document in INTEGRATION-NOTES.

### 8.4 Growth ops (audit / bundles / gift cards / layaway)

**File:** `retail-growth-ops.compiled.js`
**Houses 4 screens** (currently not all wired into the navigation — accessed via deep-link or feature flag):

1. **AuditLogScreen** — alternative audit view (richer; will replace `ops-tab-audit`'s log eventually). Includes `AuditDetailDrawer`.
2. **BundlesScreen** — bundle product manager (sub-flavour of products with kind=bundle). `BundleBuilder` modal lets user assemble component lines.
3. **GiftCardsScreen** — gift card issuance + balance lookup. `IssueGiftCardModal` captures denomination, recipient, expiry.
4. **LayawayScreen** — layaway plan manager. `TakePaymentModal` accepts a payment against an outstanding plan.

**Atoms:** `GoKpi`, `GoChip`, `GoTableShell`, `GoStatusPill` — reusable across these four screens.

**Cut-over notes:**
- Bundles: covered by `API.retail.products` with kind=bundle.
- Gift cards: would need `API.retail.giftCards.*` (not yet registered).
- Layaway: would need `API.retail.layaways.*` (not yet registered).

### 8.5 CFD (Customer-Facing Display)

**File:** `retail-cfd.compiled.js` + `cfd-mock.compiled.js`
**Purpose:** mirror the cart on a second screen for the customer to see line-by-line, totals, and a thank-you screen post-payment.

**Bus contract (`window.CFDBus`):**
```js
CFDBus.push(state)            // POS posts state changes (cart lines, totals, payment status)
CFDBus.read()                 // CFD reads latest state
CFDBus.subscribe(fn)          // CFD subscribes to changes
```
**Mock CFD:** a separate iframe that renders `cfd-mock.compiled.js`; in production it'd run on a separate device pointed at the same tenant.
**Cut-over:** if the real backend wants to push CFD state via WebSocket, replace the bus implementation. Don't touch the API — CFD is a pure state mirror, no consumer endpoints.

### 8.6 ProductThumb

**File:** `product-thumb.compiled.js`
**Exposed:** `window.ProductThumb({ p, T, ratio, size })`
**Behaviour:** if `p.image` is set, renders the image; otherwise renders a deterministic colored card with the SKU prefix. Used in catalogue tiles, receipts, history rows. Keep as-is.

### 8.7 Retail-complete helpers

**File:** `retail-complete.compiled.js`
**Exposes:** `window.inputStyle(T)` — the canonical styled input (used everywhere). Don't fork.

### 8.8 Zero-friction Flow demo (`zf-flow.jsx`)

Already documented in §2.6. Demo path, not production.

---

## 9 · Cut-over impact summary (Retail-specific)

The following table summarises every place this module touches the API, ranked by integration priority.

| Priority | Surface                    | Endpoints needed                                       | Status today        |
|---------:|----------------------------|--------------------------------------------------------|---------------------|
|        1 | POS sale capture           | `retail.sales.create / update / get`                   | ✅ registered        |
|        1 | POS catalogue              | `retail.products.list / get`                           | ✅ registered        |
|        1 | POS terminal               | `pay.terminals.list`                                   | ✅ registered        |
|        1 | Shift open/close           | `retail.shifts.create / update`                        | ✅ registered        |
|        2 | Inventory items            | `retail.products.* (CRUD)`                             | ✅ registered        |
|        2 | Inventory dashboard        | `retail.dashboard`                                     | ✅ registered        |
|        2 | Stock breakdown            | `retail.products.stock`                                | ✅ registered (custom route) |
|        2 | Receivings                 | `retail.receivings.*`                                  | ✅ registered        |
|        2 | Transfers                  | `retail.transfers.*`                                   | ✅ registered        |
|        2 | Stocktakes                 | `retail.stocktakes.*`                                  | ✅ registered        |
|        2 | POs / Suppliers / Customers| `retail.purchaseOrders.* / suppliers.* / customers.*`  | ✅ registered        |
|        3 | Manager override (PIN)     | `auth.managerOverride` (NEW)                           | ❌ missing — security |
|        3 | Audit log (server-side)    | `retail.audit.list / record` (NEW)                     | ❌ client-only today |
|        3 | Ops aggregate              | `retail.ops.today` (NEW)                               | ❌ assembled FE-side |
|        3 | Reorder suggestions        | `retail.reorder.suggestions` (NEW)                     | ❌ synthesised FE-side |
|        3 | Expiring soon              | `retail.products.expiringSoon` (NEW, or filter on list)| ❌ filter today      |
|        4 | Tasks (ops)                | `retail.tasks.*` (NEW — separate from owner.tasks)     | ❌ missing           |
|        4 | Incidents                  | `retail.incidents.*` (NEW)                             | ❌ missing           |
|        4 | Loyalty programme config   | `retail.loyalty.get / update` (NEW)                    | ❌ missing           |
|        4 | Gift cards                 | `retail.giftCards.*` (NEW)                             | ❌ missing           |
|        4 | Layaway                    | `retail.layaways.*` (NEW)                              | ❌ missing           |
|        4 | Users & access             | `platform.users.* / roles.* / locations.*` (NEW)       | ❌ missing — likely platform namespace |

Priorities reflect Day-1 (everything tagged 1 must work, 2 is week-1, 3 is sprint-1, 4 is post-launch). Take the "❌ missing" rows into `INTEGRATION-NOTES.md §C2` as new endpoint asks.

---

## 10 · Pointer index (Retail)

When in doubt, read these files in this order:

| File                                            | Why                                                    |
|-------------------------------------------------|--------------------------------------------------------|
| `front/retail/retail-hooks.js`                  | The synchronous façade — every consumer goes through this |
| `front/retail/inventory-hub.jsx`                | Reference hub structure (header / KPIs / tabs / tweaks) |
| `front/retail/pos-shell.compiled.js`            | POS architecture — full flow from open shift to sale   |
| `front/retail/pos-pay.compiled.js`              | Multi-tender capture + terminal contract               |
| `front/retail/add-product-v2.jsx`               | Reference 2-step form pattern                          |
| `front/retail/stocktake-flow.jsx`               | Reference long-running multi-stage flow (wizard → runner → review → post) |
| `front/retail/retail-returns-flow.compiled.js`  | Reference linear staged flow (find → select → refund → receipt) |
| `front/retail/users-access.compiled.js`         | Capability model + scope — the source of truth for permissions |
| `front/retail/retail-cfd.compiled.js`           | CFD bus contract                                        |
| `front/common/api-seeds.js`                     | Endpoint registry — what's wired today                 |

---

**End of SCREENS-INVENTORY (Retail).** Next module per the proposed order: **Pay**.
