Files
edr-platform/apps/edr-freight-api/src/modules/train-scheduling/container-placement.util.ts
2026-08-08 14:22:38 +00:00

100 lines
2.9 KiB
TypeScript

import type { ContainerPlacementInput } from './utils/wagon-plan.util';
export type ContainerUnitForPlacement = {
bookingId: string;
bookingReference?: string | null;
bookingContainerId: string;
unitIndex: number;
label: string;
teuSlots?: number;
sizeFt?: number;
containerNumber?: string | null;
};
export function placeholderContainerNumber(unit: ContainerUnitForPlacement): string {
const ref = unit.bookingReference ?? unit.bookingId.slice(0, 8);
return `TBD-${ref}-${unit.unitIndex + 1}`;
}
export function isPlaceholderContainerNumber(value: string | null | undefined): boolean {
return Boolean(value?.trim().startsWith('TBD-'));
}
export function resolveContainerNumber(unit: ContainerUnitForPlacement): string {
const trimmed = unit.containerNumber?.trim();
return trimmed || placeholderContainerNumber(unit);
}
export function autoFillPlacements(
units: ContainerUnitForPlacement[],
containerSlots: number[],
): ContainerPlacementInput[] {
if (!units.length || !containerSlots.length) return [];
const placements: ContainerPlacementInput[] = [];
const MAX_TEU_PER_WAGON = 2;
let currentSlotIndex = 0;
let teuInCurrentSlot = 0;
for (const unit of units) {
const teu = unit.teuSlots ?? (unit.sizeFt && unit.sizeFt >= 40 ? 2 : 1);
if (teuInCurrentSlot > 0 && teuInCurrentSlot + teu > MAX_TEU_PER_WAGON) {
currentSlotIndex += 1;
teuInCurrentSlot = 0;
}
const sequenceNo =
containerSlots[Math.min(currentSlotIndex, containerSlots.length - 1)] ??
containerSlots[containerSlots.length - 1] ??
containerSlots[0];
placements.push({
bookingContainerId: unit.bookingContainerId,
unitIndex: unit.unitIndex,
sequenceNo,
containerNumber: resolveContainerNumber(unit),
});
teuInCurrentSlot += teu;
}
return placements;
}
export function findMissingContainerNumberIssues(
units: ContainerUnitForPlacement[],
placements: ContainerPlacementInput[],
): Array<{ bookingId: string; issue: string }> {
const issues: Array<{ bookingId: string; issue: string }> = [];
const byUnit = new Map(
placements.map((p) => [`${p.bookingContainerId}:${p.unitIndex}`, p]),
);
for (const unit of units) {
const placement = byUnit.get(`${unit.bookingContainerId}:${unit.unitIndex}`);
if (!placement?.containerNumber?.trim()) {
issues.push({
bookingId: unit.bookingId,
issue: `Missing container number for ${unit.label}`,
});
}
}
return issues;
}
export function placementsForBookings(
placements: ContainerPlacementInput[],
bookingIds: Set<string>,
units: ContainerUnitForPlacement[],
): ContainerPlacementInput[] {
const unitBookingIds = new Map(
units.map((u) => [`${u.bookingContainerId}:${u.unitIndex}`, u.bookingId]),
);
return placements.filter((p) => {
const bookingId = unitBookingIds.get(`${p.bookingContainerId}:${p.unitIndex}`);
return bookingId ? bookingIds.has(bookingId) : false;
});
}