# WP2 — [FIN] BE: approving an over-issue auto-generates the draft issue

## Goal
Today `ProductionOrderController::decideOverIssueRequest()` APPROVE is a bare status flip (no stock, no issue) — the user must manually re-issue. Change the APPROVE branch to AUTO-GENERATE the draft إذن صرف for the material's WHOLE qty (planned-remaining + approved excess) by calling `IssueMaterials::execute()`, routed to the keeper (or inline-applied if the keeper gate is off). REJECT is unchanged (bare flip). This delivers D2 + D3.

## Exact files
- `/home/moonui2/moon-erp-be/Modules/Production/app/Http/Controllers/ProductionOrderController.php`
  - `decideOverIssueRequest()` (~815-843) — the shared approve/reject transition. Only the APPROVE path changes.
  - `approveOverIssueRequest()` (~796-799) / `rejectOverIssueRequest()` (~806-809) — entry points (likely unchanged; they call decide).
  - Inject/resolve `IssueMaterials` (app() or constructor).
- Reads: `ProductionOverIssueApproval` (production_order_material_id, requested_quantity, requested_by), `production_order_materials` (planned/consumed), `ProductionOrder::canIssue()`.

## Interfaces (consumes WP1)
- Calls `app(IssueMaterials::class)->execute($order, [[ 'material_id' => $approval->production_order_material_id, 'quantity' => $wholeQty ]], auth()->id())` where `$wholeQty = max(0, plannedQty − consumedQty) + (float) $approval->requested_quantity` computed FRESH from `production_order_materials` at approval time.
- Because WP1's partition sees this line's excess as fully covered by the just-approved allowance → the line is FREE → `createDraftInventoryIssue` builds the whole-qty draft; with keeper gate ON `execute` returns null (draft held for keeper); with keeper gate OFF `execute` inline-applies immediately.
- Consume still happens at keeper-approve time (unchanged `consumeOverIssueAllowances`) — WP2 does NOT consume.

## Behavior / edge rules (frozen minor decisions)
- Wrap the APPROVE branch (status flip + execute) in `DB::transaction` so they commit/rollback atomically (today `:834` has no transaction).
- **Idempotency:** the existing pending-guard (`:828-832`, 422 `over_issue_not_pending` if not pending) prevents a second approve → no double draft. Keep it BEFORE the flip.
- **`canIssue()` false** (order not Released/InProcess, e.g. closed/cancelled): still flip status → approved, but SKIP `execute()` and return a soft notice field (e.g. `{ approved: true, issue_created: false, notice: 'order_not_issuable' }`). Do NOT hard-fail the approval.
- **`wholeQty <= 0`** (nothing to issue — e.g. planned already fully consumed and requested 0): skip execute, approved with `issue_created: false`.
- **created_by** of the auto-draft = the approver (the `auth()->id()` passed to execute) — acceptable per architect.
- REJECT path: unchanged (bare flip; allowance stays with used_quantity=0 → not consumed → D5).

## Acceptance criteria
- Keeper gate ON: approve over-issue → a Draft InventoryIssue (ref_type=ProductionOrder) exists for the whole qty; allowance `used_quantity` still 0 (not consumed yet); order not yet InProcess from this; response indicates issue_created:true.
- Keeper approves that draft (existing path) → consume once (`used_quantity` = excess), `consumed_quantity` rises by the issued qty, WIP posts.
- Keeper REJECTS the draft → allowance `used_quantity` stays 0 (available), no MfgMaterialIssue, order unchanged (D5 regression guard).
- Keeper PARTIAL (reduces issued_quantity) → consume = actual excess only (D5 regression guard).
- Keeper gate OFF: approve → inline apply immediately (stock moves + WIP journal at approval) — [FIN] path.
- Approve twice → 2nd returns 422 `over_issue_not_pending`, single draft only.
- `canIssue()` false → status approved, no draft, soft notice.

## Tests (Pest, sqlite) — extend `ProductionOverIssueApprovalTest.php`
- approve (keeper ON) → draft created, used_quantity=0, no mfg issue yet.
- approve → keeper-approve → consume once (used=excess), consumed rises, single MfgMaterialIssue.
- approve → keeper-REJECT → used_quantity=0, no mfg issue, order unchanged.
- approve → keeper-PARTIAL → consume=actual excess, remainder available.
- approve (keeper OFF) → inline apply, journal posted, used=excess.
- approve twice → 422 second, one draft.
- canIssue false → approved, no draft.

## Flags
- **[FIN]** — keeper-gate-off approve posts WIP at approval time. Architect re-consult on the diff.
- **migration:** none.

## Out of scope
- WP1 partition internals. FE (WP3). WP4 cancel-revert. Do NOT add an `inventory_issue_id` link column (not needed for frozen decisions — architect §4).
