|
|
|
|
@@ -842,8 +842,12 @@ export class WarehouseInventoryService {
|
|
|
|
|
|
|
|
|
|
const now = new Date();
|
|
|
|
|
const grnNumber = this.generateGrnNumber(dto.direction, bookingId, now);
|
|
|
|
|
const truckEntrance = this.mergeSystemTruckEntrance(dto.truckEntrance, booking);
|
|
|
|
|
this.assertTruckEntrance(truckEntrance);
|
|
|
|
|
const truckEntrance = dto.truckEntrance
|
|
|
|
|
? this.mergeSystemTruckEntrance(dto.truckEntrance, booking)
|
|
|
|
|
: undefined;
|
|
|
|
|
if (dto.direction === 'EXPORT') {
|
|
|
|
|
this.assertTruckEntrance(truckEntrance);
|
|
|
|
|
}
|
|
|
|
|
const receiveNote = this.buildReceiveNote({
|
|
|
|
|
grnNumber,
|
|
|
|
|
direction: dto.direction,
|
|
|
|
|
@@ -856,7 +860,7 @@ export class WarehouseInventoryService {
|
|
|
|
|
yardId: dto.yardId,
|
|
|
|
|
zoneId: dto.zoneId,
|
|
|
|
|
bookingId,
|
|
|
|
|
quantity: 1,
|
|
|
|
|
quantity: Number(booking.containerQuantity) || 1,
|
|
|
|
|
weight: Number(booking.weight) || 0,
|
|
|
|
|
status: 'RECEIVED',
|
|
|
|
|
arrivedAt: now,
|
|
|
|
|
@@ -869,16 +873,18 @@ export class WarehouseInventoryService {
|
|
|
|
|
activityType: 'INVENTORY_RECEIVED',
|
|
|
|
|
inventoryId: saved.id,
|
|
|
|
|
warehouseId: dto.warehouseId,
|
|
|
|
|
description: `GRN ${grnNumber}: bulk received ${dto.direction} booking via truck ${truckEntrance.truckPlateNumber}`,
|
|
|
|
|
description: truckEntrance?.truckPlateNumber
|
|
|
|
|
? `GRN ${grnNumber}: bulk received ${dto.direction} booking via truck ${truckEntrance.truckPlateNumber}`
|
|
|
|
|
: `GRN ${grnNumber}: bulk received ${dto.direction} booking`,
|
|
|
|
|
performedBy: dto.performedBy,
|
|
|
|
|
},
|
|
|
|
|
manager,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
await this.notifyOwnerInventoryReceived({
|
|
|
|
|
phone: truckEntrance.customerPhone,
|
|
|
|
|
ownerName: truckEntrance.ownerName,
|
|
|
|
|
bookingReference: truckEntrance.edrDigitalBookingId,
|
|
|
|
|
phone: truckEntrance?.customerPhone ?? booking.customerPhone,
|
|
|
|
|
ownerName: truckEntrance?.ownerName ?? booking.customer,
|
|
|
|
|
bookingReference: truckEntrance?.edrDigitalBookingId ?? booking.reference,
|
|
|
|
|
grnNumber,
|
|
|
|
|
direction: dto.direction,
|
|
|
|
|
warehouseId: dto.warehouseId,
|
|
|
|
|
@@ -982,6 +988,11 @@ export class WarehouseInventoryService {
|
|
|
|
|
return this.exportInventoryByStatus('READY_FOR_LOADING', true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** EXPORT inventory received at the facility and awaiting inspection. */
|
|
|
|
|
async receivedExport(): Promise<ReadyToLoadRow[]> {
|
|
|
|
|
return this.exportInventoryByStatus('RECEIVED');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** EXPORT inventory that has been loaded onto a wagon and is queued for dispatch (LOADED). */
|
|
|
|
|
async loadedExport(): Promise<ReadyToLoadRow[]> {
|
|
|
|
|
return this.exportInventoryByStatus('LOADED');
|
|
|
|
|
@@ -1592,19 +1603,27 @@ export class WarehouseInventoryService {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async receive(dto: ReceiveWarehouseInventoryDto): Promise<WarehouseInventory> {
|
|
|
|
|
const weight = Number(dto.weight) || 0;
|
|
|
|
|
const volume = Number(dto.volume) || 0;
|
|
|
|
|
const containerCount = dto.containerId ? Math.round(Number(dto.quantity) || 0) : 0;
|
|
|
|
|
const bookingDirection = dto.bookingId ? await this.getBookingDirection(dto.bookingId) : null;
|
|
|
|
|
|
|
|
|
|
const id = await this.dataSource.transaction(async (manager) => {
|
|
|
|
|
const { warehouse, yard, zone } = await this.validateLocation(manager, dto);
|
|
|
|
|
|
|
|
|
|
if (dto.bookingId) {
|
|
|
|
|
await this.assertBookingExists(manager, dto.bookingId);
|
|
|
|
|
const bookingSource = dto.bookingId
|
|
|
|
|
? await this.getBookingTruckEntranceSource(manager, dto.bookingId)
|
|
|
|
|
: null;
|
|
|
|
|
if (dto.bookingId && !bookingSource?.reference) {
|
|
|
|
|
throw new NotFoundException(`Booking ${dto.bookingId} not found`);
|
|
|
|
|
}
|
|
|
|
|
const quantity = bookingSource
|
|
|
|
|
? Number(bookingSource.containerQuantity) || 1
|
|
|
|
|
: Number(dto.quantity) || 0;
|
|
|
|
|
const weight = bookingSource
|
|
|
|
|
? Number(bookingSource.weight) || 0
|
|
|
|
|
: Number(dto.weight) || 0;
|
|
|
|
|
const volume = Number(dto.volume) || 0;
|
|
|
|
|
const containerCount = dto.containerId ? Math.round(quantity) : 0;
|
|
|
|
|
const truckEntrance = dto.bookingId
|
|
|
|
|
? this.mergeSystemTruckEntrance(dto.truckEntrance, await this.getBookingTruckEntranceSource(manager, dto.bookingId))
|
|
|
|
|
? this.mergeSystemTruckEntrance(dto.truckEntrance, bookingSource ?? {})
|
|
|
|
|
: dto.truckEntrance;
|
|
|
|
|
this.assertTruckEntrance(truckEntrance);
|
|
|
|
|
|
|
|
|
|
@@ -1628,7 +1647,7 @@ export class WarehouseInventoryService {
|
|
|
|
|
cargoId: dto.cargoId ?? null,
|
|
|
|
|
containerId: dto.containerId ?? null,
|
|
|
|
|
goodsId: dto.goodsId ?? null,
|
|
|
|
|
quantity: Number(dto.quantity) || 0,
|
|
|
|
|
quantity,
|
|
|
|
|
weight,
|
|
|
|
|
volume: dto.volume ?? null,
|
|
|
|
|
status: 'RECEIVED',
|
|
|
|
|
@@ -2619,16 +2638,6 @@ export class WarehouseInventoryService {
|
|
|
|
|
return { warehouse, yard, zone };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async assertBookingExists(manager: EntityManager, bookingId: string): Promise<void> {
|
|
|
|
|
const rows = await manager.query(
|
|
|
|
|
'SELECT id FROM freight.bookings WHERE id = $1 AND deleted_at IS NULL LIMIT 1',
|
|
|
|
|
[bookingId],
|
|
|
|
|
);
|
|
|
|
|
if (!rows || rows.length === 0) {
|
|
|
|
|
throw new NotFoundException(`Booking ${bookingId} not found`);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private appendNote(existing: string | null | undefined, note: string): string {
|
|
|
|
|
const trimmed = existing?.trim();
|
|
|
|
|
return trimmed ? `${trimmed}\n${note}` : note;
|
|
|
|
|
@@ -2872,42 +2881,42 @@ export class WarehouseInventoryService {
|
|
|
|
|
grnNumber: string;
|
|
|
|
|
direction?: string | null;
|
|
|
|
|
notes?: string | null;
|
|
|
|
|
truckEntrance: TruckEntranceDto;
|
|
|
|
|
truckEntrance?: TruckEntranceDto;
|
|
|
|
|
}): string {
|
|
|
|
|
const truck = input.truckEntrance;
|
|
|
|
|
const rows = [
|
|
|
|
|
`GRN Number: ${input.grnNumber}`,
|
|
|
|
|
input.direction ? `Direction: ${input.direction}` : null,
|
|
|
|
|
truck.ownerName ? `Owner / Shipper: ${truck.ownerName}` : null,
|
|
|
|
|
truck.consigneeDetails ? `Consignee: ${truck.consigneeDetails}` : null,
|
|
|
|
|
truck.edrDigitalBookingId ? `EDR Digital Booking ID: ${truck.edrDigitalBookingId}` : null,
|
|
|
|
|
truck.tin ? `TIN: ${truck.tin}` : null,
|
|
|
|
|
truck.customerPhone ? `Customer Phone: ${truck.customerPhone}` : null,
|
|
|
|
|
`Truck Plate: ${truck.truckPlateNumber}`,
|
|
|
|
|
truck.trailerPlateNumber ? `Trailer Plate: ${truck.trailerPlateNumber}` : null,
|
|
|
|
|
truck.assignedEquipmentNumber ? `Assigned Wagon / Container: ${truck.assignedEquipmentNumber}` : null,
|
|
|
|
|
truck.customsSealNumber ? `Customs Seal Number: ${truck.customsSealNumber}` : null,
|
|
|
|
|
truck.truckType ? `Truck Type: ${truck.truckType}` : null,
|
|
|
|
|
`Driver: ${truck.driverName}`,
|
|
|
|
|
`Driver Phone: ${truck.driverPhone}`,
|
|
|
|
|
truck.driverLicenseNumber ? `Driver License: ${truck.driverLicenseNumber}` : null,
|
|
|
|
|
`Entrance Tare Weight: ${Number(truck.entranceTareWeightKg)} kg`,
|
|
|
|
|
truck.exitTareWeightKg !== undefined ? `Exit Tare Weight: ${Number(truck.exitTareWeightKg)} kg` : null,
|
|
|
|
|
truck.declarationNumber ? `Declaration / Bill of Entry: ${truck.declarationNumber}` : null,
|
|
|
|
|
truck.incoterms ? `Incoterms: ${truck.incoterms}` : null,
|
|
|
|
|
truck.hsCodes ? `HS Codes: ${truck.hsCodes}` : null,
|
|
|
|
|
truck.itemCode ? `Item Code: ${truck.itemCode}` : null,
|
|
|
|
|
truck.itemDescription ? `Item Description: ${truck.itemDescription}` : null,
|
|
|
|
|
truck.packagingType ? `Packaging Type: ${truck.packagingType}` : null,
|
|
|
|
|
truck.unitCount !== undefined ? `Unit Count: ${Number(truck.unitCount)}` : null,
|
|
|
|
|
truck.grossWeightKg !== undefined ? `Gross Weight: ${Number(truck.grossWeightKg)} kg` : null,
|
|
|
|
|
truck.netWeightKg !== undefined ? `Net Weight: ${Number(truck.netWeightKg)} kg` : null,
|
|
|
|
|
truck.volumeDimensions ? `Volume / Dimensions: ${truck.volumeDimensions}` : null,
|
|
|
|
|
truck.conditionAtReceipt ? `Condition at Receipt: ${truck.conditionAtReceipt}` : null,
|
|
|
|
|
truck.damagedRejectedQuantity !== undefined ? `Damaged / Rejected Quantity: ${Number(truck.damagedRejectedQuantity)}` : null,
|
|
|
|
|
truck.warehouseCodeLocation ? `Warehouse Code / Location: ${truck.warehouseCodeLocation}` : null,
|
|
|
|
|
truck.driverSignatoryName ? `Driver Signatory: ${truck.driverSignatoryName}` : null,
|
|
|
|
|
truck.warehouseManagerName ? `EDR Warehouse Manager: ${truck.warehouseManagerName}` : null,
|
|
|
|
|
truck?.ownerName ? `Owner / Shipper: ${truck.ownerName}` : null,
|
|
|
|
|
truck?.consigneeDetails ? `Consignee: ${truck.consigneeDetails}` : null,
|
|
|
|
|
truck?.edrDigitalBookingId ? `EDR Digital Booking ID: ${truck.edrDigitalBookingId}` : null,
|
|
|
|
|
truck?.tin ? `TIN: ${truck.tin}` : null,
|
|
|
|
|
truck?.customerPhone ? `Customer Phone: ${truck.customerPhone}` : null,
|
|
|
|
|
truck?.truckPlateNumber ? `Truck Plate: ${truck.truckPlateNumber}` : null,
|
|
|
|
|
truck?.trailerPlateNumber ? `Trailer Plate: ${truck.trailerPlateNumber}` : null,
|
|
|
|
|
truck?.assignedEquipmentNumber ? `Assigned Wagon / Container: ${truck.assignedEquipmentNumber}` : null,
|
|
|
|
|
truck?.customsSealNumber ? `Customs Seal Number: ${truck.customsSealNumber}` : null,
|
|
|
|
|
truck?.truckType ? `Truck Type: ${truck.truckType}` : null,
|
|
|
|
|
truck?.driverName ? `Driver: ${truck.driverName}` : null,
|
|
|
|
|
truck?.driverPhone ? `Driver Phone: ${truck.driverPhone}` : null,
|
|
|
|
|
truck?.driverLicenseNumber ? `Driver License: ${truck.driverLicenseNumber}` : null,
|
|
|
|
|
truck?.entranceTareWeightKg !== undefined ? `Entrance Tare Weight: ${Number(truck.entranceTareWeightKg)} kg` : null,
|
|
|
|
|
truck?.exitTareWeightKg !== undefined ? `Exit Tare Weight: ${Number(truck.exitTareWeightKg)} kg` : null,
|
|
|
|
|
truck?.declarationNumber ? `Declaration / Bill of Entry: ${truck.declarationNumber}` : null,
|
|
|
|
|
truck?.incoterms ? `Incoterms: ${truck.incoterms}` : null,
|
|
|
|
|
truck?.hsCodes ? `HS Codes: ${truck.hsCodes}` : null,
|
|
|
|
|
truck?.itemCode ? `Item Code: ${truck.itemCode}` : null,
|
|
|
|
|
truck?.itemDescription ? `Item Description: ${truck.itemDescription}` : null,
|
|
|
|
|
truck?.packagingType ? `Packaging Type: ${truck.packagingType}` : null,
|
|
|
|
|
truck?.unitCount !== undefined ? `Unit Count: ${Number(truck.unitCount)}` : null,
|
|
|
|
|
truck?.grossWeightKg !== undefined ? `Gross Weight: ${Number(truck.grossWeightKg)} kg` : null,
|
|
|
|
|
truck?.netWeightKg !== undefined ? `Net Weight: ${Number(truck.netWeightKg)} kg` : null,
|
|
|
|
|
truck?.volumeDimensions ? `Volume / Dimensions: ${truck.volumeDimensions}` : null,
|
|
|
|
|
truck?.conditionAtReceipt ? `Condition at Receipt: ${truck.conditionAtReceipt}` : null,
|
|
|
|
|
truck?.damagedRejectedQuantity !== undefined ? `Damaged / Rejected Quantity: ${Number(truck.damagedRejectedQuantity)}` : null,
|
|
|
|
|
truck?.warehouseCodeLocation ? `Warehouse Code / Location: ${truck.warehouseCodeLocation}` : null,
|
|
|
|
|
truck?.driverSignatoryName ? `Driver Signatory: ${truck.driverSignatoryName}` : null,
|
|
|
|
|
truck?.warehouseManagerName ? `EDR Warehouse Manager: ${truck.warehouseManagerName}` : null,
|
|
|
|
|
input.notes?.trim() ? `Remarks: ${input.notes.trim()}` : null,
|
|
|
|
|
];
|
|
|
|
|
return rows.filter(Boolean).join('\n');
|
|
|
|
|
|