mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-26 18:42:49 +00:00
feat(reports): carry time of day on report date columns
Eleven report columns bucketed their timestamp to a bare day with to_char, which is wrong for anything a user reads as an event rather than a period: two departures on the same date, or a wagon request fulfilled hours after it was raised, were indistinguishable in the output. The renderer only shows the time when the value actually has one, keyed off the string rather than a per-column flag — a genuine day bucket would otherwise render as 12:00 AM, which reads as data rather than as absence.
This commit is contained in:
@@ -68,7 +68,7 @@ export const chargedVsActualVolumeReport: ReportDefinition = {
|
|||||||
query(ctx) {
|
query(ctx) {
|
||||||
return baseQuery(ctx)
|
return baseQuery(ctx)
|
||||||
.select("COALESCE(ts.train_number, '—')", 'trainNumber')
|
.select("COALESCE(ts.train_number, '—')", 'trainNumber')
|
||||||
.addSelect(`to_char(COALESCE(ts.actual_departure_at, ts.scheduled_departure_date), 'YYYY-MM-DD')`, 'departedAt')
|
.addSelect(`to_char(COALESCE(ts.actual_departure_at, ts.scheduled_departure_date), 'YYYY-MM-DD HH24:MI')`, 'departedAt')
|
||||||
.addSelect("COALESCE(oy.label, oy.code, '?') || ' → ' || COALESCE(dy.label, dy.code, '?')", 'station')
|
.addSelect("COALESCE(oy.label, oy.code, '?') || ' → ' || COALESCE(dy.label, dy.code, '?')", 'station')
|
||||||
.addSelect(CARGO_CATEGORY_LABEL_EXPR, 'category')
|
.addSelect(CARGO_CATEGORY_LABEL_EXPR, 'category')
|
||||||
.addSelect(`ROUND((${CHARGED_TONS_EXPR})::numeric, 2)::float8`, 'chargedTons')
|
.addSelect(`ROUND((${CHARGED_TONS_EXPR})::numeric, 2)::float8`, 'chargedTons')
|
||||||
|
|||||||
@@ -91,8 +91,8 @@ export const contractUtilizationReport: ReportDefinition = {
|
|||||||
.addSelect('c.name', 'customer')
|
.addSelect('c.name', 'customer')
|
||||||
.addSelect('ct.status', 'status')
|
.addSelect('ct.status', 'status')
|
||||||
.addSelect('ct.contract_kind', 'kind')
|
.addSelect('ct.contract_kind', 'kind')
|
||||||
.addSelect(`to_char(ct.contract_valid_from, 'YYYY-MM-DD')`, 'validFrom')
|
.addSelect(`to_char(ct.contract_valid_from, 'YYYY-MM-DD HH24:MI')`, 'validFrom')
|
||||||
.addSelect(`to_char(ct.contract_valid_until, 'YYYY-MM-DD')`, 'validUntil')
|
.addSelect(`to_char(ct.contract_valid_until, 'YYYY-MM-DD HH24:MI')`, 'validUntil')
|
||||||
.addSelect('COALESCE(cap.committed, 0)::float8', 'committed')
|
.addSelect('COALESCE(cap.committed, 0)::float8', 'committed')
|
||||||
.addSelect('COALESCE(booked.tons, 0)::float8', 'bookedTons')
|
.addSelect('COALESCE(booked.tons, 0)::float8', 'bookedTons')
|
||||||
.addSelect('COALESCE(booked.cnt, 0)', 'bookings')
|
.addSelect('COALESCE(booked.cnt, 0)', 'bookings')
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ export const firstLastMileBookingsReport: ReportDefinition = {
|
|||||||
.addSelect('fl.status', 'status')
|
.addSelect('fl.status', 'status')
|
||||||
.addSelect("COALESCE(v.plate_number, '—')", 'truck')
|
.addSelect("COALESCE(v.plate_number, '—')", 'truck')
|
||||||
.addSelect("CASE WHEN fl.vehicle_id IS NOT NULL THEN 'Assigned' ELSE 'Unassigned' END", 'assigned')
|
.addSelect("CASE WHEN fl.vehicle_id IS NOT NULL THEN 'Assigned' ELSE 'Unassigned' END", 'assigned')
|
||||||
.addSelect(`to_char(fl.created_at, 'YYYY-MM-DD')`, 'createdAt');
|
.addSelect(`to_char(fl.created_at, 'YYYY-MM-DD HH24:MI')`, 'createdAt');
|
||||||
},
|
},
|
||||||
async summary(ctx) {
|
async summary(ctx) {
|
||||||
const row = await baseQuery(ctx)
|
const row = await baseQuery(ctx)
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ export const loadedCapacityReport: ReportDefinition = {
|
|||||||
query(ctx) {
|
query(ctx) {
|
||||||
return baseQuery(ctx)
|
return baseQuery(ctx)
|
||||||
.select('ts.train_number', 'trainNumber')
|
.select('ts.train_number', 'trainNumber')
|
||||||
.addSelect(`to_char(ts.scheduled_departure_date, 'YYYY-MM-DD')`, 'departureDate')
|
.addSelect(`to_char(ts.scheduled_departure_date, 'YYYY-MM-DD HH24:MI')`, 'departureDate')
|
||||||
.addSelect("COALESCE(wt.name, 'Unknown')", 'wagonType')
|
.addSelect("COALESCE(wt.name, 'Unknown')", 'wagonType')
|
||||||
.addSelect('COUNT(*)::int', 'wagons')
|
.addSelect('COUNT(*)::int', 'wagons')
|
||||||
.addSelect('COALESCE(SUM(tsw.capacity_tons), 0)::float8', 'capacityTons')
|
.addSelect('COALESCE(SUM(tsw.capacity_tons), 0)::float8', 'capacityTons')
|
||||||
|
|||||||
@@ -378,7 +378,7 @@ export const receivablesPayablesReport: ReportDefinition = {
|
|||||||
query(ctx) {
|
query(ctx) {
|
||||||
return baseQuery(ctx)
|
return baseQuery(ctx)
|
||||||
.select(SIDE_LABEL_OF('r.side_key'), 'side')
|
.select(SIDE_LABEL_OF('r.side_key'), 'side')
|
||||||
.addSelect("to_char(r.txn_date, 'YYYY-MM-DD')", 'issuedAt')
|
.addSelect("to_char(r.txn_date, 'YYYY-MM-DD HH24:MI')", 'issuedAt')
|
||||||
.addSelect('r.doc_ref', 'invoiceNumber')
|
.addSelect('r.doc_ref', 'invoiceNumber')
|
||||||
.addSelect('r.booking_ref', 'bookingRef')
|
.addSelect('r.booking_ref', 'bookingRef')
|
||||||
.addSelect('r.booking_status', 'bookingStatus')
|
.addSelect('r.booking_status', 'bookingStatus')
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ export const revenueReconciliationReport: ReportDefinition = {
|
|||||||
defaultSort: { key: 'variance', dir: 'DESC' },
|
defaultSort: { key: 'variance', dir: 'DESC' },
|
||||||
query(ctx) {
|
query(ctx) {
|
||||||
return baseQuery(ctx)
|
return baseQuery(ctx)
|
||||||
.select(`to_char(${REVENUE_DATE}, 'YYYY-MM-DD')`, 'issuedAt')
|
.select(`to_char(${REVENUE_DATE}, 'YYYY-MM-DD HH24:MI')`, 'issuedAt')
|
||||||
.addSelect('i.invoice_number', 'invoiceNumber')
|
.addSelect('i.invoice_number', 'invoiceNumber')
|
||||||
.addSelect("COALESCE(b.reference, '—')", 'bookingRef')
|
.addSelect("COALESCE(b.reference, '—')", 'bookingRef')
|
||||||
.addSelect(PAYER_EXPR, 'customer')
|
.addSelect(PAYER_EXPR, 'customer')
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ export const revenueTransactionsReport: ReportDefinition = {
|
|||||||
defaultSort: { key: 'issuedAt', dir: 'DESC' },
|
defaultSort: { key: 'issuedAt', dir: 'DESC' },
|
||||||
query(ctx) {
|
query(ctx) {
|
||||||
return baseQuery(ctx)
|
return baseQuery(ctx)
|
||||||
.select(`to_char(${REVENUE_DATE}, 'YYYY-MM-DD')`, 'issuedAt')
|
.select(`to_char(${REVENUE_DATE}, 'YYYY-MM-DD HH24:MI')`, 'issuedAt')
|
||||||
.addSelect('i.invoice_number', 'invoiceNumber')
|
.addSelect('i.invoice_number', 'invoiceNumber')
|
||||||
.addSelect("COALESCE(b.reference, '—')", 'bookingRef')
|
.addSelect("COALESCE(b.reference, '—')", 'bookingRef')
|
||||||
.addSelect("COALESCE(b.id::text, '')", 'bookingId')
|
.addSelect("COALESCE(b.id::text, '')", 'bookingId')
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ export const trainScheduleStatusReport: ReportDefinition = {
|
|||||||
.addSelect('ts.direction', 'direction')
|
.addSelect('ts.direction', 'direction')
|
||||||
.addSelect("COALESCE(o.label, 'Unknown')", 'origin')
|
.addSelect("COALESCE(o.label, 'Unknown')", 'origin')
|
||||||
.addSelect("COALESCE(d.label, 'Unknown')", 'destination')
|
.addSelect("COALESCE(d.label, 'Unknown')", 'destination')
|
||||||
.addSelect(`to_char(ts.scheduled_departure_date, 'YYYY-MM-DD')`, 'scheduledDeparture')
|
.addSelect(`to_char(ts.scheduled_departure_date, 'YYYY-MM-DD HH24:MI')`, 'scheduledDeparture')
|
||||||
.addSelect(`to_char(ts.actual_departure_at, 'YYYY-MM-DD HH24:MI')`, 'actualDeparture')
|
.addSelect(`to_char(ts.actual_departure_at, 'YYYY-MM-DD HH24:MI')`, 'actualDeparture')
|
||||||
.addSelect(`to_char(ts.actual_arrival_at, 'YYYY-MM-DD HH24:MI')`, 'actualArrival');
|
.addSelect(`to_char(ts.actual_arrival_at, 'YYYY-MM-DD HH24:MI')`, 'actualArrival');
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -57,8 +57,8 @@ export const wagonRequestsReport: ReportDefinition = {
|
|||||||
.addSelect('r.quantity', 'quantity')
|
.addSelect('r.quantity', 'quantity')
|
||||||
.addSelect('r.fulfilled_quantity', 'fulfilledQuantity')
|
.addSelect('r.fulfilled_quantity', 'fulfilledQuantity')
|
||||||
.addSelect('r.status', 'status')
|
.addSelect('r.status', 'status')
|
||||||
.addSelect(`to_char(r.created_at, 'YYYY-MM-DD')`, 'requestedAt')
|
.addSelect(`to_char(r.created_at, 'YYYY-MM-DD HH24:MI')`, 'requestedAt')
|
||||||
.addSelect(`to_char(r.fulfilled_at, 'YYYY-MM-DD')`, 'fulfilledAt')
|
.addSelect(`to_char(r.fulfilled_at, 'YYYY-MM-DD HH24:MI')`, 'fulfilledAt')
|
||||||
.addSelect(
|
.addSelect(
|
||||||
`ROUND(EXTRACT(EPOCH FROM (COALESCE(r.fulfilled_at, now()) - r.created_at))::numeric / 86400, 1)::float8`,
|
`ROUND(EXTRACT(EPOCH FROM (COALESCE(r.fulfilled_at, now()) - r.created_at))::numeric / 86400, 1)::float8`,
|
||||||
'delayDays',
|
'delayDays',
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ export const wagonStatusDurationReport: ReportDefinition = {
|
|||||||
.addSelect("COALESCE(wt.name, 'Unknown')", 'wagonType')
|
.addSelect("COALESCE(wt.name, 'Unknown')", 'wagonType')
|
||||||
.addSelect("COALESCE(y.label, 'Unassigned')", 'station')
|
.addSelect("COALESCE(y.label, 'Unassigned')", 'station')
|
||||||
.addSelect('w.status', 'status')
|
.addSelect('w.status', 'status')
|
||||||
.addSelect(`to_char(COALESCE(log.since, w.updated_at), 'YYYY-MM-DD')`, 'since')
|
.addSelect(`to_char(COALESCE(log.since, w.updated_at), 'YYYY-MM-DD HH24:MI')`, 'since')
|
||||||
.addSelect(
|
.addSelect(
|
||||||
`FLOOR(EXTRACT(EPOCH FROM (now() - COALESCE(log.since, w.updated_at))) / 86400)::int`,
|
`FLOOR(EXTRACT(EPOCH FROM (now() - COALESCE(log.since, w.updated_at))) / 86400)::int`,
|
||||||
'daysInStatus',
|
'daysInStatus',
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ export const wagonTeuUtilizationReport: ReportDefinition = {
|
|||||||
.select('w.wagon_number', 'wagonNumber')
|
.select('w.wagon_number', 'wagonNumber')
|
||||||
.addSelect("COALESCE(wt.name, 'Unknown')", 'wagonType')
|
.addSelect("COALESCE(wt.name, 'Unknown')", 'wagonType')
|
||||||
.addSelect('ts.train_number', 'trainNumber')
|
.addSelect('ts.train_number', 'trainNumber')
|
||||||
.addSelect(`to_char(ts.scheduled_departure_date, 'YYYY-MM-DD')`, 'departureDate')
|
.addSelect(`to_char(ts.scheduled_departure_date, 'YYYY-MM-DD HH24:MI')`, 'departureDate')
|
||||||
.addSelect('COUNT(c.id)::int', 'containers')
|
.addSelect('COUNT(c.id)::int', 'containers')
|
||||||
.addSelect('(COALESCE(SUM(ct.size_ft), 0) / 20.0)::float8', 'teu')
|
.addSelect('(COALESCE(SUM(ct.size_ft), 0) / 20.0)::float8', 'teu')
|
||||||
.groupBy('w.wagon_number')
|
.groupBy('w.wagon_number')
|
||||||
|
|||||||
@@ -17,10 +17,17 @@ export function formatReportCell(value: unknown, type: ReportColumnType): string
|
|||||||
case "number":
|
case "number":
|
||||||
return Number(value).toLocaleString();
|
return Number(value).toLocaleString();
|
||||||
case "date": {
|
case "date": {
|
||||||
const d = new Date(String(value));
|
const raw = String(value);
|
||||||
return Number.isNaN(d.getTime())
|
const d = new Date(raw);
|
||||||
? String(value)
|
if (Number.isNaN(d.getTime())) return raw;
|
||||||
: d.toLocaleDateString(undefined, { year: "numeric", month: "short", day: "numeric" });
|
// Day-bucket columns carry no time part — don't invent a 12:00 AM for them.
|
||||||
|
const hasTime = /\d:\d/.test(raw);
|
||||||
|
return d.toLocaleString(undefined, {
|
||||||
|
year: "numeric",
|
||||||
|
month: "short",
|
||||||
|
day: "numeric",
|
||||||
|
...(hasTime ? { hour: "2-digit", minute: "2-digit" } : {}),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
return String(value);
|
return String(value);
|
||||||
|
|||||||
Reference in New Issue
Block a user