critical priority medium complexity backend pending backend specialist Tier 1

Acceptance Criteria

BulkApprovalProcessor.process(List<String> claimIds, ApprovalDecision decision) returns Future<BulkApprovalResult>
Each claim is processed independently — a thrown exception on claim N does not prevent processing of claim N+1
ApprovalDecision enum supports at minimum: approve, reject
Each successful approval is appended to successfulClaimIds in BulkApprovalResult
Each failed approval is appended to failedRecords with correctly mapped BulkApprovalErrorCode
No database transaction wraps the entire batch — individual claim transactions only
Empty claimIds list returns BulkApprovalResult with totalProcessed=0 immediately without calling ApprovalWorkflowService
Duplicate claimIds in input are detected and reported as a single BulkApprovalFailure with errorCode: duplicateId
Network errors from Supabase are caught and mapped to BulkApprovalErrorCode.networkError with isRetryable=true
processingDurationMs in result accurately reflects wall-clock time of the batch operation
Service is injectable and depends on ApprovalWorkflowService abstraction (interface), not concrete implementation

Technical Requirements

frameworks
Dart
Riverpod
apis
Supabase PostgreSQL 15
Supabase Edge Functions (Deno)
data models
claim_event
assignment
performance requirements
Batch of 50 claims must complete in < 30 seconds on typical network
Individual claim processing must not block UI thread — all operations are async
Consider parallel processing (Future.wait with concurrency cap) if sequential latency is unacceptable in testing
security requirements
Caller's JWT is passed through to each ApprovalWorkflowService call — no elevation of privilege
Only claims belonging to the caller's organization_id may be approved — validated by RLS on Supabase
Claim IDs validated as valid UUID format before any Supabase call
No raw exception messages surfaced to the BulkApprovalResult errorMessage — sanitize to safe strings

Execution Context

Execution Tier
Tier 1

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/.

Component
Bulk Approval Processor
service medium
Epic Risks (2)
medium impact medium prob scope

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.

medium impact low prob technical

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.