+
+
+
+
+ โหมดวาดตาราง (Paint)
+
+
ลากเมาส์ผ่านช่องเวลาเพื่อลงวิชาอัตโนมัติ
-
- {{ time }}
+
+
+
+
+
+
+
+
+
+
+ กำลังวาด...
+
-
-
+
+
+ เลือกลงแต่ละกลุ่มเรียน:
+ {
+ if (checked) {
+ if (!paintSectionIds.includes(sec.value)) paintSectionIds.push(sec.value);
+ } else {
+ paintSectionIds = paintSectionIds.filter(orig => orig !== sec.value);
+ }
+ }"
+ :label="sec.label"
+ color="primary"
+ :ui="{ label: 'font-medium text-slate-700' }"
+ />
+
+
+
+
+
+
@@ -848,11 +706,101 @@ const editSubjectName = ref('')
const currentEditSubject = ref(null)
const deleteOpen = ref(false)
const subjectToDelete = ref(null)
-const subjectName = ref('')
+const selectedAddPlanId = ref(null)
+const selectedAddSubjectId = ref(null)
const selectedSections = ref([])
const editSelectedSections = ref([])
+const editSelectedPlanId = ref(null)
+const editSelectedSubjectId = ref(null)
+const editLegacySubjectName = ref('')
const saving = ref(false)
+// --- Paint Mode State (Drag to Schedule) ---
+const isPaintMode = ref(false)
+const paintSubjectId = ref(null)
+const paintSectionIds = ref([])
+const paintRoomId = ref(null)
+const isDragging = ref(false)
+const dragCurrentDay = ref(null)
+
+const paintSectionOptions = computed(() => {
+ const rawId = getRawValue(paintSubjectId.value)
+ if (!rawId) return []
+ const subj = subjects.value?.find(sub => sub.id_subject == rawId)
+ if (!subj || !subj.sections) return []
+ return subj.sections.map(sec => ({ label: sec.section_name, value: sec.id_section }))
+})
+
+watch(() => paintSubjectId.value, (newVal) => {
+ const rawId = getRawValue(newVal)
+ if (!rawId) {
+ paintSectionIds.value = []
+ } else {
+ const subj = subjects.value?.find(sub => sub.id_subject == rawId)
+ if (subj && subj.sections) {
+ paintSectionIds.value = subj.sections.map(sec => sec.id_section)
+ } else {
+ paintSectionIds.value = []
+ }
+ }
+})
+
+const togglePaintMode = (val = null) => {
+ if (!isPaintMode.value) {
+ isDragging.value = false
+ dragCurrentDay.value = null
+ }
+}
+
+const applyPaint = (dayIndex, slotIndex) => {
+ if (slotIndex === 4) return // Skip lunch break
+
+ const rawPaintSubjectId = getRawValue(paintSubjectId.value)
+ const rawPaintRoomId = getRawValue(paintRoomId.value)
+
+ let sectionIds = []
+ if (rawPaintSubjectId) {
+ sectionIds = [...paintSectionIds.value]
+ }
+
+ // Erase mode (if rawPaintSubjectId is null), otherwise paint mode
+ scheduleSlots.value[dayIndex][slotIndex].value = rawPaintSubjectId || null
+ scheduleSlots.value[dayIndex][slotIndex].room_id = rawPaintSubjectId ? (rawPaintRoomId || null) : null
+ scheduleSlots.value[dayIndex][slotIndex].section_ids = rawPaintSubjectId ? sectionIds : []
+}
+
+const startDrag = (dayIndex, slotIndex) => {
+ if (!isPaintMode.value) return
+ isDragging.value = true
+ dragCurrentDay.value = dayIndex
+ applyPaint(dayIndex, slotIndex)
+}
+
+const onDragOver = (dayIndex, slotIndex) => {
+ if (!isPaintMode.value || !isDragging.value) return
+ if (dragCurrentDay.value !== dayIndex) return // Can only drag vertically across same day
+ applyPaint(dayIndex, slotIndex)
+}
+
+const endDrag = () => {
+ if (isDragging.value) {
+ isDragging.value = false
+ dragCurrentDay.value = null
+ }
+}
+
+onMounted(() => {
+ if (import.meta.client) {
+ document.addEventListener('mouseup', endDrag)
+ }
+})
+
+onUnmounted(() => {
+ if (import.meta.client) {
+ document.removeEventListener('mouseup', endDrag)
+ }
+})
+
// Quick Add Subject to Schedule states
const quickAddOpen = ref(false)
const quickAddSubject = ref(null)
@@ -869,6 +817,51 @@ const { data: teachers, pending } = await useFetch('/api/teachers')
const { data: terms } = await useFetch('/api/terms')
// sections เป็น master table ไม่ขึ้นกับ term — fetch ครั้งเดียวพอ
const { data: sections, refresh: refreshSections, status: sectionsStatus } = await useFetch('/api/sections')
+const { data: studyPlans } = await useFetch('/api/study-plans')
+
+const studyPlanOptions = computed(() => {
+ if (!studyPlans.value) return []
+ return studyPlans.value.map(p => ({ value: p.id_plan, label: p.name_plan }))
+})
+
+// Helper to strictly get the primitive value from select boxes
+const getRawValue = (val) => {
+ if (val && typeof val === 'object' && 'value' in val) return val.value
+ return val
+}
+
+// Variables for fetching subjects dynamically
+const availableAddSubjectOptions = ref([])
+watch(selectedAddPlanId, async (newVal) => {
+ selectedAddSubjectId.value = null
+ const planId = getRawValue(newVal)
+ if (!planId) {
+ availableAddSubjectOptions.value = []
+ return
+ }
+ const subs = await $fetch('/api/study-plan-subjects', { query: { id_plan: planId } })
+ availableAddSubjectOptions.value = subs.map(s => ({ value: s.id_subject_curr, label: `${s.subject_code || ''} ${s.name_subject}`, name_subject: s.name_subject }))
+})
+
+const availableEditSubjectOptions = ref([])
+watch(editSelectedPlanId, async (newVal) => {
+ editSelectedSubjectId.value = null
+ const planId = getRawValue(newVal)
+ if (!planId) {
+ availableEditSubjectOptions.value = []
+ return
+ }
+ const subs = await $fetch('/api/study-plan-subjects', { query: { id_plan: planId } })
+ availableEditSubjectOptions.value = subs.map(s => ({ value: s.id_subject_curr, label: `${s.subject_code || ''} ${s.name_subject}`, name_subject: s.name_subject }))
+})
+
+const computedEditSubjectName = computed(() => {
+ if (editSelectedSubjectId.value) {
+ const s = availableEditSubjectOptions.value.find(s => s.value === editSelectedSubjectId.value)
+ return s ? s.label : editLegacySubjectName.value
+ }
+ return editLegacySubjectName.value
+})
const termOptions = computed(() => {
if (!terms.value || terms.value.length === 0) return []
@@ -1063,7 +1056,7 @@ const slotsCanMerge = (a, b) => {
// Logic สำหรับการ Merge ช่องที่วิชา + ห้อง + กลุ่มนักศึกษาเหมือนกันและติดกัน
const displaySlots = computed(() => {
if (!scheduleSlots.value) return []
- return scheduleSlots.value.map((daySlots) => {
+ return scheduleSlots.value.map((daySlots, dayIndex) => {
const grouped = []
for (let i = 0; i < daySlots.length; i++) {
const current = daySlots[i]
@@ -1072,13 +1065,18 @@ const displaySlots = computed(() => {
continue
}
let span = 1
- while (
- i + span < daySlots.length
- && i + span !== 4 // ไม่ Merge ข้ามพักเที่ยง
- && slotsCanMerge(current, daySlots[i + span])
- ) {
- span++
+
+ // Merge cells only if we are not in Paint Mode
+ if (!isPaintMode.value) {
+ while (
+ i + span < daySlots.length
+ && i + span !== 4 // ไม่ Merge ข้ามพักเที่ยง
+ && slotsCanMerge(current, daySlots[i + span])
+ ) {
+ span++
+ }
}
+
grouped.push({ ...current, span, originalIndex: i })
i += span - 1
}
@@ -1130,22 +1128,29 @@ const getSubjectLabel = (val, roomId = null, sectionIds = null) => {
}
const addSubject = async () => {
- if (!subjectName.value || selectedSections.value.length === 0) {
- toast.add({ title: 'ข้อผิดพลาด', description: 'กรุณากรอกชื่อวิชาและคุณกลุ่มเรียน อย่างน้อย 1 กลุ่ม', color: 'red' })
+ const subjectId = getRawValue(selectedAddSubjectId.value)
+ if (!subjectId || selectedSections.value.length === 0) {
+ toast.add({ title: 'ข้อผิดพลาด', description: 'กรุณาเลือกวิชา และคุณกลุ่มเรียน อย่างน้อย 1 กลุ่ม', color: 'red' })
return
}
+
+ const selectedSubjectData = availableAddSubjectOptions.value.find(s => s.value === subjectId)
+ if (!selectedSubjectData) return
+
try {
const res = await $fetch('/api/Subjects', {
method: 'POST',
body: {
- name_subject: subjectName.value,
+ name_subject: selectedSubjectData.name_subject, // Provide default name
+ curriculum_subject_id: selectedSubjectData.value, // Send ID
id_teacher: id,
id_sections: selectedSections.value,
term: selectedTerm.value
}
})
toast.add({ title: 'สำเร็จ', description: 'เพิ่มรายวิชาเรียบร้อยแล้ว', color: 'primary' })
- subjectName.value = ''
+ selectedAddPlanId.value = null
+ selectedAddSubjectId.value = null
selectedSections.value = []
await refreshSubjects()
} catch (err) {
@@ -1184,22 +1189,39 @@ const deleteSubject = async () => {
const editSubject = (subject) => {
currentEditSubject.value = subject
- editSubjectName.value = subject.name_subject
+ editLegacySubjectName.value = subject.name_subject
+ editSelectedPlanId.value = null
+ editSelectedSubjectId.value = null
// Get existing sections for this subject
editSelectedSections.value = subject.sections ? subject.sections.map(s => s.id_section) : []
editOpen.value = true
}
const updateSubject = async () => {
- if (!editSubjectName.value || editSelectedSections.value.length === 0) {
- toast.add({ title: 'ข้อผิดพลาด', description: 'กรุณากรอกชื่อวิชาและคุณกลุ่มเรียน อย่างน้อย 1 กลุ่ม', color: 'red' })
+ if (editSelectedSections.value.length === 0) {
+ toast.add({ title: 'ข้อผิดพลาด', description: 'กรุณาเลือกกลุ่มเรียน อย่างน้อย 1 กลุ่ม', color: 'red' })
return
}
+
+ let newName = editLegacySubjectName.value
+ let newCurrId = currentEditSubject.value.curriculum_subject_id || null
+
+ const subjectId = getRawValue(editSelectedSubjectId.value)
+
+ if (subjectId) {
+ const s = availableEditSubjectOptions.value.find(s => s.value === subjectId)
+ if (s) {
+ newName = s.name_subject
+ newCurrId = s.value
+ }
+ }
+
try {
await $fetch(`/api/Subjects/${currentEditSubject.value.id_subject}`, {
method: 'PUT',
body: {
- name_subject: editSubjectName.value,
+ name_subject: newName,
+ curriculum_subject_id: newCurrId,
id_sections: editSelectedSections.value
}
})
@@ -1242,6 +1264,7 @@ const clearSchedule = (noConfirm = false) => {
const isActiveBox = (d, s) => activeBox.value.day === d && activeBox.value.slot === s
const toggleDropdown = (d, s) => {
+ if (isPaintMode.value) return // Disable dropdown in paint mode
if (isActiveBox(d, s)) {
activeBox.value = { day: null, slot: null }
} else {
@@ -1380,4 +1403,3 @@ watch(scheduleError, (err) => {
if (err) console.error('[Schedule] API Error:', err)
})
-
diff --git a/server/api/Subjects/[id].put.js b/server/api/Subjects/[id].put.js
index e4b9cf7..f343fce 100644
--- a/server/api/Subjects/[id].put.js
+++ b/server/api/Subjects/[id].put.js
@@ -4,17 +4,18 @@ export default defineEventHandler(async (event) => {
const { id } = event.context.params
const body = await readBody(event)
- // Validate input
- if (!body.name_subject || body.name_subject.trim() === '') {
+ const newCurrId = body.curriculum_subject_id || null
+
+ if (!newCurrId) {
throw createError({
statusCode: 400,
- statusMessage: 'name_subject is required'
+ statusMessage: 'curriculum_subject_id is required'
})
}
- // 1. Update Subject name
- const stmt = db.prepare('UPDATE Subjects SET name_subject = ? WHERE id_subject = ?')
- stmt.run(body.name_subject, id)
+ // 1. Update Subject curriculum link (name_subject column is removed)
+ const stmt = db.prepare('UPDATE Subjects SET curriculum_subject_id = ? WHERE id_subject = ?')
+ stmt.run(newCurrId, id)
// Update Sections if provided
if (body.id_sections && Array.isArray(body.id_sections)) {
diff --git a/server/api/Subjects/index.get.js b/server/api/Subjects/index.get.js
index d6f1185..67201e3 100644
--- a/server/api/Subjects/index.get.js
+++ b/server/api/Subjects/index.get.js
@@ -9,6 +9,8 @@ export default defineEventHandler((event) => {
let baseQuery = `
SELECT s.*,
t.prefix, t.first_name, t.last_name,
+ cs.name_subject as curriculum_name_subject,
+ cs.subject_code,
(
SELECT GROUP_CONCAT(sec.section_name, ', ')
FROM SubjectSections ss
@@ -24,6 +26,7 @@ export default defineEventHandler((event) => {
) as sections_json
FROM Subjects s
LEFT JOIN teachers t ON s.id_teacher = t.id_teacher
+ LEFT JOIN curriculum_subjects cs ON s.curriculum_subject_id = cs.id_subject_curr
`
let whereClauses = []
@@ -45,9 +48,18 @@ export default defineEventHandler((event) => {
stmt = db.prepare(finalQuery)
subjects = stmt.all(...params)
- // Parse JSON sections for each subject
- return subjects.map(s => ({
- ...s,
- sections: s.sections_json ? JSON.parse(s.sections_json) : []
- }))
+ // Parse JSON sections for each subject and resolve dynamic name
+ return subjects.map(s => {
+ let resolvedName = 'Unknown'
+ if (s.curriculum_name_subject) {
+ // Strictly use its name (prepended with code if available)
+ resolvedName = s.subject_code ? `${s.subject_code} ${s.curriculum_name_subject}` : s.curriculum_name_subject
+ }
+
+ return {
+ ...s,
+ name_subject: resolvedName,
+ sections: s.sections_json ? JSON.parse(s.sections_json) : []
+ }
+ })
})
diff --git a/server/api/Subjects/index.post.js b/server/api/Subjects/index.post.js
index d75802c..a030a6e 100644
--- a/server/api/Subjects/index.post.js
+++ b/server/api/Subjects/index.post.js
@@ -3,16 +3,31 @@ import db from '../../utils/db.js'
export default defineEventHandler(async (event) => {
try {
const body = await readBody(event)
- if (!body.name_subject || !body.id_sections || body.id_sections.length === 0) {
+
+ // curriculum_subject_id is now strictly required as the sole name source
+ if (!body.curriculum_subject_id) {
throw createError({
statusCode: 400,
- statusMessage: 'name subject and sections are required'
+ statusMessage: 'curriculum_subject_id is required'
})
}
+ if (!body.id_sections || body.id_sections.length === 0) {
+ throw createError({
+ statusCode: 400,
+ statusMessage: 'sections are required'
+ })
+ }
+
+ // Resolve name for the response payload to keep frontend happy
+ let resolvedName = 'Unknown'
+ const cs = db.prepare('SELECT subject_code, name_subject FROM curriculum_subjects WHERE id_subject_curr = ?').get(body.curriculum_subject_id)
+ if (cs) {
+ resolvedName = cs.subject_code ? `${cs.subject_code} ${cs.name_subject}` : cs.name_subject
+ }
- // 1. Insert Subject
- const stmt = db.prepare('INSERT INTO Subjects (name_subject, id_teacher, term) VALUES (?, ?, ?)')
- const result = stmt.run(body.name_subject, body.id_teacher, body.term || null)
+ // 1. Insert Subject (name_subject column is removed)
+ const stmt = db.prepare('INSERT INTO Subjects (id_teacher, term, curriculum_subject_id) VALUES (?, ?, ?)')
+ const result = stmt.run(body.id_teacher, body.term || null, body.curriculum_subject_id)
const subjectId = result.lastInsertRowid
// 2. Insert SubjectSections (Join table)
@@ -26,7 +41,7 @@ export default defineEventHandler(async (event) => {
return {
id_subject: subjectId,
- name_subject: body.name_subject,
+ name_subject: resolvedName,
id_sections: body.id_sections,
status: 1
}
@@ -34,7 +49,7 @@ export default defineEventHandler(async (event) => {
if (error.code === 'SQLITE_CONSTRAINT_UNIQUE') {
throw createError({
statusCode: 409,
- statusMessage: 'Name already exists'
+ statusMessage: 'Subject constraint error'
})
}
throw error
diff --git a/server/api/curriculum-categories/[id].js b/server/api/curriculum-categories/[id].js
new file mode 100644
index 0000000..de91474
--- /dev/null
+++ b/server/api/curriculum-categories/[id].js
@@ -0,0 +1,29 @@
+import db from '../../utils/db.js'
+
+export default defineEventHandler(async (event) => {
+ const id = event.context.params.id
+
+ if (event.node.req.method === 'PUT') {
+ const body = await readBody(event)
+ if (!body.name_category) {
+ throw createError({ statusCode: 400, statusMessage: 'name_category is required' })
+ }
+
+ const stmt = db.prepare('UPDATE curriculum_categories SET name_category = ?, parent_id = ? WHERE id_category = ?')
+ const result = stmt.run(body.name_category, body.parent_id || null, id)
+
+ if (result.changes === 0) {
+ throw createError({ statusCode: 404, statusMessage: 'Category not found' })
+ }
+ return { success: true }
+ }
+
+ if (event.node.req.method === 'DELETE') {
+ const stmt = db.prepare('DELETE FROM curriculum_categories WHERE id_category = ?')
+ const result = stmt.run(id)
+ if (result.changes === 0) {
+ throw createError({ statusCode: 404, statusMessage: 'Category not found' })
+ }
+ return { success: true }
+ }
+})
diff --git a/server/api/curriculum-categories/index.js b/server/api/curriculum-categories/index.js
new file mode 100644
index 0000000..97fd4b0
--- /dev/null
+++ b/server/api/curriculum-categories/index.js
@@ -0,0 +1,27 @@
+import db from '../../utils/db.js'
+
+export default defineEventHandler(async (event) => {
+ if (event.node.req.method === 'GET') {
+ const query = getQuery(event)
+ if (query.id_curriculum) {
+ return db.prepare('SELECT * FROM curriculum_categories WHERE id_curriculum = ? ORDER BY id_category ASC').all(query.id_curriculum)
+ }
+ return db.prepare('SELECT * FROM curriculum_categories ORDER BY id_category ASC').all()
+ }
+
+ if (event.node.req.method === 'POST') {
+ const body = await readBody(event)
+ if (!body.id_curriculum || !body.name_category) {
+ throw createError({ statusCode: 400, statusMessage: 'id_curriculum and name_category are required' })
+ }
+
+ const stmt = db.prepare('INSERT INTO curriculum_categories (id_curriculum, parent_id, name_category) VALUES (?, ?, ?)')
+ const result = stmt.run(body.id_curriculum, body.parent_id || null, body.name_category)
+ return {
+ id_category: result.lastInsertRowid,
+ id_curriculum: body.id_curriculum,
+ parent_id: body.parent_id || null,
+ name_category: body.name_category
+ }
+ }
+})
diff --git a/server/api/curriculum-subjects/[id].js b/server/api/curriculum-subjects/[id].js
new file mode 100644
index 0000000..57c007c
--- /dev/null
+++ b/server/api/curriculum-subjects/[id].js
@@ -0,0 +1,29 @@
+import db from '../../utils/db.js'
+
+export default defineEventHandler(async (event) => {
+ const id = event.context.params.id
+
+ if (event.node.req.method === 'PUT') {
+ const body = await readBody(event)
+ if (!body.name_subject || !body.id_category) {
+ throw createError({ statusCode: 400, statusMessage: 'name_subject and id_category are required' })
+ }
+
+ const stmt = db.prepare('UPDATE curriculum_subjects SET name_subject = ?, subject_code = ?, id_category = ? WHERE id_subject_curr = ?')
+ const result = stmt.run(body.name_subject, body.subject_code || null, body.id_category, id)
+
+ if (result.changes === 0) {
+ throw createError({ statusCode: 404, statusMessage: 'Subject not found' })
+ }
+ return { success: true }
+ }
+
+ if (event.node.req.method === 'DELETE') {
+ const stmt = db.prepare('DELETE FROM curriculum_subjects WHERE id_subject_curr = ?')
+ const result = stmt.run(id)
+ if (result.changes === 0) {
+ throw createError({ statusCode: 404, statusMessage: 'Subject not found' })
+ }
+ return { success: true }
+ }
+})
diff --git a/server/api/curriculum-subjects/index.js b/server/api/curriculum-subjects/index.js
new file mode 100644
index 0000000..55847dc
--- /dev/null
+++ b/server/api/curriculum-subjects/index.js
@@ -0,0 +1,33 @@
+import db from '../../utils/db.js'
+
+export default defineEventHandler(async (event) => {
+ if (event.node.req.method === 'GET') {
+ const query = getQuery(event)
+ if (query.id_curriculum) {
+ return db.prepare(`
+ SELECT cs.*
+ FROM curriculum_subjects cs
+ JOIN curriculum_categories cc ON cs.id_category = cc.id_category
+ WHERE cc.id_curriculum = ?
+ ORDER BY cs.subject_code ASC
+ `).all(query.id_curriculum)
+ }
+ return db.prepare('SELECT * FROM curriculum_subjects ORDER BY subject_code ASC').all()
+ }
+
+ if (event.node.req.method === 'POST') {
+ const body = await readBody(event)
+ if (!body.id_category || !body.name_subject) {
+ throw createError({ statusCode: 400, statusMessage: 'id_category and name_subject are required' })
+ }
+
+ const stmt = db.prepare('INSERT INTO curriculum_subjects (id_category, subject_code, name_subject) VALUES (?, ?, ?)')
+ const result = stmt.run(body.id_category, body.subject_code || null, body.name_subject)
+ return {
+ id_subject_curr: result.lastInsertRowid,
+ id_category: body.id_category,
+ subject_code: body.subject_code || null,
+ name_subject: body.name_subject
+ }
+ }
+})
diff --git a/server/api/curriculums/[id].js b/server/api/curriculums/[id].js
new file mode 100644
index 0000000..651f178
--- /dev/null
+++ b/server/api/curriculums/[id].js
@@ -0,0 +1,36 @@
+import db from '../../utils/db.js'
+
+export default defineEventHandler(async (event) => {
+ const id = event.context.params.id
+
+ if (event.node.req.method === 'PUT') {
+ const body = await readBody(event)
+ if (!body.name_curriculum) {
+ throw createError({ statusCode: 400, statusMessage: 'name_curriculum is required' })
+ }
+
+ try {
+ const stmt = db.prepare('UPDATE curriculums SET name_curriculum = ?, description = ? WHERE id_curriculum = ?')
+ const result = stmt.run(body.name_curriculum, body.description || null, id)
+
+ if (result.changes === 0) {
+ throw createError({ statusCode: 404, statusMessage: 'Curriculum not found' })
+ }
+ return { success: true }
+ } catch (error) {
+ if (error.code === 'SQLITE_CONSTRAINT_UNIQUE') {
+ throw createError({ statusCode: 409, statusMessage: 'Curriculum name already exists' })
+ }
+ throw error
+ }
+ }
+
+ if (event.node.req.method === 'DELETE') {
+ const stmt = db.prepare('DELETE FROM curriculums WHERE id_curriculum = ?')
+ const result = stmt.run(id)
+ if (result.changes === 0) {
+ throw createError({ statusCode: 404, statusMessage: 'Curriculum not found' })
+ }
+ return { success: true }
+ }
+})
diff --git a/server/api/curriculums/index.js b/server/api/curriculums/index.js
new file mode 100644
index 0000000..d65cf63
--- /dev/null
+++ b/server/api/curriculums/index.js
@@ -0,0 +1,29 @@
+import db from '../../utils/db.js'
+
+export default defineEventHandler(async (event) => {
+ if (event.node.req.method === 'GET') {
+ return db.prepare('SELECT * FROM curriculums ORDER BY created_at DESC').all()
+ }
+
+ if (event.node.req.method === 'POST') {
+ const body = await readBody(event)
+ if (!body.name_curriculum) {
+ throw createError({ statusCode: 400, statusMessage: 'name_curriculum is required' })
+ }
+
+ try {
+ const stmt = db.prepare('INSERT INTO curriculums (name_curriculum, description) VALUES (?, ?)')
+ const result = stmt.run(body.name_curriculum, body.description || null)
+ return {
+ id_curriculum: result.lastInsertRowid,
+ name_curriculum: body.name_curriculum,
+ description: body.description || null
+ }
+ } catch (error) {
+ if (error.code === 'SQLITE_CONSTRAINT_UNIQUE') {
+ throw createError({ statusCode: 409, statusMessage: 'Curriculum name already exists' })
+ }
+ throw error
+ }
+ }
+})
diff --git a/server/api/makeup-classes/[id].js b/server/api/makeup-classes/[id].js
index 7a72c0e..b052254 100644
--- a/server/api/makeup-classes/[id].js
+++ b/server/api/makeup-classes/[id].js
@@ -13,12 +13,13 @@ export default defineEventHandler(async (event) => {
t.first_name,
t.last_name,
s.section_name,
- sub.name_subject,
+ CASE WHEN cs.subject_code IS NOT NULL THEN cs.subject_code || ' ' || cs.name_subject ELSE cs.name_subject END as name_subject,
r.room_name
FROM makeup_classes mc
LEFT JOIN teachers t ON mc.teacher_id = t.id_teacher
LEFT JOIN sections s ON mc.section_id = s.id_section
LEFT JOIN Subjects sub ON mc.subject_id = sub.id_subject
+ LEFT JOIN curriculum_subjects cs ON sub.curriculum_subject_id = cs.id_subject_curr
LEFT JOIN rooms r ON mc.room_id = r.id_room
WHERE mc.id_makeup = ?
`)
@@ -142,9 +143,12 @@ export default defineEventHandler(async (event) => {
const syncCalendarEvent = async (makeupId) => {
// ค้นหาข้อมูล makeup class ปัจจุบัน
const cls = db.prepare(`
- SELECT mc.*, s.name_subject, r.room_name, t.prefix, t.first_name, t.last_name
+ SELECT mc.*,
+ CASE WHEN cs.subject_code IS NOT NULL THEN cs.subject_code || ' ' || cs.name_subject ELSE cs.name_subject END as name_subject,
+ r.room_name, t.prefix, t.first_name, t.last_name
FROM makeup_classes mc
LEFT JOIN Subjects s ON mc.subject_id = s.id_subject
+ LEFT JOIN curriculum_subjects cs ON s.curriculum_subject_id = cs.id_subject_curr
LEFT JOIN rooms r ON mc.room_id = r.id_room
LEFT JOIN teachers t ON mc.teacher_id = t.id_teacher
WHERE mc.id_makeup = ?
diff --git a/server/api/makeup-classes/index.js b/server/api/makeup-classes/index.js
index 8b29a38..3f31d2b 100644
--- a/server/api/makeup-classes/index.js
+++ b/server/api/makeup-classes/index.js
@@ -14,12 +14,13 @@ export default defineEventHandler(async (event) => {
t.first_name,
t.last_name,
s.section_name,
- sub.name_subject,
+ CASE WHEN cs.subject_code IS NOT NULL THEN cs.subject_code || ' ' || cs.name_subject ELSE cs.name_subject END as name_subject,
r.room_name
FROM makeup_classes mc
LEFT JOIN teachers t ON mc.teacher_id = t.id_teacher
LEFT JOIN sections s ON mc.section_id = s.id_section
LEFT JOIN Subjects sub ON mc.subject_id = sub.id_subject
+ LEFT JOIN curriculum_subjects cs ON sub.curriculum_subject_id = cs.id_subject_curr
LEFT JOIN rooms r ON mc.room_id = r.id_room
WHERE 1=1
`
diff --git a/server/api/rooms/schedule.js b/server/api/rooms/schedule.js
index b4cd193..b079895 100644
--- a/server/api/rooms/schedule.js
+++ b/server/api/rooms/schedule.js
@@ -47,10 +47,12 @@ export default defineEventHandler(async (event) => {
// 2. Fetch Makeup Classes for the specific date
const makeupClasses = db.prepare(`
SELECT mc.id_makeup, mc.makeup_time_start, mc.makeup_time_end, mc.room_id,
- t.prefix, t.first_name, t.last_name, sub.name_subject as subject_name
+ t.prefix, t.first_name, t.last_name,
+ CASE WHEN cs.subject_code IS NOT NULL THEN cs.subject_code || ' ' || cs.name_subject ELSE cs.name_subject END as subject_name
FROM makeup_classes mc
LEFT JOIN teachers t ON mc.teacher_id = t.id_teacher
LEFT JOIN Subjects sub ON mc.subject_id = sub.id_subject
+ LEFT JOIN curriculum_subjects cs ON sub.curriculum_subject_id = cs.id_subject_curr
WHERE mc.makeup_date = ? AND mc.status != 'cancelled' AND mc.room_id IS NOT NULL
`).all(date)
@@ -90,11 +92,17 @@ export default defineEventHandler(async (event) => {
}
// Lookup table for subjects to get their default rooms
- const subjectsRaw = db.prepare('SELECT id_subject, name_subject FROM Subjects').all()
+ const subjectsRaw = db.prepare(`
+ SELECT s.id_subject, cs.name_subject as curriculum_name, cs.subject_code
+ FROM Subjects s
+ LEFT JOIN curriculum_subjects cs ON s.curriculum_subject_id = cs.id_subject_curr
+ `).all()
const subjectData = {}
subjectsRaw.forEach((s) => {
subjectData[s.id_subject] = {
- name: s.name_subject
+ name: s.curriculum_name
+ ? (s.subject_code ? `${s.subject_code} ${s.curriculum_name}` : s.curriculum_name)
+ : 'ไม่ทราบชื่อวิชา'
}
})
diff --git a/server/api/study-plan-subjects/[id].js b/server/api/study-plan-subjects/[id].js
new file mode 100644
index 0000000..0abd3a6
--- /dev/null
+++ b/server/api/study-plan-subjects/[id].js
@@ -0,0 +1,14 @@
+import db from '../../utils/db.js'
+
+export default defineEventHandler(async (event) => {
+ const id = event.context.params.id
+
+ if (event.node.req.method === 'DELETE') {
+ const stmt = db.prepare('DELETE FROM study_plan_subjects WHERE id_plan_subject = ?')
+ const result = stmt.run(id)
+ if (result.changes === 0) {
+ throw createError({ statusCode: 404, statusMessage: 'Study Plan Subject not found' })
+ }
+ return { success: true }
+ }
+})
diff --git a/server/api/study-plan-subjects/index.js b/server/api/study-plan-subjects/index.js
new file mode 100644
index 0000000..2602574
--- /dev/null
+++ b/server/api/study-plan-subjects/index.js
@@ -0,0 +1,47 @@
+import db from '../../utils/db.js'
+
+export default defineEventHandler(async (event) => {
+ if (event.node.req.method === 'GET') {
+ const query = getQuery(event)
+ if (query.id_plan) {
+ return db.prepare(`
+ SELECT sps.*, cs.subject_code, cs.name_subject, cc.name_category
+ FROM study_plan_subjects sps
+ JOIN curriculum_subjects cs ON sps.id_subject_curr = cs.id_subject_curr
+ LEFT JOIN curriculum_categories cc ON cs.id_category = cc.id_category
+ WHERE sps.id_plan = ?
+ ORDER BY sps.year ASC, sps.semester ASC, cc.id_category ASC, cs.subject_code ASC
+ `).all(query.id_plan)
+ }
+ return []
+ }
+
+ if (event.node.req.method === 'POST') {
+ const body = await readBody(event)
+ if (!body.id_plan || !body.id_subject_curr) {
+ throw createError({ statusCode: 400, statusMessage: 'id_plan and id_subject_curr are required' })
+ }
+ const year = body.year || 1
+ const semester = body.semester || 1
+
+ try {
+ const stmt = db.prepare('INSERT INTO study_plan_subjects (id_plan, id_subject_curr, year, semester) VALUES (?, ?, ?, ?)')
+ const result = stmt.run(body.id_plan, body.id_subject_curr, year, semester)
+
+ // Fetch details back
+ const inserted = db.prepare(`
+ SELECT sps.*, cs.subject_code, cs.name_subject
+ FROM study_plan_subjects sps
+ JOIN curriculum_subjects cs ON sps.id_subject_curr = cs.id_subject_curr
+ WHERE sps.id_plan_subject = ?
+ `).get(result.lastInsertRowid)
+
+ return inserted
+ } catch (error) {
+ if (error.code === 'SQLITE_CONSTRAINT_UNIQUE') {
+ throw createError({ statusCode: 409, statusMessage: 'Subject already exists in this plan' })
+ }
+ throw error
+ }
+ }
+})
diff --git a/server/api/study-plans/[id].js b/server/api/study-plans/[id].js
new file mode 100644
index 0000000..4333175
--- /dev/null
+++ b/server/api/study-plans/[id].js
@@ -0,0 +1,28 @@
+import db from '../../utils/db.js'
+
+export default defineEventHandler(async (event) => {
+ const id = event.context.params.id
+
+ if (event.node.req.method === 'PUT') {
+ const body = await readBody(event)
+ if (!body.name_plan || !body.level) {
+ throw createError({ statusCode: 400, statusMessage: 'Name and level are required' })
+ }
+ const stmt = db.prepare('UPDATE study_plans SET name_plan = ?, level = ? WHERE id_plan = ?')
+ const result = stmt.run(body.name_plan, body.level, id)
+
+ if (result.changes === 0) {
+ throw createError({ statusCode: 404, statusMessage: 'Study Plan not found' })
+ }
+ return { success: true }
+ }
+
+ if (event.node.req.method === 'DELETE') {
+ const stmt = db.prepare('DELETE FROM study_plans WHERE id_plan = ?')
+ const result = stmt.run(id)
+ if (result.changes === 0) {
+ throw createError({ statusCode: 404, statusMessage: 'Study Plan not found' })
+ }
+ return { success: true }
+ }
+})
diff --git a/server/api/study-plans/index.js b/server/api/study-plans/index.js
new file mode 100644
index 0000000..ff7b016
--- /dev/null
+++ b/server/api/study-plans/index.js
@@ -0,0 +1,26 @@
+import db from '../../utils/db.js'
+
+export default defineEventHandler(async (event) => {
+ if (event.node.req.method === 'GET') {
+ const query = getQuery(event)
+ if (query.id_curriculum) {
+ return db.prepare('SELECT * FROM study_plans WHERE id_curriculum = ? ORDER BY created_at DESC').all(query.id_curriculum)
+ }
+ return db.prepare('SELECT * FROM study_plans ORDER BY id_curriculum ASC, created_at DESC').all()
+ }
+
+ if (event.node.req.method === 'POST') {
+ const body = await readBody(event)
+ if (!body.id_curriculum || !body.name_plan || !body.level) {
+ throw createError({ statusCode: 400, statusMessage: 'Curriculum ID, name, and level are required' })
+ }
+ const stmt = db.prepare('INSERT INTO study_plans (id_curriculum, name_plan, level) VALUES (?, ?, ?)')
+ const result = stmt.run(body.id_curriculum, body.name_plan, body.level)
+ return {
+ id_plan: result.lastInsertRowid,
+ id_curriculum: body.id_curriculum,
+ name_plan: body.name_plan,
+ level: body.level
+ }
+ }
+})
diff --git a/server/api/teacher-classes-on-date.js b/server/api/teacher-classes-on-date.js
index 74e44f7..3c25c89 100644
--- a/server/api/teacher-classes-on-date.js
+++ b/server/api/teacher-classes-on-date.js
@@ -145,9 +145,14 @@ function createClassObj(subjectId, startSlot, duration, teacherId, date, roomId,
}
function getSubjectName(subjectId) {
- const stmt = db.prepare('SELECT name_subject FROM Subjects WHERE id_subject = ?')
- const result = stmt.get(subjectId)
- return result?.name_subject || 'ไม่ทราบชื่อวิชา'
+ const result = db.prepare(`
+ SELECT cs.name_subject as curriculum_name, cs.subject_code
+ FROM Subjects s
+ LEFT JOIN curriculum_subjects cs ON s.curriculum_subject_id = cs.id_subject_curr
+ WHERE s.id_subject = ?
+ `).get(subjectId)
+ if (!result || !result.curriculum_name) return 'ไม่ทราบชื่อวิชา'
+ return result.subject_code ? `${result.subject_code} ${result.curriculum_name}` : result.curriculum_name
}
function getSectionsForSubjectArray(subjectId) {
diff --git a/server/api/terms/[id].delete.js b/server/api/terms/[id].delete.js
index e666a2f..75307ae 100644
--- a/server/api/terms/[id].delete.js
+++ b/server/api/terms/[id].delete.js
@@ -28,7 +28,7 @@ export default defineEventHandler(async (event) => {
// 1. Get IDs before deletion so we know what to clean up
const teacherIdsInTerm = db.prepare('SELECT DISTINCT id_teacher FROM schedules WHERE term = ?').all(termStr).map(r => r.id_teacher)
- const sectionIdsInTerm = db.prepare('SELECT id_section FROM section_terms WHERE term = ?').all(termStr).map(r => r.id_section)
+ const sectionIdsInTerm = db.prepare('SELECT id_section FROM section_schedules WHERE term = ?').all(termStr).map(r => r.id_section)
// 2. Identify and Delete makeup_classes AND their associated Calendar Events
const combinedMakeupIds = []
@@ -64,8 +64,7 @@ export default defineEventHandler(async (event) => {
// 4. Delete section_schedules
db.prepare('DELETE FROM section_schedules WHERE term = ?').run(termStr)
- // 5. Delete section_terms for this term
- db.prepare('DELETE FROM section_terms WHERE term = ?').run(termStr)
+
// 6. Delete schedules (teacher timetables)
db.prepare('DELETE FROM schedules WHERE term = ?').run(termStr)
diff --git a/server/api/terms/[id].put.js b/server/api/terms/[id].put.js
index 2c03472..bf50444 100644
--- a/server/api/terms/[id].put.js
+++ b/server/api/terms/[id].put.js
@@ -59,7 +59,6 @@ export default defineEventHandler(async (event) => {
const updateRefs = db.transaction(() => {
db.prepare('UPDATE schedules SET term = ? WHERE term = ?').run(newTermStr, oldTermStr)
db.prepare('UPDATE section_schedules SET term = ? WHERE term = ?').run(newTermStr, oldTermStr)
- db.prepare('UPDATE section_terms SET term = ? WHERE term = ?').run(newTermStr, oldTermStr)
db.prepare('UPDATE external_subjects SET term = ? WHERE term = ?').run(newTermStr, oldTermStr)
db.prepare('UPDATE Subjects SET term = ? WHERE term = ?').run(newTermStr, oldTermStr)
db.prepare('UPDATE terms SET term = ?, academic_year = ?, start_date = ?, end_date = ? WHERE id_term = ?')
diff --git a/server/data/data.db b/server/data/data.db
index fae60eb..6ad7a17 100644
Binary files a/server/data/data.db and b/server/data/data.db differ
diff --git a/server/utils/autoSchedule.js b/server/utils/autoSchedule.js
index 03c9193..a893809 100644
--- a/server/utils/autoSchedule.js
+++ b/server/utils/autoSchedule.js
@@ -32,10 +32,16 @@ export async function findAvailableSlots(teacherId, missedDate, term) {
}
})
- const allSubjects = db.prepare('SELECT id_subject, name_subject FROM Subjects').all()
+ const allSubjects = db.prepare(`
+ SELECT s.id_subject, cs.name_subject as curriculum_name, cs.subject_code
+ FROM Subjects s
+ LEFT JOIN curriculum_subjects cs ON s.curriculum_subject_id = cs.id_subject_curr
+ `).all()
const subjectNameMap = {}
allSubjects.forEach((s) => {
- subjectNameMap[s.id_subject] = s.name_subject
+ subjectNameMap[s.id_subject] = s.curriculum_name
+ ? (s.subject_code ? `${s.subject_code} ${s.curriculum_name}` : s.curriculum_name)
+ : 'ไม่ทราบชื่อวิชา'
})
// 1. ดึงตารางสอนของอาจารย์ในวันที่ขาด
@@ -395,9 +401,14 @@ function getSectionSchedule(sectionId, term) {
* ดึงชื่อวิชา
*/
function getSubjectName(subjectId) {
- const stmt = db.prepare('SELECT name_subject FROM Subjects WHERE id_subject = ?')
- const result = stmt.get(subjectId)
- return result?.name_subject || 'ไม่ทราบชื่อวิชา'
+ const result = db.prepare(`
+ SELECT cs.name_subject as curriculum_name, cs.subject_code
+ FROM Subjects s
+ LEFT JOIN curriculum_subjects cs ON s.curriculum_subject_id = cs.id_subject_curr
+ WHERE s.id_subject = ?
+ `).get(subjectId)
+ if (!result || !result.curriculum_name) return 'ไม่ทราบชื่อวิชา'
+ return result.subject_code ? `${result.subject_code} ${result.curriculum_name}` : result.curriculum_name
}
/**
@@ -496,10 +507,16 @@ export async function findAvailableSlotsForMultipleClasses(teacherId, missedDate
}
})
- const allSubjects = db.prepare('SELECT id_subject, name_subject FROM Subjects').all()
+ const allSubjects = db.prepare(`
+ SELECT s.id_subject, cs.name_subject as curriculum_name, cs.subject_code
+ FROM Subjects s
+ LEFT JOIN curriculum_subjects cs ON s.curriculum_subject_id = cs.id_subject_curr
+ `).all()
const subjectNameMap = {}
allSubjects.forEach((s) => {
- subjectNameMap[s.id_subject] = s.name_subject
+ subjectNameMap[s.id_subject] = s.curriculum_name
+ ? (s.subject_code ? `${s.subject_code} ${s.curriculum_name}` : s.curriculum_name)
+ : 'ไม่ทราบชื่อวิชา'
})
// เตรียมข้อมูลวันหยุดและวันลาของอาจารย์
diff --git a/server/utils/availability.js b/server/utils/availability.js
index f05f507..1662d05 100644
--- a/server/utils/availability.js
+++ b/server/utils/availability.js
@@ -126,11 +126,19 @@ export function checkRoomAvailability({ room_id, date, start_time, end_time, ter
}
if (classUsesRoom) {
- const subject = db.prepare('SELECT name_subject FROM Subjects WHERE id_subject = ?').get(slot.value)
+ const subject = db.prepare(`
+ SELECT cs.name_subject as curriculum_name, cs.subject_code
+ FROM Subjects s
+ LEFT JOIN curriculum_subjects cs ON s.curriculum_subject_id = cs.id_subject_curr
+ WHERE s.id_subject = ?
+ `).get(slot.value)
+ const subjectName = subject?.curriculum_name
+ ? (subject.subject_code ? `${subject.subject_code} ${subject.curriculum_name}` : subject.curriculum_name)
+ : slot.value
const teacherName = [ts.prefix, ts.first_name, ts.last_name].filter(Boolean).join(' ').trim()
return {
available: false,
- reason: `ตรงกับตารางเรียนปกติ (วิชา ${subject?.name_subject || slot.value}) สอนโดย ${teacherName}`
+ reason: `ตรงกับตารางเรียนปกติ (วิชา ${subjectName}) สอนโดย ${teacherName}`
}
}
}
diff --git a/server/utils/db.js b/server/utils/db.js
index 19ea07c..7cac59c 100644
--- a/server/utils/db.js
+++ b/server/utils/db.js
@@ -13,6 +13,60 @@ if (!existsSync(dbDir)) {
const dbPath = resolve(dbDir, 'data.db')
const db = new Database(dbPath)
+// curriculums
+db.exec(`
+ CREATE TABLE IF NOT EXISTS curriculums (
+ id_curriculum INTEGER PRIMARY KEY AUTOINCREMENT,
+ name_curriculum TEXT NOT NULL UNIQUE,
+ description TEXT,
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP
+ );`)
+
+// curriculum_categories
+db.exec(`
+ CREATE TABLE IF NOT EXISTS curriculum_categories (
+ id_category INTEGER PRIMARY KEY AUTOINCREMENT,
+ id_curriculum INTEGER NOT NULL,
+ parent_id INTEGER,
+ name_category TEXT NOT NULL,
+ FOREIGN KEY (id_curriculum) REFERENCES curriculums(id_curriculum) ON DELETE CASCADE,
+ FOREIGN KEY (parent_id) REFERENCES curriculum_categories(id_category) ON DELETE CASCADE
+ );`)
+
+// curriculum_subjects
+db.exec(`
+ CREATE TABLE IF NOT EXISTS curriculum_subjects (
+ id_subject_curr INTEGER PRIMARY KEY AUTOINCREMENT,
+ id_category INTEGER NOT NULL,
+ subject_code TEXT,
+ name_subject TEXT NOT NULL,
+ FOREIGN KEY (id_category) REFERENCES curriculum_categories(id_category) ON DELETE CASCADE
+ );`)
+
+// study_plans
+db.exec(`
+ CREATE TABLE IF NOT EXISTS study_plans (
+ id_plan INTEGER PRIMARY KEY AUTOINCREMENT,
+ id_curriculum INTEGER NOT NULL,
+ name_plan TEXT NOT NULL,
+ level TEXT NOT NULL,
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+ FOREIGN KEY (id_curriculum) REFERENCES curriculums(id_curriculum) ON DELETE CASCADE
+ );`)
+
+// study_plan_subjects
+db.exec(`
+ CREATE TABLE IF NOT EXISTS study_plan_subjects (
+ id_plan_subject INTEGER PRIMARY KEY AUTOINCREMENT,
+ id_plan INTEGER NOT NULL,
+ id_subject_curr INTEGER NOT NULL,
+ year INTEGER DEFAULT 1,
+ semester INTEGER DEFAULT 1,
+ FOREIGN KEY (id_plan) REFERENCES study_plans(id_plan) ON DELETE CASCADE,
+ FOREIGN KEY (id_subject_curr) REFERENCES curriculum_subjects(id_subject_curr) ON DELETE CASCADE,
+ UNIQUE(id_plan, id_subject_curr)
+ );`)
+
// teachers
db.exec(`
CREATE TABLE IF NOT EXISTS teachers (
@@ -26,7 +80,6 @@ db.exec(`
db.exec(`
CREATE TABLE IF NOT EXISTS Subjects (
id_subject INTEGER PRIMARY KEY AUTOINCREMENT,
- name_subject TEXT NOT NULL,
id_teacher INTEGER,
term TEXT,
FOREIGN KEY (id_teacher) REFERENCES teachers(id_teacher)
@@ -106,6 +159,17 @@ try {
db.exec('ALTER TABLE teachers DROP COLUMN subject')
console.log('Migrated teachers table: removed unused subject column')
}
+ // Migration: Add year and semester to study_plan_subjects
+ const spsTableInfo = db.prepare('PRAGMA table_info(study_plan_subjects)').all()
+ if (!spsTableInfo.some(col => col.name === 'year')) {
+ db.exec('ALTER TABLE study_plan_subjects ADD COLUMN year INTEGER DEFAULT 1')
+ console.log('Migrated study_plan_subjects table: added year column')
+ }
+ if (!spsTableInfo.some(col => col.name === 'semester')) {
+ db.exec('ALTER TABLE study_plan_subjects ADD COLUMN semester INTEGER DEFAULT 1')
+ console.log('Migrated study_plan_subjects table: added semester column')
+ }
+
} catch (err) {
console.error('Migration error:', err)
}
@@ -226,10 +290,7 @@ db.exec(`
UNIQUE(id_section, term)
);`)
-// สร้าง indexes สำหรับ sections
-db.exec(`
- CREATE INDEX IF NOT EXISTS idx_section_terms_term
- ON section_terms(term);`)
+
db.exec(`
CREATE INDEX IF NOT EXISTS idx_section_schedules_section
@@ -319,7 +380,12 @@ try {
safeDropColumn('rooms', 'capacity')
safeDropColumn('rooms', 'building')
safeDropColumn('Subjects', 'id_room')
+ safeDropColumn('Subjects', 'name_subject')
safeDropColumn('teachers', 'subject')
+ safeDropColumn('study_plans', 'year')
+ safeDropColumn('study_plans', 'semester')
+
+ db.exec('DROP TABLE IF EXISTS section_terms')
// Add term column to Subjects if missing
const subjectInfo = db.prepare('PRAGMA table_info(Subjects)').all()
@@ -331,6 +397,12 @@ try {
// Add index for Subjects term
db.exec('CREATE INDEX IF NOT EXISTS idx_subjects_term ON Subjects(term)')
+ // Add curriculum_subject_id to Subjects if missing
+ if (!subjectInfo.some(col => col.name === 'curriculum_subject_id')) {
+ db.exec('ALTER TABLE Subjects ADD COLUMN curriculum_subject_id INTEGER REFERENCES curriculum_subjects(id_subject_curr) ON DELETE SET NULL')
+ console.log('Migrated Subjects table: added curriculum_subject_id column')
+ }
+
// 2. Sections/Terms Restructuring
const sectionInfo = db.prepare('PRAGMA table_info(sections)').all()
if (sectionInfo.some(col => col.name === 'term')) {
@@ -366,7 +438,7 @@ try {
db.exec('DELETE FROM sections')
const insertMaster = db.prepare('INSERT INTO sections (section_name, description, created_at) VALUES (?, ?, ?)')
- const insertTerm = db.prepare('INSERT OR IGNORE INTO section_terms (id_section, term) VALUES (?, ?)')
+
for (const name in masterSections) {
const m = masterSections[name]
@@ -375,7 +447,6 @@ try {
for (const old of m.oldIds) {
mapping[old.id] = newId
- insertTerm.run(newId, old.term)
}
}
@@ -413,14 +484,12 @@ try {
console.log('Sections/Terms migration completed successfully.')
// 2.5 Subjects table migration (Populate term if missing)
- // Run this AFTER section_terms is populated
console.log('Finalizing Subjects term data...')
db.exec(`
UPDATE Subjects
SET term = (
- SELECT st.term
+ SELECT ss.term
FROM SubjectSections ss
- JOIN section_terms st ON ss.id_section = st.id_section
WHERE ss.id_subject = Subjects.id_subject
LIMIT 1
)