Phase 6: Q.C. inspection, project folders, packet documents and the digital ITP
The Q.C. screen (KPIs, boards, project folders), the Q.C. tab on the packet page (check items from the qc_check_items settings row, visual exam per weld, NDE, hardness with the NACE label, notes), accept with a route and a signature gated by qc_missing_empty, reject with a reason or an NCR number closing the inspection into qc_history, Q.C. folder documents per project and unit with superseding revisions, packet documents as a closed set with take-out reasons, the digital ITP per unit from the frozen template (line initials with CAS, unsign, N/A, customer codes, rejects with NCR, block signatures, sign-off with the PDF hook, admin reopen, the ITP log), hold-point warnings by department page group, the scoped policy check for unit and project writes, migration 0007, tests, Playwright scenarios and docs. Co-Authored-By: Claude Fable 5.1 <[email protected]>
This commit is contained in:
@@ -20,13 +20,18 @@ import { ChangesTab } from "./ChangesTab";
|
||||
import { ChecklistTab } from "./ChecklistTab";
|
||||
import { HeatMapTab } from "./HeatMapTab";
|
||||
import { ShopBar } from "./ShopActions";
|
||||
import { HoldCallout } from "./HoldCallout";
|
||||
import { QcBar } from "./QcActions";
|
||||
import { QcRejectedCallout } from "./QcRejectedCallout";
|
||||
import { showsQcRejected } from "../lib/qc";
|
||||
import { QcTab } from "./QcTab";
|
||||
import { WeldMapTab } from "./WeldMapTab";
|
||||
import type { Write } from "../warehouse/PullTable";
|
||||
import { packetMessage, pillClassFor, WH_STAGES, pillText, progress, whLine, whStage } from "../warehouse/stages";
|
||||
import { useWhMove } from "../warehouse/useWhMove";
|
||||
|
||||
type Tab = "drawing" | "heat" | "materials" | "checklist" | "welds" | "workorders" | "changes" | "docs" | "history";
|
||||
const TABS: readonly Tab[] = ["drawing", "heat", "materials", "checklist", "welds", "workorders", "changes", "docs", "history"];
|
||||
type Tab = "drawing" | "heat" | "materials" | "checklist" | "welds" | "qc" | "workorders" | "changes" | "docs" | "history";
|
||||
const TABS: readonly Tab[] = ["drawing", "heat", "materials", "checklist", "welds", "qc", "workorders", "changes", "docs", "history"];
|
||||
function isTab(v: string | null): v is Tab {
|
||||
return v !== null && (TABS as readonly string[]).includes(v);
|
||||
}
|
||||
@@ -317,7 +322,9 @@ export function PacketPage() {
|
||||
|
||||
const drawingPages = p.pages.filter((x) => x.type === "drawing");
|
||||
const review = coToReview(p);
|
||||
const shownTab: Tab = tab === "changes" && !(p.change_orders ?? []).length ? "materials" : (tab ?? (drawingPages.length ? "drawing" : "materials"));
|
||||
const hasQc = !!p.qc || p.qc_history.length > 0;
|
||||
const hidden = (tab === "changes" && !(p.change_orders ?? []).length) || (tab === "qc" && !hasQc);
|
||||
const shownTab: Tab = hidden ? "materials" : (tab ?? (drawingPages.length ? "drawing" : "materials"));
|
||||
// The warehouse short-parts exception and the data book date are not edits of the packet itself.
|
||||
const passive = new Set(["wh_receive_short", "set_databook_at"]);
|
||||
const viewOnly =
|
||||
@@ -354,9 +361,10 @@ export function PacketPage() {
|
||||
{ id: "materials", label: "Materials", count: p.bom.length },
|
||||
{ id: "checklist", label: "Checklist", ...(p.stage ? { count: ckOpen } : {}) },
|
||||
{ id: "welds", label: "Weld map", count: p.welds.length },
|
||||
...(hasQc ? [{ id: "qc" as const, label: "Q.C." }] : []),
|
||||
{ id: "workorders", label: "Work orders", count: p.work_orders.length },
|
||||
...(cos ? [{ id: "changes" as const, label: "Changes", count: cos }] : []),
|
||||
{ id: "docs", label: "Documents", count: p.docs.length },
|
||||
{ id: "docs", label: "Documents", count: p.docs.filter((d) => !d.removed).length },
|
||||
{ id: "history", label: "History" },
|
||||
];
|
||||
const onTabError = (text: string) => setMsg({ ok: false, text });
|
||||
@@ -463,7 +471,8 @@ export function PacketPage() {
|
||||
{p.removed.by ? ` by ${p.removed.by.display_name}` : ""}. {p.removed.reason ?? ""}
|
||||
</div>
|
||||
) : null}
|
||||
{p.return_note ? (
|
||||
<QcRejectedCallout p={p} />
|
||||
{p.return_note && !(showsQcRejected(p) && p.return_note === p.qc_last?.note) ? (
|
||||
<div className="callout warn">
|
||||
<b>Returned:</b> {p.return_note}
|
||||
</div>
|
||||
@@ -652,6 +661,8 @@ export function PacketPage() {
|
||||
) : null}
|
||||
|
||||
<ShopBar p={p} busy={busy} mutate={mutate} askReason={askReason} sub={job} goTab={(t) => setTab(t)} />
|
||||
<QcBar p={p} busy={busy} mutate={mutate} sub={job} goQc={() => setTab("qc")} />
|
||||
<HoldCallout p={p} qcScreen={me?.screens.includes("qc") ?? false} />
|
||||
|
||||
<div className="tabs" role="tablist" aria-label="Packet">
|
||||
{tabs.map((t) => (
|
||||
@@ -692,9 +703,10 @@ export function PacketPage() {
|
||||
{shownTab === "welds" ? (
|
||||
<WeldMapTab key={p.id} p={p} write={write} mutate={mutate} reload={reload} askReason={askReason} sub={job} myId={me?.user.id ?? null} onError={onTabError} />
|
||||
) : null}
|
||||
{shownTab === "qc" ? <QcTab p={p} write={write} mutate={mutate} reload={reload} onError={onTabError} /> : null}
|
||||
{shownTab === "workorders" ? <WorkOrdersTab p={p} /> : null}
|
||||
{shownTab === "changes" ? <ChangesTab p={p} roles={me?.roles ?? []} sub={job} mutate={mutate} busy={busy} askReason={askReason} /> : null}
|
||||
{shownTab === "docs" ? <DocumentsTab p={p} mutate={mutate} busy={busy} /> : null}
|
||||
{shownTab === "docs" ? <DocumentsTab p={p} mutate={mutate} busy={busy} write={write} askReason={askReason} sub={job} /> : null}
|
||||
{shownTab === "history" ? <HistoryTab p={p} /> : null}
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user