core PK: id 12 required 1 unique

Description

A financial claim submitted by a peer mentor for reimbursement — covering mileage, toll, parking, transit, or other out-of-pocket costs incurred during peer mentoring activities. Expenses are linked to activities and bundled into reimbursement claims. Business rules enforce mutual-exclusion constraints between certain expense types.

22
Attributes
11
Indexes
8
Validation Rules
27
CRUD Operations

Data Structure

Name Type Description Constraints
id uuid Primary key — unique identifier for the expense record
PKrequiredunique
user_id uuid Foreign key referencing the users table — the peer mentor who incurred the expense. Set to the proxy target when a coordinator submits on behalf of a peer mentor.
required
registered_by_id uuid Foreign key referencing the users table — the user who submitted this expense record. Equals user_id for self-submitted expenses; set to coordinator ID for proxy-submitted expenses.
required
activity_id uuid Foreign key referencing the activities table — the peer mentoring activity this expense was incurred during. Nullable for standalone expenses not yet linked to an activity.
-
reimbursement_id uuid Foreign key referencing the reimbursements table — the bundled claim this expense belongs to. Null until the peer mentor groups expenses into a reimbursement submission.
-
expense_type_id uuid Foreign key referencing the expense_types table — classifies the nature of this expense (mileage, toll, parking, transit, other) and drives validation and mutual-exclusion rules.
required
organization_id uuid Foreign key referencing the organizations table — scopes the expense to a specific organization for multi-tenancy data isolation and accounting integration routing.
required
local_association_id uuid Foreign key referencing the local_associations table — scopes the expense to the peer mentor's active local association at time of submission.
required
amount_nok decimal The monetary value of the expense in Norwegian Kroner (NOK). Required for toll, parking, transit, and other expense types. Null for pure mileage claims where only distance applies.
-
distance_km decimal The travel distance in kilometres for mileage reimbursement claims. Required when expense_type is mileage. Null for non-mileage expense types.
-
description text Optional free-text description or notes about the expense — e.g., route taken, purpose of toll, or clarification for coordinator review.
-
status enum Current lifecycle state of the expense claim through submission and approval workflow.
required
receipt_required boolean Flag indicating whether a receipt attachment is required for this expense — set automatically when amount_nok exceeds the organization-configured threshold (e.g., 100 NOK for HLF). Enforced at submission time.
required
receipt_attached boolean Flag indicating whether at least one receipt has been successfully uploaded and linked to this expense. Derived from presence of receipt records but denormalized here for efficient validation queries.
required
is_proxy_submission boolean True when a coordinator submitted this expense on behalf of the peer mentor (registered_by_id ≠ user_id). Included in audit records and accounting exports for compliance tracing.
required
approved_by_id uuid Foreign key referencing the users table — the coordinator or system actor that approved this expense. Null for pending or rejected expenses. Set to a system account ID for auto-approved records.
-
approved_at datetime Timestamp when the expense was approved (auto or manual). Null until approval decision is made.
-
rejection_reason text Optional coordinator-provided reason when an expense is rejected. Surfaced to the peer mentor via notification.
-
accounting_forwarded_at datetime Timestamp when this expense was successfully forwarded to the external accounting system (Xledger or HLF Dynamics). Null until the accounting integration adapter processes it.
-
accounting_reference string External reference ID returned by the accounting system (Xledger voucher number, Dynamics transaction ID) after successful forwarding. Used for reconciliation and support queries.
-
created_at datetime Timestamp when the expense record was first created (draft saved or submitted).
required
updated_at datetime Timestamp of the most recent update to any field on this record.
required

Database Indexes

idx_expense_user_id
btree

Columns: user_id

idx_expense_activity_id
btree

Columns: activity_id

idx_expense_reimbursement_id
btree

Columns: reimbursement_id

idx_expense_expense_type_id
btree

Columns: expense_type_id

idx_expense_organization_id
btree

Columns: organization_id

idx_expense_local_association_id
btree

Columns: local_association_id

idx_expense_status
btree

Columns: status

idx_expense_user_status
btree

Columns: user_id, status

idx_expense_org_status_created
btree

Columns: organization_id, status, created_at

idx_expense_created_at
btree

Columns: created_at

idx_expense_approved_by_id
btree

Columns: approved_by_id

Validation Rules

amount_positive_for_monetary_types error

Validation failed

distance_positive_for_mileage error

Validation failed

receipt_attached_before_submission error

Validation failed

valid_expense_type_reference error

Validation failed

valid_activity_reference error

Validation failed

status_transition_validity error

Validation failed

description_length_limit error

Validation failed

amount_precision_two_decimal_places error

Validation failed

Business Rules

expense_type_mutual_exclusion
on_create

Certain expense type combinations are mutually exclusive within a single expense record or within a single reimbursement submission. For HLF specifically: mileage (km) and transit (bussbillett/kollektiv) cannot be selected simultaneously, as they represent alternative transport modes for the same journey. The organization-specific exclusion matrix is loaded from expense_types configuration.

receipt_required_above_threshold
on_create

An expense with amount_nok exceeding the organization-configured monetary threshold (e.g., 100 NOK for HLF) must have at least one receipt photo attached before it can be submitted. The receipt_required flag is set automatically when the amount is entered and receipt_attached must be true at submission time.

auto_approval_threshold_evaluation
on_update

Upon submission, the Reimbursement Approval Service evaluates the expense against the organization-configured auto-approval thresholds. For HLF: claims with distance_km ≤ 50 and no monetary expenses are automatically approved and status transitions to auto_approved. Claims exceeding thresholds are set to pending_approval for manual coordinator review.

immutable_after_reimbursement_submission
on_update

Once an expense is linked to a reimbursement claim (reimbursement_id is set and reimbursement.status is not draft), the expense record cannot be modified. Edits are blocked at the service layer to preserve claim integrity during the approval workflow.

proxy_registration_audit_trail
on_create

When is_proxy_submission is true, the registered_by_id must reference a coordinator with an active role in the same local_association_id as the target user_id. This is validated at create time and recorded immutably for Bufdir compliance and accounting audit trails.

accounting_forward_on_approval
on_update

When an expense transitions to auto_approved or manually_approved status and belongs to an organization with an active accounting integration (Xledger for Blindeforbundet, Dynamics Portal for HLF), the Accounting Integration Adapter is triggered to forward the approved expense to the external system and update accounting_forwarded_at and accounting_reference.

single_reimbursement_per_expense
on_update

An expense record may belong to at most one reimbursement claim at a time. Attempting to add an already-bundled expense (reimbursement_id IS NOT NULL) to a second reimbursement must be rejected.

distance_required_for_mileage_type
on_create

When the selected expense_type corresponds to a mileage classification, distance_km must be provided and amount_nok may be null (the monetary reimbursement amount is computed by the backend from the distance and the organization's per-km rate). When expense_type is toll, parking, transit, or other, amount_nok is required and distance_km is optional.

org_scoped_type_availability
on_create

Only expense types that are enabled for the expense's organization_id may be selected. Organizations configure which types are available (e.g., some may disable 'other' free-form expenses). The available type list is loaded from expense_types filtered by organization context.

Storage Configuration

Storage Type
primary_table
Location
main_db
Partitioning
No Partitioning
Retention
Permanent Storage

Entity Relationships

activity
incoming one_to_many

An activity may have associated travel and out-of-pocket expenses registered against it

optional
receipt
outgoing one_to_many

An expense claim can have one or more receipt photo attachments as proof of payment

optional cascade delete
expense_type
incoming one_to_many

An expense type classifies each expense claim and drives validation and mutual-exclusion rules

required
reimbursement
incoming one_to_many

A reimbursement claim bundles multiple individual expense line items for approval

optional