core PK: id 12 required 1 unique

Description

A reimbursement request submitted by a peer mentor or coordinator for travel and other approved expenses incurred while performing peer mentor activities. Tracks the full lifecycle from draft through approval or rejection, enforces expense type constraints to prevent invalid combinations, and supports both manual and automatic approval workflows based on configurable thresholds.

24
Attributes
8
Indexes
8
Validation Rules
33
CRUD Operations

Data Structure

Name Type Description Constraints
id uuid Primary key, generated client-side for offline-first compatibility
PKrequiredunique
user_id uuid FK to users — the peer mentor or coordinator who submitted the claim
required
organization_id uuid FK to organizations — tenant scoping for multi-organization isolation
required
status enum Lifecycle state of the claim — drives approval workflow routing
required
expense_date datetime Date the expense was incurred (may differ from submission date)
required
description text Free-text description of the activity context requiring reimbursement
-
total_amount decimal Computed total in NOK across all expense items — denormalized for fast querying and Bufdir reporting
required
currency string ISO 4217 currency code — defaults to NOK, extensible for future use
required
distance_km decimal Total kilometres driven for travel claims — null when no vehicle travel involved
-
has_public_transport boolean True when any expense item is a public transport ticket — used to enforce mutual exclusivity with km-based claims
required
requires_declaration boolean True when the claim type mandates a confidentiality declaration (e.g. chauffeur services for Blindeforbundet)
required
declaration_id uuid FK to declarations — linked confidentiality declaration when requires_declaration is true
-
auto_approved boolean True when the claim was approved automatically by threshold rules without coordinator review
required
auto_approval_rule_id uuid FK to auto_approval_rules — the rule that triggered auto-approval, null if manually reviewed
-
submitted_at datetime Timestamp when the user submitted the claim for review — null while in draft
-
reviewed_by uuid FK to users — coordinator or admin who approved or rejected the claim
-
reviewed_at datetime Timestamp of manual approval or rejection decision
-
rejection_reason text Free-text explanation provided by reviewer when rejecting — required on rejection
-
paid_at datetime Timestamp when reimbursement was marked as paid out
-
accounting_export_reference string External reference ID from Xledger or Dynamics after successful accounting export
-
accounting_exported_at datetime Timestamp when the claim was successfully pushed to the accounting system
-
proxy_submitted_by uuid FK to users — coordinator who submitted this claim on behalf of the peer mentor; null for self-submitted claims
-
created_at datetime Record creation timestamp
required
updated_at datetime Last modification timestamp — updated on every write
required

Database Indexes

idx_expense_claims_user_id
btree

Columns: user_id

idx_expense_claims_organization_id
btree

Columns: organization_id

idx_expense_claims_status
btree

Columns: status

idx_expense_claims_org_status
btree

Columns: organization_id, status

idx_expense_claims_user_status
btree

Columns: user_id, status

idx_expense_claims_expense_date
btree

Columns: expense_date

idx_expense_claims_submitted_at
btree

Columns: submitted_at

idx_expense_claims_accounting_export
btree

Columns: accounting_exported_at, organization_id

Validation Rules

expense_date_not_future error

Validation failed

expense_date_not_too_old warning

Validation failed

distance_km_positive error

Validation failed

total_amount_positive error

Validation failed

receipt_required_above_threshold error

Validation failed

declaration_linked_when_required error

Validation failed

currency_iso_format error

Validation failed

at_least_one_expense_item error

Validation failed

Business Rules

no_km_and_public_transport_combination
on_create

A single expense claim cannot contain both distance-based (km) items and public transport ticket items simultaneously. This is a hard technical constraint preventing invalid combinations that would result in double-claiming the same journey.

declaration_required_for_chauffeur_claims
on_update

Any claim involving chauffeur honorarium expense items must be linked to a completed confidentiality declaration before submission is allowed. Enforced per Blindeforbundet's workflow requirements.

auto_approval_threshold
on_update

Claims with distance_km below the organization's configured threshold AND no receipt-required expense items are automatically transitioned to auto_approved status without coordinator review. Default threshold is 50 km. Threshold values are read from auto_approval_rules.

submitted_claim_immutable
on_update

Once a claim reaches submitted or later status, the submitting user cannot modify expense items or claim fields. Only coordinators with approval permissions can transition status or add rejection notes.

rejection_requires_reason
on_update

Transitioning a claim to rejected status requires a non-empty rejection_reason. Provides submitter with actionable feedback for resubmission.

paid_requires_approved
on_update

Status can only transition to paid from approved or auto_approved. Prevents marking unreviewed claims as paid.

accounting_export_only_approved
always

Only claims in approved, auto_approved, or paid status can be included in accounting system export batches. Prevents exporting unapproved reimbursements.

proxy_submission_audit
on_create

When proxy_submitted_by is set (coordinator submitting on behalf of peer mentor), the action is recorded in the audit log with both user IDs for accountability and Bufdir traceability.

total_amount_sync
always

total_amount must always equal the sum of all linked expense_items.amount. Recomputed on any child item create, update, or delete to keep the denormalized aggregate consistent.

tenant_isolation
always

All queries against expense_claims must include organization_id scoping. Claims are never visible across organization boundaries regardless of user role.

Storage Configuration

Storage Type
primary_table
Location
main_db
Partitioning
by_date
Retention
Permanent Storage