Files
edr-platform/e2e/freight/cypress/e2e/flows/general_drawdown.cy.ts
2026-07-23 11:06:10 +00:00

141 lines
4.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 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 {};