high priority medium complexity integration pending integration specialist Tier 4

Acceptance Criteria

notifyClaimApproved(claimId, claimantUserId, orgId) triggers an FCM push notification to the claimant's registered device token via Edge Function dispatch
notifyClaimRejected(claimId, claimantUserId, orgId, rejectionReason) delivers FCM notification including the rejection reason text
notifyClaimPendingReview(claimId, coordinatorUserIds, claimantName, amountNok) sends FCM notification to ALL coordinator device tokens in the org
Coordinator FCM notification payload includes: claimantName, amountNok (formatted as 'kr X,XX'), claimId for deep link navigation
When app is in foreground, notification is delivered via Supabase Realtime stream (RealtimeApprovalSubscription) — no duplicate FCM pop-up
FCM token refresh is handled automatically — stale tokens are updated in device_tokens table without requiring user action
All FCM dispatch executed server-side via Supabase Edge Function — FCM server key never present in mobile binary
Failed FCM delivery (invalid token) removes the token from device_tokens table and logs to bufdir_export_audit_log equivalent
Service gracefully degrades when Realtime is unavailable — FCM-only mode with no error surfaced to user
Notification payloads contain minimal data — full claim content fetched from Supabase API on notification open (per security spec)

Technical Requirements

frameworks
BLoC (notification events fed into ApprovalBLoC)
Riverpod (NotificationService provider)
apis
Firebase Cloud Messaging API v1 — via Supabase Edge Function server-side dispatch
Supabase Edge Functions (Deno) — fcm-dispatch function
Supabase Realtime — foreground notification delivery via RealtimeApprovalSubscription
Supabase PostgREST — device_tokens table CRUD
data models
device_token (FCM token storage, platform enum, registered_at)
claim_event (notification trigger events)
assignment (coordinator lookup for pendingReview notifications)
performance requirements
FCM dispatch via Edge Function must complete in < 3s end-to-end
Token refresh must not block the notification send path — run in parallel
security requirements
FCM server key and service account never bundled in mobile app binary — all dispatch via server-side Edge Functions only
Notification payloads contain only claimId and status — no PII amounts or names in FCM data payload
device_tokens table protected by RLS — users can only read/write their own tokens
Token rotation: on FirebaseMessaging.onTokenRefresh, update device_tokens row and re-register

Execution Context

Execution Tier
Tier 4

Tier 4 - 323 tasks

Can start after Tier 3 completes

Implementation Notes

Structure the service with a dual-delivery strategy: check if RealtimeApprovalSubscription.isConnected before deciding delivery channel. For coordinator notifications, query assignment table for all coordinators in the org and batch their FCM tokens — use a single Edge Function call with a token array rather than N individual calls. The Edge Function should accept { tokens: string[], payload: FCMPayload } to support batch dispatch. FCM notification tap handling: use FirebaseMessaging.onMessageOpenedApp stream to navigate to the specific claim detail screen using the claimId in the notification data.

Place token refresh logic in a dedicated DeviceTokenSyncService called from ApprovalNotificationService to keep responsibilities separate.

Testing Requirements

Unit tests using flutter_test with mocked Supabase client and mocked FCM edge function HTTP client. Test cases: (1) notifyClaimApproved invokes edge function with correct payload schema, (2) notifyClaimPendingReview sends to all coordinator tokens in org, (3) stale token (FCM 404 response) triggers token deletion from device_tokens, (4) foreground detection routes notification to Realtime stream not FCM, (5) Edge Function unavailability surfaces as ApprovalNotificationError with retry suggestion. Mock the Edge Function HTTP call using http package mock — do not call real FCM in unit tests.

Component
Approval Status Notification Service
service medium
Epic Risks (3)
medium impact medium prob technical

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.

medium impact medium prob integration

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.

high impact low prob dependency

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.