Implement Supabase expense record persistence and auto-approval routing
epic-travel-expense-registration-core-services-task-006 — Implement the core submission flow: after validation passes and receipt is uploaded, persist the expense record to Supabase via ExpenseRepository. Then invoke the AutoApprovalEdgeFunctionClient to evaluate whether the claim qualifies for straight-through processing (under 50 km distance with no manual-approval expenses). Parse the edge function response and update the claim status in the database accordingly. Return a SubmissionOutcome indicating either auto-approved or pending-attestation status.
Acceptance Criteria
Technical Requirements
Execution Context
Tier 4 - 323 tasks
Can start after Tier 3 completes
Implementation Notes
Use Supabase Dart client's `.rpc()` for the edge function call and `.from('expenses').insert()` for persistence. Wrap the two-step operation (insert + status update after edge function) in a Supabase RPC or database function if possible, to get atomic behaviour. If not feasible, implement an idempotent retry for the status update step. The AutoApprovalEdgeFunctionClient should be an abstract class injected via Riverpod.
The auto-approval criterion (distance < 50 km AND no manual-approval items) must be evaluated server-side in the edge function, not client-side — the client must not trust its own determination. Use `supabase.auth.currentUser!.id` for user_id; never accept user_id from the draft payload.
Testing Requirements
Unit tests: mock ExpenseRepository and AutoApprovalEdgeFunctionClient. Test (1) happy path auto-approval; (2) happy path pending-attestation; (3) edge function timeout → pending-attestation, no exception propagated; (4) DB insert failure → SubmissionError returned; (5) status update failure after successful insert → compensating action triggered. Integration tests against Supabase local emulator: verify RLS blocks cross-user reads, verify edge function sets correct status. Use flutter_test for all tests.
Mutual exclusion rules are stored in the expense type catalogue's exclusive_groups field. If the catalogue schema or group definitions differ between HLF and Blindeforbundet, the validation service must handle multiple group configurations without hardcoding organisation-specific logic.
Mitigation & Contingency
Mitigation: Design the validation service to be purely data-driven: it reads exclusive_groups from the cached catalogue and enforces whichever groups are defined, with no hardcoded organisation names. Write parameterised unit tests covering at least 4 different catalogue configurations to verify generality.
Contingency: If an organisation requires non-standard exclusion semantics (e.g. partial exclusion within a group), introduce an exclusion_type field to the catalogue schema and extend the service, treating it as a catalogue configuration change rather than a code fork.
The attestation service subscribes to Supabase Realtime for live queue updates. On mobile, Realtime WebSocket connections can be dropped during network transitions, causing the coordinator queue to become stale without the user being aware.
Mitigation & Contingency
Mitigation: Implement connection lifecycle management: reconnect on network-change events, show a 'reconnecting' indicator when the subscription is broken, and perform a full queue refresh on reconnect rather than relying solely on delta events.
Contingency: Add a manual pull-to-refresh gesture on the attestation queue screen as a guaranteed fallback. If Realtime proves unreliable in production, switch to periodic polling (30-second interval) as a degraded but functional mode.
If a peer mentor submits a draft while offline and then submits the same claim again after connectivity is restored (thinking the first attempt failed), duplicate claims may be persisted in Supabase.
Mitigation & Contingency
Mitigation: Assign a client-generated idempotency key (UUID) to each draft at creation time. The submission service sends this key as an upsert key to Supabase, preventing duplicate inserts. The draft is marked 'submitted' locally after first successful upload.
Contingency: Implement a server-side duplicate detection trigger on the expense_claims table checking (activity_id, claimant_id, created_date) within a 24-hour window and returning the existing record ID rather than inserting a duplicate.