added for multiple clock in and out sessions on export

This commit is contained in:
Edison
2025-10-17 10:22:33 +08:00
parent 5276a7544c
commit 93f316d8ab
+31 -24
View File
@@ -317,7 +317,8 @@ const TZ = tz || process.env.EXPORT_TZ || 'Asia/Kuala_Lumpur';
JOIN workers w ON cr.worker_id = w.id
LEFT JOIN qr_codes qc ON cr.qr_code_id = qc.id
WHERE cr.timestamp BETWEEN ? AND ? ${workerIdClause}
ORDER BY cr.worker_id, cr.timestamp ASC
AND cr.event_type IN ('clock_in','clock_out')
ORDER BY cr.worker_id, cr.timestamp ASC
`;
const [rows] = await db.execute(query, params);
@@ -345,46 +346,52 @@ rows.forEach(row => {
});
});
// ---- Build ONE row per worker/day (no double rows) ----
const csvData = [];
const byWorkerForXlsx = new Map(); // key = "username||full_name||department" → daily rows
// ---- Build rows: one per successful [clock_in, clock_out] session ----
const csvData = [];
const byWorkerForXlsx = new Map(); // key = "username||full_name||department" → daily rows
for (const workerId in workByDay) {
const w = workByDay[workerId];
const perWorkerRows = [];
for (const workerId in workByDay) {
const w = workByDay[workerId];
const perWorkerRows = [];
for (const day of Object.keys(w.days).sort()) {
const events = w.days[day].slice().sort((a, b) => a.time - b.time);
for (const day of Object.keys(w.days).sort()) {
// events for this day in ascending time
const events = w.days[day].slice().sort((a, b) => a.time - b.time);
// total hours across all in/out pairs
let totalSec = 0;
let open = null;
for (const e of events) {
if (e.type === 'clock_in' && open == null) open = e.time;
else if (e.type === 'clock_out' && open != null) { totalSec += (e.time - open) / 1000; open = null; }
}
let open = null;
let openQr = 'Manual Entry';
const firstIn = events.find(e => e.type === 'clock_in') || null;
const lastOut = [...events].reverse().find(e => e.type === 'clock_out') || null;
for (const e of events) {
if (e.type === 'clock_in' && open == null) {
open = e.time;
openQr = e.qr_code_name || 'Manual Entry';
} else if (e.type === 'clock_out' && open != null) {
const start = open;
const end = e.time;
const dailyRow = {
username: w.username,
full_name: w.full_name,
date: day,
day: dayNameFromYMD(day),
clock_in: firstIn ? hmInTZ(firstIn.time, TZ) : '',
clock_out: lastOut ? hmInTZ(lastOut.time, TZ) : '',
work_hours: (firstIn && lastOut) ? (totalSec / 3600).toFixed(2) : '',
qr_code_name: firstIn ? (firstIn.qr_code_name || 'Manual Entry') : ''
clock_in: hmInTZ(start, TZ),
clock_out: hmInTZ(end, TZ),
work_hours: ((end - start) / 3600000).toFixed(2),
qr_code_name: openQr
};
csvData.push(dailyRow);
perWorkerRows.push(dailyRow);
// close the session
open = null;
openQr = 'Manual Entry';
}
byWorkerForXlsx.set(`${w.username}||${w.full_name}||${w.department}`, perWorkerRows);
}
}
byWorkerForXlsx.set(`${w.username}||${w.full_name}||${w.department}`, perWorkerRows);
}
// ===== XLSX branch: grouped header + per-day summary columns =====
if (wantXlsx) {
const wb = new ExcelJS.Workbook();