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 @@ +