mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-27 19:00:55 +00:00
add hard capacity ceiling to weight limit rules
This commit is contained in:
@@ -605,16 +605,22 @@ export class TrainSchedulingService {
|
||||
);
|
||||
|
||||
if (!validation.valid) {
|
||||
// Put the violation detail in the message itself — global exception
|
||||
// filters flatten the body, and "Booking validation failed" alone tells
|
||||
// staff nothing (e.g. which wagon type is missing at the yard).
|
||||
throw new BadRequestException({
|
||||
message: 'Booking validation failed',
|
||||
message: `Booking validation failed: ${validation.violations.join('; ')}`,
|
||||
violations: validation.violations,
|
||||
warnings: validation.warnings,
|
||||
});
|
||||
}
|
||||
|
||||
if (!validation.bookings.length) {
|
||||
const shortfall = validation.deferredBookings
|
||||
.map((d) => `${d.reference}: ${d.reason}`)
|
||||
.join('; ');
|
||||
throw new BadRequestException({
|
||||
message: 'No bookings fit on available fleet wagons',
|
||||
message: `No wagons available for the selected bookings${shortfall ? ` — ${shortfall}` : ''}`,
|
||||
violations: ['Insufficient fleet wagons for the selected bookings'],
|
||||
warnings: validation.warnings,
|
||||
deferredBookings: validation.deferredBookings,
|
||||
@@ -628,12 +634,14 @@ export class TrainSchedulingService {
|
||||
if (!limitLoco) {
|
||||
throw new BadRequestException('Schedule train set has no locomotives');
|
||||
}
|
||||
if (limitLoco.maxPullWeightTons < totalWeightTons) {
|
||||
// forceAssign lets staff overload the locomotive set knowingly — the
|
||||
// validator has already surfaced it as a warning in that case.
|
||||
if (!dto.forceAssign && limitLoco.maxPullWeightTons < totalWeightTons) {
|
||||
throw new BadRequestException(
|
||||
`Train set locomotives cannot pull ${totalWeightTons}T`,
|
||||
);
|
||||
}
|
||||
if (limitLoco.maxTrainLengthMeters < totalLengthMeters) {
|
||||
if (!dto.forceAssign && limitLoco.maxTrainLengthMeters < totalLengthMeters) {
|
||||
throw new BadRequestException(
|
||||
`Train set locomotives cannot support ${totalLengthMeters}m`,
|
||||
);
|
||||
@@ -2251,9 +2259,16 @@ export class TrainSchedulingService {
|
||||
max20ftPairWeightDiffTons: trainLimits.max20ftPairWeightDiffTons,
|
||||
};
|
||||
|
||||
// With forceAssign, capacity-shaped rules (train limits, total weight,
|
||||
// locomotive capability) become warnings — staff owns the override. Physical
|
||||
// impossibilities (no wagon of the required type at the yard, wrong route,
|
||||
// wrong status) can never be forced and stay violations.
|
||||
const pushLimit = (issues: string[]) =>
|
||||
forceAssign ? warnings.push(...issues) : violations.push(...issues);
|
||||
|
||||
if (resolvedMode === 'MIXED') {
|
||||
violations.push(
|
||||
...validateMixedTrainLimits(wagonPlan, [containerWagonType, bulkWagonType], trainLimits),
|
||||
pushLimit(
|
||||
validateMixedTrainLimits(wagonPlan, [containerWagonType, bulkWagonType], trainLimits),
|
||||
);
|
||||
if (requireContainerPlacements) {
|
||||
const containerBookings = fittingBookings.filter((b) => b.freightType === 'CONTAINER');
|
||||
@@ -2270,7 +2285,7 @@ export class TrainSchedulingService {
|
||||
);
|
||||
}
|
||||
} else {
|
||||
violations.push(...validateTrainLimits(wagonPlan, wagonType, trainLimits));
|
||||
pushLimit(validateTrainLimits(wagonPlan, wagonType, trainLimits));
|
||||
|
||||
if (requireContainerPlacements && resolvedMode === 'CONTAINER') {
|
||||
violations.push(
|
||||
@@ -2293,8 +2308,8 @@ export class TrainSchedulingService {
|
||||
);
|
||||
if (totalWeightTons > trainLimits.maxWeightTons) {
|
||||
const message = `Total booking weight ${totalWeightTons}T exceeds max train weight ${trainLimits.maxWeightTons}T`;
|
||||
if (!violations.includes(message)) {
|
||||
violations.push(message);
|
||||
if (!violations.includes(message) && !warnings.includes(message)) {
|
||||
pushLimit([message]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2321,9 +2336,9 @@ export class TrainSchedulingService {
|
||||
(setLimits.maxPullWeightTons < totalWeightTons ||
|
||||
setLimits.maxTrainLengthMeters < totalLengthMeters)
|
||||
) {
|
||||
violations.push(
|
||||
pushLimit([
|
||||
'Assigned locomotives cannot support the total train weight and length',
|
||||
);
|
||||
]);
|
||||
}
|
||||
} else {
|
||||
const inServiceLocomotives = await this.locomotivesRepository.findAll({
|
||||
@@ -2341,7 +2356,7 @@ export class TrainSchedulingService {
|
||||
Number(l.maxTrainLengthMeters) >= totalLengthMeters,
|
||||
)
|
||||
) {
|
||||
violations.push('No locomotive can support the total train weight and length');
|
||||
pushLimit(['No locomotive can support the total train weight and length']);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3740,7 +3755,7 @@ export class TrainSchedulingService {
|
||||
|
||||
if (!validation.valid) {
|
||||
throw new BadRequestException({
|
||||
message: 'Booking validation failed',
|
||||
message: `Booking validation failed: ${validation.violations.join('; ')}`,
|
||||
violations: validation.violations,
|
||||
warnings: validation.warnings,
|
||||
});
|
||||
|
||||
@@ -210,7 +210,14 @@ export function expandBookingContainerUnits(bookings: Booking[]): ContainerUnitR
|
||||
const wagonsPerUnit = Number(line.containerType?.wagonsPerUnit ?? (sizeFt >= 40 ? 1 : 0.5));
|
||||
const perWagon = containersPerWagonFromType(wagonsPerUnit);
|
||||
const teuSlots = teuSlotsForSizeFt(sizeFt);
|
||||
// The REAL per-container numbers/weights entered at booking time. Unit i of
|
||||
// the line maps to units[i] (sortOrder order); the line-level number is only
|
||||
// a legacy fallback — never invent numbers here.
|
||||
const units = [...(line.units ?? [])].sort(
|
||||
(a, b) => Number(a.sortOrder ?? 0) - Number(b.sortOrder ?? 0),
|
||||
);
|
||||
for (let i = 0; i < qty; i += 1) {
|
||||
const unit = units[i];
|
||||
rows.push({
|
||||
bookingId: booking.id,
|
||||
bookingReference: booking.reference,
|
||||
@@ -219,12 +226,13 @@ export function expandBookingContainerUnits(bookings: Booking[]): ContainerUnitR
|
||||
containerTypeId: line.containerTypeId ?? '',
|
||||
containerTypeCode: code,
|
||||
label: `${booking.reference} · ${i + 1}/${qty} · ${code}`,
|
||||
grossWeightTons: Number(line.vgmPerUnitTons),
|
||||
grossWeightTons: Number(unit?.vgmTons ?? line.vgmPerUnitTons),
|
||||
sizeFt,
|
||||
wagonsPerUnit,
|
||||
containersPerWagon: perWagon,
|
||||
teuSlots,
|
||||
containerNumber: line.containerNumber ?? null,
|
||||
containerNumber:
|
||||
unit?.containerNumber?.trim() || line.containerNumber || null,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user