This commit is contained in:
Ken
2026-01-19 09:33:35 +07:00
parent 374dc12b2d
commit 70838a4bc1
103 changed files with 16929 additions and 2 deletions

View File

@@ -0,0 +1,248 @@
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();