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
+86 -4
View File
@@ -43,7 +43,7 @@ ENUMS = {
"lookup_failed",
"hand",
],
"itp_status": ["open", "signed", "reopened"],
"itp_status": ["open", "signed", "reopened", "complete"],
}
# One minimal INSERT per append-only table, with ids from seed().
@@ -63,6 +63,7 @@ APPEND_INSERTS = {
"INSERT INTO packet_log (packet_id, action, by) VALUES (%(packet)s, 'Packet created by hand', %(user)s)"
),
"itp_rejects": "INSERT INTO itp_rejects (itp_id, line_key, ncr) VALUES (%(itp)s, 'k1', 'NCR-1')",
"itp_log": "INSERT INTO itp_log (itp_id, action, by) VALUES (%(itp)s, 'ITP started', %(user)s)",
"co_verifications": (
"INSERT INTO co_verifications (co_id, dept_code, by, signature_id) VALUES (%(co)s, 'WH', %(user)s, %(sig)s)"
),
@@ -347,9 +348,8 @@ def test_app_can_write_every_table_and_read_every_view(pg_settings: Settings) ->
"INSERT INTO packet_seals (packet_id, hash, key_id, sealed_by) "
"VALUES (%(packet)s, '\\x01', 'abcd1234', %(user)s)",
"INSERT INTO packet_visual_state (packet_id) VALUES (%(packet)s)",
"INSERT INTO packet_docs (packet_id, asset_id, kind, title) VALUES (%(packet)s, %(asset)s, 'mtr', 'MTR')",
"INSERT INTO qc_docs (project_id, unit_id, type, asset_id) "
"VALUES (%(project)s, %(unit)s, 'wps', %(asset)s)",
"INSERT INTO packet_docs (packet_id, asset_id, kind, title) VALUES (%(packet)s, %(asset)s, 'nde', 'RT')",
"INSERT INTO qc_docs (unit_id, type, asset_id) VALUES (%(unit)s, 'WPS / PQR', %(asset)s)",
"INSERT INTO itp_lines (itp_id, key, result, by) VALUES (%(itp)s, 'k1', 'approve', %(user)s)",
"INSERT INTO itp_blocks (itp_id, block_key, signed_by, signature_id) "
"VALUES (%(itp)s, 'b1', %(user)s, %(sig)s)",
@@ -527,3 +527,85 @@ def test_shop_columns_and_checklist_templates(pg_settings: Settings) -> None:
assert [i["key"] for i in value["weld"]] == ["dwg", "heat", "wps", "fit", "pre", "vt", "stamp", "clean", "qty"]
assert [i["key"] for i in value["plain"]] == ["dwg", "prev", "mat", "work", "insp", "qty"]
assert value["weld"][0]["text"] == "Drawing {drawing} Rev {rev} matches the packet"
def test_qc_columns_checks_and_items(pg_settings: Settings) -> None:
"""0007: the inspection NCR, document notes and kinds, Q.C. folder scope and types, the ITP details, the ITP log
and the Q.C. check items row."""
with db.connect_as("migrate", pg_settings, autocommit=True) as conn:
names = [r[0] for r in conn.execute("SELECT name FROM schema_migrations ORDER BY name").fetchall()]
assert "0007_qc.sql" in names
with app_conn(pg_settings) as conn:
for t, c, kind in (
("qc_inspections", "ncr", "text"),
("packet_docs", "note", "text"),
("qc_docs", "title", "text"),
("qc_docs", "note", "text"),
("qc_docs", "superseded_at", "timestamp with time zone"),
("qc_docs", "superseded_by_user", "uuid"),
("itps", "no", "text"),
("itps", "hdr", "jsonb"),
("itps", "created_at", "timestamp with time zone"),
("itps", "created_by", "uuid"),
("itp_lines", "initials", "text"),
("itp_lines", "cust_code", "text"),
("itp_lines", "cust_by", "text"),
("itp_lines", "cust_at", "date"),
("itp_lines", "cust_rec", "uuid"),
("itp_lines", "cust_rec_at", "timestamp with time zone"),
("itp_rejects", "kind", "text"),
("itp_blocks", "qn", "text"),
("itp_blocks", "remarks", "text"),
("itp_blocks", "cust_name", "text"),
("itp_blocks", "cust_at", "date"),
("itp_blocks", "cust_by", "uuid"),
("itp_blocks", "cust_rec_at", "timestamp with time zone"),
("itp_blocks", "row_version", "integer"),
("itp_log", "it_reason", "text"),
):
got = one(
conn,
"SELECT data_type FROM information_schema.columns WHERE table_name = %s AND column_name = %s",
(t, c),
)
assert got == kind, (t, c)
ids = seed(conn)
ids["asset"] = one(
conn,
"INSERT INTO assets (sha256, size, mime, storage_key, pages) VALUES ('\\x00', 10, 'application/pdf',"
" 'ab/q7', 1) RETURNING id",
)
with pytest.raises(psycopg.errors.CheckViolation), conn.transaction():
conn.execute(
"INSERT INTO packet_docs (packet_id, asset_id, kind) VALUES (%(packet)s, %(asset)s, 'mtr')", ids
)
with pytest.raises(psycopg.errors.CheckViolation), conn.transaction():
conn.execute(
"INSERT INTO qc_docs (project_id, unit_id, type, asset_id) VALUES (%(project)s, %(unit)s, 'ITP',"
" %(asset)s)",
ids,
)
with pytest.raises(psycopg.errors.CheckViolation), conn.transaction():
conn.execute("INSERT INTO qc_docs (unit_id, type, asset_id) VALUES (%(unit)s, 'wps', %(asset)s)", ids)
conn.execute("INSERT INTO qc_docs (project_id, type, asset_id) VALUES (%(project)s, 'ITP', %(asset)s)", ids)
# A reject may come before its NCR number; a block row exists before it is signed.
conn.execute("INSERT INTO itp_rejects (itp_id, line_key) VALUES (%(itp)s, '1.1')", ids)
assert one(conn, "SELECT kind FROM itp_rejects WHERE itp_id = %(itp)s", ids) == "reject"
conn.execute("INSERT INTO itp_blocks (itp_id, block_key) VALUES (%(itp)s, '3')", ids)
assert one(conn, "SELECT signed_at FROM itp_blocks WHERE itp_id = %(itp)s", ids) is None
conn.execute("UPDATE itps SET status = 'complete' WHERE id = %(itp)s", ids)
value = one(conn, "SELECT value FROM settings WHERE key = 'qc_check_items'")
assert [g["group"] for g in value["groups"]] == ["Data book package", "Physical spool"]
keys = [i["key"] for g in value["groups"] for i in g["items"]]
assert keys == [
"db_heats",
"db_mtr",
"db_weldmap",
"db_wps",
"db_ck",
"ph_dim",
"ph_heat",
"ph_stamp",
"ph_clean",
"ph_qty",
]