source: backend/src/main/resources/db/migration/V8__appointment_scheduling_integrity.sql

Last change on this file was cdcff72, checked in by MBK <marija.karapandzova@…>, 2 weeks ago

Fix frontend appearance and fix bugs

  • Property mode set to 100644
File size: 4.9 KB
Line 
1-- schema change: NO_SHOW must be added to the existing status constraint before
2-- anything below can ever set it
3ALTER TABLE appointments DROP CONSTRAINT appointments_status_chk;
4ALTER TABLE appointments ADD CONSTRAINT appointments_status_chk
5 CHECK (status IN ('SCHEDULED','COMPLETED','CANCELLED','IN_PROGRESS','NO_SHOW'));
6
7-- No generated column needed - we'll compute the time ranges directly in the triggers
8-- This approach is simpler and avoids immutability constraints
9
10CREATE OR REPLACE FUNCTION is_valid_appointment_transition(p_old TEXT, p_new TEXT)
11RETURNS BOOLEAN
12LANGUAGE sql
13IMMUTABLE
14AS $$
15SELECT CASE
16 WHEN p_old = p_new THEN TRUE
17 WHEN p_old = 'SCHEDULED' AND p_new IN ('IN_PROGRESS', 'COMPLETED', 'CANCELLED') THEN TRUE
18 WHEN p_old = 'IN_PROGRESS' AND p_new IN ('COMPLETED', 'CANCELLED') THEN TRUE
19 ELSE FALSE
20 END;
21$$;
22
23CREATE OR REPLACE FUNCTION trigger_appointments_enforce()
24RETURNS TRIGGER
25LANGUAGE plpgsql
26AS $$
27DECLARE
28 v_combined_datetime TIMESTAMP;
29BEGIN
30 v_combined_datetime := NEW.appointment_date::TIMESTAMP + NEW.appointment_time;
31
32 IF TG_OP = 'INSERT' AND v_combined_datetime < NOW() THEN
33 RAISE EXCEPTION 'Cannot schedule appointment in the past (appointment_date=%, appointment_time=%)',
34 NEW.appointment_date, NEW.appointment_time;
35 END IF;
36
37 IF TG_OP = 'UPDATE' THEN
38 IF NOT is_valid_appointment_transition(OLD.status, NEW.status) THEN
39 RAISE EXCEPTION 'Appointment status cannot transition from % to %',
40 OLD.status, NEW.status;
41 END IF;
42
43 IF NEW.status = 'COMPLETED' AND v_combined_datetime > NOW() THEN
44 RAISE EXCEPTION 'Cannot mark appointment COMPLETED before its scheduled time (scheduled for %)',
45 v_combined_datetime;
46 END IF;
47 END IF;
48
49 RETURN NEW;
50END;
51$$;
52
53DROP TRIGGER IF EXISTS trigger_appointments_enforce ON appointments;
54CREATE TRIGGER trigger_appointments_enforce
55 BEFORE INSERT OR UPDATE
56 ON appointments
57 FOR EACH ROW
58 EXECUTE FUNCTION trigger_appointments_enforce();
59
60CREATE OR REPLACE FUNCTION t1_appointments_no_overlap()
61RETURNS TRIGGER
62LANGUAGE plpgsql
63AS $$
64DECLARE
65 v_new_start TIMESTAMP;
66 v_new_end TIMESTAMP;
67BEGIN
68 IF NEW.status NOT IN ('SCHEDULED', 'IN_PROGRESS', 'COMPLETED') THEN
69 RETURN NEW;
70 END IF;
71
72 v_new_start := NEW.appointment_date::TIMESTAMP + NEW.appointment_time;
73 v_new_end := v_new_start + INTERVAL '30 minutes';
74
75 -- Check for doctor double-booking
76 -- Overlap condition: existing_start < new_end AND new_start < existing_end
77 IF EXISTS (
78 SELECT 1 FROM appointments a
79 WHERE a.doctor_id = NEW.doctor_id
80 AND a.status IN ('SCHEDULED', 'IN_PROGRESS', 'COMPLETED')
81 AND (a.appointment_date::TIMESTAMP + a.appointment_time) < v_new_end
82 AND v_new_start < (a.appointment_date::TIMESTAMP + a.appointment_time + INTERVAL '30 minutes')
83 AND (TG_OP <> 'UPDATE' OR a.appointment_id <> NEW.appointment_id)
84 ) THEN
85 RAISE EXCEPTION 'Doctor % has overlapping appointment at %', NEW.doctor_id, NEW.appointment_date;
86 END IF;
87
88 -- Check for patient double-booking
89 IF EXISTS (
90 SELECT 1 FROM appointments a
91 WHERE a.patient_id = NEW.patient_id
92 AND a.status IN ('SCHEDULED', 'IN_PROGRESS', 'COMPLETED')
93 AND (a.appointment_date::TIMESTAMP + a.appointment_time) < v_new_end
94 AND v_new_start < (a.appointment_date::TIMESTAMP + a.appointment_time + INTERVAL '30 minutes')
95 AND (TG_OP <> 'UPDATE' OR a.appointment_id <> NEW.appointment_id)
96 ) THEN
97 RAISE EXCEPTION 'Patient % has overlapping appointment at %', NEW.patient_id, NEW.appointment_date;
98 END IF;
99
100 RETURN NEW;
101END;
102$$;
103
104DROP TRIGGER IF EXISTS trigger_appointments_no_overlap ON appointments;
105CREATE TRIGGER trigger_appointments_no_overlap
106 BEFORE INSERT OR UPDATE
107 ON appointments
108 FOR EACH ROW
109 EXECUTE FUNCTION t1_appointments_no_overlap();
110
111CREATE OR REPLACE PROCEDURE job_mark_no_show()
112LANGUAGE plpgsql
113AS $$
114BEGIN
115 UPDATE appointments
116 SET status = 'NO_SHOW'
117 WHERE status = 'SCHEDULED'
118 AND (appointment_date::TIMESTAMP + appointment_time) < (NOW() - INTERVAL '45 minutes');
119END;
120$$;
121
122CREATE OR REPLACE VIEW v_overdue_appointments AS
123SELECT
124 a.appointment_id, a.patient_id, p.first_name, p.last_name,
125 a.doctor_id, d.first_name AS doctor_first_name, d.last_name AS doctor_last_name,
126 a.appointment_date, a.appointment_time, a.status,
127 NOW() - (a.appointment_date::TIMESTAMP + a.appointment_time) AS time_overdue
128FROM appointments a
129 JOIN patients p ON a.patient_id = p.patient_id
130 JOIN doctors d ON a.doctor_id = d.doctor_id
131WHERE a.status = 'SCHEDULED'
132 AND (a.appointment_date::TIMESTAMP + a.appointment_time) < (NOW() - INTERVAL '45 minutes')
133ORDER BY time_overdue DESC;
Note: See TracBrowser for help on using the repository browser.