mirror of
https://github.com/OCA/manufacture.git
synced 2025-01-28 16:37:15 +02:00
[IMP] mrp_bom_attribute_match: increase test strength: add another component to bom
This commit is contained in:
@@ -10,7 +10,4 @@
|
||||
"data": [
|
||||
"views/mrp_bom_views.xml",
|
||||
],
|
||||
"qweb": [],
|
||||
"application": False,
|
||||
"installable": True,
|
||||
}
|
||||
|
||||
@@ -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,87 +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,
|
||||
)
|
||||
)
|
||||
|
||||
def write(self, vals):
|
||||
super(MrpBomLine, self).write(vals)
|
||||
|
||||
|
||||
class MrpBom(models.Model):
|
||||
_inherit = "mrp.bom"
|
||||
@@ -225,8 +234,7 @@ class MrpBom(models.Model):
|
||||
)
|
||||
if component_template_product:
|
||||
# need to set product_id temporary
|
||||
if current_line.product_id != component_template_product:
|
||||
current_line.product_id = component_template_product
|
||||
current_line.product_id = component_template_product
|
||||
else:
|
||||
# component_template_id is set, but no attribute value match.
|
||||
continue
|
||||
@@ -337,8 +345,10 @@ class MrpBom(models.Model):
|
||||
else:
|
||||
return line_product_id
|
||||
|
||||
def write(self, vals):
|
||||
res = super(MrpBom, self).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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
* Ooops404 <https://ooops404.com>
|
||||
|
||||
* Ilyas
|
||||
|
||||
* `Camptocamp <https://www.camptocamp.com>`_
|
||||
|
||||
* Iván Todorovich <ivan.todorovich@camptocamp.com>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# @author Iván Todorovich <ivan.todorovich@camptocamp.com>
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
|
||||
|
||||
from odoo import Command, models
|
||||
from odoo import models
|
||||
|
||||
|
||||
class ReportBomStructure(models.AbstractModel):
|
||||
@@ -30,7 +30,8 @@ class ReportBomStructure(models.AbstractModel):
|
||||
else:
|
||||
line.product_id = line_product
|
||||
if to_ignore_line_ids:
|
||||
bom.bom_line_ids = [Command.unlink(id) for id in to_ignore_line_ids]
|
||||
for to_ignore_line_id in to_ignore_line_ids:
|
||||
bom.bom_line_ids = [(3, to_ignore_line_id, 0)]
|
||||
components, total = super()._get_bom_lines(
|
||||
bom, bom_quantity, product, line_id, level
|
||||
)
|
||||
|
||||
@@ -1,168 +1,134 @@
|
||||
from odoo import Command
|
||||
from odoo.models import BaseModel
|
||||
from odoo.tests import Form, TransactionCase
|
||||
from odoo.tests import Form, common
|
||||
|
||||
|
||||
class TestMrpBomAttributeMatchBase(TransactionCase):
|
||||
class TestMrpAttachmentMgmtBase(common.SavepointCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.env = cls.env(context=dict(cls.env.context, tracking_disable=True))
|
||||
cls.warehouse = cls.env.ref("stock.warehouse0")
|
||||
cls.route_manufacture = cls.warehouse.manufacture_pull_id.route_id
|
||||
# Create products
|
||||
cls.product_sword = cls.env["product.template"].create(
|
||||
cls._create_products(cls)
|
||||
cls._create_boms(cls)
|
||||
|
||||
def _create_products(self):
|
||||
self.warehouse = self.env.ref("stock.warehouse0")
|
||||
route_manufacture = self.warehouse.manufacture_pull_id.route_id.id
|
||||
self.product_sword = self.env["product.template"].create(
|
||||
{
|
||||
"name": "Plastic Sword",
|
||||
"type": "product",
|
||||
}
|
||||
)
|
||||
cls.product_surf = cls.env["product.template"].create(
|
||||
self.product_surf = self.env["product.template"].create(
|
||||
{
|
||||
"name": "Surf",
|
||||
"type": "product",
|
||||
}
|
||||
)
|
||||
cls.product_fin = cls.env["product.template"].create(
|
||||
self.product_fin = self.env["product.template"].create(
|
||||
{
|
||||
"name": "Surf Fin",
|
||||
"type": "product",
|
||||
}
|
||||
)
|
||||
cls.product_plastic = cls.env["product.template"].create(
|
||||
self.product_plastic = self.env["product.template"].create(
|
||||
{
|
||||
"name": "Plastic Component",
|
||||
"type": "product",
|
||||
}
|
||||
)
|
||||
cls.p1 = cls.env["product.template"].create(
|
||||
self.p1 = self.env["product.template"].create(
|
||||
{
|
||||
"name": "P1",
|
||||
"type": "product",
|
||||
"route_ids": [Command.link(cls.route_manufacture.id)],
|
||||
"route_ids": [(6, 0, [route_manufacture])],
|
||||
}
|
||||
)
|
||||
cls.p2 = cls.env["product.template"].create(
|
||||
self.p2 = self.env["product.template"].create(
|
||||
{
|
||||
"name": "P2",
|
||||
"type": "product",
|
||||
"route_ids": [Command.link(cls.route_manufacture.id)],
|
||||
"route_ids": [(6, 0, [route_manufacture])],
|
||||
}
|
||||
)
|
||||
cls.p3 = cls.env["product.template"].create(
|
||||
self.p3 = self.env["product.template"].create(
|
||||
{
|
||||
"name": "P3",
|
||||
"type": "product",
|
||||
"route_ids": [Command.link(cls.route_manufacture.id)],
|
||||
"route_ids": [(6, 0, [route_manufacture])],
|
||||
}
|
||||
)
|
||||
cls.product_9 = cls.env["product.product"].create(
|
||||
self.product_9 = self.env["product.product"].create(
|
||||
{
|
||||
"name": "Paper",
|
||||
}
|
||||
)
|
||||
cls.product_10 = cls.env["product.product"].create(
|
||||
self.product_10 = self.env["product.product"].create(
|
||||
{
|
||||
"name": "Stone",
|
||||
}
|
||||
)
|
||||
cls.product_attribute = cls.env["product.attribute"].create(
|
||||
self.product_attribute = self.env["product.attribute"].create(
|
||||
{"name": "Colour", "display_type": "radio", "create_variant": "always"}
|
||||
)
|
||||
cls.attribute_value_ids = cls.env["product.attribute.value"].create(
|
||||
self.attribute_value_ids = self.env["product.attribute.value"].create(
|
||||
[
|
||||
{"name": "Cyan", "attribute_id": cls.product_attribute.id},
|
||||
{"name": "Magenta", "attribute_id": cls.product_attribute.id},
|
||||
{"name": "Cyan", "attribute_id": self.product_attribute.id},
|
||||
{"name": "Magenta", "attribute_id": self.product_attribute.id},
|
||||
]
|
||||
)
|
||||
cls.plastic_attrs = cls.env["product.template.attribute.line"].create(
|
||||
self.plastic_attrs = self.env["product.template.attribute.line"].create(
|
||||
{
|
||||
"attribute_id": cls.product_attribute.id,
|
||||
"product_tmpl_id": cls.product_plastic.id,
|
||||
"value_ids": [Command.set(cls.product_attribute.value_ids.ids)],
|
||||
"attribute_id": self.product_attribute.id,
|
||||
"product_tmpl_id": self.product_plastic.id,
|
||||
"value_ids": [(6, 0, self.product_attribute.value_ids.ids)],
|
||||
}
|
||||
)
|
||||
cls.sword_attrs = cls.env["product.template.attribute.line"].create(
|
||||
self.sword_attrs = self.env["product.template.attribute.line"].create(
|
||||
{
|
||||
"attribute_id": cls.product_attribute.id,
|
||||
"product_tmpl_id": cls.product_sword.id,
|
||||
"value_ids": [Command.set(cls.product_attribute.value_ids.ids)],
|
||||
"attribute_id": self.product_attribute.id,
|
||||
"product_tmpl_id": self.product_sword.id,
|
||||
"value_ids": [(6, 0, self.product_attribute.value_ids.ids)],
|
||||
}
|
||||
)
|
||||
# Create boms
|
||||
cls.bom_id = cls._create_bom(
|
||||
cls.product_sword,
|
||||
[
|
||||
dict(
|
||||
component_template_id=cls.product_plastic.id,
|
||||
product_qty=1,
|
||||
),
|
||||
],
|
||||
)
|
||||
cls.fin_bom_id = cls._create_bom(
|
||||
cls.product_fin,
|
||||
[
|
||||
dict(
|
||||
product_id=cls.product_plastic.product_variant_ids[0],
|
||||
product_qty=1,
|
||||
),
|
||||
],
|
||||
)
|
||||
cls.surf_bom_id = cls._create_bom(
|
||||
cls.product_surf,
|
||||
[
|
||||
dict(
|
||||
product_id=cls.product_fin.product_variant_ids[0],
|
||||
product_qty=1,
|
||||
),
|
||||
],
|
||||
)
|
||||
cls.p1_bom_id = cls._create_bom(
|
||||
cls.p1,
|
||||
[
|
||||
dict(
|
||||
product_id=cls.p2.product_variant_ids[0],
|
||||
product_qty=1,
|
||||
),
|
||||
],
|
||||
)
|
||||
cls.p2_bom_id = cls._create_bom(
|
||||
cls.p2,
|
||||
[
|
||||
dict(
|
||||
product_id=cls.p3.product_variant_ids[0],
|
||||
product_qty=1,
|
||||
),
|
||||
],
|
||||
)
|
||||
cls.p3_bom_id = cls._create_bom(
|
||||
cls.p3,
|
||||
[
|
||||
dict(
|
||||
product_id=cls.p1.product_variant_ids[0],
|
||||
product_qty=1,
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _create_bom(cls, product, line_form_vals):
|
||||
if product._name == "product.template":
|
||||
template = product
|
||||
product = cls.env["product.product"]
|
||||
else:
|
||||
template = product.product_tmpl_id
|
||||
with Form(cls.env["mrp.bom"]) as form:
|
||||
form.product_tmpl_id = template
|
||||
form.product_id = product
|
||||
for vals in line_form_vals:
|
||||
with form.bom_line_ids.new() as line_form:
|
||||
for key, value in vals.items():
|
||||
field = line_form._model._fields.get(key)
|
||||
if field and field.relational: # pragma: no cover
|
||||
if value and not isinstance(value, BaseModel):
|
||||
value = cls.env[field.comodel_name].browse(value)
|
||||
elif not value:
|
||||
value = cls.env[field.comodel_name]
|
||||
setattr(line_form, key, value)
|
||||
return form.save()
|
||||
def _create_boms(self):
|
||||
mrp_bom_form = Form(self.env["mrp.bom"])
|
||||
mrp_bom_form.product_tmpl_id = self.product_sword
|
||||
with mrp_bom_form.bom_line_ids.new() as line_form:
|
||||
line_form.component_template_id = self.product_plastic
|
||||
line_form.product_qty = 1
|
||||
self.bom_id = mrp_bom_form.save()
|
||||
|
||||
mrp_bom_form = Form(self.env["mrp.bom"])
|
||||
mrp_bom_form.product_tmpl_id = self.product_fin
|
||||
with mrp_bom_form.bom_line_ids.new() as line_form:
|
||||
line_form.product_id = self.product_plastic.product_variant_ids[0]
|
||||
line_form.product_qty = 1
|
||||
self.fin_bom_id = mrp_bom_form.save()
|
||||
|
||||
mrp_bom_form = Form(self.env["mrp.bom"])
|
||||
mrp_bom_form.product_tmpl_id = self.product_surf
|
||||
with mrp_bom_form.bom_line_ids.new() as line_form:
|
||||
line_form.product_id = self.product_fin.product_variant_ids[0]
|
||||
line_form.product_qty = 1
|
||||
self.surf_bom_id = mrp_bom_form.save()
|
||||
|
||||
mrp_bom_form = Form(self.env["mrp.bom"])
|
||||
mrp_bom_form.product_tmpl_id = self.p1
|
||||
with mrp_bom_form.bom_line_ids.new() as line_form:
|
||||
line_form.product_id = self.p2.product_variant_ids[0]
|
||||
line_form.product_qty = 1
|
||||
self.p1_bom_id = mrp_bom_form.save()
|
||||
|
||||
mrp_bom_form = Form(self.env["mrp.bom"])
|
||||
mrp_bom_form.product_tmpl_id = self.p2
|
||||
with mrp_bom_form.bom_line_ids.new() as line_form:
|
||||
line_form.product_id = self.p3.product_variant_ids[0]
|
||||
line_form.product_qty = 1
|
||||
self.p2_bom_id = mrp_bom_form.save()
|
||||
|
||||
mrp_bom_form = Form(self.env["mrp.bom"])
|
||||
mrp_bom_form.product_tmpl_id = self.p3
|
||||
with mrp_bom_form.bom_line_ids.new() as line_form:
|
||||
line_form.product_id = self.p1.product_variant_ids[0]
|
||||
line_form.product_qty = 1
|
||||
self.p3_bom_id = mrp_bom_form.save()
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
from odoo.exceptions import UserError, ValidationError
|
||||
from odoo.tests import Form
|
||||
|
||||
from .common import TestMrpBomAttributeMatchBase
|
||||
from .common import TestMrpAttachmentMgmtBase
|
||||
|
||||
|
||||
class TestMrpBomAttributeMatch(TestMrpBomAttributeMatchBase):
|
||||
class TestMrpAttachmentMgmt(TestMrpAttachmentMgmtBase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
|
||||
def test_bom_1(self):
|
||||
mrp_bom_form = Form(self.env["mrp.bom"])
|
||||
mrp_bom_form.product_tmpl_id = self.product_sword
|
||||
@@ -19,12 +23,7 @@ class TestMrpBomAttributeMatch(TestMrpBomAttributeMatchBase):
|
||||
line_form.component_template_id = self.product_plastic
|
||||
line_form.product_qty = 1
|
||||
sword_cyan = self.sword_attrs.product_template_value_ids[0]
|
||||
with self.assertRaisesRegex(
|
||||
ValidationError,
|
||||
r"You cannot use an attribute value for attribute\(s\) .* in the "
|
||||
r"field “Apply on Variants” as it's the same attribute used in the "
|
||||
r"field “Match on Attribute” related to the component .*",
|
||||
):
|
||||
with self.assertRaises(ValidationError):
|
||||
line_form.bom_product_template_attribute_value_ids.add(sword_cyan)
|
||||
|
||||
def test_bom_2(self):
|
||||
@@ -43,12 +42,7 @@ class TestMrpBomAttributeMatch(TestMrpBomAttributeMatchBase):
|
||||
"value_ids": [(4, orchid_attribute_value_id.id)],
|
||||
}
|
||||
)
|
||||
with self.assertRaisesRegex(
|
||||
UserError,
|
||||
r"This product template is used as a component in the BOMs for .* and "
|
||||
r"attribute\(s\) .* is not present in all such product\(s\), and this "
|
||||
r"would break the BOM behavior\.",
|
||||
):
|
||||
with self.assertRaises(UserError):
|
||||
vals = {
|
||||
"attribute_id": smell_attribute.id,
|
||||
"product_tmpl_id": self.product_plastic.id,
|
||||
@@ -58,11 +52,7 @@ class TestMrpBomAttributeMatch(TestMrpBomAttributeMatchBase):
|
||||
mrp_bom_form = Form(self.env["mrp.bom"])
|
||||
mrp_bom_form.product_tmpl_id = self.product_sword
|
||||
with mrp_bom_form.bom_line_ids.new() as line_form:
|
||||
with self.assertRaisesRegex(
|
||||
UserError,
|
||||
r"Some attributes of the dynamic component are not included into "
|
||||
r"production product attributes\.",
|
||||
):
|
||||
with self.assertRaises(ValidationError):
|
||||
line_form.component_template_id = self.product_plastic
|
||||
plastic_smells_like_orchid.unlink()
|
||||
|
||||
@@ -103,10 +93,8 @@ class TestMrpBomAttributeMatch(TestMrpBomAttributeMatchBase):
|
||||
# Component skipped
|
||||
mo_form.bom_id = self.bom_id
|
||||
mo_form.product_qty = 1
|
||||
with self.assertRaisesRegex(
|
||||
ValidationError,
|
||||
r"Some attributes of the dynamic component are not included into .+",
|
||||
):
|
||||
with self.assertRaises(ValidationError):
|
||||
# Some attributes of the dynamic component are not included into ...
|
||||
self.mo_sword = mo_form.save()
|
||||
|
||||
def test_manufacturing_order_4(self):
|
||||
@@ -168,20 +156,5 @@ class TestMrpBomAttributeMatch(TestMrpBomAttributeMatchBase):
|
||||
"product_qty": 1.0,
|
||||
}
|
||||
)
|
||||
with self.assertRaisesRegex(UserError, r"Recursion error! .+"):
|
||||
with self.assertRaises(UserError):
|
||||
test_bom_3.explode(self.product_9, 1)
|
||||
|
||||
def test_mrp_report_bom_structure(self):
|
||||
sword_cyan = self.product_sword.product_variant_ids[0]
|
||||
BomStructureReport = self.env["report.mrp.report_bom_structure"]
|
||||
res = BomStructureReport._get_report_data(self.bom_id.id)
|
||||
self.assertTrue(res["is_variant_applied"])
|
||||
self.assertEqual(res["lines"]["product"], sword_cyan)
|
||||
self.assertEqual(
|
||||
res["lines"]["components"][0]["line_id"],
|
||||
self.bom_id.bom_line_ids.id,
|
||||
)
|
||||
self.assertEqual(
|
||||
res["lines"]["components"][0]["parent_id"],
|
||||
self.bom_id.id,
|
||||
)
|
||||
|
||||
@@ -1,17 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<odoo>
|
||||
<record id="mrp_bom_view_form_inherit_bom_match" model="ir.ui.view">
|
||||
<field name="name">mrp.bom.view.form.inherit.bom.match</field>
|
||||
<field name="model">mrp.bom</field>
|
||||
<field name="inherit_id" ref="mrp.mrp_bom_form_view" />
|
||||
<field name="arch" type="xml">
|
||||
<field name="bom_product_template_attribute_value_ids" position="before">
|
||||
<field
|
||||
name="match_on_attribute_ids"
|
||||
readonly='1'
|
||||
force_save="1"
|
||||
widget="many2many_tags"
|
||||
/>
|
||||
<field name="match_on_attribute_ids" widget="many2many_tags" />
|
||||
<field name="product_backup_id" invisible="1" />
|
||||
</field>
|
||||
<xpath
|
||||
|
||||
Reference in New Issue
Block a user