Files
edr-platform/apps/edr-freight-api/src/modules/train-scheduling/booking-split.service.spec.ts
Marshal 4b7f6d2548 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.
2026-07-12 10:51:31 +00:00

117 lines
3.9 KiB
TypeScript

import { BookingSplitService } from './booking-split.service';
import { Booking } from '../bookings/entities/booking.entity';
import { BookingContainer } from '../bookings/entities/booking-container.entity';
import { BookingContainerUnit } from '../bookings/entities/booking-container-unit.entity';
import { Contract } from '../contracts/entities/contract.entity';
import { BookingBatchOffer } from './entities/booking-batch-offer.entity';
/**
* applySplit split-marking behaviour: the reduced booking is flagged is_split
* and keeps a pre_split_quantities snapshot (the remainder ledger for ONE_TIME
* contracts). The contract kind is NEVER changed — a ONE_TIME contract stays
* ONE_TIME through the split chain.
*/
describe('BookingSplitService — applySplit split marking', () => {
const bookingId = 'bk-1';
const contractId = 'ct-1';
const offerId = 'of-1';
const buildService = (bookingContractKind: 'ONE_TIME' | 'GENERAL') => {
const offer = {
id: offerId,
bookingId,
status: 'OFFERED',
offeredWagons: 3,
totalWagons: 5,
offeredWeightTons: 30,
offeredAmount: 300,
offeredPricingBreakdown: {},
offeredLines: null,
} as unknown as BookingBatchOffer;
const bookingRepo = {
update: jest.fn().mockResolvedValue(undefined),
findOne: jest.fn().mockResolvedValue({
id: bookingId,
contractId,
contractKind: bookingContractKind,
cargoTotalWeightVgm: 50,
}),
find: jest.fn().mockResolvedValue([]),
softDelete: jest.fn().mockResolvedValue(undefined),
};
const contractRepo = { update: jest.fn().mockResolvedValue(undefined) };
const offerRepo = {
findOne: jest.fn().mockResolvedValue(offer),
update: jest.fn().mockResolvedValue(undefined),
};
const containerRepo = {
find: jest.fn().mockResolvedValue([]),
update: jest.fn(),
softDelete: jest.fn(),
};
const unitRepo = { find: jest.fn().mockResolvedValue([]), softDelete: jest.fn() };
const repoFor = (entity: unknown) => {
if (entity === Booking) return bookingRepo;
if (entity === Contract) return contractRepo;
if (entity === BookingBatchOffer) return offerRepo;
if (entity === BookingContainer) return containerRepo;
if (entity === BookingContainerUnit) return unitRepo;
return { find: jest.fn().mockResolvedValue([]), update: jest.fn() };
};
const dataSource = {
getRepository: jest.fn(repoFor),
transaction: jest.fn(async (fn: (m: unknown) => Promise<void>) => {
await fn({ getRepository: repoFor });
}),
};
const service = new BookingSplitService(
dataSource as never,
{} as never,
{} as never,
{ expirePayable: jest.fn() } as never,
{ payNowPartial: jest.fn() } as never,
);
return { service, bookingRepo, contractRepo };
};
it('flags the reduced booking is_split with a pre-split bulk snapshot', async () => {
const { service, bookingRepo } = buildService('ONE_TIME');
await service.applySplit(bookingId);
expect(bookingRepo.update).toHaveBeenCalledWith(
bookingId,
expect.objectContaining({
isSplit: true,
preSplitQuantities: { bulkTons: 50 },
cargoTotalWeightVgm: 30,
wagonsRequired: 3,
}),
);
});
it('never changes the contract kind — ONE_TIME stays ONE_TIME', async () => {
const { service, bookingRepo, contractRepo } = buildService('ONE_TIME');
await service.applySplit(bookingId);
expect(contractRepo.update).not.toHaveBeenCalled();
expect(bookingRepo.update).not.toHaveBeenCalledWith(
bookingId,
expect.objectContaining({ contractKind: expect.anything() }),
);
});
it('leaves a GENERAL contract untouched too', async () => {
const { service, contractRepo } = buildService('GENERAL');
await service.applySplit(bookingId);
expect(contractRepo.update).not.toHaveBeenCalled();
});
});