high priority low complexity backend pending backend specialist Tier 0

Acceptance Criteria

BulkApprovalResult Dart class exists with fields: totalProcessed (int), successCount (int), failureCount (int), successfulClaimIds (List<String>), failedRecords (List<BulkApprovalFailure>), processingDurationMs (int)
BulkApprovalFailure class contains: claimId (String), errorCode (BulkApprovalErrorCode), errorMessage (String), isRetryable (bool)
BulkApprovalErrorCode enum covers at minimum: permissionDenied, invalidStatus, claimNotFound, networkError, serverError, unknownError
All model classes are immutable (use final fields, const constructors where possible)
copyWith() method exists on BulkApprovalResult for state accumulation in processor
toJson() / fromJson() serialization implemented and tested for both classes
successCount + failureCount always equals totalProcessed (enforced via assertion or factory constructor)
Classes are placed in the correct domain layer directory (e.g., lib/domain/models/expense/)
No dependency on Flutter framework — pure Dart models only

Technical Requirements

frameworks
Dart
data models
claim_event
assignment
performance requirements
Model instantiation must complete in < 1ms
Serialization of a result with 100 failed records must complete in < 5ms
security requirements
Error messages must not leak internal server details or raw SQL errors to the model layer
claimId values are UUIDs — validate format in fromJson()

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

Keep these as pure Dart data classes with no BLoC/Riverpod dependencies — they belong in the domain layer and must be framework-agnostic. Use freezed or manual immutable pattern (prefer manual if freezed is not already used in the project to avoid new codegen dependencies). The isRetryable flag on BulkApprovalFailure is critical for future retry-subset feature — define it now. Avoid using dynamic or Object for error payloads; typed error codes prevent UI from needing string matching.

Consider making BulkApprovalResult.empty() a named constructor for initializing accumulator state in the processor.

Testing Requirements

Unit tests required for: (1) BulkApprovalResult construction with mixed success/failure counts, (2) invariant assertion that successCount + failureCount == totalProcessed, (3) toJson/fromJson round-trip for both classes, (4) all BulkApprovalErrorCode enum values are serializable, (5) copyWith preserves unmodified fields. Aim for 100% branch coverage on model logic. Tests live in test/domain/models/expense/bulk_approval_result_test.dart.

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.