Files
edr-platform/apps/edr-freight-api/src/modules/bookings/booking-transition.accept.spec.ts
Marshal 35cb20da0b feat: implement 20ft container weight-pairing validation
- Added ContainerValidationService to handle 20ft weight-pairing logic.
- Introduced validate20ftWeightPairing utility function to check weight differences.
- Updated BookingPricingService to include overweight line details and pairing errors in price response.
- Enhanced BookingTransitionService to reject submissions with unpairable 20ft containers.
- Created ShipmentValidation interface for pre-submit validation of container contracts.
- Integrated shipment validation into the contract booking process, providing warnings for overweight containers and hard blocks for pairing errors.
- Updated front-end components to display validation results and prevent submission when errors are present.
2026-07-03 09:26:27 +00:00

90 lines
3.1 KiB
TypeScript

import { BadRequestException } from '@nestjs/common';
import { BookingTransitionService } from './booking-transition.service';
/**
* Focused tests for the contract validity window set at the accept step.
* The backoffice must supply a number of days; the window runs from the accept
* moment through accept + N days.
*/
describe('BookingTransitionService — acceptIntake validity window', () => {
const booking = {
id: 'b-1',
status: 'SUBMITTED',
freightType: 'CONTAINER',
cargoTypeId: null,
};
function makeService() {
const bookingsRepository = {
update: jest.fn().mockResolvedValue({ id: 'b-1' }),
};
const bookingsService = {
findById: jest.fn().mockResolvedValue(booking),
};
const ruleEngineService = {
instantiateApprovalSteps: jest.fn().mockResolvedValue([]),
};
const service = new BookingTransitionService(
bookingsRepository as never,
ruleEngineService as never,
{} as never, // pricingService
{} as never, // contractService
{} as never, // invoiceService
{} as never, // filesService
{} as never, // fileUploadSettingsService
{} as never, // bookingBatchService
bookingsService as never,
{ isPhasedGeneralCustomsBooking: () => false } as never,
{} as never,
{ validate20ftPairing: jest.fn().mockResolvedValue([]) } as never,
);
return { service, bookingsRepository, ruleEngineService };
}
it('rejects accept when validity days is missing or non-positive', async () => {
const { service } = makeService();
await expect(
service.acceptIntake('b-1', 'staff-1', 0),
).rejects.toBeInstanceOf(BadRequestException);
await expect(
service.acceptIntake('b-1', 'staff-1', -5),
).rejects.toBeInstanceOf(BadRequestException);
await expect(
service.acceptIntake('b-1', 'staff-1', 1.5),
).rejects.toBeInstanceOf(BadRequestException);
});
it('sets a validity window of validFrom..validFrom + N days', async () => {
const { service, bookingsRepository } = makeService();
await service.acceptIntake('b-1', 'staff-1', 10);
expect(bookingsRepository.update).toHaveBeenCalledTimes(1);
const [id, updates] = bookingsRepository.update.mock.calls[0];
expect(id).toBe('b-1');
expect(updates).toMatchObject({
status: 'PENDING_APPROVAL',
approvedByStaffId: 'staff-1',
contractValidityDays: 10,
});
const from = updates.contractValidFrom as Date;
const until = updates.contractValidUntil as Date;
const diffDays = Math.round(
(until.getTime() - from.getTime()) / (1000 * 60 * 60 * 24),
);
expect(diffDays).toBe(10);
// The accept timestamp and the validity start are the same moment.
expect((updates.approvedByStaffAt as Date).getTime()).toBe(from.getTime());
});
it('instantiates the approval chain when accepting', async () => {
const { service, ruleEngineService } = makeService();
await service.acceptIntake('b-1', 'staff-1', 30);
expect(ruleEngineService.instantiateApprovalSteps).toHaveBeenCalledWith(
'b-1',
expect.objectContaining({ freightType: 'CONTAINER' }),
);
});
});