Implement Xledger REST adapter
epic-external-system-integration-configuration-core-services-task-006 — Implement the XledgerAdapter conforming to the uniform adapter interface. Handles Xledger-specific OAuth2 authentication, endpoint construction for accounting entry creation, payload serialization in Xledger's expected format, error normalization, and retry logic for transient failures. Used by Blindeforbundet for accounting exports.
Acceptance Criteria
Technical Requirements
Execution Context
Tier 2 - 518 tasks
Can start after Tier 1 completes
Handles integration between different epics or system components. Requires coordination across multiple development streams.
Implementation Notes
Implement inside a Supabase Edge Function (Deno) — never in the Flutter client. The adapter should be a Deno class that accepts an AdapterConfig object at construction (credentials resolved from vault, not passed directly). Use Deno's built-in fetch for HTTP calls. For OAuth2, implement a lightweight client credentials grant without third-party OAuth libraries to keep the Edge Function bundle small.
Xledger uses a REST API with JSON bodies — construct payloads by mapping from internal expense/activity models. Key pitfall: Xledger's cost centre and account code fields are organisation-specific — store these as part of the integration configuration (not hardcoded). Map the activity's organization_id to the Xledger company code stored in the credential vault entry. Implement the adapter's execute() method with a command pattern: accept an AdapterCommand enum (EXPORT_EXPENSE, EXPORT_ACTIVITY) and dispatch to private handler methods.
This keeps the public interface clean while allowing internal routing. For error normalization, create an XledgerErrorMapper that converts HTTP status + body to AdapterError with a code, message, and retryable boolean. Use the retryable flag in the retry loop to skip retrying on 401/403.
Testing Requirements
Unit tests (flutter_test / Deno test): payload serialization for Xledger accounting entry format, OAuth2 token acquisition and refresh flow (mocked HTTP), error normalization for all Xledger error response shapes (400, 401, 403, 429, 500, 503), retry logic verifying exponential backoff timing and max-retry enforcement. Integration tests: full OAuth2 flow against Xledger sandbox, accounting entry creation with valid Blindeforbundet test data, credential isolation test verifying cross-org access is rejected. Test coverage target: 90% on adapter business logic. All tests must run in CI without live Xledger credentials (use recorded HTTP fixtures for unit tests).
Each of the five external systems (Xledger, Dynamics, Cornerstone, Consio, Bufdir) has a different authentication flow, field schema, and error format. Forcing them into a uniform adapter interface may require compromises that result in leaky abstractions or make the adapter contract too complex to maintain.
Mitigation & Contingency
Mitigation: Design the IntegrationAdapter interface with a loose invoke() payload rather than a typed one, allowing each adapter to declare its own input/output schema. Use integration type metadata in the registry to document per-adapter quirks. Build Xledger first as the most documented API, then adapt the interface based on learnings.
Contingency: If the uniform interface cannot accommodate all five systems, split into two interface tiers: a simple polling/export adapter and a richer bidirectional adapter, with the registry declaring which tier each system implements.
Development and testing of the Cornerstone and Consio adapters depends on NHF providing sandbox API access. If credentials or documentation are delayed, these adapters cannot be validated, blocking the epic's acceptance criteria.
Mitigation & Contingency
Mitigation: Implement Xledger and Dynamics adapters first (better-documented, sandbox available). Create a mock adapter for Cornerstone/Consio using recorded API responses for CI testing. Proactively request sandbox access from NHF at project kickoff.
Contingency: Ship the epic with Cornerstone/Consio adapters in a 'stub' state (connectivity test returns a simulated success, invoke() is not production-wired) and gate the NHF integration behind a feature flag until real API access is obtained.
Real-world field mappings may include nested transformations, conditional logic, and data type coercions (e.g., Norwegian date formats, currency rounding rules) that the Field Mapping Resolver's initial design does not accommodate, requiring scope expansion mid-epic.
Mitigation & Contingency
Mitigation: Gather actual field mapping examples from Blindeforbundet (Xledger) and HLF (Dynamics) before designing the resolver. Identify the most complex transformation required and ensure the resolver design handles it. Limit Phase 1 to direct field renaming and format conversion only.
Contingency: If complex transformations are required, implement a simple expression evaluator (e.g., JSONata or a custom mini-DSL) as an extension point in the resolver, delivering basic mappings first and complex ones in a follow-up task.