Expense Receipt
Data Entity
Description
Photo evidence attached to an expense entry, capturing proof of purchase for reimbursable costs. Required for expenses exceeding the organization-configured threshold (e.g. 100 NOK for HLF). Stored as a reference to a file in cloud storage.
Data Structure
| Name | Type | Description | Constraints |
|---|---|---|---|
id |
uuid |
Primary key | PKrequiredunique |
expense_id |
uuid |
Foreign key to the parent expense entry | required |
file_key |
string |
Storage object key / path in cloud storage (e.g. S3/GCS key) | requiredunique |
file_name |
string |
Original filename as captured by the device camera or file picker | required |
mime_type |
string |
MIME type of the uploaded file (image/jpeg, image/png, application/pdf) | required |
file_size_bytes |
integer |
File size in bytes, used for storage quota tracking | required |
upload_status |
enum |
Current upload state; pending while in outbox, uploaded once confirmed in cloud storage | required |
uploaded_by_user_id |
uuid |
Foreign key to the user who captured and uploaded the receipt | required |
organization_id |
uuid |
Foreign key to the owning organization, for tenant isolation and storage quota enforcement | required |
checksum_sha256 |
string |
SHA-256 hash of the file content for integrity verification and deduplication | - |
local_file_path |
string |
Temporary local path on device while upload is pending (offline scenario); cleared after successful upload | - |
created_at |
datetime |
Timestamp when the receipt record was created | required |
updated_at |
datetime |
Timestamp of last update (e.g. upload status change) | required |
deleted_at |
datetime |
Soft-delete timestamp; null means active | - |
Database Indexes
idx_expense_receipts_expense_id
Columns: expense_id
idx_expense_receipts_organization_id
Columns: organization_id
idx_expense_receipts_uploaded_by_user_id
Columns: uploaded_by_user_id
idx_expense_receipts_upload_status
Columns: upload_status
idx_expense_receipts_file_key
Columns: file_key
idx_expense_receipts_deleted_at
Columns: deleted_at
Validation Rules
file_size_limit
error
Validation failed
mime_type_valid
error
Validation failed
expense_id_exists
error
Validation failed
file_key_unique
error
Validation failed
checksum_integrity
error
Validation failed
upload_status_transition
error
Validation failed
Business Rules
receipt_required_above_threshold
If the parent expense amount exceeds the organization's configured receipt threshold (e.g. 100 NOK), at least one receipt must be attached before the expense can be submitted. The threshold value is stored in organization settings.
receipt_belongs_to_expense_owner
The uploaded_by_user_id must match the user_id on the parent expense, or the uploader must be a Coordinator acting as proxy for that user.
receipt_organization_matches_expense
The organization_id on the receipt must match the organization_id on the parent expense to enforce tenant isolation.
receipt_immutable_after_approval
Once the parent expense is approved or rejected, receipt records may not be deleted or replaced. This preserves the audit trail for reimbursement decisions.
file_type_whitelist
Only image/jpeg, image/png, and application/pdf MIME types are accepted to prevent executable upload and ensure readability in the approval UI.
offline_receipt_queued
When the device is offline (Drift + mutation outbox pattern), receipt upload is queued with status=pending and local_file_path set. BackgroundSyncWorker promotes status to uploaded once connectivity is restored and the file reaches cloud storage.