Expense Receipts
Data Entity
Description
Stores metadata and storage references for receipt images attached to expense claims. Each receipt links to a parent expense and tracks upload lifecycle, file properties, and storage location. Receipts serve as mandatory audit evidence for reimbursement claims above organization-defined thresholds and are required for Bufdir compliance reporting.
Data Structure
| Name | Type | Description | Constraints |
|---|---|---|---|
id |
uuid |
Unique identifier for the receipt record | PKrequiredunique |
expense_id |
uuid |
Foreign key to the parent expense claim this receipt documents | required |
file_name |
string |
Original file name as captured from camera or gallery picker | required |
file_size_bytes |
integer |
File size in bytes after client-side compression | required |
mime_type |
string |
MIME type of the uploaded file | required |
storage_key |
string |
Object storage key (S3 path) for the receipt file in cloud storage | requiredunique |
storage_bucket |
string |
Name of the S3-compatible bucket where the receipt is stored | required |
upload_status |
enum |
Current upload lifecycle state of the receipt file | required |
upload_progress |
decimal |
Upload progress percentage (0.00 to 100.00) for resumable uploads | - |
upload_retry_count |
integer |
Number of upload retry attempts for failed or interrupted transfers | required |
checksum_sha256 |
string |
SHA-256 hash of the uploaded file for integrity verification and deduplication | - |
virus_scan_status |
enum |
Result of server-side virus scan on the uploaded file | required |
virus_scan_at |
datetime |
Timestamp when virus scan completed | - |
image_width |
integer |
Width of the receipt image in pixels after compression | - |
image_height |
integer |
Height of the receipt image in pixels after compression | - |
thumbnail_storage_key |
string |
Object storage key for the generated thumbnail used in approval queue previews | - |
uploaded_by |
uuid |
User ID of the person who uploaded the receipt (may differ from expense owner in proxy scenarios) | required |
description |
text |
Optional description or note about the receipt content | - |
receipt_date |
datetime |
Date shown on the receipt, if different from upload date | - |
organization_id |
uuid |
Organization scope for multi-tenant isolation, denormalized from parent expense | required |
created_at |
datetime |
Timestamp when the receipt record was created | required |
updated_at |
datetime |
Timestamp of the last update to the receipt record | required |
Database Indexes
idx_expense_receipts_expense_id
Columns: expense_id
idx_expense_receipts_organization_id
Columns: organization_id
idx_expense_receipts_upload_status
Columns: upload_status
idx_expense_receipts_uploaded_by
Columns: uploaded_by
idx_expense_receipts_storage_key
Columns: storage_key
idx_expense_receipts_virus_scan
Columns: virus_scan_status, upload_status
idx_expense_receipts_created_at
Columns: organization_id, created_at
Validation Rules
valid_mime_type
error
Validation failed
max_file_size
error
Validation failed
valid_expense_reference
error
Validation failed
valid_uploader_reference
error
Validation failed
checksum_format
error
Validation failed
storage_key_uniqueness
error
Validation failed
receipt_date_not_future
warning
Validation failed
max_receipts_per_expense
error
Validation failed
file_name_sanitization
error
Validation failed
Business Rules
receipt_required_above_threshold
Expense claims above the organization-defined amount threshold (e.g., 100 NOK for HLF) must have at least one receipt attached before the expense can be submitted for approval
receipt_linked_to_single_expense
Each receipt must be associated with exactly one expense claim; a receipt cannot be shared across multiple expenses
virus_scan_before_availability
Receipt files must pass virus scanning before they are made available for viewing or download via pre-signed URLs; infected files are quarantined and the upload_status is set to virus_detected
immutable_after_expense_approval
Once the parent expense has been approved, the receipt record and its associated file become immutable and cannot be modified or deleted to preserve audit integrity
organization_tenant_isolation
Receipt records are scoped to the owning organization; queries must always filter by organization_id to enforce multi-tenant data isolation
client_side_compression_required
Receipt images must be compressed client-side to a target size under 500 KB before upload to reduce storage costs and transfer times
receipt_type_specific_requirements
Different expense types have different receipt requirements; some expense types mandate receipts regardless of amount while others only require them above thresholds, as configured per organization
cascade_delete_with_expense
When an expense record is deleted, all associated receipt records and their cloud storage files must be deleted to prevent orphaned files
offline_queue_for_upload
When the device is offline, receipt metadata is saved locally and the file is queued for upload when connectivity is restored via the background sync service