Skip to content

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

  1. Overview
  2. Core Concepts
  3. Architecture
  4. Configuration Guide
  5. Approval Flows
  6. Business Rules
  7. Permissions
  8. Database Schema
  9. Views Reference
  10. Category Seed Data
  11. 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 > 0 but no matching approval configuration
  • Approvers: Users with the approval:soft:view permission (project owners automatically have this)
  • Single Tier: Soft approvals have only one tier (tier_number = 1)
  • No Configuration: approval_configuration_id is NULL, is_soft_approval is 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:

  1. Invoice created ($3000) -> Transaction status: "Processed"
  2. System finds approval configuration -> Creates approval_request
  3. Tier 1 Evaluation: $3000 > $100? YES
  4. Creates instances for John and Jane (status: Pending)
  5. Transaction status -> "Submitted for Approval"
  6. John Approves
  7. John's instance -> Approved
  8. Jane's instance -> Skipped (reason: "Approved by another approver")
  9. Move to Tier 2
  10. Tier 2 Evaluation: $3000 > $1000? YES
  11. Creates instance for Finance Director (status: Pending)
  12. Finance Director Approves
  13. Instance -> Approved
  14. Move to Tier 3
  15. Tier 3 Evaluation: $3000 > $5000? NO
  16. Creates instance for CFO (status: Skipped, condition_met: false)
  17. No more tiers -> Request status: Approved
  18. Transaction status -> "Approved"

Example 2: $50 Invoice - Auto-Approved

Flow:

  1. Invoice created ($50)
  2. System finds approval configuration
  3. All Tiers Evaluated:
  4. Tier 1: $50 > $100? NO -> Auto-skip
  5. Tier 2: $50 > $1000? NO -> Auto-skip
  6. Tier 3: $50 > $5000? NO -> Auto-skip
  7. All tiers skipped -> Request status: Approved (no human intervention)

Example 3: Query Flow

  1. John queries the transaction (creates message with approval_request_id)
  2. John's instance status -> "Queried"
  3. Request status -> "Queried"
  4. Message thread continues until clarified
  5. John approves after clarification -> Flow continues normally

Example 4: Rejection Flow

  1. Finance Director rejects at Tier 2
  2. Instance status -> "Rejected" with decision_note
  3. Request status -> "Rejected" with reject_reason
  4. All remaining instances -> "Skipped" (reason: "Request rejected")
  5. 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:

  1. Transaction created -> AI processing completes with 1 pending issue
  2. System checks for approval configuration -> None found
  3. System checks pendingIssuesCount -> 1 issue found
  4. System creates soft approval:
  5. Creates approval_request with is_soft_approval = true, approval_configuration_id = NULL
  6. Finds users with approval:soft:view permission
  7. Creates approval_instance for each (tier_number = 1, approval_tier_id = NULL)
  8. Transaction status -> "In Approval"
  9. Users see item in "Issues Review" tab
  10. User reviews the processing issue and approves/rejects/queries
  11. On approval -> Transaction status: "Approved"

Key Differences from Regular Approval:

  • No approval_configuration record 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_relationship on 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_id if not internal user
  • If internal user: use submission_address field
  • Submitter CAN be an approver (no self-approval restriction)

Query/Discussion

  • Uses messages table with approval_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_reason stored on both approval_request and 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_enabled column
  • transactions: Added reject_reason column
  • messages: Added approval_request_id column

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.

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_label from view, pluralized via pluralize_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