critical priority low complexity backend pending backend specialist Tier 0

Acceptance Criteria

MileageClaim is an immutable Dart class (use `final` fields, provide a `copyWith` method) with fields: `distanceKm` (double), `route` (String), `orgRateConfigSnapshot` (OrgRateConfig), `hasAdditionalExpenses` (bool), `expenseFlags` (List<ExpenseFlag> or equivalent typed collection), `submitterId` (String — authenticated user ID), `submittedAt` (DateTime, UTC).
ClaimStatus enum has exactly two values: `silentAutoApproved` and `pendingReview`. No other values are permitted without a schema migration.
SubmissionOutcome is a sealed class (or equivalent discriminated union in Dart) with two subtypes: `SubmissionSuccess` carrying `claimId` (String) and `resolvedStatus` (ClaimStatus), and `SubmissionFailure` carrying `error` (String) and optionally `cause` (Exception?).
All fields in MileageClaim are non-nullable; there are no dynamic or Object-typed fields.
OrgRateConfigSnapshot stored on MileageClaim is a value-object copy of the org config at submission time, not a live reference — ensuring historical immutability.
Unit tests exist for SubmissionOutcome pattern matching: a test that handles both `SubmissionSuccess` and `SubmissionFailure` branches without a default case, proving the sealed class is exhaustive.
Files are placed in `lib/domain/mileage_reimbursement/` (or equivalent domain layer path) — not in the UI or data layers.
`flutter analyze` reports zero issues on the new files.

Technical Requirements

frameworks
Flutter
Dart
data models
MileageClaim
ClaimStatus
SubmissionOutcome
OrgRateConfig
ExpenseFlag
performance requirements
Domain objects are pure Dart — no async operations, no I/O. Instantiation must be synchronous and effectively instantaneous.
security requirements
submitterId must be derived from the authenticated session — never accepted as a raw user-supplied string from a form field.
OrgRateConfigSnapshot must be a deep copy to prevent mutation after submission.

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

Use Dart's `sealed class` keyword (available since Dart 3.0) for SubmissionOutcome to get exhaustive pattern matching enforced by the compiler. For MileageClaim immutability, either hand-write `copyWith` or use a code generation package already in the project (e.g., `freezed` if present — check `pubspec.yaml` before adding dependencies). The `orgRateConfigSnapshot` field should store a snapshot value object, not the live OrgRateConfigRepository entity; this is critical for audit trails and the HLF auto-approval threshold logic which must use the rate config valid at submission time, not a later value. ExpenseFlag should be an enum covering the fixed HLF-style expense types (e.g., `tollRoad`, `parking`, `publicTransport`) to make feilkombinasjon enforcement possible at the type level.

Avoid `dynamic` and `Map` in domain objects — these are a domain layer, not a DTO layer.

Testing Requirements

Unit tests using `flutter_test`. Required test cases: (1) MileageClaim can be constructed with all valid fields; (2) `copyWith` produces a new instance with only the specified field changed; (3) SubmissionSuccess carries correct claimId and status; (4) SubmissionFailure carries the provided error string; (5) exhaustive pattern match on SubmissionOutcome compiles without a wildcard — verifying the sealed class is complete. No integration or widget tests required at this layer.

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.