Implement Double-Export Guard service
epic-accounting-system-export-foundation-task-009 — Build the DoubleExportGuard service that enforces the idempotency invariant: no approved claim may be included in more than one successful export run. Before any export proceeds, query export_runs for completed runs within an overlapping date range for the same org and target system. If overlap detected, surface a descriptive error with details of the conflicting run. Implement as a pre-flight check callable from the Accounting Exporter Service. Write unit tests covering overlap, no-overlap, and partial-overlap scenarios.
Acceptance Criteria
Technical Requirements
Execution Context
Tier 2 - 518 tasks
Can start after Tier 1 completes
Implementation Notes
Implement DoubleExportGuard as a class with a single public method: Future
Return OverlapCheckResult as a sealed class (Dart 3) with two variants: OverlapCheckResult.clear() and OverlapCheckResult.conflict(ExportRun). Register via Riverpod provider: final doubleExportGuardProvider = Provider((ref) => DoubleExportGuard(ref.watch(exportRunRepositoryProvider))). The Accounting Exporter Service should await this check before calling any exporter and throw if conflict detected.
Testing Requirements
Unit tests (flutter_test with mocked SupabaseClient): create a MockExportRunRepository returning controlled lists of ExportRun objects; test all 7 overlap/non-overlap scenarios listed in acceptance criteria. Integration test (against local Supabase or test schema): insert rows into export_runs, call service, verify correct conflict detection. No widget or golden tests. Test file: test/services/double_export_guard_test.dart.
Use mockito or mocktail for mocking. Aim for 100% branch coverage on the overlap logic.
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.