249 lines
6.1 KiB
JavaScript
249 lines
6.1 KiB
JavaScript
const { AttendanceLog, AttendanceDaily, UsersAuth, School } = require('../models');
|
|
const { cacheUtils } = require('../config/redis');
|
|
const { addDatabaseWriteJob, addAttendanceProcessJob } = require('../config/bullmq');
|
|
|
|
/**
|
|
* Attendance Controller - Quản lý điểm danh
|
|
*/
|
|
class AttendanceController {
|
|
/**
|
|
* Get attendance logs with pagination
|
|
*/
|
|
async getAttendanceLogs(req, res, next) {
|
|
try {
|
|
const { page = 1, limit = 50, user_id, school_id, date_from, date_to } = req.query;
|
|
const offset = (page - 1) * limit;
|
|
|
|
const cacheKey = `attendance:logs:${page}:${limit}:${user_id || 'all'}:${school_id || 'all'}:${date_from || ''}:${date_to || ''}`;
|
|
|
|
const cached = await cacheUtils.get(cacheKey);
|
|
if (cached) {
|
|
return res.json({
|
|
success: true,
|
|
data: cached,
|
|
cached: true,
|
|
});
|
|
}
|
|
|
|
const where = {};
|
|
if (user_id) where.user_id = user_id;
|
|
if (school_id) where.school_id = school_id;
|
|
if (date_from && date_to) {
|
|
where.check_time = {
|
|
[require('sequelize').Op.between]: [date_from, date_to],
|
|
};
|
|
}
|
|
|
|
const { count, rows } = await AttendanceLog.findAndCountAll({
|
|
where,
|
|
limit: parseInt(limit),
|
|
offset: parseInt(offset),
|
|
include: [
|
|
{
|
|
model: UsersAuth,
|
|
as: 'user',
|
|
attributes: ['username', 'email'],
|
|
},
|
|
{
|
|
model: School,
|
|
as: 'school',
|
|
attributes: ['school_code', 'school_name'],
|
|
},
|
|
],
|
|
order: [['check_time', 'DESC']],
|
|
});
|
|
|
|
const result = {
|
|
logs: rows,
|
|
pagination: {
|
|
total: count,
|
|
page: parseInt(page),
|
|
limit: parseInt(limit),
|
|
totalPages: Math.ceil(count / limit),
|
|
},
|
|
};
|
|
|
|
await cacheUtils.set(cacheKey, result, 300); // Cache 5 minutes
|
|
|
|
res.json({
|
|
success: true,
|
|
data: result,
|
|
cached: false,
|
|
});
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Create attendance log (QR scan)
|
|
*/
|
|
async createAttendanceLog(req, res, next) {
|
|
try {
|
|
const logData = req.body;
|
|
|
|
const job = await addDatabaseWriteJob('create', 'AttendanceLog', logData);
|
|
|
|
await cacheUtils.deletePattern('attendance:logs:*');
|
|
await cacheUtils.deletePattern('attendance:daily:*');
|
|
|
|
res.status(202).json({
|
|
success: true,
|
|
message: 'Attendance log created',
|
|
jobId: job.id,
|
|
});
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get daily attendance summary
|
|
*/
|
|
async getDailyAttendance(req, res, next) {
|
|
try {
|
|
const { page = 1, limit = 50, school_id, class_id } = req.query;
|
|
let { date } = req.query;
|
|
const offset = (page - 1) * limit;
|
|
|
|
// Use today's date if not provided
|
|
if (!date) {
|
|
date = new Date().toISOString().split('T')[0]; // Format: YYYY-MM-DD
|
|
}
|
|
|
|
const cacheKey = `attendance:daily:${school_id || 'all'}:${date}:${class_id || 'all'}:${page}:${limit}`;
|
|
|
|
const cached = await cacheUtils.get(cacheKey);
|
|
if (cached) {
|
|
return res.json({
|
|
success: true,
|
|
data: cached,
|
|
cached: true,
|
|
});
|
|
}
|
|
|
|
const where = { attendance_date: date };
|
|
if (school_id) where.school_id = school_id;
|
|
if (class_id) where.class_id = class_id;
|
|
|
|
const { count, rows } = await AttendanceDaily.findAndCountAll({
|
|
where,
|
|
limit: parseInt(limit),
|
|
offset: parseInt(offset),
|
|
include: [{
|
|
model: UsersAuth,
|
|
as: 'user',
|
|
attributes: ['username'],
|
|
}],
|
|
order: [['status', 'ASC']],
|
|
});
|
|
|
|
const result = {
|
|
attendance: rows,
|
|
date: date,
|
|
pagination: {
|
|
total: count,
|
|
page: parseInt(page),
|
|
limit: parseInt(limit),
|
|
totalPages: Math.ceil(count / limit),
|
|
},
|
|
};
|
|
|
|
await cacheUtils.set(cacheKey, result, 1800);
|
|
|
|
res.json({
|
|
success: true,
|
|
data: result,
|
|
cached: false,
|
|
});
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Process daily attendance (aggregate from logs)
|
|
*/
|
|
async processAttendance(req, res, next) {
|
|
try {
|
|
const { school_id, date } = req.body;
|
|
|
|
const job = await addAttendanceProcessJob(school_id, date);
|
|
|
|
await cacheUtils.deletePattern('attendance:daily:*');
|
|
|
|
res.status(202).json({
|
|
success: true,
|
|
message: 'Attendance processing job queued',
|
|
jobId: job.id,
|
|
});
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get attendance statistics
|
|
*/
|
|
async getAttendanceStats(req, res, next) {
|
|
try {
|
|
const { school_id, date_from, date_to } = req.query;
|
|
const cacheKey = `attendance:stats:${school_id}:${date_from}:${date_to}`;
|
|
|
|
const cached = await cacheUtils.get(cacheKey);
|
|
if (cached) {
|
|
return res.json({
|
|
success: true,
|
|
data: cached,
|
|
cached: true,
|
|
});
|
|
}
|
|
|
|
const stats = await AttendanceDaily.findAll({
|
|
where: {
|
|
school_id,
|
|
attendance_date: {
|
|
[require('sequelize').Op.between]: [date_from, date_to],
|
|
},
|
|
},
|
|
attributes: [
|
|
'status',
|
|
[require('sequelize').fn('COUNT', require('sequelize').col('id')), 'count'],
|
|
],
|
|
group: ['status'],
|
|
raw: true,
|
|
});
|
|
|
|
await cacheUtils.set(cacheKey, stats, 3600);
|
|
|
|
res.json({
|
|
success: true,
|
|
data: stats,
|
|
cached: false,
|
|
});
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get attendance datatypes
|
|
*/
|
|
async getAttendanceDatatypes(req, res, next) {
|
|
try {
|
|
const datatypes = {
|
|
AttendanceLog: AttendanceLog.rawAttributes,
|
|
AttendanceDaily: AttendanceDaily.rawAttributes,
|
|
};
|
|
res.json({
|
|
success: true,
|
|
data: datatypes,
|
|
});
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
}
|
|
}
|
|
|
|
module.exports = new AttendanceController();
|