high priority medium complexity backend pending backend specialist Tier 2

Acceptance Criteria

BulkApprovalResult.hasPartialFailure getter returns true when 0 < failureCount < totalProcessed
BulkApprovalResult.isCompleteFailure getter returns true when failureCount == totalProcessed
BulkApprovalResult.isCompleteSuccess getter returns true when failureCount == 0
failedRecords list is ordered by the original input claimIds order for deterministic UI display
Each BulkApprovalFailure.errorMessage is a human-readable English string suitable for display to a coordinator (not a raw stack trace)
Retryable failures (isRetryable=true) are grouped/flagged so the UI can offer a retry-failed subset action
BulkApprovalProcessor exposes a retryFailedRecords(BulkApprovalResult previousResult) convenience method that re-runs only the failed retryable claim IDs
Coordinator-level permission errors (permissionDenied) are reported as non-retryable
The processor emits a Stream<BulkApprovalProgress> alongside the final Future<BulkApprovalResult> to support progress reporting (processedCount, totalCount)
BulkApprovalProgress model includes: processedSoFar (int), totalCount (int), lastClaimId (String), lastOutcome (success/failure)

Technical Requirements

frameworks
Dart
Riverpod
apis
Supabase PostgreSQL 15
data models
claim_event
assignment
performance requirements
Stream events emitted after each individual claim completion — no batching of progress events
retryFailedRecords() must reuse the same processor logic path, not duplicate code
security requirements
Error messages for permissionDenied must not reveal which other organization's claim was accessed
Failed record details must not include sensitive financial amounts in error strings

Execution Context

Execution Tier
Tier 2

Tier 2 - 518 tasks

Can start after Tier 1 completes

Implementation Notes

Implement the Stream using a StreamController that is closed after the final result is produced. The process() method signature becomes: ({required StreamController progressController}) — pass it in optionally to avoid breaking existing callers. The retryFailedRecords() method filters previousResult.failedRecords where isRetryable==true, extracts claimIds, and calls process() again — this is a thin wrapper, not new logic. The human-readable error message map should be a const Map in a separate file for easy translation in the future.

Avoid exposing the StreamController outside the service — return a Stream from a getter instead.

Testing Requirements

Unit tests: (1) hasPartialFailure/isCompleteFailure/isCompleteSuccess getters for all three states, (2) failedRecords ordering matches input ordering, (3) retryFailedRecords() only processes isRetryable=true records from the previous result, (4) Stream emits correct processedSoFar increments for a 5-item batch, (5) permissionDenied error maps to isRetryable=false. Widget-level test: mock a partial failure result and verify the UI renders the correct count of failed badges. Coverage target: 90%+ on processor logic.

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.