diff --git a/apps/edr-freight-api/src/app.module.ts b/apps/edr-freight-api/src/app.module.ts index ec1664f15..b6687f276 100644 --- a/apps/edr-freight-api/src/app.module.ts +++ b/apps/edr-freight-api/src/app.module.ts @@ -49,6 +49,7 @@ import { PricingDataSeeder } from "./seed/pricing-data.seeder"; import { FileUploadSettingsSeeder } from "./seed/file-upload-settings.seeder"; import { IndodeFacilitySeeder } from "./seed/indode-facility.seeder"; import { Batch14TestDataSeeder } from "./seed/batch1-4-test-data.seeder"; +import { Batch5TestDataSeeder } from "./seed/batch5-test-data.seeder"; import { FreightPermissionKeyMigrationSeeder } from "./seed/freight-permission-key-migration.seeder"; import { DemoFreightDataSeeder } from "./seed/demo-freight-data.seeder"; //New Trains, Wagons, Container and Cargo management modules @@ -131,6 +132,7 @@ import { OverviewModule } from './modules/overview/overview.module'; DemoFreightDataSeeder, IndodeFacilitySeeder, Batch14TestDataSeeder, + Batch5TestDataSeeder, ], }) export class AppModule implements OnApplicationBootstrap { @@ -144,6 +146,7 @@ export class AppModule implements OnApplicationBootstrap { private readonly fileUploadSettingsSeeder: FileUploadSettingsSeeder, private readonly indodeFacilitySeeder: IndodeFacilitySeeder, private readonly batch14TestDataSeeder: Batch14TestDataSeeder, + private readonly batch5TestDataSeeder: Batch5TestDataSeeder, private readonly freightPermissionKeyMigrationSeeder: FreightPermissionKeyMigrationSeeder, private readonly demoFreightDataSeeder: DemoFreightDataSeeder, ) { } @@ -159,6 +162,7 @@ export class AppModule implements OnApplicationBootstrap { await this.fileUploadSettingsSeeder.run(); await this.indodeFacilitySeeder.run(); await this.batch14TestDataSeeder.run(); + await this.batch5TestDataSeeder.run(); // Idempotent demo data: ≥100 wagons/type, approval chains, 4 staff users. // Each block self-guards on an empty-table check, so this is safe every boot. await this.demoFreightDataSeeder.run(); diff --git a/apps/edr-freight-api/src/seed/batch5-test-data.seeder.ts b/apps/edr-freight-api/src/seed/batch5-test-data.seeder.ts new file mode 100644 index 000000000..f9e4e2af7 --- /dev/null +++ b/apps/edr-freight-api/src/seed/batch5-test-data.seeder.ts @@ -0,0 +1,139 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { DataSource } from 'typeorm'; + +import { Booking } from '../modules/bookings/entities/booking.entity'; +import { ServiceType } from '../modules/rule-engine/entities/service-type.entity'; +import { Yard } from '../modules/rule-engine/entities/yard.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'; + +const SEED_REFS = ['SEED-B5-EXP-001', 'SEED-B5-EXP-002', 'SEED-B5-EXP-003']; + +const SEEDS = [ + { ref: 'SEED-B5-EXP-001', weight: 5000, notes: 'Electronics export cargo' }, + { ref: 'SEED-B5-EXP-002', weight: 8500, notes: 'Textile export cargo' }, + { ref: 'SEED-B5-EXP-003', weight: 3200, notes: 'Coffee export cargo' }, +]; + +/** + * Seeds 3 EXPORT+PAID bookings with READY_FOR_LOADING + inspection PASSED inventory + * so the Batch 5 "Ready To Load" tab has visible rows to test against. + * + * Origin: any Ethiopian yard (route-based direction = EXPORT when dest = Djibouti) + * Destination: any Djiboutian yard + * Uses the INDODE_OPEN warehouse created by IndodeFacilitySeeder. + */ +@Injectable() +export class Batch5TestDataSeeder { + private readonly logger = new Logger(Batch5TestDataSeeder.name); + + constructor(private readonly dataSource: DataSource) {} + + async run(): Promise { + const bookingRepo = this.dataSource.getRepository(Booking); + + const existing = await bookingRepo.findOne({ where: { reference: SEED_REFS[0] } }); + if (existing) { + this.logger.log('Batch 5 test data already seeded, skipping'); + return; + } + + try { + const yardRepo = this.dataSource.getRepository(Yard); + const serviceTypeRepo = this.dataSource.getRepository(ServiceType); + const warehouseRepo = this.dataSource.getRepository(Warehouse); + const warehouseYardRepo = this.dataSource.getRepository(WarehouseYard); + const warehouseZoneRepo = this.dataSource.getRepository(WarehouseZone); + const inventoryRepo = this.dataSource.getRepository(WarehouseInventory); + + // Find Ethiopian origin yard and Djiboutian destination yard. + const originYard = + (await yardRepo.findOne({ where: { code: 'ADDIS_ABABA' } })) ?? + (await yardRepo.findOne({ where: { country: 'Ethiopia' } })); + const destYard = + (await yardRepo.findOne({ where: { code: 'DJIBOUTI' } })) ?? + (await yardRepo.findOne({ where: { country: 'Djibouti' } })); + + if (!originYard || !destYard) { + this.logger.warn( + `Required yards not found (origin=${originYard?.code ?? 'none'}, dest=${destYard?.code ?? 'none'}); skipping Batch 5 seed`, + ); + return; + } + + // Find any active service type (bookings require one). + const serviceType = + (await serviceTypeRepo.findOne({ where: { code: 'RAIL_CONTAINER' } })) ?? + (await serviceTypeRepo.findOne({ where: { isActive: true } })); + if (!serviceType) { + this.logger.warn('No service type found; skipping Batch 5 seed'); + return; + } + + // Find INDODE warehouse. + const warehouse = await warehouseRepo.findOne({ where: { code: 'INDODE_OPEN' } }); + if (!warehouse) { + this.logger.warn('INDODE_OPEN warehouse not found; skipping Batch 5 seed'); + return; + } + + const warehouseYard = await warehouseYardRepo.findOne({ where: { warehouseId: warehouse.id } }); + if (!warehouseYard) { + this.logger.warn('No warehouse yard found for INDODE_OPEN; skipping Batch 5 seed'); + return; + } + + const warehouseZone = await warehouseZoneRepo.findOne({ where: { yardId: warehouseYard.id } }); + if (!warehouseZone) { + this.logger.warn('No warehouse zone found; skipping Batch 5 seed'); + return; + } + + const now = new Date(); + + for (const seed of SEEDS) { + const booking = await bookingRepo.save( + bookingRepo.create({ + reference: seed.ref, + originYardId: originYard.id, + destinationYardId: destYard.id, + serviceTypeId: serviceType.id, + status: 'PAID', + paymentStatus: 'PAID', + tradeDirection: 'EXPORT', + freightType: 'BULK', + cargoTotalWeightVgm: seed.weight, + cargoFreeText: seed.notes, + }), + ); + + await inventoryRepo.save( + inventoryRepo.create({ + bookingId: booking.id, + warehouseId: warehouse.id, + yardId: warehouseYard.id, + zoneId: warehouseZone.id, + status: 'READY_FOR_LOADING', + inspectionStatus: 'PASSED', + inspectedAt: new Date(now.getTime() - 3600 * 1000), + quantity: 1, + weight: seed.weight, + arrivedAt: new Date(now.getTime() - 7200 * 1000), + readyForLoadingAt: new Date(now.getTime() - 1800 * 1000), + notes: `[SEED-B5] ${seed.notes}`, + }), + ); + + this.logger.log(`Seeded ${seed.ref} → READY_FOR_LOADING + PASSED`); + } + + this.logger.log('✅ Batch 5 Ready-To-Load test data seeded successfully'); + } catch (error) { + this.logger.error( + `Batch5TestDataSeeder failed: ${error instanceof Error ? error.message : String(error)}`, + ); + } + } +}