Warehouse receiving for export and GRN generation

This commit is contained in:
hagiye
2026-06-27 04:20:23 +03:00
parent e30f48d3bc
commit fafa64e4db
4 changed files with 350 additions and 0 deletions

View File

@@ -2,6 +2,26 @@ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { ArrayNotEmpty, IsArray, IsIn, IsNumber, IsOptional, IsString, IsUUID, Min } from 'class-validator';
export class TruckEntranceDto {
@ApiPropertyOptional()
@IsOptional()
@IsString()
ownerName?: string;
@ApiPropertyOptional()
@IsOptional()
@IsString()
consigneeDetails?: string;
@ApiPropertyOptional()
@IsOptional()
@IsString()
edrDigitalBookingId?: string;
@ApiPropertyOptional()
@IsOptional()
@IsString()
tin?: string;
@ApiProperty()
@IsString()
truckPlateNumber!: string;
@@ -11,6 +31,85 @@ export class TruckEntranceDto {
@IsString()
trailerPlateNumber?: string;
@ApiPropertyOptional()
@IsOptional()
@IsString()
assignedEquipmentNumber?: string;
@ApiPropertyOptional()
@IsOptional()
@IsString()
customsSealNumber?: string;
@ApiPropertyOptional()
@IsOptional()
@IsString()
declarationNumber?: string;
@ApiPropertyOptional()
@IsOptional()
@IsString()
incoterms?: string;
@ApiPropertyOptional()
@IsOptional()
@IsString()
hsCodes?: string;
@ApiPropertyOptional()
@IsOptional()
@IsString()
itemCode?: string;
@ApiPropertyOptional()
@IsOptional()
@IsString()
itemDescription?: string;
@ApiPropertyOptional()
@IsOptional()
@IsString()
packagingType?: string;
@ApiPropertyOptional()
@IsOptional()
@IsNumber()
@Min(0)
unitCount?: number;
@ApiPropertyOptional()
@IsOptional()
@IsNumber()
@Min(0)
grossWeightKg?: number;
@ApiPropertyOptional()
@IsOptional()
@IsNumber()
@Min(0)
netWeightKg?: number;
@ApiPropertyOptional()
@IsOptional()
@IsString()
volumeDimensions?: string;
@ApiPropertyOptional()
@IsOptional()
@IsString()
conditionAtReceipt?: string;
@ApiPropertyOptional()
@IsOptional()
@IsNumber()
@Min(0)
damagedRejectedQuantity?: number;
@ApiPropertyOptional()
@IsOptional()
@IsString()
warehouseCodeLocation?: string;
@ApiProperty()
@IsString()
driverName!: string;
@@ -39,6 +138,16 @@ export class TruckEntranceDto {
@IsNumber()
@Min(0)
exitTareWeightKg?: number;
@ApiPropertyOptional()
@IsOptional()
@IsString()
driverSignatoryName?: string;
@ApiPropertyOptional()
@IsOptional()
@IsString()
warehouseManagerName?: string;
}
/** Bulk-receive eligible PAID bookings into a warehouse location, by trade direction. */

View File

@@ -2538,14 +2538,35 @@ export class WarehouseInventoryService {
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 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,
input.notes?.trim() ? `Remarks: ${input.notes.trim()}` : null,
];
return rows.filter(Boolean).join('\n');

View File

@@ -54,36 +54,99 @@ interface Location {
}
interface TruckEntranceFormState {
ownerName: string;
consigneeDetails: string;
edrDigitalBookingId: string;
tin: string;
truckPlateNumber: string;
trailerPlateNumber: string;
assignedEquipmentNumber: string;
customsSealNumber: string;
declarationNumber: string;
incoterms: string;
hsCodes: string;
itemCode: string;
itemDescription: string;
packagingType: string;
unitCount: number | '';
grossWeightKg: number | '';
netWeightKg: number | '';
volumeDimensions: string;
conditionAtReceipt: string;
damagedRejectedQuantity: number | '';
warehouseCodeLocation: string;
driverName: string;
driverPhone: string;
driverLicenseNumber: string;
truckType: string;
entranceTareWeightKg: number | '';
exitTareWeightKg: number | '';
driverSignatoryName: string;
warehouseManagerName: string;
}
const emptyTruckEntrance = (): TruckEntranceFormState => ({
ownerName: '',
consigneeDetails: '',
edrDigitalBookingId: '',
tin: '',
truckPlateNumber: '',
trailerPlateNumber: '',
assignedEquipmentNumber: '',
customsSealNumber: '',
declarationNumber: '',
incoterms: '',
hsCodes: '',
itemCode: '',
itemDescription: '',
packagingType: '',
unitCount: '',
grossWeightKg: '',
netWeightKg: '',
volumeDimensions: '',
conditionAtReceipt: '',
damagedRejectedQuantity: '',
warehouseCodeLocation: '',
driverName: '',
driverPhone: '',
driverLicenseNumber: '',
truckType: '',
entranceTareWeightKg: '',
exitTareWeightKg: '',
driverSignatoryName: '',
warehouseManagerName: '',
});
const toTruckEntrancePayload = (form: TruckEntranceFormState): TruckEntrancePayload => ({
ownerName: form.ownerName.trim() || undefined,
consigneeDetails: form.consigneeDetails.trim() || undefined,
edrDigitalBookingId: form.edrDigitalBookingId.trim() || undefined,
tin: form.tin.trim() || undefined,
truckPlateNumber: form.truckPlateNumber.trim(),
trailerPlateNumber: form.trailerPlateNumber.trim() || undefined,
assignedEquipmentNumber: form.assignedEquipmentNumber.trim() || undefined,
customsSealNumber: form.customsSealNumber.trim() || undefined,
declarationNumber: form.declarationNumber.trim() || undefined,
incoterms: form.incoterms.trim() || undefined,
hsCodes: form.hsCodes.trim() || undefined,
itemCode: form.itemCode.trim() || undefined,
itemDescription: form.itemDescription.trim() || undefined,
packagingType: form.packagingType.trim() || undefined,
unitCount: form.unitCount === '' ? undefined : Number(form.unitCount),
grossWeightKg: form.grossWeightKg === '' ? undefined : Number(form.grossWeightKg),
netWeightKg: form.netWeightKg === '' ? undefined : Number(form.netWeightKg),
volumeDimensions: form.volumeDimensions.trim() || undefined,
conditionAtReceipt: form.conditionAtReceipt.trim() || undefined,
damagedRejectedQuantity: form.damagedRejectedQuantity === '' ? undefined : Number(form.damagedRejectedQuantity),
warehouseCodeLocation: form.warehouseCodeLocation.trim() || undefined,
driverName: form.driverName.trim(),
driverPhone: form.driverPhone.trim(),
driverLicenseNumber: form.driverLicenseNumber.trim() || undefined,
truckType: form.truckType.trim() || undefined,
entranceTareWeightKg: Number(form.entranceTareWeightKg),
exitTareWeightKg: form.exitTareWeightKg === '' ? undefined : Number(form.exitTareWeightKg),
driverSignatoryName: form.driverSignatoryName.trim() || undefined,
warehouseManagerName: form.warehouseManagerName.trim() || undefined,
});
function TruckEntranceFields({
@@ -95,6 +158,33 @@ function TruckEntranceFields({
}) {
return (
<Stack gap="sm">
<Text size="sm" fw={600}>Customer and cargo ownership</Text>
<Group grow>
<TextInput
label="Owner's name"
value={value.ownerName}
onChange={(e) => onChange({ ...value, ownerName: e.currentTarget.value })}
/>
<TextInput
label="Consignee details"
value={value.consigneeDetails}
onChange={(e) => onChange({ ...value, consigneeDetails: e.currentTarget.value })}
/>
</Group>
<Group grow>
<TextInput
label="EDR digital booking ID"
value={value.edrDigitalBookingId}
onChange={(e) => onChange({ ...value, edrDigitalBookingId: e.currentTarget.value })}
/>
<TextInput
label="TIN"
value={value.tin}
onChange={(e) => onChange({ ...value, tin: e.currentTarget.value })}
/>
</Group>
<Text size="sm" fw={600} mt="xs">Transport and equipment tracking</Text>
<Group grow>
<TextInput
label="Truck plate number"
@@ -108,6 +198,18 @@ function TruckEntranceFields({
onChange={(e) => onChange({ ...value, trailerPlateNumber: e.currentTarget.value })}
/>
</Group>
<Group grow>
<TextInput
label="Assigned wagon / container number"
value={value.assignedEquipmentNumber}
onChange={(e) => onChange({ ...value, assignedEquipmentNumber: e.currentTarget.value })}
/>
<TextInput
label="Customs seal number"
value={value.customsSealNumber}
onChange={(e) => onChange({ ...value, customsSealNumber: e.currentTarget.value })}
/>
</Group>
<Group grow>
<TextInput
label="Driver name"
@@ -149,6 +251,103 @@ function TruckEntranceFields({
onChange={(v) => onChange({ ...value, exitTareWeightKg: v === '' ? '' : Number(v) })}
/>
</Group>
<Text size="sm" fw={600} mt="xs">Customs and compliance</Text>
<Group grow>
<TextInput
label="Declaration / Bill of Entry number"
value={value.declarationNumber}
onChange={(e) => onChange({ ...value, declarationNumber: e.currentTarget.value })}
/>
<TextInput
label="Incoterms"
value={value.incoterms}
onChange={(e) => onChange({ ...value, incoterms: e.currentTarget.value })}
/>
</Group>
<TextInput
label="HS codes"
value={value.hsCodes}
onChange={(e) => onChange({ ...value, hsCodes: e.currentTarget.value })}
/>
<Text size="sm" fw={600} mt="xs">Physical cargo specifications</Text>
<Group grow>
<TextInput
label="Item code"
value={value.itemCode}
onChange={(e) => onChange({ ...value, itemCode: e.currentTarget.value })}
/>
<TextInput
label="Item description"
value={value.itemDescription}
onChange={(e) => onChange({ ...value, itemDescription: e.currentTarget.value })}
/>
</Group>
<Group grow>
<TextInput
label="Packaging type"
value={value.packagingType}
onChange={(e) => onChange({ ...value, packagingType: e.currentTarget.value })}
/>
<NumberInput
label="Unit count"
min={0}
value={value.unitCount}
onChange={(v) => onChange({ ...value, unitCount: v === '' ? '' : Number(v) })}
/>
</Group>
<Group grow>
<NumberInput
label="Gross weight (kg)"
min={0}
value={value.grossWeightKg}
onChange={(v) => onChange({ ...value, grossWeightKg: v === '' ? '' : Number(v) })}
/>
<NumberInput
label="Net weight (kg)"
min={0}
value={value.netWeightKg}
onChange={(v) => onChange({ ...value, netWeightKg: v === '' ? '' : Number(v) })}
/>
</Group>
<TextInput
label="Volume / dimensions"
value={value.volumeDimensions}
onChange={(e) => onChange({ ...value, volumeDimensions: e.currentTarget.value })}
/>
<Text size="sm" fw={600} mt="xs">Quality and inspection</Text>
<Group grow>
<TextInput
label="Condition at receipt"
value={value.conditionAtReceipt}
onChange={(e) => onChange({ ...value, conditionAtReceipt: e.currentTarget.value })}
/>
<NumberInput
label="Damaged / rejected quantity"
min={0}
value={value.damagedRejectedQuantity}
onChange={(v) => onChange({ ...value, damagedRejectedQuantity: v === '' ? '' : Number(v) })}
/>
</Group>
<TextInput
label="Warehouse code and location"
value={value.warehouseCodeLocation}
onChange={(e) => onChange({ ...value, warehouseCodeLocation: e.currentTarget.value })}
/>
<Group grow>
<TextInput
label="Driver signatory"
value={value.driverSignatoryName}
onChange={(e) => onChange({ ...value, driverSignatoryName: e.currentTarget.value })}
/>
<TextInput
label="EDR warehouse manager"
value={value.warehouseManagerName}
onChange={(e) => onChange({ ...value, warehouseManagerName: e.currentTarget.value })}
/>
</Group>
</Stack>
);
}

View File

@@ -388,14 +388,35 @@ export interface BulkReceiveResult {
}
export interface TruckEntrancePayload {
ownerName?: string;
consigneeDetails?: string;
edrDigitalBookingId?: string;
tin?: string;
truckPlateNumber: string;
trailerPlateNumber?: string;
assignedEquipmentNumber?: string;
customsSealNumber?: string;
declarationNumber?: string;
incoterms?: string;
hsCodes?: string;
itemCode?: string;
itemDescription?: string;
packagingType?: string;
unitCount?: number;
grossWeightKg?: number;
netWeightKg?: number;
volumeDimensions?: string;
conditionAtReceipt?: string;
damagedRejectedQuantity?: number;
warehouseCodeLocation?: string;
driverName: string;
driverPhone: string;
driverLicenseNumber?: string;
truckType?: string;
entranceTareWeightKg: number;
exitTareWeightKg?: number;
driverSignatoryName?: string;
warehouseManagerName?: string;
}
export interface LoadPassedExportResult {