Expense Type
Data Entity
Description
A classification for the nature of an expense claim — mileage, toll, parking, transit, or other — with associated validation rules and mutual-exclusion constraints. Organizations configure which types are available and which combinations are prohibited to prevent invalid submissions.
Data Structure
| Name | Type | Description | Constraints |
|---|---|---|---|
id |
uuid |
Primary key — globally unique identifier for the expense type record | PKrequiredunique |
organization_id |
uuid |
Foreign key to the organizations table — expense types are scoped per organization; each org defines its own permitted types and rules | required |
name |
string |
Human-readable display label for the expense type, subject to organization terminology overrides (e.g., 'Mileage', 'Toll', 'Parking', 'Public Transit', 'Other') | required |
slug |
string |
Machine-readable identifier used in API payloads, validation logic, and mutual-exclusion rule references. Immutable after creation to preserve referential integrity in expense records. | required |
category |
enum |
Classifies the expense type into a billing category that drives valuation rules, receipt requirements, and Bufdir reporting field mapping | required |
requires_receipt_above_nok |
decimal |
Monetary threshold (in NOK) above which a receipt photo must be attached before the expense can be submitted. NULL means receipt is never required for this type. HLF configures this at 100 NOK for toll, parking, and transit types. | - |
incompatible_with |
json |
Array of expense type slugs that cannot be combined with this type in a single reimbursement claim. Enforces HLF's requirement that mileage and public transit cannot be claimed simultaneously. Stored as a JSON array of slug strings. | - |
auto_approve_below_km |
decimal |
For mileage-category types only: distance threshold (in km) below which claims are automatically approved without coordinator attestation. NULL means all claims require manual approval. HLF configures this at 50 km. | - |
auto_approve_below_nok |
decimal |
For non-mileage types: monetary amount (in NOK) below which claims are automatically approved. NULL means all claims require manual approval. Complements auto_approve_below_km for mixed reimbursement bundles. | - |
min_km |
decimal |
Minimum distance in km required for a mileage claim to be valid. Applicable only when category = 'mileage'. NULL means no minimum enforced. | - |
max_km |
decimal |
Maximum distance in km allowed per single mileage claim. Applicable only when category = 'mileage'. Claims exceeding this value are rejected at submission time. | - |
max_amount_nok |
decimal |
Maximum monetary amount (in NOK) allowed per single claim of this type. Claims exceeding this value are rejected at submission time. Prevents runaway expense submissions. | - |
is_enabled |
boolean |
Whether this expense type is currently available for selection by peer mentors in the organization. Disabled types remain in the database for historical expense record integrity but are hidden from the type selector widget. | required |
display_order |
integer |
Controls the display order of expense types in the Expense Type Selector Widget. Lower values appear first. Organizations can reorder types to surface the most common ones. | required |
icon_key |
string |
Design system icon identifier to render alongside the expense type label in the selector widget (e.g., 'car', 'highway', 'parking', 'bus', 'receipt'). Must match a key defined in the design token system. | - |
bufdir_field_code |
string |
The official Bufdir reporting field code this expense type maps to. Used by the Bufdir Field Mapper Service to aggregate expenses into the correct report columns. NULL if this type does not map to a Bufdir reporting field. | - |
created_at |
datetime |
Timestamp when this expense type configuration record was created | required |
updated_at |
datetime |
Timestamp of the most recent modification to this expense type's configuration | required |
Database Indexes
idx_expense_type_organization_id
Columns: organization_id
idx_expense_type_org_slug_unique
Columns: organization_id, slug
idx_expense_type_org_enabled_order
Columns: organization_id, is_enabled, display_order
idx_expense_type_category
Columns: category
Validation Rules
slug_format_check
error
Validation failed
slug_unique_within_org
error
Validation failed
incompatible_with_references_valid_slugs
error
Validation failed
incompatible_with_is_symmetric
warning
Validation failed
auto_approve_km_only_for_mileage
error
Validation failed
receipt_threshold_non_negative
error
Validation failed
km_range_consistency
error
Validation failed
name_not_blank
error
Validation failed
Business Rules
mutual_exclusion_enforcement
An expense claim cannot include two expense types that are listed in each other's incompatible_with arrays. For example, if 'mileage' lists 'transit' as incompatible and 'transit' lists 'mileage', a reimbursement bundle cannot contain both. This prevents HLF's case of claiming both km reimbursement and a bus ticket for the same trip.
receipt_required_above_threshold
When an expense of this type is submitted with an amount exceeding requires_receipt_above_nok, a receipt photo must be attached before the submission is accepted. The Expense Registration Screen prompts for receipt upload when the threshold is crossed.
auto_approval_threshold_evaluation
When a reimbursement claim is submitted, the Reimbursement Approval Service checks auto_approve_below_km (for mileage types) and auto_approve_below_nok (for all types) to determine if automatic approval applies. Claims below both applicable thresholds are auto-approved without coordinator action.
disabled_type_submission_blocked
Expense types with is_enabled = false must not appear in the Expense Type Selector Widget and must be rejected at the API level if submitted. Historical expense records referencing a disabled type remain valid and intact.
slug_immutability
Once created, the slug field must not be changed. Existing expense records reference this type by slug for validation and mutual-exclusion rule evaluation. Changing a slug would silently break historical cross-reference integrity.
organization_scoped_type_visibility
Expense types must only be returned to users whose JWT organization_id matches the expense type's organization_id. Cross-organization type leakage is prevented at the repository query level.
mileage_distance_range_validation
For expense types in the 'mileage' category, the submitted km distance must fall within [min_km, max_km] when those bounds are configured. Claims outside the range are rejected with a descriptive error message.
amount_ceiling_enforcement
For non-mileage expense types, the submitted NOK amount must not exceed max_amount_nok when configured. This prevents unreasonably large individual toll, parking, or transit claims from bypassing coordinator review.
CRUD Operations
Storage Configuration
Entity Relationships
An expense type classifies each expense claim and drives validation and mutual-exclusion rules
An organization configures the available expense types and their validation rules for its peer mentors