mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-30 08:48:11 +00:00
feat(warehouse): P2 dashboard — gate/dock throughput + live auto-refresh
- gateStats(): items cleared through the gate today, average arrival→gate turnaround (hours, 30d), and clearances per hour over the last 24h → new GateThroughputCard in the dashboard Performance section. - Live board: every dashboard query (dashboard, ops, throughput, dwell, cycle, on-time, zone occupancy, accrual, gate) now auto-refreshes on a 60s interval, with a "Live" indicator in the header. New endpoint GET /warehouse-inventory/gate-stats (guarded). Deferred (need upstream data): capacity forecast, labour productivity, WebSocket push, yard map. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -95,6 +95,13 @@ export class WarehouseInventoryController {
|
||||
return this.inventoryService.cycleStats();
|
||||
}
|
||||
|
||||
@Get('gate-stats')
|
||||
@BookingStaff(FREIGHT_PERMS.warehouseInventory.view)
|
||||
@ApiOperation({ summary: 'Gate/dock throughput: cleared today, turnaround, hourly clearances' })
|
||||
gateStats() {
|
||||
return this.inventoryService.gateStats();
|
||||
}
|
||||
|
||||
@Post('auto-unload-arrived')
|
||||
@BookingStaff(FREIGHT_PERMS.warehouseInventory.unload)
|
||||
@ApiOperation({ summary: 'Bulk auto-unload all arrived bookings into the warehouse' })
|
||||
|
||||
@@ -537,6 +537,58 @@ export class WarehouseInventoryService {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Gate / dock throughput: items cleared through the gate today, the average
|
||||
* arrival→gate-clearance turnaround (hours, last 30 days), and gate clearances
|
||||
* bucketed per hour over the last 24 hours. Powers the gate throughput card.
|
||||
*/
|
||||
async gateStats(): Promise<{
|
||||
clearedToday: number;
|
||||
avgTurnaroundHours: number | null;
|
||||
byHour: Array<{ hour: string; count: number }>;
|
||||
}> {
|
||||
const [scalar]: Array<{ clearedToday: number; avgTurnaroundHours: number | null }> =
|
||||
await this.dataSource.query(
|
||||
`SELECT
|
||||
count(*) FILTER (WHERE gate_cleared_at::date = CURRENT_DATE)::int AS "clearedToday",
|
||||
round(
|
||||
avg(EXTRACT(EPOCH FROM (gate_cleared_at - arrived_at)) / 3600.0)
|
||||
FILTER (
|
||||
WHERE gate_cleared_at IS NOT NULL AND arrived_at IS NOT NULL
|
||||
AND gate_cleared_at > now() - interval '30 days'
|
||||
)::numeric,
|
||||
1
|
||||
)::float8 AS "avgTurnaroundHours"
|
||||
FROM freight.warehouse_inventory
|
||||
WHERE deleted_at IS NULL`,
|
||||
);
|
||||
const byHour: Array<{ hour: string; count: number }> = await this.dataSource.query(
|
||||
`WITH hours AS (
|
||||
SELECT gs AS h
|
||||
FROM generate_series(
|
||||
date_trunc('hour', now()) - interval '23 hours',
|
||||
date_trunc('hour', now()),
|
||||
interval '1 hour'
|
||||
) gs
|
||||
)
|
||||
SELECT to_char(hours.h, 'HH24:00') AS hour,
|
||||
COALESCE(g.cnt, 0)::int AS count
|
||||
FROM hours
|
||||
LEFT JOIN (
|
||||
SELECT date_trunc('hour', gate_cleared_at) AS ph, count(*) AS cnt
|
||||
FROM freight.warehouse_inventory
|
||||
WHERE deleted_at IS NULL AND gate_cleared_at IS NOT NULL
|
||||
GROUP BY 1
|
||||
) g ON g.ph = hours.h
|
||||
ORDER BY hours.h`,
|
||||
);
|
||||
return {
|
||||
clearedToday: scalar?.clearedToday ?? 0,
|
||||
avgTurnaroundHours: scalar?.avgTurnaroundHours ?? null,
|
||||
byHour,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Received-vs-dispatched throughput as a server-side time series. Buckets by
|
||||
* date_trunc over the last N periods (8 weeks / 12 months / 5 years) with a
|
||||
|
||||
Reference in New Issue
Block a user