Implement BulkApprovalProcessor service core
epic-expense-approval-workflow-bulk-and-action-task-002 — Build the BulkApprovalProcessor service that accepts a list of claim IDs and approval decision, iterates each record by delegating to ApprovalWorkflowService, wraps each call in per-record try/catch for error isolation, accumulates results, and returns a BulkApprovalResult. Successful approvals must not be rolled back when individual records fail.
Acceptance Criteria
Technical Requirements
Execution Context
Tier 1 - 540 tasks
Can start after Tier 0 completes
Implementation Notes
The core pattern is: iterate sequentially (or with bounded parallelism), wrap each delegation call in try/catch, accumulate into a mutable result builder, return immutable result at end. Start with sequential processing (simpler, easier to reason about) and only switch to Future.wait if performance testing shows it's needed — the coordinator use case typically involves < 100 claims. Map Supabase PostgreSQL error codes to BulkApprovalErrorCode in a dedicated mapper function (e.g., _mapSupabaseError) to keep the processor clean. Use Stopwatch for processingDurationMs.
Do not use Riverpod state inside the service itself — it should be a plain Dart class registered as a Riverpod provider from outside.
Testing Requirements
Unit tests (with mocked ApprovalWorkflowService): (1) all-success batch returns correct successCount, (2) all-failure batch returns correct failureCount with no successful IDs, (3) mixed batch — 3 succeed, 2 fail — returns accurate split, (4) exception thrown by ApprovalWorkflowService is caught and does not propagate, (5) empty input returns zero-count result without calling service, (6) duplicate ID detection. Integration tests against Supabase test project: (1) approve a batch of 3 pending claims and verify claim_event rows created for each, (2) batch including a non-existent claim ID produces partial failure result. Place unit tests in test/domain/services/expense/ and integration tests in test/integration/expense/.
If a bulk approval batch partially fails (some claims approved, some failed), the UI must communicate which specific claims failed without overwhelming the coordinator. A poorly designed error display could cause coordinators to re-approve already-approved claims or miss claims that still need attention.
Mitigation & Contingency
Mitigation: Design the BulkApprovalResult display to show a clear summary (e.g., '14 approved, 2 failed') with a collapsible list of failed claims including their IDs and submitter names. Failed claims should remain selected in the queue so the coordinator can retry them individually.
Contingency: If the summary UI proves insufficient, add a dedicated 'bulk action history' sheet showing the last bulk operation result, accessible from the queue screen header.
If the app is backgrounded or the network drops while the coordinator has the ApprovalActionSheet open mid-decision, the typed comment could be lost and the transition state could be ambiguous, potentially causing a coordinator to believe they approved a claim that was never submitted.
Mitigation & Contingency
Mitigation: Persist the in-progress action sheet state (selected action + comment text) to a local draft store keyed on claim ID. On sheet re-open for the same claim, restore the draft. After confirmed submission, verify the resulting claim status from the server before dismissing the sheet.
Contingency: On network error during submission, display a persistent retry banner within the sheet rather than dismissing it, so the coordinator can resubmit without re-entering their comment.