added for multiple clock in and out sessions on export
This commit is contained in:
+31
-24
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user