9.1 KiB
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.mddocs/standards/project-foundations.mddocs/standards/task-catalog.md- existing Approval implementation
- existing Quotation submit approval flow
src/db/schema.tssrc/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:
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:
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:
Which approval workflow should this quotation use?
The workflow should be selected based on business conditions.
Example:
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:
crm_approval_matrices
Recommended columns:
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:
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 = quotationfor quotation approvalproduct_type = nullmeans all product typesbranch_id = nullmeans all branchesmin_amount = nullmeans no lower boundmax_amount = nullmeans no upper boundcurrency = nullmeans 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:
src/features/crm/approval/server/matrix-resolver.ts
Function:
resolveApprovalWorkflowForEntity(input)
Input:
{
organizationId: string;
entityType: 'quotation';
branchId?: string | null;
productType?: string | null;
amount?: number | null;
currency?: string | null;
}
Output:
{
matrixId: string;
workflowId: string;
matchReason: string;
}
Matching rules:
-
Match same organization
-
Match same entity type
-
Ignore inactive or deleted matrix rows
-
Product type must match exact or be null
-
Branch must match exact or be null
-
Currency must match exact or be null
-
Amount must be within range:
minAmount <= amountif min existsamount <= maxAmountif max exists
-
Choose most specific rule
-
If specificity ties, choose highest priority
-
If still tied, choose newest active rule or throw deterministic conflict error
-
If no specific rule matches, use default rule
-
If no default rule exists, throw clear configuration error
Specificity scoring suggestion:
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:
use static quotation_standard_approval
New behavior:
quotation submit
↓
build approval matrix input from quotation
↓
resolve workflow
↓
create approval request with resolved workflow
Quotation fields used:
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:
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:
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:
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:
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:
create_approval_matrix
update_approval_matrix
delete_approval_matrix
resolve_approval_matrix
For submit approval, audit must capture:
quotationId
matrixId
workflowId
matchReason
productType
branchId
amount
currency
Scope F.2.9 Permission Rules
Add or verify permissions:
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:
docs/implementation/task-f2-approval-matrix-foundation.md
Document:
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:
npm exec tsc --noEmit
npm run build
Manual verification:
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_matricesexists- 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:
Approval Matrix Foundation = Established
Workflow Resolution = Dynamic
Ready for F.3 Approval Workflow Builder