| 268 | | Health records must be consistent: |
| 269 | | * A health record must be tied to an appointment that is DONE. |
| 270 | | * The animal in health_records must match the animal of the referenced appointment. |
| 271 | | * The record date must match the appointment date. |
| | 268 | Quiz attempts must obey assessment consistency rules: |
| | 269 | * A quiz attempt must always refer to a valid user and a valid quiz. |
| | 270 | * The achieved score must be between 0 and the quiz total_points. |
| | 271 | * A user should only attempt quizzes that belong to lessons from courses in which the user is enrolled. |
| | 272 | * Invalid quiz attempts are blocked |
| 280 | | v_appt_animal bigint; |
| 281 | | v_appt_status text; |
| 282 | | v_appt_date date; |
| 283 | | BEGIN |
| 284 | | SELECT a.animal_id, a.status, a.date_time::date |
| 285 | | INTO v_appt_animal, v_appt_status, v_appt_date |
| 286 | | FROM appointments a |
| 287 | | WHERE a.appointment_id = NEW.appointment_id; |
| 288 | | |
| 289 | | IF v_appt_animal IS NULL THEN |
| 290 | | RAISE EXCEPTION 'Appointment % not found for health record', NEW.appointment_id; |
| 291 | | END IF; |
| 292 | | |
| 293 | | IF NEW.animal_id <> v_appt_animal THEN |
| 294 | | RAISE EXCEPTION 'Health record animal_id (%) must match appointment animal_id (%) for appointment %', |
| 295 | | NEW.animal_id, v_appt_animal, NEW.appointment_id; |
| 296 | | END IF; |
| 297 | | |
| 298 | | IF v_appt_status <> 'DONE' THEN |
| 299 | | RAISE EXCEPTION 'Cannot insert health record unless appointment % is DONE (current status=%)', |
| 300 | | NEW.appointment_id, v_appt_status; |
| 301 | | END IF; |
| 302 | | |
| 303 | | IF NEW.date <> v_appt_date THEN |
| 304 | | RAISE EXCEPTION 'Health record date (%) must equal appointment date (%) for appointment %', |
| 305 | | NEW.date, v_appt_date, NEW.appointment_id; |
| 306 | | END IF; |
| 307 | | |
| 308 | | RETURN NEW; |
| 309 | | END; |
| 310 | | $$; |
| 311 | | |
| 312 | | DROP TRIGGER IF EXISTS trg_health_records_enforce ON health_records; |
| 313 | | CREATE TRIGGER trg_health_records_enforce |
| | 281 | v_total_points INT; |
| | 282 | BEGIN |
| | 283 | IF NEW.user_id IS NULL THEN |
| | 284 | RAISE EXCEPTION 'Quiz attempt must reference a valid user'; |
| | 285 | END IF; |
| | 286 | |
| | 287 | IF NEW.quiz_id IS NULL THEN |
| | 288 | RAISE EXCEPTION 'Quiz attempt must reference a valid quiz'; |
| | 289 | END IF; |
| | 290 | |
| | 291 | SELECT q.total_points |
| | 292 | INTO v_total_points |
| | 293 | FROM quiz q |
| | 294 | WHERE q.quiz_id = NEW.quiz_id; |
| | 295 | |
| | 296 | IF v_total_points IS NULL THEN |
| | 297 | RAISE EXCEPTION 'Quiz % does not exist', NEW.quiz_id; |
| | 298 | END IF; |
| | 299 | |
| | 300 | IF NEW.score IS NULL THEN |
| | 301 | RAISE EXCEPTION 'Quiz attempt score must not be NULL'; |
| | 302 | END IF; |
| | 303 | |
| | 304 | IF NEW.score < 0 OR NEW.score > v_total_points THEN |
| | 305 | RAISE EXCEPTION |
| | 306 | 'Invalid score % for quiz %, allowed range is 0 to %', |
| | 307 | NEW.score, NEW.quiz_id, v_total_points; |
| | 308 | END IF; |
| | 309 | |
| | 310 | IF NOT EXISTS ( |
| | 311 | SELECT 1 |
| | 312 | FROM enrollment e |
| | 313 | JOIN module m |
| | 314 | ON m.course_id = e.course_id |
| | 315 | JOIN lesson l |
| | 316 | ON l.module_id = m.module_id |
| | 317 | JOIN quiz q |
| | 318 | ON q.lesson_id = l.lesson_id |
| | 319 | WHERE e.user_id = NEW.user_id |
| | 320 | AND q.quiz_id = NEW.quiz_id |
| | 321 | ) THEN |
| | 322 | RAISE EXCEPTION |
| | 323 | 'User % is not enrolled in the course associated with quiz %', |
| | 324 | NEW.user_id, NEW.quiz_id; |
| | 325 | END IF; |
| | 326 | |
| | 327 | RETURN NEW; |
| | 328 | END; |
| | 329 | $$; |
| | 330 | |
| | 331 | DROP TRIGGER IF EXISTS trg_quiz_attempt_enforce ON quiz_attempt; |
| | 332 | |
| | 333 | CREATE TRIGGER trg_quiz_attempt_enforce |