core PK: id 13 required 1 unique

Description

A travel expense or reimbursement claim submitted by a peer mentor or coordinator, linked to an activity. Captures kilometre reimbursement, toll fees, parking, public transport, and other allowable expense types with configurable mutual-exclusivity rules, threshold-based auto-approval, and full lifecycle tracking from draft through reimbursement.

23
Attributes
6
Indexes
8
Validation Rules
32
CRUD Operations

Data Structure

Name Type Description Constraints
id uuid Primary key. Globally unique identifier for the expense record.
PKrequiredunique
user_id uuid FK to users. The peer mentor or coordinator who incurred the expense.
required
organization_id uuid FK to organizations. Tenant isolation key. Set from the submitting user's org context at creation time.
required
activity_id uuid FK to activities. The activity this expense is associated with. May be null for declarations or standalone cost claims not tied to a specific activity record.
-
expense_type_id uuid FK to expense_types. The primary expense type selected from the organisation-specific catalogue.
required
status enum Lifecycle status of the expense claim. Drives approval routing and reimbursement processing.
required
distance_km decimal Kilometres driven, required when expense type is kilometre reimbursement. Precision (10,2).
-
amount decimal Total monetary amount of the expense claim in the stated currency. Computed server-side for km-based types; entered directly for fixed costs. Precision (12,2).
-
currency string ISO 4217 currency code. Defaults to NOK for all Norwegian organisations.
required
expense_date datetime The date the expense was actually incurred (not the submission date). Used for reporting period allocation.
required
description text Optional free-text note added by the submitter. Not used for expense type selection; expense types are structured choices.
-
submission_date datetime Timestamp when the expense was formally submitted (status changed from draft to submitted).
-
auto_approved boolean True if the expense was approved automatically by the rule engine without human review.
required
auto_approval_rule_id uuid FK to auto_approval_rules (if applicable). Records which rule triggered auto-approval for audit traceability.
-
rejection_reason text Human-readable reason provided by the approver when an expense is rejected. Required when status transitions to rejected.
-
confidentiality_declaration_id uuid FK to declaration records. Required for driver expense types (e.g., Blindeforbundet chauffeur honorarium). Links to the signed confidentiality declaration.
-
requires_receipt boolean Computed/cached flag indicating whether at least one receipt is required for this expense based on its type and amount. Populated by receipt requirement service at submission time.
required
proxy_submitted_by uuid FK to users. Populated when a coordinator submits the expense on behalf of a peer mentor. Null for self-submitted expenses.
-
version integer Optimistic locking counter. Incremented on every update. Prevents lost updates during concurrent approval operations.
required
synced_to_accounting boolean True once the expense has been successfully exported to the organisation's accounting system (Xledger, Dynamics/Visma).
required
accounting_reference_id string External reference ID returned by the accounting system after successful export. Used for idempotency on re-export attempts.
-
created_at datetime Record creation timestamp. Set once at insert, never updated.
required
updated_at datetime Last modification timestamp. Updated on every write operation.
required

Database Indexes

idx_expense_user_created
btree

Columns: user_id, created_at

idx_expense_org_status
btree

Columns: organization_id, status

idx_expense_activity
btree

Columns: activity_id

idx_expense_expense_type
btree

Columns: expense_type_id

idx_expense_org_date
btree

Columns: organization_id, expense_date

idx_expense_status_org_submitted
btree

Columns: status, organization_id, submission_date

Validation Rules

user_id_required error

Validation failed

expense_type_id_required error

Validation failed

expense_date_required_and_not_future error

Validation failed

distance_km_positive_when_set error

Validation failed

amount_non_negative error

Validation failed

km_or_amount_required_on_submit error

Validation failed

currency_iso_format error

Validation failed

version_matches_on_update error

Validation failed

Business Rules

expense_type_mutual_exclusivity
on_create

Selected expense types must not violate the organisation's mutual-exclusivity rules. For example, kilometre reimbursement and a public-transport ticket cannot be claimed for the same journey. The expense type catalogue defines exclusivity groups per organisation.

receipt_required_above_threshold
on_create

If the expense amount exceeds the organisation-configured receipt threshold (default 100 NOK for HLF), at least one receipt must be attached before the expense can be submitted. The receipt requirement service evaluates this per expense type, amount, and organisation.

auto_approval_threshold_evaluation
on_create

On submission, the auto-approval rule engine evaluates all enabled auto-approval rules for the organisation. Expenses below the configured kilometre threshold (e.g., < 50 km) or with no receipts required are automatically approved without human review. The auto_approved flag and auto_approval_rule_id are set accordingly.

confidentiality_declaration_required_for_driver_types
on_create

For expense types classified as driver or chauffeur (Blindeforbundet requirement), a valid confidentiality declaration must exist for the submitting user before the expense can be submitted.

status_transition_guard
on_update

Expense status may only advance through the defined lifecycle: draft → submitted → pending_approval|auto_approved → approved|rejected → reimbursed. Reverse transitions are forbidden except for admin corrections with audit log entry.

rejection_reason_required
on_update

When an expense is rejected, a non-empty rejection_reason must be provided by the approving coordinator or org admin.

proxy_scope_validation
on_create

When proxy_submitted_by is set, the submitting coordinator must belong to the same local association as the peer mentor (user_id). Cross-association proxy expense submission is not permitted.

tenant_isolation
always

All expense queries must be scoped by organization_id. No cross-tenant expense data may be read or written except by Global Admins operating in explicit audit contexts.

immutable_after_reimbursement
on_update

Once an expense reaches 'reimbursed' status, its core financial fields (amount, distance_km, expense_type_id) become immutable. Only administrative metadata fields may be updated.

accounting_export_idempotency
on_update

Before exporting an approved expense to an external accounting system, the accounting_reference_id field is checked. If already populated, the export is skipped and the existing reference is returned to prevent duplicate journal entries.

Storage Configuration

Storage Type
primary_table
Location
main_db
Partitioning
No Partitioning
Retention
Permanent Storage

Components Managing This Entity

service Permission Validation Middleware ["backend"] service Proxy Scope Validator ["mobile"] data Expense Store ["mobile"] data Expense Store ["mobile"] service Expense Service ["mobile"] service Expense Service ["mobile"] service Expense Validation Service ["mobile"] ui Expense Form Screen ["mobile"] ui Expense Form Screen ["mobile"] service Receipt Requirement Service ["mobile","backend"] ui Receipt Preview Widget ["mobile"] service Expense Rule Validation Engine ["backend"] ui Expense Type Selector Widget ["mobile"] ui Expense Type Selector Widget ["mobile"] service Declaration Service ["mobile","backend"] service Declaration Service ["backend"] data Report Store ["backend"] data Report Store ["backend"] infrastructure Accounting Adapter ["backend"] infrastructure Accounting Adapter ["backend"] service Accounting Field Mapper ["backend"] service Dashboard Service ["mobile"] service Dashboard Service ["mobile"] data Local SQLite Database ["mobile"] data Local SQLite Database ["mobile"] data Sync Queue ["mobile"] data Sync Queue ["mobile"] service Background Sync Service ["mobile"] service Background Sync Service ["mobile"] service Conflict Resolution Service ["mobile"] data Analytics Store ["backend"] data Analytics Store ["backend"] service KPI Aggregation Service ["backend"] service KPI Aggregation Service ["backend"] data Expense Approval Store ["backend"] data Expense Approval Store ["backend"] service Expense Approval Service ["backend"] service Expense Approval Service ["backend"] ui Approval Decision Modal ["frontend"] ui Expense Queue Page ["frontend"] ui Expense Queue Page ["frontend"] ui Receipt Preview Widget ["frontend"] service Auto-Approval Rule Engine ["backend"] service Auto-Approval Rule Engine ["backend"] data Reimbursement Repository ["backend"] service Reimbursement Service ["backend"] service Reimbursement Service ["backend"] data Report Store ["backend"] data Report Store ["backend"] service Report Generation Service ["backend"] service Report Generation Service ["backend"] service Bufdir Export Service ["backend"] service Bufdir Export Service ["backend"] service Custom Report Service ["backend"] service Custom Report Service ["backend"] infrastructure Accounting Adapter ["backend"] infrastructure Accounting Adapter ["backend"] service Accounting API Service ["backend"] service Accounting API Service ["backend"]