critical priority medium complexity backend pending backend specialist Tier 1

Acceptance Criteria

SupabaseMileageClaimRepository implements MileageClaimRepository and is registered only via its interface
insert() maps MileageClaim to a Supabase row map and calls supabase.from('mileage_claims').insert(); initial status is always 'pending' regardless of the passed entity status
fetchByUser() queries with .eq('user_id', userId).is_('deleted_at', null).order('activity_date', ascending: false).range(offset, offset+pageSize-1)
fetchForCoordinator() queries with .eq('org_id', orgId).is_('deleted_at', null) and conditionally appends .eq('status', statusFilter.name) when a filter is provided
updateStatus() calls .update({'status': status.name, 'updated_at': DateTime.now().toIso8601String()}).eq('claim_id', claimId)
softDelete() first fetches the claim to verify correctionWindowEnd has not passed; if expired, throws CorrectionWindowExpiredException; otherwise sets deleted_at to DateTime.now()
All Supabase calls are wrapped in try/catch; PostgrestException is mapped to typed domain exceptions
Row-level security is relied upon — no manual org_id injection beyond what the auth session provides, except for coordinator queries where orgId is explicitly passed
fromJson() mapper correctly handles null deletedAt and parses ISO8601 timestamps to DateTime
Pagination returns an empty list (not an error) when the requested page is beyond available records

Technical Requirements

frameworks
Dart (latest)
Flutter
Supabase
Riverpod
apis
Supabase PostgREST REST API
Supabase Auth (session context for RLS)
data models
MileageClaim
MileageClaimStatus
performance requirements
fetchByUser must use server-side pagination (.range()) — never fetch all rows client-side
fetchForCoordinator must apply status filter server-side, not in Dart
Single Supabase round-trip per operation (no N+1 queries)
security requirements
Never bypass RLS by using the service role key — use the authenticated user session
Coordinator org_id must come from a trusted source (auth token claim or verified session), not from unvalidated client input
softDelete correction window check must be evaluated server-side or validated with a trusted server timestamp, not client DateTime.now() alone if security is critical

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Place the concrete class in lib/data/mileage_claim/supabase_mileage_claim_repository.dart. Create a private _fromJson(Map json) factory method on the repository (not on the domain model) to keep infrastructure concerns in the data layer. Use Supabase's .select() with explicit column names rather than .select('*') to avoid fetching unused columns and to make the contract explicit. The correction window check: fetch the claim first, compare correctionWindowEnd (stored on the claim) against DateTime.now().toUtc(); throw CorrectionWindowExpiredException if past.

Consider wrapping softDelete in a Supabase RPC call to make the window check atomic on the server side. Map Supabase 401/403 responses to an AuthorizationException in the domain layer.

Testing Requirements

Unit tests (flutter_test) using a fake/mock Supabase adapter: test insert maps fields correctly including forced 'pending' status; test fetchByUser passes correct query parameters and maps response to List; test fetchForCoordinator applies status filter only when non-null; test updateStatus sends correct patch body; test softDelete throws CorrectionWindowExpiredException when window is expired; test softDelete succeeds when window is still open; test that PostgrestException is mapped to a domain exception. Integration tests against a local Supabase instance (optional but recommended) covering RLS enforcement.

Component
Mileage Claim Repository
data medium
Epic Risks (2)
medium impact medium prob integration

If OrgRateConfigRepository caches the per-km rate aggressively and an admin updates the rate mid-session, ongoing form interactions will show the old rate until the Stream emits. This could result in the UI showing a rate that differs from what is stored when the claim is submitted, causing confusion or disputes.

Mitigation & Contingency

Mitigation: Subscribe to a Supabase Realtime channel for the org_configuration table so config changes propagate within seconds. Document the eventual-consistency window in code comments.

Contingency: If Realtime subscription proves unreliable in test, add a polling fallback with a configurable interval (default 5 minutes) and display a 'rate updated' toast when the stream emits a changed value.

medium impact medium prob scope

The correction window within which a claim can be deleted or voided is not explicitly specified in the feature documentation. Implementing the wrong window (e.g. 24 hours vs 7 days) could lock users out of corrections or allow inappropriate post-approval modifications.

Mitigation & Contingency

Mitigation: Raise the correction window definition as a blocking question to the HLF product owner before implementing the delete/void path in MileageClaimRepository. Implement the window duration as an org-level configuration value rather than a hardcoded constant.

Contingency: If the question cannot be resolved before implementation, default to 24 hours as the most conservative option and flag the value for review in the first user-acceptance test.