critical priority low complexity backend pending backend specialist Tier 0

Acceptance Criteria

MileageClaim is a Dart class with final fields: id (String), peerMentorId (String), organizationId (String), distanceKm (double), routeDescription (String?), status (MileageClaimStatus enum), submittedAt (DateTime?), approvedAt (DateTime?), approvedBy (String?), createdAt (DateTime), updatedAt (DateTime)
MileageClaimStatus is a Dart enum with values: draft, submitted, approved, rejected
MileageClaim.copyWith() is implemented supporting partial field overrides for all fields
MileageClaim overrides == and hashCode based on the id field only (value-based equality by identity)
MileageClaim overrides toString() returning a debug-friendly representation without PII fields (no routeDescription in toString output)
OrgConfiguration is a Dart class with final fields: id (String), organizationId (String), ratePerKm (double), autoApprovalThresholdKm (double), receiptRequiredThresholdNok (double), createdAt (DateTime), updatedAt (DateTime)
OrgConfiguration.copyWith() is implemented supporting partial field overrides for all fields
OrgConfiguration overrides == and hashCode based on the organizationId field
Both domain model classes are in the domain layer (e.g. lib/features/mileage/domain/) and have zero dependencies on Flutter widgets, Supabase, or Riverpod
MileageClaim constructor validates distanceKm > 0 — throws AssertionError in debug mode for invalid values
OrgConfiguration constructor validates ratePerKm > 0 and autoApprovalThresholdKm >= 0 — throws AssertionError in debug mode
Unit tests cover constructor, copyWith, equality, and hashCode for both classes

Technical Requirements

frameworks
Flutter
flutter_test
data models
activity
activity_type
performance requirements
Domain model instantiation is allocation-only — no I/O or async operations
copyWith creates a new instance in under 1 microsecond
security requirements
toString() must not include routeDescription or any free-text fields that could contain PII — safe for debug logging
Domain models must not contain Supabase row IDs or internal database keys beyond the domain id field

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

Do not use code generation (freezed, json_serializable) for domain models — keeping them hand-written keeps the domain layer free of build_runner dependencies and reduces compilation time. Implement copyWith manually using named optional parameters typed as Object? with a sentinel default (e.g. static const _unset = Object()) to distinguish null-as-value from not-provided.

This pattern supports nullable field overrides in copyWith without ambiguity. The MileageClaimStatus enum values should use camelCase (draft, submitted, approved, rejected) matching Dart conventions — the adapter layer (task-002) is responsible for mapping these to/from the snake_case strings stored in Supabase. Place both classes in separate files (mileage_claim.dart, org_configuration.dart) under lib/features/mileage/domain/models/.

Testing Requirements

Write unit tests using flutter_test. Cover: (1) MileageClaim constructor creates instance with all fields set correctly; (2) MileageClaim.copyWith changes only the specified field; (3) two MileageClaim instances with the same id are == regardless of other field values; (4) two MileageClaim instances with different ids are not ==; (5) same tests for OrgConfiguration with organizationId as the equality key; (6) MileageClaim constructor throws AssertionError (in debug) when distanceKm <= 0; (7) OrgConfiguration constructor throws AssertionError when ratePerKm <= 0; (8) MileageClaimStatus enum has exactly four values matching the database constraint set. Target 100% line coverage.

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.