mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-26 12:41:04 +00:00
142 lines
4.9 KiB
TypeScript
142 lines
4.9 KiB
TypeScript
/**
|
||
* BOOKING — clearance document QUERY (reject) + re-upload lifecycle:
|
||
*
|
||
* Every other spec that touches clearance only ever APPROVES documents
|
||
* (`clearGeneralBooking` in import-utils.ts). This spec drives the other
|
||
* branch of `reviewDocument` — QUERIED — which no spec exercises:
|
||
*
|
||
* – querying a document with no note is rejected (a note is required)
|
||
* – querying with a note succeeds: reviewStatus → QUERIED, the note is
|
||
* stored, and a CHANGES_REQUESTED review note is recorded on the booking
|
||
* – re-uploading the SAME document resets its review to PENDING (the
|
||
* customer's fix clears the query — submitClearanceDocuments always
|
||
* resets reviewed docs back to PENDING on re-upload)
|
||
* – GL re-reviews it APPROVED → finalize succeeds → CLEARANCE_READY
|
||
*
|
||
* GENERAL contract, per-booking clearance gate (AWAITING_DOCUMENTS at
|
||
* creation, before ops ever sees it) — same fixture shape as
|
||
* clearGeneralBooking, with an ad-hoc document. Retries off.
|
||
*/
|
||
|
||
import {
|
||
apiPost,
|
||
bookContainers,
|
||
db,
|
||
departureAt,
|
||
eatDayStr,
|
||
glUpload,
|
||
seedImportContract,
|
||
superAdmin,
|
||
withBooking,
|
||
} from "./import-utils";
|
||
|
||
const stamp = String(Date.now());
|
||
// Per-booking clearance (Path A) skips the booking-window gate entirely, so
|
||
// this date never needs a real schedule behind it — just a binding day string.
|
||
const BOOKING_DAY = eatDayStr(departureAt(33));
|
||
|
||
interface ReviewRow {
|
||
status: string;
|
||
note: string | null;
|
||
}
|
||
|
||
function reviewFor(bookingId: string, fn: (row: ReviewRow) => void) {
|
||
db<ReviewRow>(
|
||
`SELECT status, note FROM freight.booking_document_review
|
||
WHERE booking_id = $1 AND file_key = 'custom_e2e'`,
|
||
[bookingId],
|
||
).then(({ rows }) => {
|
||
expect(rows, "review row for custom_e2e").to.have.length(1);
|
||
fn(rows[0]);
|
||
});
|
||
}
|
||
|
||
describe("booking: clearance document query (reject) + re-upload lifecycle", { retries: 0 }, () => {
|
||
before(() => {
|
||
cy.task("db:seedFile", "seed-import-corridor.sql");
|
||
seedImportContract({ suffix: "CQ1", reference: `CTR-IMP-${stamp}-CQ1`, kind: "GENERAL" });
|
||
});
|
||
|
||
it("books under the GENERAL contract — starts at the clearance gate", () => {
|
||
bookContainers({
|
||
suffix: "CQ1",
|
||
runStamp: stamp,
|
||
isoSeed: 0,
|
||
forty: 1,
|
||
scheduledDate: BOOKING_DAY,
|
||
});
|
||
withBooking("CQ1", (b) =>
|
||
expect(b.status, "AWAITING_DOCUMENTS").to.eq("AWAITING_DOCUMENTS"),
|
||
);
|
||
});
|
||
|
||
it("uploads a document; querying it with no note is rejected", () => {
|
||
withBooking("CQ1", (b) => {
|
||
glUpload(`/api/bookings/${b.id}/clearance/documents`, {}, "custom_e2e");
|
||
|
||
apiPost(
|
||
superAdmin,
|
||
`/api/bookings/${b.id}/clearance/review`,
|
||
{ fileKey: "custom_e2e", status: "QUERIED" },
|
||
false,
|
||
).then((res) => {
|
||
expect(res.status, "note required").to.be.within(400, 422);
|
||
expect(JSON.stringify(res.body)).to.include("A note is required");
|
||
});
|
||
});
|
||
});
|
||
|
||
it("querying with a note succeeds — reviewStatus QUERIED, note stored, review note recorded", () => {
|
||
withBooking("CQ1", (b) => {
|
||
apiPost(superAdmin, `/api/bookings/${b.id}/clearance/review`, {
|
||
fileKey: "custom_e2e",
|
||
status: "QUERIED",
|
||
note: "E2E: wrong document, please re-upload the correct one",
|
||
}).then((res) => expect(res.status, "queried").to.be.oneOf([200, 201]));
|
||
|
||
reviewFor(b.id, (row) => {
|
||
expect(row.status, "QUERIED").to.eq("QUERIED");
|
||
expect(row.note, "note stored").to.include("wrong document");
|
||
});
|
||
|
||
db<{ n: string }>(
|
||
`SELECT count(*) AS n FROM freight.booking_review_note
|
||
WHERE booking_id = $1 AND type = 'CHANGES_REQUESTED'`,
|
||
[b.id],
|
||
).then(({ rows }) =>
|
||
expect(Number(rows[0].n), "queried review note recorded").to.be.at.least(1),
|
||
);
|
||
|
||
// Still under review — the query doesn't advance the booking status.
|
||
withBooking("CQ1", (fresh) =>
|
||
expect(fresh.status, "still DOCUMENTS_UNDER_REVIEW").to.eq("DOCUMENTS_UNDER_REVIEW"),
|
||
);
|
||
});
|
||
});
|
||
|
||
it("re-uploading resets the review to PENDING; GL approves it and finalize succeeds", () => {
|
||
withBooking("CQ1", (b) => {
|
||
glUpload(`/api/bookings/${b.id}/clearance/documents`, {}, "custom_e2e");
|
||
|
||
reviewFor(b.id, (row) => expect(row.status, "reset to PENDING").to.eq("PENDING"));
|
||
|
||
apiPost(superAdmin, `/api/bookings/${b.id}/clearance/review`, {
|
||
fileKey: "custom_e2e",
|
||
status: "APPROVED",
|
||
}).then((res) => expect(res.status, "approved").to.be.oneOf([200, 201]));
|
||
|
||
reviewFor(b.id, (row) => expect(row.status, "APPROVED").to.eq("APPROVED"));
|
||
|
||
apiPost(superAdmin, `/api/bookings/${b.id}/clearance/finalize`).then((res) =>
|
||
expect(res.status, "finalized").to.be.oneOf([200, 201]),
|
||
);
|
||
});
|
||
|
||
withBooking("CQ1", (b) =>
|
||
expect(b.status, "CLEARANCE_READY").to.eq("CLEARANCE_READY"),
|
||
);
|
||
});
|
||
});
|
||
|
||
export {};
|