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

@@ -1485,25 +1485,26 @@ class FpJob(models.Model):
def _fp_create_portal_job(self):
"""Create the fusion.plating.portal.job mirror record.
Initial state derived from the fp.job state via the same map
used by write() — so a job that's already 'in_progress' when
the portal mirror is created (e.g. a manual catch-up create)
doesn't reset to 'received'.
Seeded with 'received' then handed to
`fusion.plating.portal.job._fp_recompute_portal_state` — that
helper is the single source of truth for portal state and
derives it from the WO + shipment + invoice signals, so a
catch-up create on an already-in-progress job lands on the
right state rather than stuck on 'received'.
"""
self.ensure_one()
if self.portal_job_id:
return # already exists — idempotent
Portal = self.env['fusion.plating.portal.job'].sudo()
initial_state = self._FP_JOB_STATE_TO_PORTAL_STATE.get(
self.state, 'received',
)
portal = Portal.create({
'name': self.name,
'partner_id': self.partner_id.id,
'state': initial_state,
'state': 'received',
'x_fc_job_id': self.id,
})
self.portal_job_id = portal.id
if hasattr(portal, '_fp_recompute_portal_state'):
portal._fp_recompute_portal_state()
def _fp_create_qc_check_if_needed(self):
"""If customer has x_fc_requires_qc=True, spawn a QC check via
@@ -1528,9 +1529,17 @@ class FpJob(models.Model):
try:
QC.create_for_job(self)
except Exception as e:
# F7 — surface silent failures on the job's chatter so the
# operator sees the gap and creates the QC manually. Logging
# to /var/log/odoo/odoo-server.log alone meant nobody noticed
# (2CM's WH/JOB/00002 silently lost its QC check this way).
_logger.warning(
"Job %s: create_for_job failed: %s", self.name, e,
)
self.message_post(body=_(
'QC check auto-create failed: %(e)s. '
'Create the QC check manually from the Quality menu.'
) % {'e': e})
# ------------------------------------------------------------------
# button_mark_done — Task 2.8
@@ -1745,10 +1754,18 @@ class FpJob(models.Model):
# partner_id is the customer.
Template._dispatch(event, self, partner=self.partner_id)
except Exception as e:
# F7 — surface on chatter. A missed customer notification
# (e.g. "your parts have shipped") is invisible to the
# operator until the customer complains; the chatter post
# gives accounting / sales a recoverable signal.
_logger.warning(
"Job %s: notification %s dispatch failed: %s",
self.name, event, e,
)
self.message_post(body=_(
'Notification dispatch failed for event "%(ev)s": %(e)s. '
'Send manually if the customer expected an update.'
) % {'ev': event, 'e': e})
def _fp_create_delivery(self):
"""Create a draft fusion.plating.delivery linked to this job.
@@ -1787,9 +1804,16 @@ class FpJob(models.Model):
delivery = Delivery.create(vals)
self.delivery_id = delivery.id
except Exception as e:
# F7 — surface on chatter. Without this, the operator sees
# "Job marked done" but no delivery record exists, and the
# next milestone advance fails silently.
_logger.warning(
"Job %s: failed to auto-create delivery: %s", self.name, e,
)
self.message_post(body=_(
'Delivery auto-create failed: %(e)s. '
'Create the delivery manually from the Logistics menu.'
) % {'e': e})
def _fp_resolve_delivery_defaults(self, Delivery):
"""Build the create-vals for a fresh delivery, OR the