core PK: id 8 required 2 unique

Description

Records the approval or rejection decision for a submitted expense claim. Each expense has exactly one approval record that tracks the decision lifecycle from pending review through auto-approval or manual coordinator/admin action, capturing the reviewer identity, timestamp, decision comment, and rejection reason. Triggers reimbursement creation on approval and feeds into KPI dashboards and Bufdir financial reporting.

13
Attributes
5
Indexes
7
Validation Rules
17
CRUD Operations

Data Structure

Name Type Description Constraints
id uuid Primary key. Unique identifier for the approval record.
PKrequiredunique
expense_id uuid Foreign key to expenses. One-to-one relationship — each expense has at most one approval record. Used to look up the full expense context during review.
requiredunique
organization_id uuid Foreign key to organizations. Denormalized for tenant-scoped queries without requiring a join through expenses. All queries are scoped to this field.
required
status enum Current decision state of the approval. 'pending' is the initial state on expense submission. 'auto_approved' is set by the auto-approval rule engine without human review. 'approved' and 'rejected' are set by a coordinator or org admin.
required
reviewed_by uuid Foreign key to users. The coordinator or org admin who made the decision. Null when status is 'pending' or 'auto_approved'.
-
reviewed_at datetime Timestamp when the approval decision was made. Null when status is 'pending'. Set to the system clock at the moment of the approve/reject action.
-
decision_comment text Optional free-text comment from the reviewer. Can be used for both approvals (e.g., 'Approved with minor note') and rejections (supplementary to rejection_reason).
-
rejection_reason text Required when status is 'rejected'. Structured explanation for why the expense was rejected, displayed to the peer mentor. Not applicable for approvals or auto-approvals.
-
auto_approved boolean True when the expense was approved automatically by the auto-approval rule engine without human review. False for all manually reviewed decisions.
required
auto_approval_rule_id uuid Foreign key to auto_approval_rules (if that table exists) or stored as a reference string. Records which auto-approval rule triggered the automatic decision. Null for manually reviewed approvals.
-
version integer Optimistic lock version counter. Incremented on every status update. Concurrent approval attempts compare client-held version against this field and fail if they do not match, preventing double-approval race conditions.
required
created_at datetime Timestamp when the approval record was created. Set automatically on expense submission. Serves as the start of the approval lifecycle for reporting.
required
updated_at datetime Timestamp of the last status change. Updated on every approval or rejection action. Used to compute how long expenses have been in the pending queue.
required

Database Indexes

idx_expense_approval_expense_id
btree unique

Columns: expense_id

idx_expense_approval_organization_status
btree

Columns: organization_id, status

idx_expense_approval_organization_created_at
btree

Columns: organization_id, created_at

idx_expense_approval_reviewed_by
btree

Columns: reviewed_by

idx_expense_approval_status
btree

Columns: status

Validation Rules

expense_id_must_exist error

Validation failed

rejection_reason_required_on_reject error

Validation failed

reviewed_by_must_be_valid_user error

Validation failed

reviewed_at_required_with_reviewer error

Validation failed

status_transition_validity error

Validation failed

version_must_match_on_update error

Validation failed

organization_id_matches_expense error

Validation failed

Business Rules

one_approval_per_expense
on_create

Each expense may have exactly one approval record. A second INSERT for the same expense_id must be rejected at the database level via a unique constraint. The expense-approval-service must check for an existing record before creating a new one.

only_authorized_roles_can_review
on_update

Only users with the Coordinator or Organization Admin role may manually approve or reject an expense. Peer Mentors cannot act on expense_approvals. Global Admins may view but not approve org-level expenses by default.

tenant_isolation
always

All reads and writes must be scoped to the organization_id of the authenticated user. A coordinator from org A must never be able to read or modify approvals belonging to org B.

approval_triggers_reimbursement
on_update

When status transitions to 'approved' or 'auto_approved', the expense-approval-service must create a linked reimbursement record in the reimbursements table within the same database transaction.

finalized_decision_is_immutable
on_update

Once status is 'approved', 'auto_approved', or 'rejected', no further status transitions are permitted. Only system administrators performing data corrections may override this rule, and all such overrides must be audit-logged.

audit_all_decisions
on_update

Every status transition (pending→approved, pending→rejected, pending→auto_approved) must produce an immutable audit log entry via the audit-log-service within the same transaction, capturing the actor, action, expense_id, and timestamp.

auto_approval_sets_no_reviewer
on_create

When the auto-approval rule engine approves an expense, reviewed_by must remain null and auto_approved must be true. The auto_approval_rule_id must be populated to record which rule triggered the decision.

optimistic_locking_on_concurrent_review
on_update

If two reviewers attempt to approve or reject the same expense simultaneously, the second write must fail with a conflict error. The client must re-fetch the current state and present the already-resolved decision to the user.

Storage Configuration

Storage Type
primary_table
Location
main_db
Partitioning
No Partitioning
Retention
Permanent Storage