UI Enhancements in IT Service Request, Announcements, Workforce and notification fixes
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
-- Fix: eliminate double push notifications for announcement banners.
|
||||
--
|
||||
-- Root causes addressed:
|
||||
-- 1. scheduled_for = now() made rows immediately eligible. Multiple consecutive
|
||||
-- epoch rows (e.g. E and E+1) all had scheduled_for in the past, so all were
|
||||
-- eligible simultaneously. ON CONFLICT DO NOTHING kept the old rows intact
|
||||
-- (didn't update scheduled_for to the future), so every processBatch run
|
||||
-- found 2+ eligible rows → 2+ FCM sends.
|
||||
-- Fix A (SQL): scheduled_for = start of the NEXT epoch window (future).
|
||||
-- Fix B (SQL): one-time DELETE of all pre-existing stale rows so the queue
|
||||
-- starts clean. The edge function deduplication (Fix C) also
|
||||
-- guards against any future accumulation.
|
||||
-- Fix C (TS): processBatch deduplicates announcement_banner rows by
|
||||
-- (announcement_id, user_id), keeping only the highest-epoch
|
||||
-- row and marking stale ones processed without sending FCM.
|
||||
--
|
||||
-- 2. epoch < v_epoch (too aggressive) deleted epoch E's row at the start of
|
||||
-- epoch E+1 — the row that was due RIGHT NOW — stopping all notifications.
|
||||
-- Fix: epoch < v_epoch - 1 keeps epoch E intact for processing.
|
||||
|
||||
-- ============================================================================
|
||||
-- One-time cleanup: mark all pre-existing stale announcement_banner rows as
|
||||
-- processed so they are no longer eligible. These were inserted with the old
|
||||
-- scheduled_for = now() logic and have scheduled_for in the past, causing them
|
||||
-- to appear in every processBatch query and generate duplicate pushes.
|
||||
-- ============================================================================
|
||||
DELETE FROM public.scheduled_notifications
|
||||
WHERE notify_type = 'announcement_banner'
|
||||
AND processed = false;
|
||||
|
||||
CREATE OR REPLACE FUNCTION public.enqueue_announcement_banner_notifications()
|
||||
RETURNS void LANGUAGE plpgsql AS $$
|
||||
DECLARE
|
||||
ann RECORD;
|
||||
usr RECORD;
|
||||
v_epoch int;
|
||||
BEGIN
|
||||
FOR ann IN
|
||||
SELECT a.id, a.author_id, a.visible_roles, a.push_interval_minutes
|
||||
FROM public.announcements a
|
||||
WHERE a.banner_enabled = true
|
||||
AND a.push_interval_minutes IS NOT NULL
|
||||
AND a.is_template = false
|
||||
AND (a.banner_show_at IS NULL OR a.banner_show_at <= now())
|
||||
AND (a.banner_hide_at IS NULL OR a.banner_hide_at > now())
|
||||
LOOP
|
||||
-- One epoch per interval window; prevents duplicate pushes within the window.
|
||||
v_epoch := FLOOR(
|
||||
EXTRACT(EPOCH FROM now()) / (ann.push_interval_minutes * 60)
|
||||
)::int;
|
||||
|
||||
-- Purge rows that are ≥2 epochs stale (unprocessed due to missed cycles).
|
||||
-- We keep epoch = v_epoch - 1 because that row's scheduled_for falls
|
||||
-- exactly at the current epoch boundary and is about to be processed.
|
||||
-- Deleting epoch < v_epoch would remove rows that are due RIGHT NOW
|
||||
-- (epoch E's row has scheduled_for = (E+1)*interval = now), causing
|
||||
-- all notifications to stop firing — which is the bug this replaces.
|
||||
DELETE FROM public.scheduled_notifications
|
||||
WHERE announcement_id = ann.id
|
||||
AND notify_type = 'announcement_banner'
|
||||
AND processed = false
|
||||
AND epoch < v_epoch - 1;
|
||||
|
||||
FOR usr IN
|
||||
SELECT p.id
|
||||
FROM public.profiles p
|
||||
WHERE p.role::text = ANY(ann.visible_roles)
|
||||
AND p.id <> ann.author_id
|
||||
LOOP
|
||||
-- scheduled_for = start of the NEXT epoch window (not now()).
|
||||
-- This guarantees the row only becomes eligible AFTER the current
|
||||
-- epoch ends, so a concurrent or delayed edge-function instance can
|
||||
-- never see two different epochs' rows as simultaneously due.
|
||||
INSERT INTO public.scheduled_notifications
|
||||
(announcement_id, user_id, notify_type, scheduled_for, epoch)
|
||||
VALUES
|
||||
(ann.id, usr.id, 'announcement_banner',
|
||||
to_timestamp(((v_epoch + 1)::bigint * ann.push_interval_minutes * 60)),
|
||||
v_epoch)
|
||||
ON CONFLICT DO NOTHING;
|
||||
END LOOP;
|
||||
END LOOP;
|
||||
END;
|
||||
$$;
|
||||
Reference in New Issue
Block a user