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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user