= Database Programming == Prevention of double booking the same room {{{ create or replace function trg_prevent_double_booking() returns trigger as $$ begin if NEW.booking_status in ('PENDING','CONFIRMED') then if exists( select 1 from bookings b where b.room_id = NEW.room_id and b.booking_status in ('PENDING','CONFIRMED') and daterange(b.check_in_date, b.check_out_date, '[)') && daterange(NEW.check_in_date, NEW.check_out_date, '[)') )then raise exception 'Room % is already booked',NEW.room_id; end if; end if; return new; end; $$ language plpgsql; create trigger prevent_double_booking before insert on bookings for each row execute function trg_prevent_double_booking(); }}} == Validate the dates of a booking {{{ create or replace function trg_validate_booking_dates() returns trigger as $$ begin if new.check_in_date >= new.check_out_date then raise exception 'Check out date must be after check in date'; end if; if new.check_in_date < now() then raise exception 'Check in date cannot be in the past'; end if; return new; end; $$ language plpgsql; create trigger validate_booking_dates before insert or update on bookings for each row execute function trg_validate_booking_dates(); }}} == A review can be made only for a complete booking {{{ create or replace function trg_review_only_completed_booking() returns trigger as $$ declare v_status varchar; begin select booking_status into v_status from bookings where booking_id = new.booking_id; if v_status <> 'COMPLETED' then raise exception 'Can only review completed bookings'; end if; return new; end; $$ language plpgsql; create trigger review_only_completed_booking before insert or update on reviews for each row execute function trg_review_only_completed_booking(); }}} == Check if a room is available {{{ create or replace function fn_is_room_available(p_room_id bigint,p_check_in_date date, p_check_out_date date) returns boolean language plpgsql as $$ begin if p_check_in_date >= p_check_out_date then return false; end if; if not exists( select 1 from rooms r where r.room_id = p_room_id and r.status = 'ACTIVE' )then return false; end if; if exists( select 1 from bookings b where b.room_id = p_room_id and b.booking_status in ('PENDING','CONFIRMED') and daterange(b.check_in_date,b.check_out_date,'[)') && daterange(p_check_in_date,p_check_out_date,'[)') )then return false; end if; return true; end; $$; }}} == Calculate the number of nights stayed {{{ create or replace function fn_calculate_nights(p_check_in_date date, p_check_out_date date) returns int language plpgsql as $$ begin if p_check_in_date >= p_check_out_date then raise exception 'Check out must be after check in'; end if; return p_check_out_date - p_check_in_date; end; $$; }}} == Calculate the total price of a booking {{{ create or replace function fn_calculate_booking_price(p_room_id bigint, p_check_in_date date,p_check_out_date date, p_guest_count int) returns numeric language plpgsql as $$ declare v_price_per_night numeric; v_capacity int; v_extra_capacity int; v_extra_guest_price numeric; v_nights int; v_room_total numeric; v_amenities_total numeric; v_extra_guests int; v_extra_guest_total numeric; v_total numeric; begin select r.price_per_night, r.capacity, coalesce(r.extra_capacity,0), coalesce(r.extra_guest_price,0) into v_price_per_night, v_capacity, v_extra_capacity, v_extra_guest_price from rooms r where r.room_id = p_room_id; if p_check_in_date>=p_check_out_date then raise exception 'Check out must be after check in'; end if; v_nights := p_check_in_date - p_check_out_date; if p_guest_count > v_capacity + v_extra_capacity then raise exception 'Guest count exceeds'; end if; v_room_total := v_price_per_night * v_nights; select coalesce(sum(a.price),0) into v_amenities_total from room_amenities ra join amenities a on a.amenity_id = ra.amenity_id where ra.room_id = p_room_id and a.is_included = false; v_extra_guests := greatest(p_guest_count-v_capacity,0); v_extra_guest_total := v_extra_guests*v_extra_guest_price*v_nights; v_total := v_room_total + v_extra_guest_total +v_amenities_total; return v_total; end; $$; }}} == Cancel a booking {{{ create or replace procedure sp_cancel_booking(p_booking_id bigint) language plpgsql as $$ declare v_status varchar; begin select b.booking_status into v_status from bookings b where booking_id = p_booking_id; if v_status is null then raise exception 'Booking does not exsists'; end if; if v_status = 'COMPLETED' then raise exception 'Completed booking cannot be canceled'; end if; if v_status = 'CANCELLED' then raise exception 'The booking is already cancelled'; end if; update bookings set booking_status = 'CANCELLED' where booking_id = p_booking_id; end; $$; }}} == Complete old bookings {{{ create or replace procedure sp_complete_finished_bookings() language plpgsql as $$ begin update bookings set booking_status = 'COMPLETED' where booking_status = 'CONFIRMED' and check_out_date < current_date; end; $$; }}} == Create a booking {{{ CREATE OR REPLACE PROCEDURE book_room( p_room_id BIGINT, p_guest_id BIGINT, p_check_in_date DATE, p_check_out_date DATE, p_guests_count INT, p_total_price NUMERIC ) LANGUAGE plpgsql AS $$ BEGIN IF p_check_out_date <= p_check_in_date THEN RAISE EXCEPTION 'Check-out date must be after check-in date'; END IF; IF EXISTS ( SELECT 1 FROM bookings b WHERE b.room_id = p_room_id AND b.booking_status IN ('PENDING', 'CONFIRMED') AND daterange(b.check_in_date, b.check_out_date, '[)') && daterange(p_check_in_date, p_check_out_date, '[)') ) THEN RAISE EXCEPTION 'Room % is already booked for selected dates', p_room_id; END IF; INSERT INTO bookings ( room_id, guest_id, check_in_date, check_out_date, guests_count, total_price, booking_status ) VALUES ( p_room_id, p_guest_id, p_check_in_date, p_check_out_date, p_guests_count, p_total_price, 'PENDING' ); RAISE NOTICE 'Booking created successfully'; END; $$; }}}