DatabaseProgramming: proceduri_funkcii_trigeri.sql

File proceduri_funkcii_trigeri.sql, 21.6 KB (added by 231090, 5 days ago)
Line 
1
2--T/F na is_active
3CREATE OR REPLACE PROCEDURE activate_deactivate_service(
4 p_service_id INT,
5 p_is_active BOOLEAN
6)
7 LANGUAGE plpgsql
8AS
9$$
10BEGIN
11 UPDATE service
12 SET is_active = p_is_active
13 WHERE service_id = p_service_id;
14
15 IF NOT FOUND THEN
16 RAISE EXCEPTION 'Service % does not exist.', p_service_id;
17 END IF;
18
19 RAISE NOTICE 'Service % is now %.', p_service_id,
20 CASE WHEN p_is_active THEN 'active' ELSE 'inactive' END;
21END;
22$$;
23
24CALL activate_deactivate_service(1, FALSE);
25
26CALL activate_deactivate_service(1, TRUE);
27
28CALL activate_deactivate_service(9999, TRUE);
29
30CREATE OR REPLACE PROCEDURE confirm_appointment(
31 p_appointment_id INT
32)
33 LANGUAGE plpgsql
34AS
35$$
36BEGIN
37 UPDATE appointment
38 SET status = 'confirmed'
39 WHERE appointment_id = p_appointment_id
40 AND status = 'pending';
41
42 IF NOT FOUND THEN
43 RAISE EXCEPTION 'Appointment % does not exist or is not in pending status.', p_appointment_id;
44 END IF;
45
46 RAISE NOTICE 'Appointment % confirmed.', p_appointment_id;
47END;
48$$;
49
50-- Confirm na pending appointment
51-- (prosledi go istoto id sto ke go stavis i vo testot za complete)
52CALL confirm_appointment(2751692);
53
54
55
56CREATE OR REPLACE PROCEDURE complete_appointment(
57 p_appointment_id INT
58)
59 LANGUAGE plpgsql
60AS
61$$
62BEGIN
63 UPDATE appointment
64 SET status = 'completed'
65 WHERE appointment_id = p_appointment_id
66 AND status = 'confirmed';
67
68 IF NOT FOUND THEN
69 RAISE EXCEPTION 'Appointment % does not exist or is not in confirmed status.', p_appointment_id;
70 END IF;
71
72 RAISE NOTICE 'Appointment % completed.', p_appointment_id;
73END;
74$$;
75
76-- Complete na confirmed appointment
77CALL complete_appointment(2751692);
78
79
80-- vazno e deka staff i service treba da se od ISTA company
81CREATE OR REPLACE PROCEDURE add_staff_to_service(
82 p_staff_id INT,
83 p_service_id INT
84)
85 LANGUAGE plpgsql
86AS
87$$
88DECLARE
89 v_exists BOOLEAN;
90BEGIN
91 IF NOT EXISTS (SELECT 1 FROM staff WHERE staff_id = p_staff_id) THEN
92 RAISE EXCEPTION 'Staff % does not exist.', p_staff_id;
93 END IF;
94
95 IF NOT EXISTS (SELECT 1 FROM service WHERE service_id = p_service_id) THEN
96 RAISE EXCEPTION 'Service % does not exist.', p_service_id;
97 END IF;
98
99 IF NOT EXISTS (SELECT 1
100 FROM staff s
101 JOIN company_location cl ON cl.location_id = s.location_id
102 WHERE s.staff_id = p_staff_id
103 AND cl.company_id = (SELECT company_id FROM service WHERE service_id = p_service_id)) THEN
104 RAISE EXCEPTION 'Staff % and service % do not belong to the same company.', p_staff_id, p_service_id;
105 END IF;
106
107 -- INSERT INTO staff_service (staff_id, service_id)
108-- VALUES (p_staff_id, p_service_id)
109-- ON CONFLICT DO NOTHING;
110
111 SELECT EXISTS (SELECT 1
112 FROM staff_service
113 WHERE staff_id = p_staff_id
114 AND service_id = p_service_id)
115 INTO v_exists;
116
117 IF v_exists THEN
118 RAISE NOTICE 'Staff % is ALREADY assigned to service %.', p_staff_id, p_service_id;
119 ELSE
120 INSERT INTO staff_service (staff_id, service_id)
121 VALUES (p_staff_id, p_service_id);
122
123 RAISE NOTICE 'Staff % SUCCESSFULLY assigned to service %.', p_staff_id, p_service_id;
124 END IF;
125
126END;
127$$;
128
129
130
131CREATE OR REPLACE PROCEDURE remove_staff_from_service(
132 p_staff_id INT,
133 p_service_id INT
134)
135 LANGUAGE plpgsql
136AS
137$$
138BEGIN
139 DELETE
140 FROM staff_service
141 WHERE staff_id = p_staff_id
142 AND service_id = p_service_id;
143
144 IF NOT FOUND THEN
145 RAISE EXCEPTION 'Assignment between staff % and service % does not exist.',
146 p_staff_id, p_service_id;
147 END IF;
148
149 RAISE NOTICE 'Staff % removed from service %.', p_staff_id, p_service_id;
150END;
151$$;
152
153-- Assign
154--243 company_id
155--3347 location_id
156--173 service_id
157
158CALL add_staff_to_service(108615, 173);
159CALL remove_staff_from_service(108615, 173);
160CALL add_staff_to_service(108615, 19);
161
162CREATE OR REPLACE FUNCTION get_appointment_total(
163 p_appointment_id INT
164)
165 RETURNS NUMERIC(10, 2)
166 LANGUAGE plpgsql
167AS
168$$
169DECLARE
170 v_total NUMERIC(10, 2);
171BEGIN
172 SELECT COALESCE(SUM(price), 0)
173 INTO v_total
174 FROM appointment_service
175 WHERE appointment_id = p_appointment_id;
176
177 IF NOT FOUND THEN
178 RAISE EXCEPTION 'Appointment % does not exist.', p_appointment_id;
179 END IF;
180
181 RETURN v_total;
182END;
183$$;
184
185SELECT get_appointment_total(986317);
186-- rez 85
187SELECT get_appointment_total(986318);
188-- rez 240
189
190--COMPLEX
191
192CREATE OR REPLACE PROCEDURE staff_add_blocked_time(
193 p_staff_id INT,
194 p_start_datetime TIMESTAMPTZ,
195 p_end_datetime TIMESTAMPTZ,
196 p_reason TEXT DEFAULT NULL
197)
198 LANGUAGE plpgsql
199AS
200$$
201BEGIN
202
203
204 IF p_end_datetime <= p_start_datetime THEN
205 RAISE EXCEPTION 'end_datetime must be after start_datetime.';
206 END IF;
207
208
209 IF NOT EXISTS (SELECT 1
210 FROM staff s
211 JOIN "user" u ON u.user_id = s.staff_id
212 WHERE s.staff_id = p_staff_id
213 AND u.is_active = TRUE) THEN
214 RAISE EXCEPTION 'Staff % does not exist or is not active.', p_staff_id;
215 END IF;
216
217
218 IF EXISTS (SELECT 1
219 FROM blocked_time
220 WHERE staff_id = p_staff_id
221 AND start_datetime < p_end_datetime
222 AND end_datetime > p_start_datetime) THEN
223 RAISE EXCEPTION 'Blocked time overlaps with an existing blocked period for staff %.', p_staff_id;
224 END IF;
225
226
227 IF EXISTS (SELECT 1
228 FROM appointment
229 WHERE staff_id = p_staff_id
230 AND status <> 'cancelled'
231 AND (appointment_date + appointment_time)::TIMESTAMP AT TIME ZONE 'UTC' < p_end_datetime
232 AND (appointment_date + end_time)::TIMESTAMP AT TIME ZONE 'UTC' > p_start_datetime) THEN
233 RAISE EXCEPTION 'Blocked time overlaps with an existing appointment for staff %.', p_staff_id;
234 END IF;
235
236
237 INSERT INTO blocked_time (staff_id, start_datetime, end_datetime, reason)
238 VALUES (p_staff_id, p_start_datetime, p_end_datetime, p_reason);
239
240
241 DELETE
242 FROM staff_time_slot
243 WHERE staff_id = p_staff_id
244 AND appointment_id IS NULL
245 AND slot_start >= (p_start_datetime AT TIME ZONE 'UTC')
246 AND slot_start < (p_end_datetime AT TIME ZONE 'UTC');
247
248END;
249$$;
250
251
252
253-- dodaj block i izbrisi (utre 12-13)
254CALL staff_add_blocked_time(
255 108615,
256 (CURRENT_DATE + 1 + TIME '12:00')::TIMESTAMPTZ,
257 (CURRENT_DATE + 1 + TIME '13:00')::TIMESTAMPTZ,
258 'Personal errand'
259 );
260
261-- Test error: end before start
262CALL staff_add_blocked_time(
263 108615,
264 (CURRENT_DATE + 1 + TIME '12:00')::TIMESTAMPTZ,
265 (CURRENT_DATE + 1 + TIME '10:00')::TIMESTAMPTZ,
266 NULL
267 );
268
269-- Test error: overlaps with the block we just inserted
270CALL staff_add_blocked_time(
271 108615,
272 (CURRENT_DATE + 1 + TIME '12:30')::TIMESTAMPTZ,
273 (CURRENT_DATE + 1 + TIME '12:40')::TIMESTAMPTZ,
274 NULL
275 );
276
277CREATE OR REPLACE PROCEDURE client_book_appointment(
278 p_client_id INT,
279 p_staff_id INT,
280 p_location_id INT,
281 p_date DATE,
282 p_time TIME,
283 p_service_ids INT[]
284)
285 LANGUAGE plpgsql
286AS
287$$
288DECLARE
289 v_total_duration INT;
290 v_end_time TIME;
291 v_day_of_week day_of_week_enum;
292 v_appointment_id INT;
293 v_avail_start TIME;
294 v_avail_end TIME;
295 v_company_id INT;
296BEGIN
297
298 -- 1. Service list must not be empty
299 IF p_service_ids IS NULL OR array_length(p_service_ids, 1) = 0 THEN
300 RAISE EXCEPTION 'At least one service must be provided.';
301 END IF;
302
303 -- 2. Date/time must not be in the past
304 IF p_date < CURRENT_DATE OR (p_date = CURRENT_DATE AND p_time <= CURRENT_TIME) THEN
305 RAISE EXCEPTION 'Appointment date/time is in the past.';
306 END IF;
307
308 -- 3. Client exists and is active
309 IF NOT EXISTS (SELECT 1
310 FROM client
311 JOIN "user" u ON u.user_id = client_id
312 WHERE client_id = p_client_id
313 AND u.is_active = TRUE) THEN
314 RAISE EXCEPTION 'Client % does not exist or is not active.', p_client_id;
315 END IF;
316
317 -- 4. Staff exists, is active, and works at the given location
318 IF NOT EXISTS (SELECT 1
319 FROM staff s
320 JOIN "user" u ON u.user_id = s.staff_id
321 WHERE s.staff_id = p_staff_id
322 AND s.location_id = p_location_id
323 AND u.is_active = TRUE) THEN
324 RAISE EXCEPTION 'Staff % does not exist, is not active, or does not work at location %.', p_staff_id, p_location_id;
325 END IF;
326
327 -- 5. Resolve company_id from location
328 SELECT company_id
329 INTO v_company_id
330 FROM company_location
331 WHERE location_id = p_location_id;
332
333 -- 6. All services valid, active, and belong to this company
334 IF (SELECT COUNT(*)
335 FROM service
336 WHERE service_id = ANY (p_service_ids)
337 AND is_active = TRUE
338 AND company_id = v_company_id) <> array_length(p_service_ids, 1) THEN
339 RAISE EXCEPTION 'One or more services are invalid, inactive, or do not belong to this company.';
340 END IF;
341
342 -- 7. Staff is assigned to all requested services
343 IF (SELECT COUNT(*)
344 FROM staff_service
345 WHERE staff_id = p_staff_id
346 AND service_id = ANY (p_service_ids)) <> array_length(p_service_ids, 1) THEN
347 RAISE EXCEPTION 'Staff % is not assigned to one or more of the requested services.', p_staff_id;
348 END IF;
349
350 -- 8. Calculate total duration and derive end_time
351 SELECT COALESCE(SUM(duration_minutes), 0)
352 INTO v_total_duration
353 FROM service
354 WHERE service_id = ANY (p_service_ids);
355
356 v_end_time := p_time + (v_total_duration || ' minutes')::INTERVAL;
357 v_day_of_week := LOWER(TRIM(TO_CHAR(p_date, 'FMDay')))::day_of_week_enum;
358
359 -- 9. Staff availability covers the full window
360 SELECT start_time, end_time
361 INTO v_avail_start, v_avail_end
362 FROM staff_availability
363 WHERE staff_id = p_staff_id
364 AND day_of_week = v_day_of_week;
365
366 IF NOT FOUND THEN
367 RAISE EXCEPTION 'Staff % has no availability on %.', p_staff_id, v_day_of_week;
368 END IF;
369
370 IF p_time < v_avail_start OR v_end_time > v_avail_end THEN
371 RAISE EXCEPTION 'Appointment window %–% falls outside staff availability %–% on %.',
372 p_time, v_end_time, v_avail_start, v_avail_end, v_day_of_week;
373 END IF;
374
375 -- 10. Location is open and appointment fits within business hours
376 IF NOT EXISTS (SELECT 1
377 FROM business_hours
378 WHERE location_id = p_location_id
379 AND day_of_week = v_day_of_week
380 AND is_closed = FALSE
381 AND open_time <= p_time
382 AND close_time >= v_end_time) THEN
383 RAISE EXCEPTION 'Appointment window %–% falls outside business hours for location % on %.',
384 p_time, v_end_time, p_location_id, v_day_of_week;
385 END IF;
386
387 -- 11. No overlap with blocked_time
388 IF EXISTS (SELECT 1
389 FROM blocked_time
390 WHERE staff_id = p_staff_id
391 AND (p_date + p_time)::TIMESTAMP AT TIME ZONE 'UTC' < end_datetime
392 AND (p_date + v_end_time)::TIMESTAMP AT TIME ZONE 'UTC' > start_datetime) THEN
393 RAISE EXCEPTION 'Time slot overlaps with a blocked period for staff %.', p_staff_id;
394 END IF;
395
396 -- 12. No overlap with existing appointments
397 IF EXISTS (SELECT 1
398 FROM appointment
399 WHERE staff_id = p_staff_id
400 AND appointment_date = p_date
401 AND status <> 'cancelled'
402 AND appointment_time < v_end_time
403 AND end_time > p_time) THEN
404 RAISE EXCEPTION 'Time slot %–% conflicts with an existing appointment for staff %.',
405 p_time, v_end_time, p_staff_id;
406 END IF;
407
408 -- 13. Insert appointment
409 INSERT INTO appointment (client_id, staff_id, location_id, appointment_date, appointment_time, end_time, status)
410 VALUES (p_client_id, p_staff_id, p_location_id, p_date, p_time, v_end_time, 'pending')
411 RETURNING appointment_id INTO v_appointment_id;
412
413 -- 14. Snapshot services
414 INSERT INTO appointment_service (appointment_id, service_id, duration_minutes, price)
415 SELECT v_appointment_id, service_id, duration_minutes, price
416 FROM service
417 WHERE service_id = ANY (p_service_ids);
418
419 -- 15. Mark time slots as occupied
420 UPDATE staff_time_slot
421 SET appointment_id = v_appointment_id
422 WHERE staff_id = p_staff_id
423 AND slot_start >= (p_date + p_time)::TIMESTAMP
424 AND slot_start < (p_date + v_end_time)::TIMESTAMP
425 AND appointment_id IS NULL;
426
427END;
428$$;
429
430-- Valid booking:
431CALL client_book_appointment(710096, 23918, 3, CURRENT_DATE + 1, '13:00', ARRAY [18, 19]);
432
433-- Test error: date in the past
434CALL client_book_appointment(710096, 23918, 3, CURRENT_DATE - 1, '10:00', ARRAY [18, 19]);
435
436-- Test error: empty service list
437CALL client_book_appointment(710096, 23918, 3, CURRENT_DATE + 1, '10:00', ARRAY []::INT[]);
438
439-- Test error: overlapping slot (run the valid booking twice)
440CALL client_book_appointment(710096, 23918, 3, CURRENT_DATE + 1, '10:00', ARRAY [18, 19]);
441
442-- Test error: staff not assigned to service
443CALL client_book_appointment(710096, 23918, 3, CURRENT_DATE + 1, '14:00', ARRAY [99]);
444
445CREATE OR REPLACE PROCEDURE client_cancel_appointment(
446 p_appointment_id INT,
447 p_client_id INT,
448 p_cancellation_reason TEXT DEFAULT NULL
449)
450 LANGUAGE plpgsql
451AS
452$$
453DECLARE
454 v_rec RECORD;
455BEGIN
456
457 -- 1. Fetch appointment, verify ownership and cancellability
458 SELECT a.status, a.appointment_date, a.appointment_time, a.staff_id
459 INTO v_rec
460 FROM appointment a
461 WHERE a.appointment_id = p_appointment_id
462 AND a.client_id = p_client_id;
463
464 IF NOT FOUND THEN
465 RAISE EXCEPTION 'Appointment % not found or does not belong to client %.',
466 p_appointment_id, p_client_id;
467 END IF;
468
469 IF v_rec.status NOT IN ('pending', 'confirmed') THEN
470 RAISE EXCEPTION 'Appointment % cannot be cancelled (current status: %).',
471 p_appointment_id, v_rec.status;
472 END IF;
473
474 IF (v_rec.appointment_date + v_rec.appointment_time) <= (CURRENT_DATE + CURRENT_TIME) THEN
475 RAISE EXCEPTION 'Appointment % has already started or passed.', p_appointment_id;
476 END IF;
477
478 -- 2. Cancel it
479 UPDATE appointment
480 SET status = 'cancelled',
481 cancelled_at = NOW(),
482 cancellation_reason = p_cancellation_reason
483 WHERE appointment_id = p_appointment_id;
484
485 -- 3. Free up the time slots
486 UPDATE staff_time_slot
487 SET appointment_id = NULL
488 WHERE appointment_id = p_appointment_id;
489
490 RAISE NOTICE 'Appointment % cancelled.', p_appointment_id;
491
492END;
493$$;
494
495-- Cancel appointment 1 belonging to client 1
496CALL client_cancel_appointment(5917661, 710096, 'Changed my mind');
497
498-- Test error: already cancelled (run twice)
499CALL client_cancel_appointment(5917661, 710096, 'Changed my mind');
500
501-- Test error: appointment belongs to a different client or does not exist
502CALL client_cancel_appointment(1, 99, NULL);
503
504CREATE OR REPLACE PROCEDURE generate_invoice(
505 p_appointment_id INT,
506 p_payment_method payment_method_enum,
507 p_promo_codes TEXT[] DEFAULT NULL,
508 p_tax NUMERIC(10, 2) DEFAULT 0
509)
510 LANGUAGE plpgsql
511AS
512$$
513DECLARE
514 v_client_id INT;
515 v_company_id INT;
516 v_subtotal NUMERIC(10, 2);
517 v_discount_total NUMERIC(10, 2) := 0;
518 v_invoice_id INT;
519 v_promo RECORD;
520BEGIN
521
522 -- 1. Appointment exists, is completed, resolve client and company
523 SELECT a.client_id, cl.company_id
524 INTO v_client_id, v_company_id
525 FROM appointment a
526 JOIN company_location cl ON cl.location_id = a.location_id
527 WHERE a.appointment_id = p_appointment_id
528 AND a.status = 'completed';
529
530 IF NOT FOUND THEN
531 RAISE EXCEPTION 'Appointment % does not exist or is not completed.', p_appointment_id;
532 END IF;
533
534 -- 2. No invoice already exists
535 IF EXISTS (SELECT 1 FROM invoice WHERE appointment_id = p_appointment_id) THEN
536 RAISE EXCEPTION 'An invoice already exists for appointment %.', p_appointment_id;
537 END IF;
538
539 -- 3. Subtotal from the appointment_service snapshot
540 SELECT COALESCE(SUM(price), 0)
541 INTO v_subtotal
542 FROM appointment_service
543 WHERE appointment_id = p_appointment_id;
544
545 -- 4. Apply promo codes
546 FOR v_promo IN
547 SELECT pc.promo_id, pc.discount_type, pc.discount_value
548 FROM promo_code pc
549 WHERE pc.code = ANY (p_promo_codes)
550 AND pc.company_id = v_company_id
551 AND CURRENT_DATE BETWEEN pc.valid_from AND pc.valid_until
552 LOOP
553 v_discount_total := v_discount_total + CASE
554 WHEN v_promo.discount_type = 'percentage'
555 THEN ROUND(v_subtotal * (v_promo.discount_value / 100), 2)
556 ELSE v_promo.discount_value
557 END;
558 END LOOP;
559
560 -- Cap discount at subtotal so total never goes negative
561 v_discount_total := LEAST(v_discount_total, v_subtotal);
562
563 -- 5. Insert invoice
564 INSERT INTO invoice (appointment_id, client_id, invoice_date,
565 subtotal, discount_total, tax, payment_method)
566 VALUES (p_appointment_id, v_client_id, CURRENT_DATE,
567 v_subtotal, v_discount_total, p_tax, p_payment_method)
568 RETURNING invoice_id INTO v_invoice_id;
569
570 -- 6. Link promos to invoice
571 INSERT INTO invoice_promo (invoice_id, promo_id)
572 SELECT v_invoice_id, pc.promo_id
573 FROM promo_code pc
574 WHERE pc.code = ANY (p_promo_codes)
575 AND pc.company_id = v_company_id
576 AND CURRENT_DATE BETWEEN pc.valid_from AND pc.valid_until;
577
578 RAISE NOTICE 'Invoice % created for appointment %.', v_invoice_id, p_appointment_id;
579
580END;
581$$;
582
583-- Valid invoice: completed appointment 1, cash, no promos
584CALL generate_invoice(5025426, 'cash', NULL, 0);
585
586-- With a promo code
587CALL generate_invoice(4945256, 'card', ARRAY ['SUMMER10'], 1.50);
588
589-- Test error: invoice already exists (run twice)
590CALL generate_invoice(5025426, 'cash', NULL, 0);
591
592-- Test error: appointment not completed
593CALL generate_invoice(3, 'cash', NULL, 0);
594
595
596
597CREATE OR REPLACE PROCEDURE generate_staff_time_slots_from_to(
598 p_staff_id INT,
599 p_from_date DATE,
600 p_to_date DATE
601)
602LANGUAGE plpgsql
603AS $$
604BEGIN
605
606 IF NOT EXISTS (
607 SELECT 1 FROM staff s
608 JOIN "user" u ON u.user_id = s.staff_id
609 WHERE s.staff_id = p_staff_id AND u.is_active = TRUE
610 ) THEN
611 RAISE EXCEPTION 'Staff % does not exist or is not active.', p_staff_id;
612 END IF;
613
614 IF p_to_date < p_from_date THEN
615 RAISE EXCEPTION 'p_to_date must be on or after p_from_date.';
616 END IF;
617
618 INSERT INTO staff_time_slot (staff_id, slot_start)
619 SELECT p_staff_id, slot
620 FROM generate_series(
621 p_from_date::TIMESTAMP,
622 p_to_date::TIMESTAMP + INTERVAL '1 day' - INTERVAL '15 minutes',
623 INTERVAL '15 minutes'
624 ) AS slot
625 JOIN staff_availability sa
626 ON sa.staff_id = p_staff_id
627 AND sa.day_of_week = LOWER(TRIM(TO_CHAR(slot, 'FMDay')))::day_of_week_enum
628 AND slot::TIME >= sa.start_time
629 AND slot::TIME < sa.end_time
630 WHERE NOT EXISTS (
631 SELECT 1 FROM blocked_time bt
632 WHERE bt.staff_id = p_staff_id
633 AND bt.start_datetime <= slot
634 AND bt.end_datetime > slot
635 )
636 ON CONFLICT DO NOTHING;
637
638 RAISE NOTICE 'Slots generated for staff % — % to %.', p_staff_id, p_from_date, p_to_date;
639
640END;
641$$;
642
643
644
645CREATE OR REPLACE FUNCTION trg_check_appointment_conflicts()
646RETURNS TRIGGER
647LANGUAGE plpgsql
648AS $$
649BEGIN
650 IF EXISTS (
651 SELECT 1
652 FROM appointment
653 WHERE staff_id = NEW.staff_id
654 AND appointment_date = NEW.appointment_date
655 AND appointment_time = NEW.appointment_time
656 AND appointment_id <> NEW.appointment_id
657 AND status <> 'cancelled'
658 ) THEN
659 RAISE EXCEPTION
660 'Staff member % is already booked on % at %.',
661 NEW.staff_id, NEW.appointment_date, NEW.appointment_time
662 USING ERRCODE = 'unique_violation';
663 END IF;
664
665 IF EXISTS (
666 SELECT 1
667 FROM appointment
668 WHERE client_id = NEW.client_id
669 AND appointment_date = NEW.appointment_date
670 AND appointment_id <> NEW.appointment_id
671 AND status <> 'cancelled'
672 AND NEW.appointment_time < end_time
673 AND appointment_time < NEW.end_time
674 ) THEN
675 RAISE EXCEPTION
676 'Client % already has an overlapping appointment on % between % and %.',
677 NEW.client_id, NEW.appointment_date, NEW.appointment_time, NEW.end_time
678 USING ERRCODE = 'unique_violation';
679 END IF;
680
681 RETURN NEW;
682END;
683$$;
684
685
686
687
688CREATE TRIGGER trg_appointment_conflicts
689BEFORE INSERT OR UPDATE OF
690 staff_id, client_id, appointment_date, appointment_time, end_time, status
691ON appointment
692FOR EACH ROW
693EXECUTE FUNCTION trg_check_appointment_conflicts();