| 1 | CREATE TABLE IF NOT EXISTS users (
|
|---|
| 2 | id BIGSERIAL PRIMARY KEY,
|
|---|
| 3 | email VARCHAR(255) NOT NULL UNIQUE,
|
|---|
| 4 | password VARCHAR(255) NOT NULL,
|
|---|
| 5 | phone_number VARCHAR(50) UNIQUE,
|
|---|
| 6 | street VARCHAR(255),
|
|---|
| 7 | city VARCHAR(255)
|
|---|
| 8 | );
|
|---|
| 9 |
|
|---|
| 10 | CREATE TABLE IF NOT EXISTS employees (
|
|---|
| 11 | user_id BIGSERIAL PRIMARY KEY,
|
|---|
| 12 | net_salary DECIMAL(10,2),
|
|---|
| 13 | gross_salary DECIMAL(10,2),
|
|---|
| 14 | FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE ON UPDATE CASCADE
|
|---|
| 15 | );
|
|---|
| 16 |
|
|---|
| 17 | CREATE TABLE IF NOT EXISTS customers (
|
|---|
| 18 | user_id BIGSERIAL PRIMARY KEY,
|
|---|
| 19 | FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE ON UPDATE CASCADE
|
|---|
| 20 | );
|
|---|
| 21 |
|
|---|
| 22 | CREATE TABLE IF NOT EXISTS staff_roles (
|
|---|
| 23 | id BIGSERIAL PRIMARY KEY,
|
|---|
| 24 | name VARCHAR(255) NOT NULL
|
|---|
| 25 | );
|
|---|
| 26 |
|
|---|
| 27 | CREATE TABLE IF NOT EXISTS front_staff (
|
|---|
| 28 | employee_id BIGSERIAL PRIMARY KEY,
|
|---|
| 29 | tip_percent DECIMAL(10,2),
|
|---|
| 30 | staff_role_id BIGINT NOT NULL,
|
|---|
| 31 | FOREIGN KEY (employee_id) REFERENCES employees (user_id) ON DELETE CASCADE ON UPDATE CASCADE,
|
|---|
| 32 | FOREIGN KEY (staff_role_id) REFERENCES staff_roles (id) ON DELETE CASCADE ON UPDATE CASCADE
|
|---|
| 33 | );
|
|---|
| 34 |
|
|---|
| 35 | CREATE TABLE IF NOT EXISTS back_staff (
|
|---|
| 36 | employee_id BIGSERIAL PRIMARY KEY,
|
|---|
| 37 | staff_role_id BIGINT NOT NULL,
|
|---|
| 38 | FOREIGN KEY (employee_id) REFERENCES employees (user_id) ON DELETE CASCADE ON UPDATE CASCADE,
|
|---|
| 39 | FOREIGN KEY (staff_role_id) REFERENCES staff_roles (id) ON DELETE CASCADE ON UPDATE CASCADE
|
|---|
| 40 | );
|
|---|
| 41 |
|
|---|
| 42 | CREATE TABLE IF NOT EXISTS managers (
|
|---|
| 43 | employee_id BIGSERIAL PRIMARY KEY,
|
|---|
| 44 | FOREIGN KEY (employee_id) REFERENCES employees (user_id) ON DELETE CASCADE ON UPDATE CASCADE
|
|---|
| 45 | );
|
|---|
| 46 |
|
|---|
| 47 | CREATE TABLE IF NOT EXISTS shifts (
|
|---|
| 48 | id BIGSERIAL PRIMARY KEY,
|
|---|
| 49 | date DATE NOT NULL,
|
|---|
| 50 | start_time TIME NOT NULL,
|
|---|
| 51 | end_time TIME NOT NULL,
|
|---|
| 52 | manager_id BIGINT NOT NULL,
|
|---|
| 53 | FOREIGN KEY (manager_id) REFERENCES managers (employee_id) ON DELETE CASCADE ON UPDATE CASCADE
|
|---|
| 54 | );
|
|---|
| 55 |
|
|---|
| 56 | CREATE TABLE IF NOT EXISTS assignments (
|
|---|
| 57 | id BIGSERIAL PRIMARY KEY,
|
|---|
| 58 | clock_in_time TIME,
|
|---|
| 59 | clock_out_time TIME,
|
|---|
| 60 | manager_id BIGINT NOT NULL,
|
|---|
| 61 | employee_id BIGINT NOT NULL,
|
|---|
| 62 | shift_id BIGINT NOT NULL,
|
|---|
| 63 | FOREIGN KEY (manager_id) REFERENCES managers (employee_id) ON DELETE CASCADE ON UPDATE CASCADE,
|
|---|
| 64 | FOREIGN KEY (employee_id) REFERENCES employees (user_id) ON DELETE CASCADE ON UPDATE CASCADE,
|
|---|
| 65 | FOREIGN KEY (shift_id) REFERENCES shifts (id) ON DELETE CASCADE ON UPDATE CASCADE
|
|---|
| 66 | );
|
|---|
| 67 |
|
|---|
| 68 | CREATE TABLE IF NOT EXISTS tables (
|
|---|
| 69 | table_number BIGSERIAL PRIMARY KEY,
|
|---|
| 70 | seat_capacity INT NOT NULL
|
|---|
| 71 | );
|
|---|
| 72 |
|
|---|
| 73 | CREATE TABLE IF NOT EXISTS reservations (
|
|---|
| 74 | id BIGSERIAL PRIMARY KEY,
|
|---|
| 75 | user_id BIGINT NOT NULL,
|
|---|
| 76 | creation_timestamp TIMESTAMP NOT NULL,
|
|---|
| 77 | datetime TIMESTAMP NOT NULL,
|
|---|
| 78 | number_of_people BIGINT NOT NULL,
|
|---|
| 79 | stay_length DECIMAL(10,2) NULL, --hours
|
|---|
| 80 | FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE ON UPDATE CASCADE
|
|---|
| 81 | );
|
|---|
| 82 |
|
|---|
| 83 | CREATE TABLE IF NOT EXISTS frontstaff_managed_reservations (
|
|---|
| 84 | id BIGSERIAL PRIMARY KEY,
|
|---|
| 85 | reservation_id BIGINT NOT NULL,
|
|---|
| 86 | front_staff_id BIGINT NOT NULL,
|
|---|
| 87 | table_number BIGINT NOT NULL,
|
|---|
| 88 | FOREIGN KEY (reservation_id) REFERENCES reservations (id) ON DELETE CASCADE ON UPDATE CASCADE,
|
|---|
| 89 | FOREIGN KEY (front_staff_id) REFERENCES front_staff (employee_id) ON DELETE CASCADE ON UPDATE CASCADE,
|
|---|
| 90 | FOREIGN KEY (table_number) REFERENCES tables (table_number) ON DELETE CASCADE ON UPDATE CASCADE
|
|---|
| 91 | );
|
|---|
| 92 |
|
|---|
| 93 | CREATE TABLE IF NOT EXISTS categories (
|
|---|
| 94 | id BIGSERIAL PRIMARY KEY,
|
|---|
| 95 | name VARCHAR(255) NOT NULL,
|
|---|
| 96 | is_available BOOLEAN NOT NULL DEFAULT TRUE
|
|---|
| 97 | );
|
|---|
| 98 |
|
|---|
| 99 | CREATE TABLE IF NOT EXISTS products (
|
|---|
| 100 | id BIGSERIAL PRIMARY KEY,
|
|---|
| 101 | name VARCHAR(255) NOT NULL,
|
|---|
| 102 | description VARCHAR(1000) NOT NULL,
|
|---|
| 103 | price DECIMAL(10,2) NOT NULL,
|
|---|
| 104 | category_id BIGINT NOT NULL,
|
|---|
| 105 | manage_inventory BOOLEAN NOT NULL DEFAULT FALSE,
|
|---|
| 106 | tax_class VARCHAR(4) NOT NULL,
|
|---|
| 107 | FOREIGN KEY (category_id) REFERENCES categories (id) ON DELETE CASCADE ON UPDATE CASCADE
|
|---|
| 108 | );
|
|---|
| 109 |
|
|---|
| 110 | CREATE TABLE IF NOT EXISTS inventories (
|
|---|
| 111 | product_id BIGSERIAL PRIMARY KEY,
|
|---|
| 112 | quantity INT NOT NULL,
|
|---|
| 113 | restock_level INT NULL,
|
|---|
| 114 | FOREIGN KEY (product_id) REFERENCES products (id) ON DELETE CASCADE ON UPDATE CASCADE
|
|---|
| 115 | );
|
|---|
| 116 |
|
|---|
| 117 | CREATE TABLE IF NOT EXISTS orders (
|
|---|
| 118 | id BIGSERIAL PRIMARY KEY,
|
|---|
| 119 | status VARCHAR(255) NOT NULL DEFAULT 'PENDING',
|
|---|
| 120 | datetime TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|---|
| 121 | employee_id BIGINT DEFAULT NULL,
|
|---|
| 122 | FOREIGN KEY (employee_id) REFERENCES employees(user_id) ON DELETE SET NULL ON UPDATE CASCADE
|
|---|
| 123 | );
|
|---|
| 124 |
|
|---|
| 125 | CREATE TABLE IF NOT EXISTS order_items (
|
|---|
| 126 | id BIGSERIAL PRIMARY KEY,
|
|---|
| 127 | order_id BIGINT NOT NULL,
|
|---|
| 128 | product_id BIGINT NOT NULL,
|
|---|
| 129 | is_processed BOOLEAN NOT NULL DEFAULT FALSE,
|
|---|
| 130 | quantity INT NOT NULL,
|
|---|
| 131 | price DECIMAL(10,2) NOT NULL,
|
|---|
| 132 | created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|---|
| 133 | FOREIGN KEY (order_id) REFERENCES orders (id) ON DELETE CASCADE ON UPDATE CASCADE,
|
|---|
| 134 | FOREIGN KEY (product_id) REFERENCES products (id) ON DELETE CASCADE ON UPDATE CASCADE
|
|---|
| 135 | );
|
|---|
| 136 |
|
|---|
| 137 | CREATE TABLE IF NOT EXISTS tab_orders (
|
|---|
| 138 | order_id BIGSERIAL PRIMARY KEY,
|
|---|
| 139 | table_number BIGINT NOT NULL,
|
|---|
| 140 | FOREIGN KEY (order_id) REFERENCES orders (id) ON DELETE CASCADE ON UPDATE CASCADE,
|
|---|
| 141 | FOREIGN KEY (table_number) REFERENCES tables (table_number) ON DELETE CASCADE ON UPDATE CASCADE
|
|---|
| 142 | );
|
|---|
| 143 |
|
|---|
| 144 | CREATE TABLE IF NOT EXISTS online_orders (
|
|---|
| 145 | order_id BIGSERIAL PRIMARY KEY,
|
|---|
| 146 | delivery_address VARCHAR(255) NOT NULL,
|
|---|
| 147 | customer_id BIGINT NOT NULL,
|
|---|
| 148 | FOREIGN KEY (customer_id) REFERENCES customers (user_id) ON DELETE CASCADE ON UPDATE CASCADE,
|
|---|
| 149 | FOREIGN KEY (order_id) REFERENCES orders (id) ON DELETE CASCADE ON UPDATE CASCADE
|
|---|
| 150 | );
|
|---|
| 151 |
|
|---|
| 152 | CREATE TABLE IF NOT EXISTS payments (
|
|---|
| 153 | id BIGSERIAL PRIMARY KEY,
|
|---|
| 154 | order_id BIGINT NOT NULL,
|
|---|
| 155 | amount DECIMAL(10,2) NOT NULL,
|
|---|
| 156 | payment_type VARCHAR(32) NOT NULL,
|
|---|
| 157 | tip_amount DECIMAL(10,2) NOT NULL DEFAULT 0,
|
|---|
| 158 | created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|---|
| 159 | FOREIGN KEY (order_id) REFERENCES orders (id) ON DELETE CASCADE ON UPDATE CASCADE
|
|---|
| 160 | );
|
|---|
| 161 |
|
|---|
| 162 | -- ASSERTIONS
|
|---|
| 163 | ALTER TABLE employees
|
|---|
| 164 | ADD CONSTRAINT employees_net_salary_nonneg CHECK (net_salary IS NULL OR net_salary >= 0),
|
|---|
| 165 | ADD CONSTRAINT employees_gross_salary_nonneg CHECK (gross_salary IS NULL OR gross_salary >= 0);
|
|---|
| 166 |
|
|---|
| 167 | ALTER TABLE inventories
|
|---|
| 168 | ADD CONSTRAINT inventories_qty_nonneg CHECK (quantity >= 0),
|
|---|
| 169 | ADD CONSTRAINT inventories_restock_nonneg CHECK (restock_level IS NULL OR restock_level >= 0);
|
|---|
| 170 |
|
|---|
| 171 | ALTER TABLE products
|
|---|
| 172 | ADD CONSTRAINT products_price_nonneg CHECK (price >= 0);
|
|---|
| 173 |
|
|---|
| 174 | ALTER TABLE order_items
|
|---|
| 175 | ADD CONSTRAINT order_items_qty_pos CHECK (quantity > 0),
|
|---|
| 176 | ADD CONSTRAINT order_items_price_nonneg CHECK (price >= 0);
|
|---|
| 177 |
|
|---|
| 178 | ALTER TABLE payments
|
|---|
| 179 | ADD CONSTRAINT payments_amount_nonneg CHECK (amount >= 0),
|
|---|
| 180 | ADD CONSTRAINT payments_tip_nonneg CHECK (tip_amount >= 0);
|
|---|
| 181 |
|
|---|
| 182 | ALTER TABLE tables
|
|---|
| 183 | ADD CONSTRAINT tables_capacity_pos CHECK (seat_capacity > 0);
|
|---|
| 184 |
|
|---|
| 185 | ALTER TABLE reservations
|
|---|
| 186 | ADD CONSTRAINT reservations_people_pos CHECK (number_of_people > 0);
|
|---|
| 187 |
|
|---|
| 188 | ALTER TABLE front_staff
|
|---|
| 189 | ADD CONSTRAINT front_staff_tip_pct CHECK (tip_percent IS NULL OR (tip_percent >= 0 AND tip_percent <= 100));
|
|---|
| 190 |
|
|---|
| 191 | -- FUNCTIONS & TRIGGERS
|
|---|
| 192 | CREATE OR REPLACE FUNCTION enforce_no_double_booking()
|
|---|
| 193 | RETURNS trigger AS $$
|
|---|
| 194 | DECLARE
|
|---|
| 195 | new_span tsrange;
|
|---|
| 196 | conflict_exists boolean;
|
|---|
| 197 | BEGIN
|
|---|
| 198 | SELECT tsrange(
|
|---|
| 199 | r.datetime,
|
|---|
| 200 | r.datetime + (COALESCE(r.stay_length, 2) * INTERVAL '1 hour'),
|
|---|
| 201 | '[)'
|
|---|
| 202 | ) INTO new_span
|
|---|
| 203 | FROM reservations r
|
|---|
| 204 | WHERE r.id = NEW.reservation_id;
|
|---|
| 205 |
|
|---|
| 206 | IF new_span IS NULL THEN
|
|---|
| 207 | RAISE EXCEPTION 'Reservation % not found or invalid', NEW.reservation_id;
|
|---|
| 208 | END IF;
|
|---|
| 209 |
|
|---|
| 210 | SELECT EXISTS (
|
|---|
| 211 | SELECT 1
|
|---|
| 212 | FROM frontstaff_managed_reservations fmr
|
|---|
| 213 | JOIN reservations r2 ON r2.id = fmr.reservation_id
|
|---|
| 214 | WHERE fmr.table_number = NEW.table_number
|
|---|
| 215 | AND (NEW.id IS NULL OR fmr.id <> NEW.id)
|
|---|
| 216 | AND tsrange(
|
|---|
| 217 | r2.datetime,
|
|---|
| 218 | r2.datetime + (COALESCE(r2.stay_length, 2) * INTERVAL '1 hour'),
|
|---|
| 219 | '[)'
|
|---|
| 220 | ) && new_span
|
|---|
| 221 | ) INTO conflict_exists;
|
|---|
| 222 |
|
|---|
| 223 | IF conflict_exists THEN
|
|---|
| 224 | RAISE EXCEPTION 'Double booking prevented: table % has overlapping reservations', NEW.table_number;
|
|---|
| 225 | END IF;
|
|---|
| 226 |
|
|---|
| 227 | RETURN NEW;
|
|---|
| 228 | END;
|
|---|
| 229 | $$ LANGUAGE plpgsql;
|
|---|
| 230 |
|
|---|
| 231 | DROP TRIGGER IF EXISTS trg_no_double_booking_ins ON frontstaff_managed_reservations;
|
|---|
| 232 | CREATE CONSTRAINT TRIGGER trg_no_double_booking_ins
|
|---|
| 233 | AFTER INSERT ON frontstaff_managed_reservations
|
|---|
| 234 | DEFERRABLE INITIALLY DEFERRED
|
|---|
| 235 | FOR EACH ROW EXECUTE FUNCTION enforce_no_double_booking();
|
|---|
| 236 |
|
|---|
| 237 | DROP TRIGGER IF EXISTS trg_no_double_booking_upd ON frontstaff_managed_reservations;
|
|---|
| 238 | CREATE CONSTRAINT TRIGGER trg_no_double_booking_upd
|
|---|
| 239 | AFTER UPDATE ON frontstaff_managed_reservations
|
|---|
| 240 | DEFERRABLE INITIALLY DEFERRED
|
|---|
| 241 | FOR EACH ROW EXECUTE FUNCTION enforce_no_double_booking();
|
|---|
| 242 |
|
|---|
| 243 | CREATE INDEX IF NOT EXISTS fmr_table_idx ON frontstaff_managed_reservations (table_number);
|
|---|
| 244 | CREATE INDEX IF NOT EXISTS reservations_span_expr_gist
|
|---|
| 245 | ON reservations USING gist (
|
|---|
| 246 | tsrange(
|
|---|
| 247 | datetime,
|
|---|
| 248 | datetime + (COALESCE(stay_length, 2) * INTERVAL '1 hour'),
|
|---|
| 249 | '[)'
|
|---|
| 250 | )
|
|---|
| 251 | );
|
|---|
| 252 | CREATE INDEX IF NOT EXISTS idx_payments_order ON payments(order_id);
|
|---|
| 253 | CREATE INDEX IF NOT EXISTS idx_payments_created_at ON payments(created_at);
|
|---|
| 254 |
|
|---|
| 255 | CREATE OR REPLACE FUNCTION fmr_capacity_guard() RETURNS trigger AS $$
|
|---|
| 256 | DECLARE
|
|---|
| 257 | cap int;
|
|---|
| 258 | party bigint;
|
|---|
| 259 | BEGIN
|
|---|
| 260 | SELECT seat_capacity INTO cap FROM tables WHERE table_number = NEW.table_number;
|
|---|
| 261 | SELECT number_of_people INTO party FROM reservations WHERE id = NEW.reservation_id;
|
|---|
| 262 |
|
|---|
| 263 | IF cap IS NULL OR party IS NULL THEN
|
|---|
| 264 | RAISE EXCEPTION 'Invalid reservation % or table %', NEW.reservation_id, NEW.table_number;
|
|---|
| 265 | END IF;
|
|---|
| 266 |
|
|---|
| 267 | IF party > cap THEN
|
|---|
| 268 | RAISE EXCEPTION 'Party size % exceeds capacity % for table %',
|
|---|
| 269 | party, cap, NEW.table_number;
|
|---|
| 270 | END IF;
|
|---|
| 271 |
|
|---|
| 272 | RETURN NEW;
|
|---|
| 273 | END;
|
|---|
| 274 | $$ LANGUAGE plpgsql;
|
|---|
| 275 |
|
|---|
| 276 | DROP TRIGGER IF EXISTS trg_fmr_capacity_ins ON frontstaff_managed_reservations;
|
|---|
| 277 | CREATE TRIGGER trg_fmr_capacity_ins
|
|---|
| 278 | BEFORE INSERT ON frontstaff_managed_reservations
|
|---|
| 279 | FOR EACH ROW EXECUTE FUNCTION fmr_capacity_guard();
|
|---|
| 280 |
|
|---|
| 281 | DROP TRIGGER IF EXISTS trg_fmr_capacity_upd ON frontstaff_managed_reservations;
|
|---|
| 282 | CREATE TRIGGER trg_fmr_capacity_upd
|
|---|
| 283 | BEFORE UPDATE ON frontstaff_managed_reservations
|
|---|
| 284 | FOR EACH ROW EXECUTE FUNCTION fmr_capacity_guard();
|
|---|
| 285 |
|
|---|
| 286 | CREATE OR REPLACE FUNCTION payments_mark_order_paid() RETURNS trigger AS $$
|
|---|
| 287 | BEGIN
|
|---|
| 288 | UPDATE orders
|
|---|
| 289 | SET status = 'PAID'
|
|---|
| 290 | WHERE id = NEW.order_id;
|
|---|
| 291 | RETURN NEW;
|
|---|
| 292 | END;
|
|---|
| 293 | $$ LANGUAGE plpgsql;
|
|---|
| 294 |
|
|---|
| 295 | DROP TRIGGER IF EXISTS trg_payments_mark_order_paid ON payments;
|
|---|
| 296 | CREATE TRIGGER trg_payments_mark_order_paid
|
|---|
| 297 | AFTER INSERT ON payments
|
|---|
| 298 | FOR EACH ROW EXECUTE FUNCTION payments_mark_order_paid();
|
|---|
| 299 |
|
|---|