booking cancellation

This commit is contained in:
Marshal
2026-08-15 08:53:24 +00:00
parent 086c4fe8c9
commit c9c2d4dcb3
26 changed files with 751 additions and 163 deletions

View File

@@ -2160,7 +2160,15 @@ export class TrainSchedulingService {
return { ...detail, warnings, deferredBookings };
}
async unassignBooking(scheduleId: string, bookingId: string, userId?: string) {
async unassignBooking(
scheduleId: string,
bookingId: string,
userId?: string,
opts: {
/** false = system detach (e.g. booking cancelled) — no "removed from train, rebook" notice. */
notifyCustomer?: boolean;
} = {},
) {
const schedule = await this.trainSchedulesRepository.findByIdWithFullGraph(scheduleId);
if (!schedule) {
throw new NotFoundException(`Train schedule ${scheduleId} not found`);
@@ -2287,7 +2295,9 @@ export class TrainSchedulingService {
const removedBooking = await this.dataSource
.getRepository(Booking)
.findOne({ where: { id: bookingId }, relations: { company: true } });
if (removedBooking) this.bookingNotifier.removedFromTrain(removedBooking);
if (removedBooking && opts.notifyCustomer !== false) {
this.bookingNotifier.removedFromTrain(removedBooking);
}
this.logger.log(
`Booking ${bookingReference} removed from schedule ${scheduleId} by user ${userId ?? 'unknown'} — customer notified to reschedule or cancel.`,
);
@@ -4409,6 +4419,7 @@ export class TrainSchedulingService {
originStation: true,
destinationStation: true,
scheduleBookings: { booking: true },
shippingLineCompany: true,
},
order: { [sortBy]: sortOrder } as never,
skip,
@@ -6107,6 +6118,10 @@ export class TrainSchedulingService {
origin: schedule.originStation?.label ?? schedule.originStation?.code ?? null,
destination:
schedule.destinationStation?.label ?? schedule.destinationStation?.code ?? null,
// Dedicated shipping-line departure (hidden from customers) — the list
// highlights these rows so staff can tell them apart at a glance.
shippingLineCompanyId: schedule.shippingLineCompanyId ?? null,
shippingLineCompanyName: schedule.shippingLineCompany?.name ?? null,
// Built train (Train Builder) behind this departure, when scheduled by train.
train: schedule.trainSet?.train
? {
@@ -8997,8 +9012,14 @@ export class TrainSchedulingService {
}
const allocRepo = this.dataSource.getRepository(WagonBookingAllocation);
// Cargo type → allowed wagon types rides along: for bulk, the commodity's
// own wagon-type list (the planner's rule) decides, not only the wagon
// type's generic supportedLoadTypes.
const loadAllocations = (trainSetWagonId: string) =>
allocRepo.find({ where: { trainSetWagonId } });
allocRepo.find({
where: { trainSetWagonId },
relations: { booking: { cargoType: { wagonTypes: true } } },
});
const sourceAllocs = await loadAllocations(source.id);
if (!sourceAllocs.length) {
throw new BadRequestException('Source wagon has no load to move');
@@ -9043,10 +9064,25 @@ export class TrainSchedulingService {
slot.physicalWagon?.wagonNumber ?? `#${slot.sequenceNo}`;
const wagonLabel = (slot: TrainSetWagon | null, wagon: Wagon | null) =>
slot ? slotLabel(slot) : (wagon?.wagonNumber ?? 'the target wagon');
// Bulk is allowed on a wagon type when every bulk load's cargo type lists
// it (cargo-type ↔ wagon-type config, same rule the wagon planner uses).
const bulkCargoAllows = (allocs: WagonBookingAllocation[], wagonTypeId?: string) => {
const bulk = allocs.filter((a) => (a.loadType ?? 'CONTAINER').toUpperCase() === 'BULK');
return (
!!wagonTypeId &&
bulk.length > 0 &&
bulk.every((a) =>
(a.booking?.cargoType?.wagonTypes ?? []).some((wt) => wt.id === wagonTypeId),
)
);
};
const checkReceives = (
allocs: WagonBookingAllocation[],
label: string,
wagonType: { code?: string; supportedLoadTypes?: string[]; supportsContainer?: boolean } | null | undefined,
wagonType:
| { id?: string; code?: string; supportedLoadTypes?: string[]; supportsContainer?: boolean }
| null
| undefined,
capacityTons: number,
) => {
const incoming = loadTypesOf(allocs);
@@ -9057,6 +9093,7 @@ export class TrainSchedulingService {
const ok =
supported.includes(loadType) ||
(loadType === 'CONTAINER' && wagonType.supportsContainer) ||
(loadType === 'BULK' && bulkCargoAllows(allocs, wagonType.id)) ||
supported.length === 0;
if (!ok) {
throw new BadRequestException(