mirror of
https://github.com/rgrgogu/new_starr.git
synced 2026-09-27 00:12:54 +08:00
added new requirements for lessons and units
Signed-off-by: Kenneth Obsequio <k80308392@gmail.com>
This commit is contained in:
@@ -1,16 +1,31 @@
|
||||
/***********************************************************************************************************************************************************************
|
||||
* File Name: task_reading_progress_sync.service.js
|
||||
* Type of Program: Service
|
||||
* Description: Backfills task_progress for read_* task requirements from course_reading_progress.
|
||||
* Description: Bridges course/unit/lesson completion (course_reading_progress) to Task
|
||||
* requirements of type read_course/read_unit/read_lesson, in both directions:
|
||||
*
|
||||
* This covers the case where a user already completed reading a course/unit/lesson
|
||||
* before a task requiring that item was created or assigned.
|
||||
* hydrateReadTaskProgress(userId, requirements)
|
||||
* — given a list of TaskRequirement rows (typically when a task/task list is newly
|
||||
* assigned), backfills task_progress for any that reference content the user has
|
||||
* already completed reading.
|
||||
*
|
||||
* syncCompletedEntitiesToTaskProgress(userId, { lessonUuid, unitUuid, courseUuid })
|
||||
* — given the UUIDs of course/unit/lesson entities that just reached 'completed'
|
||||
* (from any trigger — scroll-to-bottom, watch_percent threshold, manual_complete,
|
||||
* pass_quiz, assessment pass), finds matching TaskRequirement rows the user is
|
||||
* assigned to and marks them done, then reports any task whose read-only
|
||||
* requirements are now ALL satisfied (eligible for "auto turned-in" display).
|
||||
* Called from every completion.service.js entry point — NOT just the legacy
|
||||
* lesson-progress endpoint — so a unit/course completing via pass_quiz/manual_complete
|
||||
* alone (no lesson ever read) still satisfies read_unit/read_course task requirements.
|
||||
***********************************************************************************************************************************************************************/
|
||||
'use strict';
|
||||
|
||||
const { Op } = require('sequelize');
|
||||
const CourseReadingProgress = require('../models/courses/course_reading_progress.mdl');
|
||||
const { TaskProgress } = require('../models/task/task_progress.mdl');
|
||||
const { Op } = require('sequelize');
|
||||
const CourseReadingProgress = require('../models/courses/course_reading_progress.mdl');
|
||||
const { TaskProgress } = require('../models/task/task_progress.mdl');
|
||||
const { Task, TaskRequirement, TaskListGroup } = require('../models/task/task.mdl');
|
||||
const { mdl_UserGroupMembers } = require('../models/users/user_groups.mdl');
|
||||
|
||||
const READ_TYPE_TO_PROGRESS_TYPE = {
|
||||
read_course: 'course',
|
||||
@@ -132,7 +147,97 @@ async function hydrateReadTaskProgress(userId, requirements = [], options = {})
|
||||
return missingRows;
|
||||
}
|
||||
|
||||
// ─── Accessible task lists (via group membership) ────────────────────────────
|
||||
|
||||
async function getAccessibleTaskListIds(userId) {
|
||||
const memberships = await mdl_UserGroupMembers.findAll({
|
||||
where: { user_id: userId, deletedAt: null },
|
||||
attributes: ['group_id'],
|
||||
});
|
||||
const groupIds = memberships.map((m) => m.group_id);
|
||||
if (!groupIds.length) return [];
|
||||
|
||||
const taskListGroups = await TaskListGroup.findAll({
|
||||
where: { group_id: groupIds },
|
||||
attributes: ['task_list_id'],
|
||||
});
|
||||
return [...new Set(taskListGroups.map((tlg) => tlg.task_list_id))];
|
||||
}
|
||||
|
||||
// ─── Sync from a completion event (any trigger) → task_progress ─────────────
|
||||
|
||||
/**
|
||||
* Call after ANY course/unit/lesson reaches 'completed' for a user, regardless of which
|
||||
* completion-requirement type triggered it. Finds TaskRequirement rows (read_course/
|
||||
* read_unit/read_lesson) referencing the given UUIDs, among task lists the user's groups
|
||||
* can access, backfills task_progress via hydrateReadTaskProgress, and reports any task
|
||||
* whose read-only requirements are now ALL satisfied.
|
||||
*
|
||||
* @param {number} userId
|
||||
* @param {{ lessonUuid?: string, unitUuid?: string, courseUuid?: string }} uuids
|
||||
* @returns {Promise<{ task_id, task_name }[]>}
|
||||
*/
|
||||
async function syncCompletedEntitiesToTaskProgress(userId, { lessonUuid, unitUuid, courseUuid } = {}) {
|
||||
const referenceIds = [lessonUuid, unitUuid, courseUuid].filter(Boolean);
|
||||
if (!referenceIds.length) return [];
|
||||
|
||||
const taskListIds = await getAccessibleTaskListIds(userId);
|
||||
if (!taskListIds.length) return [];
|
||||
|
||||
const requirements = await TaskRequirement.findAll({
|
||||
where: {
|
||||
reference_id: { [Op.in]: referenceIds },
|
||||
type: { [Op.in]: READ_REQUIREMENT_TYPES },
|
||||
deletedAt: null,
|
||||
},
|
||||
include: [{
|
||||
model: Task,
|
||||
as: 'task',
|
||||
where: { task_list_id: { [Op.in]: taskListIds }, deletedAt: null },
|
||||
required: true,
|
||||
attributes: ['task_id', 'name', 'task_list_id'],
|
||||
}],
|
||||
attributes: ['requirement_id', 'task_id', 'type', 'reference_id'],
|
||||
});
|
||||
if (!requirements.length) return [];
|
||||
|
||||
const newlyCompleted = await hydrateReadTaskProgress(userId, requirements);
|
||||
if (!newlyCompleted.length) return [];
|
||||
|
||||
// Check whether any impacted task now has ALL its read-only requirements satisfied —
|
||||
// tasks with any non-read requirement (upload_file/visit_link/submit_text/pass_quiz)
|
||||
// still need manual submission, so they're excluded from auto-turn-in.
|
||||
const taskIds = [...new Set(newlyCompleted.map((r) => r.task_id))];
|
||||
const completedTasks = [];
|
||||
|
||||
for (const taskId of taskIds) {
|
||||
const allReqs = await TaskRequirement.findAll({
|
||||
where: { task_id: taskId, deletedAt: null },
|
||||
attributes: ['requirement_id', 'type', 'reference_id'],
|
||||
});
|
||||
|
||||
const hasNonReadReqs = allReqs.some((r) => !READ_REQUIREMENT_TYPES.includes(r.type));
|
||||
if (hasNonReadReqs) continue;
|
||||
|
||||
const doneProgress = await TaskProgress.findAll({
|
||||
where: { task_id: taskId, user_id: userId, completed: true },
|
||||
attributes: ['requirement_id', 'reference_id'],
|
||||
});
|
||||
const doneSet = new Set(doneProgress.map((p) => `${p.requirement_id}:${p.reference_id}`));
|
||||
const allDone = allReqs.every((r) => doneSet.has(`${r.requirement_id}:${r.reference_id}`));
|
||||
|
||||
if (allDone) {
|
||||
const task = requirements.find((r) => r.task_id === taskId)?.task;
|
||||
completedTasks.push({ task_id: taskId, task_name: task?.name ?? '' });
|
||||
}
|
||||
}
|
||||
|
||||
return completedTasks;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
hydrateReadTaskProgress,
|
||||
syncCompletedEntitiesToTaskProgress,
|
||||
getAccessibleTaskListIds,
|
||||
READ_REQUIREMENT_TYPES,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user