# WP2 [FIN] — Stock Adjustment + Stock Issue (the most dangerous documents)

## Goal
Put the approval engine in front of the two inventory documents that destroy stock and hit the P&L with **no second signature today**:
- **Stock Adjustment** (`ApproveAdjustment.php:47-49`) — moves stock up or down with **zero journal entry and zero second signature**, on one permission. This is the classic shrinkage/fraud vector and the least-controlled document in the system.
- **Stock Issue** (`ApproveIssue.php:195`) — decreases stock **and posts a COGS journal entry** (`:245` → `PostSaleCogsOnIssueApproved`).

Apply **Shape 1 — "authorize, then post"**: the manager authorizes the value through the engine; the keeper then physically posts. Two genuinely different acts.

## The pattern (copy it exactly — it is the approved shape)
Model on `Modules/Purchases/app/Http/Controllers/PurchaseRequestController.php` (the template):
1. **`submit`** (new endpoint per document): `$this->submitForApprovalIfConfigured($doc, ApprovalDocumentType::X);` then set status `PendingApproval`. The engine's return value is **deliberately ignored** — the presence of `approval_logs` rows, not the status, gates everything downstream.
2. **`approve`/`reject`** (the *governance* action): if `hasApprovalLogs()` → `approveViaEngine()` / `rejectViaEngine()`. If the engine returns anything other than `fully_approved`, **return early** — the doc stays pending for the next level.
3. **The commit** — the existing `approve()` that MOVES STOCK — gains `$this->assertApprovedForPost($doc, ApprovalDocumentType::X);` **at the very top**, before any stock movement or event.

⚠️ **The existing `approve()` on these controllers IS the posting action, not an approval.** Read `ApproveAdjustment::execute()` / `ApproveIssue::execute()`: they refuse unless `isDraft()`, then move stock, then write `status = Approved` — all in one transaction. Do **not** route that method itself through the engine (that would make the last approver's click the thing that moves stock, destroying the keeper's role). Guard it; don't hijack it.

## Exact files

### BE
- `Modules/Core/app/Enums/ApprovalDocumentType.php` — add cases: `InventoryAdjustment = 'inventory_adjustment'`, `InventoryIssue = 'inventory_issue'`. Map both to a new module.
- `Modules/Core/app/Enums/ApprovalModule.php` — add `case Inventory = 'inventory';` (currently only sales/purchases/production).
- `Modules/Core/app/Services/ApprovalWorkflowService.php:382-391` — **add a row per new type** to `attachDocumentReferences` (table, number column, amount column). ⚠️ The map **silently `continue`s on an unknown type** (`:396-398`) — a missing row means the approvals inbox renders blank rows with **no error**. This is mandatory, not optional.
- `Modules/Inventory/app/Models/InventoryAdjustment.php` + `InventoryIssue.php` — add a **virtual read-only `total` accessor** (these tables have no `total`/`grand_total`; the engine reads `grand_total ?? total ?? 0` — `DrivesApprovalWorkflow:42-45`). Precedent: `Modules/Purchases/app/Models/PurchaseRequest.php:128-136`. Use the real cost column (`total_cost` / `total_cost_impact` — **check the actual column name**, don't guess).
- `Modules/Inventory/app/Enums/AdjustmentStatus.php` + `IssueStatus.php` — add `PendingApproval`. **No migration needed** (the status column is `string`).
- `Modules/Inventory/app/Http/Controllers/InventoryAdjustmentController.php` (commit at `:146`) and `InventoryIssueController.php` (commit at `:225`) — `use DrivesApprovalWorkflow`; add `submit`; add the guard to the existing `approve()`; add engine approve/reject routing.
- Routes in `Modules/Inventory/routes/api.php`.

### FE
- `src/app/core/models/inventory.model.ts` — add `approval?: DocumentApprovalState` to the adjustment + issue interfaces.
- `src/app/features/stock-adjustments/stock-adjustments.component.{html,ts}` and `src/app/features/stock-issues/stock-issues.component.{html,ts}`:
  - Drop in **`<app-approval-actions [approval]="item.approval" (acted)="load()" />`** (the shared component from WP1 — do NOT re-implement it).
  - Add a **"Submit for approval"** button when `approval.required && status === 'draft'` (mirror `features/sales/invoices/invoices.component.html:89-111`, which switches the draft action on `approval.required`).
  - **Hide the commit button while pending**: wrap it in `@if (item.approval?.state !== 'pending')` (the rule from `features/sales/orders/orders.component.html:116`).
  - **RENAME the commit button** (approved decision): Adjustment "اعتماد" → **"ترحيل" / "Post"**; Issue "اعتماد" → **"صرف" / "Issue"**. Change the icon off `pi pi-check`/green — reserve ✓+green **exclusively** for the governance approve. (BE status stays `approved` — this is label + icon only.)
- i18n: new labels for the renamed buttons + `APPROVAL_WF.DT_INVENTORY_ADJUSTMENT` / `DT_INVENTORY_ISSUE` + `APPROVAL_WF.MODULE_INVENTORY` in **both** `ar.json` and `en.json`.

## Interfaces
- **Consumes from WP1:** `<app-approval-actions>` and the `document-types` endpoint (these two types must now come back `wired: true` automatically, because they get rows in `attachDocumentReferences`).
- **Exposes to WP3:** the exact per-document pattern (submit endpoint + guard placement + `total` accessor + enum row) to replicate for Receipt/GRN.

## Acceptance criteria
- [ ] **THE INVARIANT — prove it with a test:** a pending adjustment/issue **moves ZERO stock and creates ZERO journal entry**. Assert on `inventory_movements` (empty) and journal entries (none) while pending.
- [ ] **NO REGRESSION — prove it with a test:** with **no workflow configured**, `submit`+`approve` behave *exactly* as today (engine returns `auto_approved`, creates zero `approval_logs`, `assertApprovedForPost` is a no-op, stock moves as before).
- [ ] The commit (`approve()`) is 422-blocked while an approval is pending.
- [ ] Multi-level: level 1 approves → still pending; final level approves → commit becomes possible.
- [ ] Both types appear in `GET /core/approval-workflows/document-types` as `wired: true`.
- [ ] Both appear correctly in the "My Approvals" inbox with doc number + amount (proves the `attachDocumentReferences` row is right).
- [ ] FE: submit button appears only when `approval.required && draft`; commit hidden while pending; the commit button is **renamed** and no longer a green ✓.
- [ ] `ng build` green.

## Tests
- New Pest tests per document: pending-blocks-commit; pending-moves-no-stock-and-no-JE; no-workflow-is-byte-for-byte-unchanged; multi-level advances; wrong approver refused.
- Re-run `Modules/Inventory/tests` + `Modules/Sales/tests` (COGS listener) — no NEW failures vs baseline.

## Flags
- **[FIN]** — moves stock, posts COGS. **Fable consult mandatory** on: the guard placement (before ANY stock movement/event), the `total` accessor (is it the right money for the threshold?), and the auto-approve/no-regression path.
- **Migration:** none (status column is a string).

## Out of scope
- Do NOT wire Inventory **Count** (approved decision: `finalize()` moves no stock; it produces a Draft Adjustment which this WP already gates — wiring Count would approve the same event twice).
- Do NOT wire **Transfer** or **Opening Balance** (deferred — see LEDGER).
- Do NOT fix the "adjustment has no journal entry" gap (pre-existing; recorded as a deferral).
- Do NOT touch Receipt/GRN/Delivery Note (WP3/WP4).
