Fix: hand upload and rev approval lines name the person, not the user id; PLAN questions 53, 54 and the phase 10 note
ci / frontend (pull_request) Successful in 56s
ci / server (pull_request) Successful in 13m10s
ci / image (pull_request) Skipped
ci / e2e (pull_request) Successful in 5m51s

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
2026-10-30 18:18:41 -06:00
co-authored by Claude Opus 5.5
parent a5246e2aa5
commit 1cf37d94e7
6 changed files with 18 additions and 8 deletions
+2
View File
@@ -927,6 +927,7 @@ export interface HandUpload {
name?: string | null;
reason?: string | null;
by?: WhoRef | string | null;
by_name?: string | null;
at?: string | null;
vis_rev?: string | null;
lines?: number;
@@ -974,6 +975,7 @@ export interface VisualInfo {
rev: string | number;
vis_rev?: string | null;
by?: WhoRef | string | null;
by_name?: string | null;
at?: string | null;
reason?: string | null;
/** "called": Clerical kept the rev VISUAL calls over M-Files' Effective rev (`latest`). */
+4 -1
View File
@@ -146,7 +146,7 @@ describe("Packet page, VISUAL box", () => {
it("uploads a hand BOM as multipart with the signature fields", async () => {
const fetch = mockFetch({
...base(visualDraft()),
"POST /api/packets/1/hand/bom": { body: visualDraft({ version: 4, visual: { manual_bom: { name: "bom.csv", reason: "No materials yet", lines: 3 } } }) },
"POST /api/packets/1/hand/bom": { body: visualDraft({ version: 4, visual: { manual_bom: { name: "bom.csv", reason: "No materials yet", lines: 3, by: "cf284ce9-e7a6-45d0-bba6-d46b752d553d", by_name: "Clara Clerk" } } }) },
});
renderApp("/packets/1");
fireEvent.click(await screen.findByRole("button", { name: "Upload BOM" }));
@@ -165,6 +165,9 @@ describe("Packet page, VISUAL box", () => {
expect((fd.get("file") as File).name).toBe("bom.csv");
expect(await screen.findByRole("button", { name: "Use VISUAL's BOM again" })).toBeInTheDocument();
expect(screen.getByText("BOM uploaded by hand:", { exact: false })).toBeInTheDocument();
// The line names the person, never the stored user id.
expect(screen.getByText(/BOM uploaded by hand:.*Clara Clerk/)).toBeInTheDocument();
expect(screen.queryByText(/cf284ce9/)).not.toBeInTheDocument();
});
it("uploads a hand drawing: the asset first, then the attach with reason and sign", async () => {
+7 -5
View File
@@ -21,9 +21,11 @@ type AskReason = (opts: ReasonOptions) => Promise<string | null>;
const isCl = (roles: readonly string[]) => roles.some((r) => r === "clerical" || r === "clerical_supervisor" || r === "admin");
const isClSup = (roles: readonly string[]) => roles.some((r) => r === "clerical_supervisor" || r === "admin");
function whoName(by: WhoRef | string | null | undefined): string {
if (!by) return "";
return typeof by === "string" ? by : by.display_name;
/** The server stores `by` as the user id and the display name beside it in `by_name`. */
function whoName(m: { by?: WhoRef | string | null; by_name?: string | null }): string {
if (m.by_name) return m.by_name;
if (!m.by) return "";
return typeof m.by === "string" ? m.by : m.by.display_name;
}
function foldedText(f: FoldedOp): string {
@@ -126,7 +128,7 @@ function HandLine({ what, m }: { what: string; m: HandUpload }) {
return (
<div className="small">
{what} uploaded by hand: <span className="mono">{m.name ?? ""}</span>
{m.by ? ` - ${whoName(m.by)}` : ""} {m.at ? fmtTime(m.at) : ""}
{m.by ? ` - ${whoName(m)}` : ""} {m.at ? fmtTime(m.at) : ""}
{m.reason ? ` · ${m.reason}` : ""}
{typeof m.lines === "number" ? ` · ${m.lines} line${m.lines === 1 ? "" : "s"}` : ""}
</div>
@@ -237,7 +239,7 @@ export function VisualBox({
<b>Rev {String(v.rev_ok.rev)} approved by hand</b> in place of VISUAL rev {v.rev_ok.vis_rev || "-"}
</>
)}
{v.rev_ok.by ? ` - ${whoName(v.rev_ok.by)}` : ""} {v.rev_ok.at ? fmtTime(v.rev_ok.at) : ""}
{v.rev_ok.by ? ` - ${whoName(v.rev_ok)}` : ""} {v.rev_ok.at ? fmtTime(v.rev_ok.at) : ""}
{v.rev_ok.reason ? `: ${v.rev_ok.reason}` : ""}.
</div>
) : null}