mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-26 18:42:49 +00:00
feat(warehouse): assemble dashboard cockpit + server-side throughput series
- WarehouseDashboardPage now composes the ops KPI strip, lifecycle cards, flow charts, zone-occupancy heatmap and demurrage/accrual exceptions into one control-tower view; drop the redundant lifecycle donut. - New GET /warehouse-inventory/throughput (date_trunc time series) replaces the client-side buildTrend that downloaded the entire inventory list to bucket it. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -73,6 +73,14 @@ export class WarehouseInventoryController {
|
||||
return this.inventoryService.zoneOccupancy(yardId);
|
||||
}
|
||||
|
||||
@Get('throughput')
|
||||
@BookingStaff(FREIGHT_PERMS.warehouseInventory.view)
|
||||
@ApiOperation({ summary: 'Received-vs-dispatched throughput time series (week/month/year)' })
|
||||
throughput(@Query('granularity') granularity?: string) {
|
||||
const g = granularity === 'week' || granularity === 'year' ? granularity : 'month';
|
||||
return this.inventoryService.throughput(g);
|
||||
}
|
||||
|
||||
@Post('auto-unload-arrived')
|
||||
@BookingStaff(FREIGHT_PERMS.warehouseInventory.unload)
|
||||
@ApiOperation({ summary: 'Bulk auto-unload all arrived bookings into the warehouse' })
|
||||
|
||||
@@ -436,6 +436,53 @@ export class WarehouseInventoryService {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* generate_series so empty periods still return a zero row — replaces the
|
||||
* client-side approach that downloaded the whole inventory to bucket it.
|
||||
*/
|
||||
async throughput(
|
||||
granularity: 'week' | 'month' | 'year' = 'month',
|
||||
): Promise<Array<{ periodStart: string; received: number; dispatched: number }>> {
|
||||
// Whitelist the unit — it is interpolated into date_trunc / interval literals.
|
||||
const unit: 'week' | 'month' | 'year' = ['week', 'month', 'year'].includes(granularity)
|
||||
? granularity
|
||||
: 'month';
|
||||
const back = unit === 'week' ? 7 : unit === 'month' ? 11 : 4;
|
||||
|
||||
const rows: Array<{ periodStart: string; received: number; dispatched: number }> =
|
||||
await this.dataSource.query(
|
||||
`WITH periods AS (
|
||||
SELECT gs AS period_start
|
||||
FROM generate_series(
|
||||
date_trunc('${unit}', now()) - ($1 || ' ${unit}')::interval,
|
||||
date_trunc('${unit}', now()),
|
||||
'1 ${unit}'::interval
|
||||
) gs
|
||||
)
|
||||
SELECT p.period_start AS "periodStart",
|
||||
COALESCE(r.cnt, 0)::int AS received,
|
||||
COALESCE(d.cnt, 0)::int AS dispatched
|
||||
FROM periods p
|
||||
LEFT JOIN (
|
||||
SELECT date_trunc('${unit}', arrived_at) AS ps, count(*) AS cnt
|
||||
FROM freight.warehouse_inventory
|
||||
WHERE deleted_at IS NULL AND arrived_at IS NOT NULL
|
||||
GROUP BY 1
|
||||
) r ON r.ps = p.period_start
|
||||
LEFT JOIN (
|
||||
SELECT date_trunc('${unit}', dispatched_at) AS ps, count(*) AS cnt
|
||||
FROM freight.warehouse_inventory
|
||||
WHERE deleted_at IS NULL AND dispatched_at IS NOT NULL
|
||||
GROUP BY 1
|
||||
) d ON d.ps = p.period_start
|
||||
ORDER BY p.period_start`,
|
||||
[back],
|
||||
);
|
||||
return rows;
|
||||
}
|
||||
|
||||
/**
|
||||
* Live occupancy per zone: rated capacity vs the weight/items currently held
|
||||
* (excludes items that have left — DELIVERED/DISPATCHED). Powers the yard
|
||||
|
||||
Reference in New Issue
Block a user