core PK: id 9 required 2 unique

Description

Photographic evidence attached to an expense item, captured via device camera or gallery. Stores compressed image storage path and thumbnail URL alongside the linked expense item ID; mandatory for HLF expense items exceeding 100 NOK.

13
Attributes
8
Indexes
7
Validation Rules
16
CRUD Operations

Data Structure

Name Type Description Constraints
id uuid Primary key, auto-generated UUID for each receipt record
PKrequiredunique
expense_item_id uuid Foreign key referencing the parent expense item this receipt documents. An expense item may have multiple receipts.
required
organization_id uuid Organization scope for RLS policy enforcement. Denormalized from the parent expense report to allow direct bucket-policy evaluation without joins.
required
uploaded_by uuid User ID of the peer mentor or coordinator who uploaded the receipt. Used for ownership checks and audit trail.
required
storage_path string Supabase Storage object path for the compressed full-resolution receipt image. Format: receipts/{organization_id}/{expense_item_id}/{id}. Never exposed directly to clients — signed URLs are generated on demand.
requiredunique
thumbnail_url string Public or signed URL pointing to the compressed thumbnail variant generated by the Supabase Edge Function at upload time. Used by Receipt Thumbnail Widget for grid display without loading full-resolution images.
-
file_size_bytes integer Size of the compressed image in bytes after client-side compression. Used for storage quota monitoring and upload progress reporting.
required
mime_type enum MIME type of the uploaded image file. Constrained to supported image formats only.
required
upload_status enum Lifecycle status of the receipt upload. Pending indicates client-side queued but not yet confirmed by storage backend; uploaded means Supabase Storage confirmed receipt; failed means upload was rejected or timed out.
required
original_filename string Original filename as provided by the device camera or gallery picker before compression and renaming. Stored for audit and user reference only — not used for storage addressing.
-
compression_ratio decimal Ratio of compressed size to original size (0.0–1.0) recorded at upload time by flutter_image_compress. Useful for storage cost analysis and quality audits.
-
uploaded_at datetime UTC timestamp when the receipt was successfully confirmed as stored in Supabase Storage. Set server-side on status transition to 'uploaded'.
required
deleted_at datetime Soft-delete timestamp. When set, the receipt is logically removed from active queries but retained for financial audit trail compliance. The corresponding Storage object is hard-deleted immediately; only the metadata row is soft-deleted.
-

Database Indexes

idx_receipts_expense_item_id
btree

Columns: expense_item_id

idx_receipts_uploaded_by
btree

Columns: uploaded_by

idx_receipts_organization_id
btree

Columns: organization_id

idx_receipts_upload_status
btree

Columns: upload_status

idx_receipts_uploaded_at
btree

Columns: uploaded_at

idx_receipts_expense_item_status
btree

Columns: expense_item_id, upload_status

idx_receipts_storage_path
btree unique

Columns: storage_path

idx_receipts_active
btree

Columns: expense_item_id, deleted_at

Validation Rules

file_size_within_limit error

Validation failed

mime_type_is_image error

Validation failed

expense_item_exists_and_active error

Validation failed

storage_path_unique error

Validation failed

uploaded_by_matches_session_user error

Validation failed

compression_ratio_plausible warning

Validation failed

upload_status_transition_valid error

Validation failed

Business Rules

mandatory_receipt_above_threshold
on_create

For HLF organizations, any expense item with an amount exceeding 100 NOK must have at least one receipt with upload_status = 'uploaded' before the parent travel expense report can be submitted. Expense items below the threshold may be submitted without a receipt.

receipt_belongs_to_accessible_expense_item
on_create

A receipt may only be created for an expense item that the authenticated user owns (peer mentor) or has coordinator-level access to (coordinator acting on behalf). Enforced via Supabase RLS policy joining receipts → expense_items → travel_expense_reports → users.

receipt_immutable_after_approval
on_create

Once the parent travel expense report enters 'approved' or 'rejected' status in reimbursement_approvals, no new receipts may be added and existing receipts may not be deleted. The financial record must remain intact for audit purposes.

soft_delete_with_storage_cleanup
on_delete

When a receipt is deleted (deleted_at set), the corresponding Supabase Storage object at storage_path must be hard-deleted immediately by the Image Storage Backend to prevent orphaned billing. The metadata row is soft-deleted and retained for audit trail.

thumbnail_generated_on_upload_confirmation
on_update

Upon successful transition of upload_status to 'uploaded', a Supabase Edge Function is triggered to generate a compressed thumbnail and write the resulting URL back to thumbnail_url. The receipt is considered fully processed only after thumbnail_url is populated.

organization_scoped_storage_path
on_create

The storage_path must follow the prescribed format receipts/{organization_id}/{expense_item_id}/{id} to ensure bucket-level RLS policies can enforce organization isolation without additional join queries.

Storage Configuration

Storage Type
primary_table
Location
main_db
Partitioning
No Partitioning
Retention
Permanent Storage

Entity Relationships

expense_items
incoming one_to_many

Expense items above configured thresholds require one or more photographic receipt attachments

optional cascade delete