# WP4 — [FIN] controller validation + consignment availability pre-check

## Goal
Expose the per-line routing fields safely: validate `lines.*.warehouse_id` + `lines.*.owner_partner_id` on the production issue-materials endpoint (company-scoped), enforce the warehouse/owner consistency guards, and pre-check consignment availability so an under-stocked consignor line returns a clean 422 BEFORE any draft/sequence is burned. This is what makes WP3/WP5's routing reachable + safe. (WP5 already fixed the keeper path, so exposing the fields now is safe.)

## Exact files
- `Modules/Production/app/Http/Controllers/ProductionOrderController.php` — `issueMaterials()` validation (~:657-680). ADD:
  - `'lines.*.warehouse_id' => ['nullable','integer', Rule::exists('warehouses','id')->where('company_id', $user->company_id)]` (company-scoped — folds WP2-LOW).
  - `'lines.*.owner_partner_id' => ['nullable','integer', Rule::exists('business_partners','id')->where('company_id', $user->company_id)]`.
  - These are the two keys `IssueMaterials::resolveLines` already reads (WP3). Once validated, `$request->validated()` no longer strips them → per-line routing becomes reachable.
- `Modules/Production/app/Actions/IssueMaterials.php` — `execute()` (BEFORE `createDraftInventoryIssue`, inside the tx after resolveLines): add guards + availability pre-check per resolved line that carries routing:
  - **Warehouse/owner consistency:** if `owner_partner_id > 0` → the line's effective warehouse (line warehouse_id ?? issue warehouse) MUST be `is_consignment` (Warehouse.php:36-38); else 422 (e.g. `consignor_requires_consignment_warehouse`). If `owner_partner_id === 0` or NULL-derived-own → the effective warehouse must NOT be a consignment warehouse (own material can't live in a consignment warehouse); else 422 (`own_material_needs_company_warehouse`). (Confirm the exact rule with the architect design; keep messages i18n keys under production::production.)
  - **Consignment availability pre-check:** for each line with `owner_partner_id > 0`, assert `InventoryLotBalance::forOwner($owner)->live()` SUM available (quantity − reserved) in the effective warehouse ≥ the line quantity. If short → clean 422 `consignment_insufficient` naming the partner + material + shortfall. (Scopes: InventoryLotBalance forOwner/live ~:95-104.) This fires BEFORE createDraftInventoryIssue so no sequence is burned.
  - Own lines: unchanged (ApproveIssue's existing gross-stock check stands).
- i18n: add the new validation/error message keys (ar + en) under the production module lang files.

## Interfaces
- After WP4, the endpoint accepts + validates per-line warehouse_id/owner_partner_id (company-scoped) and rejects impossible/under-stocked consignor lines with clean 422s. WP6 (FE) sends these fields.

## Acceptance
- OFF-path: an issue with no per-line fields validates + runs exactly as today (the new rules are `nullable` → skipped).
- A line with `owner_partner_id=X` + a non-consignment warehouse → 422 (consistency).
- A line with `owner_partner_id=X` but X's consignment on-hand in that warehouse < qty → 422 `consignment_insufficient` (no draft created, no sequence burned).
- A line with a warehouse_id from ANOTHER company → 422 (company-scoped exists rule).
- A valid consignor line (X has enough consignment in a consignment warehouse) → passes validation → issues (WP3 path).

## Tests (Pest, sqlite)
- validation: cross-company warehouse_id → 422; owner_partner_id of another company → 422.
- consistency: consignor + non-consignment warehouse → 422; own + consignment warehouse → 422.
- availability: consignor line short on that customer's consignment → 422, NO inventory_issue row created (assert count unchanged).
- happy path: valid consignor line passes + issues (assert the WP3 CUSTOMER-leg outcome).
- OFF-path regression: existing issue tests unchanged (baseline PerLineConsignor 4, ownership 13, over-issue 22, InventoryIssueApi 27). Ignore ConsignmentFoundationTest settle-buy pre-existing red.

## Flags
- **[FIN]** (guards a money/stock path). migration: none. Conventional commit `feat(production): validate + pre-check per-line consignor issue (ISS-2026-9166)` on hazemdev2. No FE, no main, no deploy. chown if root.

## Out of scope
- FE (WP6). The routing engine itself (WP3/WP5). Only validation + the pre-check guards here.
