critical priority medium complexity backend pending backend specialist Tier 1

Acceptance Criteria

org_account_mappings table exists in Supabase (or is created as part of this task's migration) with columns: mapping_id, org_id, target_system, expense_type, account_code, is_default (boolean)
ChartOfAccountsMapper class has mapExpenseTypeToAccountCode(orgId, expenseType, targetSystem) returning String (account code)
When an explicit mapping exists for the org+expenseType+targetSystem combination, that account code is returned
When no explicit mapping exists, the method falls back to the row where is_default = true for that org+targetSystem
When neither explicit nor default mapping exists, the method returns a hardcoded system-level fallback code ('9999' or configurable constant) and logs a warning
Mappings are loaded once per export run and cached in-memory (not re-queried per claim) — a loadMappings(orgId, targetSystem) call primes the cache
mapExpenseTypeToAccountCode is synchronous after loadMappings has been awaited
Mapper supports Xledger and Dynamics as separate target system profiles — same expense_type can map to different codes per system
A coordinator-facing admin UI placeholder or seed data mechanism exists for managing mappings (at minimum, seed data in migration for known expense types)

Technical Requirements

frameworks
Flutter
Riverpod
supabase_flutter
apis
Supabase PostgREST for org_account_mappings table
data models
org_account_mappings
expense_claims (expense_type values)
performance requirements
loadMappings must fetch all mappings for an org+system in a single query — never query per-claim
In-memory cache keyed by orgId+targetSystem prevents redundant Supabase round trips within a single export run
security requirements
RLS on org_account_mappings must restrict to coordinator and org_admin roles for the authenticated org
Account codes must not be modifiable by peer mentors — ensure INSERT/UPDATE RLS policy is restricted
Default account code fallback must be defined server-side, not client-side, to prevent manipulation

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Place in lib/features/accounting/data/services/chart_of_accounts_mapper.dart. The in-memory cache can be a simple Map> keyed by expense_type with a separate default entry. Implement a MappingProfile inner class or a flat Map where keys are expense_type values. The design decision between a Supabase table vs.

JSON file in Storage: prefer a table for editability by coordinators; use Storage JSON only if account code management is done entirely by Norse Digital Products engineers. Coordinate with HLF and Blindeforbundet accounting teams to gather the initial account code mappings for Xledger and Dynamics before seeding data.

Testing Requirements

Unit tests: Test loadMappings populates internal cache correctly from mocked Supabase response. Test mapExpenseTypeToAccountCode with explicit match — returns correct code. Test with no explicit match but default exists — returns default code. Test with no match and no default — returns fallback constant and emits warning log.

Test that calling mapExpenseTypeToAccountCode before loadMappings throws a StateError with a clear message. Test Xledger vs Dynamics return different codes for the same expense_type. Aim for 100% branch coverage on the mapping resolution logic.

Component
Chart of Accounts Mapper
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.