diff --git a/stock_product_variant_mto/README.rst b/stock_product_variant_mto/README.rst index ef7496907c5..89473b9a98d 100644 --- a/stock_product_variant_mto/README.rst +++ b/stock_product_variant_mto/README.rst @@ -64,6 +64,15 @@ Contributors - Matthieu Méquignon - Akim Juillerat +- Chau Le + +Other credits +------------- + +The development and migration of this module has been financially +supported by: + +- Camptocamp Maintainers ----------- diff --git a/stock_product_variant_mto/__manifest__.py b/stock_product_variant_mto/__manifest__.py index 6bb7ae95703..698a5c1dbed 100644 --- a/stock_product_variant_mto/__manifest__.py +++ b/stock_product_variant_mto/__manifest__.py @@ -4,7 +4,7 @@ { "name": "Stock Product Variant MTO", "summary": "Allow to individually set variants as MTO", - "version": "14.0.1.0.0", + "version": "18.0.1.0.0", "development_status": "Alpha", "category": "Inventory", "website": "https://github.com/OCA/stock-logistics-workflow", diff --git a/stock_product_variant_mto/models/product_product.py b/stock_product_variant_mto/models/product_product.py index 4cf5b36427d..87760787633 100644 --- a/stock_product_variant_mto/models/product_product.py +++ b/stock_product_variant_mto/models/product_product.py @@ -23,30 +23,31 @@ class ProductProduct(models.Model): ) route_ids = fields.Many2many( - "stock.location.route", + "stock.route", compute="_compute_route_ids", domain="[('product_selectable', '=', True)]", - store=False, + store=True, ) def _compute_is_mto(self): mto_route = self.env.ref("stock.route_warehouse0_mto", raise_if_not_found=False) + if not mto_route: + self.update({"is_mto": False}) + return + for product in self: - if not mto_route: - product.is_mto = False - continue product.is_mto = mto_route in product.product_tmpl_id.route_ids @api.depends("is_mto", "product_tmpl_id.route_ids") def _compute_route_ids(self): mto_route = self.env.ref("stock.route_warehouse0_mto", raise_if_not_found=False) for product in self: - if mto_route and mto_route in product.product_tmpl_id.route_ids: - if not product.is_mto: - product.route_ids = product.product_tmpl_id.route_ids - mto_route - continue - else: - if mto_route and product.is_mto: - product.route_ids = product.product_tmpl_id.route_ids + mto_route - continue - product.route_ids = product.product_tmpl_id.route_ids + new_route_ids = product.product_tmpl_id.route_ids + + if mto_route: + if product.is_mto and mto_route not in new_route_ids: + new_route_ids += mto_route + elif not product.is_mto and mto_route in new_route_ids: + new_route_ids -= mto_route + + product.route_ids = new_route_ids diff --git a/stock_product_variant_mto/models/product_template.py b/stock_product_variant_mto/models/product_template.py index ca9fd1751d9..de9ecdf2504 100644 --- a/stock_product_variant_mto/models/product_template.py +++ b/stock_product_variant_mto/models/product_template.py @@ -1,58 +1,72 @@ # Copyright 2023 Camptocamp SA # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) -from odoo import _, api, models +from odoo import api, models class ProductTemplate(models.Model): _inherit = "product.template" def write(self, values): - if "route_ids" not in values: + mto_route = self.env.ref("stock.route_warehouse0_mto", raise_if_not_found=False) + + if "route_ids" not in values or not mto_route: 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 - mto_route = self.env.ref("stock.route_warehouse0_mto") - template_not_mto_before = self.filtered(lambda t: mto_route not in t.route_ids) + + templates_not_mto_before = self.filtered(lambda t: mto_route not in t.route_ids) + res = super().write(values) + templates_mto_after = self.filtered(lambda t: mto_route in t.route_ids) - templates_mto_added = template_not_mto_before & templates_mto_after - templates_mto_removed = (self - template_not_mto_before) & ( - self - templates_mto_after - ) - ( + templates_mto_added = templates_not_mto_before & templates_mto_after + templates_mto_removed = self - templates_mto_after - templates_not_mto_before + + affected_product_variants = ( templates_mto_added | templates_mto_removed - ).product_variant_ids._compute_is_mto() + ).product_variant_ids + if affected_product_variants: + affected_product_variants._compute_is_mto() + return res @api.onchange("route_ids") def onchange_route_ids(self): mto_route = self.env.ref("stock.route_warehouse0_mto", raise_if_not_found=False) - if ( - mto_route not in self._origin.route_ids - and mto_route in self.route_ids._origin - ): + if not mto_route: + 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"] + ) + + if mto_route not in origin_routes and mto_route in current_routes: # Return warning activating MTO route return { "warning": { - "title": _("Warning"), - "message": _( - "Activating MTO route will reset `Variant is MTO` setting on the variants." + "title": self.env._("Warning"), + "message": self.env._( + "Activating MTO route will reset `Variant is MTO` " + "setting on the variants." ), } } - if ( - mto_route in self._origin.route_ids - and mto_route not in self.route_ids._origin - ): + + if mto_route in origin_routes and mto_route not in current_routes: # Return warning deactivating MTO route return { "warning": { - "title": _("Warning"), - "message": _( - "Deactivating MTO route will reset `Variant is MTO` setting on the variants." + "title": self.env._("Warning"), + "message": self.env._( + "Deactivating MTO route will reset `Variant is MTO` " + "setting on the variants." ), } } diff --git a/stock_product_variant_mto/readme/CONTRIBUTORS.md b/stock_product_variant_mto/readme/CONTRIBUTORS.md index da29ed10624..a5082bc02f1 100644 --- a/stock_product_variant_mto/readme/CONTRIBUTORS.md +++ b/stock_product_variant_mto/readme/CONTRIBUTORS.md @@ -1,2 +1,3 @@ - Matthieu Méquignon \<\> - Akim Juillerat \<\> +- Chau Le \<\> diff --git a/stock_product_variant_mto/readme/CREDITS.md b/stock_product_variant_mto/readme/CREDITS.md new file mode 100644 index 00000000000..c2d2a1e6b83 --- /dev/null +++ b/stock_product_variant_mto/readme/CREDITS.md @@ -0,0 +1,3 @@ +The development and migration of this module has been financially supported by: + +- Camptocamp diff --git a/stock_product_variant_mto/static/description/index.html b/stock_product_variant_mto/static/description/index.html index c514a0997ac..759e988feea 100644 --- a/stock_product_variant_mto/static/description/index.html +++ b/stock_product_variant_mto/static/description/index.html @@ -385,7 +385,8 @@

Stock Product Variant MTO

  • Credits
  • @@ -411,10 +412,19 @@

    Contributors

    + +
    +

    Other credits

    +

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

    +
      +
    • Camptocamp
    -

    Maintainers

    +

    Maintainers

    This module is maintained by the OCA.

    Odoo Community Association diff --git a/stock_product_variant_mto/tests/common.py b/stock_product_variant_mto/tests/common.py index 934781457a8..e5188da3b83 100644 --- a/stock_product_variant_mto/tests/common.py +++ b/stock_product_variant_mto/tests/common.py @@ -1,17 +1,16 @@ # Copyright 2023 Camptocamp SA # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) -from odoo.tests.common import Form, SavepointCase +from odoo.tests import Form, tagged +from odoo.addons.base.tests.common import BaseCommon -class TestMTOVariantCommon(SavepointCase): - at_install = False - post_install = True +@tagged("post_install", "-at_install") +class TestMTOVariantCommon(BaseCommon): @classmethod def setUpClass(cls): super().setUpClass() - cls.env = cls.env(context=dict(cls.env.context, tracking_disable=True)) cls.setUpClassProduct() @classmethod @@ -79,13 +78,13 @@ def toggle_is_mto(self, records): record.is_mto = not record.is_mto def assertVariantsMTO(self, records): - records.invalidate_cache(["is_mto"]) + 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_cache(["is_mto"]) + 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/stock_product_variant_mto/tests/test_mto_variant.py b/stock_product_variant_mto/tests/test_mto_variant.py index 16794f9302f..4563b108fe5 100644 --- a/stock_product_variant_mto/tests/test_mto_variant.py +++ b/stock_product_variant_mto/tests/test_mto_variant.py @@ -4,6 +4,10 @@ from .common import TestMTOVariantCommon +onchange_logger = "odoo.tests.form.onchange" + +_logger = logging.getLogger(onchange_logger) + class TestMTOVariant(TestMTOVariantCommon): def test_variants_mto(self): @@ -24,14 +28,16 @@ def test_variants_mto(self): 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 - self.add_route(pen_template, self.mto_route) + with self.assertLogs(onchange_logger, level="WARNING"): + self.add_route(pen_template, self.mto_route) self.assertVariantsMTO(pens) # Disable mto route for black_pen self.toggle_is_mto(black_pen) self.assertVariantsNotMTO(black_pen) self.assertVariantsMTO(blue_pen | green_pen | red_pen) # Disable mto route on the template, reset is_mto on variants - self.remove_route(pen_template, self.mto_route) + with self.assertLogs(onchange_logger, level="WARNING"): + self.remove_route(pen_template, self.mto_route) self.assertVariantsNotMTO(pens) def test_template_routes_updated(self): @@ -44,7 +50,8 @@ def test_template_routes_updated(self): black_pen = self.black_pen self.assertVariantsNotMTO(pens) # If template is set to MTO, all variants are updated - self.add_route(pen_template, self.mto_route) + with self.assertLogs(onchange_logger, level="WARNING"): + self.add_route(pen_template, self.mto_route) self.assertVariantsMTO(pens) # Now toggle a few variants to is_mto == False self.toggle_is_mto(black_pen | blue_pen) @@ -65,43 +72,49 @@ def test_template_warnings(self): red_pen = self.red_pen green_pen = self.green_pen black_pen = self.black_pen - onchange_logger = logging.getLogger("odoo.tests.common.onchange") 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) as log: + with self.assertLogs(onchange_logger, level="WARNING") as log_catcher: self.add_route(pen_template, self.mto_route) - self.assertIn("WARNING", log.output[0]) - self.assertIn("Activating MTO route will reset", log.output[0]) + self.assertIn("WARNING", log_catcher.output[0]) + self.assertIn("Activating MTO route will reset", log_catcher.output[0]) self.assertVariantsMTO(pens) + # Disable mto route for black pen self.toggle_is_mto(black_pen) self.assertVariantsNotMTO(black_pen) self.assertVariantsMTO(blue_pen | green_pen | red_pen) + # 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: + with self.assertLogs(onchange_logger) as log_catcher: self.add_route(pen_template, random_route) - onchange_logger.info("No warning raised") - self.assertNotIn("WARNING", log.output[0]) + _logger.info("No warning raised") + self.assertNotIn("WARNING", log_catcher.output[0]) self.assertVariantsNotMTO(black_pen) self.assertVariantsMTO(blue_pen | green_pen | red_pen) - # Disable mto route on the template, raise warning as is_mto is reset on variants - with self.assertLogs(onchange_logger) as log: + + # 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.output[0]) - self.assertIn("Deactivating MTO route will reset", log.output[0]) + 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: + with self.assertLogs(onchange_logger) as log_catcher: self.remove_route(pen_template, random_route) - onchange_logger.info("No warning raised") - self.assertNotIn("WARNING", log.output[0]) + _logger.info("No warning raised") self.assertVariantsMTO(black_pen) self.assertVariantsNotMTO(blue_pen | green_pen | red_pen)