From fc2366f9e0af5d621bb6872c8eceb72c5c0bbc29 Mon Sep 17 00:00:00 2001 From: Hagernesh Date: Fri, 10 Jul 2026 06:42:20 +0000 Subject: [PATCH 1/3] feat(warehouses): prefill receive-to-warehouse from existing booking data The receive form now arrives filled with everything the booking already knows, instead of the operator re-typing it: - container numbers come from the per-unit records (booking_container_units) entered at booking time, falling back to the line-level aggregate - customs seal number prefilled from the units' seal numbers - net weight prefilled from the booking's declared cargo weight (tonnes) - driver signatory defaults to the arriving driver's name Existing prefills (owner, TIN, phone, booking ref, cargo, packaging, unit count, first-mile / customer truck + driver) unchanged; all fields stay editable where they were editable before. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../warehouses/warehouse-inventory.service.ts | 12 +++++++++++- .../components/warehouses/ReceiveInventoryModal.tsx | 7 +++++++ .../backoffice/src/types/warehouse.ts | 2 ++ 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/apps/edr-freight-api/src/modules/warehouses/warehouse-inventory.service.ts b/apps/edr-freight-api/src/modules/warehouses/warehouse-inventory.service.ts index 2b98856d0..7b3d1c2fe 100644 --- a/apps/edr-freight-api/src/modules/warehouses/warehouse-inventory.service.ts +++ b/apps/edr-freight-api/src/modules/warehouses/warehouse-inventory.service.ts @@ -207,6 +207,7 @@ export interface EligibleBookingRow { customerTin: string | null; customerPhone: string | null; containerNumber: string | null; + sealNumbers: string | null; containerQuantity: number | null; containerPackagingType: string | null; cargoDescription: string | null; @@ -774,7 +775,8 @@ export class WarehouseInventoryService { company.name AS "customer", company.tin AS "customerTin", COALESCE(company.contact_person_phone, company.phone, company.general_manager_phone, company.etrade_phone) AS "customerPhone", - bc.container_numbers AS "containerNumber", + COALESCE(bcu.unit_numbers, bc.container_numbers) AS "containerNumber", + bcu.seal_numbers AS "sealNumbers", bc.container_quantity AS "containerQuantity", bc.container_packaging_type AS "containerPackagingType", (NULLIF(TRIM(COALESCE(b.last_mile_delivery_address, '')), '') IS NOT NULL @@ -831,6 +833,14 @@ export class WarehouseInventoryService { LEFT JOIN freight.container_types container_type ON container_type.id = booking_container.container_type_id WHERE booking_container.booking_id = b.id AND booking_container.deleted_at IS NULL ) bc ON true + LEFT JOIN LATERAL ( + SELECT string_agg(NULLIF(unit.container_number, ''), ', ' ORDER BY unit.container_number) AS unit_numbers, + string_agg(DISTINCT NULLIF(unit.seal_number, ''), ', ') AS seal_numbers + FROM freight.booking_container_units unit + JOIN freight.booking_container line + ON line.id = unit.booking_container_id AND line.deleted_at IS NULL + WHERE line.booking_id = b.id AND unit.deleted_at IS NULL + ) bcu ON true LEFT JOIN LATERAL ( SELECT first_mile.id, first_mile.status, first_mile.vehicle_id FROM freight.first_mile first_mile diff --git a/apps/edr-freight-web/backoffice/src/components/warehouses/ReceiveInventoryModal.tsx b/apps/edr-freight-web/backoffice/src/components/warehouses/ReceiveInventoryModal.tsx index a1b91decb..b55143c93 100644 --- a/apps/edr-freight-web/backoffice/src/components/warehouses/ReceiveInventoryModal.tsx +++ b/apps/edr-freight-web/backoffice/src/components/warehouses/ReceiveInventoryModal.tsx @@ -296,6 +296,10 @@ const truckEntranceFromBookings = (bookings: EligibleBooking[]): { const truckType = commonNonEmptyValue( bookings.map((booking) => booking.firstMileTruckType || booking.customerTruckType), ); + const customsSealNumber = commonNonEmptyValue(bookings.map((booking) => booking.sealNumbers)); + // Booking's declared cargo weight (tonnes) — the receive-time net until re-weighed. + const bookingWeight = bookings.length === 1 ? Number(bookings[0]?.weight ?? '') : NaN; + const netWeightKg: number | '' = Number.isFinite(bookingWeight) && bookingWeight > 0 ? bookingWeight : ''; const edrDigitalBookingId = bookings.length === 1 ? bookings[0]?.reference ?? bookings[0]?.id ?? '' @@ -321,9 +325,11 @@ const truckEntranceFromBookings = (bookings: EligibleBooking[]): { customerPhone, edrDigitalBookingId, assignedEquipmentNumber, + customsSealNumber, itemDescription, packagingType, unitCount, + netWeightKg, grossWeightKg: '', truckPlateNumber, trailerPlateNumber, @@ -331,6 +337,7 @@ const truckEntranceFromBookings = (bookings: EligibleBooking[]): { driverPhone, driverLicenseNumber, truckType, + driverSignatoryName: driverName, }, lockedFields: { ownerName: Boolean(ownerName), diff --git a/apps/edr-freight-web/backoffice/src/types/warehouse.ts b/apps/edr-freight-web/backoffice/src/types/warehouse.ts index 8a806201e..2802a0355 100644 --- a/apps/edr-freight-web/backoffice/src/types/warehouse.ts +++ b/apps/edr-freight-web/backoffice/src/types/warehouse.ts @@ -388,6 +388,8 @@ export interface EligibleBooking { customerTin: string | null; customerPhone: string | null; containerNumber: string | null; + /** Distinct seal numbers from the booking's container units, comma-joined. */ + sealNumbers: string | null; containerQuantity: number | null; containerPackagingType: string | null; cargoDescription: string | null; From a7b429544b05e7b9572e7f437469c24b9ea7aec1 Mon Sep 17 00:00:00 2001 From: Hagernesh Date: Fri, 10 Jul 2026 06:50:35 +0000 Subject: [PATCH 2/3] fix(warehouses): trim receive form to fields with a real source - remove Incoterms, HS codes, and Item code from the truck-entrance form: nothing in the booking captures them, so they were always hand-typed noise - remove the hand-typed "Warehouse code and location" input: the backend now stamps it from the warehouse/yard/zone the operator actually selected (WH / YARD / ZONE codes), so the GRN and notes always match reality - Declaration number and Item description widen to full rows Backend DTO keeps the optional fields for compatibility. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../warehouses/warehouse-inventory.service.ts | 9 +++- .../warehouses/ReceiveInventoryModal.tsx | 54 ++++--------------- 2 files changed, 17 insertions(+), 46 deletions(-) diff --git a/apps/edr-freight-api/src/modules/warehouses/warehouse-inventory.service.ts b/apps/edr-freight-api/src/modules/warehouses/warehouse-inventory.service.ts index 7b3d1c2fe..92617db6d 100644 --- a/apps/edr-freight-api/src/modules/warehouses/warehouse-inventory.service.ts +++ b/apps/edr-freight-api/src/modules/warehouses/warehouse-inventory.service.ts @@ -891,11 +891,18 @@ export class WarehouseInventoryService { }> = []; await this.dataSource.transaction(async (manager) => { - await this.validateLocation(manager, { + const { warehouse, yard, zone } = await this.validateLocation(manager, { warehouseId: dto.warehouseId, yardId: dto.yardId, zoneId: dto.zoneId, }); + // The receive location is whatever the operator selected above — never a + // hand-typed string. Stamp it on the truck entrance for the GRN/notes. + if (dto.truckEntrance && !dto.truckEntrance.warehouseCodeLocation) { + dto.truckEntrance.warehouseCodeLocation = [warehouse.code, yard.code, zone.code] + .filter(Boolean) + .join(' / '); + } for (const bookingId of dto.bookingIds) { const skip = (reason: string) => { diff --git a/apps/edr-freight-web/backoffice/src/components/warehouses/ReceiveInventoryModal.tsx b/apps/edr-freight-web/backoffice/src/components/warehouses/ReceiveInventoryModal.tsx index b55143c93..de41c1a82 100644 --- a/apps/edr-freight-web/backoffice/src/components/warehouses/ReceiveInventoryModal.tsx +++ b/apps/edr-freight-web/backoffice/src/components/warehouses/ReceiveInventoryModal.tsx @@ -150,9 +150,6 @@ interface TruckEntranceFormState { assignedEquipmentNumber: string; customsSealNumber: string; declarationNumber: string; - incoterms: string; - hsCodes: string; - itemCode: string; itemDescription: string; packagingType: string; unitCount: number | ''; @@ -162,7 +159,6 @@ interface TruckEntranceFormState { volumeDimensions: string; conditionAtReceipt: string; damagedRejectedQuantity: number | ''; - warehouseCodeLocation: string; driverName: string; driverPhone: string; driverLicenseNumber: string; @@ -205,9 +201,6 @@ const emptyTruckEntrance = (): TruckEntranceFormState => ({ assignedEquipmentNumber: '', customsSealNumber: '', declarationNumber: '', - incoterms: '', - hsCodes: '', - itemCode: '', itemDescription: '', packagingType: '', unitCount: '', @@ -217,7 +210,6 @@ const emptyTruckEntrance = (): TruckEntranceFormState => ({ volumeDimensions: '', conditionAtReceipt: '', damagedRejectedQuantity: '', - warehouseCodeLocation: '', driverName: '', driverPhone: '', driverLicenseNumber: '', @@ -239,9 +231,6 @@ const toTruckEntrancePayload = (form: TruckEntranceFormState): TruckEntrancePayl 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), @@ -251,7 +240,6 @@ const toTruckEntrancePayload = (form: TruckEntranceFormState): TruckEntrancePayl 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, @@ -557,38 +545,19 @@ function TruckEntranceFields({ )} Customs and compliance - - onChange({ ...value, declarationNumber: e.currentTarget.value })} - /> - onChange({ ...value, incoterms: e.currentTarget.value })} - /> - onChange({ ...value, hsCodes: e.currentTarget.value })} + label="Declaration / Bill of Entry number" + value={value.declarationNumber} + onChange={(e) => onChange({ ...value, declarationNumber: e.currentTarget.value })} /> Physical cargo specifications - - onChange({ ...value, itemCode: e.currentTarget.value })} - /> - onChange({ ...value, itemDescription: e.currentTarget.value })} - /> - + onChange({ ...value, itemDescription: e.currentTarget.value })} + />