mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-26 18:42:49 +00:00
141 lines
4.3 KiB
TypeScript
141 lines
4.3 KiB
TypeScript
/**
|
||
* GENERAL drawdown ledger (D+25) — one GENERAL contract capped at 60×20ft
|
||
* draws down across many bookings; the cap is enforced at CREATE, releases
|
||
* when a booking dies, and the contract stays CONTRACT_ACTIVE throughout:
|
||
*
|
||
* B1 30 → B2 20 → B3 asking 20 REJECTED ("only 10 remain") → B3' 10 → cap
|
||
* exhausted → B4 2 REJECTED → B3' expires → its 10 return → B5 10 accepted.
|
||
*
|
||
* Sequential steps of one journey — retries off.
|
||
*/
|
||
|
||
import {
|
||
bookContainers,
|
||
createImportSchedule,
|
||
db,
|
||
departureAt,
|
||
eatDayStr,
|
||
ensureCorridorRoute,
|
||
forceWindowOpen,
|
||
pollBookingStatus,
|
||
resetCorridorDay,
|
||
seedImportContract,
|
||
withSchedule,
|
||
} from "./import-utils";
|
||
|
||
const DEPARTURE = departureAt(25);
|
||
const BOOKING_DAY = eatDayStr(DEPARTURE);
|
||
|
||
const stamp = String(Date.now());
|
||
const SUFFIX = "GDCAP";
|
||
const REF = `CTR-IMP-${stamp}-${SUFFIX}`;
|
||
|
||
describe("GENERAL drawdown: a 60×20ft cap across many bookings", { retries: 0 }, () => {
|
||
before(() => {
|
||
cy.task("db:seedFile", "seed-import-corridor.sql");
|
||
seedImportContract({ suffix: SUFFIX, reference: REF, kind: "GENERAL", cap20: 60 });
|
||
});
|
||
|
||
it("operations prepares the corridor train with an open window (bookings need an open day)", () => {
|
||
ensureCorridorRoute();
|
||
resetCorridorDay(DEPARTURE);
|
||
createImportSchedule({ departure: DEPARTURE, locoPair: ["LOCO-IMP-17", "LOCO-IMP-18"] });
|
||
withSchedule(DEPARTURE, (s) => forceWindowOpen(s.id, 60));
|
||
});
|
||
|
||
it("B1 draws 30 and B2 draws 20 — 50 of 60 held", () => {
|
||
bookContainers({
|
||
suffix: SUFFIX,
|
||
runStamp: stamp,
|
||
isoSeed: 18_000,
|
||
twenty: 30,
|
||
scheduledDate: BOOKING_DAY,
|
||
});
|
||
bookContainers({
|
||
suffix: SUFFIX,
|
||
runStamp: stamp,
|
||
isoSeed: 18_100,
|
||
twenty: 20,
|
||
scheduledDate: BOOKING_DAY,
|
||
});
|
||
db<{ n: string }>(
|
||
`SELECT count(*) AS n FROM freight.bookings b
|
||
JOIN freight.contracts ct ON ct.id = b.contract_id
|
||
WHERE ct.reference = $1`,
|
||
[REF],
|
||
).then(({ rows }) => expect(Number(rows[0].n), "two live drawdowns").to.eq(2));
|
||
});
|
||
|
||
it("B3 asking 20 is REJECTED — only 10 of 60 remain", () => {
|
||
bookContainers({
|
||
suffix: SUFFIX,
|
||
runStamp: stamp,
|
||
isoSeed: 18_200,
|
||
twenty: 20,
|
||
scheduledDate: BOOKING_DAY,
|
||
expectFailure: "remain on this contract",
|
||
});
|
||
});
|
||
|
||
it("B3' takes exactly the remaining 10 — the contract COMPLETES and B4 is rejected", () => {
|
||
bookContainers({
|
||
suffix: SUFFIX,
|
||
runStamp: stamp,
|
||
isoSeed: 18_300,
|
||
twenty: 10,
|
||
scheduledDate: BOOKING_DAY,
|
||
});
|
||
// Booking the last of the cap flips the GENERAL contract to
|
||
// CONTRACT_CLOSED (fully drawn) — the next booking is rejected as such.
|
||
db<{ status: string }>(
|
||
`SELECT status FROM freight.contracts WHERE reference = $1`,
|
||
[REF],
|
||
).then(({ rows }) =>
|
||
expect(rows[0].status, "fully drawn contract completes").to.eq("CONTRACT_CLOSED"),
|
||
);
|
||
bookContainers({
|
||
suffix: SUFFIX,
|
||
runStamp: stamp,
|
||
isoSeed: 18_400,
|
||
twenty: 2,
|
||
scheduledDate: BOOKING_DAY,
|
||
expectFailure: "completed",
|
||
});
|
||
});
|
||
|
||
it("an EXPIRED booking releases its draw — B5 books the freed 10 and the ledger closes again", () => {
|
||
// Kill the newest live drawdown (B3', 10×20ft) — expiry releases its hold.
|
||
db(
|
||
`UPDATE freight.bookings b SET status = 'EXPIRED'
|
||
FROM freight.contracts ct
|
||
WHERE ct.id = b.contract_id AND ct.reference = $1
|
||
AND b.id = (
|
||
SELECT b2.id FROM freight.bookings b2
|
||
JOIN freight.contracts c2 ON c2.id = b2.contract_id
|
||
WHERE c2.reference = $1 AND b2.status NOT IN ('EXPIRED','CANCELLED','REJECTED')
|
||
ORDER BY b2.created_at DESC LIMIT 1
|
||
)`,
|
||
[REF],
|
||
);
|
||
bookContainers({
|
||
suffix: SUFFIX,
|
||
runStamp: stamp,
|
||
isoSeed: 18_500,
|
||
twenty: 10,
|
||
scheduledDate: BOOKING_DAY,
|
||
});
|
||
pollBookingStatus(SUFFIX, "AWAITING_DOCUMENTS", 5);
|
||
|
||
// Completion tracks the outstanding quantity: the released 10 were
|
||
// rebooked, so the ledger is full again and the contract stays CLOSED.
|
||
db<{ status: string }>(
|
||
`SELECT status FROM freight.contracts WHERE reference = $1`,
|
||
[REF],
|
||
).then(({ rows }) =>
|
||
expect(rows[0].status, "re-drawn contract closed").to.eq("CONTRACT_CLOSED"),
|
||
);
|
||
});
|
||
});
|
||
|
||
export {};
|