mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-01 22:53:26 +00:00
changes
This commit is contained in:
@@ -416,8 +416,14 @@ export function planWagonsWithStock(params: {
|
||||
}
|
||||
const allowedIds = new Set(candidates.map((wt) => wt.id));
|
||||
const teu = unit.teuSlots ?? teuSlotsForSizeFt(unit.sizeFt ?? 20);
|
||||
// A BULK wagon whose cargo alights before this unit boards is empty
|
||||
// steel again and may carry containers on the later leg (and vice
|
||||
// versa — see the bulk reuse pass). While both ride together, the
|
||||
// kinds never mix.
|
||||
const disjointFrom = (open: OpenSlot): boolean =>
|
||||
open.covered.to <= leg.from || leg.to <= open.covered.from;
|
||||
const fitsSlot = (open: OpenSlot): boolean =>
|
||||
open.kind === 'CONTAINER' &&
|
||||
(open.kind === 'CONTAINER' || disjointFrom(open)) &&
|
||||
allowedIds.has(open.slot.wagonTypeId) &&
|
||||
teuFits(open, leg, teu) &&
|
||||
canExtendSpan(open, leg);
|
||||
@@ -457,6 +463,7 @@ export function planWagonsWithStock(params: {
|
||||
message: `Cargo type "${booking.cargoType?.cargoTypeName ?? booking.cargoType?.code ?? 'unknown'}" has no wagon types configured — set them in its configuration before scheduling.`,
|
||||
};
|
||||
}
|
||||
const allowedIds = new Set(candidates.map((wt) => wt.id));
|
||||
// Break-bulk (PER_ITEM): `cargoTotalWeightVgm` is the ITEM COUNT and the
|
||||
// real tonnage lives in `bulkTotalWeightTons` — bookingCargoTons resolves
|
||||
// it either way. Items are indivisible, so a wagon takes whole items only,
|
||||
@@ -500,9 +507,61 @@ export function planWagonsWithStock(params: {
|
||||
)
|
||||
: candidates;
|
||||
|
||||
// One bulk booking per wagon: a wagon carrying bulk takes that one
|
||||
// booking's cargo only — never topped up from another booking, even of
|
||||
// the same cargo type. Every bulk booking therefore opens its own wagons.
|
||||
// One bulk booking per wagon PER LEG: a wagon carrying bulk takes that one
|
||||
// booking's cargo for as long as it rides — never topped up from another
|
||||
// booking on the same edges, even of the same cargo type.
|
||||
//
|
||||
// A wagon whose cargo ALIGHTS before this booking boards is free steel
|
||||
// again, though: an import container uncoupled at Dire Dawa leaves its
|
||||
// wagon empty for bulk loading there. Reuse those disjoint-leg slots
|
||||
// before opening new stock — containers already do this, and without it a
|
||||
// train with 3 wagons could not seat 3 wagons of leg-1 cargo plus 3 of
|
||||
// leg-2 cargo.
|
||||
const disjoint = (open: OpenSlot): boolean =>
|
||||
open.covered.to <= leg.from || leg.to <= open.covered.from;
|
||||
const reusable = openSlots.filter(
|
||||
(open) =>
|
||||
disjoint(open) &&
|
||||
allowedIds.has(open.slot.wagonTypeId) &&
|
||||
// A pooled wagon boards at its own yard; it cannot ride backwards.
|
||||
!(open.pool && leg.from < open.covered.from),
|
||||
);
|
||||
for (const open of reusable) {
|
||||
if (perItem ? remainingItems <= 0 : remainingWeight <= 0) break;
|
||||
const wagonType = candidates.find((wt) => wt.id === open.slot.wagonTypeId);
|
||||
if (!wagonType) continue;
|
||||
const room = bulkTonsPerWagon(
|
||||
booking.cargoType,
|
||||
open.slot.wagonTypeId,
|
||||
Number(open.slot.capacityTons),
|
||||
);
|
||||
if (!(room > 0)) continue;
|
||||
let take: number;
|
||||
if (perItem) {
|
||||
const budget = Math.min(
|
||||
bulkItemsFitFor(booking.cargoType, open.slot.wagonTypeId) ??
|
||||
Number.MAX_SAFE_INTEGER,
|
||||
perItemTons > 0 ? Math.max(1, Math.floor(room / perItemTons)) : 1,
|
||||
);
|
||||
const takeItems = Math.max(1, Math.min(budget, remainingItems));
|
||||
take = roundTons(Math.min(takeItems * perItemTons, remainingWeight));
|
||||
remainingItems -= takeItems;
|
||||
} else {
|
||||
take = roundTons(Math.min(room, remainingWeight));
|
||||
}
|
||||
addAllocation(
|
||||
open.slot,
|
||||
booking.id,
|
||||
booking.reference,
|
||||
take,
|
||||
AllocationLoadType.Bulk,
|
||||
);
|
||||
// The wagon now rides this leg too — it is the same physical steel, so
|
||||
// no extra stock is consumed beyond extending its span.
|
||||
extendSpan(open, leg);
|
||||
remainingWeight = roundTons(remainingWeight - take);
|
||||
placedAnywhere = true;
|
||||
}
|
||||
|
||||
while ((perItem ? remainingItems > 0 : remainingWeight > 0) || !placedAnywhere) {
|
||||
// Per-item: openSlot's stock-depth tie-break would override the fit
|
||||
|
||||
Reference in New Issue
Block a user