Claim Event
Data Entity
Description
An immutable audit trail entry recording a state transition in an expense claim's lifecycle (submitted, auto_approved, coordinator_approved, rejected, exported). Each event captures the actor's identity, timestamp, from/to status, and an optional coordinator comment. Together the events form the complete approval history shown in the claim status audit timeline.
Data Structure
| Name | Type | Description | Constraints |
|---|---|---|---|
id |
uuid |
Surrogate primary key generated by the database on insert. Never reused or reassigned. | PKrequiredunique |
expense_claim_id |
uuid |
Foreign key referencing the expense_claim this event belongs to. All events for a claim share this value and are fetched together to reconstruct the full approval timeline. | required |
actor_id |
uuid |
UUID of the user who triggered the state transition. References the Supabase auth.users table. For system-initiated transitions (e.g., auto-approval), this is the service account UUID defined in environment configuration. | required |
actor_role |
enum |
Role of the actor at the time of the transition. Stored redundantly so the audit log remains self-contained even if role assignments change later. | required |
from_status |
enum |
Claim status immediately before this transition. NULL for the very first event (initial submission) where no prior status existed. | - |
to_status |
enum |
Claim status after this transition. Must differ from from_status (except for the initial submission event where from_status is NULL). | required |
comment |
text |
Optional free-text comment provided by the actor, typically a coordinator explaining a rejection or approval condition. Maximum 500 characters. Required when to_status is 'rejected' by business rule (enforced at application layer). | - |
created_at |
datetime |
UTC timestamp of the moment the event was persisted. Set by the database using NOW() and never overridable by application code. Used to order events chronologically in the audit timeline. | required |
Database Indexes
idx_claim_event_expense_claim_id_created_at
Columns: expense_claim_id, created_at
idx_claim_event_expense_claim_id
Columns: expense_claim_id
idx_claim_event_actor_id
Columns: actor_id
idx_claim_event_to_status
Columns: to_status
idx_claim_event_created_at
Columns: created_at
Validation Rules
expense_claim_id_exists
error
Validation failed
actor_id_exists
error
Validation failed
to_status_not_equal_from_status
error
Validation failed
comment_max_length
error
Validation failed
actor_role_enum_value
error
Validation failed
to_status_enum_value
error
Validation failed
from_status_enum_value_or_null
error
Validation failed
Business Rules
immutable_audit_record
Once a claim_event row is inserted it must never be updated or deleted. The table must have no UPDATE or DELETE RLS policies. The immutability preserves the integrity of the approval audit trail for regulatory and accounting compliance.
server_side_timestamp
created_at must always be supplied by the database server via DEFAULT NOW(). Application code must never pass a created_at value in the INSERT payload. This prevents timestamp forgery in the audit trail.
rejection_requires_comment
When to_status is 'rejected', comment must be a non-empty string of at least 5 characters. This ensures coordinators provide a meaningful reason visible to peer mentors and auditors. Enforced at the service layer before the repository INSERT.
valid_status_transition
The from_status → to_status pair must follow the allowed state machine: NULL → submitted, submitted → auto_approved, submitted → coordinator_approved, submitted → rejected, auto_approved → exported, coordinator_approved → exported, rejected → submitted (resubmission). All other combinations are illegal.
actor_role_matches_transition
Only a 'system' actor may record auto_approved transitions. Only 'coordinator' or 'org_admin' actors may record coordinator_approved, rejected, or exported transitions. Only a 'peer_mentor' or 'coordinator' (proxy) actor may record the initial submitted transition.
single_open_transition_per_claim
A new event may only be inserted for a claim if the claim's current status (latest to_status) logically permits the requested to_status. The approval-workflow-service reads the latest event before inserting to enforce this sequencing and prevent race conditions.
first_event_null_from_status
The very first event for a claim (submission) must have from_status = NULL. Any subsequent event must have a non-null from_status matching the to_status of the immediately preceding event for that claim.
CRUD Operations
Storage Configuration
Entity Relationships
Every state transition of an expense claim generates an immutable audit event forming a complete approval history timeline