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:
Hagernesh Tadesse
2026-08-20 13:52:27 +03:00
committed by GitHub
3 changed files with 64 additions and 11 deletions

View File

@@ -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,
};
}
}

View File

@@ -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 },
];

View File

@@ -299,6 +299,9 @@ export interface WarehouseDashboard {
dispatched: number;
readyForPickup: number;
delivered: number;
emptyContainers: number;
importTrains: number;
exportTrains: number;
}
// ── Loading (Batch 3) ────────────────────────────────────────────────────────