Admin Notification Dispatcher: Org-Scoped Dispatch Logic
epic-admin-portal-core-services-task-012 — Implement org-scoped targeting in AdminNotificationDispatcher. Given a notification type and org node ID, resolve all affected recipients within the subtree, apply role filters (e.g., notify only coordinators), batch FCM dispatch calls, and record delivery outcomes per notification event.
Acceptance Criteria
Technical Requirements
Execution Context
Tier 3 - 413 tasks
Can start after Tier 2 completes
Implementation Notes
Separate the dispatch logic into three clean phases: (1) Resolution — `List> batchRecipients(recipients, batchSize: 500)`; (3) Dispatch — iterate batches, call FCM, collect outcomes. This separation makes each phase independently testable. For the notification_event status lifecycle, use a DB transaction for the initial 'pending' insert to ensure it always exists before any delivery records reference it. Use `Future.wait(batches.map(dispatchBatch))` to dispatch multiple batches concurrently where FCM rate limits permit — but add a configurable `maxConcurrentBatches` parameter defaulting to 3 to avoid overwhelming FCM.
For delivery outcome recording, collect all outcomes in memory during dispatch, then bulk-insert them in a single Supabase `upsert` call after all batches complete — this minimizes round-trips. Implement `dry_run` by injecting a `NotificationDispatchStrategy` interface with real and dry-run implementations — this avoids sprinkling `if (dryRun)` conditionals throughout the dispatch logic.
Testing Requirements
Unit tests: mock OrgHierarchyService, FCM client, and Supabase DB client; assert that recipients are resolved from the correct subtree; assert FCM calls are batched at 500 recipients per batch; assert notification_event is created with status='pending' before dispatch and updated to 'completed' after; assert partial FCM failure records correct delivered/failed counts. Test dry_run=true: assert no FCM calls are made and no DB inserts occur. Test no_token scenario: recipient without FCM token results in delivery record with status='no_token'. Integration tests against Supabase local + FCM emulator: seed 600 coordinator users with FCM tokens across 2 org nodes, dispatch a USER_STATUS_CHANGED notification, assert 2 FCM batch calls are made, assert 600 delivery records are created with correct statuses, assert notification_event has delivered_count=600.
Error recovery test: mock FCM to fail for tokens 400-500 in a 600-recipient dispatch, assert records 1-399 and 501-600 are marked 'delivered' and 400-500 are marked 'failed'.
OrgHierarchyNavigator rendering NHF's full 1,400-chapter tree in a single widget may cause Flutter frame-rate drops below 60 fps on mid-range devices, making the navigator unusable for NHF national admins.
Mitigation & Contingency
Mitigation: Implement lazy expansion: only load immediate children on node expand rather than the full tree upfront. Use virtual scrolling for long sibling lists. Test with a synthetic 1,400-node dataset on a low-end Android device during development.
Contingency: If lazy expansion is insufficient, replace the tree widget with a paginated drill-down navigator (select level → select child) that avoids rendering more than 50 nodes at a time.
Bufdir may update their required export column structure or file format during or after development. If the AdminExportService hardcodes the current Bufdir schema, any format change requires a code release rather than a config update.
Mitigation & Contingency
Mitigation: Drive the Bufdir column mapping from a configuration repository rather than hardcoded constants. Abstract column definitions into a named schema config so that format changes require only a config update and re-deployment without service logic changes.
Contingency: If Bufdir format changes post-launch, release a config update within one sprint. If the change is structural (new required sections), scope a targeted service update and communicate timeline to partner organisations.
Role transition side-effects in UserManagementService (e.g., certification expiry removing mentor from chapter listing, pause triggering coordinator notification) may interact with external services like HLF's website sync. Incomplete side-effect handling could leave the system in an inconsistent state.
Mitigation & Contingency
Mitigation: Model side-effects as explicit domain events published after the primary state change is persisted. Implement event handlers as idempotent operations so re-processing is safe. Write integration tests that assert all side-effects fire correctly for each role transition type.
Contingency: If a side-effect fails after the primary change is persisted, log the failure with full context and trigger a manual reconciliation alert to the on-call team. Provide an admin-accessible re-trigger action for failed side-effects.
If AdminStatisticsService cache TTL is set too long, org_admin may see significantly stale KPI values (e.g., a mentor newly paused an hour ago still appears as active), undermining trust in the dashboard.
Mitigation & Contingency
Mitigation: Default cache TTL to 5 minutes with a manual refresh action on the dashboard. Implement cache invalidation triggered by UserManagementService write operations that affect counted entities.
Contingency: If staleness causes org admin complaints post-launch, reduce TTL to 60 seconds and introduce a real-time Supabase subscription for high-impact counters (paused mentors, expiring certifications).