mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-27 19:00:55 +00:00
Enforce non-negative values for numeric inputs across various components
This commit is contained in:
@@ -17,7 +17,7 @@ import {
|
||||
} from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { InjectDataSource } from '@nestjs/typeorm';
|
||||
import { DataSource, EntityManager, In, IsNull, Not, QueryFailedError } from 'typeorm';
|
||||
import { DataSource, EntityManager, In, Not, QueryFailedError } from 'typeorm';
|
||||
|
||||
import { BookingsRepository } from '../bookings/bookings.repository';
|
||||
import { Booking } from '../bookings/entities/booking.entity';
|
||||
@@ -43,8 +43,6 @@ import { WagonAllocationBulkLoadsRepository } from '../train-schedules/wagon-all
|
||||
import { WagonAllocationContainerItemsRepository } from '../train-schedules/wagon-allocation-container-items.repository';
|
||||
import { WagonBookingAllocationsRepository } from '../train-schedules/wagon-booking-allocations.repository';
|
||||
import { WagonType } from '../wagon-types/entities/wagon-type.entity';
|
||||
import { CargoType } from '../rule-engine/entities/cargo-type.entity';
|
||||
import { ContainerType } from '../rule-engine/entities/container-type.entity';
|
||||
import { WagonTypesRepository } from '../wagon-types/wagon-types.repository';
|
||||
import { Wagon } from '../wagons/entities/wagon.entity';
|
||||
import { AssignBookingsDto } from './dto/assign-bookings.dto';
|
||||
@@ -3446,37 +3444,6 @@ export class TrainSchedulingService {
|
||||
return wagonType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Soft wagon-type resolution for the customer-facing availability preview
|
||||
* (getAvailableDaysForCargo). Reads the configured FK by cargo/container type;
|
||||
* returns null (→ "no days") instead of throwing when nothing is configured,
|
||||
* since this only estimates which days have wagons and creates no booking.
|
||||
*/
|
||||
private async resolveWagonTypeForPreview(
|
||||
freightType: 'CONTAINER' | 'BULK',
|
||||
cargoTypeCode: string | null,
|
||||
): Promise<WagonType | null> {
|
||||
if (freightType === 'BULK') {
|
||||
if (!cargoTypeCode) return null;
|
||||
const cargoType = await this.dataSource.getRepository(CargoType).findOne({
|
||||
where: { code: cargoTypeCode },
|
||||
relations: { wagonType: true },
|
||||
});
|
||||
return cargoType?.wagonType?.isActive ? cargoType.wagonType : null;
|
||||
}
|
||||
|
||||
// Container preview: the input carries no specific container type, so use the
|
||||
// wagon type of the first configured (active) container type.
|
||||
const containerType = await this.dataSource
|
||||
.getRepository(ContainerType)
|
||||
.findOne({
|
||||
where: { isActive: true, wagonTypeId: Not(IsNull()) },
|
||||
relations: { wagonType: true },
|
||||
order: { displayOrder: 'ASC' },
|
||||
});
|
||||
return containerType?.wagonType?.isActive ? containerType.wagonType : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stamp each plan slot with the leg it occupies (dynamic consist): the
|
||||
* boarding/alighting yards of the bookings it carries. Null means the
|
||||
@@ -4237,13 +4204,14 @@ export class TrainSchedulingService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Cargo-aware day pool: the EAT days that are actually FEASIBLE for the given
|
||||
* cargo. A day is selectable only when ≥1 OPEN schedule on the route that day
|
||||
* has BOTH (a) enough AVAILABLE wagons of the cargo's matching type at that
|
||||
* schedule's origin yard, and (b) remaining train capacity (not fully
|
||||
* allocated). Days with trains but not enough matching wagons are excluded.
|
||||
* Same `{ days: string[] }` shape as getAvailableDays — the customer still
|
||||
* picks a DAY, not a train.
|
||||
* Cargo-aware day pool: the EAT days a customer may pick for this cargo. A day
|
||||
* is selectable when ≥1 OPEN schedule on the route that day still has remaining
|
||||
* train capacity (not fully allocated). Wagon availability is deliberately NOT
|
||||
* checked here: whether a matching wagon currently sits in the right yard is an
|
||||
* operational question staff resolve when they approve or reject the booking,
|
||||
* not something the customer can act on while choosing a date. Same
|
||||
* `{ days: string[] }` shape as getAvailableDays — the customer picks a DAY,
|
||||
* not a train.
|
||||
*/
|
||||
async getAvailableDaysForCargo(input: {
|
||||
originYardId?: string;
|
||||
@@ -4259,85 +4227,17 @@ export class TrainSchedulingService {
|
||||
);
|
||||
if (schedules.length === 0) return { days: [] };
|
||||
|
||||
// Resolve the wagon type this cargo needs via the cargo/container-type FK.
|
||||
// Soft (customer availability preview): no days if unresolved, never throws.
|
||||
const requiredType = await this.resolveWagonTypeForPreview(
|
||||
input.freightType,
|
||||
input.cargoTypeCode ?? null,
|
||||
);
|
||||
if (!requiredType) return { days: [] };
|
||||
|
||||
// How many wagons of that type the cargo needs.
|
||||
const slotsNeeded = this.wagonsNeededForCargo(input, requiredType);
|
||||
void slotsNeeded; // TEMP: unused while the wagon-availability filter is off.
|
||||
|
||||
// TEMP (per request): wagon-availability filtering is DISABLED. A day is now
|
||||
// offered whenever a bookable schedule that day has remaining train capacity
|
||||
// — regardless of whether matching wagons are actually available at the
|
||||
// origin / boarding yard. This surfaces days even when no wagon is on hand.
|
||||
// Restore the block below to bring back the "enough matching wagons" gate.
|
||||
//
|
||||
// // AVAILABLE wagons of the required type, counted once per origin yard.
|
||||
// const availableByYard = new Map<string, number>();
|
||||
// const availableAt = async (yardId: string): Promise<number> => {
|
||||
// const cached = availableByYard.get(yardId);
|
||||
// if (cached !== undefined) return cached;
|
||||
// const counts = await this.countFleetAvailability(yardId);
|
||||
// const n =
|
||||
// counts.find((c) => c.wagonTypeId === requiredType.id)?.available ?? 0;
|
||||
// availableByYard.set(yardId, n);
|
||||
// return n;
|
||||
// };
|
||||
|
||||
const days = new Set<string>();
|
||||
for (const s of schedules) {
|
||||
const hasCapacity =
|
||||
Math.max(0, (s.maxWagons ?? 0) - (s.trainSet?.wagonCount ?? 0)) > 0;
|
||||
if (!hasCapacity) continue;
|
||||
// TEMP (per request): wagon-availability check commented out — see note
|
||||
// above. Dynamic consist: wagons may ride from the train's origin OR
|
||||
// already sit at the booking's own boarding yard and attach when the train
|
||||
// arrives — either pool can serve a sub-corridor booking.
|
||||
// let enoughWagons = (await availableAt(s.originStationId)) >= slotsNeeded;
|
||||
// if (
|
||||
// !enoughWagons &&
|
||||
// input.originYardId &&
|
||||
// input.originYardId !== s.originStationId
|
||||
// ) {
|
||||
// enoughWagons = (await availableAt(input.originYardId)) >= slotsNeeded;
|
||||
// }
|
||||
// if (!enoughWagons) continue;
|
||||
if (s.scheduledDepartureDate)
|
||||
days.add(eatDay(new Date(s.scheduledDepartureDate)));
|
||||
}
|
||||
return { days: [...days].sort() };
|
||||
}
|
||||
|
||||
/**
|
||||
* Wagons needed for a cargo (pre-booking estimate). BULK: ceil(weight /
|
||||
* capacity). CONTAINER: TEU packing — 40ft = 2 TEU, 20ft = 1 TEU, 2 TEU per
|
||||
* wagon. Mirrors wagon-plan.util without fabricating Booking entities.
|
||||
*/
|
||||
private wagonsNeededForCargo(
|
||||
input: {
|
||||
freightType: 'CONTAINER' | 'BULK';
|
||||
totalWeightTons?: number;
|
||||
containers?: Array<{ containerSize: string; quantity: number }>;
|
||||
},
|
||||
wagonType: WagonType,
|
||||
): number {
|
||||
if (input.freightType === 'BULK') {
|
||||
const capacity = Number(wagonType.capacityTons) || 1;
|
||||
const weight = Number(input.totalWeightTons ?? 0);
|
||||
return Math.max(1, Math.ceil(weight / capacity));
|
||||
}
|
||||
const teu = (input.containers ?? []).reduce((sum, c) => {
|
||||
const per = c.containerSize === '40ft' ? 2 : 1;
|
||||
return sum + per * Math.max(0, Number(c.quantity ?? 0));
|
||||
}, 0);
|
||||
return Math.max(1, Math.ceil(teu / 2));
|
||||
}
|
||||
|
||||
/**
|
||||
* Ordered stop yards of a schedule's route: origin → milestones → destination,
|
||||
* de-duplicated. Falls back to the two-endpoint pseudo-route when the schedule
|
||||
|
||||
Reference in New Issue
Block a user