Files
sena_db_api_layer/controllers/parentTaskController.js
silverpro89 3791b7cae1
All checks were successful
Deploy to Production / deploy (push) Successful in 21s
update
2026-01-28 11:21:21 +07:00

386 lines
10 KiB
JavaScript

const { ParentAssignedTask, GradeItem, StudentDetail, UsersAuth, ParentStudentMap } = require('../models');
const { cacheUtils } = require('../config/redis');
const { 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',
};
// Create task directly in database
const task = await ParentAssignedTask.create(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(201).json({
success: true,
message: 'Task assigned successfully',
data: task,
});
} 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 task.update({ 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 task.update({
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(200).json({
success: true,
message: 'Task completed successfully',
data: task,
});
} 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 task.update({ status });
// Clear cache
await cacheUtils.del(`parent:tasks:${task.parent_id}:*`);
await cacheUtils.del(`student:tasks:${task.student_id}:*`);
res.status(200).json({
success: true,
message: 'Task status updated successfully',
data: task,
});
} 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 task.destroy();
// Clear cache
await cacheUtils.del(`parent:tasks:${task.parent_id}:*`);
await cacheUtils.del(`student:tasks:${task.student_id}:*`);
res.status(200).json({
success: true,
message: 'Task deleted successfully',
});
} 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();