# WP3 — Deliver POS settings on the session payload

**Repo:** BE + FE · **Branch:** `hazemdev2` · **Migration:** none · **Depends:** WP1

## Goal — the silent-403 bug

The POS shell reads company settings **one key at a time** through `GET /core/settings/{key}`:
- `src/app/features/pos/pos-layout/pos-layout.component.ts` ~line **349** (`pos.default_customer_id`)
- same file ~line **373** (`sales.default_tax_id`)
via `src/app/core/services/setting.service.ts`.

That endpoint requires `core.settings.view` **unless** the key is listed in `SettingController::PUBLIC_READ_KEYS` (`Modules/Core/app/Http/Controllers/SettingController.php`, ~lines 36-68). **No `pos.*` key is in that list.**

So a cashier holding only POS permissions gets a **403**, and both call sites route the error into a branch written for *offline*, which silently substitutes a default. The manager tests it and it works; every cashier silently gets the fallback. Any future POS toggle would inherit this exact failure.

**Fix the delivery channel, not the symptom.** The session is opened once, is already company/branch/warehouse-aware, and is already fetched by the shell — it is the correct carrier.

## Exact files

**BE**
- `Modules/POS/app/Http/Controllers/POSSessionController.php` — the `open`, `active` and `show` responses.
- The session resource used by those responses (find it under `Modules/POS/app/Http/Resources/`).
- `Modules/Core/app/Http/Controllers/SettingController.php` — **only if** you conclude a key must stay individually readable; prefer not to touch it.

**FE**
- `src/app/features/pos/models/pos.model.ts` — extend the session interface.
- `src/app/features/pos/pos-layout/pos-layout.component.ts` — consume from the session instead of per-key GETs.
- `src/app/features/pos/services/pos-session.service.ts` and `pos-offline.service.ts` if the cached `active_session` shape changes.

## Design

Add a **`settings` block to the session payload**, server-composed, containing exactly the POS-relevant company settings the till must obey. Today that is the six existing `pos.*` keys plus `sales.default_tax_id`. Compose it server-side with `SettingsService` using the session's `company_id` — never the caller's permissions.

Rules:
1. The block is **additive**. Every existing session field keeps its name, type and meaning.
2. The FE reads from the session; the per-key `GET /core/settings/{key}` calls in `pos-layout.component.ts` are **removed**, not merely supplemented.
3. **Offline:** the session is already cached in IndexedDB (`active_session` store). The settings block rides along with it, so a cold offline start keeps the values it had. Preserve the existing `localStorage['pos_default_customer_id']` cold-start fallback — do not regress it.
4. Do **not** invent new setting keys. This WP changes delivery only.
5. Keep the block small and explicit — a named list of keys, not "every setting of the company". A cashier must not receive the company's whole configuration.

## Acceptance criteria

1. A user holding **only** POS permissions (no `core.settings.view`) opens a session and receives correct `pos.default_customer_id` / `default_warehouse_id` / `sales.default_tax_id` values — **prove this with a test that fails on the current code** (today it 403s and falls back).
2. `pos-layout.component.ts` no longer calls `GET /core/settings/{key}`.
3. Offline cold start still resolves the default customer.
4. No existing session field changed shape.
5. `ng build` green; POS suite shows no new failures.

## Tests

Extend the POS session tests. **Red-before-green on criterion 1** — run it against current code, confirm it fails, then implement. Paste both runs.

⚠️ Pest loads every test file into ONE process — prefix top-level helpers with the file's subject. A generic name is a fatal redeclare killing the whole suite (exit 255, zero output).

## ➕ Folded in from WP4 — the FE still gates the till on the OLD permission

WP4 replaced the backend gate on `POST /pos/sales` with **`pos.sales.create`** (was `sales.invoices.create`) and shipped a bridge migration granting it to every role that held the old one. **But the frontend was not updated, so the new capability cannot be exercised:**

- `src/app/features/pos/components/cart-panel/cart-panel.component.html` lines **77** and **188** — `*appCan="'sales.invoices.create'"`
- `src/app/features/pos/pos-layout/pos-layout.component.ts` line **202** — `canSell` computed from the same old permission

Consequence today: a user granted only `pos.sales.create` sees **no Pay button** though the server would accept them; a user granted only `sales.invoices.create` sees the button and gets a **403**.

**Not a regression** (every bridged role holds both), but the WP is inert until this lands. Change all three to `pos.sales.create`.

**Also:** `pos.refunds.create` is missing from `Modules/Core/app/Support/PermissionCatalog.php` — it is a bare, uncataloged key on the roles screen. Add it beside the `pos.sales` resource WP4 created (label/description/module, `danger` consistent with how a till refund moves money). One entry, BE.

## Out of scope

New setting definitions; the terminal-settings screen; payment/refund/offline-queue logic beyond the cached session shape and the permission-gate fix above.

## Environment

- BE tests: `/opt/cpanel/ea-php82/root/usr/bin/php -d memory_limit=1G vendor/bin/pest --filter='<TestName>'`.
- Baseline 174 passed / 14 pre-existing failures (all `StorefrontProductApiTest`). Green = no NEW failures.
- `pint` on touched files only; `chown moonui2:moonui2` after each edit; moonui2 only, never `/home/moonui`.
- Dirty working tree by design — never revert/stash/commit what you did not write; do not commit at all.
- Never `git checkout/restore/stash` `src/assets/i18n/{ar,en}.json`.
