// Phase 5B §2 — programs: first-class learning products (distinct from curriculum JSON, which // stays just the content a program points at). CRUD here mirrors assessments.js's definition // pattern (a slug primary key, a plain incrementing version, active/inactive status) rather // than inventing a new convention. async function listPrograms(pool, { activeOnly } = {}) { const { rows } = await pool.query( `SELECT p.*, count(e.enrollment_id)::int AS enrollment_count FROM programs p LEFT JOIN enrollments e ON e.program_id = p.program_id ${activeOnly ? "WHERE p.status = 'active'" : ''} GROUP BY p.program_id ORDER BY p.family, p.name` ); return rows; } async function getProgram(pool, programId) { const { rows } = await pool.query(`SELECT * FROM programs WHERE program_id = $1`, [programId]); return rows[0] || null; } async function createProgram(pool, { programId, name, description, family, deliveryMode, curriculumPath, completionAssessmentId, estimatedDurationHours, createdBy }) { const { rows } = await pool.query( `INSERT INTO programs (program_id, name, description, family, delivery_mode, curriculum_path, completion_assessment_id, estimated_duration_hours, created_by) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) RETURNING *`, [programId, name, description || null, family, deliveryMode || 'self_paced', curriculumPath || 'rootnode-path', completionAssessmentId || null, estimatedDurationHours || null, createdBy || null] ); return rows[0]; } // Bumps version whenever a field an enrollment snapshots (curriculum_path, delivery_mode) // actually changes — matches assessment_definitions' "edit later never changes what an // already-drawn session is graded against" principle. Editing only cosmetic fields (name, // description, estimated_duration_hours) does not bump the version. async function updateProgram(pool, programId, fields) { const current = await getProgram(pool, programId); if (!current) return null; const snapshotChanged = (fields.curriculum_path && fields.curriculum_path !== current.curriculum_path) || (fields.delivery_mode && fields.delivery_mode !== current.delivery_mode); const map = { ...fields }; if (snapshotChanged) map.version = current.version + 1; const cols = Object.keys(map); if (!cols.length) return current; const setClause = cols.map((c, i) => `${c} = $${i + 2}`).join(', '); const { rows } = await pool.query( `UPDATE programs SET ${setClause}, updated_at = now() WHERE program_id = $1 RETURNING *`, [programId, ...cols.map((c) => map[c])] ); return rows[0]; } module.exports = { listPrograms, getProgram, createProgram, updateProgram };