mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-30 13:28:11 +00:00
add detail batch and allocation monitoring page
This commit is contained in:
@@ -26,9 +26,11 @@ 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 { WagonAllocationContainerItem } from '../train-schedules/entities/wagon-allocation-container-item.entity';
|
||||
import { WagonBookingAllocation } from '../train-schedules/entities/wagon-booking-allocation.entity';
|
||||
import { TrainScheduleBookingsRepository } from '../train-schedules/train-schedule-bookings.repository';
|
||||
import { TrainSchedulesRepository } from '../train-schedules/train-schedules.repository';
|
||||
import { TrainCompositionRemovalLogRepository } from '../train-schedules/train-composition-removal-log.repository';
|
||||
import { WagonAllocationBulkLoadsRepository } from '../train-schedules/wagon-allocation-bulk-loads.repository';
|
||||
import { WagonAllocationContainerItemsRepository } from '../train-schedules/wagon-allocation-container-items.repository';
|
||||
import { WagonBookingAllocationsRepository } from '../train-schedules/wagon-booking-allocations.repository';
|
||||
@@ -41,6 +43,7 @@ import { GetEligibleBookingsDto } from './dto/get-eligible-bookings.dto';
|
||||
import { GetEligibleBulkBookingsDto } from './dto/get-eligible-bulk-bookings.dto';
|
||||
import { GetEligibleContainerBookingsDto } from './dto/get-eligible-container-bookings.dto';
|
||||
import { PinWagonsDto } from './dto/pin-wagons.dto';
|
||||
import { UpdateContainerItemDto } from './dto/update-container-item.dto';
|
||||
import { PreviewBulkTrainScheduleDto } from './dto/preview-bulk-train-schedule.dto';
|
||||
import { PreviewContainerTrainScheduleDto } from './dto/preview-container-train-schedule.dto';
|
||||
import { PreviewTrainScheduleDto } from './dto/preview-train-schedule.dto';
|
||||
@@ -143,6 +146,7 @@ export class TrainSchedulingService {
|
||||
private readonly wagonAllocationContainerItemsRepository: WagonAllocationContainerItemsRepository,
|
||||
private readonly wagonAllocationBulkLoadsRepository: WagonAllocationBulkLoadsRepository,
|
||||
private readonly trainCheckpointEventsRepository: TrainCheckpointEventsRepository,
|
||||
private readonly trainCompositionRemovalLogRepository: TrainCompositionRemovalLogRepository,
|
||||
private readonly configService?: ConfigService,
|
||||
) {}
|
||||
|
||||
@@ -467,7 +471,7 @@ export class TrainSchedulingService {
|
||||
return { ...detail, warnings, deferredBookings };
|
||||
}
|
||||
|
||||
async unassignBooking(scheduleId: string, bookingId: string) {
|
||||
async unassignBooking(scheduleId: string, bookingId: string, userId?: string) {
|
||||
const schedule = await this.trainSchedulesRepository.findByIdWithFullGraph(scheduleId);
|
||||
if (!schedule) {
|
||||
throw new NotFoundException(`Train schedule ${scheduleId} not found`);
|
||||
@@ -481,6 +485,9 @@ export class TrainSchedulingService {
|
||||
throw new NotFoundException(`Booking ${bookingId} is not assigned to this schedule`);
|
||||
}
|
||||
|
||||
const booking = await this.bookingsRepository.findById(bookingId);
|
||||
const bookingReference = booking?.reference ?? null;
|
||||
|
||||
await this.dataSource.transaction(async (manager) => {
|
||||
const allocationIds = (schedule.trainSet?.wagons ?? [])
|
||||
.flatMap((w) => w.allocations ?? [])
|
||||
@@ -529,6 +536,18 @@ export class TrainSchedulingService {
|
||||
}
|
||||
});
|
||||
|
||||
await this.trainCompositionRemovalLogRepository.create({
|
||||
scheduleId,
|
||||
bookingId,
|
||||
bookingReference,
|
||||
removedByUserId: userId ?? null,
|
||||
removedAt: new Date(),
|
||||
});
|
||||
|
||||
console.log(
|
||||
`[NOTIFY] Booking ${bookingReference} removed from schedule ${scheduleId} by user ${userId ?? 'unknown'} — customer should be notified to reschedule or cancel.`,
|
||||
);
|
||||
|
||||
return this.getTrainScheduleById(scheduleId);
|
||||
}
|
||||
|
||||
@@ -2236,6 +2255,103 @@ export class TrainSchedulingService {
|
||||
return result;
|
||||
}
|
||||
|
||||
async removeTrainSetWagonSlot(scheduleId: string, trainSetWagonId: string): Promise<any> {
|
||||
const schedule = await this.trainSchedulesRepository.findByIdWithFullGraph(scheduleId);
|
||||
if (!schedule) {
|
||||
throw new NotFoundException(`Train schedule ${scheduleId} not found`);
|
||||
}
|
||||
if (!['DRAFT', 'SCHEDULED'].includes(schedule.status)) {
|
||||
throw new BadRequestException('Cannot remove wagon slots from a finalized or dispatched schedule');
|
||||
}
|
||||
|
||||
const wagon = (schedule.trainSet?.wagons ?? []).find((w) => w.id === trainSetWagonId);
|
||||
if (!wagon) {
|
||||
throw new NotFoundException(`Train set wagon ${trainSetWagonId} not found in this schedule`);
|
||||
}
|
||||
|
||||
if ((wagon.allocations ?? []).length > 0) {
|
||||
throw new BadRequestException(
|
||||
'Cannot remove a wagon slot that has active allocations; remove the booking first',
|
||||
);
|
||||
}
|
||||
|
||||
await this.dataSource.transaction(async (manager) => {
|
||||
await manager.getRepository(TrainSetWagon).delete(trainSetWagonId);
|
||||
await manager.getRepository(TrainSet).update(schedule.trainSetId, {
|
||||
wagonCount: Math.max(0, (schedule.trainSet?.wagonCount ?? 0) - 1),
|
||||
totalLengthMeters: Math.max(0, (schedule.trainSet?.totalLengthMeters ?? 0) - (wagon.lengthMeters ?? 0)),
|
||||
});
|
||||
});
|
||||
|
||||
return this.getTrainScheduleById(scheduleId);
|
||||
}
|
||||
|
||||
async updateContainerItem(
|
||||
scheduleId: string,
|
||||
itemId: string,
|
||||
dto: UpdateContainerItemDto,
|
||||
): Promise<{ id: string; containerNumber: string | null }> {
|
||||
const schedule = await this.trainSchedulesRepository.findByIdWithFullGraph(scheduleId);
|
||||
if (!schedule) {
|
||||
throw new NotFoundException(`Train schedule ${scheduleId} not found`);
|
||||
}
|
||||
if (schedule.status === 'DISPATCHED') {
|
||||
throw new BadRequestException('Cannot edit a dispatched schedule');
|
||||
}
|
||||
|
||||
const item = await this.dataSource.getRepository(WagonAllocationContainerItem).findOne({
|
||||
where: { id: itemId },
|
||||
relations: ['wagonBookingAllocation', 'wagonBookingAllocation.trainSetWagon'],
|
||||
});
|
||||
|
||||
if (!item) {
|
||||
throw new NotFoundException(`Container item ${itemId} not found`);
|
||||
}
|
||||
|
||||
const wagonId = item.wagonBookingAllocationId;
|
||||
const wagonAllocation = await this.dataSource.getRepository(WagonBookingAllocation).findOne({
|
||||
where: { id: wagonId },
|
||||
relations: ['trainSetWagon'],
|
||||
});
|
||||
|
||||
if (!wagonAllocation?.trainSetWagon) {
|
||||
throw new NotFoundException(`Container item ${itemId} does not belong to this schedule`);
|
||||
}
|
||||
|
||||
const trainSetWagonId = wagonAllocation.trainSetWagon.id;
|
||||
const wagonIds = (schedule.trainSet?.wagons ?? []).map((w) => w.id);
|
||||
if (!wagonIds.includes(trainSetWagonId)) {
|
||||
throw new NotFoundException(`Container item ${itemId} does not belong to this schedule`);
|
||||
}
|
||||
|
||||
await this.dataSource.getRepository(WagonAllocationContainerItem).update(itemId, {
|
||||
containerNumber: dto.containerNumber ?? null,
|
||||
});
|
||||
|
||||
return { id: itemId, containerNumber: dto.containerNumber ?? null };
|
||||
}
|
||||
|
||||
async getUnassignedBookings(scheduleId: string): Promise<any[]> {
|
||||
const schedule = await this.trainSchedulesRepository.findByIdWithFullGraph(scheduleId);
|
||||
if (!schedule) {
|
||||
throw new NotFoundException(`Train schedule ${scheduleId} not found`);
|
||||
}
|
||||
|
||||
const allBookings = await this.bookingsRepository.findAll({
|
||||
where: { trainScheduleId: scheduleId },
|
||||
select: ['id', 'reference', 'freightType', 'priorityScore', 'cargoTotalWeightVgm', 'status', 'schedulingStatus'],
|
||||
});
|
||||
|
||||
const allocatedBookingIds = await this.getWagonAssignedBookingIds(scheduleId);
|
||||
|
||||
const unassigned = allBookings.filter((b: any) => !allocatedBookingIds.has(b.id));
|
||||
return unassigned.sort((a: any, b: any) => (b.priorityScore ?? 0) - (a.priorityScore ?? 0));
|
||||
}
|
||||
|
||||
async getCompositionRemovals(scheduleId: string): Promise<any[]> {
|
||||
return this.trainCompositionRemovalLogRepository.findByScheduleId(scheduleId);
|
||||
}
|
||||
|
||||
private async getWagonAssignedBookingIds(scheduleId: string): Promise<Set<string>> {
|
||||
const schedule = await this.trainSchedulesRepository.findByIdWithFullGraph(scheduleId);
|
||||
const wagonIds = (schedule?.trainSet?.wagons ?? []).map((w) => w.id);
|
||||
|
||||
Reference in New Issue
Block a user