Expense
Data Entity
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.
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
Columns: user_id
idx_expense_activity_id
Columns: activity_id
idx_expense_reimbursement_id
Columns: reimbursement_id
idx_expense_expense_type_id
Columns: expense_type_id
idx_expense_organization_id
Columns: organization_id
idx_expense_local_association_id
Columns: local_association_id
idx_expense_status
Columns: status
idx_expense_user_status
Columns: user_id, status
idx_expense_org_status_created
Columns: organization_id, status, created_at
idx_expense_created_at
Columns: created_at
idx_expense_approved_by_id
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
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
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
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
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
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
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
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
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
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.
CRUD Operations
Storage Configuration
Entity Relationships
An activity may have associated travel and out-of-pocket expenses registered against it
An expense claim can have one or more receipt photo attachments as proof of payment
An expense type classifies each expense claim and drives validation and mutual-exclusion rules
A reimbursement claim bundles multiple individual expense line items for approval