resolve conflict

This commit is contained in:
hagiye
2026-06-05 10:59:52 +03:00
19 changed files with 2325 additions and 654 deletions

View File

@@ -3,25 +3,28 @@ import {
ConflictException,
Injectable,
NotFoundException,
} from '@nestjs/common';
import { InjectDataSource } from '@nestjs/typeorm';
import { DataSource, EntityManager, In } from 'typeorm';
} from "@nestjs/common";
import { InjectDataSource } from "@nestjs/typeorm";
import { DataSource, EntityManager, In } from "typeorm";
import { Booking } from '../bookings/entities/booking.entity';
import { Locomotive, type LocomotiveStatus } from '../locomotives/entities/locomotive.entity';
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 { 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';
import { GetEligibleContainerBookingsDto } from './dto/get-eligible-container-bookings.dto';
import { PreviewContainerTrainScheduleDto } from './dto/preview-container-train-schedule.dto';
import { Booking } from "../bookings/entities/booking.entity";
import {
Locomotive,
type LocomotiveStatus,
} from "../locomotives/entities/locomotive.entity";
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 { 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";
import { GetEligibleContainerBookingsDto } from "./dto/get-eligible-container-bookings.dto";
import { PreviewContainerTrainScheduleDto } from "./dto/preview-container-train-schedule.dto";
const DEFAULT_WAGON_TYPE_CODE = 'NW5';
const DEFAULT_WAGON_TYPE_CODE = "NW5";
const MAX_TRAIN_WEIGHT_TONS = 3500;
const MAX_TRAIN_LENGTH_METERS = 760;
@@ -74,11 +77,12 @@ export class TrainSchedulingService {
private readonly dataSource: DataSource,
private readonly locomotivesRepository: LocomotivesRepository,
private readonly wagonTypesRepository: WagonTypesRepository,
) {}
) { }
async getEligibleContainerBookings(query: GetEligibleContainerBookingsDto) {
const bookingRepository = this.dataSource.getRepository(Booking);
const queryBuilder = bookingRepository
<<<<<<< HEAD
.createQueryBuilder('booking')
.leftJoinAndSelect('booking.company', 'company')
.leftJoinAndSelect('booking.originYard', 'originYard')
@@ -88,17 +92,35 @@ export class TrainSchedulingService {
.leftJoin(TrainScheduleBooking, 'scheduleBooking', 'scheduleBooking.booking_id = booking.id')
.where('booking.freightType = :freightType', { freightType: 'CONTAINER' })
.andWhere('scheduleBooking.id IS NULL');
=======
.createQueryBuilder("booking")
.leftJoinAndSelect("booking.customer", "customer")
.leftJoinAndSelect("booking.originYard", "originYard")
.leftJoinAndSelect("booking.destinationYard", "destinationYard")
.leftJoinAndSelect("booking.bookingContainers", "bookingContainer")
.leftJoinAndSelect("bookingContainer.containerType", "containerType")
.leftJoin(
TrainScheduleBooking,
"scheduleBooking",
"scheduleBooking.booking_id = booking.id",
)
.where("booking.freightType = :freightType", { freightType: "CONTAINER" })
.andWhere("scheduleBooking.id IS NULL");
>>>>>>> bd6ad54eea229305a214d06081e0ffd27fe163b8
if (query.originStationId) {
queryBuilder.andWhere('booking.originYardId = :originStationId', {
queryBuilder.andWhere("booking.originYardId = :originStationId", {
originStationId: query.originStationId,
});
}
if (query.destinationStationId) {
queryBuilder.andWhere('booking.destinationYardId = :destinationStationId', {
destinationStationId: query.destinationStationId,
});
queryBuilder.andWhere(
"booking.destinationYardId = :destinationStationId",
{
destinationStationId: query.destinationStationId,
},
);
}
if (query.scheduleDate) {
@@ -109,25 +131,52 @@ export class TrainSchedulingService {
}
if (query.status) {
queryBuilder.andWhere('booking.status = :status', { status: query.status });
queryBuilder.andWhere("booking.status = :status", {
status: query.status,
});
}
const bookings = await queryBuilder
.orderBy('booking.scheduled_date', 'ASC')
.addOrderBy('booking.created_at', 'ASC')
.orderBy("booking.scheduled_date", "ASC")
.addOrderBy("booking.created_at", "ASC")
.getMany();
const items: EligibleBookingItem[] = bookings.map((booking) => ({
id: booking.id,
reference: booking.reference,
<<<<<<< HEAD
customer: booking.company?.name ?? booking.company?.email ?? 'Unknown customer',
containerType: booking.bookingContainers
?.map((container) => container.containerType?.label ?? container.containerType?.code ?? 'Container')
.join(', ') ?? 'Container',
quantity: booking.bookingContainers?.reduce((sum, container) => sum + Number(container.quantity ?? 0), 0) ?? 0,
=======
customer:
booking.company?.name ?? booking.company?.email ?? "Unknown customer",
containerType:
booking.bookingContainers
?.map(
(container) =>
container.containerType?.label ??
container.containerType?.code ??
"Container",
)
.join(", ") ?? "Container",
quantity:
booking.bookingContainers?.reduce(
(sum, container) => sum + Number(container.quantity ?? 0),
0,
) ?? 0,
>>>>>>> bd6ad54eea229305a214d06081e0ffd27fe163b8
weightTons: this.roundTons(booking.cargoTotalWeightVgm),
origin: booking.originYard?.label ?? booking.originYard?.code ?? 'Unknown origin',
destination: booking.destinationYard?.label ?? booking.destinationYard?.code ?? 'Unknown destination',
origin:
booking.originYard?.label ??
booking.originYard?.code ??
"Unknown origin",
destination:
booking.destinationYard?.label ??
booking.destinationYard?.code ??
"Unknown destination",
preferredDepartureDate: booking.scheduledDate.toISOString(),
status: booking.status,
}));
@@ -155,7 +204,7 @@ export class TrainSchedulingService {
if (!validation.valid) {
throw new BadRequestException({
message: 'train_schedule_invalid',
message: "train_schedule_invalid",
violations: validation.violations,
});
}
@@ -165,92 +214,115 @@ export class TrainSchedulingService {
validation.summary.totalWeightTons,
);
const createdSchedule = await this.dataSource.transaction(async (manager) => {
const locomotiveRepository = manager.getRepository(Locomotive);
const lockedLocomotive = await locomotiveRepository.findOne({
where: { id: locomotive.id },
lock: { mode: 'pessimistic_write' },
});
const createdSchedule = await this.dataSource.transaction(
async (manager) => {
const locomotiveRepository = manager.getRepository(Locomotive);
const lockedLocomotive = await locomotiveRepository.findOne({
where: { id: locomotive.id },
lock: { mode: "pessimistic_write" },
});
if (!lockedLocomotive) {
throw new NotFoundException(`Locomotive ${locomotive.id} not found`);
}
if (lockedLocomotive.status !== 'AVAILABLE') {
throw new ConflictException(`Locomotive ${lockedLocomotive.code} is not available`);
}
if (Number(lockedLocomotive.maxPullWeightTons) < validation.summary.totalWeightTons) {
throw new BadRequestException(
`Locomotive ${lockedLocomotive.code} cannot pull ${validation.summary.totalWeightTons}T`,
);
}
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(
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,
scheduledDepartureDate: new Date(dto.scheduleDate),
status: 'SCHEDULED',
});
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}`);
if (!lockedLocomotive) {
throw new NotFoundException(`Locomotive ${locomotive.id} not found`);
}
return wagonPlan.allocations.map((allocation) =>
manager.getRepository(WagonBookingAllocation).create({
trainSetWagonId: wagon.id,
bookingId: allocation.bookingId,
allocatedWeightTons: allocation.allocatedWeightTons,
if (lockedLocomotive.status !== "AVAILABLE") {
throw new ConflictException(
`Locomotive ${lockedLocomotive.code} is not available`,
);
}
if (
Number(lockedLocomotive.maxPullWeightTons) <
validation.summary.totalWeightTons
) {
throw new BadRequestException(
`Locomotive ${lockedLocomotive.code} cannot pull ${validation.summary.totalWeightTons}T`,
);
}
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(
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,
scheduledDepartureDate: new Date(dto.scheduleDate),
status: "SCHEDULED",
});
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);
await manager.getRepository(WagonBookingAllocation).save(allocationRows);
const savedWagons = await manager.getRepository(TrainSetWagon).find({
where: { trainSetId: trainSet.id },
order: { sequenceNo: "ASC" },
});
await locomotiveRepository.update(lockedLocomotive.id, {
status: 'ASSIGNED',
});
const wagonBySequence = new Map(
savedWagons.map((wagon) => [wagon.sequenceNo, wagon]),
);
const allocationRows = validation.wagonPlan.flatMap((wagonPlan) => {
const wagon = wagonBySequence.get(wagonPlan.sequenceNo);
return savedSchedule.id;
});
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",
});
return savedSchedule.id;
},
);
return this.getContainerTrainScheduleById(createdSchedule);
}
@@ -261,7 +333,7 @@ export class TrainSchedulingService {
const bookingIds = [...new Set(dto.bookingIds)];
if (!bookingIds.length) {
throw new BadRequestException('At least one booking is required');
throw new BadRequestException("At least one booking is required");
}
const [wagonType] = await this.wagonTypesRepository.findAll({
@@ -269,7 +341,9 @@ export class TrainSchedulingService {
});
if (!wagonType) {
throw new NotFoundException(`Wagon type ${DEFAULT_WAGON_TYPE_CODE} not found`);
throw new NotFoundException(
`Wagon type ${DEFAULT_WAGON_TYPE_CODE} not found`,
);
}
const bookings = await this.loadBookingsForScheduling(bookingIds);
@@ -278,21 +352,29 @@ export class TrainSchedulingService {
if (bookings.length !== bookingIds.length) {
const foundIds = new Set(bookings.map((booking) => booking.id));
const missing = bookingIds.filter((id) => !foundIds.has(id));
violations.push(`Bookings not found: ${missing.join(', ')}`);
violations.push(`Bookings not found: ${missing.join(", ")}`);
}
const scheduledLinks = await this.dataSource.getRepository(TrainScheduleBooking).find({
where: { bookingId: In(bookingIds) },
select: { bookingId: true },
});
const scheduledLinks = await this.dataSource
.getRepository(TrainScheduleBooking)
.find({
where: { bookingId: In(bookingIds) },
select: { bookingId: true },
});
if (scheduledLinks.length > 0) {
violations.push('One or more selected bookings are already assigned to a train schedule');
violations.push(
"One or more selected bookings are already assigned to a train schedule",
);
}
const nonContainerBookings = bookings.filter((booking) => booking.freightType !== 'CONTAINER');
const nonContainerBookings = bookings.filter(
(booking) => booking.freightType !== "CONTAINER",
);
if (nonContainerBookings.length > 0) {
violations.push('Only CONTAINER bookings are supported for train scheduling');
violations.push(
"Only CONTAINER bookings are supported for train scheduling",
);
}
const scheduleDateKey = this.toUtcDateKey(dto.scheduleDate);
@@ -302,44 +384,62 @@ export class TrainSchedulingService {
booking.destinationYardId !== dto.destinationStationId,
);
if (routeMismatch) {
violations.push('Selected bookings must share the same origin and destination as the schedule');
violations.push(
"Selected bookings must share the same origin and destination as the schedule",
);
}
const dateMismatch = bookings.some(
(booking) => this.toUtcDateKey(booking.scheduledDate) !== scheduleDateKey,
);
if (dateMismatch) {
violations.push('Selected bookings must share the same schedule date');
violations.push("Selected bookings must share the same schedule date");
}
const uniqueOriginCount = new Set(bookings.map((booking) => booking.originYardId)).size;
const uniqueOriginCount = new Set(
bookings.map((booking) => booking.originYardId),
).size;
if (uniqueOriginCount > 1) {
violations.push('Selected bookings must share the same origin station');
violations.push("Selected bookings must share the same origin station");
}
const uniqueDestinationCount = new Set(bookings.map((booking) => booking.destinationYardId)).size;
const uniqueDestinationCount = new Set(
bookings.map((booking) => booking.destinationYardId),
).size;
if (uniqueDestinationCount > 1) {
violations.push('Selected bookings must share the same destination station');
violations.push(
"Selected bookings must share the same destination station",
);
}
const uniqueDateCount = new Set(
bookings.map((booking) => this.toUtcDateKey(booking.scheduledDate)),
).size;
if (uniqueDateCount > 1) {
violations.push('Selected bookings must share the same preferred departure date');
violations.push(
"Selected bookings must share the same preferred departure date",
);
}
const totalWeightTons = this.roundTons(
bookings.reduce((sum, booking) => sum + Number(booking.cargoTotalWeightVgm ?? 0), 0),
bookings.reduce(
(sum, booking) => sum + Number(booking.cargoTotalWeightVgm ?? 0),
0,
),
);
const wagonPlan = this.allocateBookingsToWagons(bookings, this.calculateNW5WagonPlan(totalWeightTons, wagonType));
const wagonPlan = this.allocateBookingsToWagons(
bookings,
this.calculateNW5WagonPlan(totalWeightTons, wagonType),
);
const totalLengthMeters = this.roundTons(
wagonPlan.reduce((sum, wagon) => sum + wagon.lengthMeters, 0),
);
if (totalWeightTons > MAX_TRAIN_WEIGHT_TONS) {
violations.push(`Total booking weight ${totalWeightTons}T exceeds max train weight ${MAX_TRAIN_WEIGHT_TONS}T`);
violations.push(
`Total booking weight ${totalWeightTons}T exceeds max train weight ${MAX_TRAIN_WEIGHT_TONS}T`,
);
}
if (totalLengthMeters > MAX_TRAIN_LENGTH_METERS) {
@@ -357,21 +457,25 @@ export class TrainSchedulingService {
);
}
const availableLocomotiveCount = await this.dataSource.getRepository(Locomotive).count({
where: { status: 'AVAILABLE' as LocomotiveStatus },
});
const availableLocomotiveCount = await this.dataSource
.getRepository(Locomotive)
.count({
where: { status: "AVAILABLE" as LocomotiveStatus },
});
if (availableLocomotiveCount === 0) {
violations.push('No available locomotive exists for scheduling');
violations.push("No available locomotive exists for scheduling");
} else {
const capableLocomotives = await this.dataSource.getRepository(Locomotive).find({
where: { status: 'AVAILABLE' },
});
const capableLocomotives = await this.dataSource
.getRepository(Locomotive)
.find({
where: { status: "AVAILABLE" },
});
const canPull = capableLocomotives.some(
(locomotive) => Number(locomotive.maxPullWeightTons) >= totalWeightTons,
);
if (!canPull) {
violations.push('No available locomotive can pull the total weight');
violations.push("No available locomotive can pull the total weight");
}
}
@@ -391,14 +495,21 @@ export class TrainSchedulingService {
};
}
calculateNW5WagonPlan(totalBookingWeightTons: number, wagonType: WagonType): WagonPlanRecord[] {
calculateNW5WagonPlan(
totalBookingWeightTons: number,
wagonType: WagonType,
): WagonPlanRecord[] {
const wagonCapacityTons = Number(wagonType.capacityTons);
const wagonsNeeded = Math.ceil(totalBookingWeightTons / wagonCapacityTons);
let remainingWeight = this.roundTons(totalBookingWeightTons);
return Array.from({ length: wagonsNeeded }, (_, index) => {
const assignedWeightTons = this.roundTons(Math.min(wagonCapacityTons, remainingWeight));
remainingWeight = this.roundTons(Math.max(0, remainingWeight - assignedWeightTons));
const assignedWeightTons = this.roundTons(
Math.min(wagonCapacityTons, remainingWeight),
);
remainingWeight = this.roundTons(
Math.max(0, remainingWeight - assignedWeightTons),
);
return {
sequenceNo: index + 1,
@@ -410,15 +521,20 @@ export class TrainSchedulingService {
});
}
async selectOrValidateLocomotive(locomotiveId: string, totalWeightTons: number) {
async selectOrValidateLocomotive(
locomotiveId: string,
totalWeightTons: number,
) {
const locomotive = await this.locomotivesRepository.findById(locomotiveId);
if (!locomotive) {
throw new NotFoundException(`Locomotive ${locomotiveId} not found`);
}
if (locomotive.status !== 'AVAILABLE') {
throw new BadRequestException(`Locomotive ${locomotive.code} is not available`);
if (locomotive.status !== "AVAILABLE") {
throw new BadRequestException(
`Locomotive ${locomotive.code} is not available`,
);
}
if (Number(locomotive.maxPullWeightTons) < totalWeightTons) {
@@ -443,7 +559,7 @@ export class TrainSchedulingService {
totalWeightTons,
totalLengthMeters,
wagonCount: wagonPlan.length,
status: 'ASSIGNED',
status: "ASSIGNED",
});
const savedTrainSet = await manager.getRepository(TrainSet).save(trainSet);
@@ -463,11 +579,16 @@ export class TrainSchedulingService {
return savedTrainSet;
}
allocateBookingsToWagons(bookings: Booking[], baseWagonPlan: WagonPlanRecord[]): WagonPlanRecord[] {
allocateBookingsToWagons(
bookings: Booking[],
baseWagonPlan: WagonPlanRecord[],
): WagonPlanRecord[] {
const remaining = bookings.map((booking) => ({
bookingId: booking.id,
bookingReference: booking.reference,
remainingWeightTons: this.roundTons(Number(booking.cargoTotalWeightVgm ?? 0)),
remainingWeightTons: this.roundTons(
Number(booking.cargoTotalWeightVgm ?? 0),
),
}));
let bookingIndex = 0;
@@ -496,7 +617,9 @@ export class TrainSchedulingService {
booking.remainingWeightTons - allocatedWeightTons,
);
wagonRemaining = this.roundTons(wagonRemaining - allocatedWeightTons);
assignedWeightTons = this.roundTons(assignedWeightTons + allocatedWeightTons);
assignedWeightTons = this.roundTons(
assignedWeightTons + allocatedWeightTons,
);
if (booking.remainingWeightTons <= 0) {
bookingIndex += 1;
@@ -519,31 +642,39 @@ export class TrainSchedulingService {
destinationStation: true,
scheduleBookings: true,
},
order: { scheduledDepartureDate: 'DESC', createdAt: 'DESC' },
order: { scheduledDepartureDate: "DESC", createdAt: "DESC" },
});
return schedules.map((schedule) => ({
id: schedule.id,
scheduleDate: schedule.scheduledDepartureDate,
origin: schedule.originStation?.label ?? schedule.originStation?.code ?? null,
origin:
schedule.originStation?.label ?? schedule.originStation?.code ?? null,
destination:
schedule.destinationStation?.label ?? schedule.destinationStation?.code ?? null,
schedule.destinationStation?.label ??
schedule.destinationStation?.code ??
null,
locomotive: schedule.trainSet?.locomotive
? {
id: schedule.trainSet.locomotive.id,
code: schedule.trainSet.locomotive.code,
name: schedule.trainSet.locomotive.name ?? null,
}
id: schedule.trainSet.locomotive.id,
code: schedule.trainSet.locomotive.code,
name: schedule.trainSet.locomotive.name ?? null,
}
: null,
wagonCount: schedule.trainSet?.wagonCount ?? 0,
totalWeightTons: this.roundTons(Number(schedule.trainSet?.totalWeightTons ?? 0)),
totalLengthMeters: this.roundTons(Number(schedule.trainSet?.totalLengthMeters ?? 0)),
totalWeightTons: this.roundTons(
Number(schedule.trainSet?.totalWeightTons ?? 0),
),
totalLengthMeters: this.roundTons(
Number(schedule.trainSet?.totalLengthMeters ?? 0),
),
bookingsCount: schedule.scheduleBookings?.length ?? 0,
status: schedule.status,
}));
}
async getContainerTrainScheduleById(id: string) {
<<<<<<< HEAD
const schedule = await this.dataSource.getRepository(TrainSchedule).findOne({
where: { id },
relations: {
@@ -553,6 +684,24 @@ export class TrainSchedulingService {
scheduleBookings: { booking: { company: true, originYard: true, destinationYard: true } },
},
});
=======
const schedule = await this.dataSource
.getRepository(TrainSchedule)
.findOne({
where: { id },
relations: {
trainSet: {
locomotive: true,
wagons: { wagonType: true, allocations: { booking: true } },
},
originStation: true,
destinationStation: true,
scheduleBookings: {
booking: { company: true, originYard: true, destinationYard: true },
},
},
});
>>>>>>> bd6ad54eea229305a214d06081e0ffd27fe163b8
if (!schedule) {
throw new NotFoundException(`Train schedule ${id} not found`);
@@ -567,47 +716,54 @@ export class TrainSchedulingService {
destinationStation: schedule.destinationStation,
trainSet: schedule.trainSet
? {
id: schedule.trainSet.id,
status: schedule.trainSet.status,
wagonCount: schedule.trainSet.wagonCount,
totalWeightTons: this.roundTons(Number(schedule.trainSet.totalWeightTons)),
totalLengthMeters: this.roundTons(Number(schedule.trainSet.totalLengthMeters)),
locomotive: schedule.trainSet.locomotive
? {
id: schedule.trainSet.locomotive.id,
code: schedule.trainSet.locomotive.code,
name: schedule.trainSet.locomotive.name,
status: schedule.trainSet.locomotive.status,
maxPullWeightTons: this.roundTons(
Number(schedule.trainSet.locomotive.maxPullWeightTons),
),
id: schedule.trainSet.id,
status: schedule.trainSet.status,
wagonCount: schedule.trainSet.wagonCount,
totalWeightTons: this.roundTons(
Number(schedule.trainSet.totalWeightTons),
),
totalLengthMeters: this.roundTons(
Number(schedule.trainSet.totalLengthMeters),
),
locomotive: schedule.trainSet.locomotive
? {
id: schedule.trainSet.locomotive.id,
code: schedule.trainSet.locomotive.code,
name: schedule.trainSet.locomotive.name,
status: schedule.trainSet.locomotive.status,
maxPullWeightTons: this.roundTons(
Number(schedule.trainSet.locomotive.maxPullWeightTons),
),
}
: null,
wagons: [...(schedule.trainSet.wagons ?? [])]
.sort((left, right) => left.sequenceNo - right.sequenceNo)
.map((wagon) => ({
id: wagon.id,
sequenceNo: wagon.sequenceNo,
capacityTons: this.roundTons(Number(wagon.capacityTons)),
lengthMeters: this.roundTons(Number(wagon.lengthMeters)),
assignedWeightTons: this.roundTons(
Number(wagon.assignedWeightTons),
),
wagonType: wagon.wagonType
? {
id: wagon.wagonType.id,
code: wagon.wagonType.code,
name: wagon.wagonType.name,
}
: null,
wagons:
[...(schedule.trainSet.wagons ?? [])]
.sort((left, right) => left.sequenceNo - right.sequenceNo)
.map((wagon) => ({
id: wagon.id,
sequenceNo: wagon.sequenceNo,
capacityTons: this.roundTons(Number(wagon.capacityTons)),
lengthMeters: this.roundTons(Number(wagon.lengthMeters)),
assignedWeightTons: this.roundTons(Number(wagon.assignedWeightTons)),
wagonType: wagon.wagonType
? {
id: wagon.wagonType.id,
code: wagon.wagonType.code,
name: wagon.wagonType.name,
}
: null,
allocations:
wagon.allocations?.map((allocation) => ({
id: allocation.id,
bookingId: allocation.bookingId,
bookingReference: allocation.booking?.reference ?? null,
allocatedWeightTons: this.roundTons(Number(allocation.allocatedWeightTons)),
})) ?? [],
})),
}
: null,
allocations:
wagon.allocations?.map((allocation) => ({
id: allocation.id,
bookingId: allocation.bookingId,
bookingReference: allocation.booking?.reference ?? null,
allocatedWeightTons: this.roundTons(
Number(allocation.allocatedWeightTons),
),
})) ?? [],
})),
}
: null,
bookings:
schedule.scheduleBookings?.map((scheduleBooking) => ({
@@ -617,17 +773,21 @@ export class TrainSchedulingService {
scheduleBooking.booking?.company?.name ??
scheduleBooking.booking?.company?.email ??
null,
weightTons: this.roundTons(Number(scheduleBooking.booking?.cargoTotalWeightVgm ?? 0)),
weightTons: this.roundTons(
Number(scheduleBooking.booking?.cargoTotalWeightVgm ?? 0),
),
status: scheduleBooking.booking?.status ?? null,
})) ?? [],
};
}
async cancelTrainSchedule(id: string) {
const schedule = await this.dataSource.getRepository(TrainSchedule).findOne({
where: { id },
relations: { trainSet: { locomotive: true } },
});
const schedule = await this.dataSource
.getRepository(TrainSchedule)
.findOne({
where: { id },
relations: { trainSet: { locomotive: true } },
});
if (!schedule) {
throw new NotFoundException(`Train schedule ${id} not found`);
@@ -635,19 +795,21 @@ export class TrainSchedulingService {
await this.dataSource.transaction(async (manager) => {
await manager.getRepository(TrainSchedule).update(schedule.id, {
status: 'CANCELLED',
status: "CANCELLED",
});
if (schedule.trainSetId) {
await manager.getRepository(TrainSet).update(schedule.trainSetId, {
status: 'CANCELLED',
status: "CANCELLED",
});
}
if (schedule.trainSet?.locomotiveId) {
await manager.getRepository(Locomotive).update(schedule.trainSet.locomotiveId, {
status: 'AVAILABLE',
});
await manager
.getRepository(Locomotive)
.update(schedule.trainSet.locomotiveId, {
status: "AVAILABLE",
});
}
});
@@ -663,7 +825,7 @@ export class TrainSchedulingService {
destinationYard: true,
bookingContainers: { containerType: true },
},
order: { createdAt: 'ASC' },
order: { createdAt: "ASC" },
});
}
@@ -673,7 +835,7 @@ export class TrainSchedulingService {
}
private roundTons(value: number | string | null | undefined) {
const numericValue = typeof value === 'number' ? value : Number(value ?? 0);
const numericValue = typeof value === "number" ? value : Number(value ?? 0);
if (!Number.isFinite(numericValue)) {
return 0;