mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-28 17:10:56 +00:00
ui design for schedule
This commit is contained in:
@@ -1201,6 +1201,15 @@ export class TrainSchedulingService {
|
||||
const lineEntry = lineById.get(placement.bookingContainerId);
|
||||
if (!lineEntry) continue;
|
||||
|
||||
// Durably persist the container number on the booking container line first, so it
|
||||
// survives a refresh regardless of whether a wagon allocation slot can be matched
|
||||
// below. booking_container is the source of truth re-read into the preview units.
|
||||
if (placement.containerNumber && placement.containerNumber.trim()) {
|
||||
await manager.getRepository(BookingContainer).update(placement.bookingContainerId, {
|
||||
containerNumber: placement.containerNumber.trim(),
|
||||
});
|
||||
}
|
||||
|
||||
const allocationId = allocationBySlotBooking.get(
|
||||
`${placement.sequenceNo}:${lineEntry.bookingId}`,
|
||||
);
|
||||
@@ -1226,13 +1235,6 @@ export class TrainSchedulingService {
|
||||
bookingContainerId: placement.bookingContainerId,
|
||||
});
|
||||
}
|
||||
|
||||
// Save container number to booking_container when staff enters a new container number
|
||||
if (placement.containerNumber && placement.containerNumber.trim()) {
|
||||
await manager.getRepository(BookingContainer).update(placement.bookingContainerId, {
|
||||
containerNumber: placement.containerNumber.trim(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (containerItems.length) {
|
||||
|
||||
@@ -53,6 +53,7 @@ export type ContainerUnitRow = {
|
||||
wagonsPerUnit?: number;
|
||||
containersPerWagon?: number;
|
||||
teuSlots?: number;
|
||||
containerNumber?: string | null;
|
||||
};
|
||||
|
||||
export type ContainerPlacementInput = {
|
||||
@@ -121,7 +122,7 @@ export function buildContainerWagonPlan(
|
||||
allocations: [],
|
||||
}));
|
||||
|
||||
return allocateBookingsToSlots(bookings, basePlan, AllocationLoadType.Container).map((slot) => ({
|
||||
return allocateContainersToSlots(bookings, basePlan).map((slot) => ({
|
||||
...slot,
|
||||
slotLoadType: 'CONTAINER' as SlotLoadType,
|
||||
}));
|
||||
@@ -223,6 +224,7 @@ export function expandBookingContainerUnits(bookings: Booking[]): ContainerUnitR
|
||||
wagonsPerUnit,
|
||||
containersPerWagon: perWagon,
|
||||
teuSlots,
|
||||
containerNumber: line.containerNumber ?? null,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -290,6 +292,65 @@ function allocateBookingsToSlots(
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Allocate container bookings across wagon slots by TEU capacity. A wagon holds at most
|
||||
* 2 TEU, so it carries either one 40ft container (2 TEU) or two 20ft containers (1 TEU
|
||||
* each) — a 40ft is NEVER mixed onto the same wagon as a 20ft. Every physical container
|
||||
* maps to a real wagon allocation, and this mirrors the frontend auto-fill packing
|
||||
* exactly so a placement's sequenceNo always lands on a slot that holds an allocation
|
||||
* for its booking.
|
||||
*
|
||||
* Weight-based packing (allocateBookingsToSlots) is wrong for containers: it collapses
|
||||
* several light containers into the first wagons by tonnage and leaves later container
|
||||
* units without an allocation slot, which silently drops their container items on assign.
|
||||
*/
|
||||
function allocateContainersToSlots(
|
||||
bookings: Booking[],
|
||||
basePlan: WagonPlanSlot[],
|
||||
): WagonPlanSlot[] {
|
||||
const slots = basePlan.map((slot) => ({
|
||||
...slot,
|
||||
assignedWeightTons: 0,
|
||||
allocations: [] as WagonAllocationRecord[],
|
||||
}));
|
||||
if (!slots.length) return slots;
|
||||
|
||||
const units = expandBookingContainerUnits(bookings);
|
||||
let currentSlotIndex = 0;
|
||||
let teuInCurrentSlot = 0;
|
||||
|
||||
for (const unit of units) {
|
||||
const teu = unit.teuSlots ?? teuSlotsForSizeFt(unit.sizeFt ?? 20);
|
||||
|
||||
// Move to the next wagon once this one can't fit the container's TEU. This keeps a
|
||||
// 40ft (2 TEU) alone on its wagon and never pairs it with a 20ft.
|
||||
if (teuInCurrentSlot > 0 && teuInCurrentSlot + teu > MAX_TEU_SLOTS_PER_WAGON) {
|
||||
currentSlotIndex += 1;
|
||||
teuInCurrentSlot = 0;
|
||||
}
|
||||
|
||||
const slot = slots[Math.min(currentSlotIndex, slots.length - 1)]!;
|
||||
|
||||
let allocation = slot.allocations.find((a) => a.bookingId === unit.bookingId);
|
||||
if (!allocation) {
|
||||
allocation = {
|
||||
bookingId: unit.bookingId,
|
||||
bookingReference: unit.bookingReference,
|
||||
allocatedWeightTons: 0,
|
||||
loadType: AllocationLoadType.Container,
|
||||
};
|
||||
slot.allocations.push(allocation);
|
||||
}
|
||||
allocation.allocatedWeightTons = roundTons(
|
||||
allocation.allocatedWeightTons + unit.grossWeightTons,
|
||||
);
|
||||
slot.assignedWeightTons = roundTons(slot.assignedWeightTons + unit.grossWeightTons);
|
||||
teuInCurrentSlot += teu;
|
||||
}
|
||||
|
||||
return slots;
|
||||
}
|
||||
|
||||
export function expandContainerItems(
|
||||
booking: Booking,
|
||||
allocationId: string,
|
||||
|
||||
Reference in New Issue
Block a user