core PK: id 14 required 2 unique

Description

Stores kilometer distance and route details for a mileage-based expense claim line. Linked one-to-one to an expense claim, capturing origin, optional destination, distance in kilometers, per-km rate at time of submission, and calculated reimbursement amount. Auto-approval is applied when distance falls below the organization's configured threshold.

18
Attributes
5
Indexes
11
Validation Rules
19
CRUD Operations

Data Structure

Name Type Description Constraints
id uuid Primary key. Generated on the client before insert for optimistic UI updates.
PKrequiredunique
expense_claim_id uuid Foreign key to the parent expense_claim record. Enforces the one-to-one relationship — each expense claim may have at most one mileage_claim row.
requiredunique
organization_id uuid Foreign key to the organization. Injected at creation time for Supabase RLS tenant isolation. Must match the organization_id on the parent expense_claim.
required
submitter_id uuid User ID of the peer mentor who submitted the claim. Used for ownership checks, history queries, and coordinator-scope validation.
required
origin string Free-text description of the travel starting point (e.g. home address, town name). Required field; rendered with a privacy-aware placeholder in the UI to discourage overly precise personal addresses.
required
destination string Free-text description of the travel end point. Optional — peer mentors may omit for privacy. Rendered with an explicit 'optional' label and privacy hint in the route input widget.
-
distance_km decimal Distance driven in kilometers. Must be a positive value with up to two decimal places. Pre-filled from the last-used distance cache for the submitting user; always editable.
required
rate_per_km decimal The organization's per-kilometer reimbursement rate in NOK at the exact moment of submission. Captured as a snapshot so historical claims remain accurate if the rate changes later.
required
reimbursement_amount decimal Gross reimbursement amount in NOK, computed as distance_km × rate_per_km and rounded per Norwegian reimbursement standards. Stored after server-side calculation to prevent client tampering.
required
approval_threshold_km decimal Snapshot of the organization's auto-approval distance threshold (default 50 km) at submission time. Stored so threshold changes do not retroactively alter already-submitted claims.
required
status enum Approval lifecycle state of the mileage claim. Set to 'auto_approved' immediately on submission when distance_km < approval_threshold_km; otherwise 'pending_review' until coordinator action.
required
auto_approved boolean True when the claim was approved automatically without coordinator intervention because distance_km was below approval_threshold_km. Used for analytics and audit reporting.
required
submitted_at datetime UTC timestamp when the claim was first submitted. Set server-side on insert; immutable thereafter.
required
reviewed_at datetime UTC timestamp when a coordinator approved or rejected the claim. Null for auto-approved and pending claims.
-
reviewed_by uuid User ID of the coordinator who performed the manual approval or rejection. Null for auto-approved claims.
-
review_comment text Optional coordinator comment attached to an approval or rejection decision. Required when status transitions to 'rejected'; optional for 'approved'.
-
created_at datetime Row creation timestamp, set server-side. Equals submitted_at in practice but kept separate for database audit hygiene.
required
updated_at datetime Row last-update timestamp, maintained by a Supabase database trigger on every write.
required

Database Indexes

idx_mileage_claim_expense_claim_id
btree unique

Columns: expense_claim_id

idx_mileage_claim_submitter_id
btree

Columns: submitter_id

idx_mileage_claim_organization_id_status
btree

Columns: organization_id, status

idx_mileage_claim_organization_id_submitted_at
btree

Columns: organization_id, submitted_at

idx_mileage_claim_submitter_id_submitted_at
btree

Columns: submitter_id, submitted_at

Validation Rules

distance_km_positive error

Validation failed

distance_km_format error

Validation failed

origin_non_empty error

Validation failed

origin_max_length error

Validation failed

destination_max_length error

Validation failed

rate_per_km_positive error

Validation failed

expense_claim_exists error

Validation failed

no_duplicate_mileage_claim error

Validation failed

reimbursement_amount_consistency error

Validation failed

valid_status_transition error

Validation failed

reviewer_must_be_coordinator error

Validation failed

Business Rules

one_to_one_expense_claim
on_create

Each expense_claim may have at most one mileage_claim. The expense_claim_id column carries a UNIQUE constraint enforced at the database level. Attempts to insert a second mileage_claim for the same expense_claim_id must be rejected with a domain error.

auto_approval_below_threshold
on_create

When distance_km is strictly less than the organization's configured auto-approval threshold (fetched via org-rate-config-repository and snapshotted into approval_threshold_km), the claim status is set to 'auto_approved' and auto_approved is set to true without requiring coordinator action.

manual_review_above_threshold
on_create

When distance_km is greater than or equal to approval_threshold_km, status is set to 'pending_review' and the claim enters the coordinator attestation queue. The claim must not be counted as approved for reimbursement until a coordinator explicitly approves it.

rate_snapshot_at_submission
on_create

rate_per_km must be fetched from the organization configuration at the moment of submission and stored immutably on the row. Subsequent rate changes must not alter previously submitted claims. The calculated reimbursement_amount is derived from this snapshot.

reimbursement_amount_derived
on_create

reimbursement_amount must equal distance_km × rate_per_km, rounded per Norwegian rounding rules (two decimal places, half-up). Client-side display via realtime-reimbursement-display is for UX only; the authoritative value is computed server-side by mileage-calculation-service before persistence.

immutable_after_final_decision
on_update

Once status transitions to 'approved', 'auto_approved', or 'rejected', the distance_km, rate_per_km, reimbursement_amount, origin, and destination fields must not be modified. Only review metadata (reviewed_at, reviewed_by, review_comment) may be added during the review transition itself.

review_comment_required_on_rejection
on_update

When a coordinator transitions status to 'rejected', review_comment must be non-null and non-empty. This is enforced at the service layer before the repository write to ensure the submitter always receives an explanation.

organization_scope_isolation
always

All reads and writes must be scoped to the authenticated user's organization_id via Supabase RLS policies. A peer mentor may only read their own mileage claims; a coordinator may only read claims within their assigned chapters; org admins may read all claims for their organization.

last_used_distance_update
on_create

After a successful claim submission, distance_km must be persisted to the local distance cache via distance-prefill-service so the next mileage entry is pre-filled with this value. Cache must be cleared on logout.

Storage Configuration

Storage Type
primary_table
Location
main_db
Partitioning
No Partitioning
Retention
Permanent Storage

Entity Relationships

expense_claim
incoming one_to_one

When a claim includes a kilometers-driven expense type, exactly one mileage_claim record stores the distance and calculated reimbursement

optional cascade delete