high priority low complexity database pending backend specialist Tier 2

Acceptance Criteria

saveDraft(claimContextId, ExpenseSelectionDraft) serialises the selection set and amounts to a JSON string and writes it to the local store under the key 'expense_draft_{claimContextId}'
loadDraft(claimContextId) returns the deserialised ExpenseSelectionDraft if a draft exists, or null if no draft is found for that context ID
Draft keys are namespaced by claimContextId so drafts from different expense claims do not overwrite each other
If serialisation fails (malformed data), saveDraft logs the error and does NOT throw — the in-memory state is preserved even if persistence fails
If deserialisation fails on loadDraft (corrupted stored JSON), the method returns null and deletes the corrupted entry so it does not block future loads
clearDraft(claimContextId) removes the stored draft for the given context ID and succeeds silently if no draft exists
The repository implementation behind an abstract interface — the BLoC depends on the interface, not the concrete shared_preferences/Hive implementation
Draft payload includes: selectedTypeIds (List<String>), amountsByTypeId (Map<String, double>), lastModifiedAt (ISO-8601 string)
Unit tests cover: save then load round-trip, load with no stored draft returns null, load with corrupted JSON returns null and clears entry, clear removes draft

Technical Requirements

frameworks
Flutter
shared_preferences or Hive
data models
activity_type
performance requirements
Write operations must be fire-and-forget (unawaited or awaited in background) — must not block the BLoC state emission
Read on initialisation is awaited once; subsequent reads are not needed
security requirements
Draft data is stored in local app storage (not exported or synced) — use flutter_secure_storage if the draft contains amounts that could be considered sensitive financial data
Claim context ID used as key must not contain PII — use only the UUID
Draft data must be cleared after the expense claim is finalised and synced to Supabase (task-012)

Execution Context

Execution Tier
Tier 2

Tier 2 - 518 tasks

Can start after Tier 1 completes

Implementation Notes

Define an abstract ExpenseDraftRepository interface with saveDraft, loadDraft, and clearDraft methods. Implement it as SharedPreferencesDraftRepository (or HiveDraftRepository). Register the implementation in the dependency injection layer (e.g., a provider or GetIt) so it can be swapped for a fake in tests. Use jsonEncode/jsonDecode for serialisation — keep the draft model a plain Dart class with a toJson/fromJson pair.

Avoid using Hive if shared_preferences is sufficient — this is a simple key-value store. The HLF workshop requirement that 'systemene må leve side ved side' underscores that mid-flow data must survive app restarts during the rollout period when users may switch between old and new registration paths.

Testing Requirements

Unit tests with flutter_test. Mock the underlying shared_preferences or Hive adapter using a fake in-memory implementation of the repository interface. Test cases: (1) save draft → load draft returns same data, (2) load draft for unknown key returns null, (3) save invalid data → load returns null and entry is cleared, (4) clear draft → subsequent load returns null, (5) multiple claim context IDs stored independently without interference. Do not test shared_preferences or Hive internals — only test the repository contract.

Component
Expense Type Repository
data low
Epic Risks (2)
high impact medium prob dependency

The per-km reimbursement rate and transit zone amounts must be read from org-specific configuration stored in Supabase. If the rate configuration table or RLS policies are not yet deployed when this epic runs, the calculation service cannot be completed and integration tests will fail.

Mitigation & Contingency

Mitigation: Define a RateConfigRepository interface and inject a stub implementation with default HLF rates from day one; write the real Supabase adapter in parallel and swap via dependency injection before merge.

Contingency: If org rate config is delayed beyond this epic's window, ship with the default-rate stub and log a prominent warning; calculate with defaults and surface a 'rates not confirmed' notice in the UI preview.

medium impact low prob technical

If the peer mentor opens an expense claim on two devices simultaneously, the local draft and the Supabase record may diverge. The repository's last-write-wins strategy could silently overwrite a valid selection with a stale one.

Mitigation & Contingency

Mitigation: Add an updated_at timestamp to the draft record and reject saves where the server timestamp is newer than the local copy; surface a conflict resolution prompt rather than silently overwriting.

Contingency: If conflict resolution UI is out of scope, fall back to server-authoritative reads on app foreground resume and discard local draft, notifying the user that their draft was refreshed from the server.