mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-27 00:52:50 +00:00
fix(warehouses): truck-arrival crash on duplicate containers; auto-GRN on import unload
Truck Arrival died with "[@mantine/core] Duplicate options are not supported" when a booking carried the same container number on two container lines (the unit uniqueness is per line, not per booking): - bookingContainerWeights now GROUPs BY container number (MAX weight) - the weighing modal dedupes both the containers MultiSelect and the assigned-trucks Select defensively, so bad data degrades instead of crashing the whole page Import GRN is now issued automatically when the arrived train is unloaded: autoUnloadArrivedBookings stamps GRN-IMPORT-<date>-<booking> on both the create and update paths (never overwrites an existing GRN). The per-row GRN button in the import queue lights up with no extra clicks. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1597,6 +1597,8 @@ export class WarehouseInventoryService {
|
||||
status: 'UNLOADED',
|
||||
unloadedAt: now,
|
||||
arrivedAt: existing.arrivedAt ?? now,
|
||||
// Import GRN is issued automatically at train unload.
|
||||
...(existing.grnNumber ? {} : { grnNumber: this.generateGrnNumber('IMPORT', booking.id, now) }),
|
||||
});
|
||||
await this.activityLog.record({
|
||||
activityType: 'INVENTORY_UNLOADED',
|
||||
@@ -1630,6 +1632,7 @@ export class WarehouseInventoryService {
|
||||
quantity: 1,
|
||||
weight: Number(booking.weight) || 0,
|
||||
status: 'UNLOADED',
|
||||
grnNumber: this.generateGrnNumber('IMPORT', booking.id, now),
|
||||
arrivedAt: now,
|
||||
unloadedAt: now,
|
||||
notes: allocated?.rule ? `Unloaded → ${allocated.path}` : 'Unloaded from arrived import train',
|
||||
@@ -2757,11 +2760,12 @@ export class WarehouseInventoryService {
|
||||
const rows: Array<{ containerNumber: string; weightTons: string }> =
|
||||
await this.dataSource.query(
|
||||
`SELECT bcu.container_number AS "containerNumber",
|
||||
COALESCE(bcu.vgm_tons, 0) AS "weightTons"
|
||||
MAX(COALESCE(bcu.vgm_tons, 0)) AS "weightTons"
|
||||
FROM freight.booking_container_units bcu
|
||||
JOIN freight.booking_container bc
|
||||
ON bc.id = bcu.booking_container_id AND bc.deleted_at IS NULL
|
||||
WHERE bc.booking_id = $1 AND bcu.deleted_at IS NULL
|
||||
GROUP BY bcu.container_number
|
||||
ORDER BY bcu.container_number`,
|
||||
[bookingId],
|
||||
);
|
||||
|
||||
@@ -202,8 +202,11 @@ export function ReleaseOrderModal({ opened, onClose, item, truckPrefill }: Relea
|
||||
];
|
||||
// Only trucks actually assigned to THIS booking (last-mile prefill or customer
|
||||
// portal) are selectable. No global fleet list — if nothing is assigned, the
|
||||
// operator types the plate manually in the field below.
|
||||
const truckSelectOptions = assignedTruckOptions;
|
||||
// operator types the plate manually in the field below. Deduped by plate:
|
||||
// duplicate option values crash Mantine's Select.
|
||||
const truckSelectOptions = [
|
||||
...new Map(assignedTruckOptions.map((t) => [t.value, t])).values(),
|
||||
];
|
||||
// Neither a last-mile truck nor a customer truck has been assigned yet.
|
||||
const noTruckAssigned = assignedTruckOptions.length === 0 && !isCustomerAssignedTruck;
|
||||
const isTruckIdentityLocked = isEntranceLocked || isCustomerAssignedTruck || hasLastMileTruckPrefill;
|
||||
@@ -216,10 +219,19 @@ export function ReleaseOrderModal({ opened, onClose, item, truckPrefill }: Relea
|
||||
const containerWeightByNumber = new Map(
|
||||
containerWeights.map((c) => [c.containerNumber.toUpperCase(), Number(c.weightTons) || 0]),
|
||||
);
|
||||
const containerSelectData = containerWeights.map((c) => ({
|
||||
value: c.containerNumber,
|
||||
label: `${c.containerNumber} · ${(Number(c.weightTons) || 0).toLocaleString()} t`,
|
||||
}));
|
||||
// Mantine Selects throw on duplicate option values — legacy bookings can carry
|
||||
// the same container number on two lines, so dedupe defensively.
|
||||
const containerSelectData = [
|
||||
...new Map(
|
||||
containerWeights.map((c) => [
|
||||
c.containerNumber,
|
||||
{
|
||||
value: c.containerNumber,
|
||||
label: `${c.containerNumber} · ${(Number(c.weightTons) || 0).toLocaleString()} t`,
|
||||
},
|
||||
]),
|
||||
).values(),
|
||||
];
|
||||
const selectedContainerNumbers = containerNumbers.map((n) => n.trim()).filter(Boolean);
|
||||
const selectedCargoWeight = Number(
|
||||
selectedContainerNumbers
|
||||
|
||||
Reference in New Issue
Block a user