mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-30 12:18:11 +00:00
- 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.
119 lines
4.7 KiB
TypeScript
119 lines
4.7 KiB
TypeScript
import { BadRequestException } from '@nestjs/common';
|
|
import { BookingTransitionService } from './booking-transition.service';
|
|
|
|
/**
|
|
* Operation-request review for general-contract drawdown orders:
|
|
* - ACCEPT a train order → FULLY_EXECUTED with the invoice ensured; import/
|
|
* domestic bookings wait for their booking-day window cycle (no immediate
|
|
* batch enqueue at accept time).
|
|
* - ACCEPT a road order → ROAD_DISPATCH_PENDING, never enters the train batch.
|
|
* - REQUEST_CHANGES requires a note → OPERATION_CHANGES_REQUESTED.
|
|
*/
|
|
describe('BookingTransitionService — operation review', () => {
|
|
function makeService(serviceTypeCode: string) {
|
|
const booking = {
|
|
id: 'b-1',
|
|
reference: 'BKG-1',
|
|
status: 'OPERATION_REQUEST_PENDING',
|
|
originYardId: 'o-1',
|
|
destinationYardId: 'd-1',
|
|
scheduledDate: new Date('2026-07-01T00:00:00.000Z'),
|
|
serviceType: { code: serviceTypeCode },
|
|
};
|
|
const bookingsRepository = {
|
|
update: jest.fn().mockResolvedValue({ id: 'b-1' }),
|
|
createReviewNote: jest.fn().mockResolvedValue(undefined),
|
|
};
|
|
const bookingsService = {
|
|
findById: jest.fn().mockResolvedValue(booking),
|
|
};
|
|
const bookingBatchService = {
|
|
enqueueRouteDayProcessing: jest.fn(),
|
|
pickExportSchedule: jest.fn(),
|
|
acceptExportBooking: jest.fn(),
|
|
};
|
|
const invoiceService = {
|
|
ensureInvoiceForBooking: jest
|
|
.fn()
|
|
.mockResolvedValue({ id: 'inv-1', invoiceNumber: 'INV-0001' }),
|
|
updateStatus: jest.fn().mockResolvedValue(undefined),
|
|
};
|
|
|
|
const service = new BookingTransitionService(
|
|
bookingsRepository as never,
|
|
{} as never, // ruleEngineService
|
|
{} as never, // pricingService
|
|
{} as never, // contractService
|
|
{} as never, // filesService
|
|
{} as never, // fileUploadSettingsService
|
|
bookingBatchService as never,
|
|
bookingsService as never,
|
|
{ isPhasedGeneralCustomsBooking: () => false } as never,
|
|
{} as never, // workflowService
|
|
invoiceService as never,
|
|
{ validate20ftPairing: jest.fn().mockResolvedValue([]) } as never,
|
|
{
|
|
accepted: jest.fn(),
|
|
approved: jest.fn(),
|
|
rejected: jest.fn(),
|
|
changesRequested: jest.fn(),
|
|
documentQueried: jest.fn(),
|
|
clearanceReady: jest.fn(),
|
|
operationChangesRequested: jest.fn(),
|
|
operationAccepted: jest.fn(),
|
|
inTransit: jest.fn(),
|
|
completed: jest.fn(),
|
|
cancelled: jest.fn(),
|
|
submittedToStaff: jest.fn(),
|
|
customerSignedToStaff: jest.fn(),
|
|
operationRequestedToStaff: jest.fn(),
|
|
clearanceDocsUploadedToStaff: jest.fn(),
|
|
dutySlipUploadedToStaff: jest.fn(),
|
|
} as never, // notifier
|
|
);
|
|
return { service, bookingsRepository, bookingBatchService, invoiceService };
|
|
}
|
|
|
|
it('ACCEPT of a train order → FULLY_EXECUTED, invoice ensured, batch waits for window cycle', async () => {
|
|
const { service, bookingsRepository, bookingBatchService, invoiceService } =
|
|
makeService('RAIL_CONTAINER');
|
|
await service.reviewOperationRequest('b-1', 'ACCEPT', 'staff-1');
|
|
expect(bookingsRepository.update).toHaveBeenCalledWith(
|
|
'b-1',
|
|
expect.objectContaining({ status: 'FULLY_EXECUTED' }),
|
|
);
|
|
expect(invoiceService.ensureInvoiceForBooking).toHaveBeenCalledTimes(1);
|
|
// Import/domestic train bookings are batched by the window cycle later —
|
|
// never enqueued directly at accept time.
|
|
expect(bookingBatchService.enqueueRouteDayProcessing).not.toHaveBeenCalled();
|
|
expect(bookingBatchService.acceptExportBooking).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('ACCEPT of a road order → ROAD_DISPATCH_PENDING and does NOT enter the batch', async () => {
|
|
const { service, bookingsRepository, bookingBatchService, invoiceService } =
|
|
makeService('ROAD_CONTAINER');
|
|
await service.reviewOperationRequest('b-1', 'ACCEPT', 'staff-1');
|
|
expect(bookingsRepository.update).toHaveBeenCalledWith(
|
|
'b-1',
|
|
expect.objectContaining({ status: 'ROAD_DISPATCH_PENDING' }),
|
|
);
|
|
expect(invoiceService.ensureInvoiceForBooking).toHaveBeenCalledTimes(1);
|
|
expect(bookingBatchService.enqueueRouteDayProcessing).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('REQUEST_CHANGES requires a note → OPERATION_CHANGES_REQUESTED', async () => {
|
|
const { service, bookingsRepository } = makeService('RAIL_CONTAINER');
|
|
await expect(
|
|
service.reviewOperationRequest('b-1', 'REQUEST_CHANGES', 'staff-1', {}),
|
|
).rejects.toBeInstanceOf(BadRequestException);
|
|
|
|
await service.reviewOperationRequest('b-1', 'REQUEST_CHANGES', 'staff-1', {
|
|
note: 'Fix the schedule',
|
|
});
|
|
expect(bookingsRepository.update).toHaveBeenCalledWith(
|
|
'b-1',
|
|
expect.objectContaining({ status: 'OPERATION_CHANGES_REQUESTED' }),
|
|
);
|
|
});
|
|
});
|