Expense Claim
Data Entity
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.
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
Columns: user_id
idx_expense_claims_organization_id
Columns: organization_id
idx_expense_claims_status
Columns: status
idx_expense_claims_org_status
Columns: organization_id, status
idx_expense_claims_user_status
Columns: user_id, status
idx_expense_claims_expense_date
Columns: expense_date
idx_expense_claims_submitted_at
Columns: submitted_at
idx_expense_claims_accounting_export
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
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
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
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
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
Transitioning a claim to rejected status requires a non-empty rejection_reason. Provides submitter with actionable feedback for resubmission.
paid_requires_approved
Status can only transition to paid from approved or auto_approved. Prevents marking unreviewed claims as paid.
accounting_export_only_approved
Only claims in approved, auto_approved, or paid status can be included in accounting system export batches. Prevents exporting unapproved reimbursements.
proxy_submission_audit
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
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
All queries against expense_claims must include organization_id scoping. Claims are never visible across organization boundaries regardless of user role.