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