From a56a238e81bd6f3bae5fb10043ea79368b413206 Mon Sep 17 00:00:00 2001 From: mivanchenko Date: Sat, 12 Sep 2026 02:42:06 +0200 Subject: [PATCH] Fix: exclude cancelled bookings from the no-overlap constraint bookings_no_overlap (the GiST exclusion constraint preventing double-booking) had no WHERE clause, so a cancelled booking's old time range stayed "occupied" forever -- permanently blocking that resource+slot from ever being booked again, even though the booking itself is dead. Found live: a cancelled test booking blocked a real reschedule attempt into the same slot ("overlaps with an existing appointment" even though nothing active was there). Co-Authored-By: Claude Sonnet 5 --- backoffice/db/init.sql | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/backoffice/db/init.sql b/backoffice/db/init.sql index 8579953..a1c8476 100644 --- a/backoffice/db/init.sql +++ b/backoffice/db/init.sql @@ -166,16 +166,23 @@ ALTER TABLE bookings ADD COLUMN IF NOT EXISTS resource_id text; ALTER TABLE bookings ADD COLUMN IF NOT EXISTS during tstzrange GENERATED ALWAYS AS (tstzrange(start_time, end_time, '[)')) STORED; --- ALTER TABLE ... ADD CONSTRAINT has no IF NOT EXISTS form, so guard by name --- to keep this file safe to re-run on every deploy like everything above it. +-- ALTER TABLE ... ADD CONSTRAINT has no IF NOT EXISTS form, so drop-then-add +-- unconditionally to keep this file safe to re-run on every deploy like +-- everything above it. The WHERE clause is load-bearing: without it, a +-- cancelled booking's old time range stays "occupied" forever, permanently +-- blocking that resource+slot from ever being booked again even though the +-- booking itself is dead -- found 2026-09-12 when a cancelled test booking +-- blocked a real reschedule into the same slot. DO $$ BEGIN - IF NOT EXISTS ( + IF EXISTS ( SELECT 1 FROM pg_constraint WHERE conname = 'bookings_no_overlap' ) THEN - ALTER TABLE bookings ADD CONSTRAINT bookings_no_overlap - EXCLUDE USING gist (resource_id WITH =, during WITH &&); + ALTER TABLE bookings DROP CONSTRAINT bookings_no_overlap; END IF; + ALTER TABLE bookings ADD CONSTRAINT bookings_no_overlap + EXCLUDE USING gist (resource_id WITH =, during WITH &&) + WHERE (status <> 'cancelled'); END $$; ALTER TABLE clients ADD COLUMN IF NOT EXISTS slug text UNIQUE;