diff --git a/Backend/src/services/ChaptersLesson.service.ts b/Backend/src/services/ChaptersLesson.service.ts index ae75acf7..41b401c6 100644 --- a/Backend/src/services/ChaptersLesson.service.ts +++ b/Backend/src/services/ChaptersLesson.service.ts @@ -1032,11 +1032,23 @@ export class ChaptersLessonService { throw new NotFoundError('Question not found in this quiz'); } - const oldSortOrder = existingQuestion.sort_order; const quizId = lesson.quiz.id; + // First, normalize sort_order to fix any gaps or duplicates + await this.normalizeQuestionSortOrder(quizId); + + // Re-fetch the question to get updated sort_order after normalization + const normalizedQuestion = await prisma.question.findUnique({ where: { id: question_id } }); + if (!normalizedQuestion) throw new NotFoundError('Question not found'); + + const oldSortOrder = normalizedQuestion.sort_order; + + // Get total question count to validate sort_order (1-based) + const questionCount = await prisma.question.count({ where: { quiz_id: quizId } }); + const newSortOrder = Math.max(1, Math.min(sort_order, questionCount)); + // If same position, no need to reorder - if (oldSortOrder === sort_order) { + if (oldSortOrder === newSortOrder) { const questions = await prisma.question.findMany({ where: { quiz_id: quizId }, orderBy: { sort_order: 'asc' }, @@ -1046,12 +1058,12 @@ export class ChaptersLessonService { } // Shift other questions to make room for the insert - if (oldSortOrder > sort_order) { + if (oldSortOrder > newSortOrder) { // Moving up: shift questions between newSortOrder and oldSortOrder-1 down by 1 await prisma.question.updateMany({ where: { quiz_id: quizId, - sort_order: { gte: sort_order, lt: oldSortOrder } + sort_order: { gte: newSortOrder, lt: oldSortOrder } }, data: { sort_order: { increment: 1 } } }); @@ -1060,7 +1072,7 @@ export class ChaptersLessonService { await prisma.question.updateMany({ where: { quiz_id: quizId, - sort_order: { gt: oldSortOrder, lte: sort_order } + sort_order: { gt: oldSortOrder, lte: newSortOrder } }, data: { sort_order: { decrement: 1 } } }); @@ -1069,7 +1081,7 @@ export class ChaptersLessonService { // Update the question's sort order await prisma.question.update({ where: { id: question_id }, - data: { sort_order } + data: { sort_order: newSortOrder } }); // Fetch all questions with updated order @@ -1121,6 +1133,9 @@ export class ChaptersLessonService { // Delete the question (CASCADE will delete choices) await prisma.question.delete({ where: { id: question_id } }); + // Normalize sort_order for remaining questions (fill gaps) + await this.normalizeQuestionSortOrder(lesson.quiz.id); + // Recalculate scores for remaining questions await this.recalculateQuestionScores(lesson.quiz.id); @@ -1176,6 +1191,34 @@ export class ChaptersLessonService { logger.info(`Recalculated quiz ${quizId}: ${questionCount} questions. Base: ${baseScore}, Remainder: ${remainder}`); } + /** + * Normalize sort_order for all questions in a quiz + * Ensures sort_order is sequential starting from 0 with no gaps or duplicates + * + * @param quizId Quiz ID to normalize + */ + private async normalizeQuestionSortOrder(quizId: number): Promise { + // Get all questions ordered by current sort_order + const questions = await prisma.question.findMany({ + where: { quiz_id: quizId }, + orderBy: { sort_order: 'asc' }, + select: { id: true, sort_order: true } + }); + + if (questions.length === 0) return; + + // Update each question with sequential sort_order starting from 1 + const updates = questions.map((question, index) => + prisma.question.update({ + where: { id: question.id }, + data: { sort_order: index + 1 } + }) + ); + + await prisma.$transaction(updates); + logger.info(`Normalized sort_order for quiz ${quizId}: ${questions.length} questions`); + } + /** * อัพเดตการตั้งค่า Quiz * Update quiz settings (title, passing_score, time_limit, etc.)