core PK: id 17 required 1 unique

Description

Represents a reimbursable expense claim submitted by a peer mentor or coordinator, covering travel costs (km reimbursement, tolls, parking, public transit) incurred during activities. Supports configurable auto-approval thresholds, receipt attachment, confidentiality declarations, and integration with external accounting systems.

31
Attributes
8
Indexes
10
Validation Rules
26
CRUD Operations

Data Structure

Name Type Description Constraints
id uuid Unique identifier for the expense record
PKrequiredunique
activity_id uuid Foreign key referencing the activity this expense is associated with
required
expense_type_id uuid Foreign key referencing the configured expense type (e.g. km reimbursement, toll, parking, public transit)
required
user_id uuid Foreign key referencing the peer mentor or coordinator who owns this expense claim
required
organization_id uuid Tenant scoping — organization this expense belongs to, enforced for all queries
required
submitted_by_user_id uuid User who submitted this expense — differs from user_id when a coordinator registers on behalf of a peer mentor (proxy registration)
required
is_proxy_submission boolean True when the expense was submitted by a coordinator on behalf of a peer mentor
required
expense_date datetime Date the expense was incurred (not the submission date)
required
amount decimal Monetary amount in NOK for fixed-cost expenses (tolls, parking, public transit). Null for pure km-based claims where amount is computed from distance × rate.
-
distance_km decimal Distance in kilometres for km-reimbursement expense types. Null for non-km expense types.
-
reimbursement_rate_nok_per_km decimal Rate per km in NOK at the time of submission, snapshot from the organization's expense type configuration to preserve historical accuracy
-
computed_amount decimal Final computed reimbursement amount in NOK. For km types: distance_km × reimbursement_rate_nok_per_km. For fixed types: equals amount. Stored for audit and reporting.
required
currency string ISO 4217 currency code, always NOK for this platform
required
status enum Lifecycle status of the expense claim
required
description text Optional free-text description or notes from the submitter about this expense
-
requires_receipt boolean Computed flag: true when computed_amount exceeds the organization's receipt threshold (default 100 NOK). Stored for efficient querying and validation.
required
receipt_verified boolean Set to true by an approver or auto-approval rule once at least one valid receipt is confirmed for claims where requires_receipt is true
required
requires_confidentiality_declaration boolean True when the expense type is configured to mandate a confidentiality declaration (e.g. driver honorarium for Blindeforbundet)
required
declaration_id uuid Foreign key to the associated confidentiality declaration when requires_confidentiality_declaration is true
-
auto_approval_eligible boolean Computed at submission time: true when the expense meets all auto-approval criteria (e.g. distance ≤ 50 km and no receipt required). Stored for auditability.
required
submitted_at datetime Timestamp when the expense was submitted for approval (status transitioned from draft to submitted/auto_approved)
-
approved_at datetime Timestamp when the expense was approved (manually or automatically)
-
approved_by_user_id uuid User who manually approved the expense. Null for auto-approved expenses.
-
rejected_at datetime Timestamp when the expense was rejected
-
rejected_by_user_id uuid User who rejected the expense
-
rejection_reason text Mandatory reason text when an approver rejects an expense claim
-
accounting_export_reference string External reference ID returned by the accounting system (Xledger / Dynamics) after successful export. Null until exported.
-
accounting_exported_at datetime Timestamp of successful export to the external accounting system
-
created_at datetime Record creation timestamp, set by the database
required
updated_at datetime Last modification timestamp, updated on every write
required
deleted_at datetime Soft-delete timestamp. Null means record is active. Set when a draft expense is withdrawn before submission.
-

Database Indexes

idx_expenses_user_id
btree

Columns: user_id

idx_expenses_activity_id
btree

Columns: activity_id

idx_expenses_organization_status
btree

Columns: organization_id, status

idx_expenses_organization_expense_date
btree

Columns: organization_id, expense_date

idx_expenses_expense_type_id
btree

Columns: expense_type_id

idx_expenses_submitted_at
btree

Columns: submitted_at

idx_expenses_accounting_export
btree

Columns: accounting_exported_at, organization_id

idx_expenses_deleted_at
btree

Columns: deleted_at

Validation Rules

amount_or_distance_required error

Validation failed

amount_positive error

Validation failed

distance_positive error

Validation failed

expense_date_not_in_future error

Validation failed

expense_type_active error

Validation failed

activity_belongs_to_submitter_org error

Validation failed

rejection_reason_required_on_reject error

Validation failed

km_type_requires_distance error

Validation failed

receipt_verified_before_approval error

Validation failed

proxy_requires_coordinator_role error

Validation failed

Business Rules

receipt_required_above_threshold
on_create

When computed_amount exceeds the organization's configured receipt threshold (default 100 NOK), the expense cannot be submitted without at least one attached receipt. Sets requires_receipt = true and blocks submission until receipt_verified = true.

auto_approval_below_threshold
on_create

Expenses meeting all auto-approval criteria — distance ≤ 50 km for km-type OR computed_amount below the manual-approval threshold AND no receipt required — are automatically transitioned to auto_approved status on submission without coordinator action.

expense_type_exclusivity
on_create

Certain expense types are mutually exclusive within a single expense record. Specifically, km-based reimbursement and public transit cannot both be selected simultaneously, as this represents an invalid combination. The expense_type configuration carries the exclusivity group; the service enforces it at submission.

status_forward_only_transitions
on_update

The status field may only advance forward through the lifecycle: draft → submitted → (auto_approved | pending_approval) → (approved | rejected). Reverting a submitted or approved expense to draft is not permitted. A rejected expense may only be corrected by creating a new expense record.

coordinator_approval_required_above_threshold
on_update

Expenses that are not auto_approval_eligible must be reviewed and actioned by a coordinator or organization admin with the appropriate permission. The expense remains in pending_approval status until an authorized approver acts.

confidentiality_declaration_required_for_driver_expenses
on_create

When the selected expense type is configured as requiring a confidentiality declaration (e.g. driver honorarium for Blindeforbundet), a linked declaration record must exist and be signed before the expense can be submitted.

org_tenant_isolation
always

All expense reads and writes are scoped to the authenticated user's organization_id. Cross-organization expense access is forbidden regardless of user role, except for global admins performing system-level audits.

soft_delete_only_drafts
on_delete

Only expense records in draft status may be soft-deleted (deleted_at set). Submitted, approved, or rejected expenses are immutable financial records and cannot be deleted — they must be rejected or superseded.

accounting_export_immutability
on_update

Once accounting_exported_at is set, the expense record is locked — no further updates to financial fields (amount, distance_km, expense_type_id) are permitted to maintain accounting system integrity.

Storage Configuration

Storage Type
primary_table
Location
main_db
Partitioning
by_date
Retention
Permanent Storage