mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-30 05:18:11 +00:00
enhance booking and contract notification systems
- Added detailed logging for socket connection events in useBookingWindowSocket. - Introduced new notification types for contract status and schedule updates. - Updated notification visuals to include new icons for contract status. - Enhanced notification href resolution for contract status and schedule updates. - Implemented booking lifecycle notifier service for customer and staff notifications. - Created contract notifier service for managing contract lifecycle notifications. - Added end-to-end tests for booking window socket functionality.
This commit is contained in:
@@ -12,6 +12,7 @@ import {
|
||||
Injectable,
|
||||
Logger,
|
||||
NotFoundException,
|
||||
Optional,
|
||||
} from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { InjectDataSource } from '@nestjs/typeorm';
|
||||
@@ -21,6 +22,7 @@ import { BookingsRepository } from '../bookings/bookings.repository';
|
||||
import { Booking } from '../bookings/entities/booking.entity';
|
||||
import { BookingContainer } from '../bookings/entities/booking-container.entity';
|
||||
import { ClearanceMilestone } from '../contracts/entities/clearance-milestone.entity';
|
||||
import { ClearanceMilestoneService } from '../contracts/clearance-milestone.service';
|
||||
import { Container } from '../container-management/entities/container.entity';
|
||||
import { Locomotive } from '../locomotives/entities/locomotive.entity';
|
||||
import { LocomotivesRepository } from '../locomotives/locomotives.repository';
|
||||
@@ -274,9 +276,45 @@ export class TrainSchedulingService {
|
||||
private readonly warehouseInventoryService: WarehouseInventoryService,
|
||||
private readonly pdfDocuments: WarehouseReleaseDocumentService,
|
||||
private readonly bookingWindowGateway: BookingWindowGateway,
|
||||
@Optional() private readonly milestoneService?: ClearanceMilestoneService,
|
||||
private readonly configService?: ConfigService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Complete customer-tracking clearance milestones for every booking on a
|
||||
* schedule when a physical lifecycle event fires (dispatch, arrive, load,
|
||||
* unload, gatepass). Uses the doc-trigger path, which is a silent no-op for
|
||||
* bookings without milestone rows (non-customs bookings), so this is safe to
|
||||
* call for every direction and flow. Never blocks the operational action.
|
||||
*/
|
||||
private async completeMilestonesForScheduleBookings(
|
||||
scheduleId: string,
|
||||
codes: string[],
|
||||
): Promise<void> {
|
||||
if (!this.milestoneService || codes.length === 0) return;
|
||||
try {
|
||||
const rows: Array<{ booking_id: string }> = await this.dataSource.query(
|
||||
`SELECT tsb.booking_id
|
||||
FROM freight.train_schedule_bookings tsb
|
||||
WHERE tsb.train_schedule_id = $1
|
||||
AND tsb.deleted_at IS NULL`,
|
||||
[scheduleId],
|
||||
);
|
||||
for (const { booking_id } of rows) {
|
||||
for (const code of codes) {
|
||||
await this.milestoneService.completeByDocTrigger(
|
||||
{ bookingId: booking_id },
|
||||
code,
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
this.logger.warn(
|
||||
`Milestone completion (${codes.join(', ')}) failed for schedule ${scheduleId}: ${(err as Error).message}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Push a schedule's current booking-window state over the socket so the
|
||||
* portal home card and backoffice GL/batch views update in real time —
|
||||
@@ -1121,26 +1159,32 @@ export class TrainSchedulingService {
|
||||
{ country: schedule.destinationCountry },
|
||||
);
|
||||
if (direction === 'IMPORT') {
|
||||
const result = await this.warehouseInventoryService.autoUnloadArrivedBookings(
|
||||
scheduleId,
|
||||
'SYSTEM_TRAIN_ARRIVAL',
|
||||
);
|
||||
// Customer tracking: cargo is off the train at the destination yard.
|
||||
void this.completeMilestonesForScheduleBookings(scheduleId, ['OFFLOADED']);
|
||||
return {
|
||||
direction,
|
||||
action: 'IMPORT_AUTO_UNLOAD',
|
||||
status: 'COMPLETED',
|
||||
result: await this.warehouseInventoryService.autoUnloadArrivedBookings(
|
||||
scheduleId,
|
||||
'SYSTEM_TRAIN_ARRIVAL',
|
||||
),
|
||||
result,
|
||||
};
|
||||
}
|
||||
|
||||
if (direction === 'EXPORT' && this.isDjiboutiPortDestination(`${schedule.destinationCode ?? ''} ${schedule.destinationName ?? ''}`)) {
|
||||
const result = await this.warehouseInventoryService.autoUnloadExportAtDjibouti(
|
||||
scheduleId,
|
||||
'SYSTEM_TRAIN_ARRIVAL',
|
||||
);
|
||||
// Customer tracking: cargo is off the train at the Djibouti port.
|
||||
void this.completeMilestonesForScheduleBookings(scheduleId, ['OFFLOADED']);
|
||||
return {
|
||||
direction,
|
||||
action: 'EXPORT_DJIBOUTI_AUTO_UNLOAD',
|
||||
status: 'COMPLETED',
|
||||
result: await this.warehouseInventoryService.autoUnloadExportAtDjibouti(
|
||||
scheduleId,
|
||||
'SYSTEM_TRAIN_ARRIVAL',
|
||||
),
|
||||
result,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1443,6 +1487,20 @@ export class TrainSchedulingService {
|
||||
|
||||
// Dispatch closed the window — drop it from portal/GL cards right away.
|
||||
void this.emitWindowState(scheduleId);
|
||||
// Customer tracking: cargo is on the departing train — loading milestones
|
||||
// plus the direction's "departed" handoff milestone.
|
||||
if (schedule.direction === 'IMPORT' || schedule.direction === 'EXPORT') {
|
||||
void this.completeMilestonesForScheduleBookings(scheduleId, [
|
||||
// CARGO_ARRIVED is export-only (cargo reached the origin yard) — the
|
||||
// doc-trigger path no-ops it for import bookings.
|
||||
'CARGO_ARRIVED',
|
||||
'READY_FOR_LOADING',
|
||||
'LOADED',
|
||||
schedule.direction === 'IMPORT'
|
||||
? 'DEPARTED_FROM_DJIBOUTI'
|
||||
: 'DEPARTED_TO_DJIBOUTI',
|
||||
]);
|
||||
}
|
||||
return this.getTrainScheduleById(scheduleId);
|
||||
}
|
||||
|
||||
@@ -1599,6 +1657,13 @@ export class TrainSchedulingService {
|
||||
LoadingStatus.Loaded,
|
||||
);
|
||||
}
|
||||
// Customer tracking: staff confirmed cargo is on the wagons (CARGO_ARRIVED
|
||||
// is the export-side "cargo reached origin yard" step that precedes it).
|
||||
void this.completeMilestonesForScheduleBookings(scheduleId, [
|
||||
'CARGO_ARRIVED',
|
||||
'READY_FOR_LOADING',
|
||||
'LOADED',
|
||||
]);
|
||||
return this.getTrainScheduleById(scheduleId);
|
||||
}
|
||||
|
||||
@@ -2417,6 +2482,13 @@ export class TrainSchedulingService {
|
||||
}
|
||||
});
|
||||
|
||||
// Customer tracking: the train reached the corridor's far end.
|
||||
if (schedule.direction === 'IMPORT' || schedule.direction === 'EXPORT') {
|
||||
void this.completeMilestonesForScheduleBookings(scheduleId, [
|
||||
schedule.direction === 'IMPORT' ? 'ARRIVED_ETHIOPIA' : 'ARRIVED_AT_DJIBOUTI',
|
||||
]);
|
||||
}
|
||||
|
||||
const detail = await this.getTrainScheduleById(scheduleId);
|
||||
const warehouseAutomation = await this.runWarehouseArrivalAutomation(scheduleId);
|
||||
return Object.assign(detail, { warehouseAutomation });
|
||||
|
||||
Reference in New Issue
Block a user