-- Add sub-level support to sub_courses and enforce valid level/sub-level combinations. ALTER TABLE sub_courses ADD COLUMN sub_level VARCHAR(2); UPDATE sub_courses SET sub_level = CASE level WHEN 'BEGINNER' THEN 'A1' WHEN 'INTERMEDIATE' THEN 'B1' WHEN 'ADVANCED' THEN 'C1' ELSE 'A1' END WHERE sub_level IS NULL; ALTER TABLE sub_courses ALTER COLUMN sub_level SET NOT NULL; ALTER TABLE sub_courses ADD CONSTRAINT sub_courses_level_sub_level_check CHECK ( (level = 'BEGINNER' AND sub_level IN ('A1', 'A2', 'A3')) OR (level = 'INTERMEDIATE' AND sub_level IN ('B1', 'B2', 'B3')) OR (level = 'ADVANCED' AND sub_level IN ('C1', 'C2', 'C3')) ); CREATE INDEX idx_sub_courses_level_sub_level ON sub_courses(level, sub_level); -- Ensure each sub-course has an entry-assessment question set. CREATE UNIQUE INDEX idx_question_sets_unique_subcourse_initial_assessment ON question_sets(owner_type, owner_id, set_type) WHERE owner_type = 'SUB_COURSE' AND set_type = 'INITIAL_ASSESSMENT' AND status != 'ARCHIVED'; CREATE OR REPLACE FUNCTION clone_default_initial_assessment_items(target_set_id BIGINT) RETURNS VOID AS $$ DECLARE template_set_id BIGINT; BEGIN SELECT id INTO template_set_id FROM question_sets WHERE set_type = 'INITIAL_ASSESSMENT' AND owner_type = 'STANDALONE' AND status = 'PUBLISHED' ORDER BY created_at DESC LIMIT 1; IF template_set_id IS NULL THEN RETURN; END IF; INSERT INTO question_set_items (set_id, question_id, display_order) SELECT target_set_id, qsi.question_id, qsi.display_order FROM question_set_items qsi WHERE qsi.set_id = template_set_id AND NOT EXISTS ( SELECT 1 FROM question_set_items existing WHERE existing.set_id = target_set_id AND existing.question_id = qsi.question_id ); END; $$ LANGUAGE plpgsql; INSERT INTO question_sets ( title, description, set_type, owner_type, owner_id, shuffle_questions, status ) SELECT sc.title || ' Entry Assessment', 'Entry assessment used to evaluate learners before joining this sub-course.', 'INITIAL_ASSESSMENT', 'SUB_COURSE', sc.id, false, 'DRAFT' FROM sub_courses sc WHERE NOT EXISTS ( SELECT 1 FROM question_sets qs WHERE qs.owner_type = 'SUB_COURSE' AND qs.owner_id = sc.id AND qs.set_type = 'INITIAL_ASSESSMENT' AND qs.status != 'ARCHIVED' ); DO $$ DECLARE r RECORD; BEGIN FOR r IN SELECT id FROM question_sets WHERE owner_type = 'SUB_COURSE' AND set_type = 'INITIAL_ASSESSMENT' AND status != 'ARCHIVED' LOOP IF NOT EXISTS (SELECT 1 FROM question_set_items WHERE set_id = r.id) THEN PERFORM clone_default_initial_assessment_items(r.id); END IF; END LOOP; END; $$; CREATE OR REPLACE FUNCTION create_sub_course_entry_assessment() RETURNS TRIGGER AS $$ BEGIN INSERT INTO question_sets ( title, description, set_type, owner_type, owner_id, shuffle_questions, status ) VALUES ( NEW.title || ' Entry Assessment', 'Entry assessment used to evaluate learners before joining this sub-course.', 'INITIAL_ASSESSMENT', 'SUB_COURSE', NEW.id, false, 'DRAFT' ) ON CONFLICT DO NOTHING; PERFORM clone_default_initial_assessment_items(( SELECT id FROM question_sets WHERE owner_type = 'SUB_COURSE' AND owner_id = NEW.id AND set_type = 'INITIAL_ASSESSMENT' AND status != 'ARCHIVED' ORDER BY created_at DESC LIMIT 1 )); RETURN NEW; END; $$ LANGUAGE plpgsql; CREATE TRIGGER trg_sub_courses_create_entry_assessment AFTER INSERT ON sub_courses FOR EACH ROW EXECUTE FUNCTION create_sub_course_entry_assessment();