Define ExpenseClaimStatus database schema and Dart models
epic-expense-approval-workflow-foundation-task-005 — Create the expense_claim_status table with columns for claim_id (PK), status (enum: draft/submitted/pending_approval/approved/rejected/exported), version (integer for optimistic locking), updated_at, and updated_by. Define corresponding Dart model with copyWith support. Apply RLS policies restricting status transitions by role.
Acceptance Criteria
Technical Requirements
Execution Context
Tier 1 - 540 tasks
Can start after Tier 0 completes
Implementation Notes
The status transition validation should be enforced at two levels: (1) a Dart ClaimStatusTransitionValidator in the domain layer that throws InvalidTransitionException for disallowed transitions before making any network call, and (2) a database trigger that enforces the same rules as a safety net. Valid transitions: draft→submitted (submitter), submitted→pending_approval (coordinator), pending_approval→approved (coordinator), pending_approval→rejected (coordinator), approved→exported (admin/finance), rejected→draft (submitter for resubmission). The version column is the key for optimistic locking in task-006 — define it here as a database-managed counter. For the copyWith() pattern, generate it or implement manually; do not use external code generation if the project doesn't already use build_runner, to keep the build consistent.
Testing Requirements
Unit tests: ExpenseClaimStatus.fromJson() all status values, copyWith() preserves unchanged fields and applies changes, ClaimStatus.fromString() throws on unknown string, version is parsed as integer not double. Integration tests against Supabase test instance: valid transition sequence (draft→submitted→pending_approval→approved→exported) succeeds, invalid transition (draft→approved) rejected by database constraint, version increments on each update, UPDATE with stale version (optimistic lock conflict) returns the current row without applying changes — verify application layer detects this, SELECT returns only records in coordinator's scope, updated_by is set to auth.uid() and cannot be overridden by client payload.
Optimistic locking in ExpenseClaimStatusRepository may produce excessive concurrency exceptions in high-volume coordinator sessions where multiple coordinators process the same queue simultaneously, causing confusing UI errors and coordinator frustration.
Mitigation & Contingency
Mitigation: Design the locking strategy with a short retry window (1-2 automatic retries with 200ms back-off) before surfacing the error to the UI. Document the concurrency model clearly so the UI layer can display a contextual 'claim was already actioned' message rather than a generic error.
Contingency: If contention remains high under load testing, switch to a last-writer-wins update with a conflict notification rather than a hard block, and log all concurrent edits for audit purposes.
FCM device tokens stored for peer mentors may be stale (app reinstalled, token rotated) causing push notifications for claim status changes to silently fail, leaving submitters unaware their claim was approved or rejected.
Mitigation & Contingency
Mitigation: Implement token refresh on every app launch and store updated tokens in Supabase. ApprovalNotificationService should fall back to in-app Realtime delivery when FCM returns an invalid-token error and should queue a token refresh request.
Contingency: If FCM delivery rates fall below acceptable thresholds in production monitoring, add a polling fallback in the peer mentor claim list screen that checks status on foreground resume.
Supabase Realtime has per-project channel and connection limits. If many coordinators and peer mentors are simultaneously subscribed across multiple screens, the project may hit quota limits causing subscription failures.
Mitigation & Contingency
Mitigation: Design RealtimeApprovalSubscription to use a single shared channel per user session rather than per-screen subscriptions. Implement subscription reference counting so channels are only opened once and reused across screens.
Contingency: Upgrade the Supabase plan tier if limits are reached, and implement graceful degradation to polling with a 30-second interval when Realtime is unavailable.