# WP6 — Cash boxes: the same scope, where it matters most

**Repo:** BE + FE · **Branch:** `hazemdev2` · **Migration:** YES · **Depends on WP5**
**[FIN]** — this touches money. Treat every decision as financial, not cosmetic.

## Goal

Apply the WP1 engine to cash boxes, the way WP3/WP4 applied it to warehouses.

> Owner, on widening the scope: «الخزن نفس القصة» — the cash boxes are the same story.

## Why this is the sharpest instance of the problem

`LabTreasuryController::index` filters by `company_id` **only**. So today **every cashier holding the
view permission sees every cash box in the company and its balance.** `PettyCashController::index`
adds only a *request-supplied* `branch_id`, which is a convenience filter, not access control — omit
the parameter and you get everything.

And `petty_cash.custodian_id` exists, is settable from the UI, and is read by **zero queries** —
exactly like `warehouses.manager_id`.

## The blocker unique to this WP

**`petty_cash_transactions` has NO creator column at all.** So mode `own_records` is not merely
unwired for cash — it is **unbuildable** without a schema change.

Settled decision (§9 Q5): **add the column.** The reasoning is not primarily about scoping — a cash
movement with no record of who made it is an **audit gap**. Scoping just made it visible.

- Add a nullable creator column and populate it on create from the authenticated user.
- **Historical rows stay null** — that is honest; we genuinely do not know who made them.
- Decide and document how a null-creator row behaves under `own_records`. Recommendation: **not
  visible** (fail closed), with the reasoning written down — but argue it, because the alternative
  (legacy rows visible to all) is defensible too and this is the owner's money.

## What to build

**Backend**
1. Wire `ResourceScope` with the `cash_box` resource through every cash-box read and action:
   `PettyCashController`, `LabTreasuryController`, and every place a cash box is listed, opened, or
   used as a payment source. **Include the balance/summary queries** — a scoped list above a
   company-wide total is the most confusing possible half-fix.
2. The creator column migration + population.
3. **Extend WP2's invariant test to Accounting** — WP2 was built so a second module is added by
   extending a data structure, not by copying the test. Use that seam. Its expected-red list for the
   new module is your checklist, exactly as WP2's was for WP3.
4. Out-of-scope by-id access ⇒ **404**, as with warehouses.

**Frontend**
5. Same treatment as WP4: no client-side filtering, a server-scoped picker, the scope badge, and
   single-option-as-static-text.
6. ⚠️ **The expenses/revenues/voucher screens offer every active cash box as a payment source with no
   branch check at all** — while checking bank accounts five lines away. Fix those with the shared
   helper WP4 built; do not add an eleventh copy of the rule.

## Acceptance criteria

1. Mode `all` (default) ⇒ identical behaviour to today, for every cash screen. **[FIN] — prove it.**
2. Mode `assigned` ⇒ a cashier sees only his boxes, **and the balances/totals agree with the list**.
3. Empty assignment ⇒ zero boxes, never all.
4. Out-of-scope box by id ⇒ 404.
5. The creator column is populated on new transactions; historical nulls behave per your documented
   decision, asserted by test.
6. WP2's invariant test covers Accounting and is green.
7. Payment-source pickers on expenses/revenues/vouchers respect the scope.
8. `pest Modules/Accounting` and the invariant test: **zero NEW failures**.
9. `npx ng build --base-href /app/` green; `npx tsc --noEmit` clean.


## ⚠️ Hand-off detail from WP1 — do not miss this

WP1 seeded the mode settings with **`is_implemented = false`**, so the settings choke point currently
**refuses any write to the mode**. That is deliberate: until the wiring exists, an admin must not be
able to switch a company into a mode that nothing enforces.

**Flipping `is_implemented => true` for `accounting.cash_box_data_scope` is part of THIS work package**, and it is
the last thing you do — after the wiring is green. Re-seed and confirm the setting becomes writable.

Also from WP1, exactly as built:
- `ResourceScope::apply($query, $user, 'warehouse', $opts)` · `::applyReport($query, ...)` for raw
  `DB::table` queries. Option keys: **`columns`** (1–2 columns; two ⇒ OR semantics — transfers pass
  both `from_warehouse_id` and `to_warehouse_id`), **`owner`** (string, or an explicit `null` to
  select the aggregate fallback).
- **`$user === null` is the documented system-context exemption** — postings and queued jobs pass
  through unrestricted. Do not "helpfully" pass a fallback user.
- Creator-less aggregates (`stock_balances`, `movements`) under `own_records` fall back to
  **`assigned`**, never to unrestricted — WP1 asserts this; do not re-decide it.
- Roles can only **narrow** the company mode, never widen it.

## Environment / rules

- Tests: `cd /home/moonui2/moon-erp-be && /opt/cpanel/ea-php82/root/usr/bin/php -d memory_limit=1G vendor/bin/pest --filter='…'`
  (bare `php` is php-cgi → "Undefined constant STDOUT").
- Run your own test file + the invariant test + `pest Modules/Accounting`.
- ⛔ **Pest loads every test file into ONE process** — prefix every top-level helper with its file's
  subject. Duplicate top-level function = fatal redeclare, exit 255, zero output (5 occurrences here).
- ⛔ NEVER `migrate:fresh` / `migrate:refresh` / `db:wipe` on `moonui2_dev_be` — not binlogged.
  Run the new migration on dev in THIS WP.
- ⛔ **NEVER** `git checkout` / `restore` / `stash` on `src/assets/i18n/ar.json` or `en.json`.
- API auth header is `X-Authorization: Bearer`.
- `./vendor/bin/pint` on touched files only. `chown moonui2:moonui2` every edited file.
  `bash local-deploy.sh` after BE edits. Builds pre-authorized; **do NOT deploy to `/app`**.
- Bilingual `[Unreleased]` bullet — user-visible, and financial.
- Commit on `hazemdev2`, conventional. **Do not push, do not merge.**
- moonui2 ONLY — never `/home/moonui`. Never print a git remote URL.

## Out of scope

Cost centres / POS terminals / POS sessions (WP7) · reworking cash accounting itself · deleting the
legacy `custodian_id` column.
