63 lines
2.2 KiB
SQL
63 lines
2.2 KiB
SQL
-- CreateEnum
|
|
CREATE TYPE "BookingStatus" AS ENUM ('HOLD', 'CONFIRMED', 'COMPLETED', 'NO_SHOW', 'CANCELLED');
|
|
|
|
-- CreateTable
|
|
CREATE TABLE "Booking" (
|
|
"id" TEXT NOT NULL,
|
|
"customerId" TEXT NOT NULL,
|
|
"therapistId" TEXT NOT NULL,
|
|
"roomId" TEXT NOT NULL,
|
|
"serviceId" TEXT NOT NULL,
|
|
"startsAt" TIMESTAMPTZ(3) NOT NULL,
|
|
"endsAt" TIMESTAMPTZ(3) NOT NULL,
|
|
"roomReleasedAt" TIMESTAMPTZ(3) NOT NULL,
|
|
"status" "BookingStatus" NOT NULL DEFAULT 'HOLD',
|
|
"holdExpiresAt" TIMESTAMPTZ(3),
|
|
"priceCents" INTEGER NOT NULL DEFAULT 0,
|
|
"depositCents" INTEGER NOT NULL DEFAULT 0,
|
|
"createdAt" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
"updatedAt" TIMESTAMPTZ(3) NOT NULL,
|
|
|
|
CONSTRAINT "Booking_pkey" PRIMARY KEY ("id")
|
|
);
|
|
|
|
-- CreateIndex
|
|
CREATE INDEX "Booking_startsAt_idx" ON "Booking"("startsAt");
|
|
|
|
-- CreateIndex
|
|
CREATE INDEX "Booking_therapistId_startsAt_idx" ON "Booking"("therapistId", "startsAt");
|
|
|
|
-- CreateIndex
|
|
CREATE INDEX "Booking_roomId_startsAt_idx" ON "Booking"("roomId", "startsAt");
|
|
|
|
-- CreateIndex
|
|
CREATE INDEX "Booking_status_holdExpiresAt_idx" ON "Booking"("status", "holdExpiresAt");
|
|
|
|
-- Required extension for combining a UUID/text equality with a range type in EXCLUDE constraints.
|
|
-- Created out-of-band by db/init/01-extensions-and-test-db.sql; safe to re-run.
|
|
CREATE EXTENSION IF NOT EXISTS btree_gist;
|
|
|
|
-- Double-booking safety net. App-level availability search prevents most conflicts;
|
|
-- these constraints are the last line of defense for race conditions and bugs.
|
|
-- Only HOLD and CONFIRMED rows are subject to the constraint — cancelled/completed
|
|
-- rows are historical and may overlap freely (e.g., a no-show then a same-slot booking).
|
|
|
|
-- A therapist can only be in one active booking at a time.
|
|
ALTER TABLE "Booking"
|
|
ADD CONSTRAINT "Booking_no_therapist_overlap"
|
|
EXCLUDE USING gist (
|
|
"therapistId" WITH =,
|
|
tstzrange("startsAt", "endsAt", '[)') WITH &&
|
|
)
|
|
WHERE (status IN ('HOLD', 'CONFIRMED'));
|
|
|
|
-- A room can only host one active booking at a time, including its post-service buffer.
|
|
ALTER TABLE "Booking"
|
|
ADD CONSTRAINT "Booking_no_room_overlap"
|
|
EXCLUDE USING gist (
|
|
"roomId" WITH =,
|
|
tstzrange("startsAt", "roomReleasedAt", '[)') WITH &&
|
|
)
|
|
WHERE (status IN ('HOLD', 'CONFIRMED'));
|
|
|