mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-26 18:42:49 +00:00
Merge pull request #1362 from Tria-plc/eims-bulk-register
feat(warehouses): add empty-containers/import-trains/export-trains to…
This commit is contained in:
@@ -3,6 +3,7 @@ import { DataSource, FindManyOptions, IsNull, ObjectLiteral, Repository } from '
|
||||
|
||||
import { Warehouse } from './entities/warehouse.entity';
|
||||
import { WarehouseInventory } from './entities/warehouse-inventory.entity';
|
||||
import { SchedulingReadFacade } from './scheduling-read.facade';
|
||||
|
||||
export interface WarehouseDashboard {
|
||||
totalWarehouses: number;
|
||||
@@ -20,11 +21,45 @@ export interface WarehouseDashboard {
|
||||
// Import branch
|
||||
readyForPickup: number;
|
||||
delivered: number;
|
||||
// Fleet / train snapshot
|
||||
emptyContainers: number;
|
||||
importTrains: number;
|
||||
exportTrains: number;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class WarehouseDashboardService {
|
||||
constructor(private readonly dataSource: DataSource) {}
|
||||
constructor(
|
||||
private readonly dataSource: DataSource,
|
||||
private readonly schedulingRead: SchedulingReadFacade,
|
||||
) {}
|
||||
|
||||
private async safeQueryCount(sql: string, params: unknown[] = []): Promise<number> {
|
||||
try {
|
||||
const rows = await this.dataSource.query(sql, params);
|
||||
return Number(rows?.[0]?.count) || 0;
|
||||
} catch {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/** ARRIVED trains with IMPORT-direction routing — same set as the import arrival queue. */
|
||||
private async safeImportTrains(): Promise<number> {
|
||||
try {
|
||||
return (await this.schedulingRead.importArriveQueue()).length;
|
||||
} catch {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/** Trains at/near Djibouti relevant to the export flow — same set as the Djibouti unloading queue. */
|
||||
private async safeExportTrains(): Promise<number> {
|
||||
try {
|
||||
return (await this.schedulingRead.exportDjiboutiArrivalQueue()).length;
|
||||
} catch {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private async safeCount<T extends ObjectLiteral>(
|
||||
repo: Repository<T>,
|
||||
@@ -69,6 +104,9 @@ export class WarehouseDashboardService {
|
||||
readyForPickup,
|
||||
delivered,
|
||||
receivedToday,
|
||||
emptyContainers,
|
||||
importTrains,
|
||||
exportTrains,
|
||||
] = await Promise.all([
|
||||
this.safeCount(warehouseRepo),
|
||||
this.safeCount(inventoryRepo),
|
||||
@@ -82,6 +120,16 @@ export class WarehouseDashboardService {
|
||||
this.safeCount(inventoryRepo, { where: { status: 'READY_FOR_PICKUP' } }),
|
||||
this.safeCount(inventoryRepo, { where: { status: 'DELIVERED' } }),
|
||||
this.safeReceivedToday(startOfToday),
|
||||
// ponytail: the container fleet has no literal EMPTY status (AVAILABLE | LOADED |
|
||||
// IN_TRANSIT | MAINTENANCE | DAMAGED) — AVAILABLE (not on a wagon, not in transit,
|
||||
// not flagged) is the closest proxy for "empty and free to use". Revisit if the
|
||||
// domain ever grows a real empty/full distinction per container.
|
||||
this.safeQueryCount(
|
||||
`SELECT count(*)::int AS count FROM freight.containers WHERE deleted_at IS NULL AND status = $1`,
|
||||
['AVAILABLE'],
|
||||
),
|
||||
this.safeImportTrains(),
|
||||
this.safeExportTrains(),
|
||||
]);
|
||||
|
||||
return {
|
||||
@@ -97,6 +145,9 @@ export class WarehouseDashboardService {
|
||||
dispatched,
|
||||
readyForPickup,
|
||||
delivered,
|
||||
emptyContainers,
|
||||
importTrains,
|
||||
exportTrains,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,17 @@
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { Badge, Card, Center, Divider, Group, Loader, SimpleGrid, Stack, Text, ThemeIcon } from '@mantine/core';
|
||||
import {
|
||||
ClipboardCheck,
|
||||
ClipboardList,
|
||||
ShieldCheck,
|
||||
PackageCheck,
|
||||
PackageOpen,
|
||||
PackagePlus,
|
||||
PackageSearch,
|
||||
CircleCheck,
|
||||
Send,
|
||||
Train,
|
||||
Truck,
|
||||
Warehouse as WarehouseIcon,
|
||||
Boxes,
|
||||
Layers,
|
||||
} from 'lucide-react';
|
||||
|
||||
import { PageContainer, PageHeader } from '@/components/page';
|
||||
@@ -53,13 +52,13 @@ const METRICS: Metric[] = [
|
||||
{ key: 'totalInventory', label: 'Total Inventory', icon: <Boxes size={22} />, to: '/dashboard/warehouse-inventory', theme: GREEN },
|
||||
{ key: 'receivedToday', label: 'Received Today', icon: <PackagePlus size={22} />, to: '/dashboard/warehouse-inventory?status=RECEIVED', theme: ORANGE },
|
||||
{ key: 'awaitingInspection', label: 'Awaiting Inspection', icon: <ClipboardList size={22} />, to: '/dashboard/warehouse-inventory?status=RECEIVED', theme: GREEN },
|
||||
{ key: 'inspected', label: 'Inspected', icon: <ShieldCheck size={22} />, to: '/dashboard/warehouse-inventory', theme: ORANGE },
|
||||
{ key: 'stored', label: 'Stored', icon: <Layers size={22} />, to: '/dashboard/warehouse-inventory?status=STORED', theme: GREEN },
|
||||
{ key: 'reserved', label: 'Reserved', icon: <ClipboardCheck size={22} />, to: '/dashboard/warehouse-inventory?status=RESERVED', theme: ORANGE },
|
||||
{ key: 'readyForLoading', label: 'Ready For Loading', icon: <PackageCheck size={22} />, to: '/dashboard/loading-queue', theme: GREEN },
|
||||
{ key: 'loaded', label: 'Loaded', icon: <Truck size={22} />, to: '/dashboard/loaded-inventory', theme: ORANGE },
|
||||
{ key: 'dispatched', label: 'Dispatched', icon: <Send size={22} />, to: '/dashboard/dispatch-queue', theme: GREEN },
|
||||
{ key: 'readyForPickup', label: 'Ready For Pickup', icon: <PackageSearch size={22} />, to: '/dashboard/warehouse-inventory?status=READY_FOR_PICKUP', theme: ORANGE },
|
||||
{ key: 'emptyContainers', label: 'Empty Containers', icon: <PackageOpen size={22} />, to: '/dashboard/containers', theme: ORANGE },
|
||||
{ key: 'importTrains', label: 'Import Trains', icon: <Train size={22} />, to: '/dashboard/import-warehouse', theme: GREEN },
|
||||
{ key: 'exportTrains', label: 'Export Trains', icon: <Train size={22} />, to: '/dashboard/export-warehouse', theme: ORANGE },
|
||||
{ key: 'loaded', label: 'Loaded', icon: <Truck size={22} />, to: '/dashboard/loaded-inventory', theme: GREEN },
|
||||
{ key: 'dispatched', label: 'Dispatched', icon: <Send size={22} />, to: '/dashboard/dispatch-queue', theme: ORANGE },
|
||||
{ key: 'readyForPickup', label: 'Ready For Pickup', icon: <PackageSearch size={22} />, to: '/dashboard/warehouse-inventory?status=READY_FOR_PICKUP', theme: GREEN },
|
||||
{ key: 'readyForLoading', label: 'Ready For Loading', icon: <PackageCheck size={22} />, to: '/dashboard/loading-queue', theme: ORANGE },
|
||||
{ key: 'delivered', label: 'Delivered', icon: <CircleCheck size={22} />, to: '/dashboard/warehouse-inventory?status=DELIVERED', theme: GREEN },
|
||||
];
|
||||
|
||||
|
||||
@@ -299,6 +299,9 @@ export interface WarehouseDashboard {
|
||||
dispatched: number;
|
||||
readyForPickup: number;
|
||||
delivered: number;
|
||||
emptyContainers: number;
|
||||
importTrains: number;
|
||||
exportTrains: number;
|
||||
}
|
||||
|
||||
// ── Loading (Batch 3) ────────────────────────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user