mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-08 04:15:43 +00:00
add dispute functionality for contract duty and implement collection dates
This commit is contained in:
@@ -0,0 +1,175 @@
|
||||
import { BadRequestException } from '@nestjs/common';
|
||||
|
||||
import { ContractClearanceService } from './contract-clearance.service';
|
||||
import type { Contract } from './entities/contract.entity';
|
||||
|
||||
/**
|
||||
* Pre-declaration transit-assignee handshake. GL Ethiopia asks Djibouti who will
|
||||
* handle the shipment in transit; Djibouti answers with a name. The customs
|
||||
* declaration stays shut until that name exists, and Djibouti may send a
|
||||
* different one later.
|
||||
*/
|
||||
describe('ContractClearanceService — transit assignee', () => {
|
||||
const contract = (over: Partial<Contract> = {}): Contract =>
|
||||
({
|
||||
id: 'ctr-1',
|
||||
reference: 'CTR-2026-00042',
|
||||
tradeDirection: 'IMPORT',
|
||||
customsClearingEnabled: true,
|
||||
contractKind: 'ONE_TIME',
|
||||
...over,
|
||||
}) as Contract;
|
||||
|
||||
let repo: { currentCycle: jest.Mock; updateCycle: jest.Mock };
|
||||
let contractsService: { findById: jest.Mock };
|
||||
let notifier: {
|
||||
transitAssigneeRequested: jest.Mock;
|
||||
transitAssigneeAssigned: jest.Mock;
|
||||
};
|
||||
let service: ContractClearanceService;
|
||||
|
||||
const cycle = (over: Record<string, unknown> = {}) => ({
|
||||
id: 'cyc-1',
|
||||
transitAssigneeRequestedAt: null,
|
||||
transitAssigneeName: null,
|
||||
...over,
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
repo = {
|
||||
currentCycle: jest.fn().mockResolvedValue(cycle()),
|
||||
updateCycle: jest.fn().mockResolvedValue(undefined),
|
||||
};
|
||||
contractsService = { findById: jest.fn().mockResolvedValue(contract()) };
|
||||
notifier = {
|
||||
transitAssigneeRequested: jest.fn(),
|
||||
transitAssigneeAssigned: jest.fn(),
|
||||
};
|
||||
service = new ContractClearanceService(
|
||||
repo as never,
|
||||
contractsService as never,
|
||||
{} as never,
|
||||
{} as never,
|
||||
{} as never,
|
||||
{} as never,
|
||||
{} as never,
|
||||
{} as never,
|
||||
{} as never,
|
||||
notifier as never,
|
||||
);
|
||||
});
|
||||
|
||||
describe('request (GL Ethiopia)', () => {
|
||||
it('stamps the ask and pings Djibouti', async () => {
|
||||
await service.requestTransitAssignee('ctr-1', ' Reefer, needs a cold-chain officer ', 'et-1');
|
||||
|
||||
const patch = repo.updateCycle.mock.calls[0][1];
|
||||
expect(patch.transitAssigneeRequestedAt).toBeInstanceOf(Date);
|
||||
expect(patch.transitAssigneeRequestedByUserId).toBe('et-1');
|
||||
expect(patch.transitAssigneeRequestNote).toBe(
|
||||
'Reefer, needs a cold-chain officer',
|
||||
);
|
||||
expect(notifier.transitAssigneeRequested).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('assign (GL Djibouti)', () => {
|
||||
it('records the officer and tells Ethiopia they can proceed', async () => {
|
||||
repo.currentCycle.mockResolvedValue(
|
||||
cycle({ transitAssigneeRequestedAt: new Date() }),
|
||||
);
|
||||
|
||||
await service.assignTransitAssignee('ctr-1', ' Ahmed Bourhan ', 'dj-1');
|
||||
|
||||
const patch = repo.updateCycle.mock.calls[0][1];
|
||||
expect(patch.transitAssigneeName).toBe('Ahmed Bourhan');
|
||||
expect(patch.transitAssigneeAssignedByUserId).toBe('dj-1');
|
||||
expect(notifier.transitAssigneeAssigned).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ id: 'ctr-1' }),
|
||||
'Ahmed Bourhan',
|
||||
null,
|
||||
);
|
||||
});
|
||||
|
||||
it('reassigns, carrying the previous name into the notice', async () => {
|
||||
repo.currentCycle.mockResolvedValue(
|
||||
cycle({
|
||||
transitAssigneeRequestedAt: new Date(),
|
||||
transitAssigneeName: 'Ahmed Bourhan',
|
||||
}),
|
||||
);
|
||||
|
||||
await service.assignTransitAssignee('ctr-1', 'Fatouma Ali', 'dj-1');
|
||||
|
||||
expect(notifier.transitAssigneeAssigned).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
'Fatouma Ali',
|
||||
'Ahmed Bourhan',
|
||||
);
|
||||
});
|
||||
|
||||
it('refuses an empty name', async () => {
|
||||
repo.currentCycle.mockResolvedValue(
|
||||
cycle({ transitAssigneeRequestedAt: new Date() }),
|
||||
);
|
||||
await expect(
|
||||
service.assignTransitAssignee('ctr-1', ' ', 'dj-1'),
|
||||
).rejects.toBeInstanceOf(BadRequestException);
|
||||
});
|
||||
|
||||
it('refuses before Ethiopia has asked', async () => {
|
||||
await expect(
|
||||
service.assignTransitAssignee('ctr-1', 'Ahmed Bourhan', 'dj-1'),
|
||||
).rejects.toThrow(/not requested/i);
|
||||
});
|
||||
});
|
||||
|
||||
describe('declaration gate', () => {
|
||||
const ensure = (c: Contract) =>
|
||||
(
|
||||
service as unknown as {
|
||||
ensureDeclarationPrerequisites: (id: string, c: Contract) => Promise<void>;
|
||||
}
|
||||
).ensureDeclarationPrerequisites('ctr-1', c);
|
||||
|
||||
beforeEach(() => {
|
||||
// Documents are approved; only the assignee decides the outcome here.
|
||||
(
|
||||
service as unknown as { isClearanceFullyApproved: unknown }
|
||||
).isClearanceFullyApproved = jest.fn().mockResolvedValue(true);
|
||||
});
|
||||
|
||||
it('tells GL to raise the request when none exists', async () => {
|
||||
await expect(ensure(contract())).rejects.toThrow(
|
||||
/Request a transit assignee/i,
|
||||
);
|
||||
});
|
||||
|
||||
it('tells GL to wait when Djibouti has not answered', async () => {
|
||||
repo.currentCycle.mockResolvedValue(
|
||||
cycle({ transitAssigneeRequestedAt: new Date() }),
|
||||
);
|
||||
await expect(ensure(contract())).rejects.toThrow(/has not assigned/i);
|
||||
});
|
||||
|
||||
it('lets the declaration through once the officer is named', async () => {
|
||||
repo.currentCycle.mockResolvedValue(
|
||||
cycle({
|
||||
transitAssigneeRequestedAt: new Date(),
|
||||
transitAssigneeName: 'Ahmed Bourhan',
|
||||
}),
|
||||
);
|
||||
(
|
||||
service as unknown as { workflowService: unknown }
|
||||
).workflowService = {
|
||||
listMilestones: jest
|
||||
.fn()
|
||||
.mockResolvedValue([
|
||||
{ milestoneCode: 'DOCUMENTS_APPROVED', status: 'COMPLETED' },
|
||||
]),
|
||||
};
|
||||
|
||||
await expect(ensure(contract())).resolves.toBeUndefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user