Files
edr-platform/apps/edr-freight-api/src/modules/contract-templates/bulk-template-direction.spec.ts
Marshal b926a3116e feat: enhance train scheduling and contract management features
- Added StationWorkControls to manage loading/unloading phases in TrainScheduleV2DetailPage.
- Implemented API endpoints for recording station work and managing wagon detach requests.
- Updated contract templates to include Ethiopian customs handling options.
- Enhanced shipment forms to collect customs clearing agent details for without-customs bookings.
- Introduced NUMBER_OF_WAGONS as a unit of measure for bulk cargo, allowing customers to specify wagon counts.
- Improved validation for customs clearing agent information in shipment forms.
- Updated various components and services to accommodate new features and ensure data integrity.
2026-08-25 21:44:21 +00:00

187 lines
6.4 KiB
TypeScript

import { BadRequestException } from '@nestjs/common';
import { ContractTemplatesRepository } from './contract-templates.repository';
import { ContractTemplatesService } from './contract-templates.service';
import {
bulkTemplateCode,
bulkTemplateDirectionFor,
} from './entities/contract-template.entity';
describe('bulkTemplateDirectionFor', () => {
it('maps the contract-side DOMESTIC onto the template-side INTERCITY', () => {
expect(bulkTemplateDirectionFor('DOMESTIC')).toBe('INTERCITY');
expect(bulkTemplateDirectionFor('INTERCITY')).toBe('INTERCITY');
expect(bulkTemplateDirectionFor(null)).toBe('INTERCITY');
expect(bulkTemplateDirectionFor(undefined)).toBe('INTERCITY');
expect(bulkTemplateDirectionFor('IMPORT')).toBe('IMPORT');
expect(bulkTemplateDirectionFor('EXPORT')).toBe('EXPORT');
});
});
describe('bulkTemplateCode', () => {
it('gives each direction/customs combination its own code', () => {
expect(bulkTemplateCode('STEEL', 'IMPORT', true)).toBe('BULK_IMPORT_STEEL_CUSTOMS');
expect(bulkTemplateCode('STEEL', 'IMPORT', false)).toBe(
'BULK_IMPORT_STEEL_NO_CUSTOMS',
);
expect(bulkTemplateCode('STEEL', 'EXPORT', true)).toBe('BULK_EXPORT_STEEL_CUSTOMS');
expect(bulkTemplateCode('STEEL', 'EXPORT', false)).toBe(
'BULK_EXPORT_STEEL_NO_CUSTOMS',
);
});
it('leaves intercity unsuffixed — it crosses no border', () => {
expect(bulkTemplateCode('STEEL', 'INTERCITY', null)).toBe('BULK_INTERCITY_STEEL');
});
it('gives the Ethiopian-customs-only variant its own suffix', () => {
expect(bulkTemplateCode('STEEL', 'IMPORT', true, true)).toBe(
'BULK_IMPORT_STEEL_ETHIOPIAN_CUSTOMS',
);
// The flag is meaningless without customs clearing.
expect(bulkTemplateCode('STEEL', 'IMPORT', false, true)).toBe(
'BULK_IMPORT_STEEL_NO_CUSTOMS',
);
});
it('produces 7 distinct codes per cargo type', () => {
const codes = [
bulkTemplateCode('STEEL', 'IMPORT', true),
bulkTemplateCode('STEEL', 'IMPORT', true, true),
bulkTemplateCode('STEEL', 'IMPORT', false),
bulkTemplateCode('STEEL', 'EXPORT', true),
bulkTemplateCode('STEEL', 'EXPORT', true, true),
bulkTemplateCode('STEEL', 'EXPORT', false),
bulkTemplateCode('STEEL', 'INTERCITY', null),
];
expect(new Set(codes).size).toBe(7);
});
});
describe('ContractTemplatesService bulk create/resolve', () => {
function build() {
const repository = {
findCargoType: jest.fn(() =>
Promise.resolve({
id: 'cargo-1',
code: 'STEEL',
cargoTypeName: 'Steel',
hasContractTemplate: true,
}),
),
findByCargoCombo: jest.fn(() => Promise.resolve(null)),
findActiveBulkTemplate: jest.fn(() => Promise.resolve(null)),
findByCode: jest.fn(() => Promise.resolve(null)),
saveTemplate: jest.fn((template) => Promise.resolve(template)),
} as unknown as ContractTemplatesRepository;
return {
repository,
service: new ContractTemplatesService(repository, {} as never),
};
}
it('stores direction and customs on an import template', async () => {
const { service } = build();
const created = await service.create({
cargoTypeId: 'cargo-1',
tradeDirection: 'EXPORT',
withCustoms: true,
});
expect(created.code).toBe('BULK_EXPORT_STEEL_CUSTOMS');
expect(created.tradeDirection).toBe('EXPORT');
expect(created.withCustoms).toBe(true);
expect(created.documentTitle).toBe(
'Steel Transportation and Customs Clearance Services',
);
});
it('stores a null customs flag for intercity', async () => {
const { service } = build();
const created = await service.create({
cargoTypeId: 'cargo-1',
tradeDirection: 'INTERCITY',
});
expect(created.code).toBe('BULK_INTERCITY_STEEL');
expect(created.withCustoms).toBeNull();
expect(created.documentTitle).toBe('Steel Transportation Services');
});
it('rejects a customs flag on intercity', async () => {
const { service } = build();
await expect(
service.create({
cargoTypeId: 'cargo-1',
tradeDirection: 'INTERCITY',
withCustoms: false,
}),
).rejects.toBeInstanceOf(BadRequestException);
});
it('requires a customs flag on import and export', async () => {
const { service } = build();
await expect(
service.create({ cargoTypeId: 'cargo-1', tradeDirection: 'IMPORT' }),
).rejects.toBeInstanceOf(BadRequestException);
});
it('creates the Ethiopian-customs-only variant alongside the full-customs one', async () => {
const { service } = build();
const created = await service.create({
cargoTypeId: 'cargo-1',
tradeDirection: 'IMPORT',
withCustoms: true,
ethiopianCustomsOnly: true,
});
expect(created.code).toBe('BULK_IMPORT_STEEL_ETHIOPIAN_CUSTOMS');
expect(created.ethiopianCustomsOnly).toBe(true);
expect(created.documentTitle).toBe(
'Steel Transportation and Ethiopian Customs Clearance Services',
);
});
it('rejects Ethiopian-customs-only without customs clearing', async () => {
const { service } = build();
await expect(
service.create({
cargoTypeId: 'cargo-1',
tradeDirection: 'IMPORT',
withCustoms: false,
ethiopianCustomsOnly: true,
}),
).rejects.toBeInstanceOf(BadRequestException);
});
it('resolves a domestic bulk contract to the intercity template, ignoring its customs flag', async () => {
const { repository, service } = build();
await service.findActiveForContract('DOMESTIC', 'BULK', true, 'cargo-1');
expect(repository.findActiveBulkTemplate).toHaveBeenCalledWith(
'cargo-1',
'INTERCITY',
null,
false,
);
});
it('resolves an import bulk contract on direction and customs', async () => {
const { repository, service } = build();
await service.findActiveForContract('IMPORT', 'BULK', false, 'cargo-1');
expect(repository.findActiveBulkTemplate).toHaveBeenCalledWith(
'cargo-1',
'IMPORT',
false,
false,
);
});
it('resolves an Ethiopian-customs-only contract to the Ethiopian variant', async () => {
const { repository, service } = build();
await service.findActiveForContract('IMPORT', 'BULK', true, 'cargo-1', true);
expect(repository.findActiveBulkTemplate).toHaveBeenCalledWith(
'cargo-1',
'IMPORT',
true,
true,
);
});
});