Build Seth's changes C1 to C11 (Gitea issues 1, 8, 18, 23, 24, 25, 27, 31, 32, 33, 36)
- C1 (issue 1): over target follows VISUAL ticket hours, not the timer. - C2 (issue 24): a Q.C. reject always needs a reason; accept needs an NCR number after a reject without one. - C3 (issue 25): an incomplete unit ITP blocks filing. - C4 (issue 27): prints open the print dialog on the page, no new tab. - C5 (issue 31): filing is blocked while VISUAL shows a work order open; the feed sets vclosed on VISUAL packets. - C6 (issue 8): the data book leaves out the history pages. - C7 (issue 18): the supervisor approves each received short; accept and filing wait for it. - C8 (issue 23): per-department checklists, edited by the department supervisor or Admin, frozen at START. - C9 (issue 32): Q.C. and Clerical may run the stamp check. - C10 (issue 33): the PM screen and reports are for PS-ProjectMgmt and Admin only. - C11 (issue 36): the Stall screen lists the packets assigned to the welder, at the supervisor's stall. Migration 0011. Docs: PLAN (also issue 9 confirmed, issue 4 asked), POLICY, SECURITY, SCHEMA, FEED, README, CHANGELOG. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
@@ -79,12 +79,13 @@ afterEach(() => {
|
||||
});
|
||||
|
||||
describe("Close and file: ready to close", () => {
|
||||
it("shows the state, the exports, and confirms the close", async () => {
|
||||
it("a hand packet: shows the state, the exports, and Clerical confirms the close", async () => {
|
||||
const open = vi.spyOn(window, "open").mockImplementation(() => null);
|
||||
const fetch = mockFetch(base(toClose(), { "POST /api/packets/1/vclosed": { body: toFile({ version: 4 }) } }));
|
||||
const hand = { source_kind: "hand" as const, vclose_check: null };
|
||||
const fetch = mockFetch(base(toClose(hand), { "POST /api/packets/1/vclosed": { body: toFile({ version: 4, ...hand }) } }));
|
||||
renderApp("/packets/1");
|
||||
const box = await panel();
|
||||
expect(box).toHaveTextContent("Back from the shop, ready to close.");
|
||||
expect(box).toHaveTextContent("Back from the shop, ready to close. Close the job in VISUAL, then click below.");
|
||||
const file = within(box).getByRole("button", { name: "File in the data book" });
|
||||
expect(file).toBeDisabled();
|
||||
expect(within(box).getByText("Close the job in VISUAL first.")).toHaveClass("why");
|
||||
@@ -102,10 +103,30 @@ describe("Close and file: ready to close", () => {
|
||||
await waitFor(() => expect(screen.getByRole("region", { name: "Close and file" })).toHaveTextContent("Ready for the data book."));
|
||||
expect(callsTo(fetch, "POST", "/api/packets/1/vclosed")).toEqual([{ closed: true }]);
|
||||
expect(screen.getByRole("region", { name: "Close and file" })).toHaveTextContent(/Job closed in VISUAL .* by Clara Clerk\./);
|
||||
expect(screen.getByRole("button", { name: "Not closed in VISUAL, move back" })).toBeEnabled();
|
||||
});
|
||||
|
||||
it("a VISUAL packet waits for the feed: no manual close, File says why it is blocked", async () => {
|
||||
const why = "VISUAL still shows a work order open on this packet. Close it in VISUAL first.";
|
||||
mockFetch(base(toClose({ blocked: { file: [why] }, vclose_check: check({ open_wos: ["L999001/001"] }) })));
|
||||
renderApp("/packets/1");
|
||||
const box = await panel();
|
||||
expect(box).toHaveTextContent("Back from the shop, ready to close. Close the job in VISUAL. The packet moves on to the data book when the feed sees it closed.");
|
||||
expect(within(box).queryByRole("button", { name: "Job closed in VISUAL" })).not.toBeInTheDocument();
|
||||
expect(within(box).getByRole("button", { name: "File in the data book" })).toBeDisabled();
|
||||
expect(within(box).getByText(why)).toHaveClass("why");
|
||||
});
|
||||
|
||||
it("a VISUAL packet the feed saw closed is ready for the data book", async () => {
|
||||
mockFetch(base(toClose({ can: ["file"], blocked: {}, vclose_check: check({ closed_at: "2026-10-17T06:00:00Z", agrees: false }) })));
|
||||
renderApp("/packets/1");
|
||||
const box = await panel();
|
||||
expect(box).toHaveTextContent(/^Ready for the data book\. VISUAL shows the job closed since .+\. Check the data book, then file it\./);
|
||||
expect(within(box).getByRole("button", { name: "File in the data book" })).toBeEnabled();
|
||||
expect(within(box).queryByRole("button", { name: "Not closed in VISUAL, move back" })).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it.each([
|
||||
["the feed saw the job closed", check({ closed_at: "2026-10-17T06:00:00Z", agrees: false }), /^VISUAL shows the job closed since .+\.$/],
|
||||
["work orders still open", check({ open_wos: ["L999001/001", "L999001/002"] }), /^VISUAL still shows 2 work orders open: L999001\/001, L999001\/002\.$/],
|
||||
["one work order open", check({ open_wos: ["L999001/001"] }), /^VISUAL still shows 1 work order open: L999001\/001\.$/],
|
||||
["the feed has not looked", check({ observed_at: null }), /^The feed has not checked VISUAL yet\.$/],
|
||||
@@ -145,8 +166,9 @@ describe("Close and file: ready for the data book", () => {
|
||||
);
|
||||
renderApp("/packets/1");
|
||||
const box = await panel();
|
||||
expect(box).toHaveTextContent("Ready for the data book.");
|
||||
expect(within(box).getByRole("button", { name: "Not closed in VISUAL, move back" })).toBeEnabled();
|
||||
expect(box).toHaveTextContent(/^Ready for the data book\. Job closed in VISUAL .+ by Clara Clerk\./);
|
||||
// A VISUAL packet: the feed decides, so there is no manual move back.
|
||||
expect(within(box).queryByRole("button", { name: "Not closed in VISUAL, move back" })).not.toBeInTheDocument();
|
||||
fireEvent.click(within(box).getByRole("button", { name: "File in the data book" }));
|
||||
expect(await within(await screen.findByRole("dialog", { name: "Sign" })).findByText("File in the data book")).toBeInTheDocument();
|
||||
await sign();
|
||||
@@ -157,57 +179,32 @@ describe("Close and file: ready for the data book", () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it("warns in a callout when VISUAL still shows work orders open", async () => {
|
||||
mockFetch(base(toFile({ vclose_check: check({ open_wos: ["L999001/001"], agrees: false }) })));
|
||||
it("File is blocked while VISUAL shows work orders open, with no second confirmation", async () => {
|
||||
const why = "VISUAL still shows a work order open on this packet. Close it in VISUAL first.";
|
||||
const fetch = mockFetch(base(toFile({ can: [], blocked: { file: [why] }, vclose_check: check({ open_wos: ["L999001/001"], agrees: false }) })));
|
||||
renderApp("/packets/1");
|
||||
const box = await panel();
|
||||
const line = within(box).getByTestId("visual-line");
|
||||
expect(line).toHaveClass("callout", "warn");
|
||||
expect(line).toHaveTextContent("VISUAL still shows 1 work order open: L999001/001.");
|
||||
const file = within(box).getByRole("button", { name: "File in the data book" });
|
||||
expect(file).toBeDisabled();
|
||||
expect(within(box).getByText(why)).toHaveClass("why");
|
||||
fireEvent.click(file);
|
||||
expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
|
||||
expect(callsTo(fetch, "POST", "/api/packets/1/transitions")).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("asks on 409 open_work_orders and sends it again with confirm_open_wos", async () => {
|
||||
const msg = "VISUAL still shows 1 work order open on this packet: L999001/001. File anyway?";
|
||||
it("shows the server's refusal when filing is refused after all", async () => {
|
||||
const detail = "VISUAL still shows a work order open on this packet. Close it in VISUAL first.";
|
||||
const fetch = mockFetch(
|
||||
base(toFile({ vclose_check: check({ open_wos: ["L999001/001"], agrees: false }) }), {
|
||||
"POST /api/packets/1/transitions": (req) => {
|
||||
const b = req.body as { sign?: unknown; confirm_open_wos?: boolean };
|
||||
if (!b.confirm_open_wos) return { status: 409, body: { code: "open_work_orders", detail: msg, work_orders: ["L999001/001"] } };
|
||||
if (!b.sign) return { status: 400, body: { code: "signature_required", detail: "This step needs your signature.", meaning: "File in the data book" } };
|
||||
return { body: filed({ seal: seal({ open_wos: ["L999001/001"] }) }) };
|
||||
},
|
||||
"/api/packets/1/seal": { body: sealView(true, seal({ open_wos: ["L999001/001"] })) },
|
||||
}),
|
||||
base(toFile(), { "POST /api/packets/1/transitions": { status: 409, body: { code: "precondition_failed", detail } } }),
|
||||
);
|
||||
renderApp("/packets/1");
|
||||
fireEvent.click(within(await panel()).getByRole("button", { name: "File in the data book" }));
|
||||
const sheet = await screen.findByRole("dialog", { name: "File with open work orders?" });
|
||||
expect(within(sheet).getByText(msg)).toBeInTheDocument();
|
||||
expect(screen.queryByRole("alert")).not.toBeInTheDocument();
|
||||
fireEvent.click(within(sheet).getByRole("button", { name: "File anyway" }));
|
||||
await sign();
|
||||
await waitFor(() => expect(screen.getByRole("region", { name: "Close and file" })).toHaveTextContent("Filed in the data book"));
|
||||
expect(callsTo(fetch, "POST", "/api/packets/1/transitions")).toEqual([
|
||||
{ action: "file", expect: { status: "complete", dept: "03" } },
|
||||
{ action: "file", expect: { status: "complete", dept: "03" }, confirm_open_wos: true },
|
||||
{ action: "file", expect: { status: "complete", dept: "03" }, confirm_open_wos: true, sign: { factor: "password", secret: "pw-1" } },
|
||||
]);
|
||||
expect(screen.getByRole("region", { name: "Close and file" })).toHaveTextContent("Filed with work orders open in VISUAL: L999001/001.");
|
||||
});
|
||||
|
||||
it("Cancel on the confirm sends nothing more", async () => {
|
||||
const fetch = mockFetch(
|
||||
base(toFile(), {
|
||||
"POST /api/packets/1/transitions": { status: 409, body: { code: "open_work_orders", detail: "VISUAL still shows 1 work order open.", work_orders: ["L999001/001"] } },
|
||||
}),
|
||||
);
|
||||
renderApp("/packets/1");
|
||||
fireEvent.click(within(await panel()).getByRole("button", { name: "File in the data book" }));
|
||||
const sheet = await screen.findByRole("dialog", { name: "File with open work orders?" });
|
||||
fireEvent.click(within(sheet).getByRole("button", { name: "Cancel" }));
|
||||
await waitFor(() => expect(screen.queryByRole("dialog", { name: "File with open work orders?" })).not.toBeInTheDocument());
|
||||
expect(callsTo(fetch, "POST", "/api/packets/1/transitions")).toHaveLength(1);
|
||||
expect(screen.getByRole("region", { name: "Close and file" })).toHaveTextContent("Ready for the data book.");
|
||||
expect(await screen.findByRole("alert")).toHaveTextContent(detail);
|
||||
expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
|
||||
expect(callsTo(fetch, "POST", "/api/packets/1/transitions")).toEqual([{ action: "file", expect: { status: "complete", dept: "03" } }]);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user