feat(freight): changed the train schedule

This commit is contained in:
Michael Abebe
2026-06-06 16:31:19 +03:00
parent a9c2f2eb97
commit b45421d2a2
7 changed files with 392 additions and 740 deletions

View File

@@ -15,9 +15,9 @@ import {
import { LocomotivesRepository } from "../locomotives/locomotives.repository";
import { TrainSetWagon } from "../train-sets/entities/train-set-wagon.entity";
import { TrainSet } from "../train-sets/entities/train-set.entity";
import { Route } from "../routes/entities/route.entity";
import { TrainScheduleBooking } from "../train-schedules/entities/train-schedule-booking.entity";
import { TrainSchedule } from "../train-schedules/entities/train-schedule.entity";
import { WagonBookingAllocation } from "../train-schedules/entities/wagon-booking-allocation.entity";
import { WagonType } from "../wagon-types/entities/wagon-type.entity";
import { WagonTypesRepository } from "../wagon-types/wagon-types.repository";
import { CreateContainerTrainScheduleDto } from "./dto/create-container-train-schedule.dto";
@@ -179,19 +179,12 @@ export class TrainSchedulingService {
}
async createContainerTrainSchedule(dto: CreateContainerTrainScheduleDto) {
const validation = await this.validateContainerBookingsForScheduling(dto);
if (!validation.valid) {
throw new BadRequestException({
message: "train_schedule_invalid",
violations: validation.violations,
});
}
const route = await this.getActiveRoute(dto.routeId);
const locomotive = await this.selectOrValidateLocomotive(
dto.locomotiveId,
validation.summary.totalWeightTons,
validation.summary.totalLengthMeters,
0,
0,
);
const createdSchedule = await this.dataSource.transaction(
@@ -212,99 +205,24 @@ export class TrainSchedulingService {
);
}
if (
Number(lockedLocomotive.maxPullWeightTons) <
validation.summary.totalWeightTons
) {
throw new BadRequestException(
`Locomotive ${lockedLocomotive.code} cannot pull ${validation.summary.totalWeightTons}T`,
);
}
if (
Number(lockedLocomotive.maxTrainLengthMeters) <
validation.summary.totalLengthMeters
) {
throw new BadRequestException(
`Locomotive ${lockedLocomotive.code} cannot support ${validation.summary.totalLengthMeters}m`,
);
}
const existingScheduleCount = await manager
.getRepository(TrainScheduleBooking)
.count({
where: {
bookingId: In(validation.bookings.map((booking) => booking.id)),
},
});
if (existingScheduleCount > 0) {
throw new BadRequestException(
"One or more bookings are already scheduled",
);
}
const trainSet = await this.buildTrainSet(
const trainSet = await this.buildEmptyTrainSet(
manager,
lockedLocomotive,
validation.wagonType,
validation.summary.totalWeightTons,
validation.summary.totalLengthMeters,
validation.wagonPlan,
);
const schedule = manager.getRepository(TrainSchedule).create({
trainSetId: trainSet.id,
originStationId: dto.originStationId,
destinationStationId: dto.destinationStationId,
routeId: route.id,
originStationId: route.originYardId,
destinationStationId: route.destinationYardId,
scheduledDepartureDate: new Date(dto.scheduleDate),
status: "SCHEDULED",
status: "DRAFT",
});
const savedSchedule = await manager
.getRepository(TrainSchedule)
.save(schedule);
const scheduleBookings = validation.bookings.map((booking) =>
manager.getRepository(TrainScheduleBooking).create({
trainScheduleId: savedSchedule.id,
bookingId: booking.id,
}),
);
await manager
.getRepository(TrainScheduleBooking)
.save(scheduleBookings);
const savedWagons = await manager.getRepository(TrainSetWagon).find({
where: { trainSetId: trainSet.id },
order: { sequenceNo: "ASC" },
});
const wagonBySequence = new Map(
savedWagons.map((wagon) => [wagon.sequenceNo, wagon]),
);
const allocationRows = validation.wagonPlan.flatMap((wagonPlan) => {
const wagon = wagonBySequence.get(wagonPlan.sequenceNo);
if (!wagon) {
throw new BadRequestException(
`Missing wagon sequence ${wagonPlan.sequenceNo}`,
);
}
return wagonPlan.allocations.map((allocation) =>
manager.getRepository(WagonBookingAllocation).create({
trainSetWagonId: wagon.id,
bookingId: allocation.bookingId,
allocatedWeightTons: allocation.allocatedWeightTons,
}),
);
});
await manager
.getRepository(WagonBookingAllocation)
.save(allocationRows);
await locomotiveRepository.update(lockedLocomotive.id, {
status: "ASSIGNED",
});
@@ -589,6 +507,21 @@ export class TrainSchedulingService {
return savedTrainSet;
}
async buildEmptyTrainSet(
manager: EntityManager,
locomotive: Locomotive,
) {
const trainSet = manager.getRepository(TrainSet).create({
locomotiveId: locomotive.id,
totalWeightTons: 0,
totalLengthMeters: 0,
wagonCount: 0,
status: 'DRAFT',
});
return manager.getRepository(TrainSet).save(trainSet);
}
allocateBookingsToWagons(
bookings: Booking[],
baseWagonPlan: WagonPlanRecord[],
@@ -648,6 +581,7 @@ export class TrainSchedulingService {
const schedules = await this.dataSource.getRepository(TrainSchedule).find({
relations: {
trainSet: { locomotive: true },
route: true,
originStation: true,
destinationStation: true,
scheduleBookings: true,
@@ -658,6 +592,7 @@ export class TrainSchedulingService {
return schedules.map((schedule) => ({
id: schedule.id,
scheduleDate: schedule.scheduledDepartureDate,
routeName: schedule.route?.name ?? null,
origin:
schedule.originStation?.label ?? schedule.originStation?.code ?? null,
destination:
@@ -689,6 +624,7 @@ export class TrainSchedulingService {
.findOne({
where: { id },
relations: {
route: true,
trainSet: {
locomotive: true,
wagons: { wagonType: true, allocations: { booking: true } },
@@ -708,6 +644,12 @@ export class TrainSchedulingService {
return {
id: schedule.id,
status: schedule.status,
route: schedule.route
? {
id: schedule.route.id,
name: schedule.route.name,
}
: null,
scheduledDepartureDate: schedule.scheduledDepartureDate,
scheduledArrivalDate: schedule.scheduledArrivalDate,
originStation: schedule.originStation,
@@ -830,6 +772,22 @@ export class TrainSchedulingService {
});
}
private async getActiveRoute(routeId: string) {
const route = await this.dataSource.getRepository(Route).findOne({
where: { id: routeId },
});
if (!route) {
throw new NotFoundException(`Route ${routeId} not found`);
}
if (!route.isActive) {
throw new BadRequestException(`Route ${route.name} is inactive`);
}
return route;
}
private toUtcDateKey(value: Date | string) {
const date = value instanceof Date ? value : new Date(value);
return date.toISOString().slice(0, 10);