core PK: id 11 required 2 unique

Description

Stores metadata and storage references for receipt images and documents attached to expense claims. Supports mandatory audit documentation requirements for Bufdir-funded organisations, enabling peer mentors to photograph receipts immediately after incurring costs and attach them to travel expense registrations.

20
Attributes
5
Indexes
6
Validation Rules
13
CRUD Operations

Data Structure

Name Type Description Constraints
id uuid Primary key — unique identifier for the receipt record
PKrequiredunique
expense_id uuid Foreign key to the expense this receipt is attached to
required
storage_key string Object storage path/key used to retrieve the file from S3-compatible storage (e.g. receipts/{org_id}/{expense_id}/{uuid}.jpg)
requiredunique
original_filename string Original filename as provided by the device image picker or camera, preserved for display purposes
-
mime_type string MIME type of the uploaded file (e.g. image/jpeg, image/png, application/pdf)
required
file_size_bytes integer Size of the stored (compressed) file in bytes, used for storage quota monitoring
required
original_size_bytes integer Size of the original uncompressed file before client-side compression, used for compression ratio reporting
-
upload_status enum Current upload lifecycle state of the receipt
required
virus_scan_status enum Result of the server-side virus scan performed after upload to object storage
-
virus_scan_completed_at datetime Timestamp when the virus scan completed, used for audit trail
-
upload_started_at datetime Timestamp when the chunked upload was initiated, used for resumable upload tracking
-
upload_completed_at datetime Timestamp when all chunks were received and the file was confirmed stored in object storage
-
upload_retry_count integer Number of times the upload was retried after a failure, used for diagnostics
required
chunk_upload_id string External identifier for a resumable multipart upload session (e.g. S3 multipart upload ID), null when upload is not in progress
-
is_required boolean Whether this receipt was required by the organisation's expense type rules at time of submission (denormalised for audit clarity)
required
sort_order integer Display order of the receipt within the expense claim, set by the peer mentor when multiple receipts are attached
required
deleted_at datetime Soft-delete timestamp — set when the peer mentor removes a receipt before expense submission. NULL means active.
-
deleted_by_user_id uuid User who soft-deleted this receipt, for audit traceability
-
created_at datetime Timestamp when the receipt record was created (upload initiated)
required
updated_at datetime Timestamp of the last status update to this record
required

Database Indexes

idx_expense_receipt_expense_id
btree

Columns: expense_id

idx_expense_receipt_storage_key
btree unique

Columns: storage_key

idx_expense_receipt_upload_status
btree

Columns: upload_status

idx_expense_receipt_expense_id_deleted_at
btree

Columns: expense_id, deleted_at

idx_expense_receipt_created_at
btree

Columns: created_at

Validation Rules

file_size_within_limit error

Validation failed

storage_key_format error

Validation failed

expense_id_exists error

Validation failed

upload_status_transition error

Validation failed

mime_type_consistency error

Validation failed

deleted_receipt_not_counted_as_required warning

Validation failed

Business Rules

receipt_required_by_expense_type
on_create

Certain expense types (e.g. toll, parking, public transport, expenses over 100 NOK for HLF) require at least one receipt. The expense cannot be submitted without a receipt in an 'available' upload status when the requirement applies.

virus_scan_required_before_availability
on_update

A receipt must pass a clean virus scan result before its upload_status transitions to 'available'. Infected files are quarantined and the expense submission is blocked.

Enforced by: Receipt Storage
max_receipts_per_expense
on_create

A single expense claim may have at most 10 active (non-soft-deleted) receipts attached to prevent storage abuse.

receipt_immutable_after_approval
on_update

Once the parent expense has been approved (expense_approvals.status = 'approved'), receipts cannot be added, updated, or soft-deleted. The audit trail must remain intact.

supported_mime_types_only
on_create

Only image/jpeg, image/png, and application/pdf are accepted. Other MIME types are rejected at the receipt-storage infrastructure layer before the record is persisted.

org_scoped_storage_path
on_create

The storage_key must be prefixed with the organisation ID of the submitting user to enforce tenant isolation in object storage. Cross-org path access must be rejected.

Storage Configuration

Storage Type
primary_table
Location
main_db
Partitioning
No Partitioning
Retention
Permanent Storage