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();