Receipt
Data Entity
Description
A photographic record of a physical receipt attached to an expense claim. Stored as a compressed image in object storage with a metadata record in the database. Required for expense claims above the organization-configured threshold (e.g., over 100 NOK for HLF).
Data Structure
| Name | Type | Description | Constraints |
|---|---|---|---|
id |
uuid |
Immutable primary key for the receipt record, generated server-side on upload. | PKrequiredunique |
expense_id |
uuid |
Foreign key referencing the parent expense claim this receipt documents. A single expense may have multiple receipt photos. | required |
uploader_id |
uuid |
Foreign key referencing the user (peer mentor or coordinator proxy) who uploaded this receipt image. | required |
organization_id |
uuid |
Foreign key referencing the organization under which this receipt was submitted. Used to enforce organization-scoped storage namespacing and prevent cross-tenant access. | required |
storage_key |
string |
The namespaced object storage key for the compressed image file, structured as '{organization_id}/receipts/{uuid}.jpg'. Globally unique across all tenants. | requiredunique |
filename |
string |
The original client-side filename of the uploaded image (e.g., 'IMG_2041.jpeg'). Stored for display and download labelling only — not used for storage addressing. | required |
mime_type |
enum |
MIME type of the uploaded image after compression. Constrained to the supported image formats. | required |
file_size_bytes |
integer |
Size of the compressed image file in bytes. Recorded post-compression for storage tracking and audit purposes. Must be positive. | required |
original_file_size_bytes |
integer |
Size of the original image before compression, in bytes. Stored for debugging and compression ratio analysis. | - |
checksum |
string |
SHA-256 hash of the compressed image file contents. Used to detect file corruption and prevent duplicate uploads of identical images within the same expense. | required |
uploaded_at |
datetime |
UTC timestamp of when the receipt image was successfully stored and the metadata record was committed. Set server-side on creation. | required |
deleted_at |
datetime |
UTC timestamp for soft deletion. When set, the receipt is logically deleted and the corresponding storage object is scheduled for cleanup. Null means the receipt is active. | - |
storage_object_deleted_at |
datetime |
UTC timestamp of when the corresponding object storage file was physically deleted. May lag behind deleted_at due to async cleanup jobs. | - |
Database Indexes
idx_receipt_expense_id
Columns: expense_id
idx_receipt_uploader_id
Columns: uploader_id
idx_receipt_organization_id
Columns: organization_id
idx_receipt_storage_key
Columns: storage_key
idx_receipt_checksum_expense
Columns: expense_id, checksum
idx_receipt_uploaded_at
Columns: uploaded_at
idx_receipt_active
Columns: expense_id, deleted_at
Validation Rules
allowed_mime_type
error
Validation failed
file_size_within_limit
error
Validation failed
expense_id_must_exist
error
Validation failed
storage_key_format
error
Validation failed
file_size_bytes_positive
error
Validation failed
checksum_format
error
Validation failed
Business Rules
receipt_required_above_threshold
An expense claim at or above the organization-configured monetary threshold (e.g., 100 NOK for HLF) must have at least one receipt attached before the reimbursement claim can be submitted for approval. The threshold is read from the organization settings at submission time.
organization_scoped_storage_isolation
Every receipt's storage_key must be prefixed with the uploader's organization_id to enforce strict cross-tenant storage isolation. No receipt object may be accessible from a storage key belonging to a different organization.
uploader_must_own_expense
The uploader_id on the receipt must either own the parent expense or be a coordinator performing a proxy registration on behalf of the expense owner. Peer mentors may only upload receipts to their own expense records.
soft_delete_triggers_storage_cleanup
Setting deleted_at on a receipt record must schedule asynchronous deletion of the corresponding object in object storage. The storage_object_deleted_at field is updated once the async job confirms the object has been removed.
duplicate_image_prevention
If a receipt with the same checksum is already attached to the same expense (and not soft-deleted), the upload is rejected as a duplicate. This prevents redundant storage costs from re-uploads of identical images.
signed_url_time_limited_access
Receipt images must never be served via permanent public URLs. All access to receipt image files must go through time-limited signed URLs generated by the signed_url_provider, enforcing that access is always authenticated and scoped.
compression_before_upload
Receipt images must be compressed client-side before transmission to reduce bandwidth and storage costs. The receipt-upload-service enforces compression using a configurable quality setting before the multipart POST is sent to the backend.
CRUD Operations
Storage Configuration
Entity Relationships
An expense claim can have one or more receipt photo attachments as proof of payment