core PK: id 10 required 2 unique

Description

Tracks the approval lifecycle for a travel expense report, including auto-approved and manually attested decisions with complete audit trails. Records coordinator decisions, rejection reasons, and timestamps required for Bufdir grant compliance auditing.

17
Attributes
7
Indexes
10
Validation Rules
19
CRUD Operations

Data Structure

Name Type Description Constraints
id uuid Immutable primary key for the approval record, generated server-side on creation.
PKrequiredunique
expense_report_id uuid Foreign key to travel_expense_reports. Each expense report has exactly one approval record. Enforced as unique to guarantee the one-to-one relationship.
requiredunique
organization_id uuid Denormalized organization reference for RLS-scoped queries, allowing coordinators to list all pending approvals within their org without joining through expense reports.
required
status enum Current state of the approval lifecycle. Transitions: pending → auto_approved | pending → approved | pending → rejected | pending → requires_correction. Terminal states (auto_approved, approved, rejected) cannot transition back to pending.
required
auto_approved boolean True when the record was approved automatically by the Auto-Approval Service because the expense report fell below configured mileage and amount thresholds. False for all manual coordinator decisions.
required
approved_by uuid User ID of the coordinator or administrator who manually attested or rejected the expense report. Null for auto-approved records and for records still in pending state.
-
decision_at datetime Timestamp (UTC) when the final approval decision was recorded — either the automated threshold evaluation timestamp or the coordinator's manual attestation time. Null until a terminal status is reached.
-
comment text Optional free-text comment from the coordinator accompanying an approval decision. Stored for audit purposes and displayed in the reimbursement detail screen.
-
rejection_reason text Mandatory rejection reason captured when status transitions to 'rejected'. Must be non-empty and meaningful — used to inform the peer mentor of the correction needed and retained in the Bufdir audit trail.
-
correction_requested_at datetime Timestamp when the coordinator set status to 'requires_correction', enabling calculation of how long a report has been awaiting peer mentor action.
-
threshold_config_snapshot json Immutable snapshot of the organization's approval threshold configuration (mileage limit in km, expense amount limit in NOK, per-km rate) captured at submission time. Ensures the audit trail reflects the rules that were in effect when the decision was made, even if the organization later changes its thresholds.
required
total_amount_nok decimal Denormalized total reimbursement amount (in NOK) calculated from the parent expense report's expense items at submission time. Used for threshold evaluation and stored for fast audit queries without joining expense_items.
required
total_distance_km decimal Denormalized total mileage distance (in km) from all mileage-type expense items in the parent expense report. Used for auto-approval threshold evaluation. Zero when no mileage items are present.
required
accounting_synced_at datetime Timestamp when this approved record was successfully synchronized to the organization's accounting system (Xledger or Dynamics). Null until sync completes. Used by the Accounting Sync Orchestrator to determine pending sync work.
-
accounting_sync_reference string External reference ID returned by the accounting system (Xledger ledger entry ID or Dynamics record GUID) after successful sync. Used for cross-system reconciliation and displayed in audit logs.
-
created_at datetime Server-side timestamp when the approval record was first created, typically at the moment the parent expense report was submitted. Used for aging calculations and Bufdir reporting period filters.
required
updated_at datetime Server-side timestamp updated on every write to this record. Used by Riverpod real-time subscriptions and optimistic concurrency checks.
required

Database Indexes

idx_reimbursement_approvals_expense_report_id
btree unique

Columns: expense_report_id

idx_reimbursement_approvals_org_status
btree

Columns: organization_id, status

idx_reimbursement_approvals_organization_id
btree

Columns: organization_id

idx_reimbursement_approvals_status
btree

Columns: status

idx_reimbursement_approvals_approved_by
btree

Columns: approved_by

idx_reimbursement_approvals_created_at
btree

Columns: created_at

idx_reimbursement_approvals_accounting_synced
btree

Columns: organization_id, accounting_synced_at

Validation Rules

expense_report_id_exists error

Validation failed

status_transition_validity error

Validation failed

approved_by_is_valid_coordinator error

Validation failed

rejection_reason_non_empty error

Validation failed

total_amount_matches_expense_items error

Validation failed

total_distance_non_negative error

Validation failed

threshold_snapshot_schema_valid error

Validation failed

comment_and_rejection_reason_length error

Validation failed

organization_id_matches_expense_report error

Validation failed

auto_approved_flag_consistency error

Validation failed

Business Rules

one_approval_per_expense_report
on_create

Each travel_expense_reports record must have exactly one reimbursement_approvals record. The approval record is created atomically when the expense report is submitted, preventing any report from existing without an associated approval lifecycle entry. Enforced by the unique constraint on expense_report_id.

auto_approval_threshold_evaluation
on_create

When an expense report is submitted, the Auto-Approval Service evaluates the total mileage (km) and total expense amount (NOK) against the organization's configured thresholds. If both values are within the configured limits, the approval record is created with status 'auto_approved' and auto_approved=true. The threshold_config_snapshot is always written to capture the rules in effect at decision time.

terminal_status_immutability
on_update

Once an approval record reaches a terminal status ('auto_approved', 'approved', 'rejected'), its status field cannot be changed. This guarantees the integrity of the Bufdir audit trail. Any attempt to update status from a terminal state must be rejected with an error. The 'requires_correction' state is non-terminal: a coordinator may subsequently approve or reject after the peer mentor resubmits.

rejection_requires_reason
on_update

A status transition to 'rejected' is only permitted when a non-empty rejection_reason is provided. The rejection reason is surfaced to the peer mentor in the Approval Status Widget and stored permanently in the audit trail for Bufdir compliance.

manual_attestation_requires_coordinator_role
on_update

The approved_by field may only be set to the ID of a user who holds the coordinator or admin role within the same organization as the expense report. The Attestation Service validates the acting user's role via the Permission Guard before executing the approval RPC.

decision_timestamp_required_on_terminal_transition
on_update

When status transitions to any terminal state ('auto_approved', 'approved', 'rejected'), decision_at must be set to the current UTC timestamp. The server enforces this via the Supabase Edge Function runtime — the client cannot supply its own timestamp to prevent clock manipulation.

accounting_sync_on_approval
on_update

When status transitions to 'approved' or 'auto_approved', the Accounting Sync Orchestrator is triggered to route the record to the correct accounting connector (Xledger or Dynamics) based on the organization's integration_configs feature flags. The accounting_synced_at field is updated only after a confirmed successful sync.

org_scoped_rls_enforcement
always

Row-Level Security policies on this table restrict read access to users who belong to the same organization as the approval record (via user_organization_memberships). Coordinators may only view and act on approvals within their organizational scope. Peer mentors may only read their own approval records.

threshold_snapshot_immutability
on_update

The threshold_config_snapshot field is set once at record creation and must never be modified. It captures the exact threshold rules that governed the auto-approval decision, enabling accurate retrospective auditing even if the organization later adjusts its thresholds.

bufdir_compliance_retention
on_delete

Approval records must be retained permanently (never deleted by automated processes) because they form part of the Bufdir grant compliance audit trail. Admin-initiated deletion is restricted to global admin role and must create an audit log entry before execution.

Storage Configuration

Storage Type
primary_table
Location
main_db
Partitioning
No Partitioning
Retention
Permanent Storage

Entity Relationships

travel_expense_reports
incoming one_to_one

Each expense report has exactly one approval record tracking its auto-approval or manual attestation lifecycle

optional cascade delete