392 lines
10 KiB
JavaScript
392 lines
10 KiB
JavaScript
const { ParentAssignedTask, GradeItem, StudentDetail, UsersAuth, ParentStudentMap } = require('../models');
|
|
const { cacheUtils } = require('../config/redis');
|
|
const { addDatabaseWriteJob, addNotificationJob } = require('../config/bullmq');
|
|
const { v4: uuidv4 } = require('uuid');
|
|
|
|
/**
|
|
* Parent Task Controller - Quản lý bài tập phụ huynh giao cho con
|
|
*/
|
|
class ParentTaskController {
|
|
/**
|
|
* POST /api/parent-tasks/assign - Phụ huynh giao bài tập cho con
|
|
*/
|
|
async assignTask(req, res, next) {
|
|
try {
|
|
const { parent_id, student_id, grade_item_id, due_date, message, priority } = req.body;
|
|
|
|
if (!parent_id || !student_id || !grade_item_id || !due_date) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: 'Missing required fields: parent_id, student_id, grade_item_id, due_date',
|
|
});
|
|
}
|
|
|
|
// Kiểm tra quyền của phụ huynh
|
|
const parentMapping = await ParentStudentMap.findOne({
|
|
where: {
|
|
parent_id,
|
|
student_id,
|
|
},
|
|
});
|
|
|
|
if (!parentMapping) {
|
|
return res.status(403).json({
|
|
success: false,
|
|
message: 'Parent-student relationship not found',
|
|
});
|
|
}
|
|
|
|
if (!parentMapping.can_assign_tasks) {
|
|
return res.status(403).json({
|
|
success: false,
|
|
message: 'Parent does not have permission to assign tasks',
|
|
});
|
|
}
|
|
|
|
// Kiểm tra grade_item có tồn tại không
|
|
const gradeItem = await GradeItem.findByPk(grade_item_id);
|
|
if (!gradeItem) {
|
|
return res.status(404).json({
|
|
success: false,
|
|
message: 'Grade item not found',
|
|
});
|
|
}
|
|
|
|
const taskData = {
|
|
id: uuidv4(),
|
|
parent_id,
|
|
student_id,
|
|
grade_item_id,
|
|
assigned_at: new Date(),
|
|
due_date: new Date(due_date),
|
|
status: 'pending',
|
|
message: message || null,
|
|
priority: priority || 'normal',
|
|
};
|
|
|
|
// Async write to DB via BullMQ
|
|
await addDatabaseWriteJob('create', 'ParentAssignedTask', taskData);
|
|
|
|
// Gửi notification cho học sinh
|
|
await addNotificationJob({
|
|
user_id: student_id,
|
|
type: 'parent_task_assigned',
|
|
title: 'Bài tập mới từ phụ huynh',
|
|
message: message || `Bạn có bài tập mới: ${gradeItem.item_name}`,
|
|
data: taskData,
|
|
});
|
|
|
|
// Clear cache
|
|
await cacheUtils.del(`parent:tasks:${parent_id}`);
|
|
await cacheUtils.del(`student:tasks:${student_id}`);
|
|
|
|
res.status(202).json({
|
|
success: true,
|
|
message: 'Task assignment request queued',
|
|
data: taskData,
|
|
});
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* GET /api/parent-tasks/parent/:parent_id - Danh sách bài tập phụ huynh đã giao
|
|
*/
|
|
async getParentTasks(req, res, next) {
|
|
try {
|
|
const { parent_id } = req.params;
|
|
const { status, student_id } = req.query;
|
|
|
|
const cacheKey = `parent:tasks:${parent_id}:${status || 'all'}:${student_id || 'all'}`;
|
|
|
|
const cached = await cacheUtils.get(cacheKey);
|
|
if (cached) {
|
|
return res.json({
|
|
success: true,
|
|
data: cached,
|
|
cached: true,
|
|
});
|
|
}
|
|
|
|
const where = { parent_id };
|
|
if (status) where.status = status;
|
|
if (student_id) where.student_id = student_id;
|
|
|
|
const tasks = await ParentAssignedTask.findAll({
|
|
where,
|
|
include: [
|
|
{
|
|
model: StudentDetail,
|
|
as: 'student',
|
|
attributes: ['student_code', 'full_name'],
|
|
},
|
|
{
|
|
model: GradeItem,
|
|
as: 'gradeItem',
|
|
attributes: ['item_name', 'max_score', 'description'],
|
|
},
|
|
],
|
|
order: [
|
|
['priority', 'DESC'],
|
|
['due_date', 'ASC'],
|
|
],
|
|
});
|
|
|
|
// Check for overdue tasks
|
|
const now = new Date();
|
|
for (const task of tasks) {
|
|
if (task.status !== 'completed' && new Date(task.due_date) < now) {
|
|
if (task.status !== 'overdue') {
|
|
await addDatabaseWriteJob('update', 'ParentAssignedTask', {
|
|
id: task.id,
|
|
status: 'overdue',
|
|
});
|
|
task.status = 'overdue';
|
|
}
|
|
}
|
|
}
|
|
|
|
await cacheUtils.set(cacheKey, tasks, 900); // Cache 15 min
|
|
|
|
res.json({
|
|
success: true,
|
|
data: tasks,
|
|
});
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* GET /api/parent-tasks/student/:student_id - Danh sách bài tập của học sinh
|
|
*/
|
|
async getStudentTasks(req, res, next) {
|
|
try {
|
|
const { student_id } = req.params;
|
|
const { status } = req.query;
|
|
|
|
const cacheKey = `student:tasks:${student_id}:${status || 'all'}`;
|
|
|
|
const cached = await cacheUtils.get(cacheKey);
|
|
if (cached) {
|
|
return res.json({
|
|
success: true,
|
|
data: cached,
|
|
cached: true,
|
|
});
|
|
}
|
|
|
|
const where = { student_id };
|
|
if (status) where.status = status;
|
|
|
|
const tasks = await ParentAssignedTask.findAll({
|
|
where,
|
|
include: [
|
|
{
|
|
model: UsersAuth,
|
|
as: 'parent',
|
|
attributes: ['username', 'full_name'],
|
|
},
|
|
{
|
|
model: GradeItem,
|
|
as: 'gradeItem',
|
|
attributes: ['item_name', 'max_score', 'description'],
|
|
},
|
|
],
|
|
order: [
|
|
['priority', 'DESC'],
|
|
['due_date', 'ASC'],
|
|
],
|
|
});
|
|
|
|
await cacheUtils.set(cacheKey, tasks, 900); // Cache 15 min
|
|
|
|
res.json({
|
|
success: true,
|
|
data: tasks,
|
|
});
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* PUT /api/parent-tasks/:id/complete - Học sinh hoàn thành bài tập
|
|
*/
|
|
async completeTask(req, res, next) {
|
|
try {
|
|
const { id } = req.params;
|
|
const { student_notes } = req.body;
|
|
|
|
const task = await ParentAssignedTask.findByPk(id);
|
|
|
|
if (!task) {
|
|
return res.status(404).json({
|
|
success: false,
|
|
message: 'Task not found',
|
|
});
|
|
}
|
|
|
|
if (task.status === 'completed') {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: 'Task already completed',
|
|
});
|
|
}
|
|
|
|
await addDatabaseWriteJob('update', 'ParentAssignedTask', {
|
|
id,
|
|
status: 'completed',
|
|
completion_date: new Date(),
|
|
student_notes: student_notes || null,
|
|
});
|
|
|
|
// Gửi notification cho phụ huynh
|
|
await addNotificationJob({
|
|
user_id: task.parent_id,
|
|
type: 'parent_task_completed',
|
|
title: 'Bài tập đã hoàn thành',
|
|
message: `Con bạn đã hoàn thành bài tập`,
|
|
data: { task_id: id },
|
|
});
|
|
|
|
// Clear cache
|
|
await cacheUtils.del(`parent:tasks:${task.parent_id}:*`);
|
|
await cacheUtils.del(`student:tasks:${task.student_id}:*`);
|
|
|
|
res.status(202).json({
|
|
success: true,
|
|
message: 'Task completion request queued',
|
|
});
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* PUT /api/parent-tasks/:id/status - Cập nhật trạng thái bài tập
|
|
*/
|
|
async updateTaskStatus(req, res, next) {
|
|
try {
|
|
const { id } = req.params;
|
|
const { status } = req.body;
|
|
|
|
const validStatuses = ['pending', 'in_progress', 'completed', 'overdue'];
|
|
if (!validStatuses.includes(status)) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: `Invalid status. Must be one of: ${validStatuses.join(', ')}`,
|
|
});
|
|
}
|
|
|
|
const task = await ParentAssignedTask.findByPk(id);
|
|
if (!task) {
|
|
return res.status(404).json({
|
|
success: false,
|
|
message: 'Task not found',
|
|
});
|
|
}
|
|
|
|
await addDatabaseWriteJob('update', 'ParentAssignedTask', {
|
|
id,
|
|
status,
|
|
});
|
|
|
|
// Clear cache
|
|
await cacheUtils.del(`parent:tasks:${task.parent_id}:*`);
|
|
await cacheUtils.del(`student:tasks:${task.student_id}:*`);
|
|
|
|
res.status(202).json({
|
|
success: true,
|
|
message: 'Task status update queued',
|
|
});
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* DELETE /api/parent-tasks/:id - Xóa bài tập (chỉ phụ huynh mới xóa được)
|
|
*/
|
|
async deleteTask(req, res, next) {
|
|
try {
|
|
const { id } = req.params;
|
|
const { parent_id } = req.body; // Xác thực parent_id
|
|
|
|
const task = await ParentAssignedTask.findByPk(id);
|
|
|
|
if (!task) {
|
|
return res.status(404).json({
|
|
success: false,
|
|
message: 'Task not found',
|
|
});
|
|
}
|
|
|
|
if (task.parent_id !== parent_id) {
|
|
return res.status(403).json({
|
|
success: false,
|
|
message: 'Only the parent who assigned the task can delete it',
|
|
});
|
|
}
|
|
|
|
await addDatabaseWriteJob('delete', 'ParentAssignedTask', { id });
|
|
|
|
// Clear cache
|
|
await cacheUtils.del(`parent:tasks:${task.parent_id}:*`);
|
|
await cacheUtils.del(`student:tasks:${task.student_id}:*`);
|
|
|
|
res.status(202).json({
|
|
success: true,
|
|
message: 'Task deletion request queued',
|
|
});
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* GET /api/parent-tasks/stats - Thống kê bài tập
|
|
*/
|
|
async getTaskStats(req, res, next) {
|
|
try {
|
|
const { parent_id, student_id } = req.query;
|
|
|
|
const cacheKey = `parent:tasks:stats:${parent_id || 'all'}:${student_id || 'all'}`;
|
|
|
|
const cached = await cacheUtils.get(cacheKey);
|
|
if (cached) {
|
|
return res.json({
|
|
success: true,
|
|
data: cached,
|
|
cached: true,
|
|
});
|
|
}
|
|
|
|
const where = {};
|
|
if (parent_id) where.parent_id = parent_id;
|
|
if (student_id) where.student_id = student_id;
|
|
|
|
const stats = await ParentAssignedTask.findAll({
|
|
where,
|
|
attributes: [
|
|
'status',
|
|
[require('sequelize').fn('COUNT', require('sequelize').col('id')), 'count'],
|
|
],
|
|
group: ['status'],
|
|
});
|
|
|
|
await cacheUtils.set(cacheKey, stats, 300); // Cache 5 min
|
|
|
|
res.json({
|
|
success: true,
|
|
data: stats,
|
|
});
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
}
|
|
}
|
|
|
|
module.exports = new ParentTaskController();
|