core PK: id 10 required 2 unique

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).

13
Attributes
7
Indexes
6
Validation Rules
14
CRUD Operations

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
btree

Columns: expense_id

idx_receipt_uploader_id
btree

Columns: uploader_id

idx_receipt_organization_id
btree

Columns: organization_id

idx_receipt_storage_key
btree unique

Columns: storage_key

idx_receipt_checksum_expense
btree

Columns: expense_id, checksum

idx_receipt_uploaded_at
btree

Columns: uploaded_at

idx_receipt_active
btree

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
on_create

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
on_create

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
on_create

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
on_delete

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
on_create

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
always

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
on_create

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.

Enforced by: Receipt Upload Service

Storage Configuration

Storage Type
primary_table
Location
main_db
Partitioning
No Partitioning
Retention
Permanent Storage

Entity Relationships

expense
incoming one_to_many

An expense claim can have one or more receipt photo attachments as proof of payment

optional cascade delete