critical priority medium complexity backend pending backend specialist Tier 1

Acceptance Criteria

SupabaseMileageAdapter exposes toJson(MileageClaim) → Map<String, dynamic> and fromJson(Map<String, dynamic>) → MileageClaim as static or instance methods
SupabaseMileageAdapter exposes toJson(OrgConfiguration) → Map<String, dynamic> and fromJson(Map<String, dynamic>) → OrgConfiguration for the org config entity
All non-nullable domain fields map to non-nullable JSON keys; missing required keys throw a descriptive MappingException (not a generic Null check failed)
Nullable domain fields (e.g. approved_at, approved_by) are handled with explicit null checks — no use of the ! operator on potentially null values
DateTime fields are serialised to ISO 8601 UTC strings and deserialised back without timezone offset loss
Numeric fields (distance_km, rate_per_km, auto_approval_threshold_km) are deserialized as Dart double — no silent int-to-double truncation
status enum field is validated against the allowed set ('draft', 'submitted', 'approved', 'rejected') during deserialization — invalid values throw a descriptive error
Round-trip test: fromJson(toJson(claim)) produces a value-equal MileageClaim instance
Round-trip test: fromJson(toJson(config)) produces a value-equal OrgConfiguration instance
Adapter does not import any Flutter widgets or Riverpod — it is a pure Dart class with no framework dependencies
All public methods are covered by unit tests in the flutter_test suite

Technical Requirements

frameworks
Flutter
Riverpod
flutter_test
apis
Supabase PostgreSQL 15
data models
activity
activity_type
performance requirements
Serialisation of a single MileageClaim completes in under 1ms (pure Dart, no I/O)
Deserialisation of a batch of 100 MileageClaim records completes in under 50ms
security requirements
Adapter must not log field values — avoid print() or debugPrint() with domain object contents to prevent PII leakage in logs
MappingException messages must not include raw field values that could contain PII (e.g. route_description) — include field name only

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Keep the adapter as a pure Dart class in the infrastructure layer (e.g. lib/features/mileage/infrastructure/supabase_mileage_adapter.dart). Do not use code generation (json_serializable) for this adapter — hand-written mapping gives explicit control over null handling and validation which is critical here. Define a MappingException class (or reuse an existing domain exception) with a final String field and message.

For the status enum, prefer a switch expression with an exhaustive default clause that throws rather than returning a fallback value — silent fallbacks mask data integrity issues. Document the field name mapping table (Dart name → Supabase column name) in a comment block at the top of the class for future maintainers.

Testing Requirements

Write unit tests using flutter_test (no Supabase connection required). Cover: (1) happy-path round-trip for MileageClaim with all fields populated; (2) happy-path round-trip for MileageClaim with all nullable fields set to null; (3) happy-path round-trip for OrgConfiguration; (4) fromJson throws MappingException when a required field is missing; (5) fromJson throws MappingException when status has an unrecognised value; (6) DateTime round-trip preserves UTC timezone; (7) numeric fields deserialise as double when Supabase returns them as int (common with integer columns). Aim for 100% line coverage of the adapter class.

Component
Supabase Mileage Adapter
infrastructure low
Epic Risks (3)
high impact medium prob security

Supabase Row Level Security policies for mileage_claims may require complex join conditions to distinguish peer mentor (own claims only) from coordinator (chapter-scoped claims) access. If the RLS policy is misconfigured, coordinators could see claims outside their chapter scope or peer mentors could read other users' data, causing a data privacy incident.

Mitigation & Contingency

Mitigation: Write RLS policy SQL as part of this epic with explicit test cases for each role. Use Supabase's built-in policy testing tools and add integration tests that assert cross-user data isolation before merging.

Contingency: If RLS configuration proves too complex to test reliably within the epic, add an application-layer guard in the adapter that filters query results by authenticated user ID as a defence-in-depth measure while the policy is corrected.

medium impact low prob scope

Norwegian tax authority reimbursement rounding rules may change or may not be publicly documented in machine-readable form. Using the wrong rounding convention could cause systematic over- or under-payment, leading to compliance issues for the organisation.

Mitigation & Contingency

Mitigation: Source the exact rounding specification from HLF's finance team before implementing MileageCalculationService. Document the rule as a comment in the source code and link to the authoritative reference.

Contingency: If the rule is ambiguous, implement both truncation and half-up rounding behind a configuration flag so the organisation can switch without a code release.

low impact low prob technical

SharedPreferences reads and writes are asynchronous. If the distance prefill service (built in the next epic) calls LocalDistanceCache concurrently with a post-submission write, a race condition could result in the cache returning a stale or partially written value, causing the next form load to show an incorrect default.

Mitigation & Contingency

Mitigation: Wrap all SharedPreferences access in LocalDistanceCache with sequential async operations and document the non-concurrent usage contract in the class API.

Contingency: If race conditions are observed in testing, introduce a simple mutex pattern using a Completer to serialise cache operations.