Load and cache org-specific threshold configuration
epic-travel-expense-registration-foundation-task-007 — Build the ExpenseThresholdConfig service that loads organisation-specific auto-approval thresholds (e.g. HLF: auto-approve under 50 km / no receipt required under 100 NOK) from Supabase at app startup. Cache values locally with a TTL so the rules are available offline. Expose a synchronous API so downstream services can check thresholds without async delays.
Acceptance Criteria
Technical Requirements
Implementation Notes
Model the threshold config as a Supabase table (e.g., `organization_expense_settings`) with columns: organization_id, max_km_auto_approval, max_amount_no_receipt, receipt_required_above_nok, auto_approval_enabled. Use a separate table rather than embedding in the organizations table to keep org data and operational config separate. For local cache, Hive is preferred over flutter_secure_storage for this use case since the data is not sensitive and Hive's synchronous read API maps cleanly to the synchronous getter requirement. Cache the raw JSON response and parse on load.
The singleton pattern in Riverpod should use `ref.keepAlive()` in a `StreamProvider` or a plain `Provider` with a `dispose` callback that cancels any background refresh timers. Expose the service via a Riverpod `Provider
Testing Requirements
Unit tests (flutter_test + mocktail): mock Supabase client and test initialize() with successful fetch, test fallback to stale cache on network failure, test ExpenseConfigUnavailableException when both network and cache unavailable. Test isAutoApprovalEligible() with boundary values (exactly at threshold, 1 NOK over, 1 km over). Test TTL logic: inject a mock clock that returns a time 25 hours after cache write to verify stale detection. Test org switching clears previous org's thresholds.
Integration test: load real thresholds from local Supabase, verify synchronous getters return correct values after initialize(). Minimum 90% line coverage on the service class.
Row-level security policies for expense claims must correctly scope data to organisation, role (peer mentor sees own claims only, coordinator sees org-wide queue), and claim status. Incorrect RLS can expose claims cross-organisation or prevent coordinators from accessing the attestation queue.
Mitigation & Contingency
Mitigation: Define RLS policies in code-reviewed migration files. Write integration tests that attempt cross-org reads with different JWT roles and assert access denial. Review with a second engineer before merging migrations.
Contingency: If RLS is misconfigured post-deployment, disable the affected policy temporarily and apply a hotfix migration within the same release window. No claim data is exposed publicly due to Supabase project-level auth requirement.
The auto-approval Edge Function is triggered server-side on expense insert. Cold-start latency or Edge Function failures can block the submission response and degrade UX, especially on mobile networks.
Mitigation & Contingency
Mitigation: Implement the auto-approval Edge Function client with a timeout and graceful fallback: if no result is received within 5 seconds, treat the claim as 'pending' and poll for the status update via Supabase Realtime. Keep the Edge Function warm with a periodic ping.
Contingency: If Edge Function reliability is unacceptable, move auto-approval evaluation to a database trigger or Postgres function as an interim measure, accepting that threshold configuration changes require a migration rather than a settings update.
The expense type catalogue and threshold configuration are cached locally for offline use. If an organisation updates their catalogue exclusion rules or thresholds while a peer mentor is offline, the local cache may allow submissions that violate the new policy.
Mitigation & Contingency
Mitigation: Cache entries include a TTL (24 hours). On connectivity restore, refresh cache before allowing new submissions. Server-side validation in the Edge Function and save functions provides a second enforcement layer.
Contingency: If a stale-cache submission passes client validation but fails server validation, surface a clear error message explaining that the expense type rules have been updated and prompt the user to review their selection with the refreshed catalogue.