mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-02 16:13:40 +00:00
Loading queque changes
This commit is contained in:
@@ -43,6 +43,7 @@ import { DemoBookingsSeeder } from "./seed/demo-bookings.seeder";
|
|||||||
import { PricingDataSeeder } from "./seed/pricing-data.seeder";
|
import { PricingDataSeeder } from "./seed/pricing-data.seeder";
|
||||||
import { FileUploadSettingsSeeder } from "./seed/file-upload-settings.seeder";
|
import { FileUploadSettingsSeeder } from "./seed/file-upload-settings.seeder";
|
||||||
import { IndodeFacilitySeeder } from "./seed/indode-facility.seeder";
|
import { IndodeFacilitySeeder } from "./seed/indode-facility.seeder";
|
||||||
|
import { Batch14TestDataSeeder } from "./seed/batch1-4-test-data.seeder";
|
||||||
//New Trains, Wagons, Container and Cargo management modules
|
//New Trains, Wagons, Container and Cargo management modules
|
||||||
import { TrainsModule } from "./modules/trains/trains.module";
|
import { TrainsModule } from "./modules/trains/trains.module";
|
||||||
import { WagonsModule } from './modules/wagons/wagons.module';
|
import { WagonsModule } from './modules/wagons/wagons.module';
|
||||||
@@ -106,7 +107,7 @@ import { FacilitiesModule } from './modules/facilities/facilities.module';
|
|||||||
FacilitiesModule,
|
FacilitiesModule,
|
||||||
WarehousesModule,
|
WarehousesModule,
|
||||||
],
|
],
|
||||||
providers: [EdrOrgSeeder, DemoUsersSeeder,FreightStaffUsersSeeder, DemoBookingsSeeder, PricingDataSeeder, FileUploadSettingsSeeder, IndodeFacilitySeeder],
|
providers: [EdrOrgSeeder, DemoUsersSeeder,FreightStaffUsersSeeder, DemoBookingsSeeder, PricingDataSeeder, FileUploadSettingsSeeder, IndodeFacilitySeeder, Batch14TestDataSeeder],
|
||||||
})
|
})
|
||||||
export class AppModule implements OnApplicationBootstrap {
|
export class AppModule implements OnApplicationBootstrap {
|
||||||
constructor(
|
constructor(
|
||||||
@@ -118,6 +119,7 @@ export class AppModule implements OnApplicationBootstrap {
|
|||||||
private readonly pricingDataSeeder: PricingDataSeeder,
|
private readonly pricingDataSeeder: PricingDataSeeder,
|
||||||
private readonly fileUploadSettingsSeeder: FileUploadSettingsSeeder,
|
private readonly fileUploadSettingsSeeder: FileUploadSettingsSeeder,
|
||||||
private readonly indodeFacilitySeeder: IndodeFacilitySeeder,
|
private readonly indodeFacilitySeeder: IndodeFacilitySeeder,
|
||||||
|
private readonly batch14TestDataSeeder: Batch14TestDataSeeder,
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
async onApplicationBootstrap() {
|
async onApplicationBootstrap() {
|
||||||
@@ -128,10 +130,7 @@ export class AppModule implements OnApplicationBootstrap {
|
|||||||
await this.demoBookingsSeeder.run();
|
await this.demoBookingsSeeder.run();
|
||||||
await this.pricingDataSeeder.run();
|
await this.pricingDataSeeder.run();
|
||||||
await this.fileUploadSettingsSeeder.run();
|
await this.fileUploadSettingsSeeder.run();
|
||||||
try {
|
await this.indodeFacilitySeeder.run();
|
||||||
await this.indodeFacilitySeeder.run();
|
await this.batch14TestDataSeeder.run();
|
||||||
} catch (error) {
|
|
||||||
console.error('IndodeFacilitySeeder failed:', error);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
172
apps/edr-freight-api/src/seed/batch1-4-test-data.seeder.ts
Normal file
172
apps/edr-freight-api/src/seed/batch1-4-test-data.seeder.ts
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
import { Injectable, Logger } from '@nestjs/common';
|
||||||
|
import { DataSource, Repository } from 'typeorm';
|
||||||
|
|
||||||
|
import { Booking } from '../modules/bookings/entities/booking.entity';
|
||||||
|
import { Facility } from '../modules/facilities/entities/facility.entity';
|
||||||
|
import { Warehouse } from '../modules/warehouses/entities/warehouse.entity';
|
||||||
|
import { WarehouseInventory } from '../modules/warehouses/entities/warehouse-inventory.entity';
|
||||||
|
import { WarehouseYard } from '../modules/warehouses/entities/warehouse-yard.entity';
|
||||||
|
import { WarehouseZone } from '../modules/warehouses/entities/warehouse-zone.entity';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test data seeder for Batch 1-4 warehouse system.
|
||||||
|
* Creates Indode facility with warehouses, yards, zones, and sample inventory
|
||||||
|
* in all status states (RECEIVED, STORED, RESERVED, READY_FOR_LOADING, LOADED, DISPATCHED).
|
||||||
|
*/
|
||||||
|
@Injectable()
|
||||||
|
export class Batch14TestDataSeeder {
|
||||||
|
private readonly logger = new Logger(Batch14TestDataSeeder.name);
|
||||||
|
|
||||||
|
constructor(private readonly dataSource: DataSource) {}
|
||||||
|
|
||||||
|
async run(): Promise<void> {
|
||||||
|
try {
|
||||||
|
const facilityRepo = this.dataSource.getRepository(Facility);
|
||||||
|
const warehouseRepo = this.dataSource.getRepository(Warehouse);
|
||||||
|
const yardRepo = this.dataSource.getRepository(WarehouseYard);
|
||||||
|
const zoneRepo = this.dataSource.getRepository(WarehouseZone);
|
||||||
|
const inventoryRepo = this.dataSource.getRepository(WarehouseInventory);
|
||||||
|
|
||||||
|
// Check if facility already exists
|
||||||
|
const existingFacility = await facilityRepo.findOne({
|
||||||
|
where: { code: 'INDODE_TEST' },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (existingFacility) {
|
||||||
|
this.logger.log('Batch 1-4 test data already seeded, skipping');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create Facility
|
||||||
|
const facility = await facilityRepo.save(
|
||||||
|
facilityRepo.create({
|
||||||
|
code: 'INDODE_TEST',
|
||||||
|
name: 'Indode Test Facility',
|
||||||
|
facilityType: 'DRY_PORT',
|
||||||
|
facilityStatus: 'ACTIVE',
|
||||||
|
locationName: 'Indode',
|
||||||
|
country: 'Djibouti',
|
||||||
|
city: 'Djibouti',
|
||||||
|
isActive: true,
|
||||||
|
capacity: 100000,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
this.logger.log(`Created facility: ${facility.code}`);
|
||||||
|
|
||||||
|
// Create Warehouse
|
||||||
|
const warehouse = await warehouseRepo.save(
|
||||||
|
warehouseRepo.create({
|
||||||
|
code: 'TEST_WH_001',
|
||||||
|
name: 'Test Warehouse 1',
|
||||||
|
type: 'OPEN_WAREHOUSE',
|
||||||
|
locationName: 'Test Location',
|
||||||
|
status: 'ACTIVE',
|
||||||
|
isActive: true,
|
||||||
|
facilityId: facility.id,
|
||||||
|
capacityWeight: 50000,
|
||||||
|
capacityContainers: 500,
|
||||||
|
maxWeight: 50000,
|
||||||
|
maxVolume: 10000,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
this.logger.log(`Created warehouse: ${warehouse.code}`);
|
||||||
|
|
||||||
|
// Create Yards
|
||||||
|
const yard1 = await yardRepo.save(
|
||||||
|
yardRepo.create({
|
||||||
|
warehouseId: warehouse.id,
|
||||||
|
code: 'YARD_001',
|
||||||
|
name: 'Container Yard 1',
|
||||||
|
type: 'CONTAINER_YARD',
|
||||||
|
status: 'ACTIVE',
|
||||||
|
isActive: true,
|
||||||
|
capacityWeight: 25000,
|
||||||
|
capacityContainers: 250,
|
||||||
|
maxWeight: 25000,
|
||||||
|
maxVolume: 5000,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const yard2 = await yardRepo.save(
|
||||||
|
yardRepo.create({
|
||||||
|
warehouseId: warehouse.id,
|
||||||
|
code: 'YARD_002',
|
||||||
|
name: 'Bulk Yard 1',
|
||||||
|
type: 'BULK_YARD',
|
||||||
|
status: 'ACTIVE',
|
||||||
|
isActive: true,
|
||||||
|
capacityWeight: 25000,
|
||||||
|
capacityContainers: 100,
|
||||||
|
maxWeight: 25000,
|
||||||
|
maxVolume: 5000,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
this.logger.log(`Created yards: ${yard1.code}, ${yard2.code}`);
|
||||||
|
|
||||||
|
// Create Zones
|
||||||
|
const zone1 = await zoneRepo.save(
|
||||||
|
zoneRepo.create({
|
||||||
|
yardId: yard1.id,
|
||||||
|
code: 'ZONE_001',
|
||||||
|
name: 'Container Zone A',
|
||||||
|
type: 'CONTAINER_ZONE',
|
||||||
|
status: 'ACTIVE',
|
||||||
|
isActive: true,
|
||||||
|
capacityWeight: 12500,
|
||||||
|
capacityContainers: 125,
|
||||||
|
maxWeight: 12500,
|
||||||
|
maxVolume: 2500,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const zone2 = await zoneRepo.save(
|
||||||
|
zoneRepo.create({
|
||||||
|
yardId: yard2.id,
|
||||||
|
code: 'ZONE_002',
|
||||||
|
name: 'Bulk Zone A',
|
||||||
|
type: 'BULK_ZONE',
|
||||||
|
status: 'ACTIVE',
|
||||||
|
isActive: true,
|
||||||
|
capacityWeight: 12500,
|
||||||
|
capacityContainers: 50,
|
||||||
|
maxWeight: 12500,
|
||||||
|
maxVolume: 2500,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
this.logger.log(`Created zones: ${zone1.code}, ${zone2.code}`);
|
||||||
|
|
||||||
|
// Create inventory in all statuses for testing
|
||||||
|
const statuses = ['RECEIVED', 'STORED', 'RESERVED', 'READY_FOR_LOADING', 'LOADED', 'DISPATCHED'] as const;
|
||||||
|
const now = new Date();
|
||||||
|
|
||||||
|
for (let i = 0; i < statuses.length; i++) {
|
||||||
|
const status = statuses[i];
|
||||||
|
const zone = i < 3 ? zone1 : zone2;
|
||||||
|
|
||||||
|
await inventoryRepo.save(
|
||||||
|
inventoryRepo.create({
|
||||||
|
warehouseId: warehouse.id,
|
||||||
|
yardId: zone.yardId,
|
||||||
|
zoneId: zone.id,
|
||||||
|
code: `TEST_INV_${status}_001`,
|
||||||
|
status: status as any,
|
||||||
|
quantity: 100 + i * 10,
|
||||||
|
weight: 500 + i * 50,
|
||||||
|
volume: 100 + i * 10,
|
||||||
|
arrivedAt: new Date(now.getTime() - i * 3600000), // Staggered arrival times
|
||||||
|
storedAt: status !== 'RECEIVED' ? new Date(now.getTime() - (i - 1) * 3600000) : null,
|
||||||
|
reservedAt: ['RESERVED', 'READY_FOR_LOADING', 'LOADED', 'DISPATCHED'].includes(status) ? new Date() : null,
|
||||||
|
readyForLoadingAt: ['READY_FOR_LOADING', 'LOADED', 'DISPATCHED'].includes(status) ? new Date() : null,
|
||||||
|
loadedAt: ['LOADED', 'DISPATCHED'].includes(status) ? new Date() : null,
|
||||||
|
dispatchedAt: status === 'DISPATCHED' ? new Date() : null,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
this.logger.log('Created 6 test inventory items in all statuses');
|
||||||
|
|
||||||
|
this.logger.log('✅ Batch 1-4 test data seeded successfully');
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error(`Batch 1-4 seeder failed: ${error instanceof Error ? error.message : String(error)}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -65,52 +65,64 @@ export class IndodeFacilitySeeder {
|
|||||||
constructor(private readonly dataSource: DataSource) {}
|
constructor(private readonly dataSource: DataSource) {}
|
||||||
|
|
||||||
async run(): Promise<void> {
|
async run(): Promise<void> {
|
||||||
await this.dataSource.transaction(async (manager) => {
|
try {
|
||||||
const facilityRepo = manager.getRepository(Facility);
|
await this.dataSource.transaction(async (manager) => {
|
||||||
const warehouseRepo = manager.getRepository(Warehouse);
|
const facilityRepo = manager.getRepository(Facility);
|
||||||
const yardRepo = manager.getRepository(WarehouseYard);
|
const warehouseRepo = manager.getRepository(Warehouse);
|
||||||
const zoneRepo = manager.getRepository(WarehouseZone);
|
const yardRepo = manager.getRepository(WarehouseYard);
|
||||||
|
const zoneRepo = manager.getRepository(WarehouseZone);
|
||||||
|
|
||||||
// Ensure facility exists
|
// Ensure facility exists
|
||||||
const facility = await facilityRepo.findOne({
|
const facility = await facilityRepo.findOne({
|
||||||
where: { code: INDODE_FACILITY.code },
|
where: { code: INDODE_FACILITY.code },
|
||||||
});
|
|
||||||
|
|
||||||
if (facility) {
|
|
||||||
this.logger.log('Indode facility already exists, skipping seed');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const newFacility = facilityRepo.create(INDODE_FACILITY);
|
|
||||||
const savedFacility = await facilityRepo.save(newFacility);
|
|
||||||
this.logger.log(`Created facility: ${savedFacility.code}`);
|
|
||||||
|
|
||||||
// Create warehouses for the facility
|
|
||||||
for (const warehouseData of WAREHOUSES) {
|
|
||||||
const warehouse = await warehouseRepo.findOne({
|
|
||||||
where: { code: warehouseData.code },
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (warehouse) {
|
if (facility) {
|
||||||
this.logger.log(`Warehouse ${warehouseData.code} already exists, skipping`);
|
this.logger.log('Indode facility already exists, skipping seed');
|
||||||
continue;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const newWarehouse = warehouseRepo.create({
|
const newFacility = facilityRepo.create(INDODE_FACILITY);
|
||||||
...warehouseData,
|
const savedFacility = await facilityRepo.save(newFacility);
|
||||||
facilityId: savedFacility.id,
|
this.logger.log(`Created facility: ${savedFacility.code}`);
|
||||||
});
|
|
||||||
const savedWarehouse = await warehouseRepo.save(newWarehouse);
|
|
||||||
this.logger.log(`Created warehouse: ${savedWarehouse.code} under facility ${savedFacility.code}`);
|
|
||||||
|
|
||||||
// Create 11 yards per warehouse
|
// Create warehouses for the facility
|
||||||
await this.createYardsForWarehouse(yardRepo, zoneRepo, savedWarehouse);
|
for (const warehouseData of WAREHOUSES) {
|
||||||
}
|
try {
|
||||||
|
const warehouse = await warehouseRepo.findOne({
|
||||||
|
where: { code: warehouseData.code },
|
||||||
|
});
|
||||||
|
|
||||||
this.logger.log(
|
if (warehouse) {
|
||||||
'Indode Multipurpose Dry Port facility seeded successfully with 2 warehouses and 11 yards each',
|
this.logger.log(`Warehouse ${warehouseData.code} already exists, skipping`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newWarehouse = warehouseRepo.create({
|
||||||
|
...warehouseData,
|
||||||
|
facilityId: savedFacility.id,
|
||||||
|
});
|
||||||
|
const savedWarehouse = await warehouseRepo.save(newWarehouse);
|
||||||
|
this.logger.log(`Created warehouse: ${savedWarehouse.code} under facility ${savedFacility.code}`);
|
||||||
|
|
||||||
|
// Create 11 yards per warehouse
|
||||||
|
await this.createYardsForWarehouse(yardRepo, zoneRepo, savedWarehouse);
|
||||||
|
} catch (warehouseError) {
|
||||||
|
this.logger.warn(
|
||||||
|
`Failed to create warehouse ${warehouseData.code}: ${warehouseError instanceof Error ? warehouseError.message : String(warehouseError)}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.logger.log(
|
||||||
|
'Indode Multipurpose Dry Port facility seeded successfully with 2 warehouses and 11 yards each',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error(
|
||||||
|
`IndodeFacilitySeeder error: ${error instanceof Error ? error.message : String(error)}`,
|
||||||
);
|
);
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async createYardsForWarehouse(
|
private async createYardsForWarehouse(
|
||||||
|
|||||||
@@ -1,13 +1,18 @@
|
|||||||
import { Card, Container, Stack } from '@mantine/core';
|
import { Badge, Card, Container, Stack, Tabs } from '@mantine/core';
|
||||||
|
|
||||||
import Breadcrumbs from '@/components/ui/Breadcrumbs';
|
import Breadcrumbs from '@/components/ui/Breadcrumbs';
|
||||||
import { InventoryWorkbench, VisualEmptyState, WarehouseHero } from '@/components/warehouses';
|
import { InventoryWorkbench, VisualEmptyState, WarehouseHero } from '@/components/warehouses';
|
||||||
import { useWarehouseInventory } from '@/hooks/useWarehouses';
|
import { useWarehouseInventory } from '@/hooks/useWarehouses';
|
||||||
|
|
||||||
/** Items that are READY_FOR_LOADING — load them onto a wagon from here. */
|
/**
|
||||||
|
* Loading Queue - Manage bookings ready for loading.
|
||||||
|
* Tabs:
|
||||||
|
* - Awaiting Payment: UNPAID bookings
|
||||||
|
* - Ready For Loading: PAID bookings ready to load onto wagon
|
||||||
|
*/
|
||||||
export default function LoadingQueuePage() {
|
export default function LoadingQueuePage() {
|
||||||
const { data, isLoading } = useWarehouseInventory({ status: 'READY_FOR_LOADING' });
|
const { data: paidItems, isLoading: paidLoading } = useWarehouseInventory({ status: 'READY_FOR_LOADING' });
|
||||||
const items = data ?? [];
|
const paidBookings = paidItems ?? [];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container size="xxl" py="lg">
|
<Container size="xxl" py="lg">
|
||||||
@@ -18,19 +23,40 @@ export default function LoadingQueuePage() {
|
|||||||
variant="wagon"
|
variant="wagon"
|
||||||
secondaryVariant="cargo"
|
secondaryVariant="cargo"
|
||||||
title="Loading Queue"
|
title="Loading Queue"
|
||||||
subtitle="Inventory that is ready for loading onto a wagon."
|
subtitle="Manage bookings and inventory through the loading workflow."
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Card withBorder radius="md" padding="lg">
|
<Card withBorder radius="md" padding="lg">
|
||||||
{!isLoading && items.length === 0 ? (
|
<Tabs defaultValue="ready">
|
||||||
<VisualEmptyState
|
<Tabs.List>
|
||||||
variant="wagon"
|
<Tabs.Tab value="awaiting" leftSection={<Badge size="xs">Unpaid</Badge>}>
|
||||||
title="Nothing waiting to load"
|
Awaiting Payment
|
||||||
description="Items appear here once they are marked Ready For Loading."
|
</Tabs.Tab>
|
||||||
/>
|
<Tabs.Tab value="ready" leftSection={<Badge size="xs" color="green">Ready</Badge>}>
|
||||||
) : (
|
Ready For Loading
|
||||||
<InventoryWorkbench items={items} isLoading={isLoading} />
|
</Tabs.Tab>
|
||||||
)}
|
</Tabs.List>
|
||||||
|
|
||||||
|
<Tabs.Panel value="awaiting" pt="md">
|
||||||
|
<VisualEmptyState
|
||||||
|
variant="wagon"
|
||||||
|
title="No unpaid bookings"
|
||||||
|
description="Unpaid bookings will appear here pending payment verification."
|
||||||
|
/>
|
||||||
|
</Tabs.Panel>
|
||||||
|
|
||||||
|
<Tabs.Panel value="ready" pt="md">
|
||||||
|
{!paidLoading && paidBookings.length === 0 ? (
|
||||||
|
<VisualEmptyState
|
||||||
|
variant="wagon"
|
||||||
|
title="Nothing waiting to load"
|
||||||
|
description="Items appear here once they are marked Ready For Loading."
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<InventoryWorkbench items={paidBookings} isLoading={paidLoading} />
|
||||||
|
)}
|
||||||
|
</Tabs.Panel>
|
||||||
|
</Tabs>
|
||||||
</Card>
|
</Card>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Container>
|
</Container>
|
||||||
|
|||||||
Reference in New Issue
Block a user