core PK: id 11 required 2 unique

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.

14
Attributes
6
Indexes
6
Validation Rules
13
CRUD Operations

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
btree

Columns: expense_id

idx_expense_receipts_organization_id
btree

Columns: organization_id

idx_expense_receipts_uploaded_by_user_id
btree

Columns: uploaded_by_user_id

idx_expense_receipts_upload_status
btree

Columns: upload_status

idx_expense_receipts_file_key
btree unique

Columns: file_key

idx_expense_receipts_deleted_at
btree

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
on_create

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
on_create

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
on_create

The organization_id on the receipt must match the organization_id on the parent expense to enforce tenant isolation.

receipt_immutable_after_approval
on_delete

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
on_create

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
on_create

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.

Storage Configuration

Storage Type
primary_table
Location
main_db
Partitioning
No Partitioning
Retention
archive_after_1year