Time unification

This commit is contained in:
2026-06-17 15:06:38 -04:00
parent 069a44bd54
commit 54a2460eac
67 changed files with 1442 additions and 1329 deletions
@@ -1,11 +1,20 @@
import express from 'express';
import { DateTime } from 'luxon';
import { getDbConnection, getPoolStatus } from '../db/connection.js';
import { parseMySql, toMySqlBound } from '../../../shared/business-time/index.js';
const router = express.Router();
const TIMEZONE = 'America/New_York';
// timeclock.TimeStamp stores Central wall-clock literals; the driver returns
// them as strings (dateStrings: true). Parse in business time, compare/display
// in ET (pay-period boundaries are an ET display convention).
const punchDateTime = (punch) => {
const dt = parseMySql(punch.TimeStamp);
return dt ? dt.setZone(TIMEZONE) : null;
};
// Punch types from the database
const PUNCH_TYPES = {
OUT: 0,
@@ -108,17 +117,17 @@ function calculateHoursByWeek(punches, payPeriod) {
byEmployee.forEach((employeeData) => {
// Sort punches by timestamp
employeeData.punches.sort((a, b) => new Date(a.TimeStamp) - new Date(b.TimeStamp));
employeeData.punches.sort((a, b) => (punchDateTime(a)?.toMillis() ?? 0) - (punchDateTime(b)?.toMillis() ?? 0));
// Calculate hours for each week
const week1Punches = employeeData.punches.filter(p => {
const dt = DateTime.fromJSDate(new Date(p.TimeStamp), { zone: TIMEZONE });
return dt >= payPeriod.week1.start && dt <= payPeriod.week1.end;
const dt = punchDateTime(p);
return dt && dt >= payPeriod.week1.start && dt <= payPeriod.week1.end;
});
const week2Punches = employeeData.punches.filter(p => {
const dt = DateTime.fromJSDate(new Date(p.TimeStamp), { zone: TIMEZONE });
return dt >= payPeriod.week2.start && dt <= payPeriod.week2.end;
const dt = punchDateTime(p);
return dt && dt >= payPeriod.week2.start && dt <= payPeriod.week2.end;
});
const week1Hours = calculateHoursFromPunches(week1Punches);
@@ -205,7 +214,7 @@ function calculateHoursFromPunches(punches) {
let breakStart = null;
punches.forEach(punch => {
const punchTime = new Date(punch.TimeStamp);
const punchTime = punchDateTime(punch)?.toJSDate() ?? new Date(NaN);
switch (punch.PunchType) {
case PUNCH_TYPES.IN:
@@ -283,9 +292,11 @@ router.get('/', async (req, res) => {
console.log(`[PAYROLL-METRICS] DB connection obtained in ${Date.now() - startTime}ms`);
try {
// Build query for the pay period
const periodStart = payPeriod.start.toJSDate();
const periodEnd = payPeriod.end.toJSDate();
// Build query for the pay period. Bounds are serialized to Central
// wall-clock strings (the column's literal zone), half-open
// [start, start + 14 days).
const periodStart = toMySqlBound(payPeriod.start);
const periodEnd = toMySqlBound(payPeriod.start.plus({ days: 14 }));
const timeclockQuery = `
SELECT
@@ -296,7 +307,7 @@ router.get('/', async (req, res) => {
e.lastname
FROM timeclock tc
LEFT JOIN employees e ON tc.EmployeeID = e.employeeid
WHERE tc.TimeStamp >= ? AND tc.TimeStamp <= ?
WHERE tc.TimeStamp >= ? AND tc.TimeStamp < ?
AND e.hidden = 0
AND e.disabled = 0
ORDER BY tc.EmployeeID, tc.TimeStamp
@@ -322,8 +333,8 @@ router.get('/', async (req, res) => {
// Get previous pay period data for comparison
const prevPayPeriod = navigatePayPeriod(payPeriod.start, -1);
const [prevTimeclockRows] = await connection.execute(timeclockQuery, [
prevPayPeriod.start.toJSDate(),
prevPayPeriod.end.toJSDate(),
toMySqlBound(prevPayPeriod.start),
toMySqlBound(prevPayPeriod.start.plus({ days: 14 })),
]);
const prevHoursData = calculateHoursByWeek(prevTimeclockRows, prevPayPeriod);