diff --git a/fusion_plating/fusion_plating_bridge_mrp/__manifest__.py b/fusion_plating/fusion_plating_bridge_mrp/__manifest__.py
index 55e962fe..a413733a 100644
--- a/fusion_plating/fusion_plating_bridge_mrp/__manifest__.py
+++ b/fusion_plating/fusion_plating_bridge_mrp/__manifest__.py
@@ -5,7 +5,7 @@
{
"name": "Fusion Plating — MRP Bridge",
- 'version': '19.0.6.3.0',
+ 'version': '19.0.6.4.0',
'category': 'Manufacturing/Plating',
'summary': 'Bridge Fusion Plating facilities, baths and tanks to Odoo MRP work orders.',
'description': """
diff --git a/fusion_plating/fusion_plating_bridge_mrp/models/mrp_workorder.py b/fusion_plating/fusion_plating_bridge_mrp/models/mrp_workorder.py
index 9bd57f1c..9a2180fd 100644
--- a/fusion_plating/fusion_plating_bridge_mrp/models/mrp_workorder.py
+++ b/fusion_plating/fusion_plating_bridge_mrp/models/mrp_workorder.py
@@ -26,6 +26,13 @@ class MrpWorkorder(models.Model):
# ------------------------------------------------------------------
# Plating-specific fields
# ------------------------------------------------------------------
+ x_fc_requires_bath = fields.Boolean(
+ string='Requires Bath/Tank',
+ compute='_compute_requires_bath',
+ store=False,
+ help='True when this WO involves a chemistry bath. Surfaced to '
+ 'the form view so bath/tank fields render as required.',
+ )
x_fc_bath_id = fields.Many2one(
'fusion.plating.bath', string='Bath', tracking=True,
)
@@ -512,10 +519,82 @@ class MrpWorkorder(models.Model):
# ------------------------------------------------------------------
# T2.2 — Certification gate on WO start
+ # T2.3 — Required-field gate (bath/tank for wet WOs, assigned operator)
# ------------------------------------------------------------------
+ WET_FAMILIES = (
+ 'plating', 'pre_treatment', 'post_treatment',
+ 'strip', 'passivation',
+ )
+ # Keyword fallback used when the workcenter / process-type metadata
+ # is missing — covers most shop floor naming conventions. Lowercased.
+ WET_NAME_KEYWORDS = (
+ 'plat', 'nickel', 'chrome', 'anodiz', 'zinc',
+ 'etch', 'clean', 'rinse', 'strip', 'passivat',
+ 'zincate', 'alkalin', 'acid', 'electroless',
+ )
+
+ @api.depends('x_fc_bath_id', 'name', 'workcenter_id')
+ def _compute_requires_bath(self):
+ for wo in self:
+ wo.x_fc_requires_bath = wo._fp_is_wet_process()
+
+ def _fp_is_wet_process(self):
+ """Best-effort check: does this WO involve a chemistry bath?
+
+ Three signals, in priority order:
+ 1. A bath is already linked → definitely wet
+ 2. The workcenter's FP work-centre supports a wet process family
+ 3. The WO's name contains a wet-process keyword
+ """
+ self.ensure_one()
+ if self.x_fc_bath_id:
+ return True
+ wc = self.workcenter_id
+ fpwc = getattr(wc, 'x_fc_fp_work_center_id', False)
+ if fpwc:
+ families = set(fpwc.supported_process_ids.mapped('process_family'))
+ if families & set(self.WET_FAMILIES):
+ return True
+ name = (self.name or '').lower()
+ return any(k in name for k in self.WET_NAME_KEYWORDS)
+
+ def _fp_check_required_fields_before_start(self):
+ """Block button_start if the WO is missing data the shop must
+ record for traceability + compliance.
+
+ Rules:
+ • Every WO needs an assigned operator (x_fc_assigned_user_id) —
+ without it, productivity records can't be attributed and
+ proficiency tracking goes nowhere.
+ • Wet (bath) WOs additionally need x_fc_bath_id + x_fc_tank_id —
+ for chemistry traceability and physical-location audit
+ (which exact tank ran the job).
+ """
+ from odoo.exceptions import UserError
+ for wo in self:
+ missing = []
+ if not wo.x_fc_assigned_user_id:
+ missing.append(_('Assigned Operator'))
+ if wo._fp_is_wet_process():
+ if not wo.x_fc_bath_id:
+ missing.append(_('Bath'))
+ if not wo.x_fc_tank_id:
+ missing.append(_('Tank'))
+ if missing:
+ raise UserError(_(
+ 'Cannot start work order "%(wo)s" — please fill these '
+ 'required fields first:\n • %(fields)s\n\n'
+ 'Open the work order form and have the planner set them.'
+ ) % {
+ 'wo': wo.display_name or wo.name,
+ 'fields': '\n • '.join(missing),
+ })
+
def button_start(self):
"""Block start unless the current user's linked employee holds
- an active certification for this WO's process type."""
+ an active certification for this WO's process type AND every
+ required field for traceability is filled in."""
+ self._fp_check_required_fields_before_start()
self._fp_check_operator_certification()
res = super().button_start()
# Capture audit AFTER the super call so we don't stamp WOs that
diff --git a/fusion_plating/fusion_plating_bridge_mrp/views/mrp_workorder_views.xml b/fusion_plating/fusion_plating_bridge_mrp/views/mrp_workorder_views.xml
index 590a711a..7145ead3 100644
--- a/fusion_plating/fusion_plating_bridge_mrp/views/mrp_workorder_views.xml
+++ b/fusion_plating/fusion_plating_bridge_mrp/views/mrp_workorder_views.xml
@@ -93,8 +93,10 @@
+