Implement Accounting Exporter Service orchestrator
epic-accounting-system-export-foundation-task-013 — Build the AccountingExporterService that orchestrates the full export pipeline: (1) run DoubleExportGuard pre-flight check, (2) create a pending ExportRun record via ExportRunRepository, (3) fetch approved claims via ApprovedClaimsQueryService, (4) route to XledgerExporter or DynamicsExporter based on org config, (5) mark all included claims as exported (set exported_at), (6) update ExportRun status to completed with file URL and record count. Handle failures by updating ExportRun status to failed with error detail. Expose a single triggerExport(orgId, dateRange, targetSystem) method.
Acceptance Criteria
Technical Requirements
Execution Context
Tier 3 - 413 tasks
Can start after Tier 2 completes
Implementation Notes
Use a sealed class or enum for ExportStatus (PENDING, COMPLETED, FAILED, DUPLICATE, EMPTY) to make exhaustive switch handling compile-time safe in Dart. Wrap the entire pipeline from 'create ExportRun' through 'update ExportRun to completed' in a try/catch/finally where the finally block guarantees ExportRun is never left in PENDING state. The claims update and ExportRun completion update should be issued as two sequential Supabase calls inside a Postgres transaction if the Supabase client supports it; otherwise use a Postgres function called via rpc() to keep atomicity server-side. Inject a Clock abstraction rather than calling DateTime.now() directly to keep tests deterministic.
The routing logic should delegate to an AccountingExporterFactory that reads OrgAccountingConfig — keep AccountingExporterService free of any Xledger/Dynamics-specific imports. Use Dart's async/await with structured error handling (no unawaited futures). Log export start, completion, and failure at INFO level with orgId and dateRange (no claim content).
Testing Requirements
Unit tests (flutter_test / dart test): mock all five injected dependencies and verify each orchestration branch independently — happy path Xledger, happy path Dynamics, DUPLICATE guard abort, empty claims result, exporter throws exception (verify ExportRun set to failed), claims update called with correct IDs. Integration test (Supabase local emulator): seed approved claims for an org, call triggerExport, assert ExportRun row in DB has status='completed' and all seeded claims have exported_at set. Regression test: re-run triggerExport for same org/dateRange, assert DUPLICATE result and no new ExportRun row created. Test coverage gate: 90% line coverage on AccountingExporterService class.
Adding exported_at and export_run_id columns to expense_claims requires a live migration on a table shared with the approval workflow. A poorly timed migration could lock the table and block claim submissions or approvals.
Mitigation & Contingency
Mitigation: Use non-blocking ADD COLUMN with a DEFAULT of NULL (no backfill needed) executed during a low-traffic window. Test migration rollback on a staging replica before production deployment.
Contingency: If migration causes table lock contention, roll back and reschedule for a maintenance window. Use a feature flag to gate the export UI until the migration completes successfully.
Chart of accounts mapping configurations for Xledger and Dynamics may not be fully specified by stakeholders at development time, leaving the mapper with incomplete data and causing validation failures for unmapped expense categories.
Mitigation & Contingency
Mitigation: Implement the mapper to return a structured validation error (not a crash) for any unmapped field, and surface these errors clearly in the export confirmation dialog. Request full mapping tables from Blindeforbundet and HLF stakeholders as a pre-condition for this epic.
Contingency: If mappings arrive incomplete, ship the mapper with the available subset and mark unmapped categories as excluded (skipped with reason). Coordinators see which categories are skipped and can manually submit those records.
Supabase Vault configuration for storing per-org accounting credentials may require infra permissions or environment secrets not yet provisioned in staging or production, blocking development and testing of credential retrieval.
Mitigation & Contingency
Mitigation: Provision Vault secrets and environment configuration in staging as the first task of this epic. Document the exact secret naming convention and rotation procedure before implementation begins.
Contingency: If Vault is unavailable, use environment variables scoped to the Edge Function as a temporary fallback for development. Block production deployment until Vault-based storage is confirmed operational.