| 1 | CREATE OR REPLACE PROCEDURE sp_enroll_student_in_course(
|
|---|
| 2 | p_student_id INTEGER,
|
|---|
| 3 | p_course_code TEXT,
|
|---|
| 4 | p_academic_year SMALLINT,
|
|---|
| 5 | p_semester SMALLINT,
|
|---|
| 6 | OUT success BOOLEAN,
|
|---|
| 7 | OUT message TEXT
|
|---|
| 8 | )
|
|---|
| 9 | LANGUAGE plpgsql AS $$
|
|---|
| 10 | DECLARE
|
|---|
| 11 | v_pe_id INTEGER;
|
|---|
| 12 | v_se_id INTEGER;
|
|---|
| 13 | v_edition_id INTEGER;
|
|---|
| 14 | v_current_credits INTEGER;
|
|---|
| 15 | v_course_credits INTEGER;
|
|---|
| 16 | BEGIN
|
|---|
| 17 |
|
|---|
| 18 | SELECT id INTO v_pe_id
|
|---|
| 19 | FROM Program_Enrollment
|
|---|
| 20 | WHERE student_id = p_student_id
|
|---|
| 21 | AND date_disenrollment IS NULL
|
|---|
| 22 | AND finished IS NOT TRUE;
|
|---|
| 23 |
|
|---|
| 24 | IF v_pe_id IS NULL THEN
|
|---|
| 25 | success := FALSE;
|
|---|
| 26 | message := 'No active program enrollment found for this student.';
|
|---|
| 27 | RETURN;
|
|---|
| 28 | END IF;
|
|---|
| 29 |
|
|---|
| 30 |
|
|---|
| 31 | SELECT id INTO v_se_id
|
|---|
| 32 | FROM Semester_Enrollment
|
|---|
| 33 | WHERE program_enrollment_id = v_pe_id
|
|---|
| 34 | AND academic_year = p_academic_year
|
|---|
| 35 | AND semester::int = p_semester::int;
|
|---|
| 36 |
|
|---|
| 37 | IF v_se_id IS NULL THEN
|
|---|
| 38 | success := FALSE;
|
|---|
| 39 | message := format('Student is not enrolled in semester %s/%s.', p_academic_year, p_semester);
|
|---|
| 40 | RETURN;
|
|---|
| 41 | END IF;
|
|---|
| 42 |
|
|---|
| 43 |
|
|---|
| 44 | SELECT id INTO v_edition_id
|
|---|
| 45 | FROM Course_Edition
|
|---|
| 46 | WHERE course_code = p_course_code
|
|---|
| 47 | AND academic_year = p_academic_year
|
|---|
| 48 | AND semester = p_semester;
|
|---|
| 49 |
|
|---|
| 50 | IF v_edition_id IS NULL THEN
|
|---|
| 51 | success := FALSE;
|
|---|
| 52 | message := format('Course edition %s (%s/%s) does not exist.', p_course_code, p_academic_year, p_semester);
|
|---|
| 53 | RETURN;
|
|---|
| 54 | END IF;
|
|---|
| 55 |
|
|---|
| 56 |
|
|---|
| 57 | IF EXISTS (
|
|---|
| 58 | SELECT 1 FROM Course_Enrollment
|
|---|
| 59 | WHERE semester_enrollment_id = v_se_id
|
|---|
| 60 | AND course_edition_id = v_edition_id
|
|---|
| 61 | ) THEN
|
|---|
| 62 | success := FALSE;
|
|---|
| 63 | message := 'Student is already enrolled in this course for this semester.';
|
|---|
| 64 | RETURN;
|
|---|
| 65 | END IF;
|
|---|
| 66 |
|
|---|
| 67 |
|
|---|
| 68 |
|
|---|
| 69 | IF EXISTS (
|
|---|
| 70 | SELECT 1 FROM v_student_registration_state
|
|---|
| 71 | WHERE student_id = p_student_id
|
|---|
| 72 | AND course_code = p_course_code
|
|---|
| 73 | AND can_enroll = FALSE
|
|---|
| 74 | ) THEN
|
|---|
| 75 | SELECT enrollment_status INTO message
|
|---|
| 76 | FROM v_student_registration_state
|
|---|
| 77 | WHERE student_id = p_student_id
|
|---|
| 78 | AND course_code = p_course_code;
|
|---|
| 79 |
|
|---|
| 80 | success := FALSE;
|
|---|
| 81 | message := 'Prerequisite or other restriction: ' || COALESCE(message, 'unknown');
|
|---|
| 82 | RETURN;
|
|---|
| 83 | END IF;
|
|---|
| 84 |
|
|---|
| 85 |
|
|---|
| 86 | INSERT INTO Course_Enrollment (semester_enrollment_id, course_edition_id)
|
|---|
| 87 | VALUES (v_se_id, v_edition_id);
|
|---|
| 88 |
|
|---|
| 89 | success := TRUE;
|
|---|
| 90 | message := 'Enrollment successful.';
|
|---|
| 91 | END;
|
|---|
| 92 | $$;
|
|---|
| 93 |
|
|---|
| 94 |
|
|---|
| 95 |
|
|---|
| 96 | CREATE OR REPLACE PROCEDURE sp_submit_exam_attempt(
|
|---|
| 97 | p_exam_id INTEGER,
|
|---|
| 98 | p_course_enrollment_id INTEGER,
|
|---|
| 99 | p_answers JSONB,
|
|---|
| 100 | OUT success BOOLEAN,
|
|---|
| 101 | OUT message TEXT
|
|---|
| 102 | )
|
|---|
| 103 | LANGUAGE plpgsql AS $$
|
|---|
| 104 | DECLARE
|
|---|
| 105 | v_attempt_number SMALLINT;
|
|---|
| 106 | v_total_points FLOAT := 0;
|
|---|
| 107 | v_max_points FLOAT;
|
|---|
| 108 | v_answer_record RECORD;
|
|---|
| 109 | v_exam_scheduled TIMESTAMP;
|
|---|
| 110 | v_exam_duration SMALLINT;
|
|---|
| 111 | v_exam_end TIMESTAMP;
|
|---|
| 112 | v_already_submitted BOOLEAN;
|
|---|
| 113 | BEGIN
|
|---|
| 114 |
|
|---|
| 115 | SELECT scheduled_at, duration_minutes INTO v_exam_scheduled, v_exam_duration
|
|---|
| 116 | FROM Exam WHERE id = p_exam_id;
|
|---|
| 117 |
|
|---|
| 118 | IF v_exam_scheduled IS NULL THEN
|
|---|
| 119 | success := FALSE;
|
|---|
| 120 | message := 'Exam not found.';
|
|---|
| 121 | RETURN;
|
|---|
| 122 | END IF;
|
|---|
| 123 |
|
|---|
| 124 | v_exam_end := v_exam_scheduled + (v_exam_duration * INTERVAL '1 minute');
|
|---|
| 125 |
|
|---|
| 126 | IF CURRENT_TIMESTAMP < v_exam_scheduled THEN
|
|---|
| 127 | success := FALSE;
|
|---|
| 128 | message := 'Exam has not started yet.';
|
|---|
| 129 | RETURN;
|
|---|
| 130 | END IF;
|
|---|
| 131 |
|
|---|
| 132 | IF CURRENT_TIMESTAMP > v_exam_end + INTERVAL '5 minutes' THEN
|
|---|
| 133 | success := FALSE;
|
|---|
| 134 | message := 'Exam submission deadline has passed.';
|
|---|
| 135 | RETURN;
|
|---|
| 136 | END IF;
|
|---|
| 137 |
|
|---|
| 138 |
|
|---|
| 139 | SELECT COUNT(*) > 0 INTO v_already_submitted
|
|---|
| 140 | FROM Exam_Attempt
|
|---|
| 141 | WHERE exam_id = p_exam_id
|
|---|
| 142 | AND course_enrollment_id = p_course_enrollment_id;
|
|---|
| 143 |
|
|---|
| 144 | IF v_already_submitted THEN
|
|---|
| 145 | success := FALSE;
|
|---|
| 146 | message := 'You have already submitted an attempt for this exam.';
|
|---|
| 147 | RETURN;
|
|---|
| 148 | END IF;
|
|---|
| 149 |
|
|---|
| 150 |
|
|---|
| 151 | SELECT SUM(points) INTO v_max_points
|
|---|
| 152 | FROM Exam_Problem
|
|---|
| 153 | WHERE exam_id = p_exam_id;
|
|---|
| 154 |
|
|---|
| 155 |
|
|---|
| 156 | FOR v_answer_record IN SELECT * FROM jsonb_to_recordset(p_answers) AS x(problem_id INTEGER, answer TEXT, points_acquired FLOAT)
|
|---|
| 157 | LOOP
|
|---|
| 158 |
|
|---|
| 159 | IF NOT EXISTS (SELECT 1 FROM Exam_Problem WHERE exam_id = p_exam_id AND id = v_answer_record.problem_id) THEN
|
|---|
| 160 | success := FALSE;
|
|---|
| 161 | message := format('Problem id %s does not belong to exam %s.', v_answer_record.problem_id, p_exam_id);
|
|---|
| 162 | RETURN;
|
|---|
| 163 | END IF;
|
|---|
| 164 | IF v_answer_record.points_acquired < 0 THEN
|
|---|
| 165 | success := FALSE;
|
|---|
| 166 | message := 'Points acquired cannot be negative.';
|
|---|
| 167 | RETURN;
|
|---|
| 168 | END IF;
|
|---|
| 169 | v_total_points := v_total_points + v_answer_record.points_acquired;
|
|---|
| 170 | END LOOP;
|
|---|
| 171 |
|
|---|
| 172 |
|
|---|
| 173 | v_attempt_number := 1;
|
|---|
| 174 |
|
|---|
| 175 |
|
|---|
| 176 | INSERT INTO Exam_Attempt (
|
|---|
| 177 | exam_id, course_enrollment_id, attempt_number, submitted_at, total_points
|
|---|
| 178 | )
|
|---|
| 179 | VALUES (
|
|---|
| 180 | p_exam_id, p_course_enrollment_id, v_attempt_number, CURRENT_TIMESTAMP, v_total_points
|
|---|
| 181 | );
|
|---|
| 182 |
|
|---|
| 183 |
|
|---|
| 184 | FOR v_answer_record IN SELECT * FROM jsonb_to_recordset(p_answers) AS x(problem_id INTEGER, answer TEXT, points_acquired FLOAT)
|
|---|
| 185 | LOOP
|
|---|
| 186 | INSERT INTO Student_Answer (
|
|---|
| 187 | exam_id, exam_problem_id, exam_attempt_ceid, exam_attempt_attempt_number,
|
|---|
| 188 | points_acquired, answer
|
|---|
| 189 | )
|
|---|
| 190 | VALUES (
|
|---|
| 191 | p_exam_id, v_answer_record.problem_id, p_course_enrollment_id, v_attempt_number,
|
|---|
| 192 | v_answer_record.points_acquired, v_answer_record.answer
|
|---|
| 193 | );
|
|---|
| 194 | END LOOP;
|
|---|
| 195 |
|
|---|
| 196 | success := TRUE;
|
|---|
| 197 | message := format('Exam submitted. Total points: %s / %s', v_total_points, v_max_points);
|
|---|
| 198 | END;
|
|---|
| 199 | $$;
|
|---|
| 200 |
|
|---|
| 201 |
|
|---|
| 202 |
|
|---|
| 203 |
|
|---|
| 204 | CREATE OR REPLACE PROCEDURE sp_export_aggregated_results(
|
|---|
| 205 | p_teacher_id INTEGER,
|
|---|
| 206 | p_course_edition_id INTEGER,
|
|---|
| 207 | p_data JSONB,
|
|---|
| 208 | OUT success BOOLEAN,
|
|---|
| 209 | OUT new_result_id INTEGER,
|
|---|
| 210 | OUT message TEXT
|
|---|
| 211 | )
|
|---|
| 212 | LANGUAGE plpgsql AS $$
|
|---|
| 213 | BEGIN
|
|---|
| 214 |
|
|---|
| 215 | IF NOT EXISTS (
|
|---|
| 216 | SELECT 1 FROM Teaches
|
|---|
| 217 | WHERE teacher_id = p_teacher_id AND course_edition_id = p_course_edition_id
|
|---|
| 218 | ) THEN
|
|---|
| 219 | success := FALSE;
|
|---|
| 220 | message := 'Teacher does not teach this course edition.';
|
|---|
| 221 | RETURN;
|
|---|
| 222 | END IF;
|
|---|
| 223 |
|
|---|
| 224 |
|
|---|
| 225 | IF NOT fn_validate_aggregated_data(p_data) THEN
|
|---|
| 226 | success := FALSE;
|
|---|
| 227 | message := 'Invalid aggregated data format. See fn_validate_aggregated_data for schema.';
|
|---|
| 228 | RETURN;
|
|---|
| 229 | END IF;
|
|---|
| 230 |
|
|---|
| 231 |
|
|---|
| 232 | INSERT INTO Aggregated_Course_Edition_Results (
|
|---|
| 233 | exported_at, teaches_teacher_id, teaches_course_edition_id, data
|
|---|
| 234 | )
|
|---|
| 235 | VALUES (
|
|---|
| 236 | CURRENT_TIMESTAMP, p_teacher_id, p_course_edition_id, p_data
|
|---|
| 237 | )
|
|---|
| 238 | RETURNING id INTO new_result_id;
|
|---|
| 239 |
|
|---|
| 240 |
|
|---|
| 241 |
|
|---|
| 242 |
|
|---|
| 243 | success := TRUE;
|
|---|
| 244 | message := format('Aggregated results exported with id = %s.', new_result_id);
|
|---|
| 245 | END;
|
|---|
| 246 | $$;
|
|---|
| 247 |
|
|---|
| 248 |
|
|---|
| 249 |
|
|---|
| 250 |
|
|---|
| 251 | CREATE OR REPLACE PROCEDURE sp_graduate_student(
|
|---|
| 252 | p_program_enrollment_id INTEGER,
|
|---|
| 253 | OUT success BOOLEAN,
|
|---|
| 254 | OUT message TEXT
|
|---|
| 255 | )
|
|---|
| 256 | LANGUAGE plpgsql AS $$
|
|---|
| 257 | DECLARE
|
|---|
| 258 | v_outstanding INTEGER;
|
|---|
| 259 | BEGIN
|
|---|
| 260 |
|
|---|
| 261 | IF NOT EXISTS (
|
|---|
| 262 | SELECT 1 FROM Program_Enrollment
|
|---|
| 263 | WHERE id = p_program_enrollment_id
|
|---|
| 264 | AND date_disenrollment IS NULL
|
|---|
| 265 | AND finished IS NOT TRUE
|
|---|
| 266 | ) THEN
|
|---|
| 267 | success := FALSE;
|
|---|
| 268 | message := 'Program enrollment not found or already finished.';
|
|---|
| 269 | RETURN;
|
|---|
| 270 | END IF;
|
|---|
| 271 |
|
|---|
| 272 |
|
|---|
| 273 | SELECT outstanding_mandatory_courses INTO v_outstanding
|
|---|
| 274 | FROM v_program_completion_status
|
|---|
| 275 | WHERE program_enrollment_id = p_program_enrollment_id;
|
|---|
| 276 |
|
|---|
| 277 | IF v_outstanding IS NULL THEN
|
|---|
| 278 | success := FALSE;
|
|---|
| 279 | message := 'Could not compute completion status.';
|
|---|
| 280 | RETURN;
|
|---|
| 281 | END IF;
|
|---|
| 282 |
|
|---|
| 283 | IF v_outstanding > 0 THEN
|
|---|
| 284 | success := FALSE;
|
|---|
| 285 | message := format('Cannot graduate: %s mandatory course(s) still outstanding.', v_outstanding);
|
|---|
| 286 | RETURN;
|
|---|
| 287 | END IF;
|
|---|
| 288 |
|
|---|
| 289 |
|
|---|
| 290 | UPDATE Program_Enrollment
|
|---|
| 291 | SET finished = TRUE, date_disenrollment = CURRENT_DATE
|
|---|
| 292 | WHERE id = p_program_enrollment_id;
|
|---|
| 293 |
|
|---|
| 294 | success := TRUE;
|
|---|
| 295 | message := 'Student has graduated. Program enrollment marked as finished.';
|
|---|
| 296 | END;
|
|---|
| 297 | $$;
|
|---|
| 298 |
|
|---|
| 299 |
|
|---|
| 300 |
|
|---|
| 301 |
|
|---|
| 302 |
|
|---|