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,14 +364,41 @@ export class FpJobWorkspace extends Component {
async onFinishStep(step) { async onFinishStep(step) {
if (step.requires_signoff) { if (step.requires_signoff) {
if (this.state.data.user_has_plating_signature) {
// One-tap confirm with a preview of the saved Plating Signature.
this.dialog.add(FpSignatureConfirm, {
title: `Sign to finish ${step.name}`,
contextLabel: `${this.state.data.job.display_wo_name} · Step ${step.sequence_display}: ${step.name}`,
signatureUrl: this.state.data.user_plating_signature,
onConfirm: () => this._commitSignOff(step, null), // use saved sig
onRedraw: () => this._openSignaturePad(step), // draw a new one
});
} else {
// First time — draw once; the backend persists it to the
// user's Plating Signature so later sign-offs are one-tap.
this._openSignaturePad(step);
}
return;
}
// Plain finish — route through /fp/workspace/finish_step which
// returns structured errors so we can show the FpFinishBlockDialog
// for required-inputs failures (with manager bypass option).
await this._callFinishStep(step, /* bypass */ false);
}
_openSignaturePad(step) {
this.dialog.add(FpSignaturePad, { this.dialog.add(FpSignaturePad, {
title: `Sign to finish ${step.name}`, title: `Sign to finish ${step.name}`,
contextLabel: `${this.state.data.job.display_wo_name} · Step ${step.sequence_display}: ${step.name}`, contextLabel: `${this.state.data.job.display_wo_name} · Step ${step.sequence_display}: ${step.name}`,
onSubmit: async (dataUri) => { onSubmit: (dataUri) => this._commitSignOff(step, dataUri),
});
}
async _commitSignOff(step, dataUri) {
try { try {
const res = await fpRpc("/fp/workspace/sign_off", { const res = await fpRpc("/fp/workspace/sign_off", {
step_id: step.id, step_id: step.id,
signature_data_uri: dataUri, signature_data_uri: dataUri, // null -> backend uses the saved signature
}); });
if (res && res.ok) { if (res && res.ok) {
this.notification.add("Step signed off and finished.", { type: "success" }); this.notification.add("Step signed off and finished.", { type: "success" });
@@ -381,14 +409,6 @@ export class FpJobWorkspace extends Component {
} catch (err) { } catch (err) {
this.notification.add(err.message, { type: "danger" }); this.notification.add(err.message, { type: "danger" });
} }
},
});
return;
}
// Plain finish — route through /fp/workspace/finish_step which
// returns structured errors so we can show the FpFinishBlockDialog
// for required-inputs failures (with manager bypass option).
await this._callFinishStep(step, /* bypass */ false);
} }
async _callFinishStep(step, bypassRequiredInputs) { async _callFinishStep(step, bypassRequiredInputs) {