Expense Receipt
Data Entity
Description
Photographic or digital proof of payment attached to an expense claim. Stores metadata about the uploaded receipt file, including its storage location, upload status, and association to a specific expense record.
Data Structure
| Name | Type | Description | Constraints |
|---|---|---|---|
id |
uuid |
Primary key, uniquely identifies the receipt record | PKrequiredunique |
expense_id |
uuid |
Foreign key referencing the parent expense record this receipt belongs to | required |
file_name |
string |
Original filename of the uploaded receipt image as provided by the device camera or file picker | required |
storage_key |
string |
Object storage key (path) where the file is stored in cloud storage (e.g. S3-compatible bucket path) | requiredunique |
storage_bucket |
string |
Name of the cloud storage bucket containing this receipt file | required |
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 uploaded file in bytes, used for storage quota enforcement and display | required |
upload_status |
enum |
Current upload state of the receipt file, supporting offline-first flow | required |
presigned_url |
text |
Temporary pre-signed URL for secure client-side access to the file. Regenerated on demand; not persisted long-term. | - |
presigned_url_expires_at |
datetime |
Expiry timestamp for the presigned URL. After expiry, a new URL must be requested. | - |
thumbnail_storage_key |
string |
Object storage key for the compressed thumbnail version of the receipt image, used in list views | - |
uploaded_by_user_id |
uuid |
Foreign key referencing the user who uploaded this receipt | required |
uploaded_at |
datetime |
Timestamp when the upload was successfully completed in cloud storage | - |
created_at |
datetime |
Timestamp when the receipt record was first created (may precede upload completion in offline scenarios) | required |
updated_at |
datetime |
Timestamp of the last modification to this record | required |
deleted_at |
datetime |
Soft-delete timestamp. Non-null means the receipt has been removed by the user or admin. | - |
organization_id |
uuid |
Organization context for multi-tenancy scoping. Denormalized from the parent expense for efficient tenant-level queries and access control. | required |
Database Indexes
idx_expense_receipts_expense_id
Columns: expense_id
idx_expense_receipts_storage_key
Columns: storage_key
idx_expense_receipts_uploaded_by
Columns: uploaded_by_user_id
idx_expense_receipts_organization_id
Columns: organization_id
idx_expense_receipts_upload_status
Columns: upload_status
idx_expense_receipts_deleted_at
Columns: deleted_at
Validation Rules
allowed_mime_types
error
Validation failed
max_file_size_10mb
error
Validation failed
storage_key_uniqueness
error
Validation failed
expense_id_must_exist
error
Validation failed
uploaded_by_must_match_expense_owner
error
Validation failed
presigned_url_expiry_check
warning
Validation failed
Business Rules
receipt_required_above_threshold
A receipt upload is mandatory for any expense where the amount exceeds 100 NOK. The expense cannot be submitted for approval without at least one successfully uploaded receipt.
receipt_belongs_to_submitters_organization
The organization_id on the receipt must match the organization context of the user who submitted the expense, enforcing multi-tenant data isolation.
immutable_after_approval
Once the parent expense has been approved via reimbursement_approvals, receipt records become read-only and cannot be deleted or replaced.
soft_delete_only
Receipts are never hard-deleted from the database. Deletion sets deleted_at to preserve audit trails for financial records.
max_receipts_per_expense
A single expense record may have at most 5 receipt attachments to prevent storage abuse.
offline_pending_upload_sync
Receipts created offline (upload_status=pending) must be synced to cloud storage when connectivity is restored. Background sync service is responsible for transitioning status from pending → uploading → uploaded.