high priority low complexity backend pending backend specialist Tier 2

Acceptance Criteria

A `orgRateConfigRepositoryProvider` of type `Provider<OrgRateConfigRepository>` is declared, providing the concrete SupabaseOrgRateConfigRepository instance
A `orgRateConfigProvider` of type `StreamProvider<OrgRateConfigResult>` (or `StreamProvider.family<OrgRateConfigResult, String>` if orgId is passed as parameter) is declared, reading from orgRateConfigRepositoryProvider
The StreamProvider is scoped to the active orgId: when the user switches organisation, the provider is invalidated and re-created for the new orgId, triggering a fresh stream subscription
The provider's `ref.onDispose()` callback calls `repository.dispose()` (or equivalent cleanup) to close the BehaviorSubject and cancel Supabase subscriptions
All provider declarations are in a dedicated file: lib/providers/mileage/org_rate_config_providers.dart (or equivalent providers layer path)
Consumers can watch the stream with `ref.watch(orgRateConfigProvider)` and receive AsyncValue<OrgRateConfigResult> with loading/data/error states
Provider compiles and is accessible from any widget or service that has access to WidgetRef or Ref
A test using ProviderContainer verifies the provider emits at least one value from a mock repository without throwing

Technical Requirements

frameworks
Flutter
Dart
Riverpod
apis
OrgRateConfigRepository.watchConfig(orgId)
Riverpod StreamProvider / StreamProvider.family
data models
OrgRateConfig
OrgRateConfigResult
performance requirements
Provider initialization must be lazy (not eagerly evaluated at app startup)
Org switch invalidation must trigger within one frame of the org context change
security requirements
The orgId used to scope the provider must come from the authenticated session provider — not from untrusted user input or route parameters

Execution Context

Execution Tier
Tier 2

Tier 2 - 518 tasks

Can start after Tier 1 completes

Implementation Notes

Use `StreamProvider.autoDispose.family` if the orgId varies per session, or `StreamProvider.autoDispose` reading the orgId from a separate `currentOrgIdProvider`. The `autoDispose` modifier ensures the stream subscription is cancelled when no widget is watching, preventing unnecessary Supabase traffic when the user navigates away from mileage screens. Example structure: `final orgRateConfigProvider = StreamProvider.autoDispose((ref) { final orgId = ref.watch(currentOrgIdProvider); final repository = ref.watch(orgRateConfigRepositoryProvider); ref.onDispose(() => repository.disposeForOrg(orgId)); return repository.watchConfig(orgId: orgId); });`. If the project uses Riverpod code generation (`@riverpod` annotation), prefer the generated approach for consistency with other providers in the codebase.

Ensure the provider file exports are included in the barrel export if the project uses one.

Testing Requirements

Write a Riverpod provider test using ProviderContainer: (1) override orgRateConfigRepositoryProvider with a mock repository that emits a known OrgRateConfigLoaded value, (2) await the StreamProvider and assert it returns AsyncData wrapping the expected result, (3) verify that calling container.invalidate(orgRateConfigProvider) followed by re-reading re-subscribes to the mock repository's stream. Use flutter_test with ProviderContainer directly (no widget tree needed). Place tests in test/providers/mileage/org_rate_config_providers_test.dart.

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.