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:
Marshal
2026-07-12 10:51:31 +00:00
parent 6695c5448e
commit 4b7f6d2548
108 changed files with 3187 additions and 1368 deletions

View File

@@ -6,11 +6,12 @@ import { Contract } from '../contracts/entities/contract.entity';
import { BookingBatchOffer } from './entities/booking-batch-offer.entity';
/**
* applySplit promotion behaviour: a ONE_TIME contract must be flipped to GENERAL
* (both the parent contract row and the booking's denormalized copy) so the split
* remainder can be rebooked. A GENERAL booking is left untouched.
* 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 ONE_TIME promotion', () => {
describe('BookingSplitService — applySplit split marking', () => {
const bookingId = 'bk-1';
const contractId = 'ct-1';
const offerId = 'of-1';
@@ -34,6 +35,7 @@ describe('BookingSplitService — applySplit ONE_TIME promotion', () => {
id: bookingId,
contractId,
contractKind: bookingContractKind,
cargoTotalWeightVgm: 50,
}),
find: jest.fn().mockResolvedValue([]),
softDelete: jest.fn().mockResolvedValue(undefined),
@@ -76,22 +78,35 @@ describe('BookingSplitService — applySplit ONE_TIME promotion', () => {
return { service, bookingRepo, contractRepo };
};
it('promotes a ONE_TIME booking + parent contract to GENERAL', async () => {
const { service, bookingRepo, contractRepo } = buildService('ONE_TIME');
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({ contractKind: 'GENERAL' }),
);
expect(contractRepo.update).toHaveBeenCalledWith(
contractId,
expect.objectContaining({ contractKind: 'GENERAL' }),
expect.objectContaining({
isSplit: true,
preSplitQuantities: { bulkTons: 50 },
cargoTotalWeightVgm: 30,
wagonsRequired: 3,
}),
);
});
it('leaves a GENERAL booking untouched (no contract promotion)', async () => {
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);