76 lines
3.3 KiB
PL/PgSQL
76 lines
3.3 KiB
PL/PgSQL
-- Migration: add scheduled_notifications queue and enqueue function for shift reminders
|
|
-- Creates a queue table and stored procedure to enqueue 15-minute and end-of-shift reminders
|
|
|
|
CREATE EXTENSION IF NOT EXISTS pgcrypto;
|
|
|
|
-- Queue table for scheduled notifications
|
|
CREATE TABLE IF NOT EXISTS public.scheduled_notifications (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
schedule_id uuid NOT NULL REFERENCES public.duty_schedules(id) ON DELETE CASCADE,
|
|
user_id uuid NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
|
|
notify_type text NOT NULL, -- 'start_15' | 'end'
|
|
scheduled_for timestamptz NOT NULL,
|
|
created_at timestamptz NOT NULL DEFAULT now(),
|
|
processed boolean NOT NULL DEFAULT false,
|
|
processed_at timestamptz,
|
|
retry_count int NOT NULL DEFAULT 0,
|
|
last_error text
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_scheduled_notifications_scheduled_for ON public.scheduled_notifications(scheduled_for);
|
|
CREATE INDEX IF NOT EXISTS idx_scheduled_notifications_processed ON public.scheduled_notifications(processed);
|
|
|
|
-- Ensure uniqueness per schedule/user/notify type
|
|
DO $$
|
|
BEGIN
|
|
IF NOT EXISTS (
|
|
SELECT 1 FROM pg_constraint c
|
|
JOIN pg_class t ON c.conrelid = t.oid
|
|
WHERE c.conname = 'uniq_sched_user_type' AND t.relname = 'scheduled_notifications'
|
|
) THEN
|
|
ALTER TABLE public.scheduled_notifications ADD CONSTRAINT uniq_sched_user_type UNIQUE (schedule_id, user_id, notify_type);
|
|
END IF;
|
|
END$$;
|
|
|
|
-- The project already has notification_pushes and try_mark_notification_pushed (see earlier migrations).
|
|
|
|
-- Enqueue function: finds duty_schedules in the configured windows and inserts into queue.
|
|
CREATE OR REPLACE FUNCTION public.enqueue_due_shift_notifications()
|
|
RETURNS void LANGUAGE plpgsql AS $$
|
|
DECLARE
|
|
rec RECORD;
|
|
BEGIN
|
|
-- 15-minute-before reminders
|
|
FOR rec IN
|
|
SELECT id AS schedule_id, user_id AS user_id, start_time AS start_at
|
|
FROM public.duty_schedules
|
|
WHERE start_time BETWEEN now() + interval '15 minutes' - interval '30 seconds' AND now() + interval '15 minutes' + interval '30 seconds'
|
|
AND status IN ('arrival', 'late')
|
|
LOOP
|
|
-- Skip if user already checked in for this duty (attendance_logs references duty_schedule_id)
|
|
IF EXISTS (SELECT 1 FROM public.attendance_logs al WHERE al.duty_schedule_id = rec.schedule_id AND al.check_in_at IS NOT NULL) THEN
|
|
CONTINUE;
|
|
END IF;
|
|
|
|
INSERT INTO public.scheduled_notifications (schedule_id, user_id, notify_type, scheduled_for)
|
|
VALUES (rec.schedule_id, rec.user_id, 'start_15', rec.start_at)
|
|
ON CONFLICT (schedule_id, user_id, notify_type) DO NOTHING;
|
|
END LOOP;
|
|
|
|
-- End-of-shift reminders (near exact end)
|
|
FOR rec IN
|
|
SELECT id AS schedule_id, user_id AS user_id, end_time AS end_at
|
|
FROM public.duty_schedules
|
|
WHERE end_time BETWEEN now() - interval '30 seconds' AND now() + interval '30 seconds'
|
|
AND status IN ('arrival', 'late')
|
|
LOOP
|
|
INSERT INTO public.scheduled_notifications (schedule_id, user_id, notify_type, scheduled_for)
|
|
VALUES (rec.schedule_id, rec.user_id, 'end', rec.end_at)
|
|
ON CONFLICT (schedule_id, user_id, notify_type) DO NOTHING;
|
|
END LOOP;
|
|
END;
|
|
$$;
|
|
|
|
-- NOTE: Schedule this function with pg_cron or your Postgres scheduler. Example (requires pg_cron extension enabled):
|
|
-- SELECT cron.schedule('shift_reminders_every_min', '*/1 * * * *', $$SELECT public.enqueue_due_shift_notifications();$$);
|