Added My Schedule tab in attendance screen
Allow 1 Day, Whole Week and Date Range swapping
This commit is contained in:
@@ -0,0 +1,227 @@
|
||||
-- Business rule: PM Duty always comes with ON_CALL (consecutive overnight
|
||||
-- shift, same person, same calendar day). Accepting a swap that involves a
|
||||
-- PM schedule must also atomically transfer the companion on_call duty
|
||||
-- schedule to the new PM holder so the pairing stays intact.
|
||||
--
|
||||
-- Handles BOTH swap directions:
|
||||
-- A) target_shift_id is PM → requester receives PM; transfer recipient's
|
||||
-- companion ON_CALL to requester.
|
||||
-- B) shift_id is PM → recipient receives PM; transfer requester's
|
||||
-- companion ON_CALL to recipient.
|
||||
--
|
||||
-- Example (direction A — Normal user initiates):
|
||||
-- Before: User A owns Normal (08:00–17:00), User B owns PM (15:00–23:00)
|
||||
-- and ON_CALL (23:00–07:00) on the same calendar day.
|
||||
-- After accept: User A owns PM + ON_CALL, User B owns Normal.
|
||||
--
|
||||
-- Example (direction B — PM user initiates):
|
||||
-- Before: User B owns PM + ON_CALL, User A owns Normal.
|
||||
-- After accept: same result — User A owns PM + ON_CALL, User B owns Normal.
|
||||
--
|
||||
-- Supersedes 20260322180000_auto_reject_orphaned_swaps.sql.
|
||||
|
||||
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;
|
||||
v_target_type text;
|
||||
v_target_date date;
|
||||
v_requester_type text;
|
||||
v_requester_date date;
|
||||
v_companion_id uuid;
|
||||
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 terminal → nothing to do.
|
||||
IF v_swap.status IN ('accepted', 'rejected') THEN
|
||||
RETURN;
|
||||
END IF;
|
||||
|
||||
-- ── ACCEPTED ──────────────────────────────────────────────────────────────
|
||||
IF p_action = 'accepted' THEN
|
||||
|
||||
-- Permission: recipient or admin/dispatcher
|
||||
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;
|
||||
|
||||
-- Ownership validation 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;
|
||||
|
||||
-- Read both schedules' types and dates BEFORE the swap so we can decide
|
||||
-- companion direction independently of ownership changes.
|
||||
SELECT shift_type,
|
||||
DATE(start_time AT TIME ZONE 'Asia/Manila')
|
||||
INTO v_target_type, v_target_date
|
||||
FROM public.duty_schedules
|
||||
WHERE id = v_swap.target_shift_id;
|
||||
|
||||
SELECT shift_type,
|
||||
DATE(start_time AT TIME ZONE 'Asia/Manila')
|
||||
INTO v_requester_type, v_requester_date
|
||||
FROM public.duty_schedules
|
||||
WHERE id = v_swap.shift_id;
|
||||
|
||||
-- Primary swap: transfer both schedules atomically
|
||||
UPDATE public.duty_schedules
|
||||
SET user_id = v_swap.recipient_id, swap_request_id = p_swap_id
|
||||
WHERE id = v_swap.shift_id; -- requester's schedule → recipient
|
||||
|
||||
UPDATE public.duty_schedules
|
||||
SET user_id = v_swap.requester_id, swap_request_id = p_swap_id
|
||||
WHERE id = v_swap.target_shift_id; -- recipient's schedule → requester
|
||||
|
||||
-- ── Companion ON_CALL transfer ────────────────────────────────────────
|
||||
-- Direction A: target is PM → requester is the new PM holder.
|
||||
-- Find companion ON_CALL previously owned by the RECIPIENT and give it
|
||||
-- to the REQUESTER.
|
||||
IF v_target_type = 'pm' THEN
|
||||
SELECT id INTO v_companion_id
|
||||
FROM public.duty_schedules
|
||||
WHERE user_id = v_swap.recipient_id
|
||||
AND shift_type = 'on_call'
|
||||
AND DATE(start_time AT TIME ZONE 'Asia/Manila') = v_target_date
|
||||
ORDER BY start_time
|
||||
LIMIT 1;
|
||||
|
||||
IF v_companion_id IS NOT NULL THEN
|
||||
UPDATE public.duty_schedules
|
||||
SET user_id = v_swap.requester_id, swap_request_id = p_swap_id
|
||||
WHERE id = v_companion_id;
|
||||
|
||||
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_companion_id
|
||||
OR target_shift_id = v_companion_id
|
||||
);
|
||||
END IF;
|
||||
|
||||
-- Direction B: requester's schedule is PM → recipient is the new PM holder.
|
||||
-- Find companion ON_CALL previously owned by the REQUESTER and give it
|
||||
-- to the RECIPIENT.
|
||||
ELSIF v_requester_type = 'pm' THEN
|
||||
SELECT id INTO v_companion_id
|
||||
FROM public.duty_schedules
|
||||
WHERE user_id = v_swap.requester_id
|
||||
AND shift_type = 'on_call'
|
||||
AND DATE(start_time AT TIME ZONE 'Asia/Manila') = v_requester_date
|
||||
ORDER BY start_time
|
||||
LIMIT 1;
|
||||
|
||||
IF v_companion_id IS NOT NULL THEN
|
||||
UPDATE public.duty_schedules
|
||||
SET user_id = v_swap.recipient_id, swap_request_id = p_swap_id
|
||||
WHERE id = v_companion_id;
|
||||
|
||||
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_companion_id
|
||||
OR target_shift_id = v_companion_id
|
||||
);
|
||||
END IF;
|
||||
END IF;
|
||||
-- ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
-- Accept the swap request
|
||||
UPDATE public.swap_requests
|
||||
SET status = 'accepted', updated_at = now()
|
||||
WHERE id = p_swap_id;
|
||||
|
||||
-- Auto-reject all other pending swaps referencing either primary schedule
|
||||
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());
|
||||
|
||||
-- ── REJECTED ──────────────────────────────────────────────────────────────
|
||||
ELSIF p_action = 'rejected' THEN
|
||||
|
||||
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());
|
||||
|
||||
-- ── ADMIN_REVIEW ──────────────────────────────────────────────────────────
|
||||
ELSE
|
||||
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 after SECURITY DEFINER replace
|
||||
GRANT EXECUTE ON FUNCTION public.respond_shift_swap(uuid, text) TO authenticated;
|
||||
Reference in New Issue
Block a user