Integrate ReceiptCameraSheet into expense wizard flow
epic-receipt-capture-and-attachment-core-logic-task-013 — Wire ReceiptCameraSheet and ReceiptAttachmentService into the expense registration wizard. The sheet must open inline without navigating away from the form, preserving all form field state. On image selection, immediately trigger non-blocking upload via ReceiptAttachmentService and update ReceiptAttachmentIndicator. Ensure form submission succeeds even if upload is still in progress.
Acceptance Criteria
Technical Requirements
Execution Context
Tier 5 - 253 tasks
Can start after Tier 4 completes
Implementation Notes
The expense wizard state (BLoC or Riverpod Notifier) should hold a receiptAttachmentServiceId or a reference to the active ReceiptAttachmentService instance so that the ReceiptAttachmentIndicator and the submit logic share the same service instance. Do not reconstruct ReceiptAttachmentService on each build — provide it via dependency injection (Riverpod Provider or BLoC dependency). For the 'wait for upload on submit' behaviour, use await service.uploadCompleter.future inside the form submission handler — ReceiptAttachmentService should expose a Future that resolves when the current upload succeeds or fails. Use showModalBottomSheet with a Future
Keep the wizard's form key (GlobalKey
Testing Requirements
Widget integration tests: verify form fields retain values after ReceiptCameraSheet dismissal; verify ReceiptAttachmentService.startUpload is called when sheet returns a file; verify ReceiptAttachmentIndicator transitions to uploading state immediately after image selection; verify form submit button is disabled with correct error message when receipt is mandatory and not attached; verify form submit is enabled when amount is below 100 kr regardless of attachment; verify wizard submits successfully when upload completes before submission; verify wizard waits for in-progress upload before submitting. Use mocktail for ReceiptAttachmentService and flutter_test pump helpers to simulate async upload completion. Test on both iOS and Android form factor targets.
Non-blocking upload creates a race condition: if the claim record is submitted and saved before the upload completes, the storage path may never be written to the claim_receipts table, leaving the claim with a missing receipt that was nonetheless required.
Mitigation & Contingency
Mitigation: Design the attachment service to queue a completion callback that writes the storage path to the claim record upon upload completion, even after the claim form has submitted. Use a local task queue with persistence to survive app backgrounding. Test the race condition explicitly with simulated slow uploads.
Contingency: If the async path association proves unreliable, fall back to blocking upload before claim submission with a clear progress indicator, accepting the UX trade-off in exchange for data integrity.
The offline capture requirement (cache locally, sync when connected) significantly increases state management complexity. If the offline queue is not durable, receipts captured without connectivity may be lost when the app is killed, causing claim submission failures users are not aware of.
Mitigation & Contingency
Mitigation: Persist the offline upload queue to local storage (e.g., Hive or SQLite) on every state transition. Implement background sync using WorkManager (Android) and BGTaskScheduler (iOS). Scope the initial delivery to online-only flow if offline sync cannot be adequately tested before release.
Contingency: Ship without offline support in the first release, displaying a clear 'Upload requires connection' message. Add offline sync as a follow-on task once the core online flow is validated in production.
The inline bottom sheet presentation within a multi-step wizard can conflict with existing modal navigation and back-button handling, particularly if the expense wizard itself uses nested navigation or custom route management.
Mitigation & Contingency
Mitigation: Review the expense wizard navigation architecture before implementation. Use showModalBottomSheet with barrier dismissal disabled to prevent accidental dismissal. Coordinate with the expense wizard team on modal stacking behavior and ensure the camera sheet does not interfere with wizard step transitions.
Contingency: If modal stacking causes navigation issues, present the camera sheet as a full-screen dialog using PageRouteBuilder with a transparent barrier, preserving wizard state via the existing Bloc while still appearing inline.