Expense
Data Entity
Description
Records travel and other reimbursable costs submitted by peer mentors, including kilometre allowances, tolls, parking, and public transport. Supports threshold-based auto-approval, receipt attachment, confidentiality declarations, and accounting export.
Data Structure
| Name | Type | Description | Constraints |
|---|---|---|---|
id |
uuid |
Primary key | PKrequiredunique |
user_id |
uuid |
Peer mentor who submitted the expense | required |
organization_id |
uuid |
Tenant the expense belongs to, derived from user's active organization context | required |
expense_type_id |
uuid |
Foreign key to expense_types — determines allowed fields and receipt requirements | required |
activity_id |
uuid |
Optional link to the activity this expense was incurred for | - |
status |
enum |
Lifecycle state of the expense claim | required |
amount_nok |
decimal |
Total claimed amount in Norwegian Kroner (NOK), computed from type-specific fields | required |
distance_km |
decimal |
Distance in kilometres for kilometre-allowance expense types; null for other types | - |
rate_per_km |
decimal |
Organisation-configured rate per kilometre at time of submission; snapshotted to preserve history | - |
description |
text |
Optional free-text note explaining the purpose or context of the expense | - |
expense_date |
datetime |
Date the cost was incurred (not necessarily the submission date) | required |
submitted_at |
datetime |
Timestamp when peer mentor submitted the claim for review | - |
requires_receipt |
boolean |
Derived from expense_type and amount threshold; cached at submission time to lock approval requirements | required |
receipt_uploaded |
boolean |
True when at least one expense_receipt record is linked and confirmed uploaded | required |
confidentiality_declaration_id |
uuid |
Foreign key to confidentiality_declarations for driver/transport expenses that require a signed declaration (Blindeforbundet) | - |
auto_approved |
boolean |
True when the expense was automatically approved by the rules engine without manual review | required |
auto_approval_rule_snapshot |
json |
Snapshot of the auto-approval rule that triggered automatic approval, for audit purposes | - |
rejection_reason |
text |
Reason provided by the approver when rejecting the expense | - |
offline_id |
string |
Client-generated UUID used when expense was created offline; used by the ID-mapping service to reconcile after sync | - |
sync_status |
enum |
Tracks whether the record originated offline and has been successfully synced | required |
created_at |
datetime |
Record creation timestamp (server-side) | required |
updated_at |
datetime |
Last modification timestamp | required |
Database Indexes
idx_expenses_user_id
Columns: user_id
idx_expenses_organization_status
Columns: organization_id, status
idx_expenses_expense_date
Columns: expense_date
idx_expenses_activity_id
Columns: activity_id
idx_expenses_offline_id
Columns: offline_id
idx_expenses_submitted_at
Columns: organization_id, submitted_at
Validation Rules
amount_positive
error
Validation failed
distance_required_for_km_type
error
Validation failed
expense_date_not_future
error
Validation failed
expense_type_belongs_to_org
error
Validation failed
receipt_count_when_required
error
Validation failed
description_max_length
error
Validation failed
offline_id_uniqueness
error
Validation failed
Business Rules
no_type_combination
An expense may have only one expense_type. Kilometre allowance and public-transport ticket cannot be claimed on the same record. Enforced by selecting from a controlled expense_types list, not free text.
receipt_required_above_threshold
Expenses with amount_nok above the organisation-configured threshold (default 100 NOK for HLF) must have at least one receipt uploaded before submission can be finalised.
auto_approval_below_threshold
Expenses under the organisation-configured kilometre limit (default 50 km) or with no monetary outlay are automatically approved by the rules engine without manual review.
confidentiality_declaration_required_for_driver
Driver/chauffeur expenses (Blindeforbundet) require a linked confidentiality declaration before the expense can be submitted.
status_transition_guard
Status transitions must follow the defined lifecycle: draft → submitted → (auto_approved | pending_review) → (approved | rejected) → paid. Backwards transitions are not permitted.
immutable_after_approval
Once an expense reaches approved or paid status, its amount_nok, distance_km, expense_type_id, and expense_date fields are immutable.
tenant_isolation
All reads and writes must be scoped to the user's active organization_id. Cross-tenant access is rejected at the API layer.
accounting_export_eligibility
Only expenses with status 'approved' or 'paid' are included in accounting API exports and Bufdir reporting.