From 95cb73d91a23c60b6661ac03b023c6320c6911bd Mon Sep 17 00:00:00 2001 From: gsinghpal Date: Tue, 12 May 2026 13:36:29 -0400 Subject: [PATCH] feat(numbering): wire NCR, CAPA, Hold, RMA into parent-numbered mixin MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Hold derives parent via job_id.sale_order_id; RMA via sale_order_id directly — both get HOLD- / RMA- names. NCR and CAPA have no SO link in core, so they fall back to their legacy sequences (NCR/YYYY/NNN, CAPA/YYYY/NNN); future modules can override the _fp_parent_sale_order hook to enable parent naming. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../fusion_plating_quality/__manifest__.py | 2 +- .../fusion_plating_quality/models/fp_capa.py | 34 +++++++++++++++++-- .../fusion_plating_quality/models/fp_ncr.py | 33 ++++++++++++++++-- .../models/fp_quality_hold.py | 28 +++++++++++++-- .../fusion_plating_quality/models/fp_rma.py | 28 +++++++++++++-- 5 files changed, 112 insertions(+), 13 deletions(-) diff --git a/fusion_plating/fusion_plating_quality/__manifest__.py b/fusion_plating/fusion_plating_quality/__manifest__.py index 1533a00a..612e9127 100644 --- a/fusion_plating/fusion_plating_quality/__manifest__.py +++ b/fusion_plating/fusion_plating_quality/__manifest__.py @@ -5,7 +5,7 @@ { 'name': 'Fusion Plating — Quality (QMS)', - 'version': '19.0.4.12.2', + 'version': '19.0.4.13.0', 'category': 'Manufacturing/Plating', 'summary': 'Native QMS for plating shops: NCR, CAPA, calibration, AVL, FAIR, ' 'internal audits, customer specs, document control. CE + EE compatible.', diff --git a/fusion_plating/fusion_plating_quality/models/fp_capa.py b/fusion_plating/fusion_plating_quality/models/fp_capa.py index e3fc74fb..1e755274 100644 --- a/fusion_plating/fusion_plating_quality/models/fp_capa.py +++ b/fusion_plating/fusion_plating_quality/models/fp_capa.py @@ -16,7 +16,7 @@ class FpCapa(models.Model): """ _name = 'fusion.plating.capa' _description = 'Fusion Plating — Corrective / Preventive Action' - _inherit = ['mail.thread', 'mail.activity.mixin'] + _inherit = ['mail.thread', 'mail.activity.mixin', 'fp.parent.numbered.mixin'] _order = 'due_date asc, id desc' name = fields.Char( @@ -100,6 +100,23 @@ class FpCapa(models.Model): ) active = fields.Boolean(default=True) + # ------------------------------------------------------------------ + # Parent-numbered mixin hooks + # CAPAs reach SO via ncr_id → fp.job link if present (jobless NCRs + # fall back to legacy sequence). + # ------------------------------------------------------------------ + def _fp_parent_sale_order(self): + # CAPA usually flows from an NCR. If the NCR has a job-back-link + # (added by future modules), we can reach SO through it. For now + # there's no link in core — falls back to legacy seq. + return self.env['sale.order'] + + def _fp_name_prefix(self): + return 'CAPA' + + def _fp_parent_counter_field(self): + return 'x_fc_pn_capa_count' + @api.model def _default_name(self): seq = self.env['ir.sequence'].next_by_code('fusion.plating.capa') @@ -109,8 +126,19 @@ class FpCapa(models.Model): def create(self, vals_list): for vals in vals_list: if not vals.get('name') or vals.get('name') == '/': - vals['name'] = self._default_name() - return super().create(vals_list) + vals['name'] = 'New' + records = super().create(vals_list) + for rec in records: + if rec.name and rec.name != 'New': + continue + if not rec._fp_assign_parent_name(): + seq = self.env['ir.sequence'].next_by_code('fusion.plating.capa') or 'New' + self.env.cr.execute( + "UPDATE fusion_plating_capa SET name = %s WHERE id = %s", + (seq, rec.id), + ) + rec.invalidate_recordset(['name']) + return records @api.depends('due_date', 'state') def _compute_is_overdue(self): diff --git a/fusion_plating/fusion_plating_quality/models/fp_ncr.py b/fusion_plating/fusion_plating_quality/models/fp_ncr.py index c85823ef..5656763d 100644 --- a/fusion_plating/fusion_plating_quality/models/fp_ncr.py +++ b/fusion_plating/fusion_plating_quality/models/fp_ncr.py @@ -17,7 +17,7 @@ class FpNcr(models.Model): """ _name = 'fusion.plating.ncr' _description = 'Fusion Plating — Non-Conformance Report' - _inherit = ['mail.thread', 'mail.activity.mixin'] + _inherit = ['mail.thread', 'mail.activity.mixin', 'fp.parent.numbered.mixin'] _order = 'reported_date desc, id desc' name = fields.Char( @@ -130,6 +130,22 @@ class FpNcr(models.Model): ) active = fields.Boolean(default=True) + # ------------------------------------------------------------------ + # Parent-numbered mixin hooks + # NCRs don't have a direct SO/job link in core yet — falls back to + # legacy fusion.plating.ncr sequence. When a future module adds a + # link, it can override _fp_parent_sale_order to enable parent + # naming retroactively without further changes here. + # ------------------------------------------------------------------ + def _fp_parent_sale_order(self): + return self.env['sale.order'] + + def _fp_name_prefix(self): + return 'NCR' + + def _fp_parent_counter_field(self): + return 'x_fc_pn_ncr_count' + @api.model def _default_name(self): seq = self.env['ir.sequence'].next_by_code('fusion.plating.ncr') @@ -139,8 +155,19 @@ class FpNcr(models.Model): def create(self, vals_list): for vals in vals_list: if not vals.get('name') or vals.get('name') == '/': - vals['name'] = self._default_name() - return super().create(vals_list) + vals['name'] = 'New' + records = super().create(vals_list) + for rec in records: + if rec.name and rec.name != 'New': + continue + if not rec._fp_assign_parent_name(): + seq = self.env['ir.sequence'].next_by_code('fusion.plating.ncr') or 'New' + self.env.cr.execute( + "UPDATE fusion_plating_ncr SET name = %s WHERE id = %s", + (seq, rec.id), + ) + rec.invalidate_recordset(['name']) + return records @api.depends('capa_ids') def _compute_capa_count(self): diff --git a/fusion_plating/fusion_plating_quality/models/fp_quality_hold.py b/fusion_plating/fusion_plating_quality/models/fp_quality_hold.py index ae831910..df211205 100644 --- a/fusion_plating/fusion_plating_quality/models/fp_quality_hold.py +++ b/fusion_plating/fusion_plating_quality/models/fp_quality_hold.py @@ -17,7 +17,7 @@ class FpQualityHold(models.Model): """ _name = 'fusion.plating.quality.hold' _description = 'Fusion Plating — Quality Hold' - _inherit = ['mail.thread', 'mail.activity.mixin'] + _inherit = ['mail.thread', 'mail.activity.mixin', 'fp.parent.numbered.mixin'] _order = 'create_date desc' name = fields.Char( @@ -124,6 +124,17 @@ class FpQualityHold(models.Model): # ------------------------------------------------------------------ # Defaults / create # ------------------------------------------------------------------ + # Parent-numbered mixin hooks. Holds reach the SO through their + # linked fp.job (the standard authoring path on the shop floor). + def _fp_parent_sale_order(self): + return self.job_id.sale_order_id if self.job_id else self.env['sale.order'] + + def _fp_name_prefix(self): + return 'HOLD' + + def _fp_parent_counter_field(self): + return 'x_fc_pn_hold_count' + @api.model def _default_name(self): seq = self.env['ir.sequence'].next_by_code( @@ -135,8 +146,19 @@ class FpQualityHold(models.Model): def create(self, vals_list): for vals in vals_list: if not vals.get('name') or vals.get('name') == '/': - vals['name'] = self._default_name() - return super().create(vals_list) + vals['name'] = 'New' + records = super().create(vals_list) + for rec in records: + if rec.name and rec.name != 'New': + continue + if not rec._fp_assign_parent_name(): + seq = self.env['ir.sequence'].next_by_code('fusion.plating.quality.hold') or 'New' + self.env.cr.execute( + "UPDATE fusion_plating_quality_hold SET name = %s WHERE id = %s", + (seq, rec.id), + ) + rec.invalidate_recordset(['name']) + return records # ------------------------------------------------------------------ # Actions diff --git a/fusion_plating/fusion_plating_quality/models/fp_rma.py b/fusion_plating/fusion_plating_quality/models/fp_rma.py index d032966d..60c270e9 100644 --- a/fusion_plating/fusion_plating_quality/models/fp_rma.py +++ b/fusion_plating/fusion_plating_quality/models/fp_rma.py @@ -36,7 +36,7 @@ _logger = logging.getLogger(__name__) class FpRma(models.Model): _name = 'fusion.plating.rma' _description = 'Fusion Plating — Return Material Authorisation' - _inherit = ['mail.thread', 'mail.activity.mixin'] + _inherit = ['mail.thread', 'mail.activity.mixin', 'fp.parent.numbered.mixin'] _order = 'create_date desc, id desc' _rec_name = 'name' @@ -243,6 +243,17 @@ class FpRma(models.Model): # ------------------------------------------------------------------ # Defaults / create / name # ------------------------------------------------------------------ + # Parent-numbered mixin hooks. RMAs reach the SO directly via + # sale_order_id (set at create-time from the original order). + def _fp_parent_sale_order(self): + return self.sale_order_id + + def _fp_name_prefix(self): + return 'RMA' + + def _fp_parent_counter_field(self): + return 'x_fc_pn_rma_count' + @api.model def _default_name(self): seq = self.env['ir.sequence'].next_by_code('fusion.plating.rma') @@ -252,8 +263,19 @@ class FpRma(models.Model): def create(self, vals_list): for vals in vals_list: if not vals.get('name') or vals.get('name') == '/': - vals['name'] = self._default_name() - return super().create(vals_list) + vals['name'] = 'New' + records = super().create(vals_list) + for rec in records: + if rec.name and rec.name != 'New': + continue + if not rec._fp_assign_parent_name(): + seq = self.env['ir.sequence'].next_by_code('fusion.plating.rma') or 'New' + self.env.cr.execute( + "UPDATE fusion_plating_rma SET name = %s WHERE id = %s", + (seq, rec.id), + ) + rec.invalidate_recordset(['name']) + return records # ------------------------------------------------------------------ # Computes