Import operation gate pass

This commit is contained in:
hagiye
2026-06-27 21:04:27 +03:00
parent 3441ee4a0a
commit fee4365b70
37 changed files with 3084 additions and 116 deletions

View File

@@ -1,5 +1,7 @@
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { Type } from 'class-transformer';
import { ArrayNotEmpty, IsArray, IsIn, IsNumber, IsOptional, IsString, IsUUID, Min } from 'class-validator';
import { ValidateNested } from 'class-validator';
export class TruckEntranceDto {
@ApiPropertyOptional()
@@ -179,8 +181,11 @@ export class BulkReceiveDto {
@IsUUID('all', { each: true })
bookingIds!: string[];
@ApiProperty({ type: TruckEntranceDto })
truckEntrance!: TruckEntranceDto;
@ApiPropertyOptional({ type: TruckEntranceDto })
@IsOptional()
@ValidateNested()
@Type(() => TruckEntranceDto)
truckEntrance?: TruckEntranceDto;
@ApiPropertyOptional()
@IsOptional()

View File

@@ -1,5 +1,7 @@
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { Type } from 'class-transformer';
import { IsNumber, IsOptional, IsString, IsUUID, Min } from 'class-validator';
import { ValidateNested } from 'class-validator';
import { TruckEntranceDto } from './bulk-receive.dto';
export class ReceiveWarehouseInventoryDto {
@@ -57,6 +59,8 @@ export class ReceiveWarehouseInventoryDto {
notes?: string;
@ApiProperty({ type: TruckEntranceDto })
@ValidateNested()
@Type(() => TruckEntranceDto)
truckEntrance!: TruckEntranceDto;
@ApiPropertyOptional()

View File

@@ -86,6 +86,12 @@ export class WarehouseInventoryController {
return this.inventoryService.readyToLoadExport();
}
@Get('received-export')
@ApiOperation({ summary: 'EXPORT inventory that has been received and is awaiting inspection' })
receivedExport() {
return this.inventoryService.receivedExport();
}
@Get('loaded-export')
@ApiOperation({ summary: 'EXPORT inventory that is LOADED and queued for dispatch' })
loadedExport() {

View File

@@ -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');

View File

@@ -124,6 +124,7 @@ import { WarehousesService } from './warehouses.service';
WarehouseAllocationService,
WarehouseFeeService,
WarehouseSchedulingAdapterService,
WarehouseReleaseDocumentService,
],
})
export class WarehousesModule {}