# Task F.2: Approval Matrix Foundation ## Status Planned ## Objective Build the Approval Matrix foundation so the system can select the correct approval workflow automatically when a quotation is submitted for approval. This task upgrades approval from a single static workflow to rule-based workflow resolution. The goal is to support approval flow selection by: * entity type * product type * branch * quotation amount * default fallback * priority when multiple rules match This task does not build the full Workflow Builder UI or Notification system yet. --- ## Mandatory Review Review before implementation: * `AGENTS.md` * `docs/standards/project-foundations.md` * `docs/standards/task-catalog.md` * existing Approval implementation * existing Quotation submit approval flow * `src/db/schema.ts` * `src/features/crm/approval/**` * `src/features/crm/quotations/**` * `src/features/crm/opportunities/**` * `src/features/foundation/audit-log/**` * `src/features/foundation/master-options/**` * `docs/security/crm-authorization-boundaries.md` --- ## Current Foundation Already available: ```txt crm_approval_workflows crm_approval_steps crm_approval_requests crm_approval_actions quotation_standard_approval quotation submit approval flow quotation approval actions audit foundation ``` Current limitation: ```txt Quotation approval uses a fixed or first active workflow. ``` This task introduces matrix-based workflow resolution. --- ## Business Requirement When a quotation is submitted for approval, the system must determine: ```txt Which approval workflow should this quotation use? ``` The workflow should be selected based on business conditions. Example: ```txt Crane quotation under 500,000 → Crane Standard Approval Crane quotation over 5,000,000 → Crane Executive Approval Dock Door quotation → Dock Door Approval No specific match → Default Quotation Approval ``` --- ## Scope F.2.1 Approval Matrix Schema Add table: ```txt crm_approval_matrices ``` Recommended columns: ```txt id organization_id entity_type workflow_id branch_id product_type min_amount max_amount currency priority is_default is_active description created_at updated_at deleted_at created_by updated_by ``` Recommended types: ```txt entity_type text not null workflow_id text not null branch_id text null product_type text null min_amount numeric(14,2) null max_amount numeric(14,2) null currency text null priority integer not null default 100 is_default boolean not null default false is_active boolean not null default true ``` Rules: * `entity_type = quotation` for quotation approval * `product_type = null` means all product types * `branch_id = null` means all branches * `min_amount = null` means no lower bound * `max_amount = null` means no upper bound * `currency = null` means all currencies * lower priority number wins, or document project convention if already defined * only active rules are considered * soft-deleted rules are ignored --- ## Scope F.2.2 Matrix Matching Rules Implement resolver: ```txt src/features/crm/approval/server/matrix-resolver.ts ``` Function: ```ts resolveApprovalWorkflowForEntity(input) ``` Input: ```ts { organizationId: string; entityType: 'quotation'; branchId?: string | null; productType?: string | null; amount?: number | null; currency?: string | null; } ``` Output: ```ts { matrixId: string; workflowId: string; matchReason: string; } ``` Matching rules: 1. Match same organization 2. Match same entity type 3. Ignore inactive or deleted matrix rows 4. Product type must match exact or be null 5. Branch must match exact or be null 6. Currency must match exact or be null 7. Amount must be within range: * `minAmount <= amount` if min exists * `amount <= maxAmount` if max exists 8. Choose most specific rule 9. If specificity ties, choose highest priority 10. If still tied, choose newest active rule or throw deterministic conflict error 11. If no specific rule matches, use default rule 12. If no default rule exists, throw clear configuration error Specificity scoring suggestion: ```txt productType match = +10 branch match = +10 currency match = +5 amount range match = +5 default rule = -100 ``` --- ## Scope F.2.3 Submit Approval Integration Update quotation submit approval flow. Current behavior: ```txt use static quotation_standard_approval ``` New behavior: ```txt quotation submit ↓ build approval matrix input from quotation ↓ resolve workflow ↓ create approval request with resolved workflow ``` Quotation fields used: ```txt organizationId branchId productType / quotationType totalAmount currency ``` Rules: * do not select workflow in route handler * service must call matrix resolver * approval request must store selected workflowId * audit must include selected matrixId and workflowId * existing approval action flow should continue unchanged after request is created --- ## Scope F.2.4 Default Matrix Seed Update seed to create at least one default quotation approval matrix. Example: ```txt entity_type = quotation workflow = quotation_standard_approval product_type = null branch_id = null min_amount = null max_amount = null currency = null priority = 999 is_default = true is_active = true ``` This preserves current behavior while enabling future product-specific rules. --- ## Scope F.2.5 Product Type Matrix Examples Seed optional sample rules only if project seed policy allows. Examples: ```txt quotation_crane_standard product_type = crane amount <= 500000 workflow = quotation_standard_approval quotation_crane_executive product_type = crane amount > 5000000 workflow = quotation_executive_approval ``` If required workflows do not exist, do not seed these sample rules. --- ## Scope F.2.6 Matrix Admin API Foundation Create API foundation for managing matrix rows. Routes: ```txt GET /api/crm/approval/matrices POST /api/crm/approval/matrices GET /api/crm/approval/matrices/[id] PATCH /api/crm/approval/matrices/[id] DELETE /api/crm/approval/matrices/[id] ``` Rules: * no UI required in F.2 * enforce approval admin permission * validate workflow exists * validate no invalid range * soft delete only * prevent deleting default matrix if it is the only active default for entity type --- ## Scope F.2.7 Validation Rules Create schemas: ```txt src/features/crm/approval/schemas/approval-matrix.schema.ts ``` Validation: * entityType required * workflowId required * minAmount must be <= maxAmount when both exist * priority must be integer * productType optional * branchId optional * currency optional * isDefault boolean * isActive boolean Conflict warning: * multiple active matrix rows can overlap * resolver must handle deterministic selection * API may warn but does not need to fully block overlap in F.2 --- ## Scope F.2.8 Audit Logging Audit actions: ```txt create_approval_matrix update_approval_matrix delete_approval_matrix resolve_approval_matrix ``` For submit approval, audit must capture: ```txt quotationId matrixId workflowId matchReason productType branchId amount currency ``` --- ## Scope F.2.9 Permission Rules Add or verify permissions: ```txt crm.approval.matrix.read crm.approval.matrix.create crm.approval.matrix.update crm.approval.matrix.delete ``` Submit approval should still use existing quotation approval submit permission. Rules: * route handlers must not check role strings directly * use existing CRM authorization foundation * admin-only API for matrix maintenance --- ## Scope F.2.10 Documentation Create: ```txt docs/implementation/task-f2-approval-matrix-foundation.md ``` Document: ```txt schema matching rules priority rules default fallback submit approval integration seed behavior known limitations ``` --- ## Explicit Non-Scope Do not implement: * full Workflow Builder UI * drag-and-drop step editor * Notification Center * Email sending * LINE / webhook sending * reminder / escalation * approval dashboard * approval delegation * parallel approval * any-one approval * dynamic approver lookup by manager hierarchy These belong to later F tasks. --- ## Verification Run: ```txt npm exec tsc --noEmit npm run build ``` Manual verification: ```txt Default matrix exists after seed Submit quotation approval still works Approval request stores resolved workflowId Matrix resolver chooses product-specific rule over default Matrix resolver chooses branch-specific rule over all-branch rule Matrix resolver chooses amount-range rule correctly Matrix resolver handles no-match with clear error Matrix CRUD APIs work Soft delete matrix works Audit logs include matrix resolution Existing approval approve/reject actions still work Existing quotation flow still works ``` --- ## Definition of Done Task is complete when: * `crm_approval_matrices` exists * default quotation approval matrix is seeded * quotation submit approval uses matrix resolver * workflow selection supports product type, branch, amount, currency, default fallback, and priority * matrix CRUD APIs exist * audit logs capture matrix changes and resolution * existing approval actions continue working Result: ```txt Approval Matrix Foundation = Established Workflow Resolution = Dynamic Ready for F.3 Approval Workflow Builder ```