summaryrefslogtreecommitdiff
path: root/addons/mrp/models/mrp_bom.py
diff options
context:
space:
mode:
authorSébastien Theys <seb@odoo.com>2019-08-26 08:38:56 +0000
committerSébastien Theys <seb@odoo.com>2019-08-26 13:01:13 +0000
commit22a11a6f4d0b08a56712fafd0f87a2e7bb5b00ec (patch)
treebb88f34b8bc298c31aa3af5c83f1f393a7f108bc /addons/mrp/models/mrp_bom.py
parent191213bca2582ed60c92769a8aa8a17cca0432e6 (diff)
[REF] product, *: save product.template.attribute.value on variants
* account, hr_expense, mrp, sale, sale_product_configurator, stock_account, website_sale, website_sale_comparison Before this commit, the `product.attribute.value` were stored on the variants. This required filtering of attribute lines to find the appropriate matching `product.template.attribute.value` that were used in most of the business code, such as when computing the `price_extra`. This also prevented to have multiple attribute lines for the same attribute, which is needed to handle use cases such as grape varieties for wine products. This will be done in the following commit. After this commit, the combination of `product.template.attribute.value` will be directly stored on the product variant. Other changes ============= Add `combination_indices` on product, which allows to quickly find a variant matching a combination (1 simple indexed equality query as opposed to 1 query with as many joins as there are attribute lines), and to add an easy SQL constraint to ensure active combination uniqueness. Add active field on `product.template.attribute.line` and `product.template.attribute.value`, with the same behavior as variants: They become archived if they can't be unlinked. This allows to keep the database consistent, such as archived variants correctly keeping all their values, sales order lines keeping their custom and no_variant. This is done with the help of `ondelete=restrict` on the corresponding m2m fields, and the `unlink` methods falling back to archiving when `unlink` is restricted. Part of task-1912579 PR: #32946
Diffstat (limited to 'addons/mrp/models/mrp_bom.py')
-rw-r--r--addons/mrp/models/mrp_bom.py69
1 files changed, 41 insertions, 28 deletions
diff --git a/addons/mrp/models/mrp_bom.py b/addons/mrp/models/mrp_bom.py
index 0a66b1f04e8..5b9cc752296 100644
--- a/addons/mrp/models/mrp_bom.py
+++ b/addons/mrp/models/mrp_bom.py
@@ -74,17 +74,25 @@ class MrpBom(models.Model):
def onchange_product_id(self):
if self.product_id:
for line in self.bom_line_ids:
- line.attribute_value_ids = False
+ line.bom_product_template_attribute_value_ids = False
@api.constrains('product_id', 'product_tmpl_id', 'bom_line_ids')
- def _check_product_recursion(self):
+ def _check_bom_lines(self):
for bom in self:
- if bom.product_id:
- if bom.bom_line_ids.filtered(lambda x: x.product_id == bom.product_id):
- raise ValidationError(_('BoM line product %s should not be same as BoM product.') % bom.display_name)
- else:
- if bom.bom_line_ids.filtered(lambda x: x.product_id.product_tmpl_id == bom.product_tmpl_id):
- raise ValidationError(_('BoM line product %s should not be same as BoM product.') % bom.display_name)
+ for bom_line in bom.bom_line_ids:
+ if bom.product_id and bom_line.product_id == bom.product_id:
+ raise ValidationError(_("BoM line product %s should not be the same as BoM product.") % bom.display_name)
+ if bom_line.product_tmpl_id == bom.product_tmpl_id:
+ raise ValidationError(_("BoM line product %s should not be the same as BoM product.") % bom.display_name)
+ if bom.product_id and bom_line.bom_product_template_attribute_value_ids:
+ raise ValidationError(_("BoM cannot concern product %s and have a line with attributes (%s) at the same time.")
+ % (bom.product_id.display_name, ", ".join([ptav.display_name for ptav in bom_line.bom_product_template_attribute_value_ids])))
+ for ptav in bom_line.bom_product_template_attribute_value_ids:
+ if ptav.product_tmpl_id != bom.product_tmpl_id:
+ raise ValidationError(
+ _("The attribute value %s set on product %s does not match the BoM product %s.") %
+ (ptav.display_name, ptav.product_tmpl_id.display_name, bom_line.parent_product_tmpl_id.display_name)
+ )
@api.onchange('product_uom_id')
def onchange_product_uom_id(self):
@@ -103,7 +111,7 @@ class MrpBom(models.Model):
if self.product_id.product_tmpl_id != self.product_tmpl_id:
self.product_id = False
for line in self.bom_line_ids:
- line.attribute_value_ids = False
+ line.bom_product_template_attribute_value_ids = False
@api.onchange('routing_id')
def onchange_routing_id(self):
@@ -248,10 +256,11 @@ class MrpBomLine(models.Model):
'mrp.bom', 'Parent BoM',
index=True, ondelete='cascade', required=True)
parent_product_tmpl_id = fields.Many2one('product.template', 'Parent Product Template', related='bom_id.product_tmpl_id')
- valid_product_attribute_value_ids = fields.Many2many('product.attribute.value', related='bom_id.product_tmpl_id.valid_product_attribute_value_ids')
- attribute_value_ids = fields.Many2many(
- 'product.attribute.value', string='Apply on Variants',
- help="BOM Product Variants needed form apply this line.")
+ possible_bom_product_template_attribute_value_ids = fields.Many2many('product.template.attribute.value', compute='_compute_possible_bom_product_template_attribute_value_ids')
+ bom_product_template_attribute_value_ids = fields.Many2many(
+ 'product.template.attribute.value', string="Apply on Variants", ondelete='restrict',
+ domain="[('id', 'in', possible_bom_product_template_attribute_value_ids)]",
+ help="BOM Product Variants needed to apply this line.")
operation_id = fields.Many2one(
'mrp.routing.workcenter', 'Consumed in Operation',
help="The operation where the components are consumed, or the finished products created.")
@@ -268,6 +277,15 @@ class MrpBomLine(models.Model):
'You should install the mrp_byproduct module if you want to manage extra products on BoMs !'),
]
+ @api.depends(
+ 'parent_product_tmpl_id.attribute_line_ids.value_ids',
+ 'parent_product_tmpl_id.attribute_line_ids.attribute_id.create_variant',
+ 'parent_product_tmpl_id.attribute_line_ids.product_template_value_ids.ptav_active',
+ )
+ def _compute_possible_bom_product_template_attribute_value_ids(self):
+ for line in self:
+ line.possible_bom_product_template_attribute_value_ids = line.parent_product_tmpl_id.valid_product_template_attribute_line_ids._without_no_variant_attributes().product_template_value_ids._only_active()
+
@api.depends('product_id', 'bom_id')
def _compute_child_bom_id(self):
for line in self:
@@ -309,15 +327,6 @@ class MrpBomLine(models.Model):
if self.product_id:
self.product_uom_id = self.product_id.uom_id.id
- @api.onchange('parent_product_tmpl_id')
- def onchange_parent_product(self):
- if not self.parent_product_tmpl_id:
- return {}
- return {'domain': {'attribute_value_ids': [
- ('id', 'in', self.parent_product_tmpl_id.valid_product_attribute_value_ids.ids),
- ('attribute_id.create_variant', '!=', 'no_variant')
- ]}}
-
@api.model_create_multi
def create(self, vals_list):
for values in vals_list:
@@ -326,13 +335,17 @@ class MrpBomLine(models.Model):
return super(MrpBomLine, self).create(vals_list)
def _skip_bom_line(self, product):
- """ Control if a BoM line should be produce, can be inherited for add
+ """ Control if a BoM line should be produced, can be inherited to add
custom control. It currently checks that all variant values are in the
- product. """
- if self.attribute_value_ids:
- for att, att_values in groupby(self.attribute_value_ids, lambda l: l.attribute_id):
- values = self.env['product.attribute.value'].concat(*list(att_values))
- if not (product.attribute_value_ids & values):
+ product.
+
+ If multiple values are encoded for the same attribute line, only one of
+ them has to be found on the variant.
+ """
+ self.ensure_one()
+ if self.bom_product_template_attribute_value_ids:
+ for ptal, iter_ptav in groupby(self.bom_product_template_attribute_value_ids.sorted('attribute_line_id'), lambda ptav: ptav.attribute_line_id):
+ if not any([ptav in product.product_template_attribute_value_ids for ptav in iter_ptav]):
return True
return False