RelationalSchema: ddl.sql

File ddl.sql, 10.3 KB (added by 221128, 6 weeks ago)
Line 
1CREATE 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
10CREATE 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
17CREATE 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
22CREATE TABLE IF NOT EXISTS staff_roles (
23 id BIGSERIAL PRIMARY KEY,
24 name VARCHAR(255) NOT NULL
25);
26
27CREATE 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
35CREATE 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
42CREATE 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
47CREATE 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
56CREATE 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
68CREATE TABLE IF NOT EXISTS tables (
69 table_number BIGSERIAL PRIMARY KEY,
70 seat_capacity INT NOT NULL
71);
72
73CREATE 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
83CREATE 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
93CREATE 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
99CREATE 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
110CREATE 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
117CREATE 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
125CREATE 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
137CREATE 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
144CREATE 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
152CREATE 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
163ALTER 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
167ALTER 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
171ALTER TABLE products
172 ADD CONSTRAINT products_price_nonneg CHECK (price >= 0);
173
174ALTER TABLE order_items
175 ADD CONSTRAINT order_items_qty_pos CHECK (quantity > 0),
176 ADD CONSTRAINT order_items_price_nonneg CHECK (price >= 0);
177
178ALTER TABLE payments
179 ADD CONSTRAINT payments_amount_nonneg CHECK (amount >= 0),
180 ADD CONSTRAINT payments_tip_nonneg CHECK (tip_amount >= 0);
181
182ALTER TABLE tables
183 ADD CONSTRAINT tables_capacity_pos CHECK (seat_capacity > 0);
184
185ALTER TABLE reservations
186 ADD CONSTRAINT reservations_people_pos CHECK (number_of_people > 0);
187
188ALTER 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
192CREATE OR REPLACE FUNCTION enforce_no_double_booking()
193RETURNS trigger AS $$
194DECLARE
195 new_span tsrange;
196 conflict_exists boolean;
197BEGIN
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;
228END;
229$$ LANGUAGE plpgsql;
230
231DROP TRIGGER IF EXISTS trg_no_double_booking_ins ON frontstaff_managed_reservations;
232CREATE CONSTRAINT TRIGGER trg_no_double_booking_ins
233AFTER INSERT ON frontstaff_managed_reservations
234DEFERRABLE INITIALLY DEFERRED
235FOR EACH ROW EXECUTE FUNCTION enforce_no_double_booking();
236
237DROP TRIGGER IF EXISTS trg_no_double_booking_upd ON frontstaff_managed_reservations;
238CREATE CONSTRAINT TRIGGER trg_no_double_booking_upd
239AFTER UPDATE ON frontstaff_managed_reservations
240DEFERRABLE INITIALLY DEFERRED
241FOR EACH ROW EXECUTE FUNCTION enforce_no_double_booking();
242
243CREATE INDEX IF NOT EXISTS fmr_table_idx ON frontstaff_managed_reservations (table_number);
244CREATE 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 );
252CREATE INDEX IF NOT EXISTS idx_payments_order ON payments(order_id);
253CREATE INDEX IF NOT EXISTS idx_payments_created_at ON payments(created_at);
254
255CREATE OR REPLACE FUNCTION fmr_capacity_guard() RETURNS trigger AS $$
256DECLARE
257 cap int;
258 party bigint;
259BEGIN
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;
273END;
274$$ LANGUAGE plpgsql;
275
276DROP TRIGGER IF EXISTS trg_fmr_capacity_ins ON frontstaff_managed_reservations;
277CREATE TRIGGER trg_fmr_capacity_ins
278BEFORE INSERT ON frontstaff_managed_reservations
279FOR EACH ROW EXECUTE FUNCTION fmr_capacity_guard();
280
281DROP TRIGGER IF EXISTS trg_fmr_capacity_upd ON frontstaff_managed_reservations;
282CREATE TRIGGER trg_fmr_capacity_upd
283BEFORE UPDATE ON frontstaff_managed_reservations
284FOR EACH ROW EXECUTE FUNCTION fmr_capacity_guard();
285
286CREATE OR REPLACE FUNCTION payments_mark_order_paid() RETURNS trigger AS $$
287BEGIN
288 UPDATE orders
289 SET status = 'PAID'
290 WHERE id = NEW.order_id;
291 RETURN NEW;
292END;
293$$ LANGUAGE plpgsql;
294
295DROP TRIGGER IF EXISTS trg_payments_mark_order_paid ON payments;
296CREATE TRIGGER trg_payments_mark_order_paid
297AFTER INSERT ON payments
298FOR EACH ROW EXECUTE FUNCTION payments_mark_order_paid();
299