feat(plating): session 2026-05-23 deploys — F1/F7/S22/S23 + UI fixes

Consolidated commit of session work already deployed to entech and
verified via the deep audit + the persona walk:

S22 — Signoff gate (fp.job.step.requires_signoff was 100% unenforced,
42/42 done steps had NULL signoff_user_id). Three-piece fix:
_fp_autosign_if_required (captures finisher on button_finish),
_fp_check_signoff_complete (raises UserError if NULL after autosign),
action_signoff (explicit supervisor pre-sign). Bypass:
fp_skip_signoff_gate=True.

S23 — Transition-form gate (same dormant-field shape as S22, caught
preventively before recipe authors flipped requires_transition_form
on). Model helpers on fp.job.step.move + controller gate in
move_controller (parts commit) + pre-reject in rack commit.

F7 — Chatter standardization: _fp_create_qc_check_if_needed,
_fp_fire_notification, _fp_create_delivery silent failures now also
post to job chatter instead of only logging to file.

UI fixes:
- Critical Rule 20 documented + applied: OWL templates only expose
  Math as a global. Calling String(d) inside t-on-click throws
  'v2 is not a function'. Fixed pin_pad.xml (string array instead of
  number array with String() coercion). Also swept parseInt/
  parseFloat in recipe_tree_editor + simple_recipe_editor.
- Notes panel HTML escape fix: chatter messages off /fp/workspace/load
  were rendered via t-out, escaping the HTML. Wrap with markup() in
  job_workspace.js refresh() before assigning to state.

Versions:
  fusion_plating         19.0.20.8.0 → 19.0.20.9.0
  fusion_plating_jobs    19.0.10.20.0 → 19.0.10.23.0
  fusion_plating_shopfloor 19.0.30.2.0 → 19.0.30.5.0

All deployed to entech (LXC 111) and verified live.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsinghpal
2026-05-23 20:37:17 -04:00
parent d6ebcb6233
commit 1a3ca8704e
15 changed files with 597 additions and 142 deletions

View File

@@ -321,7 +321,7 @@
<label>Estimated Duration (min)</label>
<input type="number" class="form-control" min="0" step="1"
t-att-value="state.selectedNode.estimated_duration || 0"
t-on-change="(ev) => { state.selectedNode.estimated_duration = parseFloat(ev.target.value) || 0; }"/>
t-on-change="(ev) => { state.selectedNode.estimated_duration = (+ev.target.value) || 0; }"/>
</div>
<div class="o_fp_re_field">
@@ -380,7 +380,7 @@
<label for="fp_re_workflow_state">Triggers Workflow State</label>
<select id="fp_re_workflow_state"
class="form-select"
t-on-change="(ev) => { state.selectedNode.triggers_workflow_state_id = ev.target.value ? parseInt(ev.target.value, 10) : false; }">
t-on-change="(ev) => { state.selectedNode.triggers_workflow_state_id = ev.target.value ? (+ev.target.value) : false; }">
<option value=""
t-att-selected="!state.selectedNode.triggers_workflow_state_id">
— None (use default-kind matching) —

View File

@@ -199,7 +199,7 @@
t-if="state.workflowStates and state.workflowStates.length">
<label>Triggers Workflow State</label>
<select class="form-select"
t-on-change="(ev) => { state.editTriggersWorkflowStateId = ev.target.value ? parseInt(ev.target.value, 10) : false; }">
t-on-change="(ev) => { state.editTriggersWorkflowStateId = ev.target.value ? (+ev.target.value) : false; }">
<option value="" t-att-selected="!state.editTriggersWorkflowStateId">— None (use Step Type) —</option>
<t t-foreach="state.workflowStates" t-as="ws" t-key="ws.id">
<option t-att-value="ws.id"
@@ -598,7 +598,7 @@
t-if="state.workflowStates and state.workflowStates.length">
<label class="form-label">Triggers Workflow State</label>
<select class="form-select"
t-on-change="(ev) => { state.libraryEditor.triggers_workflow_state_id = ev.target.value ? parseInt(ev.target.value, 10) : false; }">
t-on-change="(ev) => { state.libraryEditor.triggers_workflow_state_id = ev.target.value ? (+ev.target.value) : false; }">
<option value=""
t-att-selected="!state.libraryEditor.triggers_workflow_state_id">
— None (use default-kind matching) —