Phase 6: Q.C. inspection, project folders, packet documents and the digital ITP
ci / frontend (push) Successful in 37s
ci / server (push) Successful in 9m18s
ci / image (push) Successful in 35s
ci / e2e (push) Successful in 2m17s

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:
2026-10-17 20:08:53 -06:00
co-authored by Claude Fable 5.1
parent 8bb1f7ae4d
commit 00531cb329
64 changed files with 11715 additions and 260 deletions
+16 -3
View File
@@ -16,6 +16,7 @@ import type {
PacketHeader,
PacketListQuery,
PacketRead,
QcFields,
ReceiptBody,
ShortBody,
StageFields,
@@ -43,10 +44,12 @@ export const packetsApi = {
attachPages: (id: number, body: AttachPagesBody) => api.post<PacketRead>(`${base(id)}/pages`, body),
removePages: (id: number, n: number, body: { reason?: string } & Guarded = {}) =>
api.delWithBody<PacketRead>(`${base(id)}/pages/${n}`, body),
addDoc: (id: number, body: { asset_id: string; kind: string; title: string } & Guarded) =>
/** Phase 6: kind from the closed set; the title defaults to the kind label on the server. */
addDoc: (id: number, body: { asset_id: string; kind: string; title?: string; note?: string } & Guarded) =>
api.post<PacketRead>(`${base(id)}/docs`, body),
removeDoc: (id: number, docId: number, extra: Guarded = {}) =>
api.delWithBody<PacketRead>(`${base(id)}/docs/${docId}`, extra),
/** Takes a document out of the data book. The row stays, with the reason. */
removeDoc: (id: number, docId: number, body: { reason: string } & Guarded) =>
api.delWithBody<PacketRead>(`${base(id)}/docs/${docId}`, body),
move: (id: number, body: { unit_id: number; reason?: string } & Guarded) => api.post<PacketRead>(`${base(id)}/move`, body),
remove: (id: number, body: { reason: string } & Guarded) => api.delWithBody<PacketRead | null>(base(id), body),
setVclosed: (id: number, closed: boolean, extra: Guarded = {}) =>
@@ -114,6 +117,16 @@ export const packetsApi = {
clock: (id: number, op: "start" | "pause" | "resume", g: Guarded = {}) => api.post<PacketRead>(`${base(id)}/stage/clock`, { op, ...g }),
coVerify: (id: number, no: number, body: { dept: string; note?: string } & Guarded) =>
api.post<PacketRead>(`${base(id)}/change-orders/${no}/verify`, body),
/* Phase 6: the Q.C. inspection. Every answer is the packet. */
/** One check of the open inspection. */
qcCheck: (id: number, key: string, done: boolean, g: Guarded = {}) =>
api.put<PacketRead>(`${base(id)}/qc/checks/${encodeURIComponent(key)}`, { done, ...g }),
/** The visual exam of one weld. result "" clears it. */
qcWeld: (id: number, no: number, body: { result?: "accept" | "reject" | ""; note?: string }, g: Guarded = {}) =>
api.put<PacketRead>(`${base(id)}/qc/welds/${no}`, { ...body, ...g }),
/** The scalar inspection fields with a field-level base: 409 field_conflict when someone changed one first. */
qcPatch: (id: number, body: FieldCas<QcFields>) => api.patch<PacketRead>(`${base(id)}/qc`, body),
};
/** GET /api/departments/{code}/people: the welder picker and the weld sheet's stamp. */