mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-26 18:42:49 +00:00
- planned couples: loose wagons join the train at a route stop, added from the schedule yards tab; capacity credits them per corridor edge and coupling validates locomotive weight/length caps per leg - real-cut toggle: a cut wagon permanently leaves the train build at its cut yard (soft cut still sits out one trip only) - fix heaviest-leg display counting a shared slot's full cargo on every spanned edge (phantom pull-weight overload on S-2026-00045) - confirmation dialogs for workspace add/load/unload/remove actions - train-builder History and Detached-wagons tabs, backed by paginated endpoints; builder detaches now always write adjustment-log rows Migrations 3660 (planned_wagon_couples, planned_wagon_real_cuts) and 3670 (adjustment log train_schedule_id nullable) — both applied to the dev DB by hand; watch mode does not run migrations. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
66 lines
2.1 KiB
TypeScript
66 lines
2.1 KiB
TypeScript
import { ContractBookingService } from './contract-booking.service';
|
|
|
|
/**
|
|
* Wagon-cancellation credit rebook must work after the contract lapses (the
|
|
* freight was paid while it was live), while every other create path stays
|
|
* blocked. assertGate is the status gate createUnderContract runs; this pins
|
|
* the EXPIRED carve-out to the allowExpired flag.
|
|
*/
|
|
describe('ContractBookingService.assertGate expired-contract rebook carve-out', () => {
|
|
// assertGate only reads contract fields — no constructor deps needed.
|
|
const service = Object.create(
|
|
ContractBookingService.prototype,
|
|
) as ContractBookingService;
|
|
const gate = (
|
|
contract: Record<string, unknown>,
|
|
allowExpired: boolean,
|
|
): Promise<string> =>
|
|
(
|
|
service as unknown as {
|
|
assertGate: (
|
|
c: unknown,
|
|
gl: boolean,
|
|
init: boolean,
|
|
allowExpired: boolean,
|
|
) => Promise<string>;
|
|
}
|
|
).assertGate(contract, true, false, allowExpired);
|
|
|
|
it('refuses an EXPIRED contract on the normal create path', async () => {
|
|
await expect(
|
|
gate({ status: 'EXPIRED', contractKind: 'GENERAL' }, false),
|
|
).rejects.toThrow(/fully executed/i);
|
|
});
|
|
|
|
it('lets a credit rebook through on an EXPIRED contract (Path A)', async () => {
|
|
await expect(
|
|
gate({ status: 'EXPIRED', contractKind: 'GENERAL' }, true),
|
|
).resolves.toBe('STAFF');
|
|
});
|
|
|
|
it('lets a credit rebook through on an EXPIRED customs contract (Path B)', async () => {
|
|
await expect(
|
|
gate(
|
|
{
|
|
status: 'EXPIRED',
|
|
contractKind: 'GENERAL',
|
|
customsClearingEnabled: true,
|
|
},
|
|
true,
|
|
),
|
|
).resolves.toBe('GL_ET');
|
|
});
|
|
|
|
it('still refuses a SUSPENDED contract even for a rebook', async () => {
|
|
await expect(
|
|
gate({ status: 'SUSPENDED', contractKind: 'GENERAL' }, true),
|
|
).rejects.toThrow(/suspended/i);
|
|
});
|
|
|
|
it('does not open the gate for other non-executed statuses', async () => {
|
|
await expect(
|
|
gate({ status: 'DRAFT', contractKind: 'GENERAL' }, true),
|
|
).rejects.toThrow(/fully executed/i);
|
|
});
|
|
});
|