critical priority medium complexity database pending database specialist Tier 1

Acceptance Criteria

ExpenseClaimStatusRepository exposes: updateStatus(String claimId, ClaimStatus newStatus), getClaimsByStatus(ClaimStatus status, String chapterId, {int page, int pageSize}), getClaimStatus(String claimId), and watchClaimStatus(String claimId) → Stream<ClaimStatus>
ClaimStatus enum covers all five states: pending, under_review, approved, rejected, auto_approved
Status transitions are validated against an allowed-transitions map before any database write — e.g., approved → pending is rejected with an InvalidTransitionFailure
Allowed transitions: pending → under_review, pending → auto_approved, under_review → approved, under_review → rejected, under_review → more_info_requested (if applicable), rejected → pending (re-submission path)
getClaimsByStatus returns a paginated PaginatedResult<ExpenseClaim> with total count, current page, and hasMore flag
Pagination uses cursor-based or offset+limit strategy consistent with the rest of the app — page size defaults to 20
watchClaimStatus returns a Stream that emits the current status immediately on subscription and then emits each subsequent change
Stream subscription is backed by Supabase Realtime — confirmed by an integration test that updates a row and verifies the stream emits within 2 seconds
RLS ensures a coordinator can only fetch/watch claims in their own chapter
updateStatus records a ClaimEvent via ClaimEventsRepository (from task-001) for auditability

Technical Requirements

frameworks
Flutter
Dart
supabase_flutter (Realtime)
Riverpod StreamProvider or BLoC StreamSubscription
apis
Supabase PostgREST REST API
Supabase Realtime (postgres_changes)
data models
ExpenseClaim
ClaimStatus
ClaimEvent
PaginatedResult
performance requirements
getClaimsByStatus must use compound index on (status, chapter_id, created_at) for efficient pagination
Realtime subscription must filter by chapter_id using Supabase filter parameter to avoid sending cross-chapter events to the client
Paginated fetches must complete within 400ms for page sizes up to 50
security requirements
RLS on expense_claims table must restrict SELECT to claims in the coordinator's chapter
Status update must verify the claim belongs to the coordinator's chapter before applying — do not trust client-provided chapter context
Realtime channel must use server-side RLS filtering, not client-side filtering of received events

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Implement the transition guard as a static method on ClaimStatus or a dedicated ClaimStatusTransitionGuard class — this makes it independently testable. For Supabase Realtime, use `supabase.channel('claim_status_$claimId').on(PostgresChangeEvent.update, ...).subscribe()`. Ensure the StreamController is properly closed when the repository is disposed — expose a dispose() method and call it from the BLoC/provider's onClose/dispose lifecycle. For pagination, prefer keyset (cursor) pagination over offset if claims are frequently inserted to avoid page drift.

The Realtime subscription added here is the foundation for task-004, which will generalise it to the full approval queue.

Testing Requirements

Unit tests: test each valid and invalid status transition path in the transition guard. Mock Supabase and verify correct query parameters for paginated fetches (eq filters, range, order). Test Stream emits correctly from a mocked Realtime channel. Integration tests: insert a claim, subscribe to watchClaimStatus, update the status in a second connection, and assert the stream emits the new status within a timeout.

Test that page 2 of paginated results contains the correct items. Test RLS cross-chapter access is blocked. Use flutter_test with fake async for stream timing. Target 85%+ line coverage.

Epic Risks (3)
medium impact medium prob technical

Maintaining multi-select state across paginated list pages is architecturally complex in Flutter with Riverpod/BLoC. If the selection state is stored in the widget tree rather than the state layer, page transitions and list redraws can silently clear selections, causing coordinators to lose their multi-select and re-enter it.

Mitigation & Contingency

Mitigation: Store the selected claim ID set in a dedicated Riverpod StateNotifier outside the paginated list widget tree. The paginated list reads selection state from this provider and does not own it. Selection persists independently of list scroll position or page loads.

Contingency: If cross-page selection proves prohibitively complex, limit bulk selection to the currently visible page (add a clear warning in the UI) and prioritise single-page bulk approval for the initial release.

medium impact medium prob integration

If a coordinator has the queue open while another coordinator approves claims from the same queue (possible in large organisations with shared chapter coverage), the Realtime update may arrive out of order or be missed during a reconnect, leaving the first coordinator's view stale and allowing them to attempt to approve an already-actioned claim.

Mitigation & Contingency

Mitigation: The ApprovalWorkflowService's optimistic locking (from the foundation epic) will catch the concurrent edit at the database level. The CoordinatorReviewQueueScreen should handle the resulting ConcurrencyException by removing the claim from the local list and showing a brief snackbar: 'This claim was already actioned by another coordinator.'

Contingency: Add a queue staleness indicator (a subtle 'last updated X seconds ago' label) and a manual refresh button as a fallback for coordinators who notice inconsistencies.

low impact high prob dependency

The end-to-end test requirement that a peer mentor receives a push notification within 30 seconds of coordinator approval depends on FCM delivery latency, which is outside the application's control and can vary significantly in CI/CD environments.

Mitigation & Contingency

Mitigation: Structure end-to-end tests to verify notification intent (correct FCM payload dispatched, correct Realtime event emitted) rather than actual device delivery timing. Use test doubles for FCM delivery in automated tests and reserve real-device delivery tests for manual pre-release validation.

Contingency: If notification timing requirements must be validated in automation, instrument the ApprovalNotificationService with a test hook that records dispatch timestamps and assert against those rather than actual FCM callbacks.