Accounting Export
Data Entity
Description
Records each batch transfer of approved expense and reimbursement data to an external accounting system (Xledger, Microsoft Dynamics, or generic). Captures the full lifecycle of an export attempt — from initiation through field mapping, submission, and outcome — providing an immutable audit trail and enabling retry, rollback, and idempotency guarantees.
Data Structure
| Name | Type | Description | Constraints |
|---|---|---|---|
id |
uuid |
Globally unique identifier for the export record. | PKrequiredunique |
organization_id |
uuid |
Foreign key to the organization that initiated this export. Enforces tenant isolation — all queries must be scoped by this column. | required |
created_by_user_id |
uuid |
Foreign key to the user (Coordinator or Org Admin) who initiated the export. NULL for scheduler-triggered automated exports. | - |
system_type |
enum |
The target accounting system for this export batch. | required |
status |
enum |
Lifecycle status of the export attempt. | required |
batch_reference |
string |
Internal batch identifier generated at export initiation. Used for idempotency checks and grouping related export records. | requiredunique |
external_reference_id |
string |
The journal entry, voucher, or batch ID assigned by the target accounting system on successful submission. NULL until the system confirms receipt. | - |
period_start |
datetime |
Start of the reporting or export period covered by this batch (e.g., first day of the month for scheduled exports). | - |
period_end |
datetime |
End of the reporting or export period covered by this batch. Must be after period_start when both are set. | - |
expense_ids |
json |
Array of expense UUIDs included in this export batch. Stored as JSON array for auditability without requiring a separate join table. | required |
reimbursement_ids |
json |
Array of reimbursement UUIDs included in this batch. May be empty if export covers only expense metadata. | required |
expense_count |
integer |
Denormalized count of expenses in this batch. Derived from expense_ids.length at write time for fast aggregation queries. | required |
reimbursement_count |
integer |
Denormalized count of reimbursements in this batch. | required |
total_amount |
decimal |
Sum of all reimbursement amounts in this export batch in NOK, with two decimal places. Used for reconciliation with the accounting system. | required |
payload_hash |
string |
SHA-256 hash of the sorted expense_ids + reimbursement_ids array. Used for idempotency: prevents duplicate submissions of the identical batch. | requiredunique |
field_mapping_snapshot |
json |
Snapshot of the organization's expense-type-to-account-code mapping at export time. Preserved for audit purposes even if the live mapping changes later. | - |
dry_run |
boolean |
True if this export was a validation-only dry run that did not commit records to the accounting system. | required |
retry_count |
integer |
Number of times this export has been retried after an initial failure. Caps at 3 before the export is marked permanently failed. | required |
error_details |
json |
Structured error payload from the accounting system adapter on failure. Includes error code, message, and offending field path. NULL on success. | - |
exported_at |
datetime |
Timestamp when the accounting system confirmed receipt (status changed to completed). NULL while pending or processing. | - |
rolled_back_at |
datetime |
Timestamp when the export was rolled back. NULL unless status is rolled_back. | - |
rolled_back_by_user_id |
uuid |
User who triggered the rollback. NULL unless status is rolled_back. | - |
created_at |
datetime |
Record creation timestamp, set at insert time and never modified. | required |
updated_at |
datetime |
Last modification timestamp. Updated on every status transition. | required |
Database Indexes
idx_accounting_export_org_created
Columns: organization_id, created_at
idx_accounting_export_org_status
Columns: organization_id, status
idx_accounting_export_payload_hash
Columns: payload_hash
idx_accounting_export_batch_reference
Columns: batch_reference
idx_accounting_export_org_period
Columns: organization_id, period_start, period_end
idx_accounting_export_status_created
Columns: status, created_at
Validation Rules
batch_must_contain_items
error
Validation failed
period_range_valid
error
Validation failed
system_type_matches_org_config
error
Validation failed
total_amount_matches_sum
error
Validation failed
expense_count_matches_array_length
error
Validation failed
external_reference_required_on_completion
error
Validation failed
error_details_required_on_failure
warning
Validation failed
Business Rules
immutable_on_completion
Once status transitions to 'completed', the record is immutable. No fields except audit metadata may be changed. Rollback is modeled as a new record, not a deletion.
approved_expenses_only
Only expenses and reimbursements with status 'approved' may be included in an export batch. The service layer validates all IDs against approval status before creating the record.
idempotency_via_payload_hash
Before creating a new export record, the service computes a SHA-256 hash of the sorted expense_ids + reimbursement_ids. If a record with the same payload_hash already exists and is not in 'failed' or 'rolled_back' status, the request is rejected as a duplicate.
tenant_isolation
All read and write operations must include an organization_id WHERE clause. Cross-organization access is forbidden even for Global Admins querying operational data.
retry_cap
An export may be retried at most 3 times (retry_count max: 3). After reaching the cap, the status is permanently set to 'failed' and manual intervention is required.
dry_run_excluded_from_reimbursement_update
Exports where dry_run = true must not update the export_status on reimbursement records. Dry runs are validation-only and produce no committed state.
no_delete
Accounting export records are never hard-deleted. They form part of the financial audit trail. Rollback sets status to 'rolled_back' and records the rollback actor and timestamp.
field_mapping_snapshot_required_for_completed
When an export transitions to 'completed', a field_mapping_snapshot must be present. This preserves the mapping configuration that was active at export time for future audit reconciliation.