feat: implement shipping line booking completion functionality

- Added ShippingLineBookingCompletionController and associated service to handle the completion of shipping line bookings.
- Introduced a new module for booking completion to maintain module separation and avoid cyclic dependencies.
- Updated the train scheduling global rules to set default desk hours to 24 hours.
- Modified existing services and entities to accommodate the new booking completion logic.
- Enhanced the front-end components to support the new booking completion flow, including updates to the booking detail and bookings pages.
- Implemented validation and error handling for booking completion, ensuring that only approved bookings can be completed.
- Added migration to set default desk hours in the database.
This commit is contained in:
Marshal
2026-08-13 15:10:27 +00:00
parent 5837ac7b6e
commit 0e00a98ef3
18 changed files with 681 additions and 389 deletions

View File

@@ -944,6 +944,15 @@ export class BookingTransitionService {
bookingId: string,
scheduledDate: string,
requestedTrainScheduleId?: string | null,
opts?: {
/**
* Skip the customer day-pool departure/compatibility gate. Used ONLY by
* the shipping-line completion path, which has already validated the day
* against the line's own dedicated train (those trains are excluded from
* the customer pools, so the gate here would wrongly reject them).
*/
bypassDayPool?: boolean;
},
): Promise<Booking> {
const booking = await this.bookingsService.findById(bookingId);
assertBookingStatus(booking, [
@@ -971,20 +980,22 @@ export class BookingTransitionService {
// gate; quantity never blocks — oversized bookings get a partial split
// offer). The batch engine assigns the specific train within that
// (route, day) pool later.
const { hasDeparture, hasCompatible } =
await this.bookingsService.checkDayCompatibilityForBooking(
booking,
eatDay(date),
);
if (!hasDeparture) {
throw new BadRequestException(
"No departures available on the selected day for this route",
);
}
if (!hasCompatible) {
throw new BadRequestException(
"No wagon on the selected day can carry this cargo type — please choose another day",
);
if (!opts?.bypassDayPool) {
const { hasDeparture, hasCompatible } =
await this.bookingsService.checkDayCompatibilityForBooking(
booking,
eatDay(date),
);
if (!hasDeparture) {
throw new BadRequestException(
"No departures available on the selected day for this route",
);
}
if (!hasCompatible) {
throw new BadRequestException(
"No wagon on the selected day can carry this cargo type — please choose another day",
);
}
}
// Export is FCFS and never splits — a booking must ride one train whole. So