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:
@@ -1 +1,83 @@
|
|||||||
TO BE GENERATED
|
===============================
|
||||||
|
MRP Account BOM Attribute Match
|
||||||
|
===============================
|
||||||
|
|
||||||
|
.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||||
|
!! This file is generated by oca-gen-addon-readme !!
|
||||||
|
!! changes will be overwritten. !!
|
||||||
|
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||||
|
|
||||||
|
.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
|
||||||
|
:target: https://odoo-community.org/page/development-status
|
||||||
|
:alt: Beta
|
||||||
|
.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png
|
||||||
|
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
|
||||||
|
:alt: License: AGPL-3
|
||||||
|
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fmanufacture-lightgray.png?logo=github
|
||||||
|
:target: https://github.com/OCA/manufacture/tree/15.0/mrp_account_bom_attribute_match
|
||||||
|
:alt: OCA/manufacture
|
||||||
|
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
|
||||||
|
:target: https://translation.odoo-community.org/projects/manufacture-15-0/manufacture-15-0-mrp_account_bom_attribute_match
|
||||||
|
:alt: Translate me on Weblate
|
||||||
|
.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png
|
||||||
|
:target: https://runboat.odoo-community.org/webui/builds.html?repo=OCA/manufacture&target_branch=15.0
|
||||||
|
:alt: Try me on Runboat
|
||||||
|
|
||||||
|
|badge1| |badge2| |badge3| |badge4| |badge5|
|
||||||
|
|
||||||
|
Glue module between `mrp_bom_attribute_match` and `mrp_account`.
|
||||||
|
|
||||||
|
**Table of contents**
|
||||||
|
|
||||||
|
.. contents::
|
||||||
|
:local:
|
||||||
|
|
||||||
|
Bug Tracker
|
||||||
|
===========
|
||||||
|
|
||||||
|
Bugs are tracked on `GitHub Issues <https://github.com/OCA/manufacture/issues>`_.
|
||||||
|
In case of trouble, please check there if your issue has already been reported.
|
||||||
|
If you spotted it first, help us smashing it by providing a detailed and welcomed
|
||||||
|
`feedback <https://github.com/OCA/manufacture/issues/new?body=module:%20mrp_account_bom_attribute_match%0Aversion:%2015.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
|
||||||
|
|
||||||
|
Do not contact contributors directly about support or help with technical issues.
|
||||||
|
|
||||||
|
Credits
|
||||||
|
=======
|
||||||
|
|
||||||
|
Authors
|
||||||
|
~~~~~~~
|
||||||
|
|
||||||
|
* Camptocamp
|
||||||
|
|
||||||
|
Contributors
|
||||||
|
~~~~~~~~~~~~
|
||||||
|
|
||||||
|
* `Camptocamp <https://www.camptocamp.com>`_
|
||||||
|
|
||||||
|
* Iván Todorovich <ivan.todorovich@camptocamp.com>
|
||||||
|
|
||||||
|
Maintainers
|
||||||
|
~~~~~~~~~~~
|
||||||
|
|
||||||
|
This module is maintained by the OCA.
|
||||||
|
|
||||||
|
.. image:: https://odoo-community.org/logo.png
|
||||||
|
:alt: Odoo Community Association
|
||||||
|
:target: https://odoo-community.org
|
||||||
|
|
||||||
|
OCA, or the Odoo Community Association, is a nonprofit organization whose
|
||||||
|
mission is to support the collaborative development of Odoo features and
|
||||||
|
promote its widespread use.
|
||||||
|
|
||||||
|
.. |maintainer-ivantodorovich| image:: https://github.com/ivantodorovich.png?size=40px
|
||||||
|
:target: https://github.com/ivantodorovich
|
||||||
|
:alt: ivantodorovich
|
||||||
|
|
||||||
|
Current `maintainer <https://odoo-community.org/page/maintainer-role>`__:
|
||||||
|
|
||||||
|
|maintainer-ivantodorovich|
|
||||||
|
|
||||||
|
This module is part of the `OCA/manufacture <https://github.com/OCA/manufacture/tree/15.0/mrp_account_bom_attribute_match>`_ project on GitHub.
|
||||||
|
|
||||||
|
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
{
|
{
|
||||||
"name": "MRP Account BOM Attribute Match",
|
"name": "MRP Account BOM Attribute Match",
|
||||||
"summary": "Glue module between `mrp_account` and `mrp_bom_attribute_match`",
|
"summary": "Glue module between `mrp_account` and `mrp_bom_attribute_match`",
|
||||||
"version": "15.0.1.0.0",
|
"version": "14.0.1.0.0",
|
||||||
"author": "Camptocamp, Odoo Community Association (OCA)",
|
"author": "Camptocamp, Odoo Community Association (OCA)",
|
||||||
"maintainers": ["ivantodorovich"],
|
"maintainers": ["ivantodorovich"],
|
||||||
"website": "https://github.com/OCA/manufacture",
|
"website": "https://github.com/OCA/manufacture",
|
||||||
|
|||||||
@@ -2,13 +2,13 @@
|
|||||||
# @author Iván Todorovich <ivan.todorovich@camptocamp.com>
|
# @author Iván Todorovich <ivan.todorovich@camptocamp.com>
|
||||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
|
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
|
||||||
|
|
||||||
from odoo import Command, models
|
from odoo import models
|
||||||
|
|
||||||
|
|
||||||
class ProductProduct(models.Model):
|
class ProductProduct(models.Model):
|
||||||
_inherit = "product.product"
|
_inherit = "product.product"
|
||||||
|
|
||||||
def _compute_bom_price(self, bom, boms_to_recompute=False, byproduct_bom=False):
|
def _compute_bom_price(self, bom, boms_to_recompute=False):
|
||||||
# OVERRIDE to fill in the `line.product_id` if a component template is used.
|
# OVERRIDE to fill in the `line.product_id` if a component template is used.
|
||||||
# To avoid a complete override, we HACK the bom by replacing it with a virtual
|
# To avoid a complete override, we HACK the bom by replacing it with a virtual
|
||||||
# record, and modifying it's lines on-the-fly.
|
# record, and modifying it's lines on-the-fly.
|
||||||
@@ -30,5 +30,6 @@ class ProductProduct(models.Model):
|
|||||||
else:
|
else:
|
||||||
line.product_id = line_product
|
line.product_id = line_product
|
||||||
if to_ignore_line_ids:
|
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:
|
||||||
return super()._compute_bom_price(bom, boms_to_recompute, byproduct_bom)
|
bom.bom_line_ids = [(3, to_ignore_line_id, 0)]
|
||||||
|
return super()._compute_bom_price(bom, boms_to_recompute)
|
||||||
|
|||||||
8
mrp_account_bom_attribute_match/readme/CONTRIBUTORS.rst
Normal file
8
mrp_account_bom_attribute_match/readme/CONTRIBUTORS.rst
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
* `Camptocamp <https://www.camptocamp.com>`_
|
||||||
|
|
||||||
|
* Iván Todorovich <ivan.todorovich@camptocamp.com>
|
||||||
|
|
||||||
|
|
||||||
|
* `Ooops404 <https://ooops404.com>`_
|
||||||
|
|
||||||
|
* Ilyas <irazor147@gmail.com>
|
||||||
0
mrp_account_bom_attribute_match/readme/USAGE.rst
Normal file
0
mrp_account_bom_attribute_match/readme/USAGE.rst
Normal file
BIN
mrp_account_bom_attribute_match/static/description/icon.png
Normal file
BIN
mrp_account_bom_attribute_match/static/description/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.2 KiB |
@@ -1 +1,2 @@
|
|||||||
|
from . import common
|
||||||
from . import test_mrp_account_bom_attribute_match
|
from . import test_mrp_account_bom_attribute_match
|
||||||
|
|||||||
171
mrp_account_bom_attribute_match/tests/common.py
Normal file
171
mrp_account_bom_attribute_match/tests/common.py
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
from odoo.models import BaseModel
|
||||||
|
from odoo.tests import Form, SavepointCase
|
||||||
|
|
||||||
|
|
||||||
|
class TestMrpBomAttributeMatchBase(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(
|
||||||
|
{
|
||||||
|
"name": "Plastic Sword",
|
||||||
|
"type": "product",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
cls.product_surf = cls.env["product.template"].create(
|
||||||
|
{
|
||||||
|
"name": "Surf",
|
||||||
|
"type": "product",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
cls.product_fin = cls.env["product.template"].create(
|
||||||
|
{
|
||||||
|
"name": "Surf Fin",
|
||||||
|
"type": "product",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
cls.product_plastic = cls.env["product.template"].create(
|
||||||
|
{
|
||||||
|
"name": "Plastic Component",
|
||||||
|
"type": "product",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
cls.p1 = cls.env["product.template"].create(
|
||||||
|
{
|
||||||
|
"name": "P1",
|
||||||
|
"type": "product",
|
||||||
|
"route_ids": [(4, cls.route_manufacture.id, 0)],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
cls.p2 = cls.env["product.template"].create(
|
||||||
|
{
|
||||||
|
"name": "P2",
|
||||||
|
"type": "product",
|
||||||
|
"route_ids": [(4, cls.route_manufacture.id, 0)],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
cls.p3 = cls.env["product.template"].create(
|
||||||
|
{
|
||||||
|
"name": "P3",
|
||||||
|
"type": "product",
|
||||||
|
"route_ids": [(4, cls.route_manufacture.id, 0)],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
cls.product_9 = cls.env["product.product"].create(
|
||||||
|
{
|
||||||
|
"name": "Paper",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
cls.product_10 = cls.env["product.product"].create(
|
||||||
|
{
|
||||||
|
"name": "Stone",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
cls.product_attribute = cls.env["product.attribute"].create(
|
||||||
|
{"name": "Colour", "display_type": "radio", "create_variant": "always"}
|
||||||
|
)
|
||||||
|
cls.attribute_value_ids = cls.env["product.attribute.value"].create(
|
||||||
|
[
|
||||||
|
{"name": "Cyan", "attribute_id": cls.product_attribute.id},
|
||||||
|
{"name": "Magenta", "attribute_id": cls.product_attribute.id},
|
||||||
|
]
|
||||||
|
)
|
||||||
|
cls.plastic_attrs = cls.env["product.template.attribute.line"].create(
|
||||||
|
{
|
||||||
|
"attribute_id": cls.product_attribute.id,
|
||||||
|
"product_tmpl_id": cls.product_plastic.id,
|
||||||
|
"value_ids": [(6, 0, cls.product_attribute.value_ids.ids)],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
cls.sword_attrs = cls.env["product.template.attribute.line"].create(
|
||||||
|
{
|
||||||
|
"attribute_id": cls.product_attribute.id,
|
||||||
|
"product_tmpl_id": cls.product_sword.id,
|
||||||
|
"value_ids": [(6, 0, cls.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,
|
||||||
|
),
|
||||||
|
dict(
|
||||||
|
product_id=cls.product_9,
|
||||||
|
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()
|
||||||
@@ -2,9 +2,7 @@
|
|||||||
# @author Iván Todorovich <ivan.todorovich@camptocamp.com>
|
# @author Iván Todorovich <ivan.todorovich@camptocamp.com>
|
||||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
|
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
|
||||||
|
|
||||||
from odoo.addons.mrp_bom_attribute_match.tests.common import (
|
from .common import TestMrpBomAttributeMatchBase
|
||||||
TestMrpBomAttributeMatchBase,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class TestMrpAccount(TestMrpBomAttributeMatchBase):
|
class TestMrpAccount(TestMrpBomAttributeMatchBase):
|
||||||
|
|||||||
@@ -10,7 +10,4 @@
|
|||||||
"data": [
|
"data": [
|
||||||
"views/mrp_bom_views.xml",
|
"views/mrp_bom_views.xml",
|
||||||
],
|
],
|
||||||
"qweb": [],
|
|
||||||
"application": False,
|
|
||||||
"installable": True,
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,10 @@ class MrpBomLine(models.Model):
|
|||||||
"product.template", "Component (product template)"
|
"product.template", "Component (product template)"
|
||||||
)
|
)
|
||||||
match_on_attribute_ids = fields.Many2many(
|
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(
|
product_uom_category_id = fields.Many2one(
|
||||||
"uom.category",
|
"uom.category",
|
||||||
@@ -56,87 +59,93 @@ class MrpBomLine(models.Model):
|
|||||||
@api.onchange("component_template_id")
|
@api.onchange("component_template_id")
|
||||||
def _onchange_component_template_id(self):
|
def _onchange_component_template_id(self):
|
||||||
if self.component_template_id:
|
if self.component_template_id:
|
||||||
|
if self.product_id:
|
||||||
|
self.product_backup_id = self.product_id
|
||||||
|
self.product_id = False
|
||||||
if (
|
if (
|
||||||
self.product_uom_id.category_id
|
self.product_uom_id.category_id
|
||||||
!= self.component_template_id.uom_id.category_id
|
!= self.component_template_id.uom_id.category_id
|
||||||
):
|
):
|
||||||
self.product_uom_id = self.component_template_id.uom_id
|
self.product_uom_id = self.component_template_id.uom_id
|
||||||
else:
|
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:
|
if self.product_uom_id.category_id != self.product_id.uom_id.category_id:
|
||||||
self.product_uom_id = self.product_id.uom_id
|
self.product_uom_id = self.product_id.uom_id
|
||||||
self._update_component_attributes()
|
|
||||||
|
|
||||||
def _update_component_attributes(self):
|
@api.depends("component_template_id")
|
||||||
if self.component_template_id:
|
def _compute_match_on_attribute_ids(self):
|
||||||
self._check_component_attributes()
|
for rec in self:
|
||||||
self.product_backup_id = self.product_id.id
|
if rec.component_template_id:
|
||||||
self.match_on_attribute_ids = (
|
rec.match_on_attribute_ids = (
|
||||||
self.component_template_id.attribute_line_ids.mapped("attribute_id")
|
rec.component_template_id.attribute_line_ids.attribute_id.filtered(
|
||||||
.filtered(lambda x: x.create_variant != "no_variant")
|
lambda x: x.create_variant != "no_variant"
|
||||||
.ids
|
)
|
||||||
)
|
)
|
||||||
self.product_id = False
|
else:
|
||||||
self._check_variants_validity()
|
rec.match_on_attribute_ids = False
|
||||||
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.constrains("component_template_id")
|
||||||
def _check_component_attributes(self):
|
def _check_component_attributes(self):
|
||||||
comp_attr_ids = (
|
for rec in self:
|
||||||
self.component_template_id.valid_product_template_attribute_line_ids.attribute_id.ids
|
if not rec.component_template_id:
|
||||||
)
|
continue
|
||||||
prod_attr_ids = (
|
comp_attrs = (
|
||||||
self.bom_id.product_tmpl_id.valid_product_template_attribute_line_ids.attribute_id.ids
|
rec.component_template_id.valid_product_template_attribute_line_ids.attribute_id
|
||||||
)
|
|
||||||
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,
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
if not all(item in prod_attr_ids for item in comp_attr_ids):
|
prod_attrs = (
|
||||||
raise ValidationError(
|
rec.bom_id.product_tmpl_id.valid_product_template_attribute_line_ids.attribute_id
|
||||||
_(
|
|
||||||
"Some attributes of the dynamic component are not included into "
|
|
||||||
"production product attributes."
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
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")
|
@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:
|
if self.bom_product_template_attribute_value_ids:
|
||||||
self._check_variants_validity()
|
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):
|
class MrpBom(models.Model):
|
||||||
_inherit = "mrp.bom"
|
_inherit = "mrp.bom"
|
||||||
@@ -225,8 +234,7 @@ class MrpBom(models.Model):
|
|||||||
)
|
)
|
||||||
if component_template_product:
|
if component_template_product:
|
||||||
# need to set product_id temporary
|
# 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:
|
else:
|
||||||
# component_template_id is set, but no attribute value match.
|
# component_template_id is set, but no attribute value match.
|
||||||
continue
|
continue
|
||||||
@@ -337,8 +345,10 @@ class MrpBom(models.Model):
|
|||||||
else:
|
else:
|
||||||
return line_product_id
|
return line_product_id
|
||||||
|
|
||||||
def write(self, vals):
|
@api.constrains("product_tmpl_id", "product_id")
|
||||||
res = super(MrpBom, self).write(vals)
|
def _check_component_attributes(self):
|
||||||
for line in self.bom_line_ids:
|
return self.bom_line_ids._check_component_attributes()
|
||||||
line._update_component_attributes()
|
|
||||||
return res
|
@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):
|
class MrpProduction(models.Model):
|
||||||
@@ -12,7 +12,6 @@ class MrpProduction(models.Model):
|
|||||||
bom_line.product_id = False
|
bom_line.product_id = False
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def write(self, vals):
|
@api.constrains("bom_id")
|
||||||
for bl in self.bom_id.bom_line_ids.filtered("component_template_id"):
|
def _check_component_attributes(self):
|
||||||
bl._check_component_attributes()
|
self.bom_id._check_component_attributes()
|
||||||
return super().write(vals)
|
|
||||||
|
|||||||
@@ -1,31 +1,24 @@
|
|||||||
from odoo import _, models
|
from odoo import _, api, models
|
||||||
from odoo.exceptions import UserError
|
from odoo.exceptions import UserError
|
||||||
|
|
||||||
|
|
||||||
class ProductTemplate(models.Model):
|
class ProductTemplate(models.Model):
|
||||||
_inherit = "product.template"
|
_inherit = "product.template"
|
||||||
|
|
||||||
def write(self, vals):
|
@api.constrains("attribute_line_ids")
|
||||||
res = super().write(vals)
|
|
||||||
for rec in self:
|
|
||||||
rec._check_product_with_component_change_allowed()
|
|
||||||
rec._check_component_change_allowed()
|
|
||||||
return res
|
|
||||||
|
|
||||||
def _check_product_with_component_change_allowed(self):
|
def _check_product_with_component_change_allowed(self):
|
||||||
self.ensure_one()
|
for rec in self:
|
||||||
if len(self.attribute_line_ids) > 0 and len(self.bom_ids) > 0:
|
if not rec.attribute_line_ids:
|
||||||
for bom in self.bom_ids:
|
continue
|
||||||
for line in bom.bom_line_ids.filtered(
|
for bom in rec.bom_ids:
|
||||||
lambda x: x.match_on_attribute_ids
|
for line in bom.bom_line_ids.filtered("match_on_attribute_ids"):
|
||||||
):
|
prod_attr_ids = rec.attribute_line_ids.attribute_id.filtered(
|
||||||
prod_attr_ids = self.attribute_line_ids.attribute_id.filtered(
|
|
||||||
lambda x: x.create_variant != "no_variant"
|
lambda x: x.create_variant != "no_variant"
|
||||||
).ids
|
).ids
|
||||||
comp_attr_ids = line.match_on_attribute_ids.ids
|
comp_attr_ids = line.match_on_attribute_ids.ids
|
||||||
diff = list(set(comp_attr_ids) - set(prod_attr_ids))
|
diff_ids = list(set(comp_attr_ids) - set(prod_attr_ids))
|
||||||
if len(diff) > 0:
|
diff = rec.env["product.attribute"].browse(diff_ids)
|
||||||
attr_recs = self.env["product.attribute"].browse(diff)
|
if diff:
|
||||||
raise UserError(
|
raise UserError(
|
||||||
_(
|
_(
|
||||||
"The attributes you're trying to remove are used in "
|
"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 "
|
"To remove these attributes, first remove the BOM line "
|
||||||
"with the matching component.\n"
|
"with the matching component.\n"
|
||||||
"Attributes: %(attributes)s\nBoM: %(bom)s",
|
"Attributes: %(attributes)s\nBoM: %(bom)s",
|
||||||
attributes=", ".join(attr_recs.mapped("name")),
|
attributes=", ".join(diff.mapped("name")),
|
||||||
bom=bom.display_name,
|
bom=bom.display_name,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@api.constrains("attribute_line_ids")
|
||||||
def _check_component_change_allowed(self):
|
def _check_component_change_allowed(self):
|
||||||
self.ensure_one()
|
for rec in self:
|
||||||
if len(self.attribute_line_ids) > 0:
|
if not rec.attribute_line_ids:
|
||||||
|
continue
|
||||||
boms = self._get_component_boms()
|
boms = self._get_component_boms()
|
||||||
if boms:
|
if not boms:
|
||||||
for bom in boms:
|
continue
|
||||||
vpa = bom.product_tmpl_id.valid_product_template_attribute_line_ids
|
for bom in boms:
|
||||||
prod_attr_ids = vpa.attribute_id.ids
|
vpa = bom.product_tmpl_id.valid_product_template_attribute_line_ids
|
||||||
comp_attr_ids = self.attribute_line_ids.attribute_id.ids
|
prod_attr_ids = vpa.attribute_id.ids
|
||||||
diff = list(set(comp_attr_ids) - set(prod_attr_ids))
|
comp_attr_ids = self.attribute_line_ids.attribute_id.ids
|
||||||
if len(diff) > 0:
|
diff = list(set(comp_attr_ids) - set(prod_attr_ids))
|
||||||
attr_recs = self.env["product.attribute"].browse(diff)
|
if len(diff) > 0:
|
||||||
raise UserError(
|
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 "
|
"This product template is used as a component in the "
|
||||||
"not present in all such product(s), and this would "
|
"BOMs for %(bom)s and attribute(s) %(attributes)s is "
|
||||||
"break the BOM behavior.",
|
"not present in all such product(s), and this would "
|
||||||
attributes=", ".join(attr_recs.mapped("name")),
|
"break the BOM behavior.",
|
||||||
bom=bom.display_name,
|
attributes=", ".join(attr_recs.mapped("name")),
|
||||||
)
|
bom=bom.display_name,
|
||||||
)
|
)
|
||||||
|
)
|
||||||
|
|
||||||
def _get_component_boms(self):
|
def _get_component_boms(self):
|
||||||
self.ensure_one()
|
self.ensure_one()
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
* Ooops404 <https://ooops404.com>
|
* Ooops404 <https://ooops404.com>
|
||||||
|
|
||||||
* Ilyas
|
* 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>
|
# @author Iván Todorovich <ivan.todorovich@camptocamp.com>
|
||||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
|
# 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):
|
class ReportBomStructure(models.AbstractModel):
|
||||||
@@ -30,7 +30,8 @@ class ReportBomStructure(models.AbstractModel):
|
|||||||
else:
|
else:
|
||||||
line.product_id = line_product
|
line.product_id = line_product
|
||||||
if to_ignore_line_ids:
|
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(
|
components, total = super()._get_bom_lines(
|
||||||
bom, bom_quantity, product, line_id, level
|
bom, bom_quantity, product, line_id, level
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,168 +1,134 @@
|
|||||||
from odoo import Command
|
from odoo.tests import Form, common
|
||||||
from odoo.models import BaseModel
|
|
||||||
from odoo.tests import Form, TransactionCase
|
|
||||||
|
|
||||||
|
|
||||||
class TestMrpBomAttributeMatchBase(TransactionCase):
|
class TestMrpAttachmentMgmtBase(common.SavepointCase):
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
super().setUpClass()
|
super().setUpClass()
|
||||||
cls.env = cls.env(context=dict(cls.env.context, tracking_disable=True))
|
cls._create_products(cls)
|
||||||
cls.warehouse = cls.env.ref("stock.warehouse0")
|
cls._create_boms(cls)
|
||||||
cls.route_manufacture = cls.warehouse.manufacture_pull_id.route_id
|
|
||||||
# Create products
|
def _create_products(self):
|
||||||
cls.product_sword = cls.env["product.template"].create(
|
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",
|
"name": "Plastic Sword",
|
||||||
"type": "product",
|
"type": "product",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
cls.product_surf = cls.env["product.template"].create(
|
self.product_surf = self.env["product.template"].create(
|
||||||
{
|
{
|
||||||
"name": "Surf",
|
"name": "Surf",
|
||||||
"type": "product",
|
"type": "product",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
cls.product_fin = cls.env["product.template"].create(
|
self.product_fin = self.env["product.template"].create(
|
||||||
{
|
{
|
||||||
"name": "Surf Fin",
|
"name": "Surf Fin",
|
||||||
"type": "product",
|
"type": "product",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
cls.product_plastic = cls.env["product.template"].create(
|
self.product_plastic = self.env["product.template"].create(
|
||||||
{
|
{
|
||||||
"name": "Plastic Component",
|
"name": "Plastic Component",
|
||||||
"type": "product",
|
"type": "product",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
cls.p1 = cls.env["product.template"].create(
|
self.p1 = self.env["product.template"].create(
|
||||||
{
|
{
|
||||||
"name": "P1",
|
"name": "P1",
|
||||||
"type": "product",
|
"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",
|
"name": "P2",
|
||||||
"type": "product",
|
"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",
|
"name": "P3",
|
||||||
"type": "product",
|
"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",
|
"name": "Paper",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
cls.product_10 = cls.env["product.product"].create(
|
self.product_10 = self.env["product.product"].create(
|
||||||
{
|
{
|
||||||
"name": "Stone",
|
"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"}
|
{"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": "Cyan", "attribute_id": self.product_attribute.id},
|
||||||
{"name": "Magenta", "attribute_id": cls.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,
|
"attribute_id": self.product_attribute.id,
|
||||||
"product_tmpl_id": cls.product_plastic.id,
|
"product_tmpl_id": self.product_plastic.id,
|
||||||
"value_ids": [Command.set(cls.product_attribute.value_ids.ids)],
|
"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,
|
"attribute_id": self.product_attribute.id,
|
||||||
"product_tmpl_id": cls.product_sword.id,
|
"product_tmpl_id": self.product_sword.id,
|
||||||
"value_ids": [Command.set(cls.product_attribute.value_ids.ids)],
|
"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_boms(self):
|
||||||
def _create_bom(cls, product, line_form_vals):
|
mrp_bom_form = Form(self.env["mrp.bom"])
|
||||||
if product._name == "product.template":
|
mrp_bom_form.product_tmpl_id = self.product_sword
|
||||||
template = product
|
with mrp_bom_form.bom_line_ids.new() as line_form:
|
||||||
product = cls.env["product.product"]
|
line_form.component_template_id = self.product_plastic
|
||||||
else:
|
line_form.product_qty = 1
|
||||||
template = product.product_tmpl_id
|
self.bom_id = mrp_bom_form.save()
|
||||||
with Form(cls.env["mrp.bom"]) as form:
|
|
||||||
form.product_tmpl_id = template
|
mrp_bom_form = Form(self.env["mrp.bom"])
|
||||||
form.product_id = product
|
mrp_bom_form.product_tmpl_id = self.product_fin
|
||||||
for vals in line_form_vals:
|
with mrp_bom_form.bom_line_ids.new() as line_form:
|
||||||
with form.bom_line_ids.new() as line_form:
|
line_form.product_id = self.product_plastic.product_variant_ids[0]
|
||||||
for key, value in vals.items():
|
line_form.product_qty = 1
|
||||||
field = line_form._model._fields.get(key)
|
self.fin_bom_id = mrp_bom_form.save()
|
||||||
if field and field.relational: # pragma: no cover
|
|
||||||
if value and not isinstance(value, BaseModel):
|
mrp_bom_form = Form(self.env["mrp.bom"])
|
||||||
value = cls.env[field.comodel_name].browse(value)
|
mrp_bom_form.product_tmpl_id = self.product_surf
|
||||||
elif not value:
|
with mrp_bom_form.bom_line_ids.new() as line_form:
|
||||||
value = cls.env[field.comodel_name]
|
line_form.product_id = self.product_fin.product_variant_ids[0]
|
||||||
setattr(line_form, key, value)
|
line_form.product_qty = 1
|
||||||
return form.save()
|
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.exceptions import UserError, ValidationError
|
||||||
from odoo.tests import Form
|
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):
|
def test_bom_1(self):
|
||||||
mrp_bom_form = Form(self.env["mrp.bom"])
|
mrp_bom_form = Form(self.env["mrp.bom"])
|
||||||
mrp_bom_form.product_tmpl_id = self.product_sword
|
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.component_template_id = self.product_plastic
|
||||||
line_form.product_qty = 1
|
line_form.product_qty = 1
|
||||||
sword_cyan = self.sword_attrs.product_template_value_ids[0]
|
sword_cyan = self.sword_attrs.product_template_value_ids[0]
|
||||||
with self.assertRaisesRegex(
|
with self.assertRaises(ValidationError):
|
||||||
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 .*",
|
|
||||||
):
|
|
||||||
line_form.bom_product_template_attribute_value_ids.add(sword_cyan)
|
line_form.bom_product_template_attribute_value_ids.add(sword_cyan)
|
||||||
|
|
||||||
def test_bom_2(self):
|
def test_bom_2(self):
|
||||||
@@ -43,12 +42,7 @@ class TestMrpBomAttributeMatch(TestMrpBomAttributeMatchBase):
|
|||||||
"value_ids": [(4, orchid_attribute_value_id.id)],
|
"value_ids": [(4, orchid_attribute_value_id.id)],
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
with self.assertRaisesRegex(
|
with self.assertRaises(UserError):
|
||||||
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\.",
|
|
||||||
):
|
|
||||||
vals = {
|
vals = {
|
||||||
"attribute_id": smell_attribute.id,
|
"attribute_id": smell_attribute.id,
|
||||||
"product_tmpl_id": self.product_plastic.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 = Form(self.env["mrp.bom"])
|
||||||
mrp_bom_form.product_tmpl_id = self.product_sword
|
mrp_bom_form.product_tmpl_id = self.product_sword
|
||||||
with mrp_bom_form.bom_line_ids.new() as line_form:
|
with mrp_bom_form.bom_line_ids.new() as line_form:
|
||||||
with self.assertRaisesRegex(
|
with self.assertRaises(ValidationError):
|
||||||
UserError,
|
|
||||||
r"Some attributes of the dynamic component are not included into "
|
|
||||||
r"production product attributes\.",
|
|
||||||
):
|
|
||||||
line_form.component_template_id = self.product_plastic
|
line_form.component_template_id = self.product_plastic
|
||||||
plastic_smells_like_orchid.unlink()
|
plastic_smells_like_orchid.unlink()
|
||||||
|
|
||||||
@@ -103,10 +93,8 @@ class TestMrpBomAttributeMatch(TestMrpBomAttributeMatchBase):
|
|||||||
# Component skipped
|
# Component skipped
|
||||||
mo_form.bom_id = self.bom_id
|
mo_form.bom_id = self.bom_id
|
||||||
mo_form.product_qty = 1
|
mo_form.product_qty = 1
|
||||||
with self.assertRaisesRegex(
|
with self.assertRaises(ValidationError):
|
||||||
ValidationError,
|
# Some attributes of the dynamic component are not included into ...
|
||||||
r"Some attributes of the dynamic component are not included into .+",
|
|
||||||
):
|
|
||||||
self.mo_sword = mo_form.save()
|
self.mo_sword = mo_form.save()
|
||||||
|
|
||||||
def test_manufacturing_order_4(self):
|
def test_manufacturing_order_4(self):
|
||||||
@@ -168,20 +156,5 @@ class TestMrpBomAttributeMatch(TestMrpBomAttributeMatchBase):
|
|||||||
"product_qty": 1.0,
|
"product_qty": 1.0,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
with self.assertRaisesRegex(UserError, r"Recursion error! .+"):
|
with self.assertRaises(UserError):
|
||||||
test_bom_3.explode(self.product_9, 1)
|
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" ?>
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
<odoo>
|
<odoo>
|
||||||
<record id="mrp_bom_view_form_inherit_bom_match" model="ir.ui.view">
|
<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="model">mrp.bom</field>
|
||||||
<field name="inherit_id" ref="mrp.mrp_bom_form_view" />
|
<field name="inherit_id" ref="mrp.mrp_bom_form_view" />
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<field name="bom_product_template_attribute_value_ids" position="before">
|
<field name="bom_product_template_attribute_value_ids" position="before">
|
||||||
<field
|
<field name="match_on_attribute_ids" widget="many2many_tags" />
|
||||||
name="match_on_attribute_ids"
|
|
||||||
readonly='1'
|
|
||||||
force_save="1"
|
|
||||||
widget="many2many_tags"
|
|
||||||
/>
|
|
||||||
<field name="product_backup_id" invisible="1" />
|
<field name="product_backup_id" invisible="1" />
|
||||||
</field>
|
</field>
|
||||||
<xpath
|
<xpath
|
||||||
|
|||||||
Reference in New Issue
Block a user