critical priority low complexity backend pending backend specialist Tier 1

Acceptance Criteria

requiresReceipt(double amountNok, String orgId) returns true when amountNok strictly exceeds the org threshold
requiresReceipt returns false when amountNok is strictly below the org threshold
requiresReceipt returns true when amountNok equals the threshold (boundary: at-or-above requires receipt)
When org config is unavailable offline, the validator falls back to the cached default threshold of 100 NOK
When no cached config exists at all, the validator defaults to 100 NOK (safe default — require receipt)
The validator is exposed as a Riverpod Provider or StateNotifierProvider and can be overridden in tests
The service reads threshold config via the loader introduced in task-011 with no direct Supabase calls inside the validator itself
The validator does not throw under any input condition — all error paths return a bool
The service is stateless; successive calls with the same orgId produce consistent results given the same cached config
All public API surface is documented with Dart doc comments

Technical Requirements

frameworks
Flutter
Riverpod
data models
OrgReceiptConfig
OrgThresholdConfig
performance requirements
requiresReceipt must complete synchronously in under 1ms — no async operations in the hot path
Config lookup from cache must not trigger a network call
security requirements
Threshold default must be conservative (require receipt) to prevent accidental bypass of financial controls
OrgId must be validated as non-empty before cache lookup to prevent config bleed between orgs

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Create a pure Dart class ReceiptThresholdValidator with a single public method requiresReceipt(double amountNok, String orgId). Inject the config loader (from task-011) via constructor or Riverpod ref — prefer constructor injection for testability. Use >= for the comparison so the boundary value (exactly at threshold) requires a receipt, matching HLF's stated requirement of 'receipt for expenses over 100 NOK' (100 itself should require one). Expose via a riverpod Provider at the top of the feature's providers file.

Keep the class in lib/features/receipts/domain/receipt_threshold_validator.dart. Do not add any Flutter imports — this is pure Dart business logic.

Testing Requirements

Unit tests are covered in task-013. For this implementation task, verify manually that the Riverpod provider tree compiles without circular dependencies, that the provider can be overridden with ProviderScope in widget tests, and that the synchronous code path is exercised via a quick smoke test before committing.

Component
Receipt Threshold Validator
service low
Epic Risks (3)
high impact medium prob security

Supabase Storage RLS policies using org/user/claim path scoping may not enforce correctly if claim ownership is not present in the JWT or if path segments are constructed differently at upload vs. read time, leading to data leakage or access denial for legitimate users.

Mitigation & Contingency

Mitigation: Define and test RLS policies in isolation before wiring to app code. Write integration tests that assert cross-org and cross-user access is denied. Use service-role key only in edge functions, never in client code.

Contingency: If client-side RLS proves insufficient, route all storage reads through a Supabase Edge Function that validates ownership before generating signed URLs, adding a controlled server-side enforcement layer.

high impact medium prob technical

Aggressive image compression may reduce receipt legibility below the threshold required for financial auditing, causing claim rejections or compliance failures despite technically successful uploads.

Mitigation & Contingency

Mitigation: Define minimum legibility requirements with HLF finance team before implementation. Set compression targets conservatively (e.g., max 1MB, min 80% JPEG quality) and validate with sample receipt images. Provide compression statistics in verbose/debug mode.

Contingency: If post-compression quality is disputed by auditors, increase the quality floor at the cost of larger file sizes, and add a manual override allowing users to skip compression for PDFs and high-quality scans.

medium impact medium prob dependency

The Flutter image_picker package behaves differently on iOS 17+ (PHPicker) vs older Android (Intent-based), particularly for file types, permission flows, and PDF selection, which may cause platform-specific failures not caught in development.

Mitigation & Contingency

Mitigation: Test image picker integration on physical devices for both platforms early in the sprint. Pin the image_picker package version and review changelogs before updates. Write widget tests using mock file results for each platform branch.

Contingency: If PHPicker or Android Intent differences cause blocking issues, implement separate platform-specific picker delegates behind the unified interface, allowing platform-specific fixes without breaking the shared API.