diff --git a/mrp_bom_attribute_match/models/mrp_bom.py b/mrp_bom_attribute_match/models/mrp_bom.py index 4469178c5..ccb7f4160 100644 --- a/mrp_bom_attribute_match/models/mrp_bom.py +++ b/mrp_bom_attribute_match/models/mrp_bom.py @@ -18,7 +18,10 @@ class MrpBomLine(models.Model): "product.template", "Component (product template)" ) match_on_attribute_ids = fields.Many2many( - "product.attribute", string="Match on Attributes", readonly=True + "product.attribute", + string="Match on Attributes", + compute="_compute_match_on_attribute_ids", + store=True, ) product_uom_category_id = fields.Many2one( "uom.category", @@ -56,84 +59,93 @@ class MrpBomLine(models.Model): @api.onchange("component_template_id") def _onchange_component_template_id(self): if self.component_template_id: + if self.product_id: + self.product_backup_id = self.product_id + self.product_id = False if ( self.product_uom_id.category_id != self.component_template_id.uom_id.category_id ): self.product_uom_id = self.component_template_id.uom_id else: + if self.product_backup_id: + self.product_id = self.product_backup_id + self.product_backup_id = False if self.product_uom_id.category_id != self.product_id.uom_id.category_id: self.product_uom_id = self.product_id.uom_id - self._update_component_attributes() - def _update_component_attributes(self): - if self.component_template_id: - self._check_component_attributes() - self.product_backup_id = self.product_id.id - self.match_on_attribute_ids = ( - self.component_template_id.attribute_line_ids.mapped("attribute_id") - .filtered(lambda x: x.create_variant != "no_variant") - .ids - ) - self.product_id = False - self._check_variants_validity() - else: - self.match_on_attribute_ids = False - if self.product_backup_id and not self.product_id: - self.product_id = self.product_backup_id.id - self.product_backup_id = False + @api.depends("component_template_id") + def _compute_match_on_attribute_ids(self): + for rec in self: + if rec.component_template_id: + rec.match_on_attribute_ids = ( + rec.component_template_id.attribute_line_ids.attribute_id.filtered( + lambda x: x.create_variant != "no_variant" + ) + ) + else: + rec.match_on_attribute_ids = False + @api.constrains("component_template_id") def _check_component_attributes(self): - comp_attr_ids = ( - self.component_template_id.valid_product_template_attribute_line_ids.attribute_id.ids - ) - prod_attr_ids = ( - self.bom_id.product_tmpl_id.valid_product_template_attribute_line_ids.attribute_id.ids - ) - if len(comp_attr_ids) == 0: - raise ValidationError( - _( - "No match on attribute has been detected for Component " - "(Product Template) %s", - self.component_template_id.display_name, - ) + for rec in self: + if not rec.component_template_id: + continue + comp_attrs = ( + rec.component_template_id.valid_product_template_attribute_line_ids.attribute_id ) - if not all(item in prod_attr_ids for item in comp_attr_ids): - raise ValidationError( - _( - "Some attributes of the dynamic component are not included into " - "production product attributes." - ) + prod_attrs = ( + rec.bom_id.product_tmpl_id.valid_product_template_attribute_line_ids.attribute_id ) + if not comp_attrs: + raise ValidationError( + _( + "No match on attribute has been detected for Component " + "(Product Template) %s", + rec.component_template_id.display_name, + ) + ) + if not all(attr in prod_attrs for attr in comp_attrs): + raise ValidationError( + _( + "Some attributes of the dynamic component are not included into " + "production product attributes." + ) + ) + + @api.constrains("component_template_id", "bom_product_template_attribute_value_ids") + def _check_variants_validity(self): + for rec in self: + if ( + not rec.bom_product_template_attribute_value_ids + or not rec.component_template_id + ): + continue + variant_attrs = rec.bom_product_template_attribute_value_ids.attribute_id + same_attr_ids = set(rec.match_on_attribute_ids.ids) & set(variant_attrs.ids) + same_attrs = self.env["product.attribute"].browse(same_attr_ids) + if same_attrs: + raise ValidationError( + _( + "You cannot use an attribute value for attribute(s) %(attributes)s " + "in the field “Apply on Variants” as it's the same attribute used " + "in the field “Match on Attribute” related to the component " + "%(component)s.", + attributes=", ".join(same_attrs.mapped("name")), + component=rec.component_template_id.name, + ) + ) + + @api.onchange("match_on_attribute_ids") + def _onchange_match_on_attribute_ids_check_component_attributes(self): + if self.match_on_attribute_ids: + self._check_component_attributes() @api.onchange("bom_product_template_attribute_value_ids") - def _onchange_attribute_value_ids(self): + def _onchange_bom_product_template_attribute_value_ids_check_variants(self): if self.bom_product_template_attribute_value_ids: self._check_variants_validity() - def _check_variants_validity(self): - if ( - not self.bom_product_template_attribute_value_ids - or not self.component_template_id - ): - return - variant_attr_ids = self.bom_product_template_attribute_value_ids.mapped( - "attribute_id" - ) - same_attrs = set(self.match_on_attribute_ids.ids) & set(variant_attr_ids.ids) - if len(same_attrs) > 0: - attr_recs = self.env["product.attribute"].browse(same_attrs) - raise ValidationError( - _( - "You cannot use an attribute value for attribute(s) %(attributes)s " - "in the field “Apply on Variants” as it's the same attribute used " - "in the field “Match on Attribute” related to the component " - "%(component)s.", - attributes=", ".join(attr_recs.mapped("name")), - component=self.component_template_id.name, - ) - ) - class MrpBom(models.Model): _inherit = "mrp.bom" @@ -333,8 +345,10 @@ class MrpBom(models.Model): else: return line_product_id - def write(self, vals): - res = super().write(vals) - for line in self.bom_line_ids: - line._update_component_attributes() - return res + @api.constrains("product_tmpl_id", "product_id") + def _check_component_attributes(self): + return self.bom_line_ids._check_component_attributes() + + @api.constrains("product_tmpl_id", "product_id") + def _check_variants_validity(self): + return self.bom_line_ids._check_variants_validity() diff --git a/mrp_bom_attribute_match/models/mrp_production.py b/mrp_bom_attribute_match/models/mrp_production.py index ed2467915..076604e1b 100644 --- a/mrp_bom_attribute_match/models/mrp_production.py +++ b/mrp_bom_attribute_match/models/mrp_production.py @@ -1,4 +1,4 @@ -from odoo import models +from odoo import api, models class MrpProduction(models.Model): @@ -12,7 +12,6 @@ class MrpProduction(models.Model): bom_line.product_id = False return res - def write(self, vals): - for bl in self.bom_id.bom_line_ids.filtered("component_template_id"): - bl._check_component_attributes() - return super().write(vals) + @api.constrains("bom_id") + def _check_component_attributes(self): + self.bom_id._check_component_attributes() diff --git a/mrp_bom_attribute_match/models/product.py b/mrp_bom_attribute_match/models/product.py index d2e238a4f..7c9088c09 100644 --- a/mrp_bom_attribute_match/models/product.py +++ b/mrp_bom_attribute_match/models/product.py @@ -1,31 +1,24 @@ -from odoo import _, models +from odoo import _, api, models from odoo.exceptions import UserError class ProductTemplate(models.Model): _inherit = "product.template" - def write(self, vals): - res = super().write(vals) - for rec in self: - rec._check_product_with_component_change_allowed() - rec._check_component_change_allowed() - return res - + @api.constrains("attribute_line_ids") def _check_product_with_component_change_allowed(self): - self.ensure_one() - if len(self.attribute_line_ids) > 0 and len(self.bom_ids) > 0: - for bom in self.bom_ids: - for line in bom.bom_line_ids.filtered( - lambda x: x.match_on_attribute_ids - ): - prod_attr_ids = self.attribute_line_ids.attribute_id.filtered( + for rec in self: + if not rec.attribute_line_ids: + continue + for bom in rec.bom_ids: + for line in bom.bom_line_ids.filtered("match_on_attribute_ids"): + prod_attr_ids = rec.attribute_line_ids.attribute_id.filtered( lambda x: x.create_variant != "no_variant" ).ids comp_attr_ids = line.match_on_attribute_ids.ids - diff = list(set(comp_attr_ids) - set(prod_attr_ids)) - if len(diff) > 0: - attr_recs = self.env["product.attribute"].browse(diff) + diff_ids = list(set(comp_attr_ids) - set(prod_attr_ids)) + diff = rec.env["product.attribute"].browse(diff_ids) + if diff: raise UserError( _( "The attributes you're trying to remove are used in " @@ -33,33 +26,36 @@ class ProductTemplate(models.Model): "To remove these attributes, first remove the BOM line " "with the matching component.\n" "Attributes: %(attributes)s\nBoM: %(bom)s", - attributes=", ".join(attr_recs.mapped("name")), + attributes=", ".join(diff.mapped("name")), bom=bom.display_name, ) ) + @api.constrains("attribute_line_ids") def _check_component_change_allowed(self): - self.ensure_one() - if len(self.attribute_line_ids) > 0: + for rec in self: + if not rec.attribute_line_ids: + continue boms = self._get_component_boms() - if boms: - for bom in boms: - vpa = bom.product_tmpl_id.valid_product_template_attribute_line_ids - prod_attr_ids = vpa.attribute_id.ids - comp_attr_ids = self.attribute_line_ids.attribute_id.ids - diff = list(set(comp_attr_ids) - set(prod_attr_ids)) - if len(diff) > 0: - attr_recs = self.env["product.attribute"].browse(diff) - raise UserError( - _( - "This product template is used as a component in the " - "BOMs for %(bom)s and attribute(s) %(attributes)s is " - "not present in all such product(s), and this would " - "break the BOM behavior.", - attributes=", ".join(attr_recs.mapped("name")), - bom=bom.display_name, - ) + if not boms: + continue + for bom in boms: + vpa = bom.product_tmpl_id.valid_product_template_attribute_line_ids + prod_attr_ids = vpa.attribute_id.ids + comp_attr_ids = self.attribute_line_ids.attribute_id.ids + diff = list(set(comp_attr_ids) - set(prod_attr_ids)) + if len(diff) > 0: + attr_recs = self.env["product.attribute"].browse(diff) + raise UserError( + _( + "This product template is used as a component in the " + "BOMs for %(bom)s and attribute(s) %(attributes)s is " + "not present in all such product(s), and this would " + "break the BOM behavior.", + attributes=", ".join(attr_recs.mapped("name")), + bom=bom.display_name, ) + ) def _get_component_boms(self): self.ensure_one() diff --git a/mrp_bom_attribute_match/views/mrp_bom_views.xml b/mrp_bom_attribute_match/views/mrp_bom_views.xml index ca482aae7..3ba95fb69 100644 --- a/mrp_bom_attribute_match/views/mrp_bom_views.xml +++ b/mrp_bom_attribute_match/views/mrp_bom_views.xml @@ -5,12 +5,7 @@ - +