critical priority low complexity backend pending backend specialist Tier 1

Acceptance Criteria

MileageClaimService class exists in `lib/domain/mileage_reimbursement/mileage_claim_service.dart` (or equivalent service layer path).
Constructor accepts exactly four typed parameters: `AutoApprovalEvaluator autoApprovalEvaluator`, `MileageClaimRepository claimRepository`, `DistancePrefillService distancePrefillService`, `OrgRateConfigRepository orgRateConfigRepository`. No positional parameters — use named parameters for clarity.
Public method `Future<SubmissionOutcome> submitClaim({required MileageClaimFormInput input})` is declared (body may be `throw UnimplementedError()` at this scaffold stage).
A Riverpod `Provider<MileageClaimService>` (or `riverpod` equivalent) is defined that constructs MileageClaimService by reading its four dependencies from their respective providers.
The four dependency providers (AutoApprovalEvaluatorProvider, MileageClaimRepositoryProvider, DistancePrefillServiceProvider, OrgRateConfigRepositoryProvider) are declared — stubs are acceptable if not yet fully implemented, but they must be typed and compile.
No circular provider dependencies exist — `flutter analyze` and Riverpod's own lint rules pass without errors.
A unit test verifies that MileageClaimService can be instantiated with mock implementations of all four dependencies using `ProviderContainer` or direct constructor injection.
MileageClaimFormInput is a separate input DTO (not the MileageClaim domain object) carrying raw form values before assembly.

Technical Requirements

frameworks
Flutter
Riverpod
Dart
data models
MileageClaim
MileageClaimFormInput
SubmissionOutcome
OrgRateConfig
performance requirements
Service instantiation must be lazy (Riverpod default) — not created until first read.
security requirements
MileageClaimService must not hold any mutable state between calls — it is a stateless orchestrator. All state lives in repositories.
The Riverpod provider must not expose internal repository references to the widget layer.

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Keep MileageClaimFormInput as a simple data class (no business logic) — it is the raw UI-to-service boundary object. MileageClaim (from task-001) is the fully-assembled domain object. This distinction is important: the service scaffold accepts raw form input, assembles the domain object internally in task-003, and persists it in task-005. Define MileageClaimFormInput in the same domain folder as MileageClaim.

For the Riverpod provider, use `Provider` (not `StateNotifierProvider` or `FutureProvider`) because MileageClaimService is a stateless service — it has no state of its own. Place the provider definition in a dedicated `mileage_claim_providers.dart` file to keep provider declarations co-located and easy to discover. Ensure the provider file imports all four dependency providers — if any don't exist yet, create stub providers that `throw UnimplementedError()` so the graph compiles.

Testing Requirements

Unit tests using `flutter_test` and Riverpod's `ProviderContainer` for DI testing. Required test cases: (1) MileageClaimService constructs successfully with all four mocked dependencies; (2) calling `submitClaim()` on the scaffold throws `UnimplementedError` (verifying the stub is correctly wired, not silently returning null); (3) the Riverpod provider graph resolves MileageClaimService without throwing — use `ProviderContainer` with overrides for all four dependencies. Use `Mockito` or `mocktail` (whichever is in the project's `pubspec.yaml`) for mock generation.

Component
Mileage Claim Service
service medium
Epic Risks (2)
high impact medium prob integration

The auto-approval rule requires checking whether any additional expense lines are attached to the claim. The interface between the mileage claim and any co-submitted expense items is not fully defined within this feature's component scope. If the domain model does not include an explicit additionalExpenses collection, the evaluator cannot make a correct determination, which could auto-approve claims that should require manual review.

Mitigation & Contingency

Mitigation: Define the MileageClaim domain object interface with an explicit additionalExpenses: List field (nullable/empty for mileage-only claims) before implementing the service. Coordinate with the Expense Type Selection feature team to agree on the shared domain contract.

Contingency: If the cross-feature contract cannot be finalised before implementation, implement the evaluator to treat any non-null additionalExpenses list as requiring manual review and document the assumption for review during integration testing.

medium impact medium prob technical

A peer mentor who taps the submit button multiple times rapidly (e.g. due to slow network) could cause MileageClaimService to be invoked concurrently, resulting in duplicate claim records being persisted with the same trip data.

Mitigation & Contingency

Mitigation: Implement a submission-in-progress guard in MileageClaimService using a BLoC/Cubit state flag that prevents re-entrant calls. The UI layer (implemented in Epic 4) will also disable the submit button during processing.

Contingency: Add a Supabase-level unique constraint or idempotency key on (user_id, origin, distance, submitted_at truncated to minute) to prevent duplicate rows reaching the database even if the application guard fails.