From 7402c5808fb3e17c1b4d59b5f8c681026284f229 Mon Sep 17 00:00:00 2001 From: Ilyas Date: Fri, 17 Jun 2022 19:31:21 +0700 Subject: [PATCH 01/19] [14.0][ADD] mrp_bom_attribute_match --- mrp_bom_attribute_match/README.rst | 137 +++++ mrp_bom_attribute_match/__init__.py | 1 + mrp_bom_attribute_match/__manifest__.py | 16 + mrp_bom_attribute_match/i18n/it.po | 157 ++++++ .../i18n/mrp_bom_attribute_match.pot | 133 +++++ mrp_bom_attribute_match/models/__init__.py | 3 + mrp_bom_attribute_match/models/mrp_bom.py | 297 +++++++++++ .../models/mrp_production.py | 19 + mrp_bom_attribute_match/models/product.py | 67 +++ .../readme/CONTRIBUTORS.rst | 3 + .../readme/DESCRIPTION.rst | 38 ++ mrp_bom_attribute_match/readme/USAGE.rst | 20 + .../static/description/icon.png | Bin 0 -> 9455 bytes .../static/description/index.html | 476 ++++++++++++++++++ mrp_bom_attribute_match/tests/__init__.py | 1 + mrp_bom_attribute_match/tests/common.py | 134 +++++ .../tests/test_mrp_bom_attribute_match.py | 160 ++++++ .../views/mrp_bom_views.xml | 34 ++ 18 files changed, 1696 insertions(+) create mode 100644 mrp_bom_attribute_match/README.rst create mode 100644 mrp_bom_attribute_match/__init__.py create mode 100644 mrp_bom_attribute_match/__manifest__.py create mode 100644 mrp_bom_attribute_match/i18n/it.po create mode 100644 mrp_bom_attribute_match/i18n/mrp_bom_attribute_match.pot create mode 100644 mrp_bom_attribute_match/models/__init__.py create mode 100644 mrp_bom_attribute_match/models/mrp_bom.py create mode 100644 mrp_bom_attribute_match/models/mrp_production.py create mode 100644 mrp_bom_attribute_match/models/product.py create mode 100644 mrp_bom_attribute_match/readme/CONTRIBUTORS.rst create mode 100644 mrp_bom_attribute_match/readme/DESCRIPTION.rst create mode 100644 mrp_bom_attribute_match/readme/USAGE.rst create mode 100644 mrp_bom_attribute_match/static/description/icon.png create mode 100644 mrp_bom_attribute_match/static/description/index.html create mode 100644 mrp_bom_attribute_match/tests/__init__.py create mode 100644 mrp_bom_attribute_match/tests/common.py create mode 100644 mrp_bom_attribute_match/tests/test_mrp_bom_attribute_match.py create mode 100644 mrp_bom_attribute_match/views/mrp_bom_views.xml diff --git a/mrp_bom_attribute_match/README.rst b/mrp_bom_attribute_match/README.rst new file mode 100644 index 0000000000..c81644097c --- /dev/null +++ b/mrp_bom_attribute_match/README.rst @@ -0,0 +1,137 @@ +=================== +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-LGPL--3-blue.png + :target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html + :alt: License: LGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fmanufacture-lightgray.png?logo=github + :target: https://github.com/OCA/manufacture/tree/14.0/mrp_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-14-0/manufacture-14-0-mrp_bom_attribute_match + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png + :target: https://runbot.odoo-community.org/runbot/129/14.0 + :alt: Try me on Runbot + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module addresses the BoM case where the product to manufacture has one attribute with tens or hundreds of values (usually attribute "color", eg: "Configurable Desk" can be produced in 900 different colors). + +Creating a dynamic BoM currently requires adding one BoM line for each attribute value to match component variant with attribute value (eg: component "Desk board (Green)" to be applied to variant "Green"). + +This has 3 downsides: + +- BoM lines proliferation (more error prone) + +- Difficult to update in case a new attribute value (new color paint) is added + +- Difficult to update in case base component changes. + + +This module allows to use a product template as component in BoM lines, automatically matching component variant to use in MO line with the attribute value selected for manufacture. + +Eg: Product template "Desk Board" is added to BoM line for product "Configurable Desk"; match is made on attribute "Color". In MO, if product to manufacture is "Configurable Desk (Steel, Pink)", MO line will have component "Desk Board (Pink)". + +Using the same BoM, if product to manufacture is "Configurable Desk (Steel, Yellow)", MO line will have component "Desk Board (Yellow)". + + +The flow is valid also if the Component (Product Template) has more than one attribute matching the product to manufacture; in this case, on MO line the component variant will be the one matching multiple attribute values for the product to manufacture. + + +Various checks are in place to make sure this flow is not disrupted: + +- user cannot add a product in field "Component (Product Template)" which: + + does not have matching attributes with product to manufacture + + has a different variant-generating attribute than the product to manufacture + +- Adding a new variant-generating attribute to a product used as "Component (Product Template)" raises an error if the attribute is not included in all the products to manufacture where component is referenced. + +- Removing an attribute used for BoM attribute matching from product to manufacture raises an error. + +- On a BoM line with Component (Product Template) set, an attribute value of attributes referenced in "Match on attribute" field cannot be used in field "Apply to variant". + +- If attribute value for matching attribute in manufactured product is not present in component (product template), the BoM line is skipped in MO. + +**Table of contents** + +.. contents:: + :local: + +Usage +===== + +Using this module you can have dynamic components of a BOM. +It will allow you to have only 1 line in the BOM if you have hundreds of attribute +values for manufacturing product and hundreds of attributes values of component (material). + +How to use + + #. Create a product to produce e.g. Desk. + #. Set 1 attribute (e.g. Color). And select possible values for it. + #. Create a component product (material) e.g. Plastic. + #. Set 1 attribute (Color). And select possible values for it. + #. Create a BOM. + #. Select a manufacturing product Desk. + #. Add a BOM line. Select Component (product template) Plastic. + #. You will see Color attribute appeared in the Apply On Attribute field. + #. Save the BOM. + #. Create Manufacturing Order. Select Desk with e.g. Red color to produce and BOM you created. + #. You will see in the component list Plastic added with corresponding (red) color. + +Consider, that to use this feature component must have only 1 attribute. +And a values of this attribute of a manufacturing product should be available for a component. + +Bug Tracker +=========== + +Bugs are tracked on `GitHub 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 `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +~~~~~~~ + +* Ilyas +* Ooops + +Contributors +~~~~~~~~~~~~ + +* Ooops404 + + * Ilyas + +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. + +This module is part of the `OCA/manufacture `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/mrp_bom_attribute_match/__init__.py b/mrp_bom_attribute_match/__init__.py new file mode 100644 index 0000000000..0650744f6b --- /dev/null +++ b/mrp_bom_attribute_match/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/mrp_bom_attribute_match/__manifest__.py b/mrp_bom_attribute_match/__manifest__.py new file mode 100644 index 0000000000..6fa47b638e --- /dev/null +++ b/mrp_bom_attribute_match/__manifest__.py @@ -0,0 +1,16 @@ +{ + "name": "BOM Attribute Match", + "version": "14.0.1.0.1", + "category": "Manufacturing", + "author": "Ilyas, Ooops, Odoo Community Association (OCA)", + "summary": "Dynamic BOM component based on product attribute", + "depends": ["mrp_account"], + "license": "LGPL-3", + "website": "https://github.com/OCA/manufacture", + "data": [ + "views/mrp_bom_views.xml", + ], + "qweb": [], + "application": False, + "installable": True, +} diff --git a/mrp_bom_attribute_match/i18n/it.po b/mrp_bom_attribute_match/i18n/it.po new file mode 100644 index 0000000000..f840f129b2 --- /dev/null +++ b/mrp_bom_attribute_match/i18n/it.po @@ -0,0 +1,157 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * mrp_bom_attribute_match +# +# Francesco @ Ooops , 2022. +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 12.0+e\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2022-01-14 17:50+0000\n" +"PO-Revision-Date: 2022-03-14 13:36+0000\n" +"Last-Translator: Francesco @ Ooops \n" +"Language-Team: Italian \n" +"Language: it\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 4.4-dev\n" + +#. module: mrp_bom_attribute_match +#: model:ir.model,name:mrp_bom_attribute_match.model_mrp_bom +msgid "Bill of Material" +msgstr "Distinta base" + +#. module: mrp_bom_attribute_match +#: model:ir.model,name:mrp_bom_attribute_match.model_mrp_bom_line +msgid "Bill of Material Line" +msgstr "Riga Distinta Base" + +#. module: mrp_bom_attribute_match +#: model:ir.model.fields,field_description:mrp_bom_attribute_match.field_mrp_bom_line__product_id +msgid "Component" +msgstr "Componente" + +#. module: mrp_bom_attribute_match +#: model:ir.model.fields,field_description:mrp_bom_attribute_match.field_mrp_bom_line__component_template_id +msgid "Component (product template)" +msgstr "Componente (modello prodotto)" + +#. module: mrp_bom_attribute_match +#: model:ir.model.fields,field_description:mrp_bom_attribute_match.field_mrp_bom__display_name +#: model:ir.model.fields,field_description:mrp_bom_attribute_match.field_mrp_bom_line__display_name +#: model:ir.model.fields,field_description:mrp_bom_attribute_match.field_mrp_production__display_name +#: model:ir.model.fields,field_description:mrp_bom_attribute_match.field_product_template__display_name +msgid "Display Name" +msgstr "Nome visualizzato" + +#. module: mrp_bom_attribute_match +#: model:ir.model.fields,field_description:mrp_bom_attribute_match.field_mrp_bom__id +#: model:ir.model.fields,field_description:mrp_bom_attribute_match.field_mrp_bom_line__id +#: model:ir.model.fields,field_description:mrp_bom_attribute_match.field_mrp_production__id +#: model:ir.model.fields,field_description:mrp_bom_attribute_match.field_product_template__id +msgid "ID" +msgstr "ID" + +#. module: mrp_bom_attribute_match +#: model:ir.model.fields,field_description:mrp_bom_attribute_match.field_mrp_bom____last_update +#: model:ir.model.fields,field_description:mrp_bom_attribute_match.field_mrp_bom_line____last_update +#: model:ir.model.fields,field_description:mrp_bom_attribute_match.field_mrp_production____last_update +#: model:ir.model.fields,field_description:mrp_bom_attribute_match.field_product_template____last_update +msgid "Last Modified on" +msgstr "Ultima modifica il" + +#. module: mrp_bom_attribute_match +#: model:ir.model.fields,field_description:mrp_bom_attribute_match.field_mrp_bom_line__match_on_attribute_ids +msgid "Match on Attributes" +msgstr "" + +#. module: mrp_bom_attribute_match +#: model:ir.model.fields,field_description:mrp_bom_attribute_match.field_mrp_bom_line__product_backup_id +msgid "Product Backup" +msgstr "Backup componente" + +#. module: mrp_bom_attribute_match +#: model:ir.model,name:mrp_bom_attribute_match.model_product_template +msgid "Product Template" +msgstr "Modello Prodotto" + +#. module: mrp_bom_attribute_match +#: model:ir.model,name:mrp_bom_attribute_match.model_mrp_production +msgid "Production Order" +msgstr "Ordine di Produzione" + +#. module: mrp_bom_attribute_match +#: code:addons/mrp_bom_attribute_match/models/mrp_bom.py:0 +#, python-format +msgid "" +"Recursion error! A product with a Bill of Material should not have itself " +"in its BoM or child BoMs!" +msgstr "" +"Errore ricorsivo! Un prodotto con una distinta base non può essere presente " +"nella sua DiBa o nelle DiBa figlie!" + +#. module: mrp_bom_attribute_match +#: code:addons/mrp_bom_attribute_match/models/mrp_bom.py:0 +#, python-format +msgid "" +"Some attributes of the dynamic component are not included into production " +"product attributes." +msgstr "" + +#. module: mrp_bom_attribute_match +#: model:ir.model.fields,help:mrp_bom_attribute_match.field_mrp_bom_line__product_backup_id +msgid "Technical field to store previous value of product_id" +msgstr "Campo tecnico per preservare i valori di product_id" + +#. module: mrp_bom_attribute_match +#: code:addons/mrp_bom_attribute_match/models/product.py:0 +#, python-format +msgid "" +"This product template is used as a component in the BOMs for %s and " +"attribute(s) %s is not present in all such product(s), and this would break " +"the BOM behavior." +msgstr "" + +#. module: mrp_bom_attribute_match +#: code:addons/mrp_bom_attribute_match/models/mrp_bom.py:0 +#, python-format +msgid "" +"You cannot use an attribute value for attribute %s in the field “Apply on " +"Variants” as it’s the same attribute used in field “Match on " +"Attribute”related to the component %s." +msgstr "" +"Non puoi utilizzare un valore attributo dell'attributo %s nel campo “Applica " +"alle varianti” dato che è lo stesso attributo utilizzato nel campo " +"“Corrispondenza su attributo” relativo al componente %s." + +#~ msgid "Dynamic component must have only 1 attribute" +#~ msgstr "Il componente dinamico deve avere un solo attributo" + +#~ msgid "Match on Attribute" +#~ msgstr "Corrispondenza su attributo" + +#~ msgid "Only product template with one attribute can be added to this field." +#~ msgstr "" +#~ "Solo i modelli prodotto con un solo attributo possono essere aggiunti a " +#~ "questo campo." + +#~ msgid "" +#~ "This product included into BOMs as dynamic component. Please remove it " +#~ "from related BOMs to be able to have multiple attributes for it. BOM ids: " +#~ "%s" +#~ msgstr "" +#~ "Questo prodotto è incluso in una distinta base come componente dinamico. " +#~ "Per aggiungere altri attributi, devi rimuoverlo dalle seguenti DiBa: %s" + +#~ msgid "Attribute Value Backup" +#~ msgstr "Backup Valori Attributo" + +#~ msgid "Match On Attribute" +#~ msgstr "Corrispondenza su attributo" + +#~ msgid "Technical field to store previous value of attribute_value_ids" +#~ msgstr "" +#~ "Campo tecnico per preservare i valori precedenti di attribute_value_ids" diff --git a/mrp_bom_attribute_match/i18n/mrp_bom_attribute_match.pot b/mrp_bom_attribute_match/i18n/mrp_bom_attribute_match.pot new file mode 100644 index 0000000000..600a3c9e9a --- /dev/null +++ b/mrp_bom_attribute_match/i18n/mrp_bom_attribute_match.pot @@ -0,0 +1,133 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * mrp_bom_attribute_match +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 14.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: mrp_bom_attribute_match +#: model:ir.model,name:mrp_bom_attribute_match.model_mrp_bom +msgid "Bill of Material" +msgstr "" + +#. module: mrp_bom_attribute_match +#: model:ir.model,name:mrp_bom_attribute_match.model_mrp_bom_line +msgid "Bill of Material Line" +msgstr "" + +#. module: mrp_bom_attribute_match +#: model:ir.model.fields,field_description:mrp_bom_attribute_match.field_mrp_bom_line__product_id +msgid "Component" +msgstr "" + +#. module: mrp_bom_attribute_match +#: model:ir.model.fields,field_description:mrp_bom_attribute_match.field_mrp_bom_line__component_template_id +msgid "Component (product template)" +msgstr "" + +#. module: mrp_bom_attribute_match +#: model:ir.model.fields,field_description:mrp_bom_attribute_match.field_mrp_bom__display_name +#: model:ir.model.fields,field_description:mrp_bom_attribute_match.field_mrp_bom_line__display_name +#: model:ir.model.fields,field_description:mrp_bom_attribute_match.field_mrp_production__display_name +#: model:ir.model.fields,field_description:mrp_bom_attribute_match.field_product_template__display_name +msgid "Display Name" +msgstr "" + +#. module: mrp_bom_attribute_match +#: model:ir.model.fields,field_description:mrp_bom_attribute_match.field_mrp_bom__id +#: model:ir.model.fields,field_description:mrp_bom_attribute_match.field_mrp_bom_line__id +#: model:ir.model.fields,field_description:mrp_bom_attribute_match.field_mrp_production__id +#: model:ir.model.fields,field_description:mrp_bom_attribute_match.field_product_template__id +msgid "ID" +msgstr "" + +#. module: mrp_bom_attribute_match +#: model:ir.model.fields,field_description:mrp_bom_attribute_match.field_mrp_bom____last_update +#: model:ir.model.fields,field_description:mrp_bom_attribute_match.field_mrp_bom_line____last_update +#: model:ir.model.fields,field_description:mrp_bom_attribute_match.field_mrp_production____last_update +#: model:ir.model.fields,field_description:mrp_bom_attribute_match.field_product_template____last_update +msgid "Last Modified on" +msgstr "" + +#. module: mrp_bom_attribute_match +#: model:ir.model.fields,field_description:mrp_bom_attribute_match.field_mrp_bom_line__match_on_attribute_ids +msgid "Match on Attributes" +msgstr "" + +#. module: mrp_bom_attribute_match +#: code:addons/mrp_bom_attribute_match/models/mrp_bom.py:0 +#, python-format +msgid "" +"No match on attribute has been detected for Component (Product Template) %s" +msgstr "" + +#. module: mrp_bom_attribute_match +#: model:ir.model.fields,field_description:mrp_bom_attribute_match.field_mrp_bom_line__product_backup_id +msgid "Product Backup" +msgstr "" + +#. module: mrp_bom_attribute_match +#: model:ir.model,name:mrp_bom_attribute_match.model_product_template +msgid "Product Template" +msgstr "" + +#. module: mrp_bom_attribute_match +#: model:ir.model,name:mrp_bom_attribute_match.model_mrp_production +msgid "Production Order" +msgstr "" + +#. module: mrp_bom_attribute_match +#: code:addons/mrp_bom_attribute_match/models/mrp_bom.py:0 +#, python-format +msgid "" +"Recursion error! A product with a Bill of Material should not have itself " +"in its BoM or child BoMs!" +msgstr "" + +#. module: mrp_bom_attribute_match +#: code:addons/mrp_bom_attribute_match/models/mrp_bom.py:0 +#, python-format +msgid "" +"Some attributes of the dynamic component are not included into production " +"product attributes." +msgstr "" + +#. module: mrp_bom_attribute_match +#: model:ir.model.fields,help:mrp_bom_attribute_match.field_mrp_bom_line__product_backup_id +msgid "Technical field to store previous value of product_id" +msgstr "" + +#. module: mrp_bom_attribute_match +#: code:addons/mrp_bom_attribute_match/models/product.py:0 +#, python-format +msgid "" +"The attributes you're trying to remove is used in BoM as a match with Component (Product Template). To remove these attributes, first remove the BOM line with the matching component.\n" +"Attributes: %s\n" +"BoM: %s" +msgstr "" + +#. module: mrp_bom_attribute_match +#: code:addons/mrp_bom_attribute_match/models/product.py:0 +#, python-format +msgid "" +"This product template is used as a component in the BOMs for %s and " +"attribute(s) %s is not present in all such product(s), and this would break " +"the BOM behavior." +msgstr "" + +#. module: mrp_bom_attribute_match +#: code:addons/mrp_bom_attribute_match/models/mrp_bom.py:0 +#, python-format +msgid "" +"You cannot use an attribute value for attribute %s in the field “Apply on " +"Variants” as it’s the same attribute used in field “Match on " +"Attribute”related to the component %s." +msgstr "" diff --git a/mrp_bom_attribute_match/models/__init__.py b/mrp_bom_attribute_match/models/__init__.py new file mode 100644 index 0000000000..8485117d59 --- /dev/null +++ b/mrp_bom_attribute_match/models/__init__.py @@ -0,0 +1,3 @@ +from . import mrp_bom +from . import mrp_production +from . import product diff --git a/mrp_bom_attribute_match/models/mrp_bom.py b/mrp_bom_attribute_match/models/mrp_bom.py new file mode 100644 index 0000000000..0e6d5e7de2 --- /dev/null +++ b/mrp_bom_attribute_match/models/mrp_bom.py @@ -0,0 +1,297 @@ +import logging + +from odoo import _, api, fields, models +from odoo.exceptions import UserError, ValidationError +from odoo.tools import float_round + +_log = logging.getLogger(__name__) + + +class MrpBomLine(models.Model): + _inherit = "mrp.bom.line" + + product_id = fields.Many2one("product.product", "Component", required=False) + product_backup_id = fields.Many2one( + "product.product", help="Technical field to store previous value of product_id" + ) + component_template_id = fields.Many2one( + "product.template", "Component (product template)" + ) + match_on_attribute_ids = fields.Many2many( + "product.attribute", string="Match on Attributes", readonly=True + ) + + @api.onchange("component_template_id") + def _onchange_component_template_id(self): + 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 + + 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 + ) + ) + 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." + ) + ) + + @api.onchange("bom_product_template_attribute_value_ids") + def _onchange_attribute_value_ids(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: + raise ValidationError( + _( + "You cannot use an attribute value for attribute" + " %s in the field “Apply on Variants” as it’s the" + " same attribute used in field “Match on Attribute”" + "related to the component %s." + % ( + self.env["product.attribute"].browse(same_attrs), + self.component_template_id.name, + ) + ) + ) + + +class MrpBom(models.Model): + _inherit = "mrp.bom" + + # flake8: noqa: C901 + def explode(self, product, quantity, picking_type=False): + # Had to replace this method + """ + Explodes the BoM and creates two lists with all the information you need: + bom_done and line_done + Quantity describes the number of times you need the BoM: so the quantity + divided by the number created by the BoM + and converted into its UoM + """ + from collections import defaultdict + + graph = defaultdict(list) + V = set() + + def check_cycle(v, visited, recStack, graph): + visited[v] = True + recStack[v] = True + for neighbour in graph[v]: + if visited[neighbour] is False: + if check_cycle(neighbour, visited, recStack, graph) is True: + return True + elif recStack[neighbour] is True: + return True + recStack[v] = False + return False + + product_ids = set() + product_boms = {} + + def update_product_boms(): + products = self.env["product.product"].browse(product_ids) + product_boms.update( + self._get_product2bom( + products, + bom_type="phantom", + picking_type=picking_type or self.picking_type_id, + company_id=self.company_id.id, + ) + ) + # Set missing keys to default value + for product in products: + product_boms.setdefault(product, self.env["mrp.bom"]) + + boms_done = [ + ( + self, + { + "qty": quantity, + "product": product, + "original_qty": quantity, + "parent_line": False, + }, + ) + ] + lines_done = [] + V |= {product.product_tmpl_id.id} + + bom_lines = [] + for bom_line in self.bom_line_ids: + product_id = bom_line.product_id + V |= {product_id.product_tmpl_id.id} + graph[product.product_tmpl_id.id].append(product_id.product_tmpl_id.id) + bom_lines.append((bom_line, product, quantity, False)) + product_ids.add(product_id.id) + update_product_boms() + product_ids.clear() + while bom_lines: + current_line, current_product, current_qty, parent_line = bom_lines[0] + bom_lines = bom_lines[1:] + + if current_line._skip_bom_line(current_product): + continue + + line_quantity = current_qty * current_line.product_qty + if current_line.product_id not in product_boms: + update_product_boms() + product_ids.clear() + # upd start + component_template_product = self.get_component_template_product( + current_line, product, current_line.product_id + ) + if component_template_product: + # need to set product_id temporary + current_line.product_id = component_template_product + else: + # component_template_id is set, but no attribute value match. + continue + # upd end + bom = product_boms.get(current_line.product_id) + if bom: + converted_line_quantity = current_line.product_uom_id._compute_quantity( + line_quantity / bom.product_qty, bom.product_uom_id + ) + bom_lines += [ + ( + line, + current_line.product_id, + converted_line_quantity, + current_line, + ) + for line in bom.bom_line_ids + ] + for bom_line in bom.bom_line_ids: + graph[current_line.product_id.product_tmpl_id.id].append( + bom_line.product_id.product_tmpl_id.id + ) + if bom_line.product_id.product_tmpl_id.id in V and check_cycle( + bom_line.product_id.product_tmpl_id.id, + {key: False for key in V}, + {key: False for key in V}, + graph, + ): + raise UserError( + _( + "Recursion error! A product with a Bill of Material " + "should not have itself in its BoM or child BoMs!" + ) + ) + V |= {bom_line.product_id.product_tmpl_id.id} + if bom_line.product_id not in product_boms: + product_ids.add(bom_line.product_id.id) + boms_done.append( + ( + bom, + { + "qty": converted_line_quantity, + "product": current_product, + "original_qty": quantity, + "parent_line": current_line, + }, + ) + ) + else: + # We round up here because the user expects + # that if he has to consume a little more, the whole UOM unit + # should be consumed. + rounding = current_line.product_uom_id.rounding + line_quantity = float_round( + line_quantity, precision_rounding=rounding, rounding_method="UP" + ) + lines_done.append( + ( + current_line, + { + "qty": line_quantity, + "product": current_product, + "original_qty": quantity, + "parent_line": parent_line, + }, + ) + ) + return boms_done, lines_done + + def get_component_template_product(self, bom_line, bom_product_id, line_product_id): + if bom_line.component_template_id: + comp = bom_line.component_template_id + comp_attr_ids = ( + comp.valid_product_template_attribute_line_ids.attribute_id.ids + ) + prod_attr_ids = ( + bom_product_id.valid_product_template_attribute_line_ids.attribute_id.ids + ) + # check attributes + if not all(item in prod_attr_ids for item in comp_attr_ids): + _log.info( + "Component skipped. Component attributes must be included into " + "product attributes to use component_template_id." + ) + return False + # find matching combination + combination = self.env["product.template.attribute.value"] + for ptav in bom_product_id.product_template_attribute_value_ids: + combination |= self.env["product.template.attribute.value"].search( + [ + ("product_tmpl_id", "=", comp.id), + ("attribute_id", "=", ptav.attribute_id.id), + ( + "product_attribute_value_id", + "=", + ptav.product_attribute_value_id.id, + ), + ] + ) + if len(combination) == 0: + return False + product_id = comp._get_variant_for_combination(combination) + if product_id and product_id.active: + return product_id + return False + 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 diff --git a/mrp_bom_attribute_match/models/mrp_production.py b/mrp_bom_attribute_match/models/mrp_production.py new file mode 100644 index 0000000000..b276a491ad --- /dev/null +++ b/mrp_bom_attribute_match/models/mrp_production.py @@ -0,0 +1,19 @@ +from odoo import models + + +class MrpProduction(models.Model): + _inherit = "mrp.production" + + def action_confirm(self): + res = super(MrpProduction, self).action_confirm() + if self and self.bom_id: + for bom_line in self.bom_id.bom_line_ids: + if bom_line.component_template_id: + # product_id was set in mrp.bom.explode for correct flow. Need to remove it. + bom_line.product_id = False + return res + + def write(self, vals): + for bl in self.bom_id.bom_line_ids.filtered(lambda x: x.component_template_id): + bl.check_component_attributes() + return super(MrpProduction, self).write(vals) diff --git a/mrp_bom_attribute_match/models/product.py b/mrp_bom_attribute_match/models/product.py new file mode 100644 index 0000000000..b9b0cc4c51 --- /dev/null +++ b/mrp_bom_attribute_match/models/product.py @@ -0,0 +1,67 @@ +from odoo import _, models +from odoo.exceptions import UserError + + +class ProductTemplate(models.Model): + _inherit = "product.template" + + def write(self, vals): + res = super(ProductTemplate, self).write(vals) + self.check_product_with_component_change_allowed() + self.check_component_change_allowed() + return res + + def check_product_with_component_change_allowed(self): + 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( + 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) + raise UserError( + _( + "The attributes you're trying to remove is used in BoM " + "as a match with Component (Product Template). To " + "remove these attributes, first remove the BOM line " + "with the matching component.\nAttributes: %s\nBoM: %s" + % (attr_recs.mapped("name"), bom.display_name) + ) + ) + + def check_component_change_allowed(self): + if len(self.attribute_line_ids) > 0: + 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 %s and attribute(s) %s is not present in all " + "such product(s), and this would break the BOM " + "behavior." + % ( + bom.display_name, + attr_recs.mapped("name"), + ) + ) + ) + + def get_component_boms(self): + bom_lines = self.env["mrp.bom.line"].search( + [("component_template_id", "=", self._origin.id)] + ) + if bom_lines: + return bom_lines.mapped("bom_id") + return False diff --git a/mrp_bom_attribute_match/readme/CONTRIBUTORS.rst b/mrp_bom_attribute_match/readme/CONTRIBUTORS.rst new file mode 100644 index 0000000000..a1c144e4c2 --- /dev/null +++ b/mrp_bom_attribute_match/readme/CONTRIBUTORS.rst @@ -0,0 +1,3 @@ +* Ooops404 + + * Ilyas diff --git a/mrp_bom_attribute_match/readme/DESCRIPTION.rst b/mrp_bom_attribute_match/readme/DESCRIPTION.rst new file mode 100644 index 0000000000..e079f5673e --- /dev/null +++ b/mrp_bom_attribute_match/readme/DESCRIPTION.rst @@ -0,0 +1,38 @@ +This module addresses the BoM case where the product to manufacture has one attribute with tens or hundreds of values (usually attribute "color", eg: "Configurable Desk" can be produced in 900 different colors). + +Creating a dynamic BoM currently requires adding one BoM line for each attribute value to match component variant with attribute value (eg: component "Desk board (Green)" to be applied to variant "Green"). + +This has 3 downsides: + +- BoM lines proliferation (more error prone) + +- Difficult to update in case a new attribute value (new color paint) is added + +- Difficult to update in case base component changes. + + +This module allows to use a product template as component in BoM lines, automatically matching component variant to use in MO line with the attribute value selected for manufacture. + +Eg: Product template "Desk Board" is added to BoM line for product "Configurable Desk"; match is made on attribute "Color". In MO, if product to manufacture is "Configurable Desk (Steel, Pink)", MO line will have component "Desk Board (Pink)". + +Using the same BoM, if product to manufacture is "Configurable Desk (Steel, Yellow)", MO line will have component "Desk Board (Yellow)". + + +The flow is valid also if the Component (Product Template) has more than one attribute matching the product to manufacture; in this case, on MO line the component variant will be the one matching multiple attribute values for the product to manufacture. + + +Various checks are in place to make sure this flow is not disrupted: + +- user cannot add a product in field "Component (Product Template)" which: + + does not have matching attributes with product to manufacture + + has a different variant-generating attribute than the product to manufacture + +- Adding a new variant-generating attribute to a product used as "Component (Product Template)" raises an error if the attribute is not included in all the products to manufacture where component is referenced. + +- Removing an attribute used for BoM attribute matching from product to manufacture raises an error. + +- On a BoM line with Component (Product Template) set, an attribute value of attributes referenced in "Match on attribute" field cannot be used in field "Apply to variant". + +- If attribute value for matching attribute in manufactured product is not present in component (product template), the BoM line is skipped in MO. diff --git a/mrp_bom_attribute_match/readme/USAGE.rst b/mrp_bom_attribute_match/readme/USAGE.rst new file mode 100644 index 0000000000..b0bb99b71e --- /dev/null +++ b/mrp_bom_attribute_match/readme/USAGE.rst @@ -0,0 +1,20 @@ +Using this module you can have dynamic components of a BOM. +It will allow you to have only 1 line in the BOM if you have hundreds of attribute +values for manufacturing product and hundreds of attributes values of component (material). + +How to use + + #. Create a product to produce e.g. Desk. + #. Set 1 attribute (e.g. Color). And select possible values for it. + #. Create a component product (material) e.g. Plastic. + #. Set 1 attribute (Color). And select possible values for it. + #. Create a BOM. + #. Select a manufacturing product Desk. + #. Add a BOM line. Select Component (product template) Plastic. + #. You will see Color attribute appeared in the Apply On Attribute field. + #. Save the BOM. + #. Create Manufacturing Order. Select Desk with e.g. Red color to produce and BOM you created. + #. You will see in the component list Plastic added with corresponding (red) color. + +Consider, that to use this feature component must have only 1 attribute. +And a values of this attribute of a manufacturing product should be available for a component. diff --git a/mrp_bom_attribute_match/static/description/icon.png b/mrp_bom_attribute_match/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..3a0328b516c4980e8e44cdb63fd945757ddd132d GIT binary patch literal 9455 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 z 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 Xviia!U7SGha1wx#SCgwmn*{w2TRX*I literal 0 HcmV?d00001 diff --git a/mrp_bom_attribute_match/static/description/index.html b/mrp_bom_attribute_match/static/description/index.html new file mode 100644 index 0000000000..44775fb6c5 --- /dev/null +++ b/mrp_bom_attribute_match/static/description/index.html @@ -0,0 +1,476 @@ + + + + + + +BOM Attribute Match + + + +
+

BOM Attribute Match

+ + +

Beta License: LGPL-3 OCA/manufacture Translate me on Weblate Try me on Runbot

+

This module addresses the BoM case where the product to manufacture has one attribute with tens or hundreds of values (usually attribute “color”, eg: “Configurable Desk” can be produced in 900 different colors).

+

Creating a dynamic BoM currently requires adding one BoM line for each attribute value to match component variant with attribute value (eg: component “Desk board (Green)” to be applied to variant “Green”).

+

This has 3 downsides:

+
    +
  • BoM lines proliferation (more error prone)
  • +
  • Difficult to update in case a new attribute value (new color paint) is added
  • +
  • Difficult to update in case base component changes.
  • +
+

This module allows to use a product template as component in BoM lines, automatically matching component variant to use in MO line with the attribute value selected for manufacture.

+

Eg: Product template “Desk Board” is added to BoM line for product “Configurable Desk”; match is made on attribute “Color”. In MO, if product to manufacture is “Configurable Desk (Steel, Pink)”, MO line will have component “Desk Board (Pink)”.

+

Using the same BoM, if product to manufacture is “Configurable Desk (Steel, Yellow)”, MO line will have component “Desk Board (Yellow)”.

+

The flow is valid also if the Component (Product Template) has more than one attribute matching the product to manufacture; in this case, on MO line the component variant will be the one matching multiple attribute values for the product to manufacture.

+

Various checks are in place to make sure this flow is not disrupted:

+
    +
  • user cannot add a product in field “Component (Product Template)” which:

    +
    +

    does not have matching attributes with product to manufacture

    +

    has a different variant-generating attribute than the product to manufacture

    +
    +
  • +
  • Adding a new variant-generating attribute to a product used as “Component (Product Template)” raises an error if the attribute is not included in all the products to manufacture where component is referenced.

    +
  • +
  • Removing an attribute used for BoM attribute matching from product to manufacture raises an error.

    +
  • +
  • On a BoM line with Component (Product Template) set, an attribute value of attributes referenced in “Match on attribute” field cannot be used in field “Apply to variant”.

    +
  • +
  • If attribute value for matching attribute in manufactured product is not present in component (product template), the BoM line is skipped in MO.

    +
  • +
+

Table of contents

+ +
+

Usage

+

Using this module you can have dynamic components of a BOM. +It will allow you to have only 1 line in the BOM if you have hundreds of attribute +values for manufacturing product and hundreds of attributes values of component (material).

+

How to use

+
+
    +
  1. Create a product to produce e.g. Desk.
  2. +
  3. Set 1 attribute (e.g. Color). And select possible values for it.
  4. +
  5. Create a component product (material) e.g. Plastic.
  6. +
  7. Set 1 attribute (Color). And select possible values for it.
  8. +
  9. Create a BOM.
  10. +
  11. Select a manufacturing product Desk.
  12. +
  13. Add a BOM line. Select Component (product template) Plastic.
  14. +
  15. You will see Color attribute appeared in the Apply On Attribute field.
  16. +
  17. Save the BOM.
  18. +
  19. Create Manufacturing Order. Select Desk with e.g. Red color to produce and BOM you created.
  20. +
  21. You will see in the component list Plastic added with corresponding (red) color.
  22. +
+
+

Consider, that to use this feature component must have only 1 attribute. +And a values of this attribute of a manufacturing product should be available for a component.

+
+
+

Bug Tracker

+

Bugs are tracked on GitHub 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.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • Ilyas
  • +
  • Ooops
  • +
+
+
+

Contributors

+ +
+
+

Maintainers

+

This module is maintained by the OCA.

+Odoo Community Association +

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.

+

This module is part of the OCA/manufacture project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+ + diff --git a/mrp_bom_attribute_match/tests/__init__.py b/mrp_bom_attribute_match/tests/__init__.py new file mode 100644 index 0000000000..9b6b0f0740 --- /dev/null +++ b/mrp_bom_attribute_match/tests/__init__.py @@ -0,0 +1 @@ +from . import test_mrp_bom_attribute_match diff --git a/mrp_bom_attribute_match/tests/common.py b/mrp_bom_attribute_match/tests/common.py new file mode 100644 index 0000000000..5307b8e18b --- /dev/null +++ b/mrp_bom_attribute_match/tests/common.py @@ -0,0 +1,134 @@ +from odoo.tests import Form, common + + +class TestMrpAttachmentMgmtBase(common.SavepointCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + 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", + } + ) + self.product_surf = self.env["product.template"].create( + { + "name": "Surf", + "type": "product", + } + ) + self.product_fin = self.env["product.template"].create( + { + "name": "Surf Fin", + "type": "product", + } + ) + self.product_plastic = self.env["product.template"].create( + { + "name": "Plastic Component", + "type": "product", + } + ) + self.p1 = self.env["product.template"].create( + { + "name": "P1", + "type": "product", + "route_ids": [(6, 0, [route_manufacture])], + } + ) + self.p2 = self.env["product.template"].create( + { + "name": "P2", + "type": "product", + "route_ids": [(6, 0, [route_manufacture])], + } + ) + self.p3 = self.env["product.template"].create( + { + "name": "P3", + "type": "product", + "route_ids": [(6, 0, [route_manufacture])], + } + ) + self.product_9 = self.env["product.product"].create( + { + "name": "Paper", + } + ) + self.product_10 = self.env["product.product"].create( + { + "name": "Stone", + } + ) + self.product_attribute = self.env["product.attribute"].create( + {"name": "Colour", "display_type": "radio", "create_variant": "always"} + ) + self.attribute_value_ids = self.env["product.attribute.value"].create( + [ + {"name": "Cyan", "attribute_id": self.product_attribute.id}, + {"name": "Magenta", "attribute_id": self.product_attribute.id}, + ] + ) + self.plastic_attrs = self.env["product.template.attribute.line"].create( + { + "attribute_id": self.product_attribute.id, + "product_tmpl_id": self.product_plastic.id, + "value_ids": [(6, 0, self.product_attribute.value_ids.ids)], + } + ) + self.sword_attrs = self.env["product.template.attribute.line"].create( + { + "attribute_id": self.product_attribute.id, + "product_tmpl_id": self.product_sword.id, + "value_ids": [(6, 0, self.product_attribute.value_ids.ids)], + } + ) + + 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() diff --git a/mrp_bom_attribute_match/tests/test_mrp_bom_attribute_match.py b/mrp_bom_attribute_match/tests/test_mrp_bom_attribute_match.py new file mode 100644 index 0000000000..62a30adb1a --- /dev/null +++ b/mrp_bom_attribute_match/tests/test_mrp_bom_attribute_match.py @@ -0,0 +1,160 @@ +from odoo.exceptions import UserError, ValidationError +from odoo.tests import Form + +from .common import TestMrpAttachmentMgmtBase + + +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 + with mrp_bom_form.bom_line_ids.new() as line_form: + line_form.product_id = self.product_plastic.product_variant_ids[0] + line_form.component_template_id = self.product_plastic + self.assertEqual(line_form.product_id.id, False) + line_form.component_template_id = self.env["product.template"] + self.assertEqual( + line_form.product_id, self.product_plastic.product_variant_ids[0] + ) + 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.assertRaises(ValidationError): + line_form.bom_product_template_attribute_value_ids.add(sword_cyan) + + def test_bom_2(self): + smell_attribute = self.env["product.attribute"].create( + {"name": "Smell", "display_type": "radio", "create_variant": "always"} + ) + orchid_attribute_value_id = self.env["product.attribute.value"].create( + [ + {"name": "Orchid", "attribute_id": smell_attribute.id}, + ] + ) + plastic_smells_like_orchid = self.env["product.template.attribute.line"].create( + { + "attribute_id": smell_attribute.id, + "product_tmpl_id": self.product_plastic.id, + "value_ids": [(4, orchid_attribute_value_id.id)], + } + ) + with self.assertRaises(UserError): + vals = { + "attribute_id": smell_attribute.id, + "product_tmpl_id": self.product_plastic.id, + "value_ids": [(4, orchid_attribute_value_id.id)], + } + self.product_plastic.write({"attribute_line_ids": [(0, 0, vals)]}) + 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.assertRaises(ValidationError): + line_form.component_template_id = self.product_plastic + plastic_smells_like_orchid.unlink() + + def test_manufacturing_order_1(self): + mo_form = Form(self.env["mrp.production"]) + mo_form.product_id = self.product_sword.product_variant_ids.filtered( + lambda x: x.display_name == "Plastic Sword (Cyan)" + ) + mo_form.bom_id = self.bom_id + mo_form.product_qty = 1 + self.mo_sword = mo_form.save() + self.mo_sword.action_confirm() + # Assert correct component variant was selected automatically + self.assertEqual( + self.mo_sword.move_raw_ids.product_id.display_name, + "Plastic Component (Cyan)", + ) + + def test_manufacturing_order_2(self): + # Delete Cyan value from plastic + self.plastic_attrs.value_ids = [(3, self.plastic_attrs.value_ids[0].id, 0)] + mo_form = Form(self.env["mrp.production"]) + mo_form.product_id = self.product_sword.product_variant_ids.filtered( + lambda x: x.display_name == "Plastic Sword (Cyan)" + ) + mo_form.bom_id = self.bom_id + mo_form.product_qty = 1 + self.mo_sword = mo_form.save() + with self.assertRaises(UserError): + # Add some materials to consume before marking this MO as to do. + self.mo_sword.action_confirm() + + def test_manufacturing_order_3(self): + # Delete attribute from sword + self.product_sword.attribute_line_ids = [(5, 0, 0)] + mo_form = Form(self.env["mrp.production"]) + mo_form.product_id = self.product_sword.product_variant_ids[0] + # Component skipped + mo_form.bom_id = self.bom_id + mo_form.product_qty = 1 + 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): + mo_form = Form(self.env["mrp.production"]) + mo_form.product_id = self.product_surf.product_variant_ids[0] + mo_form.bom_id = self.surf_bom_id + mo_form.product_qty = 1 + self.mo_sword = mo_form.save() + self.mo_sword.action_confirm() + + # def test_manufacturing_order_5(self): + # mo_form = Form(self.env["mrp.production"]) + # mo_form.product_id = self.product_surf.product_variant_ids[0] + # mo_form.bom_id = self.surf_wrong_bom_id + # mo_form.product_qty = 1 + # self.mo_sword = mo_form.save() + # self.mo_sword.action_confirm() + + # def test_manufacturing_order_6(self): + # mo_form = Form(self.env["mrp.production"]) + # mo_form.product_id = self.p1.product_variant_ids[0] + # mo_form.bom_id = self.p1_bom_id + # mo_form.product_qty = 1 + # self.mo_sword = mo_form.save() + # self.mo_sword.action_confirm() + + def test_bom_recursion(self): + test_bom_3 = self.env["mrp.bom"].create( + { + "product_id": self.product_9.id, + "product_tmpl_id": self.product_9.product_tmpl_id.id, + "product_uom_id": self.product_9.uom_id.id, + "product_qty": 1.0, + "consumption": "flexible", + "type": "normal", + } + ) + test_bom_4 = self.env["mrp.bom"].create( + { + "product_id": self.product_10.id, + "product_tmpl_id": self.product_10.product_tmpl_id.id, + "product_uom_id": self.product_10.uom_id.id, + "product_qty": 1.0, + "consumption": "flexible", + "type": "phantom", + } + ) + self.env["mrp.bom.line"].create( + { + "bom_id": test_bom_3.id, + "product_id": self.product_10.id, + "product_qty": 1.0, + } + ) + self.env["mrp.bom.line"].create( + { + "bom_id": test_bom_4.id, + "product_id": self.product_9.id, + "product_qty": 1.0, + } + ) + with self.assertRaises(UserError): + test_bom_3.explode(self.product_9, 1) diff --git a/mrp_bom_attribute_match/views/mrp_bom_views.xml b/mrp_bom_attribute_match/views/mrp_bom_views.xml new file mode 100644 index 0000000000..3b52384cd5 --- /dev/null +++ b/mrp_bom_attribute_match/views/mrp_bom_views.xml @@ -0,0 +1,34 @@ + + + + mrp.bom.view.form.inherit.bom.match + mrp.bom + + + + + + + + + + + + {'readonly': [('component_template_id', '!=', False)]} + + "1" + + + + From 77bc876a1715295d9d32406ce595ed3f6bc58127 Mon Sep 17 00:00:00 2001 From: Bole Date: Mon, 22 Aug 2022 11:23:52 +0000 Subject: [PATCH 02/19] Added translation using Weblate (Croatian) --- mrp_bom_attribute_match/i18n/hr.po | 135 +++++++++++++++++++++++++++++ 1 file changed, 135 insertions(+) create mode 100644 mrp_bom_attribute_match/i18n/hr.po diff --git a/mrp_bom_attribute_match/i18n/hr.po b/mrp_bom_attribute_match/i18n/hr.po new file mode 100644 index 0000000000..d0e5a13285 --- /dev/null +++ b/mrp_bom_attribute_match/i18n/hr.po @@ -0,0 +1,135 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * mrp_bom_attribute_match +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 14.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: hr\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" +"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" + +#. module: mrp_bom_attribute_match +#: model:ir.model,name:mrp_bom_attribute_match.model_mrp_bom +msgid "Bill of Material" +msgstr "" + +#. module: mrp_bom_attribute_match +#: model:ir.model,name:mrp_bom_attribute_match.model_mrp_bom_line +msgid "Bill of Material Line" +msgstr "" + +#. module: mrp_bom_attribute_match +#: model:ir.model.fields,field_description:mrp_bom_attribute_match.field_mrp_bom_line__product_id +msgid "Component" +msgstr "" + +#. module: mrp_bom_attribute_match +#: model:ir.model.fields,field_description:mrp_bom_attribute_match.field_mrp_bom_line__component_template_id +msgid "Component (product template)" +msgstr "" + +#. module: mrp_bom_attribute_match +#: model:ir.model.fields,field_description:mrp_bom_attribute_match.field_mrp_bom__display_name +#: model:ir.model.fields,field_description:mrp_bom_attribute_match.field_mrp_bom_line__display_name +#: model:ir.model.fields,field_description:mrp_bom_attribute_match.field_mrp_production__display_name +#: model:ir.model.fields,field_description:mrp_bom_attribute_match.field_product_template__display_name +msgid "Display Name" +msgstr "" + +#. module: mrp_bom_attribute_match +#: model:ir.model.fields,field_description:mrp_bom_attribute_match.field_mrp_bom__id +#: model:ir.model.fields,field_description:mrp_bom_attribute_match.field_mrp_bom_line__id +#: model:ir.model.fields,field_description:mrp_bom_attribute_match.field_mrp_production__id +#: model:ir.model.fields,field_description:mrp_bom_attribute_match.field_product_template__id +msgid "ID" +msgstr "" + +#. module: mrp_bom_attribute_match +#: model:ir.model.fields,field_description:mrp_bom_attribute_match.field_mrp_bom____last_update +#: model:ir.model.fields,field_description:mrp_bom_attribute_match.field_mrp_bom_line____last_update +#: model:ir.model.fields,field_description:mrp_bom_attribute_match.field_mrp_production____last_update +#: model:ir.model.fields,field_description:mrp_bom_attribute_match.field_product_template____last_update +msgid "Last Modified on" +msgstr "" + +#. module: mrp_bom_attribute_match +#: model:ir.model.fields,field_description:mrp_bom_attribute_match.field_mrp_bom_line__match_on_attribute_ids +msgid "Match on Attributes" +msgstr "" + +#. module: mrp_bom_attribute_match +#: code:addons/mrp_bom_attribute_match/models/mrp_bom.py:0 +#, python-format +msgid "" +"No match on attribute has been detected for Component (Product Template) %s" +msgstr "" + +#. module: mrp_bom_attribute_match +#: model:ir.model.fields,field_description:mrp_bom_attribute_match.field_mrp_bom_line__product_backup_id +msgid "Product Backup" +msgstr "" + +#. module: mrp_bom_attribute_match +#: model:ir.model,name:mrp_bom_attribute_match.model_product_template +msgid "Product Template" +msgstr "" + +#. module: mrp_bom_attribute_match +#: model:ir.model,name:mrp_bom_attribute_match.model_mrp_production +msgid "Production Order" +msgstr "" + +#. module: mrp_bom_attribute_match +#: code:addons/mrp_bom_attribute_match/models/mrp_bom.py:0 +#, python-format +msgid "" +"Recursion error! A product with a Bill of Material should not have itself " +"in its BoM or child BoMs!" +msgstr "" + +#. module: mrp_bom_attribute_match +#: code:addons/mrp_bom_attribute_match/models/mrp_bom.py:0 +#, python-format +msgid "" +"Some attributes of the dynamic component are not included into production " +"product attributes." +msgstr "" + +#. module: mrp_bom_attribute_match +#: model:ir.model.fields,help:mrp_bom_attribute_match.field_mrp_bom_line__product_backup_id +msgid "Technical field to store previous value of product_id" +msgstr "" + +#. module: mrp_bom_attribute_match +#: code:addons/mrp_bom_attribute_match/models/product.py:0 +#, python-format +msgid "" +"The attributes you're trying to remove is used in BoM as a match with Component (Product Template). To remove these attributes, first remove the BOM line with the matching component.\n" +"Attributes: %s\n" +"BoM: %s" +msgstr "" + +#. module: mrp_bom_attribute_match +#: code:addons/mrp_bom_attribute_match/models/product.py:0 +#, python-format +msgid "" +"This product template is used as a component in the BOMs for %s and " +"attribute(s) %s is not present in all such product(s), and this would break " +"the BOM behavior." +msgstr "" + +#. module: mrp_bom_attribute_match +#: code:addons/mrp_bom_attribute_match/models/mrp_bom.py:0 +#, python-format +msgid "" +"You cannot use an attribute value for attribute %s in the field “Apply on " +"Variants” as it’s the same attribute used in field “Match on " +"Attribute”related to the component %s." +msgstr "" From 37a187bb7f96d4dc11edefa12ec9e0430da4fef6 Mon Sep 17 00:00:00 2001 From: Bole Date: Mon, 22 Aug 2022 11:30:19 +0000 Subject: [PATCH 03/19] Translated using Weblate (Croatian) Currently translated at 83.3% (15 of 18 strings) Translation: manufacture-14.0/manufacture-14.0-mrp_bom_attribute_match Translate-URL: https://translation.odoo-community.org/projects/manufacture-14-0/manufacture-14-0-mrp_bom_attribute_match/hr/ --- mrp_bom_attribute_match/i18n/hr.po | 34 ++++++++++++++++++------------ 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/mrp_bom_attribute_match/i18n/hr.po b/mrp_bom_attribute_match/i18n/hr.po index d0e5a13285..a64adba075 100644 --- a/mrp_bom_attribute_match/i18n/hr.po +++ b/mrp_bom_attribute_match/i18n/hr.po @@ -6,7 +6,8 @@ msgid "" msgstr "" "Project-Id-Version: Odoo Server 14.0\n" "Report-Msgid-Bugs-To: \n" -"Last-Translator: Automatically generated\n" +"PO-Revision-Date: 2022-08-22 14:07+0000\n" +"Last-Translator: Bole \n" "Language-Team: none\n" "Language: hr\n" "MIME-Version: 1.0\n" @@ -14,26 +15,27 @@ msgstr "" "Content-Transfer-Encoding: \n" "Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" "%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" +"X-Generator: Weblate 4.3.2\n" #. module: mrp_bom_attribute_match #: model:ir.model,name:mrp_bom_attribute_match.model_mrp_bom msgid "Bill of Material" -msgstr "" +msgstr "Sastavnica" #. module: mrp_bom_attribute_match #: model:ir.model,name:mrp_bom_attribute_match.model_mrp_bom_line msgid "Bill of Material Line" -msgstr "" +msgstr "Stavka sastavnice" #. module: mrp_bom_attribute_match #: model:ir.model.fields,field_description:mrp_bom_attribute_match.field_mrp_bom_line__product_id msgid "Component" -msgstr "" +msgstr "Komponenta" #. module: mrp_bom_attribute_match #: model:ir.model.fields,field_description:mrp_bom_attribute_match.field_mrp_bom_line__component_template_id msgid "Component (product template)" -msgstr "" +msgstr "Komponenta (predložak)" #. module: mrp_bom_attribute_match #: model:ir.model.fields,field_description:mrp_bom_attribute_match.field_mrp_bom__display_name @@ -41,7 +43,7 @@ msgstr "" #: model:ir.model.fields,field_description:mrp_bom_attribute_match.field_mrp_production__display_name #: model:ir.model.fields,field_description:mrp_bom_attribute_match.field_product_template__display_name msgid "Display Name" -msgstr "" +msgstr "Naziv" #. module: mrp_bom_attribute_match #: model:ir.model.fields,field_description:mrp_bom_attribute_match.field_mrp_bom__id @@ -49,7 +51,7 @@ msgstr "" #: model:ir.model.fields,field_description:mrp_bom_attribute_match.field_mrp_production__id #: model:ir.model.fields,field_description:mrp_bom_attribute_match.field_product_template__id msgid "ID" -msgstr "" +msgstr "ID" #. module: mrp_bom_attribute_match #: model:ir.model.fields,field_description:mrp_bom_attribute_match.field_mrp_bom____last_update @@ -57,34 +59,34 @@ msgstr "" #: model:ir.model.fields,field_description:mrp_bom_attribute_match.field_mrp_production____last_update #: model:ir.model.fields,field_description:mrp_bom_attribute_match.field_product_template____last_update msgid "Last Modified on" -msgstr "" +msgstr "Zadnje modificirano" #. module: mrp_bom_attribute_match #: model:ir.model.fields,field_description:mrp_bom_attribute_match.field_mrp_bom_line__match_on_attribute_ids msgid "Match on Attributes" -msgstr "" +msgstr "Odgovarajući atributi" #. module: mrp_bom_attribute_match #: code:addons/mrp_bom_attribute_match/models/mrp_bom.py:0 #, python-format msgid "" "No match on attribute has been detected for Component (Product Template) %s" -msgstr "" +msgstr "Nije pronađen odgovarajući atribut za Komponentu (predložak) %s" #. module: mrp_bom_attribute_match #: model:ir.model.fields,field_description:mrp_bom_attribute_match.field_mrp_bom_line__product_backup_id msgid "Product Backup" -msgstr "" +msgstr "Pričuvni proizvod" #. module: mrp_bom_attribute_match #: model:ir.model,name:mrp_bom_attribute_match.model_product_template msgid "Product Template" -msgstr "" +msgstr "Predložak proizvoda" #. module: mrp_bom_attribute_match #: model:ir.model,name:mrp_bom_attribute_match.model_mrp_production msgid "Production Order" -msgstr "" +msgstr "Nalog za proizvodnju" #. module: mrp_bom_attribute_match #: code:addons/mrp_bom_attribute_match/models/mrp_bom.py:0 @@ -93,6 +95,8 @@ msgid "" "Recursion error! A product with a Bill of Material should not have itself " "in its BoM or child BoMs!" msgstr "" +"Greška rekurzije! Proizvodsa Sastavnicom, nesmije imati sebe u Sastavnici " +"ili podređenim sastavnicama!" #. module: mrp_bom_attribute_match #: code:addons/mrp_bom_attribute_match/models/mrp_bom.py:0 @@ -101,11 +105,13 @@ msgid "" "Some attributes of the dynamic component are not included into production " "product attributes." msgstr "" +"Neki atributi dinamičke komponente nisu uključeni u atributima glavnog " +"proizvoda." #. module: mrp_bom_attribute_match #: model:ir.model.fields,help:mrp_bom_attribute_match.field_mrp_bom_line__product_backup_id msgid "Technical field to store previous value of product_id" -msgstr "" +msgstr "Tehničko polje za pohranu prethodne vrijednosti polja product_id" #. module: mrp_bom_attribute_match #: code:addons/mrp_bom_attribute_match/models/product.py:0 From 03e7221456eec614d41b99c9dfb5715343c8c42c Mon Sep 17 00:00:00 2001 From: Francesco Foresti Date: Mon, 22 Aug 2022 15:36:35 +0000 Subject: [PATCH 04/19] Translated using Weblate (Italian) Currently translated at 100.0% (16 of 16 strings) Translation: manufacture-14.0/manufacture-14.0-mrp_bom_attribute_match Translate-URL: https://translation.odoo-community.org/projects/manufacture-14-0/manufacture-14-0-mrp_bom_attribute_match/it/ --- mrp_bom_attribute_match/i18n/it.po | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/mrp_bom_attribute_match/i18n/it.po b/mrp_bom_attribute_match/i18n/it.po index f840f129b2..ad12dfab95 100644 --- a/mrp_bom_attribute_match/i18n/it.po +++ b/mrp_bom_attribute_match/i18n/it.po @@ -8,8 +8,8 @@ msgstr "" "Project-Id-Version: Odoo Server 12.0+e\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-01-14 17:50+0000\n" -"PO-Revision-Date: 2022-03-14 13:36+0000\n" -"Last-Translator: Francesco @ Ooops \n" +"PO-Revision-Date: 2022-08-22 18:07+0000\n" +"Last-Translator: Francesco Foresti \n" "Language-Team: Italian \n" "Language: it\n" @@ -17,7 +17,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: \n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 4.4-dev\n" +"X-Generator: Weblate 4.3.2\n" #. module: mrp_bom_attribute_match #: model:ir.model,name:mrp_bom_attribute_match.model_mrp_bom @@ -66,7 +66,7 @@ msgstr "Ultima modifica il" #. module: mrp_bom_attribute_match #: model:ir.model.fields,field_description:mrp_bom_attribute_match.field_mrp_bom_line__match_on_attribute_ids msgid "Match on Attributes" -msgstr "" +msgstr "Corrispondenza su attributi" #. module: mrp_bom_attribute_match #: model:ir.model.fields,field_description:mrp_bom_attribute_match.field_mrp_bom_line__product_backup_id @@ -100,6 +100,8 @@ msgid "" "Some attributes of the dynamic component are not included into production " "product attributes." msgstr "" +"Alcuni attributi del componente non sono inclusi tra gli attributi del " +"prodotto da produrre." #. module: mrp_bom_attribute_match #: model:ir.model.fields,help:mrp_bom_attribute_match.field_mrp_bom_line__product_backup_id @@ -114,6 +116,9 @@ msgid "" "attribute(s) %s is not present in all such product(s), and this would break " "the BOM behavior." msgstr "" +"Questo modello prodotto è utilizzato come componente nella DiBa per i " +"prodotti %s e gli attributi %s non sono presenti in tutti questi prodotti; " +"ciò può corrompere il comportamento del BOM." #. module: mrp_bom_attribute_match #: code:addons/mrp_bom_attribute_match/models/mrp_bom.py:0 From 6b0c735e95849f7aae87b9ee0aa4e6034b5ce749 Mon Sep 17 00:00:00 2001 From: Ilyas Date: Tue, 30 Aug 2022 20:36:04 +0500 Subject: [PATCH 05/19] [14.0][FIX] bom_attribute_match: BOM unit of measure should match uom of a component_template_id. --- mrp_bom_attribute_match/README.rst | 6 ++--- mrp_bom_attribute_match/__manifest__.py | 6 ++--- mrp_bom_attribute_match/i18n/hr.po | 9 +++++++- mrp_bom_attribute_match/i18n/it.po | 23 +++++++++++++++++++ .../i18n/mrp_bom_attribute_match.pot | 5 ++++ mrp_bom_attribute_match/models/mrp_bom.py | 19 +++++++++++++++ .../static/description/index.html | 2 +- .../views/mrp_bom_views.xml | 7 ++++++ 8 files changed, 69 insertions(+), 8 deletions(-) diff --git a/mrp_bom_attribute_match/README.rst b/mrp_bom_attribute_match/README.rst index c81644097c..e3865fbeb5 100644 --- a/mrp_bom_attribute_match/README.rst +++ b/mrp_bom_attribute_match/README.rst @@ -10,9 +10,9 @@ BOM Attribute Match .. |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-LGPL--3-blue.png - :target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html - :alt: License: LGPL-3 +.. |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/14.0/mrp_bom_attribute_match :alt: OCA/manufacture diff --git a/mrp_bom_attribute_match/__manifest__.py b/mrp_bom_attribute_match/__manifest__.py index 6fa47b638e..825505c650 100644 --- a/mrp_bom_attribute_match/__manifest__.py +++ b/mrp_bom_attribute_match/__manifest__.py @@ -1,11 +1,11 @@ { "name": "BOM Attribute Match", - "version": "14.0.1.0.1", + "version": "14.0.1.0.2", "category": "Manufacturing", "author": "Ilyas, Ooops, Odoo Community Association (OCA)", "summary": "Dynamic BOM component based on product attribute", - "depends": ["mrp_account"], - "license": "LGPL-3", + "depends": ["mrp_account", "web_domain_field"], + "license": "AGPL-3", "website": "https://github.com/OCA/manufacture", "data": [ "views/mrp_bom_views.xml", diff --git a/mrp_bom_attribute_match/i18n/hr.po b/mrp_bom_attribute_match/i18n/hr.po index a64adba075..6c49fa541f 100644 --- a/mrp_bom_attribute_match/i18n/hr.po +++ b/mrp_bom_attribute_match/i18n/hr.po @@ -83,6 +83,11 @@ msgstr "Pričuvni proizvod" msgid "Product Template" msgstr "Predložak proizvoda" +#. module: mrp_bom_attribute_match +#: model:ir.model.fields,field_description:mrp_bom_attribute_match.field_mrp_bom_line__product_uom_id_domain +msgid "Product Uom Id Domain" +msgstr "" + #. module: mrp_bom_attribute_match #: model:ir.model,name:mrp_bom_attribute_match.model_mrp_production msgid "Production Order" @@ -117,7 +122,9 @@ msgstr "Tehničko polje za pohranu prethodne vrijednosti polja product_id" #: code:addons/mrp_bom_attribute_match/models/product.py:0 #, python-format msgid "" -"The attributes you're trying to remove is used in BoM as a match with Component (Product Template). To remove these attributes, first remove the BOM line with the matching component.\n" +"The attributes you're trying to remove is used in BoM as a match with " +"Component (Product Template). To remove these attributes, first remove the " +"BOM line with the matching component.\n" "Attributes: %s\n" "BoM: %s" msgstr "" diff --git a/mrp_bom_attribute_match/i18n/it.po b/mrp_bom_attribute_match/i18n/it.po index ad12dfab95..bc24993a97 100644 --- a/mrp_bom_attribute_match/i18n/it.po +++ b/mrp_bom_attribute_match/i18n/it.po @@ -68,6 +68,13 @@ msgstr "Ultima modifica il" msgid "Match on Attributes" msgstr "Corrispondenza su attributi" +#. module: mrp_bom_attribute_match +#: code:addons/mrp_bom_attribute_match/models/mrp_bom.py:0 +#, python-format +msgid "" +"No match on attribute has been detected for Component (Product Template) %s" +msgstr "" + #. module: mrp_bom_attribute_match #: model:ir.model.fields,field_description:mrp_bom_attribute_match.field_mrp_bom_line__product_backup_id msgid "Product Backup" @@ -78,6 +85,11 @@ msgstr "Backup componente" msgid "Product Template" msgstr "Modello Prodotto" +#. module: mrp_bom_attribute_match +#: model:ir.model.fields,field_description:mrp_bom_attribute_match.field_mrp_bom_line__product_uom_id_domain +msgid "Product Uom Id Domain" +msgstr "" + #. module: mrp_bom_attribute_match #: model:ir.model,name:mrp_bom_attribute_match.model_mrp_production msgid "Production Order" @@ -108,6 +120,17 @@ msgstr "" msgid "Technical field to store previous value of product_id" msgstr "Campo tecnico per preservare i valori di product_id" +#. module: mrp_bom_attribute_match +#: code:addons/mrp_bom_attribute_match/models/product.py:0 +#, python-format +msgid "" +"The attributes you're trying to remove is used in BoM as a match with " +"Component (Product Template). To remove these attributes, first remove the " +"BOM line with the matching component.\n" +"Attributes: %s\n" +"BoM: %s" +msgstr "" + #. module: mrp_bom_attribute_match #: code:addons/mrp_bom_attribute_match/models/product.py:0 #, python-format diff --git a/mrp_bom_attribute_match/i18n/mrp_bom_attribute_match.pot b/mrp_bom_attribute_match/i18n/mrp_bom_attribute_match.pot index 600a3c9e9a..8278f03e91 100644 --- a/mrp_bom_attribute_match/i18n/mrp_bom_attribute_match.pot +++ b/mrp_bom_attribute_match/i18n/mrp_bom_attribute_match.pot @@ -79,6 +79,11 @@ msgstr "" msgid "Product Template" msgstr "" +#. module: mrp_bom_attribute_match +#: model:ir.model.fields,field_description:mrp_bom_attribute_match.field_mrp_bom_line__product_uom_id_domain +msgid "Product Uom Id Domain" +msgstr "" + #. module: mrp_bom_attribute_match #: model:ir.model,name:mrp_bom_attribute_match.model_mrp_production msgid "Production Order" diff --git a/mrp_bom_attribute_match/models/mrp_bom.py b/mrp_bom_attribute_match/models/mrp_bom.py index 0e6d5e7de2..e3552e166a 100644 --- a/mrp_bom_attribute_match/models/mrp_bom.py +++ b/mrp_bom_attribute_match/models/mrp_bom.py @@ -1,3 +1,4 @@ +import json import logging from odoo import _, api, fields, models @@ -20,6 +21,21 @@ class MrpBomLine(models.Model): match_on_attribute_ids = fields.Many2many( "product.attribute", string="Match on Attributes", readonly=True ) + product_uom_id_domain = fields.Char(compute="_compute_product_uom_id_domain") + + @api.depends("component_template_id", "product_id") + def _compute_product_uom_id_domain(self): + for r in self: + if r.component_template_id: + category_id = r.component_template_id.uom_id.category_id.id + if ( + r.product_uom_id.category_id.id + != r.component_template_id.uom_id.category_id.id + ): + r.product_uom_id = r.component_template_id.uom_id + else: + category_id = r.product_uom_category_id.id + r.product_uom_id_domain = json.dumps([("category_id", "=", category_id)]) @api.onchange("component_template_id") def _onchange_component_template_id(self): @@ -93,6 +109,9 @@ def check_variants_validity(self): ) ) + def write(self, vals): + super(MrpBomLine, self).write(vals) + class MrpBom(models.Model): _inherit = "mrp.bom" diff --git a/mrp_bom_attribute_match/static/description/index.html b/mrp_bom_attribute_match/static/description/index.html index 44775fb6c5..1c08184a08 100644 --- a/mrp_bom_attribute_match/static/description/index.html +++ b/mrp_bom_attribute_match/static/description/index.html @@ -367,7 +367,7 @@

BOM Attribute Match

!! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! --> -

Beta License: LGPL-3 OCA/manufacture Translate me on Weblate Try me on Runbot

+

Beta License: AGPL-3 OCA/manufacture Translate me on Weblate Try me on Runbot

This module addresses the BoM case where the product to manufacture has one attribute with tens or hundreds of values (usually attribute “color”, eg: “Configurable Desk” can be produced in 900 different colors).

Creating a dynamic BoM currently requires adding one BoM line for each attribute value to match component variant with attribute value (eg: component “Desk board (Green)” to be applied to variant “Green”).

This has 3 downsides:

diff --git a/mrp_bom_attribute_match/views/mrp_bom_views.xml b/mrp_bom_attribute_match/views/mrp_bom_views.xml index 3b52384cd5..90126ae7e0 100644 --- a/mrp_bom_attribute_match/views/mrp_bom_views.xml +++ b/mrp_bom_attribute_match/views/mrp_bom_views.xml @@ -19,6 +19,13 @@ position="after" > + + + + product_uom_id_domain Date: Tue, 25 Oct 2022 15:41:00 -0300 Subject: [PATCH 06/19] [MIG] mrp_bom_attribute_match: Migration to 15.0 Notes: * `_get_product2bom` renamed to `_bom_find` * Test case `test_manufacturing_order_2`: * No longer check for "Add some materials to consume before marking this MO as to do.", as it's already supported in core since https://github.com/odoo/odoo/commit/bf5e1debf --- mrp_bom_attribute_match/__manifest__.py | 5 +---- mrp_bom_attribute_match/models/mrp_bom.py | 7 ++----- mrp_bom_attribute_match/models/mrp_production.py | 15 +++++++-------- mrp_bom_attribute_match/models/product.py | 2 +- mrp_bom_attribute_match/readme/CONTRIBUTORS.rst | 4 ++++ mrp_bom_attribute_match/tests/common.py | 4 ++-- .../tests/test_mrp_bom_attribute_match.py | 4 +--- mrp_bom_attribute_match/views/mrp_bom_views.xml | 3 +-- 8 files changed, 19 insertions(+), 25 deletions(-) diff --git a/mrp_bom_attribute_match/__manifest__.py b/mrp_bom_attribute_match/__manifest__.py index 825505c650..ad3620acec 100644 --- a/mrp_bom_attribute_match/__manifest__.py +++ b/mrp_bom_attribute_match/__manifest__.py @@ -1,6 +1,6 @@ { "name": "BOM Attribute Match", - "version": "14.0.1.0.2", + "version": "15.0.1.0.0", "category": "Manufacturing", "author": "Ilyas, Ooops, Odoo Community Association (OCA)", "summary": "Dynamic BOM component based on product attribute", @@ -10,7 +10,4 @@ "data": [ "views/mrp_bom_views.xml", ], - "qweb": [], - "application": False, - "installable": True, } diff --git a/mrp_bom_attribute_match/models/mrp_bom.py b/mrp_bom_attribute_match/models/mrp_bom.py index e3552e166a..4d8137d549 100644 --- a/mrp_bom_attribute_match/models/mrp_bom.py +++ b/mrp_bom_attribute_match/models/mrp_bom.py @@ -109,9 +109,6 @@ def check_variants_validity(self): ) ) - def write(self, vals): - super(MrpBomLine, self).write(vals) - class MrpBom(models.Model): _inherit = "mrp.bom" @@ -149,7 +146,7 @@ def check_cycle(v, visited, recStack, graph): def update_product_boms(): products = self.env["product.product"].browse(product_ids) product_boms.update( - self._get_product2bom( + self._bom_find( products, bom_type="phantom", picking_type=picking_type or self.picking_type_id, @@ -310,7 +307,7 @@ def get_component_template_product(self, bom_line, bom_product_id, line_product_ return line_product_id def write(self, vals): - res = super(MrpBom, self).write(vals) + res = super().write(vals) for line in self.bom_line_ids: line.update_component_attributes() return res diff --git a/mrp_bom_attribute_match/models/mrp_production.py b/mrp_bom_attribute_match/models/mrp_production.py index b276a491ad..71b89d0255 100644 --- a/mrp_bom_attribute_match/models/mrp_production.py +++ b/mrp_bom_attribute_match/models/mrp_production.py @@ -5,15 +5,14 @@ class MrpProduction(models.Model): _inherit = "mrp.production" def action_confirm(self): - res = super(MrpProduction, self).action_confirm() - if self and self.bom_id: - for bom_line in self.bom_id.bom_line_ids: - if bom_line.component_template_id: - # product_id was set in mrp.bom.explode for correct flow. Need to remove it. - bom_line.product_id = False + res = super().action_confirm() + for bom_line in self.bom_id.bom_line_ids: + if bom_line.component_template_id: + # product_id was set in mrp.bom.explode for correct flow. Need to remove it. + bom_line.product_id = False return res def write(self, vals): - for bl in self.bom_id.bom_line_ids.filtered(lambda x: x.component_template_id): + for bl in self.bom_id.bom_line_ids.filtered("component_template_id"): bl.check_component_attributes() - return super(MrpProduction, self).write(vals) + return super().write(vals) diff --git a/mrp_bom_attribute_match/models/product.py b/mrp_bom_attribute_match/models/product.py index b9b0cc4c51..da1012b46b 100644 --- a/mrp_bom_attribute_match/models/product.py +++ b/mrp_bom_attribute_match/models/product.py @@ -6,7 +6,7 @@ class ProductTemplate(models.Model): _inherit = "product.template" def write(self, vals): - res = super(ProductTemplate, self).write(vals) + res = super().write(vals) self.check_product_with_component_change_allowed() self.check_component_change_allowed() return res diff --git a/mrp_bom_attribute_match/readme/CONTRIBUTORS.rst b/mrp_bom_attribute_match/readme/CONTRIBUTORS.rst index a1c144e4c2..1a74944ba7 100644 --- a/mrp_bom_attribute_match/readme/CONTRIBUTORS.rst +++ b/mrp_bom_attribute_match/readme/CONTRIBUTORS.rst @@ -1,3 +1,7 @@ * Ooops404 * Ilyas + +* `Camptocamp `_ + + * Iván Todorovich diff --git a/mrp_bom_attribute_match/tests/common.py b/mrp_bom_attribute_match/tests/common.py index 5307b8e18b..82f747818b 100644 --- a/mrp_bom_attribute_match/tests/common.py +++ b/mrp_bom_attribute_match/tests/common.py @@ -1,7 +1,7 @@ -from odoo.tests import Form, common +from odoo.tests import Form, TransactionCase -class TestMrpAttachmentMgmtBase(common.SavepointCase): +class TestMrpAttachmentMgmtBase(TransactionCase): @classmethod def setUpClass(cls): super().setUpClass() diff --git a/mrp_bom_attribute_match/tests/test_mrp_bom_attribute_match.py b/mrp_bom_attribute_match/tests/test_mrp_bom_attribute_match.py index 62a30adb1a..2a88899d97 100644 --- a/mrp_bom_attribute_match/tests/test_mrp_bom_attribute_match.py +++ b/mrp_bom_attribute_match/tests/test_mrp_bom_attribute_match.py @@ -81,9 +81,7 @@ def test_manufacturing_order_2(self): mo_form.bom_id = self.bom_id mo_form.product_qty = 1 self.mo_sword = mo_form.save() - with self.assertRaises(UserError): - # Add some materials to consume before marking this MO as to do. - self.mo_sword.action_confirm() + self.mo_sword.action_confirm() def test_manufacturing_order_3(self): # Delete attribute from sword diff --git a/mrp_bom_attribute_match/views/mrp_bom_views.xml b/mrp_bom_attribute_match/views/mrp_bom_views.xml index 90126ae7e0..61db60afc3 100644 --- a/mrp_bom_attribute_match/views/mrp_bom_views.xml +++ b/mrp_bom_attribute_match/views/mrp_bom_views.xml @@ -1,7 +1,6 @@ - - mrp.bom.view.form.inherit.bom.match + mrp.bom From aa70c603185188e4038db60705a9569d1a03fa01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A0n=20Todorovich?= Date: Tue, 25 Oct 2022 16:18:54 -0300 Subject: [PATCH 07/19] [FIX] mrp_bom_attribute_match: properly translate strings --- mrp_bom_attribute_match/models/mrp_bom.py | 18 ++++++++--------- mrp_bom_attribute_match/models/product.py | 24 +++++++++++------------ 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/mrp_bom_attribute_match/models/mrp_bom.py b/mrp_bom_attribute_match/models/mrp_bom.py index 4d8137d549..af2be4e2d7 100644 --- a/mrp_bom_attribute_match/models/mrp_bom.py +++ b/mrp_bom_attribute_match/models/mrp_bom.py @@ -69,7 +69,8 @@ def check_component_attributes(self): raise ValidationError( _( "No match on attribute has been detected for Component " - "(Product Template) %s" % self.component_template_id.display_name + "(Product Template) %s", + self.component_template_id.display_name, ) ) if not all(item in prod_attr_ids for item in comp_attr_ids): @@ -96,16 +97,15 @@ def check_variants_validity(self): ) 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 in the field “Apply on Variants” as it’s the" - " same attribute used in field “Match on Attribute”" - "related to the component %s." - % ( - self.env["product.attribute"].browse(same_attrs), - self.component_template_id.name, - ) + "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, ) ) diff --git a/mrp_bom_attribute_match/models/product.py b/mrp_bom_attribute_match/models/product.py index da1012b46b..7d34ae5073 100644 --- a/mrp_bom_attribute_match/models/product.py +++ b/mrp_bom_attribute_match/models/product.py @@ -26,11 +26,13 @@ def check_product_with_component_change_allowed(self): attr_recs = self.env["product.attribute"].browse(diff) raise UserError( _( - "The attributes you're trying to remove is used in BoM " - "as a match with Component (Product Template). To " - "remove these attributes, first remove the BOM line " - "with the matching component.\nAttributes: %s\nBoM: %s" - % (attr_recs.mapped("name"), bom.display_name) + "The attributes you're trying to remove are used in " + "the BoM as a match with Component (Product Template). " + "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")), + bom=bom.display_name, ) ) @@ -48,13 +50,11 @@ def check_component_change_allowed(self): raise UserError( _( "This product template is used as a component in the " - "BOMs for %s and attribute(s) %s is not present in all " - "such product(s), and this would break the BOM " - "behavior." - % ( - bom.display_name, - attr_recs.mapped("name"), - ) + "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, ) ) From 923f818791dfb71928ef5898d5e1bd2c12349101 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A0n=20Todorovich?= Date: Tue, 25 Oct 2022 16:38:00 -0300 Subject: [PATCH 08/19] [IMP] mrp_bom_attribute_match: assert exception messages --- .../tests/test_mrp_bom_attribute_match.py | 28 +++++++++++++++---- 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/mrp_bom_attribute_match/tests/test_mrp_bom_attribute_match.py b/mrp_bom_attribute_match/tests/test_mrp_bom_attribute_match.py index 2a88899d97..e6b5b5083d 100644 --- a/mrp_bom_attribute_match/tests/test_mrp_bom_attribute_match.py +++ b/mrp_bom_attribute_match/tests/test_mrp_bom_attribute_match.py @@ -23,7 +23,12 @@ def test_bom_1(self): 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.assertRaises(ValidationError): + 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 .*", + ): line_form.bom_product_template_attribute_value_ids.add(sword_cyan) def test_bom_2(self): @@ -42,7 +47,12 @@ def test_bom_2(self): "value_ids": [(4, orchid_attribute_value_id.id)], } ) - with self.assertRaises(UserError): + 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\.", + ): vals = { "attribute_id": smell_attribute.id, "product_tmpl_id": self.product_plastic.id, @@ -52,7 +62,11 @@ def test_bom_2(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: - with self.assertRaises(ValidationError): + with self.assertRaisesRegex( + UserError, + r"Some attributes of the dynamic component are not included into " + r"production product attributes\.", + ): line_form.component_template_id = self.product_plastic plastic_smells_like_orchid.unlink() @@ -91,8 +105,10 @@ def test_manufacturing_order_3(self): # Component skipped mo_form.bom_id = self.bom_id mo_form.product_qty = 1 - with self.assertRaises(ValidationError): - # Some attributes of the dynamic component are not included into ... + with self.assertRaisesRegex( + ValidationError, + r"Some attributes of the dynamic component are not included into .+", + ): self.mo_sword = mo_form.save() def test_manufacturing_order_4(self): @@ -154,5 +170,5 @@ def test_bom_recursion(self): "product_qty": 1.0, } ) - with self.assertRaises(UserError): + with self.assertRaisesRegex(UserError, r"Recursion error! .+"): test_bom_3.explode(self.product_9, 1) From c4f3a7701e21b6b1476182ebb7849947c2ae0071 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A0n=20Todorovich?= Date: Tue, 25 Oct 2022 16:41:38 -0300 Subject: [PATCH 09/19] [FIX] mrp_bom_attribute_match: write products recordset The check expects a single record, so it should be processed accordingly. --- mrp_bom_attribute_match/models/product.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/mrp_bom_attribute_match/models/product.py b/mrp_bom_attribute_match/models/product.py index 7d34ae5073..ec4a9062d6 100644 --- a/mrp_bom_attribute_match/models/product.py +++ b/mrp_bom_attribute_match/models/product.py @@ -7,11 +7,13 @@ class ProductTemplate(models.Model): def write(self, vals): res = super().write(vals) - self.check_product_with_component_change_allowed() - self.check_component_change_allowed() + 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): + 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( @@ -37,6 +39,7 @@ def check_product_with_component_change_allowed(self): ) def check_component_change_allowed(self): + self.ensure_one() if len(self.attribute_line_ids) > 0: boms = self.get_component_boms() if boms: @@ -59,6 +62,7 @@ def check_component_change_allowed(self): ) def get_component_boms(self): + self.ensure_one() bom_lines = self.env["mrp.bom.line"].search( [("component_template_id", "=", self._origin.id)] ) From 50f3706aa1d71c3dd45784a6bf54fbccdd34fcf7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A0n=20Todorovich?= Date: Tue, 25 Oct 2022 16:42:53 -0300 Subject: [PATCH 10/19] [IMP] mrp_bom_attribute_match: make private methods, private These methods shouldn't be exposed to xmlrpc. --- mrp_bom_attribute_match/models/mrp_bom.py | 22 ++++++++++--------- .../models/mrp_production.py | 2 +- mrp_bom_attribute_match/models/product.py | 12 +++++----- 3 files changed, 19 insertions(+), 17 deletions(-) diff --git a/mrp_bom_attribute_match/models/mrp_bom.py b/mrp_bom_attribute_match/models/mrp_bom.py index af2be4e2d7..868a26b190 100644 --- a/mrp_bom_attribute_match/models/mrp_bom.py +++ b/mrp_bom_attribute_match/models/mrp_bom.py @@ -39,11 +39,11 @@ def _compute_product_uom_id_domain(self): @api.onchange("component_template_id") def _onchange_component_template_id(self): - self.update_component_attributes() + self._update_component_attributes() - def update_component_attributes(self): + def _update_component_attributes(self): if self.component_template_id: - self.check_component_attributes() + 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") @@ -51,14 +51,14 @@ def update_component_attributes(self): .ids ) self.product_id = False - self.check_variants_validity() + 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 - def check_component_attributes(self): + def _check_component_attributes(self): comp_attr_ids = ( self.component_template_id.valid_product_template_attribute_line_ids.attribute_id.ids ) @@ -84,9 +84,9 @@ def check_component_attributes(self): @api.onchange("bom_product_template_attribute_value_ids") def _onchange_attribute_value_ids(self): if self.bom_product_template_attribute_value_ids: - self.check_variants_validity() + self._check_variants_validity() - def check_variants_validity(self): + def _check_variants_validity(self): if ( not self.bom_product_template_attribute_value_ids or not self.component_template_id @@ -192,7 +192,7 @@ def update_product_boms(): update_product_boms() product_ids.clear() # upd start - component_template_product = self.get_component_template_product( + component_template_product = self._get_component_template_product( current_line, product, current_line.product_id ) if component_template_product: @@ -267,7 +267,9 @@ def update_product_boms(): ) return boms_done, lines_done - def get_component_template_product(self, bom_line, bom_product_id, line_product_id): + def _get_component_template_product( + self, bom_line, bom_product_id, line_product_id + ): if bom_line.component_template_id: comp = bom_line.component_template_id comp_attr_ids = ( @@ -309,5 +311,5 @@ def get_component_template_product(self, bom_line, bom_product_id, line_product_ def write(self, vals): res = super().write(vals) for line in self.bom_line_ids: - line.update_component_attributes() + line._update_component_attributes() return res diff --git a/mrp_bom_attribute_match/models/mrp_production.py b/mrp_bom_attribute_match/models/mrp_production.py index 71b89d0255..ed24679152 100644 --- a/mrp_bom_attribute_match/models/mrp_production.py +++ b/mrp_bom_attribute_match/models/mrp_production.py @@ -14,5 +14,5 @@ def action_confirm(self): def write(self, vals): for bl in self.bom_id.bom_line_ids.filtered("component_template_id"): - bl.check_component_attributes() + bl._check_component_attributes() return super().write(vals) diff --git a/mrp_bom_attribute_match/models/product.py b/mrp_bom_attribute_match/models/product.py index ec4a9062d6..d2e238a4f3 100644 --- a/mrp_bom_attribute_match/models/product.py +++ b/mrp_bom_attribute_match/models/product.py @@ -8,11 +8,11 @@ class ProductTemplate(models.Model): def write(self, vals): res = super().write(vals) for rec in self: - rec.check_product_with_component_change_allowed() - rec.check_component_change_allowed() + 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() if len(self.attribute_line_ids) > 0 and len(self.bom_ids) > 0: for bom in self.bom_ids: @@ -38,10 +38,10 @@ def check_product_with_component_change_allowed(self): ) ) - def check_component_change_allowed(self): + def _check_component_change_allowed(self): self.ensure_one() if len(self.attribute_line_ids) > 0: - boms = self.get_component_boms() + boms = self._get_component_boms() if boms: for bom in boms: vpa = bom.product_tmpl_id.valid_product_template_attribute_line_ids @@ -61,7 +61,7 @@ def check_component_change_allowed(self): ) ) - def get_component_boms(self): + def _get_component_boms(self): self.ensure_one() bom_lines = self.env["mrp.bom.line"].search( [("component_template_id", "=", self._origin.id)] From f5f5b6ca48df42ea188a58d19fd978ce4a80a8b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A0n=20Todorovich?= Date: Thu, 27 Oct 2022 16:42:33 -0300 Subject: [PATCH 11/19] [IMP] mrp_bom_attribute_match: Drop the web_field_domain dependency. The same result can be achieved with a computed field. Moreover, we have one already in odoo core. Originally it was a related field, but we can change it to a computed and add our extra logic there. --- mrp_bom_attribute_match/__manifest__.py | 2 +- mrp_bom_attribute_match/models/mrp_bom.py | 55 ++++++++++++++----- .../views/mrp_bom_views.xml | 7 --- 3 files changed, 41 insertions(+), 23 deletions(-) diff --git a/mrp_bom_attribute_match/__manifest__.py b/mrp_bom_attribute_match/__manifest__.py index ad3620acec..f3e5dc6059 100644 --- a/mrp_bom_attribute_match/__manifest__.py +++ b/mrp_bom_attribute_match/__manifest__.py @@ -4,7 +4,7 @@ "category": "Manufacturing", "author": "Ilyas, Ooops, Odoo Community Association (OCA)", "summary": "Dynamic BOM component based on product attribute", - "depends": ["mrp_account", "web_domain_field"], + "depends": ["mrp_account"], "license": "AGPL-3", "website": "https://github.com/OCA/manufacture", "data": [ diff --git a/mrp_bom_attribute_match/models/mrp_bom.py b/mrp_bom_attribute_match/models/mrp_bom.py index 868a26b190..4469178c59 100644 --- a/mrp_bom_attribute_match/models/mrp_bom.py +++ b/mrp_bom_attribute_match/models/mrp_bom.py @@ -1,4 +1,3 @@ -import json import logging from odoo import _, api, fields, models @@ -21,24 +20,50 @@ class MrpBomLine(models.Model): match_on_attribute_ids = fields.Many2many( "product.attribute", string="Match on Attributes", readonly=True ) - product_uom_id_domain = fields.Char(compute="_compute_product_uom_id_domain") + product_uom_category_id = fields.Many2one( + "uom.category", + related=None, + compute="_compute_product_uom_category_id", + ) - @api.depends("component_template_id", "product_id") - def _compute_product_uom_id_domain(self): - for r in self: - if r.component_template_id: - category_id = r.component_template_id.uom_id.category_id.id - if ( - r.product_uom_id.category_id.id - != r.component_template_id.uom_id.category_id.id - ): - r.product_uom_id = r.component_template_id.uom_id - else: - category_id = r.product_uom_category_id.id - r.product_uom_id_domain = json.dumps([("category_id", "=", category_id)]) + @api.depends("product_id", "component_template_id") + def _compute_product_uom_category_id(self): + """Compute the product_uom_category_id field. + + This is the product category that will be allowed to use on the product_uom_id + field, already covered by core module: + https://github.com/odoo/odoo/blob/331b9435c/addons/mrp/models/mrp_bom.py#L372 + + In core, though, this field is related to "product_id.uom_id.category_id". + Here we make it computed to choose between component_template_id and + product_id, depending on which one is set + """ + # pylint: disable=missing-return + # NOTE: To play nice with other modules trying to do the same: + # 1) Set the field value as if it were a related field (core behaviour) + # 2) Call super (if it's there) + # 3) Update only the records we want + for rec in self: + rec.product_uom_category_id = rec.product_id.uom_id.category_id + if hasattr(super(), "_compute_product_uom_category_id"): + super()._compute_product_uom_category_id() + for rec in self: + if rec.component_template_id: + rec.product_uom_category_id = ( + rec.component_template_id.uom_id.category_id + ) @api.onchange("component_template_id") def _onchange_component_template_id(self): + if self.component_template_id: + 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_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): diff --git a/mrp_bom_attribute_match/views/mrp_bom_views.xml b/mrp_bom_attribute_match/views/mrp_bom_views.xml index 61db60afc3..ca482aae7f 100644 --- a/mrp_bom_attribute_match/views/mrp_bom_views.xml +++ b/mrp_bom_attribute_match/views/mrp_bom_views.xml @@ -18,13 +18,6 @@ position="after" > - - - - product_uom_id_domain Date: Thu, 27 Oct 2022 17:19:24 -0300 Subject: [PATCH 12/19] [IMP] mrp_bom_attribute_match: use constraints and computed fields Instead of updates triggered by onchanges or writes, use constraints to execute all checks, and a computed field for `match_on_attribute_ids` --- mrp_bom_attribute_match/models/mrp_bom.py | 146 ++++++++++-------- .../models/mrp_production.py | 9 +- mrp_bom_attribute_match/models/product.py | 72 ++++----- .../views/mrp_bom_views.xml | 7 +- 4 files changed, 119 insertions(+), 115 deletions(-) diff --git a/mrp_bom_attribute_match/models/mrp_bom.py b/mrp_bom_attribute_match/models/mrp_bom.py index 4469178c59..ccb7f41604 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 @@ def _compute_product_uom_category_id(self): @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 @@ def _get_component_template_product( 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 ed24679152..076604e1b7 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 @@ def action_confirm(self): 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 d2e238a4f3..7c9088c09f 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 @@ def _check_product_with_component_change_allowed(self): "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 ca482aae7f..3ba95fb697 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 @@ - + Date: Mon, 31 Oct 2022 08:12:28 -0300 Subject: [PATCH 13/19] [IMP] mrp_bom_attribute_match: drop the 'account' dependency There isn't any reson to depend on it. --- mrp_bom_attribute_match/__manifest__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mrp_bom_attribute_match/__manifest__.py b/mrp_bom_attribute_match/__manifest__.py index f3e5dc6059..a093c6a2de 100644 --- a/mrp_bom_attribute_match/__manifest__.py +++ b/mrp_bom_attribute_match/__manifest__.py @@ -4,7 +4,7 @@ "category": "Manufacturing", "author": "Ilyas, Ooops, Odoo Community Association (OCA)", "summary": "Dynamic BOM component based on product attribute", - "depends": ["mrp_account"], + "depends": ["mrp"], "license": "AGPL-3", "website": "https://github.com/OCA/manufacture", "data": [ From b65569efad24e853f2d8e695d29d8c60d57289b2 Mon Sep 17 00:00:00 2001 From: oca-ci Date: Fri, 23 Dec 2022 18:45:32 +0000 Subject: [PATCH 14/19] [UPD] Update mrp_bom_attribute_match.pot --- .../i18n/mrp_bom_attribute_match.pot | 48 +++++-------------- 1 file changed, 12 insertions(+), 36 deletions(-) diff --git a/mrp_bom_attribute_match/i18n/mrp_bom_attribute_match.pot b/mrp_bom_attribute_match/i18n/mrp_bom_attribute_match.pot index 8278f03e91..6bb00e48f3 100644 --- a/mrp_bom_attribute_match/i18n/mrp_bom_attribute_match.pot +++ b/mrp_bom_attribute_match/i18n/mrp_bom_attribute_match.pot @@ -4,7 +4,7 @@ # msgid "" msgstr "" -"Project-Id-Version: Odoo Server 14.0\n" +"Project-Id-Version: Odoo Server 15.0\n" "Report-Msgid-Bugs-To: \n" "Last-Translator: \n" "Language-Team: \n" @@ -33,30 +33,6 @@ msgstr "" msgid "Component (product template)" msgstr "" -#. module: mrp_bom_attribute_match -#: model:ir.model.fields,field_description:mrp_bom_attribute_match.field_mrp_bom__display_name -#: model:ir.model.fields,field_description:mrp_bom_attribute_match.field_mrp_bom_line__display_name -#: model:ir.model.fields,field_description:mrp_bom_attribute_match.field_mrp_production__display_name -#: model:ir.model.fields,field_description:mrp_bom_attribute_match.field_product_template__display_name -msgid "Display Name" -msgstr "" - -#. module: mrp_bom_attribute_match -#: model:ir.model.fields,field_description:mrp_bom_attribute_match.field_mrp_bom__id -#: model:ir.model.fields,field_description:mrp_bom_attribute_match.field_mrp_bom_line__id -#: model:ir.model.fields,field_description:mrp_bom_attribute_match.field_mrp_production__id -#: model:ir.model.fields,field_description:mrp_bom_attribute_match.field_product_template__id -msgid "ID" -msgstr "" - -#. module: mrp_bom_attribute_match -#: model:ir.model.fields,field_description:mrp_bom_attribute_match.field_mrp_bom____last_update -#: model:ir.model.fields,field_description:mrp_bom_attribute_match.field_mrp_bom_line____last_update -#: model:ir.model.fields,field_description:mrp_bom_attribute_match.field_mrp_production____last_update -#: model:ir.model.fields,field_description:mrp_bom_attribute_match.field_product_template____last_update -msgid "Last Modified on" -msgstr "" - #. module: mrp_bom_attribute_match #: model:ir.model.fields,field_description:mrp_bom_attribute_match.field_mrp_bom_line__match_on_attribute_ids msgid "Match on Attributes" @@ -80,8 +56,8 @@ msgid "Product Template" msgstr "" #. module: mrp_bom_attribute_match -#: model:ir.model.fields,field_description:mrp_bom_attribute_match.field_mrp_bom_line__product_uom_id_domain -msgid "Product Uom Id Domain" +#: model:ir.model.fields,field_description:mrp_bom_attribute_match.field_mrp_bom_line__product_uom_category_id +msgid "Product Uom Category" msgstr "" #. module: mrp_bom_attribute_match @@ -114,25 +90,25 @@ msgstr "" #: code:addons/mrp_bom_attribute_match/models/product.py:0 #, python-format msgid "" -"The attributes you're trying to remove is used in BoM as a match with Component (Product Template). To remove these attributes, first remove the BOM line with the matching component.\n" -"Attributes: %s\n" -"BoM: %s" +"The attributes you're trying to remove are used in the BoM as a match with Component (Product Template). To remove these attributes, first remove the BOM line with the matching component.\n" +"Attributes: %(attributes)s\n" +"BoM: %(bom)s" msgstr "" #. module: mrp_bom_attribute_match #: code:addons/mrp_bom_attribute_match/models/product.py:0 #, python-format msgid "" -"This product template is used as a component in the BOMs for %s and " -"attribute(s) %s is not present in all such product(s), and this would break " -"the BOM behavior." +"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." msgstr "" #. module: mrp_bom_attribute_match #: code:addons/mrp_bom_attribute_match/models/mrp_bom.py:0 #, python-format msgid "" -"You cannot use an attribute value for attribute %s in the field “Apply on " -"Variants” as it’s the same attribute used in field “Match on " -"Attribute”related to the component %s." +"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." msgstr "" From ccfc7e3dc8df66e5ea919f3bd261df82ea2dfbf5 Mon Sep 17 00:00:00 2001 From: OCA-git-bot Date: Fri, 23 Dec 2022 18:49:11 +0000 Subject: [PATCH 15/19] [UPD] README.rst --- mrp_bom_attribute_match/README.rst | 14 +++++++++----- .../static/description/index.html | 10 +++++++--- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/mrp_bom_attribute_match/README.rst b/mrp_bom_attribute_match/README.rst index e3865fbeb5..e009708746 100644 --- a/mrp_bom_attribute_match/README.rst +++ b/mrp_bom_attribute_match/README.rst @@ -14,13 +14,13 @@ BOM Attribute Match :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/14.0/mrp_bom_attribute_match + :target: https://github.com/OCA/manufacture/tree/15.0/mrp_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-14-0/manufacture-14-0-mrp_bom_attribute_match + :target: https://translation.odoo-community.org/projects/manufacture-15-0/manufacture-15-0-mrp_bom_attribute_match :alt: Translate me on Weblate .. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png - :target: https://runbot.odoo-community.org/runbot/129/14.0 + :target: https://runbot.odoo-community.org/runbot/129/15.0 :alt: Try me on Runbot |badge1| |badge2| |badge3| |badge4| |badge5| @@ -99,7 +99,7 @@ Bug Tracker Bugs are tracked on `GitHub 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 `_. +`feedback `_. Do not contact contributors directly about support or help with technical issues. @@ -119,6 +119,10 @@ Contributors * Ilyas +* `Camptocamp `_ + + * Iván Todorovich + Maintainers ~~~~~~~~~~~ @@ -132,6 +136,6 @@ 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. -This module is part of the `OCA/manufacture `_ project on GitHub. +This module is part of the `OCA/manufacture `_ project on GitHub. You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/mrp_bom_attribute_match/static/description/index.html b/mrp_bom_attribute_match/static/description/index.html index 1c08184a08..bee8234efd 100644 --- a/mrp_bom_attribute_match/static/description/index.html +++ b/mrp_bom_attribute_match/static/description/index.html @@ -367,7 +367,7 @@

BOM Attribute Match

!! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! --> -

Beta License: AGPL-3 OCA/manufacture Translate me on Weblate Try me on Runbot

+

Beta License: AGPL-3 OCA/manufacture Translate me on Weblate Try me on Runbot

This module addresses the BoM case where the product to manufacture has one attribute with tens or hundreds of values (usually attribute “color”, eg: “Configurable Desk” can be produced in 900 different colors).

Creating a dynamic BoM currently requires adding one BoM line for each attribute value to match component variant with attribute value (eg: component “Desk board (Green)” to be applied to variant “Green”).

This has 3 downsides:

@@ -439,7 +439,7 @@

Bug Tracker

Bugs are tracked on GitHub 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.

+feedback.

Do not contact contributors directly about support or help with technical issues.

@@ -458,6 +458,10 @@

Contributors

  • Ilyas
  • +
  • Camptocamp +
  • @@ -467,7 +471,7 @@

    Maintainers

    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.

    -

    This module is part of the OCA/manufacture project on GitHub.

    +

    This module is part of the OCA/manufacture project on GitHub.

    You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

    From d13f9925c3df6e6d6fb5f9166c29f4cf34a9e4d5 Mon Sep 17 00:00:00 2001 From: mymage Date: Mon, 26 Dec 2022 14:21:06 +0000 Subject: [PATCH 16/19] Translated using Weblate (Italian) Currently translated at 100.0% (19 of 19 strings) Translation: manufacture-15.0/manufacture-15.0-mrp_bom_attribute_match Translate-URL: https://translation.odoo-community.org/projects/manufacture-15-0/manufacture-15-0-mrp_bom_attribute_match/it/ --- mrp_bom_attribute_match/i18n/it.po | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/mrp_bom_attribute_match/i18n/it.po b/mrp_bom_attribute_match/i18n/it.po index bc24993a97..f649ed49e3 100644 --- a/mrp_bom_attribute_match/i18n/it.po +++ b/mrp_bom_attribute_match/i18n/it.po @@ -8,8 +8,8 @@ msgstr "" "Project-Id-Version: Odoo Server 12.0+e\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-01-14 17:50+0000\n" -"PO-Revision-Date: 2022-08-22 18:07+0000\n" -"Last-Translator: Francesco Foresti \n" +"PO-Revision-Date: 2022-12-26 16:46+0000\n" +"Last-Translator: mymage \n" "Language-Team: Italian \n" "Language: it\n" @@ -17,7 +17,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: \n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 4.3.2\n" +"X-Generator: Weblate 4.14.1\n" #. module: mrp_bom_attribute_match #: model:ir.model,name:mrp_bom_attribute_match.model_mrp_bom @@ -74,6 +74,8 @@ msgstr "Corrispondenza su attributi" msgid "" "No match on attribute has been detected for Component (Product Template) %s" msgstr "" +"Nessuna corrispondenza trovata nell'attributo per il componente (modello " +"prodotto) %s" #. module: mrp_bom_attribute_match #: model:ir.model.fields,field_description:mrp_bom_attribute_match.field_mrp_bom_line__product_backup_id @@ -88,7 +90,7 @@ msgstr "Modello Prodotto" #. module: mrp_bom_attribute_match #: model:ir.model.fields,field_description:mrp_bom_attribute_match.field_mrp_bom_line__product_uom_id_domain msgid "Product Uom Id Domain" -msgstr "" +msgstr "Dominio ID UdM prodotto" #. module: mrp_bom_attribute_match #: model:ir.model,name:mrp_bom_attribute_match.model_mrp_production @@ -130,6 +132,11 @@ msgid "" "Attributes: %s\n" "BoM: %s" msgstr "" +"L'attributo che si sta cercando di eliminare è utilizzato nelle DB come " +"corrispondenza con un componente (modello prodotto). Per rimuovere questo " +"attributo, prima rimuovere la riga di DB con il componente corrispondente.\n" +"Attributo: %s\n" +"DB: %s" #. module: mrp_bom_attribute_match #: code:addons/mrp_bom_attribute_match/models/product.py:0 From 08aa533582ff4ec8c60b26b8838a1e4d1280563d Mon Sep 17 00:00:00 2001 From: mymage Date: Sun, 1 Jan 2023 12:07:45 +0000 Subject: [PATCH 17/19] Translated using Weblate (Italian) Currently translated at 100.0% (19 of 19 strings) Translation: manufacture-15.0/manufacture-15.0-mrp_bom_attribute_match Translate-URL: https://translation.odoo-community.org/projects/manufacture-15-0/manufacture-15-0-mrp_bom_attribute_match/it/ --- mrp_bom_attribute_match/i18n/it.po | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/mrp_bom_attribute_match/i18n/it.po b/mrp_bom_attribute_match/i18n/it.po index f649ed49e3..a8cea03c10 100644 --- a/mrp_bom_attribute_match/i18n/it.po +++ b/mrp_bom_attribute_match/i18n/it.po @@ -8,7 +8,7 @@ msgstr "" "Project-Id-Version: Odoo Server 12.0+e\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-01-14 17:50+0000\n" -"PO-Revision-Date: 2022-12-26 16:46+0000\n" +"PO-Revision-Date: 2023-01-01 13:45+0000\n" "Last-Translator: mymage \n" "Language-Team: Italian \n" @@ -132,11 +132,12 @@ msgid "" "Attributes: %s\n" "BoM: %s" msgstr "" -"L'attributo che si sta cercando di eliminare è utilizzato nelle DB come " +"L'attributo che si sta cercando di eliminare è utilizzato nelle DiBa come " "corrispondenza con un componente (modello prodotto). Per rimuovere questo " -"attributo, prima rimuovere la riga di DB con il componente corrispondente.\n" +"attributo, prima rimuovere la riga di DiBa con il componente corrispondente." +"\n" "Attributo: %s\n" -"DB: %s" +"DiBa: %s" #. module: mrp_bom_attribute_match #: code:addons/mrp_bom_attribute_match/models/product.py:0 From 3d199d93abb0b97735e2e98d679a952971f54351 Mon Sep 17 00:00:00 2001 From: Stefano Consolaro Date: Tue, 3 Jan 2023 19:31:47 +0100 Subject: [PATCH 18/19] [IMP] mrp_bom_attribute_match: pre-commit stuff --- .../odoo/addons/mrp_bom_attribute_match | 1 + setup/mrp_bom_attribute_match/setup.py | 6 ++++++ 2 files changed, 7 insertions(+) create mode 120000 setup/mrp_bom_attribute_match/odoo/addons/mrp_bom_attribute_match create mode 100644 setup/mrp_bom_attribute_match/setup.py diff --git a/setup/mrp_bom_attribute_match/odoo/addons/mrp_bom_attribute_match b/setup/mrp_bom_attribute_match/odoo/addons/mrp_bom_attribute_match new file mode 120000 index 0000000000..c9961e5316 --- /dev/null +++ b/setup/mrp_bom_attribute_match/odoo/addons/mrp_bom_attribute_match @@ -0,0 +1 @@ +../../../../mrp_bom_attribute_match \ No newline at end of file diff --git a/setup/mrp_bom_attribute_match/setup.py b/setup/mrp_bom_attribute_match/setup.py new file mode 100644 index 0000000000..28c57bb640 --- /dev/null +++ b/setup/mrp_bom_attribute_match/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +) From 5ee00e3c788f59e81297ff0281fd75f8a1d5260f Mon Sep 17 00:00:00 2001 From: Stefano Consolaro Date: Tue, 3 Jan 2023 19:33:51 +0100 Subject: [PATCH 19/19] [MIG] mrp_bom_attribute_match: Migration to 16.0 --- mrp_bom_attribute_match/README.rst | 23 +++++----- mrp_bom_attribute_match/__manifest__.py | 2 +- .../static/description/index.html | 42 ++++++++++--------- mrp_bom_attribute_match/tests/common.py | 23 +++++++++- .../tests/test_mrp_bom_attribute_match.py | 11 +++-- .../views/mrp_bom_views.xml | 6 +++ 6 files changed, 72 insertions(+), 35 deletions(-) diff --git a/mrp_bom_attribute_match/README.rst b/mrp_bom_attribute_match/README.rst index e009708746..35e16b86e3 100644 --- a/mrp_bom_attribute_match/README.rst +++ b/mrp_bom_attribute_match/README.rst @@ -2,10 +2,13 @@ BOM Attribute Match =================== -.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:f598ece9b32319e94b8b593ab4fedb6065d0965d3725a767a2ba630893da7d4d + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! .. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png :target: https://odoo-community.org/page/development-status @@ -14,16 +17,16 @@ BOM Attribute Match :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_bom_attribute_match + :target: https://github.com/OCA/manufacture/tree/16.0/mrp_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_bom_attribute_match + :target: https://translation.odoo-community.org/projects/manufacture-16-0/manufacture-16-0-mrp_bom_attribute_match :alt: Translate me on Weblate -.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png - :target: https://runbot.odoo-community.org/runbot/129/15.0 - :alt: Try me on Runbot +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/builds?repo=OCA/manufacture&target_branch=16.0 + :alt: Try me on Runboat -|badge1| |badge2| |badge3| |badge4| |badge5| +|badge1| |badge2| |badge3| |badge4| |badge5| This module addresses the BoM case where the product to manufacture has one attribute with tens or hundreds of values (usually attribute "color", eg: "Configurable Desk" can be produced in 900 different colors). @@ -98,8 +101,8 @@ Bug Tracker Bugs are tracked on `GitHub 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 `_. +If you spotted it first, help us to smash it by providing a detailed and welcomed +`feedback `_. Do not contact contributors directly about support or help with technical issues. @@ -136,6 +139,6 @@ 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. -This module is part of the `OCA/manufacture `_ project on GitHub. +This module is part of the `OCA/manufacture `_ project on GitHub. You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/mrp_bom_attribute_match/__manifest__.py b/mrp_bom_attribute_match/__manifest__.py index a093c6a2de..b2c75b66e9 100644 --- a/mrp_bom_attribute_match/__manifest__.py +++ b/mrp_bom_attribute_match/__manifest__.py @@ -1,6 +1,6 @@ { "name": "BOM Attribute Match", - "version": "15.0.1.0.0", + "version": "16.0.1.0.0", "category": "Manufacturing", "author": "Ilyas, Ooops, Odoo Community Association (OCA)", "summary": "Dynamic BOM component based on product attribute", diff --git a/mrp_bom_attribute_match/static/description/index.html b/mrp_bom_attribute_match/static/description/index.html index bee8234efd..acbe97a2d6 100644 --- a/mrp_bom_attribute_match/static/description/index.html +++ b/mrp_bom_attribute_match/static/description/index.html @@ -1,20 +1,20 @@ - + - + BOM Attribute Match