core PK: id 10 required 1 unique

Description

A single line item within an expense claim representing one reimbursable cost — mileage, toll, parking, public transport, accommodation, meal, or chauffeur fee. Enforces mutually exclusive expense type combinations, receipt thresholds, and auto-approval eligibility rules per organization configuration.

18
Attributes
5
Indexes
8
Validation Rules
16
CRUD Operations

Data Structure

Name Type Description Constraints
id uuid Primary key, generated server-side (UUID v4). Stable offline ID assigned before sync.
PKrequiredunique
claim_id uuid Foreign key to expense_claims. Every item belongs to exactly one claim.
required
expense_type enum Structured expense type. Fixed vocabulary prevents invalid combinations (e.g. mileage + public_transport on the same item). HLF requirement: no free-text type.
required
description text Optional free-text note for the item (e.g. route description for mileage, vendor name for a receipt). Not a substitute for expense_type — type is always the structured field.
-
amount decimal Monetary value of the item in the stated currency. For mileage this is computed as distance_km × unit_rate_nok_per_km at save time and stored for auditability.
required
currency string ISO 4217 currency code. Defaults to 'NOK'. Stored explicitly to support future multi-currency scenarios.
required
distance_km decimal Kilometers driven. Required when expense_type = 'mileage'. Null for all other types.
-
unit_rate_nok decimal Rate per km in NOK at the time of claim creation, snapshotted from expense-type-config-repository. Immutable after save so rate changes do not retroactively alter approved claims.
-
quantity decimal Generic quantity for non-mileage countable items (e.g. number of toll passages, parking days). Defaults to 1.
-
requires_receipt boolean Computed flag set at item creation: true when amount >= organization's receipt_threshold (default 100 NOK for HLF). Drives UI prompt to attach a receipt and blocks approval if no receipt is linked.
required
is_auto_approvable boolean True when the item meets all auto-approval criteria: mileage-only type, distance_km <= org threshold (default 50 km), and no receipt items present on the same claim. Set by expense-validation-service at create/update.
required
status enum Approval lifecycle state for this individual item. Parent claim status is a rollup of all item statuses.
required
approved_by_user_id uuid User ID of the coordinator or admin who manually approved or rejected this item. Null for auto-approved items and pending items.
-
approved_at datetime Timestamp of approval or rejection. Set at the same time as status transition.
-
rejection_reason text Free-text reason entered by the approver when rejecting. Required when status transitions to 'rejected'.
-
sort_order integer Display ordering of items within a claim. Client-assigned, 0-indexed.
-
created_at datetime Server-side creation timestamp (UTC). Immutable after insert.
required
updated_at datetime Last-modified timestamp (UTC). Updated on every write including status transitions.
required

Database Indexes

idx_expense_items_claim_id
btree

Columns: claim_id

idx_expense_items_claim_status
btree

Columns: claim_id, status

idx_expense_items_expense_type
btree

Columns: expense_type

idx_expense_items_status
btree

Columns: status

idx_expense_items_approved_by
btree

Columns: approved_by_user_id

Validation Rules

amount_positive error

Validation failed

distance_km_required_for_mileage error

Validation failed

distance_km_upper_bound warning

Validation failed

rejection_reason_required error

Validation failed

currency_iso_format error

Validation failed

expense_type_org_allowed error

Validation failed

description_length error

Validation failed

chauffeur_fee_requires_declaration error

Validation failed

Business Rules

mileage_public_transport_mutual_exclusivity
on_create

A single expense item cannot have expense_type = 'mileage' and expense_type = 'public_transport' simultaneously. Across items on the same claim, if a mileage item and a public_transport item cover the same trip leg, the submission is flagged for manual review. Prevents double-dipping on the same journey segment.

receipt_required_above_threshold
on_create

If amount >= organization's receipt_threshold_nok (default 100 NOK), requires_receipt is set to true and the claim cannot transition out of 'pending' until at least one receipt is linked in expense_receipts. Enforced at approval time, not at creation.

auto_approval_eligibility
on_create

Items are auto-approvable when: expense_type is 'mileage', distance_km <= org auto_approval_distance_threshold (default 50 km), and no item on the parent claim has requires_receipt = true. Eligible items transition to 'auto_approved' immediately on claim submission without coordinator action.

mileage_rate_snapshot
on_create

unit_rate_nok is snapshotted from the current organization rate configuration at item creation and stored immutably. Subsequent rate config changes do not affect existing items. This ensures approved claim amounts are auditable against the rate in effect at the time of the activity.

amount_computed_for_mileage
on_create

For expense_type = 'mileage', amount is computed server-side as distance_km × unit_rate_nok. The client submits distance_km; the server rejects any client-supplied amount that deviates to prevent manipulation.

no_delete_after_approval
on_delete

Items with status 'approved', 'auto_approved', or 'rejected' cannot be deleted. Cancellation (status = 'cancelled') is the soft-delete path; hard delete is only permitted for 'pending' items while the parent claim is still in draft.

Enforced by: Expense Service
claim_status_rollup
on_update

After any item status transition, expense-service recomputes the parent expense_claim status: all items auto_approved → claim auto_approved; any item approved/auto_approved and none pending → claim approved; any item rejected → claim partially_rejected; all items cancelled → claim cancelled.

accounting_export_lock
on_update

Once a claim has been dispatched to the accounting system via expense-batch-dispatcher, its items become immutable. Any correction attempt must open a new supplementary claim.

Storage Configuration

Storage Type
primary_table
Location
main_db
Partitioning
No Partitioning
Retention
Permanent Storage