Expense Approval
Data Entity
Description
Records the approval lifecycle for peer mentor expense claims, tracking reviewer decisions, timestamps, rejection reasons, and audit trail for reimbursement workflows in the Admin Web Portal.
Data Structure
| Name | Type | Description | Constraints |
|---|---|---|---|
id |
uuid |
Primary key | PKrequiredunique |
expense_id |
uuid |
Foreign key to the expense being reviewed | requiredunique |
status |
enum |
Current approval status of the expense | required |
reviewed_by_user_id |
uuid |
ID of the admin or coordinator who reviewed the expense; null if auto-approved | - |
reviewed_at |
datetime |
Timestamp when the review decision was made | - |
auto_approved |
boolean |
True when approval was triggered by an auto-approval rule (under threshold, no receipts required) | required |
auto_approval_rule_id |
uuid |
Reference to the approval rule that triggered auto-approval; null for manual reviews | - |
rejection_reason |
text |
Free-text reason provided by reviewer when rejecting or requesting more information | - |
rejection_category |
enum |
Structured rejection category for reporting and analytics | - |
organization_id |
uuid |
Organization the expense belongs to, denormalized for tenant isolation and query performance | required |
reimbursement_amount |
decimal |
Approved reimbursement amount in NOK; may differ from claimed amount if partially approved | - |
currency |
string |
ISO 4217 currency code for the reimbursement amount | - |
accounting_export_id |
uuid |
Reference to accounting_sync_logs record when this approval has been exported to accounting system | - |
exported_at |
datetime |
Timestamp when approval record was exported to accounting system | - |
reviewer_notes |
text |
Internal notes from the reviewer visible only to admins, not exposed to the peer mentor | - |
created_at |
datetime |
Timestamp when the approval record was created (usually on expense submission) | required |
updated_at |
datetime |
Timestamp of last status change or update | required |
Database Indexes
idx_expense_approvals_expense_id
Columns: expense_id
idx_expense_approvals_organization_status
Columns: organization_id, status
idx_expense_approvals_reviewed_by
Columns: reviewed_by_user_id
idx_expense_approvals_reviewed_at
Columns: reviewed_at
idx_expense_approvals_accounting_export
Columns: accounting_export_id
idx_expense_approvals_auto_approved
Columns: organization_id, auto_approved, status
Validation Rules
valid_status_transition
error
Validation failed
reviewed_at_not_future
error
Validation failed
reimbursement_amount_positive
error
Validation failed
currency_iso_format
error
Validation failed
reviewer_must_be_org_admin_or_coordinator
error
Validation failed
rejection_category_with_rejection_reason
warning
Validation failed
Business Rules
one_approval_per_expense
Each expense may have exactly one approval record. The expense_id column is unique — a second approval record for the same expense is rejected at the database level.
auto_approval_threshold
Expenses meeting all criteria defined in an active approval rule (e.g. distance under 50 km, no receipts, known expense type) are automatically approved without reviewer action. auto_approved is set true and auto_approval_rule_id is populated.
reviewer_required_for_manual_approval
When status transitions to approved or rejected manually, reviewed_by_user_id and reviewed_at must be set. Auto-approved records are exempt.
rejection_reason_required_on_reject
When status is set to rejected or requires_info, rejection_reason must be provided so the peer mentor understands the outcome.
no_deletion_of_approvals
Approval records are never deleted — they form part of the financial audit trail. Status changes replace deletion.
tenant_isolation
All reads and writes must be scoped by organization_id matching the authenticated user's organization. Cross-tenant access is rejected.
approved_amount_not_exceed_claimed
reimbursement_amount on approval may not exceed the amount on the linked expense record.
audit_log_on_status_change
Every status transition writes an entry to audit_logs including previous status, new status, reviewer identity, and timestamp.
exported_approval_immutable
Once an approval record has been exported to the accounting system (exported_at is set), its status and reimbursement_amount may not be changed without a corrective workflow.