drawings: on a rev mismatch, Clerical can keep the retired rev VISUAL calls (#41)
ci / server (pull_request) Failing after 17s
ci / frontend (pull_request) Successful in 42s
ci / image (pull_request) Skipped
ci / e2e (pull_request) Skipped

When VISUAL calls a rev that M-Files has retired, the draft is still
flagged rev_mismatch, but the drawing job now also copies the rev VISUAL
calls (MFilesKit 0.1.3 DrawingLookup.revision) into drawing_revisions and
visual.called. A Clerical Supervisor picks either rev on the packet:
"Use latest rev N" as before, or "Use rev V as VISUAL calls it"
(rev-approve with use=called), with a reason and a signature.

The kept rev holds while M-Files' Effective rev stays the one chosen
against: no flag, no change order. A newer Effective rev flags the
packet again; a new VISUAL rev clears the choice.

Migration 0009 adds drawing_revisions. Needs mfileskit 0.1.3.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
2026-10-19 13:39:14 -06:00
co-authored by Claude Opus 5.5
parent 4c0cba4da5
commit bb8eb17748
21 changed files with 547 additions and 55 deletions
+43
View File
@@ -221,6 +221,49 @@ def test_rev_approve(client: TestClient, fx: Settings) -> None:
assert p["drawing_state"] == "attached" and "rev_mismatch" not in p["flags"]
def test_rev_approve_keeps_the_rev_visual_calls(client: TestClient, fx: Settings) -> None:
run_feed(fx, 1)
run_drawings(fx)
with app_conn(fx) as conn:
pid = packet_id(conn, gen.KEY_TWO_OPS) # VISUAL rev 2 (Retired in M-Files), M-Files rev 3
nf = packet_id(conn, gen.KEY_NOT_FOUND)
body = {"reason": "Engineering says build lot 1 to rev 2", "sign": SIGN, "use": "called"}
as_user(client, "clerk.leo")
r = client.post(f"/api/packets/{nf}/rev-approve", json=body)
assert r.status_code == 409 and r.json()["code"] == "nothing_staged", r.text
r = client.post(f"/api/packets/{pid}/rev-approve", json={**body, "use": "oldest"})
assert r.status_code == 422, r.text
r = client.post(f"/api/packets/{pid}/rev-approve", json=body)
assert r.status_code == 200, r.text
with app_conn(fx) as conn:
p = packet_row(conn, pid)
assert p["drawing_state"] == "attached" and "rev_mismatch" not in p["flags"]
ok = p["visual"]["rev_ok"]
assert (ok["rev"], ok["vis_rev"], ok["use"], ok["latest"], ok["controlled"]) == (
"2",
"2",
"called",
"3",
"Retired",
)
assert p["visual"]["rev_confirmed"] is True
pages = drawing_pages(conn, pid)
assert pages and all(pg[1] == "2" for pg in pages)
log = conn.execute(
"SELECT note FROM packet_log WHERE packet_id = %s AND action = 'Rev VISUAL calls kept by hand'", (pid,)
).fetchall()
assert len(log) == 1 and "Retired in M-Files" in log[0][0] and "M-Files has rev 3" in log[0][0]
# the drawing job leaves the kept rev alone while M-Files' Effective rev is still 3
with app_conn(fx) as conn:
conn.execute("UPDATE drawing_lookups SET checked_at = now() - interval '2 days', next_check_at = now()")
run_drawings(fx)
with app_conn(fx) as conn:
p = packet_row(conn, pid)
assert p["drawing_state"] == "attached" and "rev_mismatch" not in p["flags"]
assert p["visual"]["rev_confirmed"] is True and p["visual"].get("called") is None
assert all(pg[1] == "2" for pg in drawing_pages(conn, pid))
# ---------------------------------------------------------------- hand uploads