Files
alla-allaos-fullstack/plans/task-f.2.md
phaichayon 052677fa2c task f.3
2026-06-26 11:03:25 +07:00

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.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:

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 = 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:

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:

  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:

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_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:

Approval Matrix Foundation = Established
Workflow Resolution = Dynamic
Ready for F.3 Approval Workflow Builder