mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-30 08:48:11 +00:00
enhance contract and booking services with server-side search and validation improvements
- Added parameter to and for server-side free-text search on contract reference, company name, and booking details. - Introduced new validation errors in for container clashes and space issues when creating bookings. - Implemented paginated dropdown settings retrieval in . - Updated to fetch active yards using a new method that handles pagination. - Enhanced with a method to fetch all records by walking through pages. - Refactored to support filtering and pagination in schedule listings. - Improved to return a paginated list of facilities. - Updated UI components in and to utilize debounced search inputs for better performance. - Added alerts in to inform users about booking constraints related to splits and capacity. - Enhanced to display notifications for split bookings and capacity usage.
This commit is contained in:
@@ -0,0 +1,140 @@
|
||||
import { ConflictException } from '@nestjs/common';
|
||||
|
||||
import { BookingBatchService } from './booking-batch.service';
|
||||
import { Booking } from '../bookings/entities/booking.entity';
|
||||
|
||||
/**
|
||||
* Export whole-booking single-train gate: an export booking never splits — it
|
||||
* rides one train whole or is rejected. The report must try every fillable
|
||||
* train on the day (first full → use the second), and when none fits, say how
|
||||
* much space is still bookable so the customer knows what he CAN book.
|
||||
*/
|
||||
describe('BookingBatchService — exportSpaceReport (whole-booking, single train)', () => {
|
||||
const DAY = new Date('2026-07-20T10:00:00Z');
|
||||
|
||||
const schedule = (id: string) => ({
|
||||
id,
|
||||
status: 'SCHEDULED',
|
||||
direction: 'EXPORT',
|
||||
scheduledDepartureDate: DAY,
|
||||
bookingWindowStatus: 'OPEN',
|
||||
windowPhase: null, // legacy gate: OPEN alone makes it fillable
|
||||
});
|
||||
|
||||
const fullGraph = (id: string) => ({
|
||||
id,
|
||||
originStationId: 'yard-a',
|
||||
destinationStationId: 'yard-b',
|
||||
routeId: null, // legacy two-stop pseudo-route — no milestone query
|
||||
scheduleBookings: [],
|
||||
trainSet: {
|
||||
locomotive: {
|
||||
maxPullWeightTons: 500,
|
||||
maxTrainLengthMeters: 140,
|
||||
overageToleranceTons: 0,
|
||||
overageToleranceMeters: 0,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const exportBooking = (cargoTons: number) =>
|
||||
({
|
||||
id: 'bk-exp',
|
||||
freightType: 'BULK',
|
||||
tradeDirection: 'EXPORT',
|
||||
scheduledDate: DAY,
|
||||
originYardId: 'yard-a',
|
||||
destinationYardId: 'yard-b',
|
||||
cargoTotalWeightVgm: cargoTons,
|
||||
bookingContainers: [],
|
||||
}) as unknown as Booking;
|
||||
|
||||
// A reserved bulk booking heavy enough to exhaust the 500t pull budget.
|
||||
const heavyReserved = {
|
||||
id: 'bk-heavy',
|
||||
freightType: 'BULK',
|
||||
originYardId: 'yard-a',
|
||||
destinationYardId: 'yard-b',
|
||||
cargoTotalWeightVgm: 476,
|
||||
bookingContainers: [],
|
||||
} as unknown as Booking;
|
||||
|
||||
let service: BookingBatchService;
|
||||
let trainSchedulesRepository: {
|
||||
findAll: jest.Mock;
|
||||
findByIdWithFullGraph: jest.Mock;
|
||||
findById: jest.Mock;
|
||||
};
|
||||
let bookingsRepository: { findReservedForSchedule: jest.Mock };
|
||||
|
||||
beforeEach(() => {
|
||||
trainSchedulesRepository = {
|
||||
findAll: jest.fn().mockResolvedValue([]),
|
||||
findByIdWithFullGraph: jest
|
||||
.fn()
|
||||
.mockImplementation(async (id: string) => fullGraph(id)),
|
||||
findById: jest.fn(),
|
||||
};
|
||||
bookingsRepository = { findReservedForSchedule: jest.fn().mockResolvedValue([]) };
|
||||
|
||||
// WagonType.find() → [] so representative default dims apply (bulk 60t
|
||||
// payload / 23.4t tare / 14m); RouteMilestone is never queried (routeId null).
|
||||
const genericRepo = { find: jest.fn().mockResolvedValue([]) };
|
||||
const dataSource = { getRepository: jest.fn().mockReturnValue(genericRepo) };
|
||||
|
||||
service = new BookingBatchService(
|
||||
dataSource as never,
|
||||
bookingsRepository as never,
|
||||
trainSchedulesRepository as never,
|
||||
{} as never, // trainScheduleBookingsRepository
|
||||
{} as never, // notifier
|
||||
{} as never, // scheduler
|
||||
{} as never, // trainSchedulingService
|
||||
{} as never, // billing
|
||||
{} as never, // bookingWindowGateway
|
||||
{} as never, // pricingService
|
||||
);
|
||||
});
|
||||
|
||||
it('uses the other train when the first one is full', async () => {
|
||||
trainSchedulesRepository.findAll.mockResolvedValue([
|
||||
schedule('train-1'),
|
||||
schedule('train-2'),
|
||||
]);
|
||||
bookingsRepository.findReservedForSchedule.mockImplementation(
|
||||
async (id: string) => (id === 'train-1' ? [heavyReserved] : []),
|
||||
);
|
||||
|
||||
const report = await service.exportSpaceReport(exportBooking(60));
|
||||
|
||||
expect(report.scheduleId).toBe('train-2');
|
||||
});
|
||||
|
||||
it('rejects a booking no single train fits and reports the bookable space', async () => {
|
||||
trainSchedulesRepository.findAll.mockResolvedValue([schedule('train-1')]);
|
||||
|
||||
const report = await service.exportSpaceReport(exportBooking(900));
|
||||
|
||||
expect(report.scheduleId).toBeNull();
|
||||
expect(report.bestAvailable).not.toBeNull();
|
||||
expect(report.bestAvailable!.cargoTons).toBeGreaterThan(0);
|
||||
expect(report.bestAvailable!.cargoTons).toBeLessThan(900);
|
||||
expect(report.fullMessage).toMatch(/largest remaining space is about .* tons/);
|
||||
expect(report.fullMessage).toMatch(/single train whole/);
|
||||
|
||||
await expect(service.pickExportSchedule(exportBooking(900))).rejects.toThrow(
|
||||
ConflictException,
|
||||
);
|
||||
});
|
||||
|
||||
it('says no train is accepting bookings when the day has none', async () => {
|
||||
trainSchedulesRepository.findAll.mockResolvedValue([]);
|
||||
|
||||
const report = await service.exportSpaceReport(exportBooking(60));
|
||||
|
||||
expect(report.scheduleId).toBeNull();
|
||||
expect(report.fullMessage).toBe(
|
||||
'No export train is accepting bookings for this day',
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user