import { WarehouseInventoryService } from './warehouse-inventory.service'; import type { UnloadBookingDto } from './dto/unload-booking.dto'; /** * A GRN is the receipt for cargo entering the warehouse, so unloadBooking must * issue one for every direction — import as well as export. It used to mint only * for export, leaving import cargo received with no GRN. */ function makeService(opts: { tradeDirection: string | null; existing?: { id: string; grnNumber: string | null }; }) { const created: Record[] = []; const updated: Array<{ id: string; patch: Record }> = []; const inventoryRepository = { findAll: jest.fn().mockResolvedValue(opts.existing ? [opts.existing] : []), update: jest.fn((id: string, patch: Record) => { updated.push({ id, patch }); return Promise.resolve(); }), create: jest.fn((row: Record) => { created.push(row); return Promise.resolve({ id: 'new-inv', ...row }); }), }; const service = Object.create(WarehouseInventoryService.prototype) as Record; service.inventoryRepository = inventoryRepository; service.dataSource = { query: jest.fn().mockResolvedValue([{ tradeDirection: opts.tradeDirection }]), }; // Location comes straight from the dto in these cases, so pickDefaultLocation // is never reached; findById just echoes what was written. service.findById = jest.fn((id: string) => Promise.resolve(updated.find((u) => u.id === id)?.patch ?? created[0] ?? { id }), ); const dto: UnloadBookingDto = { warehouseId: 'w1', yardId: 'y1', zoneId: 'z1', } as UnloadBookingDto; return { service: service as unknown as WarehouseInventoryService, dto, created, updated }; } describe('unloadBooking — GRN issuance', () => { it('issues an IMPORT GRN when unloading a fresh import booking', async () => { const { service, dto, created } = makeService({ tradeDirection: 'IMPORT' }); await service.unloadBooking('b-import', dto); expect(created[0].grnNumber).toMatch(/^GRN-IMPORT-/); }); it('still issues an EXPORT GRN', async () => { const { service, dto, created } = makeService({ tradeDirection: 'EXPORT' }); await service.unloadBooking('b-export', dto); expect(created[0].grnNumber).toMatch(/^GRN-EXPORT-/); }); it('mints a GRN for an existing import row that has none', async () => { const { service, dto, updated } = makeService({ tradeDirection: 'IMPORT', existing: { id: 'inv-1', grnNumber: null }, }); await service.unloadBooking('b-import', dto); expect(updated[0].patch.grnNumber).toMatch(/^GRN-IMPORT-/); }); it('does not reissue when the row already has a GRN', async () => { const { service, dto, updated } = makeService({ tradeDirection: 'IMPORT', existing: { id: 'inv-1', grnNumber: 'GRN-IMPORT-EXISTING' }, }); await service.unloadBooking('b-import', dto); expect(updated[0].patch).not.toHaveProperty('grnNumber'); }); });