Implement MileageCalculationService with Norwegian rounding
epic-mileage-reimbursement-entry-infrastructure-task-006 — Implement the stateless MileageCalculationService that computes reimbursement amounts from distance (km) and organisation rate (NOK/km). Apply Norwegian rounding rules (round half up to nearest øre). Expose a synchronous calculate(distance, ratePerKm) API suitable for real-time widget-layer invocation.
Acceptance Criteria
Technical Requirements
Execution Context
Tier 1 - 540 tasks
Can start after Tier 0 completes
Implementation Notes
Norwegian rounding ('avrunding til nærmeste øre') uses round-half-up semantics, which Dart's built-in double arithmetic does NOT guarantee due to floating-point representation. Implement using integer arithmetic: convert both operands to integers in units of 1/100 øre (i.e., multiply by 10000), perform multiplication as int, then round the last two digits using integer division with remainder check ((remainder >= 5) → round up). Return (intResult / 100.0). This avoids all floating-point drift.
Place the class in lib/features/mileage/domain/services/mileage_calculation_service.dart. Make the class final and the constructor const. No dependency injection is needed — callers instantiate directly or the BLoC/Riverpod layer holds a single instance.
Testing Requirements
Unit tests only (flutter_test). Tests must be deterministic and pass without any network, platform channel, or SharedPreferences access. Minimum coverage: 100% of the calculate() method branches. Test suite must run in < 500 ms.
See task-008 for the full test matrix.
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.
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.
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.