core PK: id 9 required 1 unique

Description

A bundled reimbursement claim grouping one or more expenses submitted by a peer mentor for coordinator or automated approval. Tracks the overall claim status through the approval lifecycle: pending, auto-approved, manually approved, or rejected. Upon approval, forwards data to external accounting systems.

18
Attributes
9
Indexes
8
Validation Rules
21
CRUD Operations

Data Structure

Name Type Description Constraints
id uuid Globally unique identifier for the reimbursement claim, used as the primary key and in all cross-entity references.
PKrequiredunique
user_id uuid Foreign key referencing the users table — the peer mentor who submitted this reimbursement claim. Used to scope reads and enforce ownership.
required
organization_id uuid Foreign key referencing the organizations table. Used for multi-tenancy scoping of all queries — ensures data cannot cross organizational boundaries.
required
local_association_id uuid Foreign key referencing the local_associations table. Used for coordinator-scoped approval queue queries — coordinators may only see claims from their own local association.
required
status enum Current lifecycle state of the reimbursement claim. Drives UI badge rendering, approval queue visibility, and accounting forwarding logic.
required
total_amount_nok decimal Computed sum of all linked expense amounts in Norwegian kroner. Denormalized for fast threshold evaluation and queue list display without requiring a JOIN-aggregate on each read.
required
total_distance_km decimal Computed sum of all linked mileage expense distances in kilometres. Denormalized for threshold-based auto-approval evaluation without recomputing on each approval check.
-
submitted_at datetime Timestamp when the peer mentor submitted the claim for review. Set once on submission and immutable thereafter. Used for queue sorting and time-window reporting.
required
approved_at datetime Timestamp when the claim reached a terminal approved state (either auto_approved or manually_approved). Null while the claim is pending or under clarification.
-
rejected_at datetime Timestamp when the claim was rejected by a coordinator. Null for all non-rejected states.
-
approved_by uuid Foreign key referencing the users table — the coordinator who issued the manual approval or rejection decision. Null for auto-approved claims and pending claims.
-
rejection_reason text Optional freeform text provided by the coordinator explaining why the claim was rejected or what clarification is needed. Required when status transitions to 'rejected'.
-
accounting_forwarded_at datetime Timestamp when the approved claim was successfully forwarded to the external accounting system (Xledger for Blindeforbundet, Dynamics portal for HLF). Null until forwarding completes. Used for idempotency — prevents double-forwarding on retry.
-
accounting_reference string External reference ID returned by the accounting system (Xledger transaction ID or Dynamics record ID) after successful forwarding. Used for reconciliation and audit trail.
-
coordinator_notes text Optional notes added by the coordinator during the approval or clarification workflow. Stored on the reimbursement for display in the claim detail screen.
-
auto_approval_rules_applied json JSONB snapshot of the threshold configuration values that were evaluated at auto-approval time (km threshold, amount threshold, organization_id). Preserved for audit trail even if thresholds change later.
-
created_at datetime Record creation timestamp, set by the database on INSERT. Distinct from submitted_at to allow draft states if introduced in future versions.
required
updated_at datetime Timestamp of the last status change or field update. Maintained by the application layer on every UPDATE operation.
required

Database Indexes

idx_reimbursement_user_id
btree

Columns: user_id

idx_reimbursement_organization_id
btree

Columns: organization_id

idx_reimbursement_local_association_id
btree

Columns: local_association_id

idx_reimbursement_status
btree

Columns: status

idx_reimbursement_submitted_at
btree

Columns: submitted_at

idx_reimbursement_org_status
btree

Columns: organization_id, status

idx_reimbursement_assoc_status_submitted
btree

Columns: local_association_id, status, submitted_at

idx_reimbursement_user_submitted
btree

Columns: user_id, submitted_at

idx_reimbursement_accounting_forwarded
btree

Columns: accounting_forwarded_at

Validation Rules

total_amount_non_negative error

Validation failed

total_distance_non_negative error

Validation failed

submitted_at_not_future error

Validation failed

user_must_be_active_peer_mentor error

Validation failed

organization_and_association_consistency error

Validation failed

coordinator_notes_length error

Validation failed

accounting_reference_format error

Validation failed

auto_approval_rules_applied_schema error

Validation failed

Business Rules

minimum_one_expense_required
on_create

A reimbursement claim must reference at least one linked expense record at the time of submission. An empty claim cannot be submitted and will be rejected at the service layer before persisting.

auto_approval_threshold_evaluation
on_create

On submission, the Reimbursement Approval Service evaluates total_distance_km against the organization-specific km threshold (default: 50 km) and total_amount_nok against the monetary threshold. Claims meeting both conditions (distance below threshold AND no individual expense over the amount threshold) are auto-approved immediately and status is set to 'auto_approved'. The threshold configuration at decision time is snapshotted into auto_approval_rules_applied for immutable audit trail.

immutable_after_terminal_state
on_update

Once a reimbursement claim reaches a terminal state (auto_approved, manually_approved, rejected), its status, total_amount_nok, total_distance_km, and expense linkages cannot be modified. Any attempt to update a terminal-state claim is rejected with a 409 Conflict response.

coordinator_association_scope
always

A coordinator may only read and act on reimbursement claims belonging to peer mentors within their own local_association_id. The reimbursement-repository and reimbursement-admin-repository enforce this at the SQL query level via JOIN to user_organization_roles, not at the application layer.

accounting_forwarding_on_approval
on_update

When a claim transitions to 'auto_approved' or 'manually_approved', the Accounting Integration Adapter is invoked to forward the claim to the correct external accounting system for the organization (Xledger for Blindeforbundet, Dynamics portal for HLF). The adapter uses exponential backoff retry. accounting_forwarded_at remains null until forwarding succeeds, and idempotency is enforced by checking this field before attempting forwarding.

rejection_reason_required
on_update

When a coordinator transitions a claim to 'rejected' or 'clarification_requested', a non-empty rejection_reason must be provided. This is enforced at the service layer before the status update is persisted.

audit_log_on_every_decision
on_update

Every status transition — whether automatic or manual — must produce an immutable audit log entry via the Approval Audit Logger, capturing the actor identity, decision, timestamp, and rule evaluation details (for auto-approvals). No decision may be persisted without a corresponding audit entry in the same transaction.

valid_status_transition
on_update

Status transitions must follow the defined lifecycle DAG: pending → auto_approved | manually_approved | rejected | clarification_requested. clarification_requested → manually_approved | rejected. Terminal states (auto_approved, manually_approved, rejected) have no outgoing transitions. Any attempt to set an invalid transition is rejected.

peer_mentor_ownership
on_create

Only the peer mentor identified by user_id may create a reimbursement claim, and only their own claims are visible in their personal expense history. Coordinators may view claims of peer mentors in their association; they cannot create claims on behalf of others.

Storage Configuration

Storage Type
primary_table
Location
main_db
Partitioning
No Partitioning
Retention
Permanent Storage

Entity Relationships

expense
outgoing one_to_many

A reimbursement claim bundles multiple individual expense line items for approval

optional
reimbursement_approval
outgoing one_to_many

A reimbursement claim can have multiple approval records (e.g., initial auto-approval followed by manual override)

optional
user
incoming one_to_many

A user (peer mentor) submits many reimbursement claims over their active period

required