feat: add shared notification, communication, and export services for bookings, courses, and events; introduce btree_gist extension and new booking atomic function
This commit is contained in:
@@ -1,25 +1,4 @@
|
||||
-- =====================================================
|
||||
-- Atomic Booking Creation with Overlap Prevention
|
||||
--
|
||||
-- Problem: Creating a booking requires checking room
|
||||
-- availability, validating capacity, and inserting — all
|
||||
-- as separate queries. Race conditions can double-book
|
||||
-- a room for overlapping dates.
|
||||
--
|
||||
-- Fix:
|
||||
-- A) Enable btree_gist extension for exclusion constraints.
|
||||
-- B) Add GiST exclusion constraint to prevent overlapping
|
||||
-- bookings for the same room (non-cancelled/no_show).
|
||||
-- C) Single transactional PG function that locks the room,
|
||||
-- validates inputs, calculates price, and inserts. The
|
||||
-- exclusion constraint provides a final safety net.
|
||||
-- =====================================================
|
||||
|
||||
-- A) Enable btree_gist extension (required for exclusion constraints on non-GiST types)
|
||||
CREATE EXTENSION IF NOT EXISTS btree_gist;
|
||||
|
||||
-- B) Add exclusion constraint to prevent overlapping bookings (idempotent)
|
||||
DO $$
|
||||
DO $excl$
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM pg_constraint WHERE conname = 'excl_booking_room_dates'
|
||||
@@ -32,97 +11,4 @@ BEGIN
|
||||
) WHERE (status NOT IN ('cancelled', 'no_show'));
|
||||
END IF;
|
||||
END;
|
||||
$$;
|
||||
|
||||
-- C) Atomic booking creation function
|
||||
CREATE OR REPLACE FUNCTION public.create_booking_atomic(
|
||||
p_account_id uuid,
|
||||
p_room_id uuid,
|
||||
p_guest_id uuid DEFAULT NULL,
|
||||
p_check_in date DEFAULT NULL,
|
||||
p_check_out date DEFAULT NULL,
|
||||
p_adults integer DEFAULT 1,
|
||||
p_children integer DEFAULT 0,
|
||||
p_status text DEFAULT 'confirmed',
|
||||
p_total_price numeric DEFAULT NULL,
|
||||
p_notes text DEFAULT NULL
|
||||
)
|
||||
RETURNS uuid
|
||||
LANGUAGE plpgsql
|
||||
SECURITY DEFINER
|
||||
SET search_path = ''
|
||||
AS $$
|
||||
DECLARE
|
||||
v_room record;
|
||||
v_computed_price numeric(10,2);
|
||||
v_booking_id uuid;
|
||||
BEGIN
|
||||
-- 1. Lock the room row to serialize booking attempts
|
||||
SELECT * INTO v_room
|
||||
FROM public.rooms
|
||||
WHERE id = p_room_id
|
||||
FOR UPDATE;
|
||||
|
||||
-- 2. Validate room exists
|
||||
IF v_room IS NULL THEN
|
||||
RAISE EXCEPTION 'Room % not found', p_room_id
|
||||
USING ERRCODE = 'P0002';
|
||||
END IF;
|
||||
|
||||
-- 3. Validate check_out > check_in
|
||||
IF p_check_in IS NULL OR p_check_out IS NULL THEN
|
||||
RAISE EXCEPTION 'check_in and check_out dates are required'
|
||||
USING ERRCODE = 'P0001';
|
||||
END IF;
|
||||
|
||||
IF p_check_out <= p_check_in THEN
|
||||
RAISE EXCEPTION 'check_out (%) must be after check_in (%)', p_check_out, p_check_in
|
||||
USING ERRCODE = 'P0001';
|
||||
END IF;
|
||||
|
||||
-- 4. Validate total guests do not exceed room capacity
|
||||
IF (p_adults + p_children) > v_room.capacity THEN
|
||||
RAISE EXCEPTION 'Total guests (%) exceed room capacity (%)', (p_adults + p_children), v_room.capacity
|
||||
USING ERRCODE = 'P0001';
|
||||
END IF;
|
||||
|
||||
-- 5. Calculate price if not provided
|
||||
IF p_total_price IS NOT NULL THEN
|
||||
v_computed_price := p_total_price;
|
||||
ELSE
|
||||
v_computed_price := v_room.price_per_night * (p_check_out - p_check_in);
|
||||
END IF;
|
||||
|
||||
-- 6. Insert the booking (exclusion constraint prevents double-booking)
|
||||
INSERT INTO public.bookings (
|
||||
account_id,
|
||||
room_id,
|
||||
guest_id,
|
||||
check_in,
|
||||
check_out,
|
||||
adults,
|
||||
children,
|
||||
status,
|
||||
total_price,
|
||||
notes
|
||||
) VALUES (
|
||||
p_account_id,
|
||||
p_room_id,
|
||||
p_guest_id,
|
||||
p_check_in,
|
||||
p_check_out,
|
||||
p_adults,
|
||||
p_children,
|
||||
p_status,
|
||||
v_computed_price,
|
||||
p_notes
|
||||
)
|
||||
RETURNING id INTO v_booking_id;
|
||||
|
||||
-- 7. Return the new booking id
|
||||
RETURN v_booking_id;
|
||||
END;
|
||||
$$;
|
||||
|
||||
GRANT EXECUTE ON FUNCTION public.create_booking_atomic(uuid, uuid, uuid, date, date, integer, integer, text, numeric, text) TO authenticated;
|
||||
GRANT EXECUTE ON FUNCTION public.create_booking_atomic(uuid, uuid, uuid, date, date, integer, integer, text, numeric, text) TO service_role;
|
||||
$excl$;
|
||||
|
||||
Reference in New Issue
Block a user