import { Injectable, Logger } from '@nestjs/common'; import { DataSource } from 'typeorm'; import { Booking } from '../modules/bookings/entities/booking.entity'; import { CompanyProfile } from '../modules/companies/entities/company-profile.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; } // bookings.company_id AND bookings.company_profile_id are both NOT NULL, so a // seed booking needs an owning company profile. Resolve the profile and take // its company from it, so the two columns can never disagree. Without this the // seeder aborted on its first insert. const companyProfile = await this.dataSource .getRepository(CompanyProfile) .findOne({ where: {} }); if (!companyProfile) { this.logger.warn('No company profile 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, companyId: companyProfile.companyId, companyProfileId: companyProfile.id, originYardId: originYard.id, destinationYardId: destYard.id, serviceTypeId: serviceType.id, status: 'PAID', paymentStatus: 'PAID', scheduledDate: now, contractType: 'SPOT', equipmentReturn: 'TERMINAL', paymentCurrency: 'ETB', totalAmount: 0, isGovernment: false, 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)}`, ); } } }