Approval System¶
A flexible, multi-tier approval system for FarmCove Delta that supports configurable approval workflows per project for different record types (transactions, contracts, documents, etc.).
Table of Contents¶
- Overview
- Core Concepts
- Architecture
- Configuration Guide
- Approval Flows
- Business Rules
- Permissions
- Database Schema
- Views Reference
- Category Seed Data
- User Interface
Overview¶
The approval system enables project administrators to define approval workflows that records must pass through before being finalized. Key features include:
- Per-Project Configuration: Each project can have different approval rules
- Type/Subtype Granularity: Configure approvals for specific record types (e.g., only Invoices, not Expenses)
- Multi-Tier Workflows: Sequential approval levels with different approvers at each tier
- Conditional Tiers: Tiers can be skipped based on conditions (e.g., amount thresholds)
- Entity-Based Approvers: Approvers are project team members (entities with user accounts)
- Full Audit Trail: Every approval action is tracked with timestamps and notes
- Query/Discussion: Approvers can query records and communicate with submitters
- Soft Approvals: Automatic review workflow for records with processing issues but no formal approval configuration
Architecture Diagram¶
+---------------------------------------------------------------------------+
| PROJECT |
| "Summer Film 2024" |
| approvals_enabled = true |
+---------------------------------------------------------------------------+
|
v
+---------------------------------------------------------------------------+
| APPROVAL CONFIGURATION |
| Type: transactions / Subtype: Invoice |
+---------------------------------------------------------------------------+
|
+-----------------------+-----------------------+
v v v
+-------------------+ +-------------------+ +-------------------+
| TIER 1 | | TIER 2 | | TIER 3 |
| amount > 100 | | amount > 1000 | | amount > 5000 |
| | | | | |
| Approvers: | | Approvers: | | Approvers: |
| - John (Entity) | | - Finance Dir | | - CFO |
| - Jane (Entity) | | (Entity) | | (Entity) |
+-------------------+ +-------------------+ +-------------------+
Core Concepts¶
Approval Configuration¶
Defines that a specific type/subtype combination requires approvals for a project. For example:
- Project "Summer Film 2024" + Type "transactions" + Subtype "Invoice" = Approval required
- Project "Summer Film 2024" + Type "transactions" + Subtype "Expense" = No configuration = No approval needed
Tiers¶
Sequential approval levels within a configuration. Each tier:
- Has a tier number (1, 2, 3...) determining order
- Can have optional conditions that determine if approval is needed
- Has one or more approvers assigned
- Only ONE approval per tier is sufficient (other approvers are marked as skipped)
Conditions¶
Optional rules per tier that determine if approval is needed. Stored as JSONB:
{
"logic": "ANY",
"rules": [
{
"field": "amount",
"operator": "gt",
"value": "1000"
}
]
}
logic: "ANY" (OR) or "ALL" (AND)field: "amount" | "entity_name"operator: "gt" | "gte" | "lt" | "lte" | "eq" | "neq" | "in" | "not_in"
If conditions are not met, the tier is automatically skipped.
Approvers¶
Entities (project team members) assigned to approve at each tier:
- Must have a user account (
user_id IS NOT NULL) - Must have an active project relationship
- One approval per tier is sufficient
Approval Request¶
Created when a record is submitted for approval. Tracks:
- The record being approved (type + ID)
- Current tier being evaluated
- Overall status (Pending, Approved, Rejected, Queried)
- Submitter information
Approval Instance¶
Individual approval assignments per approver. Each instance tracks:
- Which approver is assigned
- Whether the tier condition was met
- The approver's decision and any notes
- Timestamps for all actions
Soft Approval¶
A special approval flow for records that have processing issues but no formal approval configuration. Instead of auto-approving, the system creates a soft approval request for review:
- Trigger: Record has
pendingIssuesCount > 0but no matching approval configuration - Approvers: Users with the
approval:soft:viewpermission (project owners automatically have this) - Single Tier: Soft approvals have only one tier (tier_number = 1)
- No Configuration:
approval_configuration_idis NULL,is_soft_approvalis TRUE - Tab Label: Displayed as "Issues Review" in the approval UI
This ensures records with problems are reviewed by designated users rather than being auto-approved.
Configuration Guide¶
Step 1: Enable Approvals for Project¶
Set projects.approvals_enabled = true for the project.
Step 2: Enable Type (Optional)¶
Create an approval_configuration with subtype_category_id = NULL to enable the type:
- project_id: Your project
- type_category_id: Category for 'transactions'
- subtype_category_id: NULL (type-level enablement)
Step 3: Create Subtype Configuration¶
Create an approval_configuration for each subtype that needs approvals:
- project_id: Your project
- type_category_id: Category for 'transactions'
- subtype_category_id: Category for 'Invoice'
Step 4: Add Tiers¶
Create approval_tiers for each approval level:
-- Tier 1: Manager approval for amounts > $100
INSERT INTO approval_tiers (approval_configuration_id, tier_number, name, conditions)
VALUES (config_id, 1, 'Manager Approval',
'{"logic": "ANY", "rules": [{"field": "amount", "operator": "gt", "value": "100"}]}');
-- Tier 2: Finance Director for amounts > $1000
INSERT INTO approval_tiers (approval_configuration_id, tier_number, name, conditions)
VALUES (config_id, 2, 'Finance Director',
'{"logic": "ANY", "rules": [{"field": "amount", "operator": "gt", "value": "1000"}]}');
Step 5: Assign Approvers¶
Add approval_tier_approvers for each tier:
INSERT INTO approval_tier_approvers (approval_tier_id, entity_id)
VALUES
(tier1_id, john_entity_id),
(tier1_id, jane_entity_id),
(tier2_id, finance_director_entity_id);
Approval Flows¶
Example 1: $3000 Invoice - Full Approval Flow¶
Setup:
- Tier 1: amount > $100 -> John, Jane
- Tier 2: amount > $1000 -> Finance Director
- Tier 3: amount > $5000 -> CFO
Flow:
- Invoice created ($3000) -> Transaction status: "Processed"
- System finds approval configuration -> Creates approval_request
- Tier 1 Evaluation: $3000 > $100? YES
- Creates instances for John and Jane (status: Pending)
- Transaction status -> "Submitted for Approval"
- John Approves
- John's instance -> Approved
- Jane's instance -> Skipped (reason: "Approved by another approver")
- Move to Tier 2
- Tier 2 Evaluation: $3000 > $1000? YES
- Creates instance for Finance Director (status: Pending)
- Finance Director Approves
- Instance -> Approved
- Move to Tier 3
- Tier 3 Evaluation: $3000 > $5000? NO
- Creates instance for CFO (status: Skipped, condition_met: false)
- No more tiers -> Request status: Approved
- Transaction status -> "Approved"
Example 2: $50 Invoice - Auto-Approved¶
Flow:
- Invoice created ($50)
- System finds approval configuration
- All Tiers Evaluated:
- Tier 1: $50 > $100? NO -> Auto-skip
- Tier 2: $50 > $1000? NO -> Auto-skip
- Tier 3: $50 > $5000? NO -> Auto-skip
- All tiers skipped -> Request status: Approved (no human intervention)
Example 3: Query Flow¶
- John queries the transaction (creates message with approval_request_id)
- John's instance status -> "Queried"
- Request status -> "Queried"
- Message thread continues until clarified
- John approves after clarification -> Flow continues normally
Example 4: Rejection Flow¶
- Finance Director rejects at Tier 2
- Instance status -> "Rejected" with decision_note
- Request status -> "Rejected" with reject_reason
- All remaining instances -> "Skipped" (reason: "Request rejected")
- Transaction status -> "Rejected"
Example 5: Higher Tier Early Approval¶
CFO (Tier 3 approver) can see and approve items still at Tier 1:
- Creates/updates CFO's instance -> "Approved"
- All lower tier instances -> "Skipped" (reason: "Approved by higher tier approver")
- Request -> "Approved"
Example 6: Soft Approval - Transaction with Processing Issues¶
Scenario: An Expense transaction is created but has no approval configuration. However, AI processing flagged an issue (e.g., missing justification).
Flow:
- Transaction created -> AI processing completes with 1 pending issue
- System checks for approval configuration -> None found
- System checks
pendingIssuesCount-> 1 issue found - System creates soft approval:
- Creates
approval_requestwithis_soft_approval = true,approval_configuration_id = NULL - Finds users with
approval:soft:viewpermission - Creates
approval_instancefor each (tier_number = 1, approval_tier_id = NULL) - Transaction status -> "In Approval"
- Users see item in "Issues Review" tab
- User reviews the processing issue and approves/rejects/queries
- On approval -> Transaction status: "Approved"
Key Differences from Regular Approval:
- No
approval_configurationrecord exists - Triggered by processing issues, not configuration
- Single tier only (no multi-tier workflow)
- Approvers determined by permission, not tier assignment
Business Rules¶
Hierarchical Enable Logic¶
Approvals must be enabled at three levels:
1. Project Level: projects.approvals_enabled = true?
+-- NO -> No approvals for anything in this project
+-- YES -> Continue
2. Type Level: approval_configuration (project, type, NULL) exists and is_active?
+-- NO -> No approvals for this type
+-- YES -> Continue
3. Subtype Level: approval_configuration (project, type, subtype) exists?
+-- NO -> No approval needed for this subtype
+-- YES -> Apply tiers from this configuration
Tier Evaluation Rules¶
- Tiers evaluated in order (1, 2, 3...)
- If conditions = NULL/empty -> Tier ALWAYS requires approval
- If conditions not met -> Tier auto-skipped
- Multiple conditions with
logic: "ANY"-> OR (any triggers tier) - Multiple conditions with
logic: "ALL"-> AND (all must match)
Approver Rules¶
- Approvers are entities with
user_id IS NOT NULL - Must have active
project_relationshipon the project - ONE approval per tier is sufficient
- Other approvers marked as skipped when one approves
Higher Tier Access¶
- Tier N approvers can see ALL items from Tier 1 to N
- Early approval by higher tier skips all lower tiers
Submitter Rules¶
- For transactions:
created_by_user_idif not internal user - If internal user: use
submission_addressfield - Submitter CAN be an approver (no self-approval restriction)
Query/Discussion¶
- Uses
messagestable withapproval_request_id - Only submitter and assigned approvers can participate
- Query pauses approval at current tier until resolved
Rejection¶
- Any approver can reject at any tier
- Rejection ends the approval process
- All remaining instances marked as skipped
reject_reasonstored on bothapproval_requestand the record
Permissions¶
| Permission Key | Description | Scope |
|---|---|---|
| approval_rule:view | View approval configurations | project |
| approval_rule:create | Create approval configurations | project |
| approval_rule:edit | Edit approval configurations | project |
| approval_rule:delete | Delete approval configurations | project |
| approval:view | View approval requests | project |
| approval:approve | Approve/reject/query records | project |
| approval:soft:view | View and action records with processing issues when no formal approval config | project |
Note: The approval:soft:view permission is automatically granted to users with the project:owner role, ensuring at least one approver exists for soft approvals.
Database Schema¶
See DATABASE_SCHEMA.md for complete table definitions.
Tables¶
| Table | Purpose |
|---|---|
| approval_configurations | Enable approvals for type/subtype per project |
| approval_tiers | Define approval levels with conditions |
| approval_tier_approvers | Assign approvers to tiers |
| approval_requests | Track records through approval workflow |
| approval_instances | Track each approver's decision (audit trail) |
Enum¶
- approval_status:
Pending,Approved,Rejected,Skipped,Queried
Changes to Existing Tables¶
- projects: Added
approvals_enabledcolumn - transactions: Added
reject_reasoncolumn - messages: Added
approval_request_idcolumn
Views Reference¶
Configuration Views¶
| View | Purpose | Filter By |
|---|---|---|
| v_project_approval_types | Types enabled per project (toggle switches) | project_id |
| v_project_approval_subtypes | Subtypes with config status | project_id, type_code |
| v_approval_configuration_summary | Config overview with tier/approver counts | project_id |
| v_approval_tier_details | Detailed tier view with approvers | approval_configuration_id |
Request/Instance Views¶
| View | Purpose | Filter By |
|---|---|---|
| v_user_pending_approvals | User's pending approval items | user_id, entity_id |
| v_approval_request_status | Request progress with tier statistics | record_type, record_id |
| v_approval_request_timeline | Full audit trail for a request | approval_request_id |
| v_approval_messages | Discussion thread messages | approval_request_id |
| v_soft_approval_approvers | Users who can approve soft approvals | project_id |
Note: Views now include an is_soft_approval flag to distinguish soft approvals from regular approvals.
View Usage Examples¶
Get pending approvals for current user:
SELECT * FROM v_user_pending_approvals
WHERE user_id = auth.uid()
AND can_approve = true;
Get approval status for a transaction:
SELECT * FROM v_approval_request_status
WHERE record_type = 'transactions'
AND record_id = 'uuid-of-transaction';
Get full timeline for an approval request:
SELECT * FROM v_approval_request_timeline
WHERE approval_request_id = 'uuid-of-request'
ORDER BY event_at ASC;
Category Seed Data¶
The approval system uses the category system for type/subtype definitions:
Category Group: approval_types
| Code | Name | Parent | Metadata |
|---|---|---|---|
| transactions | Transactions | NULL | {"table_name": "transactions"} |
| Expense | Expense | transactions | is_media=T, is_accounting=T, is_personal_accounting=T |
| Invoice | Invoice | transactions | is_media=T, is_accounting=T, is_personal_accounting=T |
| Payroll Invoice | Payroll Invoice | transactions | is_media=T, is_accounting=F, is_personal_accounting=F |
| Reimbursement | Reimbursement | transactions | is_media=T, is_accounting=F, is_personal_accounting=F |
| Unknown | Unknown | transactions | is_media=T, is_accounting=T, is_personal_accounting=T |
User Interface¶
The approval system provides three main pages for managing approvals, located under Management > Approvals.
Menu Structure¶
| Page | Icon | Purpose |
|---|---|---|
| Pending | CircleDashed | Items awaiting approval |
| Queries | CircleHelp | Discussion threads |
| History | History | Completed approval actions |
Pending Page¶
Displays approval items assigned to the user.
Features:
- Tabs: Grouped by approval type (uses
tab_labelfrom view, pluralized viapluralize_type()) - Badge counts: Number of pending items per tab
- Assignment column: "Mine" or "Lower Tier" badges for filtering
- Default filter: Shows only user's directly assigned items ("Mine")
- Actions: ApprovalButtons component (Approve/Reject/Query)
Data source: v_user_pending_approvals
Queries Page¶
Displays approval queries for discussion.
Features:
- Tabs: Grouped by approval type
- Can Reply column: Indicates if user can respond to the query
- Thread view: Expandable discussion threads
Data source: v_user_approval_queries
History Page¶
Displays user's completed approval decisions.
Features:
- Tabs: Grouped by approval type
- Action column: Badge showing Approved/Rejected/Skipped status
- Decision date and notes: Full audit trail of user's decisions
Data source: v_user_approval_history
ApprovalButtons Component¶
Reusable action buttons component located at src/components/approvals/ApprovalButtons/.
Features:
- Uses ButtonGroup with dropdown for multiple actions
- Priority order for main action: Approve > Query > Reject
- Icons: CircleCheck (Approve), CircleHelp (Query), CircleX (Reject)
- Supports loading states and size variants