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,7 +1,12 @@
import express from 'express';
import { DateTime } from 'luxon';
import { getDbConnection, getPoolStatus } from '../db/connection.js';
import { getTimeRangeConditions } from '../utils/timeUtils.js';
import {
getTimeRangeConditions,
customRange,
previousRange,
BUSINESS_TZ,
} from '../../../shared/business-time/index.js';
const router = express.Router();
@@ -170,9 +175,10 @@ router.get('/', async (req, res) => {
const ordersPerHour = totalPickingHours > 0 ? totalOrdersPicked / totalPickingHours : 0;
const piecesPerHour = totalPickingHours > 0 ? totalPiecesPicked / totalPickingHours : 0;
// Get daily trend data for picking
// Use DATE_FORMAT to get date string in Eastern timezone
// Business day starts at 1 AM, so subtract 1 hour before taking the date
// Get daily trend data for picking.
// Columns store Central wall-clock literals, and the business date IS
// the Central calendar date — so DATE(col) is the business date with
// no hour shift.
const pickingTrendWhere = whereClause.replace(/date_placed/g, 'pt.createddate');
const pickingTrendQuery = `
SELECT
@@ -181,22 +187,22 @@ router.get('/', async (req, res) => {
pt_agg.piecesPicked
FROM (
SELECT
DATE_FORMAT(DATE_SUB(pt.createddate, INTERVAL 1 HOUR), '%Y-%m-%d') as date,
DATE_FORMAT(pt.createddate, '%Y-%m-%d') as date,
COALESCE(SUM(pt.totalpieces_picked), 0) as piecesPicked
FROM picking_ticket pt
WHERE ${pickingTrendWhere}
AND pt.closeddate IS NOT NULL
GROUP BY DATE_FORMAT(DATE_SUB(pt.createddate, INTERVAL 1 HOUR), '%Y-%m-%d')
GROUP BY DATE_FORMAT(pt.createddate, '%Y-%m-%d')
) pt_agg
LEFT JOIN (
SELECT
DATE_FORMAT(DATE_SUB(pt.createddate, INTERVAL 1 HOUR), '%Y-%m-%d') as date,
DATE_FORMAT(pt.createddate, '%Y-%m-%d') as date,
COUNT(DISTINCT CASE WHEN ptb.is_sub = 0 OR ptb.is_sub IS NULL THEN ptb.orderid END) as ordersPicked
FROM picking_ticket pt
LEFT JOIN picking_ticket_buckets ptb ON pt.pickingid = ptb.pickingid
WHERE ${pickingTrendWhere}
AND pt.closeddate IS NOT NULL
GROUP BY DATE_FORMAT(DATE_SUB(pt.createddate, INTERVAL 1 HOUR), '%Y-%m-%d')
GROUP BY DATE_FORMAT(pt.createddate, '%Y-%m-%d')
) order_counts ON pt_agg.date = order_counts.date
ORDER BY pt_agg.date
`;
@@ -205,13 +211,13 @@ router.get('/', async (req, res) => {
const shippingTrendWhere = whereClause.replace(/date_placed/g, 'o.date_shipped');
const shippingTrendQuery = `
SELECT
DATE_FORMAT(DATE_SUB(o.date_shipped, INTERVAL 1 HOUR), '%Y-%m-%d') as date,
DATE_FORMAT(o.date_shipped, '%Y-%m-%d') as date,
COUNT(DISTINCT CASE WHEN o.order_type != 8 OR o.order_type IS NULL THEN o.order_id END) as ordersShipped,
COALESCE(SUM(o.stats_prod_pieces), 0) as piecesShipped
FROM _order o
WHERE ${shippingTrendWhere}
AND o.order_status IN (100, 92)
GROUP BY DATE_FORMAT(DATE_SUB(o.date_shipped, INTERVAL 1 HOUR), '%Y-%m-%d')
GROUP BY DATE_FORMAT(o.date_shipped, '%Y-%m-%d')
ORDER BY date
`;
@@ -239,13 +245,14 @@ router.get('/', async (req, res) => {
});
});
// Generate all dates in the period range for complete trend data
// Generate all business dates in the half-open period [start, end) for
// complete trend data. Business date = Chicago calendar date.
const allDatesInRange = [];
const startDt = DateTime.fromJSDate(periodStart).setZone(TIMEZONE).startOf('day');
const endDt = DateTime.fromJSDate(periodEnd).setZone(TIMEZONE).startOf('day');
const startDt = DateTime.fromJSDate(periodStart).setZone(BUSINESS_TZ).startOf('day');
const endDt = DateTime.fromJSDate(periodEnd).setZone(BUSINESS_TZ);
let currentDt = startDt;
while (currentDt <= endDt) {
while (currentDt < endDt) {
allDatesInRange.push(currentDt.toFormat('yyyy-MM-dd'));
currentDt = currentDt.plus({ days: 1 });
}
@@ -255,7 +262,8 @@ router.get('/', async (req, res) => {
const picking = pickingByDate.get(date) || { ordersPicked: 0, piecesPicked: 0 };
const shippingData = shippingByDate.get(date) || { ordersShipped: 0, piecesShipped: 0 };
// Parse date string in Eastern timezone to get proper ISO timestamp
// The string is already the business date; the zone here is only for
// the display timestamp label (office time).
const dateDt = DateTime.fromFormat(date, 'yyyy-MM-dd', { zone: TIMEZONE });
return {
@@ -446,22 +454,13 @@ function getPreviousPeriodRange(timeRange, startDate, endDate) {
return null;
}
const start = new Date(startDate);
const end = new Date(endDate);
if (Number.isNaN(start.getTime()) || Number.isNaN(end.getTime())) {
try {
const prev = previousRange(customRange(startDate, endDate));
if (!prev) return null;
return getTimeRangeConditions('custom', prev.start.toISO(), prev.end.toISO());
} catch {
return null;
}
const duration = end.getTime() - start.getTime();
if (!Number.isFinite(duration) || duration <= 0) {
return null;
}
const prevEnd = new Date(start.getTime() - 1);
const prevStart = new Date(prevEnd.getTime() - duration);
return getTimeRangeConditions('custom', prevStart.toISOString(), prevEnd.toISOString());
}
function getPreviousTimeRange(timeRange) {