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:
@@ -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. */
|
||||
|
||||
@@ -0,0 +1,436 @@
|
||||
// The Q.C. home, the Q.C. folders and the digital ITP (phase 6 contract, api/qc.py and api/itp.py).
|
||||
// These types belong to the Q.C. screens. The packet page types stay in types.ts.
|
||||
import { api, request } from "./client";
|
||||
import { qs } from "./query";
|
||||
import type { Guarded, PacketCard, ProjectStatus, SignBody } from "./types";
|
||||
|
||||
/** A person on a record. */
|
||||
export interface QcWho {
|
||||
id: string;
|
||||
display_name: string;
|
||||
emp_no?: string | null;
|
||||
}
|
||||
|
||||
/* ---------- Q.C. home ---------- */
|
||||
|
||||
export interface QcCardInfo {
|
||||
location: string | null;
|
||||
sent_at: string | null;
|
||||
inspector: string | null;
|
||||
result: string | null;
|
||||
}
|
||||
|
||||
/** A board card: the list card plus the inspection facts and the open ITP hold count. */
|
||||
export interface QcCard extends PacketCard {
|
||||
qc?: QcCardInfo | null;
|
||||
itp_hold_count?: number | null;
|
||||
}
|
||||
|
||||
export interface QcKpis {
|
||||
waiting: number;
|
||||
accepted_7d: number;
|
||||
rejected_7d: number;
|
||||
}
|
||||
|
||||
export const QC_COLUMNS = ["ready", "accepted", "rejected"] as const;
|
||||
export type QcColumn = (typeof QC_COLUMNS)[number];
|
||||
|
||||
/** One project on the folder grid. */
|
||||
export interface QcFolderCard {
|
||||
id: number;
|
||||
number: string;
|
||||
name: string | null;
|
||||
customer: string | null;
|
||||
po: string | null;
|
||||
status: ProjectStatus;
|
||||
units: number;
|
||||
docs: number;
|
||||
units_without_itp: number;
|
||||
}
|
||||
|
||||
export interface QcHome {
|
||||
kpis: QcKpis;
|
||||
columns: Record<QcColumn, QcCard[]>;
|
||||
folders: QcFolderCard[];
|
||||
}
|
||||
|
||||
export interface QcHomeQuery {
|
||||
project_id?: number;
|
||||
unit_id?: number;
|
||||
}
|
||||
|
||||
/* ---------- Q.C. folders ---------- */
|
||||
|
||||
/** A Q.C. folder document. Nothing is deleted; a new revision supersedes the old row. */
|
||||
export interface QcDoc {
|
||||
id: number;
|
||||
project_id: number | null;
|
||||
unit_id: number | null;
|
||||
type: string;
|
||||
title: string | null;
|
||||
rev: string | null;
|
||||
note: string | null;
|
||||
asset_id: string;
|
||||
url: string;
|
||||
file_name: string | null;
|
||||
size: number | null;
|
||||
auto: boolean;
|
||||
added_at: string;
|
||||
added_by: QcWho | null;
|
||||
superseded_by: number | null;
|
||||
superseded_at: string | null;
|
||||
superseded_by_user: QcWho | null;
|
||||
}
|
||||
|
||||
/** The unit's ITP in short. */
|
||||
export interface ItpSummary {
|
||||
id: number;
|
||||
no: string;
|
||||
status: ItpStatus;
|
||||
done: number;
|
||||
total: number;
|
||||
holds: number;
|
||||
}
|
||||
|
||||
export interface QcFolderProject {
|
||||
id: number;
|
||||
number: string;
|
||||
name: string | null;
|
||||
customer: string | null;
|
||||
po: string | null;
|
||||
status: ProjectStatus;
|
||||
}
|
||||
|
||||
export interface QcFolderUnitRow {
|
||||
id: number;
|
||||
number: string;
|
||||
lot: string;
|
||||
name: string | null;
|
||||
status: ProjectStatus;
|
||||
docs: number;
|
||||
itp: ItpSummary | null;
|
||||
has_itp_doc: boolean;
|
||||
}
|
||||
|
||||
export interface QcProjectFolder {
|
||||
project: QcFolderProject;
|
||||
docs: QcDoc[];
|
||||
superseded: QcDoc[];
|
||||
units: QcFolderUnitRow[];
|
||||
}
|
||||
|
||||
export interface QcUnitFolder {
|
||||
unit: { id: number; number: string; lot: string; name: string | null; status: ProjectStatus; po: string | null };
|
||||
project: QcFolderProject;
|
||||
docs: QcDoc[];
|
||||
inherited: QcDoc[];
|
||||
superseded: QcDoc[];
|
||||
itp: ItpSummary | null;
|
||||
no_itp: boolean;
|
||||
}
|
||||
|
||||
export type QcDocBody = ({ project_id: number; unit_id?: never } | { unit_id: number; project_id?: never }) & {
|
||||
type: string;
|
||||
title?: string;
|
||||
rev?: string;
|
||||
note?: string;
|
||||
asset_id: string;
|
||||
replaces?: number;
|
||||
} & Guarded;
|
||||
|
||||
/* ---------- Digital ITP ---------- */
|
||||
|
||||
export type ItpStatus = "open" | "complete";
|
||||
export type ItpPageCode = "WP" | "C" | "FA";
|
||||
|
||||
export interface ItpItem {
|
||||
n: string;
|
||||
t: string;
|
||||
code: string;
|
||||
cust: string;
|
||||
crit: string;
|
||||
}
|
||||
|
||||
export interface ItpSection {
|
||||
n: string;
|
||||
t: string;
|
||||
page: ItpPageCode | string;
|
||||
sig?: boolean;
|
||||
final?: boolean;
|
||||
items: ItpItem[];
|
||||
}
|
||||
|
||||
export interface ItpTemplate {
|
||||
ver: string;
|
||||
legend: string;
|
||||
sections: ItpSection[];
|
||||
}
|
||||
|
||||
export interface ItpReject {
|
||||
at: string;
|
||||
by: QcWho | null;
|
||||
ncr: string | null;
|
||||
note: string | null;
|
||||
kind: "reject" | "ncr";
|
||||
}
|
||||
|
||||
export interface ItpLine {
|
||||
key: string;
|
||||
result: "approve" | "reject" | "na" | null;
|
||||
na: boolean;
|
||||
by: QcWho | null;
|
||||
initials: string | null;
|
||||
at: string | null;
|
||||
ncr: string | null;
|
||||
/** Rejected, no NCR yet. */
|
||||
ncr_missing: boolean;
|
||||
note: string | null;
|
||||
cust_code: string | null;
|
||||
cust_by: string | null;
|
||||
cust_at: string | null;
|
||||
cust_rec: QcWho | null;
|
||||
rejects: ItpReject[];
|
||||
row_version: number;
|
||||
}
|
||||
|
||||
export interface ItpBlock {
|
||||
key: string;
|
||||
sections: string[];
|
||||
/** "Sections 1–3" or "Section 4". */
|
||||
scope: string;
|
||||
final: boolean;
|
||||
ready: boolean;
|
||||
all_na: boolean;
|
||||
signed: boolean;
|
||||
signed_at: string | null;
|
||||
signed_by: QcWho | null;
|
||||
qn: string | null;
|
||||
remarks: string | null;
|
||||
cust_name: string | null;
|
||||
cust_at: string | null;
|
||||
cust_by: QcWho | null;
|
||||
row_version: number;
|
||||
}
|
||||
|
||||
export interface ItpHoldRow {
|
||||
n: string;
|
||||
t: string;
|
||||
page: string;
|
||||
codes: string[];
|
||||
rejected: boolean;
|
||||
ncr: string | null;
|
||||
}
|
||||
|
||||
export interface ItpLogRow {
|
||||
id: number;
|
||||
at: string;
|
||||
by: QcWho | null;
|
||||
by_name: string | null;
|
||||
action: string;
|
||||
note: string | null;
|
||||
signed: boolean;
|
||||
}
|
||||
|
||||
export interface ItpHeader {
|
||||
client: string;
|
||||
pname: string;
|
||||
pno: string;
|
||||
po: string;
|
||||
unit: string;
|
||||
}
|
||||
|
||||
export interface Itp {
|
||||
id: number;
|
||||
unit_id: number;
|
||||
no: string;
|
||||
hdr: ItpHeader;
|
||||
status: ItpStatus;
|
||||
version: number;
|
||||
template: ItpTemplate;
|
||||
pages: Array<{ code: string; name: string }>;
|
||||
lines: Record<string, ItpLine>;
|
||||
blocks: ItpBlock[];
|
||||
progress: { done: number; total: number; holds: number };
|
||||
holds: ItpHoldRow[];
|
||||
signoff: { at: string; by: QcWho | null; asset_id: string | null } | null;
|
||||
reopen: { at: string; by: QcWho | null; reason: string } | null;
|
||||
created_at: string;
|
||||
created_by: QcWho | null;
|
||||
can: { edit: boolean; sign_off: boolean; reopen: boolean };
|
||||
log: ItpLogRow[];
|
||||
}
|
||||
|
||||
/** GET /api/units/{id}/itp when the unit has none. */
|
||||
export interface NoItp {
|
||||
itp: null;
|
||||
unit: { id: number; number: string };
|
||||
}
|
||||
|
||||
export type HeaderField = "no" | keyof ItpHeader;
|
||||
|
||||
export interface ItpPatch {
|
||||
set: Partial<Record<HeaderField, string>>;
|
||||
base: Partial<Record<HeaderField, string>>;
|
||||
}
|
||||
|
||||
export interface LinePatch {
|
||||
na?: boolean;
|
||||
cust_code?: string;
|
||||
cust_by?: string;
|
||||
cust_at?: string | null;
|
||||
note?: string;
|
||||
}
|
||||
|
||||
export interface BlockPatch {
|
||||
qn?: string;
|
||||
remarks?: string;
|
||||
cust_name?: string;
|
||||
cust_at?: string;
|
||||
cust_clear?: true;
|
||||
}
|
||||
|
||||
/* ---------- normalising ---------- */
|
||||
|
||||
function list<T>(raw: unknown): T[] {
|
||||
return Array.isArray(raw) ? (raw as T[]) : [];
|
||||
}
|
||||
|
||||
export function normaliseHome(raw: Partial<QcHome> | null): QcHome {
|
||||
const cols = (raw?.columns ?? {}) as Partial<Record<QcColumn, unknown>>;
|
||||
const columns = Object.fromEntries(QC_COLUMNS.map((c) => [c, list<QcCard>(cols[c])])) as QcHome["columns"];
|
||||
return {
|
||||
kpis: { waiting: 0, accepted_7d: 0, rejected_7d: 0, ...(raw?.kpis ?? {}) },
|
||||
columns,
|
||||
folders: list<QcFolderCard>(raw?.folders),
|
||||
};
|
||||
}
|
||||
|
||||
function normaliseProjectFolder(raw: QcProjectFolder): QcProjectFolder {
|
||||
return { ...raw, docs: list(raw.docs), superseded: list(raw.superseded), units: list(raw.units) };
|
||||
}
|
||||
|
||||
function normaliseUnitFolder(raw: QcUnitFolder): QcUnitFolder {
|
||||
return { ...raw, docs: list(raw.docs), inherited: list(raw.inherited), superseded: list(raw.superseded), itp: raw.itp ?? null };
|
||||
}
|
||||
|
||||
/** Every list present, so the screen never trips on a missing field. */
|
||||
export function normaliseItp(raw: Itp): Itp {
|
||||
const lines: Record<string, ItpLine> = {};
|
||||
for (const [k, l] of Object.entries(raw.lines ?? {})) lines[k] = { ...l, rejects: list(l.rejects) };
|
||||
return {
|
||||
...raw,
|
||||
hdr: { client: "", pname: "", pno: "", po: "", unit: "", ...((raw.hdr ?? {}) as Partial<ItpHeader>) },
|
||||
template: { ...raw.template, sections: list(raw.template?.sections) },
|
||||
pages: list(raw.pages),
|
||||
lines,
|
||||
blocks: list<ItpBlock>(raw.blocks).map((b) => ({ ...b, sections: list(b.sections) })),
|
||||
progress: { done: 0, total: 0, holds: 0, ...((raw.progress ?? {}) as Partial<Itp["progress"]>) },
|
||||
holds: list(raw.holds),
|
||||
log: list(raw.log),
|
||||
can: { edit: false, sign_off: false, reopen: false, ...((raw.can ?? {}) as Partial<Itp["can"]>) },
|
||||
};
|
||||
}
|
||||
|
||||
function isItp(raw: Itp | NoItp | null): raw is Itp {
|
||||
return !!raw && "id" in raw && typeof (raw as Itp).id === "number";
|
||||
}
|
||||
|
||||
/* ---------- calls ---------- */
|
||||
|
||||
const enc = encodeURIComponent;
|
||||
|
||||
export async function getQcHome(q: QcHomeQuery = {}, signal?: AbortSignal): Promise<QcHome> {
|
||||
return normaliseHome(await api.get<Partial<QcHome> | null>(`/api/qc/home${qs({ ...q })}`, signal));
|
||||
}
|
||||
|
||||
export async function getFolders(show: "active" | "all" = "active", signal?: AbortSignal): Promise<QcFolderCard[]> {
|
||||
const raw = await api.get<unknown>(`/api/qc/folders${qs({ show })}`, signal);
|
||||
if (Array.isArray(raw)) return raw as QcFolderCard[];
|
||||
return list<QcFolderCard>((raw as { folders?: unknown } | null)?.folders);
|
||||
}
|
||||
|
||||
export async function getProjectFolder(id: number, signal?: AbortSignal): Promise<QcProjectFolder> {
|
||||
return normaliseProjectFolder(await api.get<QcProjectFolder>(`/api/qc/folders/projects/${id}`, signal));
|
||||
}
|
||||
|
||||
export async function getUnitFolder(id: number, signal?: AbortSignal): Promise<QcUnitFolder> {
|
||||
return normaliseUnitFolder(await api.get<QcUnitFolder>(`/api/qc/folders/units/${id}`, signal));
|
||||
}
|
||||
|
||||
/** Answers the folder read of the scope the document went into. */
|
||||
export async function addQcDoc(body: QcDocBody): Promise<QcProjectFolder | QcUnitFolder> {
|
||||
const raw = await api.post<QcProjectFolder | QcUnitFolder>("/api/qc/docs", body);
|
||||
return "unit" in raw ? normaliseUnitFolder(raw) : normaliseProjectFolder(raw);
|
||||
}
|
||||
|
||||
/** The unit's ITP, or null when it has none yet. */
|
||||
export async function getItp(unitId: number, signal?: AbortSignal): Promise<Itp | null> {
|
||||
const raw = await api.get<Itp | NoItp | null>(`/api/units/${unitId}/itp`, signal);
|
||||
return isItp(raw) ? normaliseItp(raw) : null;
|
||||
}
|
||||
|
||||
const itp = async (p: Promise<Itp>) => normaliseItp(await p);
|
||||
|
||||
export const startItp = (unitId: number, g: Guarded = {}) => itp(api.post<Itp>(`/api/units/${unitId}/itp`, { ...g }));
|
||||
|
||||
export const patchItp = (id: number, body: ItpPatch, g: Guarded = {}) => itp(api.patch<Itp>(`/api/itps/${id}`, { ...body, ...g }));
|
||||
|
||||
/** Approve (initial) a line. `rowVersion` makes it a compare-and-set. */
|
||||
export const signLine = (id: number, key: string, rowVersion?: number, g: Guarded = {}) =>
|
||||
itp(
|
||||
request<Itp>(`/api/itps/${id}/lines/${enc(key)}/sign`, {
|
||||
method: "POST",
|
||||
body: { ...g },
|
||||
...(rowVersion !== undefined ? { headers: { "If-Match": String(rowVersion) } } : {}),
|
||||
}),
|
||||
);
|
||||
|
||||
export const unsignLine = (id: number, key: string, g: Guarded = {}) => itp(api.post<Itp>(`/api/itps/${id}/lines/${enc(key)}/unsign`, { ...g }));
|
||||
|
||||
export const patchLine = (id: number, key: string, body: LinePatch, g: Guarded = {}) =>
|
||||
itp(api.patch<Itp>(`/api/itps/${id}/lines/${enc(key)}`, { ...body, ...g }));
|
||||
|
||||
export const sectionNa = (id: number, n: string, na: boolean, g: Guarded = {}) =>
|
||||
itp(api.post<Itp>(`/api/itps/${id}/sections/${enc(n)}/na`, { na, ...g }));
|
||||
|
||||
export const rejectLine = (id: number, key: string, body: { ncr?: string; note?: string }, g: Guarded = {}) =>
|
||||
itp(api.post<Itp>(`/api/itps/${id}/lines/${enc(key)}/reject`, { ...body, ...g }));
|
||||
|
||||
export const enterNcr = (id: number, key: string, ncr: string, g: Guarded = {}) =>
|
||||
itp(api.post<Itp>(`/api/itps/${id}/lines/${enc(key)}/ncr`, { ncr, ...g }));
|
||||
|
||||
export const patchBlock = (id: number, key: string, body: BlockPatch, g: Guarded = {}) =>
|
||||
itp(api.patch<Itp>(`/api/itps/${id}/blocks/${enc(key)}`, { ...body, ...g }));
|
||||
|
||||
export const signBlock = (id: number, key: string, sign: SignBody | undefined, g: Guarded = {}) =>
|
||||
itp(api.post<Itp>(`/api/itps/${id}/blocks/${enc(key)}/sign`, { ...g, ...(sign ? { sign } : {}) }));
|
||||
|
||||
export const unsignBlock = (id: number, key: string, g: Guarded = {}) =>
|
||||
itp(request<Itp>(`/api/itps/${id}/blocks/${enc(key)}/sign`, { method: "DELETE", body: { ...g } }));
|
||||
|
||||
export const signOff = (id: number, sign: SignBody | undefined, g: Guarded = {}) =>
|
||||
itp(api.post<Itp>(`/api/itps/${id}/signoff`, { ...g, ...(sign ? { sign } : {}) }));
|
||||
|
||||
export const reopenItp = (id: number, body: { reason: string; it_reason?: string }) => itp(api.post<Itp>(`/api/itps/${id}/reopen`, body));
|
||||
|
||||
export const qcApi = {
|
||||
getQcHome,
|
||||
getFolders,
|
||||
getProjectFolder,
|
||||
getUnitFolder,
|
||||
addQcDoc,
|
||||
getItp,
|
||||
startItp,
|
||||
patchItp,
|
||||
signLine,
|
||||
unsignLine,
|
||||
patchLine,
|
||||
sectionNa,
|
||||
rejectLine,
|
||||
enterNcr,
|
||||
patchBlock,
|
||||
signBlock,
|
||||
unsignBlock,
|
||||
signOff,
|
||||
reopenItp,
|
||||
};
|
||||
+144
-3
@@ -293,6 +293,137 @@ export interface PacketDoc {
|
||||
added_at: string | null;
|
||||
added_by: WhoRef | null;
|
||||
url: string;
|
||||
/** Phase 6: the kind's label from the closed set. */
|
||||
kind_label?: string;
|
||||
note?: string | null;
|
||||
file_name?: string | null;
|
||||
size?: number | null;
|
||||
/** Phase 6: a document taken out of the data book. The row stays as a record. */
|
||||
removed?: { at: string; by: WhoRef | null; reason: string | null } | null;
|
||||
}
|
||||
|
||||
/* ---------- Phase 6: Q.C. on the packet ---------- */
|
||||
|
||||
/** One Q.C. check of the open inspection. */
|
||||
export interface QcCheckEntry {
|
||||
done: boolean;
|
||||
at?: string | null;
|
||||
by?: string | null;
|
||||
by_name?: string | null;
|
||||
stamp?: string | null;
|
||||
}
|
||||
|
||||
/** The visual exam of one weld, keyed by the weld number as text. An empty result means cleared. */
|
||||
export interface QcWeldExam {
|
||||
result?: "accept" | "reject" | "" | null;
|
||||
note?: string | null;
|
||||
at?: string | null;
|
||||
by?: string | null;
|
||||
by_name?: string | null;
|
||||
}
|
||||
|
||||
/** The open Q.C. inspection (`qc` on the packet read). */
|
||||
export interface QcInspection {
|
||||
id: number;
|
||||
sent_at: string | null;
|
||||
sent_by?: WhoRef | null;
|
||||
book: boolean | null;
|
||||
spool: boolean | null;
|
||||
location: string | null;
|
||||
sent_note: string | null;
|
||||
nde_method: string | null;
|
||||
nde_result: string | null;
|
||||
nde_report: string | null;
|
||||
hardness: string | null;
|
||||
inspector: string | null;
|
||||
stamp: string | null;
|
||||
notes: string | null;
|
||||
result: "accepted" | "rejected" | null;
|
||||
result_at: string | null;
|
||||
result_note: string | null;
|
||||
result_by?: WhoRef | null;
|
||||
row_version: number;
|
||||
checks: Record<string, QcCheckEntry>;
|
||||
weld_exam: Record<string, QcWeldExam>;
|
||||
ncr?: string | null;
|
||||
/** The packet's WPS matches /NACE/i. */
|
||||
nace_label?: boolean;
|
||||
/** What Q.C. still has to record before an accept (the server's qc_missing). */
|
||||
missing?: string[];
|
||||
}
|
||||
|
||||
/** One closed inspection (`qc_history[]` and `qc_last`). */
|
||||
export interface QcHistoryRow {
|
||||
id: number;
|
||||
at: string | null;
|
||||
result: "accepted" | "rejected" | string | null;
|
||||
inspector: string | null;
|
||||
note: string | null;
|
||||
dept_code: string | null;
|
||||
ncr?: string | null;
|
||||
rejected_welds?: string[];
|
||||
by?: WhoRef | null;
|
||||
location?: string | null;
|
||||
sent_at?: string | null;
|
||||
}
|
||||
|
||||
/** One Q.C. check item with the old qcTab hint. */
|
||||
export interface QcItem {
|
||||
group: string;
|
||||
key: string;
|
||||
text: string;
|
||||
hint?: string | null;
|
||||
}
|
||||
|
||||
/** A current Q.C. folder document of the packet's unit or project. */
|
||||
export interface QcFolderDoc {
|
||||
id: number;
|
||||
type: string;
|
||||
title: string | null;
|
||||
rev: string | null;
|
||||
url: string;
|
||||
scope: "unit" | "project";
|
||||
added_at: string | null;
|
||||
}
|
||||
|
||||
/** The unit's ITP summary. */
|
||||
export interface ItpSummary {
|
||||
id: number;
|
||||
no: string | null;
|
||||
status: "open" | "complete" | string;
|
||||
done: number;
|
||||
total: number;
|
||||
holds: number;
|
||||
}
|
||||
|
||||
/** One open hold, witness or rejected point on the packet's ITP pages. */
|
||||
export interface ItpHoldItem {
|
||||
n: string;
|
||||
t: string;
|
||||
codes: string[];
|
||||
rejected: boolean;
|
||||
ncr: string | null;
|
||||
}
|
||||
|
||||
/** The open ITP hold points for the packet's department (a warning only). */
|
||||
export interface ItpHold {
|
||||
no: string | null;
|
||||
count: number;
|
||||
rejected: boolean;
|
||||
/** The department's page codes, or null for every page. */
|
||||
pages: string[] | null;
|
||||
items: ItpHoldItem[];
|
||||
}
|
||||
|
||||
/** PATCH /api/packets/{id}/qc: the scalar inspection fields. */
|
||||
export interface QcFields {
|
||||
inspector: string | null;
|
||||
stamp: string | null;
|
||||
nde_method: string | null;
|
||||
nde_result: string | null;
|
||||
nde_report: string | null;
|
||||
hardness: string | null;
|
||||
notes: string | null;
|
||||
}
|
||||
|
||||
/** GET /api/packets/{id} and every packet mutation answer. Children the phase 2 screens do not use stay loose. */
|
||||
@@ -343,8 +474,8 @@ export interface PacketRead {
|
||||
stage: Stage | null;
|
||||
stages: Stage[];
|
||||
wh: WhInfo | null;
|
||||
qc: Record<string, unknown> | null;
|
||||
qc_history: unknown[];
|
||||
qc: QcInspection | null;
|
||||
qc_history: QcHistoryRow[];
|
||||
change_orders: ChangeOrder[];
|
||||
open_cos: number[];
|
||||
open_shorts: number;
|
||||
@@ -354,7 +485,7 @@ export interface PacketRead {
|
||||
blocked: Record<string, string[]>;
|
||||
holder: string;
|
||||
checklist_items: Array<{ group: string; key: string; text: string }>;
|
||||
qc_items: unknown[];
|
||||
qc_items: QcItem[];
|
||||
flag: string | null;
|
||||
/** Phase 5: the database now(), to tick the clock from. */
|
||||
server_now?: string | null;
|
||||
@@ -364,6 +495,14 @@ export interface PacketRead {
|
||||
labor?: Labor | null;
|
||||
/** Phase 5: the borrowed heat map of a sibling packet, read-only. */
|
||||
heat_src?: HeatSrc | null;
|
||||
/** Phase 6: the newest closed inspection, or null. */
|
||||
qc_last?: QcHistoryRow | null;
|
||||
/** Phase 6: the current Q.C. folder documents of the unit and the project. */
|
||||
qc_folder_docs?: QcFolderDoc[];
|
||||
/** Phase 6: the unit's ITP summary. */
|
||||
itp?: ItpSummary | null;
|
||||
/** Phase 6: open ITP hold points for this packet's department. */
|
||||
itp_hold?: ItpHold | null;
|
||||
}
|
||||
|
||||
/* ---------- Phase 5: the shop on the packet ---------- */
|
||||
@@ -585,6 +724,8 @@ export interface TransitionBody {
|
||||
spool?: boolean;
|
||||
location?: string;
|
||||
for_sup_id?: string;
|
||||
/** Phase 6: QC_REJECT. An NCR number like NCR-2026-0021. */
|
||||
ncr?: string;
|
||||
sign?: SignBody;
|
||||
it_reason?: string;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user