feat(fusion_plating_shopfloor): workspace sign-off confirms saved signature, draws only when absent

onFinishStep: if the user has a saved Plating Signature, show FpSignatureConfirm
(one-tap, preview); otherwise open the draw-pad. Factored _openSignaturePad +
_commitSignOff (sends null data URI when using the saved signature).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsinghpal
2026-06-04 00:22:42 -04:00
parent b5a300f439
commit 190b394001

View File

@@ -25,6 +25,7 @@ import { useService } from "@web/core/utils/hooks";
import { WorkflowChip } from "./components/workflow_chip"; import { WorkflowChip } from "./components/workflow_chip";
import { GateViz } from "./components/gate_viz"; import { GateViz } from "./components/gate_viz";
import { FpSignaturePad } from "./components/signature_pad"; import { FpSignaturePad } from "./components/signature_pad";
import { FpSignatureConfirm } from "./components/signature_confirm";
import { FpHoldComposer } from "./components/hold_composer"; import { FpHoldComposer } from "./components/hold_composer";
import { FpTabletLock } from "./tablet_lock"; import { FpTabletLock } from "./tablet_lock";
import { FpRackPartsDialog } from "./rack_parts_dialog"; import { FpRackPartsDialog } from "./rack_parts_dialog";
@@ -38,7 +39,7 @@ import { FileModel } from "@web/core/file_viewer/file_model";
export class FpJobWorkspace extends Component { export class FpJobWorkspace extends Component {
static template = "fusion_plating_shopfloor.JobWorkspace"; static template = "fusion_plating_shopfloor.JobWorkspace";
static props = ["*"]; static props = ["*"];
static components = { WorkflowChip, GateViz, FpSignaturePad, FpHoldComposer, FpTabletLock, FpRackPartsDialog, FpDamageDialog, FpFinishBlockDialog, RackingPanel, FpMovePartsDialog }; static components = { WorkflowChip, GateViz, FpSignaturePad, FpSignatureConfirm, FpHoldComposer, FpTabletLock, FpRackPartsDialog, FpDamageDialog, FpFinishBlockDialog, RackingPanel, FpMovePartsDialog };
setup() { setup() {
this.notification = useService("notification"); this.notification = useService("notification");
@@ -363,26 +364,20 @@ export class FpJobWorkspace extends Component {
async onFinishStep(step) { async onFinishStep(step) {
if (step.requires_signoff) { if (step.requires_signoff) {
this.dialog.add(FpSignaturePad, { if (this.state.data.user_has_plating_signature) {
title: `Sign to finish ${step.name}`, // One-tap confirm with a preview of the saved Plating Signature.
contextLabel: `${this.state.data.job.display_wo_name} · Step ${step.sequence_display}: ${step.name}`, this.dialog.add(FpSignatureConfirm, {
onSubmit: async (dataUri) => { title: `Sign to finish ${step.name}`,
try { contextLabel: `${this.state.data.job.display_wo_name} · Step ${step.sequence_display}: ${step.name}`,
const res = await fpRpc("/fp/workspace/sign_off", { signatureUrl: this.state.data.user_plating_signature,
step_id: step.id, onConfirm: () => this._commitSignOff(step, null), // use saved sig
signature_data_uri: dataUri, onRedraw: () => this._openSignaturePad(step), // draw a new one
}); });
if (res && res.ok) { } else {
this.notification.add("Step signed off and finished.", { type: "success" }); // First time — draw once; the backend persists it to the
await this.refresh(); // user's Plating Signature so later sign-offs are one-tap.
} else { this._openSignaturePad(step);
this.notification.add((res && res.error) || "Sign-off failed", { type: "danger" }); }
}
} catch (err) {
this.notification.add(err.message, { type: "danger" });
}
},
});
return; return;
} }
// Plain finish — route through /fp/workspace/finish_step which // Plain finish — route through /fp/workspace/finish_step which
@@ -391,6 +386,31 @@ export class FpJobWorkspace extends Component {
await this._callFinishStep(step, /* bypass */ false); await this._callFinishStep(step, /* bypass */ false);
} }
_openSignaturePad(step) {
this.dialog.add(FpSignaturePad, {
title: `Sign to finish ${step.name}`,
contextLabel: `${this.state.data.job.display_wo_name} · Step ${step.sequence_display}: ${step.name}`,
onSubmit: (dataUri) => this._commitSignOff(step, dataUri),
});
}
async _commitSignOff(step, dataUri) {
try {
const res = await fpRpc("/fp/workspace/sign_off", {
step_id: step.id,
signature_data_uri: dataUri, // null -> backend uses the saved signature
});
if (res && res.ok) {
this.notification.add("Step signed off and finished.", { type: "success" });
await this.refresh();
} else {
this.notification.add((res && res.error) || "Sign-off failed", { type: "danger" });
}
} catch (err) {
this.notification.add(err.message, { type: "danger" });
}
}
async _callFinishStep(step, bypassRequiredInputs) { async _callFinishStep(step, bypassRequiredInputs) {
try { try {
const res = await rpc("/fp/workspace/finish_step", { const res = await rpc("/fp/workspace/finish_step", {