critical priority high complexity backend pending backend specialist Tier 1

Acceptance Criteria

`XledgerExporter` class is implemented and passes static type analysis as a valid implementor of `AccountingExporter`
A `ChartOfAccountsMapper` dependency is injected (not instantiated internally) β€” it provides the mapping from claim category + org unit to Xledger account codes
All five required fields are mapped: `amount` (NOK decimal, 2dp), `transaction_date` (ISO 8601 date), `category` β†’ Xledger account code, `cost_center` β†’ Xledger cost center code, `org_unit` β†’ Xledger department code
Validation is performed before any mapping: a claim with a null or empty required field produces a `ValidationError` with a `FieldValidationFailure` entry identifying the field name and provided value
A claim with an amount of 0.00 is rejected with a `ValidationError` β€” zero-amount claims are not valid Xledger entries
A claim category that has no mapping in `ChartOfAccountsMapper` produces a `ValidationError` with reason `'No Xledger account code for category: {category}'`
If multiple claims in a batch have validation failures, all failures are collected and returned in a single `ValidationError` β€” the method does NOT fail fast on the first bad claim
The mapping result is an intermediate `List<XledgerMappedRecord>` value object, not yet serialized β€” this type is package-private and only used within `XledgerExporter`
The method logs the number of claims processed and the number of validation failures at debug level (no PII in logs)
All mapping logic is pure (no async calls, no side effects) β€” async exists only at the outer `export()` method boundary for the ChartOfAccountsMapper lookup if it requires I/O

Technical Requirements

frameworks
Flutter
Dart
apis
Xledger Chart of Accounts API or static mapping table (TBD with Blindeforbundet)
data models
ApprovedClaim
XledgerMappedRecord
ChartOfAccountsEntry
AccountingExportError
FieldValidationFailure
performance requirements
Mapping 380 claims (HLF peak volume, applicable here for Blindeforbundet scale) must complete in under 200ms synchronously
Chart of accounts lookup must use an in-memory cache populated at service start β€” not a per-claim network call
security requirements
Claim amounts must be handled as `Decimal` or integer minor units (ΓΈre), never `double`, to prevent floating-point rounding errors in financial calculations
No claim personal identifiers (user name, personnel number) are included in Xledger mapped records β€” only financial and accounting fields
Validation error messages must not expose internal system identifiers beyond claim ID

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Financial amounts must NEVER use Dart's `double`. Use the `decimal` package (`decimal: ^2.x`) or represent amounts as integer ΓΈre (100 = 1.00 NOK) throughout the domain. Only convert to a formatted decimal string at serialization time. For the `ChartOfAccountsMapper`, define it as an abstract class (injected dependency) backed initially by a static JSON config file loaded at startup β€” this avoids blocking Xledger API availability during export.

The mapping configuration JSON should be versioned alongside the app so Blindeforbundet's accounting team can update it without a code change (loaded from assets). Collect all validation failures before returning (do not use exceptions mid-loop) β€” use a `List` accumulator pattern. The `XledgerMappedRecord` should mirror the exact column names from the Xledger import specification to simplify the serialization task (task-004).

Testing Requirements

Unit tests only using `flutter_test`. Required scenarios: (1) happy path β€” 5 valid claims map successfully to `XledgerMappedRecord` with correct field values; (2) claim with null cost_center β€” produces `ValidationError` naming the field; (3) claim with zero amount β€” produces `ValidationError`; (4) claim with unmapped category β€” produces `ValidationError` with category name in reason; (5) batch with 3 invalid and 2 valid claims β€” all 3 failures collected in single `ValidationError`, not only first; (6) amount correctly converted to 2 decimal places; (7) date formatted as ISO 8601. Use a stub `ChartOfAccountsMapper` returning predictable mappings. Minimum 12 unit tests.

Component
Xledger Exporter
service high
Epic Risks (3)
high impact medium prob dependency

The Xledger CSV/JSON import specification may not be available in full detail at implementation time. If the field format, column ordering, encoding requirements, or required fields differ from assumptions, the generated file will be rejected by Xledger on first production use.

Mitigation & Contingency

Mitigation: Obtain the official Xledger import specification document from Blindeforbundet before starting XledgerExporter implementation. Build a dedicated acceptance test that validates a sample export file against all documented constraints.

Contingency: If the spec arrives late, implement a configurable column-mapping layer so that field order and names can be adjusted via configuration without code changes. Ship a file-based export that coordinators can manually verify before connecting to Xledger import.

high impact low prob technical

The atomic claim-marking transaction in Double-Export Guard could fail under high concurrency if two coordinators trigger an export for overlapping date ranges simultaneously, potentially allowing duplicate exports to proceed past the guard.

Mitigation & Contingency

Mitigation: Use a database-level advisory lock or a SELECT FOR UPDATE on the relevant claim rows within the export transaction to serialize concurrent exports per organization. Add an integration test that simulates concurrent export triggers.

Contingency: If locking proves problematic at the database level, implement an application-level distributed lock using a Supabase row in a dedicated export_locks table with an expiry timestamp and automatic cleanup on failure.

medium impact high prob integration

HLF's Dynamics portal API endpoint may not be available or documented in time for Phase 1, leaving DynamicsExporter unable to be validated against a real system and potentially shipping with an incorrect field schema.

Mitigation & Contingency

Mitigation: Design DynamicsExporter for file-based export first (CSV/JSON download), with the API push implemented behind a feature flag. Request a Dynamics test environment or sandbox from HLF as early as possible.

Contingency: Ship DynamicsExporter as a file export only for Phase 1. Phase the API push integration into a follow-on task once the Dynamics sandbox is available, using the same AccountingExporter interface with no breaking changes.