Implement cache update via DistancePrefillService with graceful degradation
epic-mileage-reimbursement-entry-claim-orchestration-task-006 — After successful persistence, invoke DistancePrefillService.updatePrefill() with the submitted route and distance so subsequent form loads benefit from the most recent data. Wrap this call in a try-catch: if the cache update fails, log the error but still return a successful SubmissionOutcome to the caller. Persistence success must not be contingent on cache update success.
Acceptance Criteria
Technical Requirements
Execution Context
Tier 5 - 253 tasks
Can start after Tier 4 completes
Implementation Notes
Place the cache update call immediately after the persistence await, inside a dedicated try-catch that does not share scope with the persistence try-catch. Pattern: `try { await _distancePrefillService.updatePrefill(route: claim.route, distance: claim.distanceKm); } catch (e, stackTrace) { _logger.warning('Cache update failed for claim ${claim.id}', e, stackTrace); }` — then fall through to return the success SubmissionOutcome unconditionally. Avoid using `unawaited()` here as it makes it harder to test and reason about ordering. The key architectural constraint is that the cache is a best-effort side effect, not a requirement for correctness.
Ensure DistancePrefillService is injected (not constructed inline) so tests can swap in a mock.
Testing Requirements
Unit tests (covered in task-007) must verify the try-catch isolation. For this task, manually verify with a mock DistancePrefillService that throws: (1) a generic Exception, (2) a network-specific error, and (3) a null/unexpected state. In all three cases assert that the returned SubmissionOutcome is a success type. Also verify the log call is made with the correct arguments using a mock logger.
No integration tests required at this task level — those are handled in task-008.
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.
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.