mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-26 18:42:49 +00:00
- 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.
187 lines
6.4 KiB
TypeScript
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,
|
|
);
|
|
});
|
|
});
|