Added My Schedule tab in attendance screen
Allow 1 Day, Whole Week and Date Range swapping
This commit is contained in:
@@ -0,0 +1,120 @@
|
||||
-- When a swap is accepted, any OTHER pending/admin_review swap requests that
|
||||
-- reference the same schedules become invalid (ownership changed). Rather than
|
||||
-- letting them linger as "pending" until someone taps them and gets a confusing
|
||||
-- ownership error, we automatically reject them in the same transaction.
|
||||
--
|
||||
-- This fixes the "PM shift still showing after ON_CALL swap accepted" scenario:
|
||||
-- Swap A: User X's ON_CALL ↔ User Y's PM → accepted
|
||||
-- Swap B: User X's ON_CALL ↔ User Z's PM → auto-rejected here (stale)
|
||||
|
||||
CREATE OR REPLACE FUNCTION public.respond_shift_swap(p_swap_id uuid, p_action text)
|
||||
RETURNS void LANGUAGE plpgsql SECURITY DEFINER SET search_path = public AS $$
|
||||
DECLARE
|
||||
v_swap RECORD;
|
||||
BEGIN
|
||||
SELECT * INTO v_swap FROM public.swap_requests WHERE id = p_swap_id;
|
||||
IF NOT FOUND THEN
|
||||
RAISE EXCEPTION 'swap request not found';
|
||||
END IF;
|
||||
|
||||
IF p_action NOT IN ('accepted','rejected','admin_review') THEN
|
||||
RAISE EXCEPTION 'invalid action';
|
||||
END IF;
|
||||
|
||||
-- Idempotency guard: already in terminal state → nothing more to do.
|
||||
IF v_swap.status IN ('accepted', 'rejected') THEN
|
||||
RETURN;
|
||||
END IF;
|
||||
|
||||
IF p_action = 'accepted' THEN
|
||||
-- only recipient or admin/dispatcher can accept
|
||||
IF NOT (
|
||||
v_swap.recipient_id = auth.uid()
|
||||
OR EXISTS (SELECT 1 FROM public.profiles p WHERE p.id = auth.uid() AND p.role IN ('admin','dispatcher'))
|
||||
) THEN
|
||||
RAISE EXCEPTION 'permission denied';
|
||||
END IF;
|
||||
|
||||
-- ensure both shifts are still owned by the expected users before swapping
|
||||
IF NOT EXISTS (SELECT 1 FROM public.duty_schedules WHERE id = v_swap.shift_id AND user_id = v_swap.requester_id) THEN
|
||||
RAISE EXCEPTION 'requester shift ownership changed, cannot accept swap';
|
||||
END IF;
|
||||
IF v_swap.target_shift_id IS NULL THEN
|
||||
RAISE EXCEPTION 'target shift missing';
|
||||
END IF;
|
||||
IF NOT EXISTS (SELECT 1 FROM public.duty_schedules WHERE id = v_swap.target_shift_id AND user_id = v_swap.recipient_id) THEN
|
||||
RAISE EXCEPTION 'target shift ownership changed, cannot accept swap';
|
||||
END IF;
|
||||
|
||||
-- perform the swap (atomic within function) and stamp swap_request_id
|
||||
UPDATE public.duty_schedules
|
||||
SET user_id = v_swap.recipient_id, swap_request_id = p_swap_id
|
||||
WHERE id = v_swap.shift_id;
|
||||
|
||||
UPDATE public.duty_schedules
|
||||
SET user_id = v_swap.requester_id, swap_request_id = p_swap_id
|
||||
WHERE id = v_swap.target_shift_id;
|
||||
|
||||
UPDATE public.swap_requests
|
||||
SET status = 'accepted', updated_at = now()
|
||||
WHERE id = p_swap_id;
|
||||
|
||||
-- Auto-reject all OTHER pending/admin_review swap requests that reference
|
||||
-- either of the now-swapped schedules. These are stale — the shift
|
||||
-- ownerships changed, so they can never be fulfilled.
|
||||
UPDATE public.swap_requests
|
||||
SET status = 'rejected', updated_at = now()
|
||||
WHERE id <> p_swap_id
|
||||
AND status IN ('pending', 'admin_review')
|
||||
AND (
|
||||
shift_id = v_swap.shift_id
|
||||
OR shift_id = v_swap.target_shift_id
|
||||
OR target_shift_id = v_swap.shift_id
|
||||
OR target_shift_id = v_swap.target_shift_id
|
||||
);
|
||||
|
||||
INSERT INTO public.swap_request_participants(swap_request_id, user_id, role)
|
||||
VALUES (p_swap_id, auth.uid(), 'approver')
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
INSERT INTO public.notifications(user_id, actor_id, type, created_at)
|
||||
VALUES (v_swap.requester_id, auth.uid(), 'swap_update', now());
|
||||
|
||||
ELSIF p_action = 'rejected' THEN
|
||||
-- only recipient or admin/dispatcher can reject
|
||||
IF NOT (
|
||||
v_swap.recipient_id = auth.uid()
|
||||
OR EXISTS (SELECT 1 FROM public.profiles p WHERE p.id = auth.uid() AND p.role IN ('admin','dispatcher'))
|
||||
) THEN
|
||||
RAISE EXCEPTION 'permission denied';
|
||||
END IF;
|
||||
|
||||
UPDATE public.swap_requests
|
||||
SET status = 'rejected', updated_at = now()
|
||||
WHERE id = p_swap_id;
|
||||
|
||||
INSERT INTO public.swap_request_participants(swap_request_id, user_id, role)
|
||||
VALUES (p_swap_id, auth.uid(), 'approver')
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
INSERT INTO public.notifications(user_id, actor_id, type, created_at)
|
||||
VALUES (v_swap.requester_id, auth.uid(), 'swap_update', now());
|
||||
|
||||
ELSE -- admin_review
|
||||
-- only requester may escalate for admin review
|
||||
IF NOT (v_swap.requester_id = auth.uid()) THEN
|
||||
RAISE EXCEPTION 'permission denied';
|
||||
END IF;
|
||||
|
||||
UPDATE public.swap_requests
|
||||
SET status = 'admin_review', updated_at = now()
|
||||
WHERE id = p_swap_id;
|
||||
|
||||
INSERT INTO public.notifications(user_id, actor_id, type, created_at)
|
||||
VALUES (v_swap.requester_id, auth.uid(), 'swap_update', now());
|
||||
END IF;
|
||||
END;
|
||||
$$;
|
||||
|
||||
-- Re-grant EXECUTE (SECURITY DEFINER drops grants on replace).
|
||||
GRANT EXECUTE ON FUNCTION public.respond_shift_swap(uuid, text) TO authenticated;
|
||||
Reference in New Issue
Block a user