Receipt
Data Entity
Description
Stores the metadata and Supabase Storage reference for a receipt image attached to an expense claim. Receipts are required when a claim amount exceeds the organization's configured threshold (e.g., 100 NOK). Images are compressed before upload and accessed via time-limited signed URLs. Deleted when a claim is cancelled.
Data Structure
| Name | Type | Description | Constraints |
|---|---|---|---|
id |
uuid |
Primary key, generated server-side on insert | PKrequiredunique |
expense_claim_id |
uuid |
Foreign key referencing the expense claim this receipt is attached to. One-to-one relationship enforced by unique constraint. | requiredunique |
storage_path |
string |
Fully qualified Supabase Storage path in the format {org_id}/{user_id}/{claim_id}/{filename}. Used to generate signed download URLs. Never exposed directly to clients. | required |
mime_type |
enum |
MIME type of the uploaded file, determined at capture time and validated before upload. | required |
file_size_bytes |
integer |
Size of the stored file in bytes after compression. Used for storage monitoring and to enforce the 10 MB per-file limit. | required |
uploaded_at |
datetime |
UTC timestamp when the receipt was successfully uploaded to Supabase Storage and the metadata record was persisted. | required |
organization_id |
uuid |
Owning organization. Drives Supabase RLS policy so that coordinators and admins can only access receipts within their organization scope. | required |
uploaded_by_user_id |
uuid |
Supabase auth user ID of the peer mentor (or proxy coordinator) who performed the upload. Retained for audit purposes after the claim is processed. | required |
Database Indexes
idx_receipt_expense_claim_id
Columns: expense_claim_id
idx_receipt_organization_id
Columns: organization_id
idx_receipt_uploaded_at
Columns: uploaded_at
idx_receipt_uploaded_by_user_id
Columns: uploaded_by_user_id
Validation Rules
allowed_mime_types
error
Validation failed
max_file_size_10mb
error
Validation failed
storage_path_not_empty
error
Validation failed
expense_claim_must_exist
error
Validation failed
uploaded_at_not_future
error
Validation failed
organization_id_matches_claim
error
Validation failed
Business Rules
receipt_required_above_threshold
A receipt must be attached to an expense claim when the total claim amount exceeds the organization's configured monetary threshold (default 100 NOK). Submission is blocked until a receipt is provided.
one_receipt_per_claim
Each expense claim may have at most one receipt record. The unique constraint on expense_claim_id enforces this at the database level. Replacing a receipt requires deleting the old record first.
compress_before_upload
Receipt images must be compressed to the configured target size limit (default 1 MB) before uploading to Supabase Storage. The file_size_bytes stored in the record reflects the post-compression size.
signed_url_access_only
The storage_path column is never returned directly to clients. All read access to the receipt file must go through time-limited signed URLs generated by the storage service. This prevents unauthorized direct access to storage bucket paths.
delete_on_claim_cancellation
When an expense claim is cancelled, the associated receipt record and its Supabase Storage object must both be deleted. The ON DELETE CASCADE on expense_claim_id handles the database row; the application layer must additionally remove the storage object.
organization_scoped_storage_path
The storage_path must use the pattern {organization_id}/{user_id}/{claim_id}/{filename} so that Supabase Storage RLS bucket policies can enforce organization-level isolation.
CRUD Operations
Storage Configuration
Entity Relationships
When a claim amount exceeds the configured threshold, exactly one receipt image record is linked to the expense claim for audit purposes