diff --git a/product_variant_route_mto/README.rst b/product_variant_route_mto/README.rst new file mode 100644 index 00000000000..b18e4a339b1 --- /dev/null +++ b/product_variant_route_mto/README.rst @@ -0,0 +1,105 @@ +========================= +Product Variant Route MTO +========================= + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:05d176ec1f7bf87893bca915bd87346e248c55c1f634024018d88317b9b69b7b + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Alpha-red.png + :target: https://odoo-community.org/page/development-status + :alt: Alpha +.. |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%2Fproduct--attribute-lightgray.png?logo=github + :target: https://github.com/OCA/product-attribute/tree/17.0/product_variant_route_mto + :alt: OCA/product-attribute +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/product-attribute-17-0/product-attribute-17-0-product_variant_route_mto + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/builds?repo=OCA/product-attribute&target_branch=17.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module allows to set a product variant as MTO (enabling the Make To +Order route on that variant only) while the related product is not MTO. +However, you cannot mark a variant has non MTO when the template is MTO. + +.. IMPORTANT:: + This is an alpha version, the data model and design can change at any time without warning. + Only for development or testing purpose, do not use in production. + `More details on development status `_ + +**Table of contents** + +.. contents:: + :local: + +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 to smash it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +------- + +* Camptocamp SA + +Contributors +------------ + +- Matthieu Méquignon +- Akim Juillerat +- Chau Le +- Jacques-Etienne Baudoux (BCIM) + +Other credits +------------- + +The development and migration of this module has been financially +supported by: + +- Camptocamp + +Maintainers +----------- + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +.. |maintainer-mmequignon| image:: https://github.com/mmequignon.png?size=40px + :target: https://github.com/mmequignon + :alt: mmequignon +.. |maintainer-jbaudoux| image:: https://github.com/jbaudoux.png?size=40px + :target: https://github.com/jbaudoux + :alt: jbaudoux + +Current `maintainers `__: + +|maintainer-mmequignon| |maintainer-jbaudoux| + +This module is part of the `OCA/product-attribute `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/product_variant_route_mto/__init__.py b/product_variant_route_mto/__init__.py new file mode 100644 index 00000000000..0650744f6bc --- /dev/null +++ b/product_variant_route_mto/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/product_variant_route_mto/__manifest__.py b/product_variant_route_mto/__manifest__.py new file mode 100644 index 00000000000..bf98412ae72 --- /dev/null +++ b/product_variant_route_mto/__manifest__.py @@ -0,0 +1,19 @@ +# Copyright 2023 Camptocamp SA +# Copyright 2025 Jacques-Etienne Baudoux (BCIM) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) + +{ + "name": "Product Variant Route MTO", + "summary": "Allow to individually set variants as MTO", + "version": "17.0.1.0.0", + "development_status": "Alpha", + "category": "Inventory", + "website": "https://github.com/OCA/product-attribute", + "author": "Camptocamp SA, Odoo Community Association (OCA)", + "maintainers": ["mmequignon", "jbaudoux"], + "license": "AGPL-3", + "installable": True, + "auto_install": False, + "depends": ["product_route_mto"], + "data": ["views/product_product.xml"], +} diff --git a/product_variant_route_mto/i18n/it.po b/product_variant_route_mto/i18n/it.po new file mode 100644 index 00000000000..4eb1c55af1a --- /dev/null +++ b/product_variant_route_mto/i18n/it.po @@ -0,0 +1,86 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * product_variant_route_mto +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 18.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2025-06-25 10:25+0000\n" +"Last-Translator: mymage \n" +"Language-Team: none\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 5.10.4\n" + +#. module: product_variant_route_mto +#: model:ir.model.fields,help:product_variant_route_mto.field_product_product__is_mto +msgid "" +"\n" +" Check or Uncheck this field to enable the Make To Order on the variant,\n" +" independantly from its template configuration.\n" +"\n" +" Please note that activating or deactivating Make To Order on the template,\n" +" will reset this setting on its variants.\n" +msgstr "" +"\n" +" Selezionare o deselezionare questo campo per abilitare la funzione " +"\"Produci su ordine\" sulla variante, \n" +" indipendentemente dalla configurazione del modello.\n" +"\n" +" Si fa di notare che l'attivazione o la disattivazione della funzione " +"\"Produci su ordine\" sul modello \n" +" reimposterà questa impostazione sulle relative varianti.\n" + +#. module: product_variant_route_mto +#. odoo-python +#: code:addons/product_variant_route_mto/models/product_template.py:0 +msgid "" +"Activating MTO route will reset `Variant is MTO` setting on the variants." +msgstr "" +"L'attivazione della rotta MTO reimposterà l'impostazione `Variante è MTO` " +"nelle varianti." + +#. module: product_variant_route_mto +#. odoo-python +#: code:addons/product_variant_route_mto/models/product_template.py:0 +msgid "" +"Deactivating MTO route will reset `Variant is MTO` setting on the variants." +msgstr "" +"Disattivando la rotta MTO reimposterà l'impostazione `Variante è MTO` nelle " +"varianti." + +#. module: product_variant_route_mto +#: model:ir.model,name:product_variant_route_mto.model_product_template +msgid "Product" +msgstr "Prodotto" + +#. module: product_variant_route_mto +#: model:ir.model,name:product_variant_route_mto.model_product_product +msgid "Product Variant" +msgstr "Variante prodotto" + +#. module: product_variant_route_mto +#: model:ir.model.fields,field_description:product_variant_route_mto.field_product_product__route_ids +msgid "Route" +msgstr "Percorso" + +#. module: product_variant_route_mto +#: model:ir.model.fields,field_description:product_variant_route_mto.field_product_product__is_mto +msgid "Variant is MTO" +msgstr "Variante è MTO" + +#. module: product_variant_route_mto +#. odoo-python +#: code:addons/product_variant_route_mto/models/product_template.py:0 +msgid "Warning" +msgstr "Attenzione" + +#. module: product_variant_route_mto +#. odoo-python +#: code:addons/product_variant_route_mto/models/product_product.py:0 +msgid "You cannot mark a variant as non MTO when the product is MTO" +msgstr "Una variante non può non essere MTO se il prodotto è MTO" diff --git a/product_variant_route_mto/i18n/product_variant_route_mto.pot b/product_variant_route_mto/i18n/product_variant_route_mto.pot new file mode 100644 index 00000000000..2654929519b --- /dev/null +++ b/product_variant_route_mto/i18n/product_variant_route_mto.pot @@ -0,0 +1,71 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * product_variant_route_mto +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 18.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: product_variant_route_mto +#: model:ir.model.fields,help:product_variant_route_mto.field_product_product__is_mto +msgid "" +"\n" +" Check or Uncheck this field to enable the Make To Order on the variant,\n" +" independantly from its template configuration.\n" +"\n" +" Please note that activating or deactivating Make To Order on the template,\n" +" will reset this setting on its variants.\n" +msgstr "" + +#. module: product_variant_route_mto +#. odoo-python +#: code:addons/product_variant_route_mto/models/product_template.py:0 +msgid "" +"Activating MTO route will reset `Variant is MTO` setting on the variants." +msgstr "" + +#. module: product_variant_route_mto +#. odoo-python +#: code:addons/product_variant_route_mto/models/product_template.py:0 +msgid "" +"Deactivating MTO route will reset `Variant is MTO` setting on the variants." +msgstr "" + +#. module: product_variant_route_mto +#: model:ir.model,name:product_variant_route_mto.model_product_template +msgid "Product" +msgstr "" + +#. module: product_variant_route_mto +#: model:ir.model,name:product_variant_route_mto.model_product_product +msgid "Product Variant" +msgstr "" + +#. module: product_variant_route_mto +#: model:ir.model.fields,field_description:product_variant_route_mto.field_product_product__route_ids +msgid "Route" +msgstr "" + +#. module: product_variant_route_mto +#: model:ir.model.fields,field_description:product_variant_route_mto.field_product_product__is_mto +msgid "Variant is MTO" +msgstr "" + +#. module: product_variant_route_mto +#. odoo-python +#: code:addons/product_variant_route_mto/models/product_template.py:0 +msgid "Warning" +msgstr "" + +#. module: product_variant_route_mto +#. odoo-python +#: code:addons/product_variant_route_mto/models/product_product.py:0 +msgid "You cannot mark a variant as non MTO when the product is MTO" +msgstr "" diff --git a/product_variant_route_mto/models/__init__.py b/product_variant_route_mto/models/__init__.py new file mode 100644 index 00000000000..18b37e85320 --- /dev/null +++ b/product_variant_route_mto/models/__init__.py @@ -0,0 +1,2 @@ +from . import product_product +from . import product_template diff --git a/product_variant_route_mto/models/product_product.py b/product_variant_route_mto/models/product_product.py new file mode 100644 index 00000000000..2814627f4ed --- /dev/null +++ b/product_variant_route_mto/models/product_product.py @@ -0,0 +1,68 @@ +# Copyright 2023 Camptocamp SA +# Copyright 2025 Jacques-Etienne Baudoux (BCIM) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) + +from odoo import _, api, fields, models +from odoo.exceptions import ValidationError + +IS_MTO_HELP = """ + Check or Uncheck this field to enable the Make To Order on the variant, + independantly from its template configuration.\n + Please note that activating or deactivating Make To Order on the template, + will reset this setting on its variants. +""" + + +class ProductProduct(models.Model): + _inherit = "product.product" + + is_mto = fields.Boolean( + string="Variant is MTO", + compute="_compute_is_mto", + store=True, + readonly=False, + help=IS_MTO_HELP, + ) + + route_ids = fields.Many2many( + "stock.route", + compute="_compute_route_ids", + domain="[('product_selectable', '=', True)]", + store=False, + search="_search_route_ids", + ) + + def _compute_is_mto(self): + for product in self: + product.is_mto = product.product_tmpl_id.is_mto + + @api.depends("is_mto", "product_tmpl_id.route_ids") + def _compute_route_ids(self): + mto_routes = self.env["stock.route"].search([("is_mto", "=", True)]) + for product in self: + routes = product.product_tmpl_id.route_ids + if product.is_mto: + routes += mto_routes + product.route_ids = routes + + def _search_route_ids(self, operator, value): + mto_routes = self.env["stock.route"].search([("is_mto", "=", True)]) + if operator in ("=", "!=") and value in mto_routes: + return [("is_mto", operator, True)] + domain = [] + route_ids = value.copy() + for idx, route_id in enumerate(route_ids): + if route_id in mto_routes.ids: + route_ids.pop(idx) + domain = [("is_mto", "=" if operator == "in" else "!=", True)] + if route_ids: + domain += [("product_tmpl_id.route_ids", operator, route_ids)] + return domain + + @api.constrains("is_mto") + def _check_template_is_mto(self): + for product in self: + if not product.is_mto and product.product_tmpl_id.is_mto: + raise ValidationError( + _("You cannot mark a variant as non MTO when the product is MTO") + ) diff --git a/product_variant_route_mto/models/product_template.py b/product_variant_route_mto/models/product_template.py new file mode 100644 index 00000000000..15074e105c4 --- /dev/null +++ b/product_variant_route_mto/models/product_template.py @@ -0,0 +1,73 @@ +# Copyright 2023 Camptocamp SA +# Copyright 2025 Jacques-Etienne Baudoux (BCIM) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) + +from odoo import _, api, models + + +class ProductTemplate(models.Model): + _inherit = "product.template" + + def write(self, values): + if "route_ids" not in values: + return super().write(values) + + # As _compute_is_mto cannot use api.depends (or it would reset MTO + # route on variants as soon as there is a change on the template routes), + # we need to check which template in self had MTO route activated + # or deactivated to force the recomputation of is_mto on variants + templates_mto_before = self.filtered("is_mto") + templates_not_mto_before = self - templates_mto_before + + res = super().write(values) + + templates_mto_after = self.filtered("is_mto") + templates_not_mto_after = self - templates_mto_after + + templates_mto_added = templates_not_mto_before & templates_mto_after + templates_mto_removed = templates_not_mto_after & templates_mto_before + + ( + templates_mto_added | templates_mto_removed + ).product_variant_ids._compute_is_mto() + + return res + + @api.onchange("route_ids") + def onchange_route_ids(self): + mto_routes = self.env["stock.route"].search([("is_mto", "=", True)]) + if not mto_routes: + return + + origin_routes = ( + self._origin.route_ids if self._origin else self.env["stock.route"] + ) + current_routes = ( + self.route_ids._origin if self.route_ids else self.env["stock.route"] + ) + + added_routes = current_routes - origin_routes + if set(mto_routes.ids) & set(added_routes.ids): + # Return warning activating MTO route + return { + "warning": { + "title": _("Warning"), + "message": _( + "Activating MTO route will reset `Variant is MTO` " + "setting on the variants." + ), + } + } + + removed_routes = origin_routes - current_routes + if set(mto_routes.ids) & set(removed_routes.ids): + # Return warning deactivating MTO route + return { + "warning": { + "title": _("Warning"), + "message": _( + "Deactivating MTO route will reset `Variant is MTO` " + "setting on the variants." + ), + } + } diff --git a/product_variant_route_mto/pyproject.toml b/product_variant_route_mto/pyproject.toml new file mode 100644 index 00000000000..4231d0cccb3 --- /dev/null +++ b/product_variant_route_mto/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/product_variant_route_mto/readme/CONFIGURATION.md b/product_variant_route_mto/readme/CONFIGURATION.md new file mode 100644 index 00000000000..616f736ea52 --- /dev/null +++ b/product_variant_route_mto/readme/CONFIGURATION.md @@ -0,0 +1,2 @@ +The checkbox `Variant is MTO` on the product variant allows +to force usage or non-usage of the MTO route for the variant. diff --git a/product_variant_route_mto/readme/CONTRIBUTORS.md b/product_variant_route_mto/readme/CONTRIBUTORS.md new file mode 100644 index 00000000000..09e1cc56282 --- /dev/null +++ b/product_variant_route_mto/readme/CONTRIBUTORS.md @@ -0,0 +1,4 @@ +- Matthieu Méquignon \<\> +- Akim Juillerat \<\> +- Chau Le \<\> +- Jacques-Etienne Baudoux (BCIM) \<\> diff --git a/product_variant_route_mto/readme/CREDITS.md b/product_variant_route_mto/readme/CREDITS.md new file mode 100644 index 00000000000..c2d2a1e6b83 --- /dev/null +++ b/product_variant_route_mto/readme/CREDITS.md @@ -0,0 +1,3 @@ +The development and migration of this module has been financially supported by: + +- Camptocamp diff --git a/product_variant_route_mto/readme/DESCRIPTION.md b/product_variant_route_mto/readme/DESCRIPTION.md new file mode 100644 index 00000000000..d2fafcf69b0 --- /dev/null +++ b/product_variant_route_mto/readme/DESCRIPTION.md @@ -0,0 +1,3 @@ +This module allows to set a product variant as MTO (enabling the Make To +Order route on that variant only) while the related product is not MTO. +However, you cannot mark a variant has non MTO when the template is MTO. diff --git a/product_variant_route_mto/static/description/icon.png b/product_variant_route_mto/static/description/icon.png new file mode 100644 index 00000000000..1dcc49c24f3 Binary files /dev/null and b/product_variant_route_mto/static/description/icon.png differ diff --git a/product_variant_route_mto/static/description/index.html b/product_variant_route_mto/static/description/index.html new file mode 100644 index 00000000000..527f2221ca0 --- /dev/null +++ b/product_variant_route_mto/static/description/index.html @@ -0,0 +1,445 @@ + + + + + +Product Variant Route MTO + + + +
+

Product Variant Route MTO

+ + +

Alpha License: AGPL-3 OCA/product-attribute Translate me on Weblate Try me on Runboat

+

This module allows to set a product variant as MTO (enabling the Make To +Order route on that variant only) while the related product is not MTO. +However, you cannot mark a variant has non MTO when the template is MTO.

+
+

Important

+

This is an alpha version, the data model and design can change at any time without warning. +Only for development or testing purpose, do not use in production. +More details on development status

+
+

Table of contents

+ +
+

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 to smash it by providing a detailed and welcomed +feedback.

+

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

+
+
+

Credits

+
+

Authors

+
    +
  • Camptocamp SA
  • +
+
+
+

Contributors

+ +
+
+

Other credits

+

The development and migration of this module has been financially +supported by:

+
    +
  • Camptocamp
  • +
+
+
+

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.

+

Current maintainers:

+

mmequignon jbaudoux

+

This module is part of the OCA/product-attribute project on GitHub.

+

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

+
+
+
+ + diff --git a/product_variant_route_mto/tests/__init__.py b/product_variant_route_mto/tests/__init__.py new file mode 100644 index 00000000000..8cc9739069e --- /dev/null +++ b/product_variant_route_mto/tests/__init__.py @@ -0,0 +1 @@ +from . import test_mto_variant diff --git a/product_variant_route_mto/tests/common.py b/product_variant_route_mto/tests/common.py new file mode 100644 index 00000000000..e5188da3b83 --- /dev/null +++ b/product_variant_route_mto/tests/common.py @@ -0,0 +1,90 @@ +# Copyright 2023 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) + +from odoo.tests import Form, tagged + +from odoo.addons.base.tests.common import BaseCommon + + +@tagged("post_install", "-at_install") +class TestMTOVariantCommon(BaseCommon): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.setUpClassProduct() + + @classmethod + def setUpClassProduct(cls): + cls.color = cls.env["product.attribute"].create({"name": "Color"}) + value_model = cls.env["product.attribute.value"] + cls.values = value_model.create( + [ + {"name": "red", "attribute_id": cls.color.id}, + {"name": "blue", "attribute_id": cls.color.id}, + {"name": "black", "attribute_id": cls.color.id}, + {"name": "green", "attribute_id": cls.color.id}, + ] + ) + cls.value_red = cls.values.filtered(lambda v: v.name == "red") + cls.value_blue = cls.values.filtered(lambda v: v.name == "blue") + cls.value_black = cls.values.filtered(lambda v: v.name == "black") + cls.value_green = cls.values.filtered(lambda v: v.name == "green") + cls.template_pen = cls.env["product.template"].create( + { + "name": "pen", + "attribute_line_ids": [ + ( + 0, + 0, + { + "attribute_id": cls.color.id, + "value_ids": [(6, 0, cls.values.ids)], + }, + ) + ], + } + ) + cls.variants_pen = cls.template_pen.product_variant_ids + cls.black_pen = cls.variants_pen.filtered( + lambda v: v.product_template_attribute_value_ids.name == "black" + ) + cls.green_pen = cls.variants_pen.filtered( + lambda v: v.product_template_attribute_value_ids.name == "green" + ) + cls.red_pen = cls.variants_pen.filtered( + lambda v: v.product_template_attribute_value_ids.name == "red" + ) + cls.blue_pen = cls.variants_pen.filtered( + lambda v: v.product_template_attribute_value_ids.name == "blue" + ) + cls.mto_route = cls.env.ref("stock.route_warehouse0_mto") + cls.mto_route.active = True + + def add_route(self, template, route): + if not route: + route = self.mto_route + with Form(template) as record: + record.route_ids.add(route) + + def remove_route(self, template, route): + if not route: + route = self.mto_route + with Form(template) as record: + record.route_ids.remove(id=route.id) + + @classmethod + def toggle_is_mto(self, records): + for record in records: + record.is_mto = not record.is_mto + + def assertVariantsMTO(self, records): + records.invalidate_recordset(["is_mto"]) + self.assertTrue(all([record.is_mto for record in records])) + for rec in records: + self.assertIn(self.mto_route, rec.route_ids) + + def assertVariantsNotMTO(self, records): + records.invalidate_recordset(["is_mto"]) + self.assertFalse(any([record.is_mto for record in records])) + for rec in records: + self.assertNotIn(self.mto_route, rec.route_ids) diff --git a/product_variant_route_mto/tests/test_mto_variant.py b/product_variant_route_mto/tests/test_mto_variant.py new file mode 100644 index 00000000000..0e1aeebc0d1 --- /dev/null +++ b/product_variant_route_mto/tests/test_mto_variant.py @@ -0,0 +1,122 @@ +# Copyright 2023 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) +import logging + +from odoo.exceptions import ValidationError + +from .common import TestMTOVariantCommon + +onchange_logger = "odoo.tests.form.onchange" + +_logger = logging.getLogger(onchange_logger) + + +class TestMTOVariant(TestMTOVariantCommon): + def test_variants_mto(self): + # instanciate variables + pen_template = self.template_pen + pens = self.variants_pen + blue_pen = self.blue_pen + red_pen = self.red_pen + green_pen = self.green_pen + black_pen = self.black_pen + self.assertVariantsNotMTO(pens) + # enable mto route for black pen + self.toggle_is_mto(black_pen) + self.assertVariantsMTO(black_pen) + self.assertVariantsNotMTO(blue_pen | green_pen | red_pen) + # enable mto route for black and blue pens + self.toggle_is_mto(blue_pen) + self.assertVariantsMTO(black_pen | blue_pen) + self.assertVariantsNotMTO(red_pen | green_pen) + # Now enable the mto route for the template, all variants get is_mto = True + with self.assertLogs(onchange_logger, level="WARNING"): + self.add_route(pen_template, self.mto_route) + self.assertVariantsMTO(pens) + # Disable mto route for black_pen + with self.assertRaises(ValidationError): + self.toggle_is_mto(black_pen) + # Disable mto route on the template, reset is_mto on variants + with self.assertLogs(onchange_logger, level="WARNING"): + self.remove_route(pen_template, self.mto_route) + self.assertVariantsNotMTO(pens) + + def test_template_routes_updated(self): + # instanciate variables + pen_template = self.template_pen + pens = self.variants_pen + blue_pen = self.blue_pen + red_pen = self.red_pen + green_pen = self.green_pen + black_pen = self.black_pen + self.assertVariantsNotMTO(pens) + # Now toggle a variant to is_mto + self.toggle_is_mto(black_pen) + self.assertVariantsMTO(black_pen) + self.assertVariantsNotMTO(green_pen | red_pen | blue_pen) + # Now modifying template.route_ids to trigger variant's _compute_is_mto + random_route = self.mto_route.create({"name": "loutourout de la vit"}) + self.add_route(pen_template, random_route) + self.assertVariantsMTO(black_pen) + self.assertVariantsNotMTO(green_pen | red_pen | blue_pen) + self.remove_route(pen_template, random_route) + self.assertVariantsMTO(black_pen) + self.assertVariantsNotMTO(green_pen | red_pen | blue_pen) + + def test_template_warnings(self): + # instanciate variables + pen_template = self.template_pen + pens = self.variants_pen + blue_pen = self.blue_pen + red_pen = self.red_pen + green_pen = self.green_pen + black_pen = self.black_pen + self.assertVariantsNotMTO(pens) + + # enable mto route for black pen + self.toggle_is_mto(black_pen) + self.assertVariantsMTO(black_pen) + + # Enable mto route on the template, raise warning as is_mto is reset on variants + with self.assertLogs(onchange_logger, level="WARNING") as log_catcher: + self.add_route(pen_template, self.mto_route) + self.assertIn("WARNING", log_catcher.output[0]) + self.assertIn("Activating MTO route will reset", log_catcher.output[0]) + self.assertVariantsMTO(pens) + + # Enable unrelated route does not raise warning nor reset + random_route = self.mto_route.create({"name": "loutourout de la vit"}) + with self.assertLogs(onchange_logger) as log_catcher: + self.add_route(pen_template, random_route) + _logger.info("No warning raised") + self.assertNotIn("WARNING", log_catcher.output[0]) + self.assertVariantsMTO(pens) + + # Disable mto route on the template, + # raise warning as is_mto is reset on variants + with self.assertLogs(onchange_logger) as log_catcher: + self.remove_route(pen_template, self.mto_route) + self.assertIn("WARNING", log_catcher.output[0]) + self.assertIn("Deactivating MTO route will reset", log_catcher.output[0]) + self.assertVariantsNotMTO(pens) + + # Enable mto route for black pen + self.toggle_is_mto(black_pen) + self.assertVariantsMTO(black_pen) + self.assertVariantsNotMTO(blue_pen | green_pen | red_pen) + + # Disable unrelated route does not raise warning nor reset + with self.assertLogs(onchange_logger) as log_catcher: + self.remove_route(pen_template, random_route) + _logger.info("No warning raised") + self.assertVariantsMTO(black_pen) + self.assertVariantsNotMTO(blue_pen | green_pen | red_pen) + + def test_search_route_ids(self): + route = self.env["stock.route"].search([("is_mto", "=", False)], limit=1) + search = self.env["product.product"]._search_route_ids("=", route) + self.assertIn("product_tmpl_id.route_ids", search[0][0]) + + def test_search_route_ids_mtp(self): + search = self.env["product.product"]._search_route_ids("=", self.mto_route) + self.assertIn("is_mto", search[0][0]) diff --git a/product_variant_route_mto/views/product_product.xml b/product_variant_route_mto/views/product_product.xml new file mode 100644 index 00000000000..99c0340cee1 --- /dev/null +++ b/product_variant_route_mto/views/product_product.xml @@ -0,0 +1,15 @@ + + + + + product.product.form.easy.inherit + product.product + + + + + + + +