import { BadRequestException } from '@nestjs/common'; import type { DataSource, EntityManager } from 'typeorm'; /** The booking fields the gate needs. */ export interface ExportLoadGateBooking { id: string; tradeDirection?: string | null; } /** * Export cargo may not be loaded onto its train until it has physically reached * the warehouse and been issued a GRN — whether it got there by first-mile or by * the customer's own truck, and even though a wagon is already allocated. An * allocation is a plan; the GRN is the proof the goods are actually in hand. * * Several loading paths (per-yard load, workspace confirm-loaded) marked cargo * loaded straight off the allocation, skipping the warehouse, so a booking could * ride the train with nothing ever received. This closes that for export; import * loads off a train and is unaffected. * * "Received with a GRN" = an inventory row that has reached the warehouse * (RECEIVED or any later stage) and carries a GRN, in the column or the notes * fallback older rows use. */ export async function assertExportReceivedWithGrn( db: DataSource | EntityManager, booking: ExportLoadGateBooking, ): Promise { if (booking.tradeDirection !== 'EXPORT') return; const [row] = await db.query( `SELECT 1 FROM freight.warehouse_inventory inv WHERE inv.booking_id = $1 AND inv.deleted_at IS NULL AND inv.status IN ('RECEIVED', 'STORED', 'RESERVED', 'READY_FOR_LOADING', 'LOADED', 'DISPATCHED') AND COALESCE( NULLIF(TRIM(inv.grn_number), ''), substring(inv.notes FROM 'GRN Number: ([^\\n\\r]+)') ) IS NOT NULL LIMIT 1`, [booking.id], ); if (!row) { throw new BadRequestException( 'This export booking has not been received at the warehouse yet — receive its cargo and generate a GRN before loading it onto the train.', ); } }