# WP1 — Wire the ApprovalWorkflow engine into Sales [FIN]

## Goal
Make the generic Core approval engine actually gate Sales documents. Today the engine (`ApprovalWorkflowService::submitForApproval`) has ZERO callers; Sales uses a hardcoded status chain (Draft→PendingApproval→Approved→Posted) with a single permission-gated `approve`. Wire them together so that when an active `ApprovalWorkflow` exists for a sales document type and the amount matches a level's thresholds, the document is held **pending approval** and CANNOT be posted until the engine's `approval_logs` for it are all approved — while preserving today's behavior byte-for-byte when NO workflow is defined (the engine already returns `'auto_approved'` in that case).

## Exact files (BE, `/home/moonui2/moon-erp-be`)
- `Modules/Core/app/Services/ApprovalWorkflowService.php` — the engine. READ it fully. Signature: `submitForApproval(int $companyId, ApprovalDocumentType $documentType, int $documentId, float $amount): string` → `'auto_approved'` | `'pending'`. Also `approve()`, `reject()`, `canUserApprove()`, `getPendingForUser()`. Add a small helper if needed: `isFullyApproved(company, ApprovalDocumentType, docId): bool` (no pending logs remain) and `hasPendingApproval(...)` — check if such helpers already exist before adding.
- `Modules/Core/app/Enums/ApprovalDocumentType.php` — the sales cases (`sales_quotation/order/invoice/return/delivery_note`) + `module()` map.
- `Modules/Sales/app/Http/Controllers/SalesInvoiceController.php` — `submitApproval()`, `approve()`, `post()`. This is the primary wiring site.
- `Modules/Sales/app/Actions/PostSalesInvoice.php` — `canPost()===Approved` hard-guard (`:38`). Post must ALSO refuse if the engine still has a pending log for the invoice.
- `Modules/Sales/app/Enums/InvoiceStatus.php` — status transitions.
- Then the same wiring for **sales orders, quotations, returns** (their controllers' submitApproval/approve/post-equivalent) — invoice FIRST, get it right, then replicate.
- Create a small shared trait/service used by all sales doc controllers so WP2 (Purchases) can reuse it — e.g. `Modules/Core/app/Support/DrivesApprovalWorkflow.php` with `submitForApprovalIfConfigured($doc, ApprovalDocumentType)` and `assertApprovedForPost($doc, ApprovalDocumentType)`.

## Wiring contract (the exact behavior)
1. **On `submitApproval` (or on create-that-would-post):** call `submitForApproval(company, docType, doc->id, doc->grand_total|total)`.
   - Returns `'auto_approved'` → proceed exactly as today (advance status toward Approved; no engine rows).
   - Returns `'pending'` → set the document to `PendingApproval`; do NOT let it be approved/posted by the old single-permission path. The engine has created `approval_logs`.
2. **On `approve` (controller action):** if the doc has engine logs, route the approval THROUGH the engine (`ApprovalWorkflowService::approve` for the current user's pending level, honoring `canUserApprove` = assigned user/role + level order). Only when the engine reports the doc **fully approved** does the document status become `Approved`. If the doc has NO engine logs (auto path), keep today's behavior.
3. **On `post`:** in addition to `canPost()===Approved`, assert the engine has no pending log for this document (`assertApprovedForPost`) — a hard block. If no logs exist (auto path) this is a no-op → today's behavior.
4. **Amount thresholds + levels + assigned approver** are all handled INSIDE the engine already — you just pass the amount and route approve through it. Do not re-implement threshold logic.

## Interfaces (exposed to later WPs)
- Shared helper `DrivesApprovalWorkflow` (trait) with `submitForApprovalIfConfigured(Model $doc, ApprovalDocumentType $t): string` and `assertApprovedForPost(Model $doc, ApprovalDocumentType $t): void` (throws 422 if pending). WP2 (Purchases) reuses these.
- The document is `PendingApproval` while engine logs are pending; `Approved` only when fully approved. WP3 will read `approval_logs` to expose per-document state to the FE.

## Acceptance criteria
- **NO-REGRESSION (mandatory):** with NO `ApprovalWorkflow` row for `sales_invoice`, creating+posting an invoice behaves EXACTLY as before (existing SalesInvoiceApiTest stays green). Prove with a test.
- With an active workflow (1 level, role=X, min_amount=10000): an invoice total ≥10000 becomes `pending_approval`, cannot be posted (422), and only a user satisfying the level's approver (role X) can approve; after approval it can post. An invoice total <10000 (below threshold) auto-approves and posts as today.
- Multi-level: a 2-level workflow requires BOTH levels before the doc is fully approved/postable.
- Same wiring works for sales order/quotation/return (at least invoice fully; others wired to submit+block-post).

## Tests (Pest, sqlite)
- Extend/add `Modules/Sales/tests/Feature/` + a wiring test `SalesApprovalWiringTest.php`: no-workflow auto-post (no-regression), threshold-gated pending+block+approve+post, wrong-approver 403, multi-level. Baseline before starting: SalesInvoiceApiTest = **33 passed**; ApprovalWorkflowApiTest = **21 passed**. Gate = zero NEW failures. Run file-by-file (shared test DB).

## Flags
- **[FIN]** — touches document posting/lifecycle → Fable/advisor review of the diff (orchestrator runs it).
- Migration: only if you add an index on `approval_logs(document_type, document_id, status)` for the "any pending?" query — timestamp must sort after existing; run on `moonui2_dev_be` same step. ⛔ never migrate:fresh.

## Out of scope
- Purchases (WP2). FE (WP4-6). Production/other modules. Do NOT change the engine's threshold/level math (it's correct). Do NOT commit-to-main / deploy.
