critical priority medium complexity backend pending backend specialist Tier 2

Acceptance Criteria

checkForOverlap(orgId, targetSystem, DateRange) returns Future<OverlapCheckResult> — either OverlapCheckResult.clear() or OverlapCheckResult.conflict(ExportRun conflictingRun)
A completed export run [A_start, A_end] conflicts with a requested range [B_start, B_end] if and only if B_start <= A_end AND B_end >= A_start (standard interval overlap)
Overlap check is scoped to the same organization_id AND the same target_system — an Xledger run does NOT block a Dynamics run for the same date range
Only export_runs with status = 'completed' are considered blocking — 'pending', 'failed', and 'cancelled' runs are ignored
The conflict result includes the conflicting run's id, date_range, completed_at timestamp, and record_count for display in the UI error message
If multiple conflicts exist, the result contains the most recent conflicting run (latest completed_at)
Service throws an ExportGuardException (typed, not generic Exception) when the Supabase query itself fails, with the original error wrapped
Unit tests pass for: no existing runs (clear), exact same range (conflict), partial overlap start (conflict), partial overlap end (conflict), adjacent non-overlapping ranges (clear), same range different target system (clear), same range failed status run (clear)
Service is implemented as a plain Dart class injected by interface — no static methods — for testability

Technical Requirements

frameworks
Flutter
Riverpod
flutter_test
apis
Supabase PostgreSQL — export_runs table query with date range overlap filter
data models
annual_summary
performance requirements
Overlap query executes in under 200ms for orgs with up to 1000 export run records
Use a single SQL query with date range overlap condition — no client-side filtering of full table scan
security requirements
Query must be scoped by organization_id from the authenticated user's JWT claims — never accept orgId from client payload directly
Row-Level Security on export_runs table must be enforced — service role key used server-side only
Service must not expose details of other organizations' export runs in error messages

Execution Context

Execution Tier
Tier 2

Tier 2 - 518 tasks

Can start after Tier 1 completes

Implementation Notes

Implement DoubleExportGuard as a class with a single public method: Future checkForOverlap(String orgId, ExportTargetSystem targetSystem, DateRange range). Internally call ExportRunRepository.getCompletedRunsForOrg(orgId, targetSystem) which returns a List. Perform overlap detection in pure Dart (no async) using the standard interval overlap formula: rangeA.start.isBefore(rangeB.end) && rangeA.end.isAfter(rangeB.start). Keep date comparisons exclusive on neither end — use inclusive start/end (isBeforeOrEqual / isAfterOrEqual helpers).

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.

Component
Export Run Repository
data medium
Epic Risks (3)
high impact medium prob technical

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.

medium impact high prob scope

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.

medium impact medium prob dependency

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.