310 lines
8.3 KiB
JavaScript
310 lines
8.3 KiB
JavaScript
const { StaffTrainingAssignment, StaffAchievement, UsersAuth, Subject } = require('../models');
|
|
const { cacheUtils } = require('../config/redis');
|
|
const { v4: uuidv4 } = require('uuid');
|
|
|
|
/**
|
|
* Training Controller - Quản lý đào tạo nhân sự
|
|
*/
|
|
class TrainingController {
|
|
/**
|
|
* GET /api/training/assignments/:staff_id - Danh sách khóa học bắt buộc của nhân viên
|
|
*/
|
|
async getStaffAssignments(req, res, next) {
|
|
try {
|
|
const { staff_id } = req.params;
|
|
const { status, priority } = req.query;
|
|
|
|
const cacheKey = `training:assignments:${staff_id}:${status || 'all'}:${priority || 'all'}`;
|
|
|
|
const cached = await cacheUtils.get(cacheKey);
|
|
if (cached) {
|
|
return res.json({
|
|
success: true,
|
|
data: cached,
|
|
cached: true,
|
|
});
|
|
}
|
|
|
|
const where = { staff_id };
|
|
if (status) where.status = status;
|
|
if (priority) where.priority = priority;
|
|
|
|
const assignments = await StaffTrainingAssignment.findAll({
|
|
where,
|
|
include: [
|
|
{
|
|
model: Subject,
|
|
as: 'subject',
|
|
attributes: ['subject_name', 'subject_code', 'description'],
|
|
},
|
|
{
|
|
model: UsersAuth,
|
|
as: 'assignedBy',
|
|
attributes: ['username', 'full_name'],
|
|
},
|
|
],
|
|
order: [
|
|
['priority', 'DESC'],
|
|
['deadline', 'ASC'],
|
|
],
|
|
});
|
|
|
|
// Check for overdue assignments
|
|
const now = new Date();
|
|
for (const assignment of assignments) {
|
|
if (assignment.status !== 'completed' && new Date(assignment.deadline) < now) {
|
|
if (assignment.status !== 'overdue') {
|
|
await assignment.update({ status: 'overdue' });
|
|
}
|
|
}
|
|
}
|
|
|
|
await cacheUtils.set(cacheKey, assignments, 900); // Cache 15 min
|
|
|
|
res.json({
|
|
success: true,
|
|
data: assignments,
|
|
});
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* POST /api/training/assignments - Phân công đào tạo mới
|
|
*/
|
|
async assignTraining(req, res, next) {
|
|
try {
|
|
const { staff_id, course_name, subject_id, deadline, priority, notes, assigned_by } = req.body;
|
|
|
|
if (!staff_id || !course_name || !deadline || !assigned_by) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: 'Missing required fields: staff_id, course_name, deadline, assigned_by',
|
|
});
|
|
}
|
|
|
|
const assignmentData = {
|
|
id: uuidv4(),
|
|
staff_id,
|
|
course_name,
|
|
subject_id: subject_id || null,
|
|
assigned_by,
|
|
assigned_date: new Date(),
|
|
deadline: new Date(deadline),
|
|
status: 'pending',
|
|
priority: priority || 'normal',
|
|
notes: notes || null,
|
|
};
|
|
|
|
const newAssignment = await StaffTrainingAssignment.create(assignmentData);
|
|
|
|
// Clear cache
|
|
await cacheUtils.del(`training:assignments:${staff_id}:*`);
|
|
|
|
res.status(201).json({
|
|
success: true,
|
|
message: 'Training assignment created successfully',
|
|
data: newAssignment,
|
|
});
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* PUT /api/training/assignments/:id/status - Cập nhật trạng thái assignment
|
|
*/
|
|
async updateAssignmentStatus(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 assignment = await StaffTrainingAssignment.findByPk(id);
|
|
if (!assignment) {
|
|
return res.status(404).json({
|
|
success: false,
|
|
message: 'Assignment not found',
|
|
});
|
|
}
|
|
|
|
await assignment.update({ status });
|
|
|
|
// Clear cache
|
|
await cacheUtils.del(`training:assignments:${assignment.staff_id}:*`);
|
|
|
|
res.status(200).json({
|
|
success: true,
|
|
message: 'Assignment status updated successfully',
|
|
data: assignment,
|
|
});
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* POST /api/training/achievements - Ghi nhận hoàn thành khóa đào tạo
|
|
*/
|
|
async createAchievement(req, res, next) {
|
|
try {
|
|
const {
|
|
staff_id,
|
|
course_name,
|
|
course_id,
|
|
completion_date,
|
|
certificate_url,
|
|
certificate_code,
|
|
type,
|
|
score,
|
|
total_hours,
|
|
verified_by,
|
|
} = req.body;
|
|
|
|
if (!staff_id || !course_name || !completion_date) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: 'Missing required fields: staff_id, course_name, completion_date',
|
|
});
|
|
}
|
|
|
|
const achievementData = {
|
|
id: uuidv4(),
|
|
staff_id,
|
|
course_name,
|
|
course_id: course_id || null,
|
|
completion_date: new Date(completion_date),
|
|
certificate_url: certificate_url || null,
|
|
certificate_code: certificate_code || null,
|
|
type: type || 'mandatory',
|
|
score: score || null,
|
|
total_hours: total_hours || null,
|
|
verified_by: verified_by || null,
|
|
verified_at: verified_by ? new Date() : null,
|
|
};
|
|
|
|
const newAchievement = await StaffAchievement.create(achievementData);
|
|
|
|
// Clear cache
|
|
await cacheUtils.del(`training:achievements:${staff_id}`);
|
|
|
|
res.status(201).json({
|
|
success: true,
|
|
message: 'Achievement created successfully',
|
|
data: newAchievement,
|
|
});
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* GET /api/training/achievements/:staff_id - Danh sách chứng chỉ của nhân viên
|
|
*/
|
|
async getStaffAchievements(req, res, next) {
|
|
try {
|
|
const { staff_id } = req.params;
|
|
const { type } = req.query;
|
|
|
|
const cacheKey = `training:achievements:${staff_id}:${type || 'all'}`;
|
|
|
|
const cached = await cacheUtils.get(cacheKey);
|
|
if (cached) {
|
|
return res.json({
|
|
success: true,
|
|
data: cached,
|
|
cached: true,
|
|
});
|
|
}
|
|
|
|
const where = { staff_id };
|
|
if (type) where.type = type;
|
|
|
|
const achievements = await StaffAchievement.findAll({
|
|
where,
|
|
include: [
|
|
{
|
|
model: Subject,
|
|
as: 'course',
|
|
attributes: ['subject_name', 'subject_code'],
|
|
},
|
|
{
|
|
model: UsersAuth,
|
|
as: 'verifiedBy',
|
|
attributes: ['username', 'full_name'],
|
|
},
|
|
],
|
|
order: [['completion_date', 'DESC']],
|
|
});
|
|
|
|
await cacheUtils.set(cacheKey, achievements, 1800); // Cache 30 min
|
|
|
|
res.json({
|
|
success: true,
|
|
data: achievements,
|
|
});
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* GET /api/training/stats - Thống kê đào tạo
|
|
*/
|
|
async getTrainingStats(req, res, next) {
|
|
try {
|
|
const cacheKey = 'training:stats';
|
|
|
|
const cached = await cacheUtils.get(cacheKey);
|
|
if (cached) {
|
|
return res.json({
|
|
success: true,
|
|
data: cached,
|
|
cached: true,
|
|
});
|
|
}
|
|
|
|
const assignmentStats = await StaffTrainingAssignment.findAll({
|
|
attributes: [
|
|
'status',
|
|
[require('sequelize').fn('COUNT', require('sequelize').col('id')), 'count'],
|
|
],
|
|
group: ['status'],
|
|
});
|
|
|
|
const achievementStats = await StaffAchievement.findAll({
|
|
attributes: [
|
|
'type',
|
|
[require('sequelize').fn('COUNT', require('sequelize').col('id')), 'count'],
|
|
[require('sequelize').fn('SUM', require('sequelize').col('total_hours')), 'total_hours'],
|
|
],
|
|
group: ['type'],
|
|
});
|
|
|
|
const stats = {
|
|
assignments: assignmentStats,
|
|
achievements: achievementStats,
|
|
};
|
|
|
|
await cacheUtils.set(cacheKey, stats, 300); // Cache 5 min
|
|
|
|
res.json({
|
|
success: true,
|
|
data: stats,
|
|
});
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
}
|
|
}
|
|
|
|
module.exports = new TrainingController();
|