Skip to content

Commit 3b60890

Browse files
committed
[FIX] product_variant_route_mto: rely on product_route_mto
that relies on stock_route_mto
1 parent c1a124b commit 3b60890

File tree

7 files changed

+83
-67
lines changed

7 files changed

+83
-67
lines changed

product_variant_route_mto/__manifest__.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
# Copyright 2023 Camptocamp SA
2+
# Copyright 2025 Jacques-Etienne Baudoux (BCIM) <[email protected]>
23
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
34

45
{
@@ -9,10 +10,10 @@
910
"category": "Inventory",
1011
"website": "https://github.com/OCA/product-attribute",
1112
"author": "Camptocamp SA, Odoo Community Association (OCA)",
12-
"maintainers": ["mmequignon"],
13+
"maintainers": ["mmequignon", "jbaudoux"],
1314
"license": "AGPL-3",
1415
"installable": True,
1516
"auto_install": False,
16-
"depends": ["stock"],
17+
"depends": ["product_route_mto"],
1718
"data": ["views/product_product.xml"],
1819
}

product_variant_route_mto/models/product_product.py

Lines changed: 33 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
# Copyright 2023 Camptocamp SA
2+
# Copyright 2025 Jacques-Etienne Baudoux (BCIM) <[email protected]>
23
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
34

45
from odoo import api, fields, models
6+
from odoo.exceptions import ValidationError
57

68
IS_MTO_HELP = """
79
Check or Uncheck this field to enable the Make To Order on the variant,
@@ -27,26 +29,42 @@ class ProductProduct(models.Model):
2729
compute="_compute_route_ids",
2830
domain="[('product_selectable', '=', True)]",
2931
store=False,
32+
search="_search_route_ids",
3033
)
3134

3235
def _compute_is_mto(self):
33-
mto_route = self.env.ref("stock.route_warehouse0_mto", raise_if_not_found=False)
3436
for product in self:
35-
if not mto_route:
36-
product.is_mto = False
37-
continue
38-
product.is_mto = mto_route in product.product_tmpl_id.route_ids
37+
product.is_mto = product.product_tmpl_id.is_mto
3938

4039
@api.depends("is_mto", "product_tmpl_id.route_ids")
4140
def _compute_route_ids(self):
42-
mto_route = self.env.ref("stock.route_warehouse0_mto", raise_if_not_found=False)
41+
mto_routes = self.env["stock.route"].search([("is_mto", "=", True)])
4342
for product in self:
44-
if mto_route and mto_route in product.product_tmpl_id.route_ids:
45-
if not product.is_mto:
46-
product.route_ids = product.product_tmpl_id.route_ids - mto_route
47-
continue
48-
else:
49-
if mto_route and product.is_mto:
50-
product.route_ids = product.product_tmpl_id.route_ids + mto_route
51-
continue
52-
product.route_ids = product.product_tmpl_id.route_ids
43+
routes = product.product_tmpl_id.route_ids
44+
if product.is_mto:
45+
routes += mto_routes
46+
product.route_ids = routes
47+
48+
def _search_route_ids(self, operator, value):
49+
mto_routes = self.env["stock.route"].search([("is_mto", "=", True)])
50+
if operator in ("=", "!=") and value in mto_routes:
51+
return [("is_mto", operator, True)]
52+
domain = []
53+
route_ids = value.copy()
54+
for idx, route_id in enumerate(route_ids):
55+
if route_id in mto_routes.ids:
56+
route_ids.pop(idx)
57+
domain = [("is_mto", "=" if operator == "in" else "!=", True)]
58+
if route_ids:
59+
domain += [("product_tmpl_id.route_ids", operator, route_ids)]
60+
return domain
61+
62+
@api.constrains("is_mto")
63+
def _check_template_is_mto(self):
64+
for product in self:
65+
if not product.is_mto and product.product_tmpl_id.is_mto:
66+
raise ValidationError(
67+
self.env._(
68+
"You cannot mark a variant as non MTO when the product is MTO"
69+
)
70+
)

product_variant_route_mto/models/product_template.py

Lines changed: 29 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
# Copyright 2023 Camptocamp SA
2+
# Copyright 2025 Jacques-Etienne Baudoux (BCIM) <[email protected]>
23
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
34

45
from odoo import api, models
@@ -10,30 +11,43 @@ class ProductTemplate(models.Model):
1011
def write(self, values):
1112
if "route_ids" not in values:
1213
return super().write(values)
14+
1315
# As _compute_is_mto cannot use api.depends (or it would reset MTO
1416
# route on variants as soon as there is a change on the template routes),
1517
# we need to check which template in self had MTO route activated
1618
# or deactivated to force the recomputation of is_mto on variants
17-
mto_route = self.env.ref("stock.route_warehouse0_mto")
18-
template_not_mto_before = self.filtered(lambda t: mto_route not in t.route_ids)
19+
templates_mto_before = self.filtered("is_mto")
20+
templates_not_mto_before = self - templates_mto_before
21+
1922
res = super().write(values)
20-
templates_mto_after = self.filtered(lambda t: mto_route in t.route_ids)
21-
templates_mto_added = template_not_mto_before & templates_mto_after
22-
templates_mto_removed = (self - template_not_mto_before) & (
23-
self - templates_mto_after
24-
)
23+
24+
templates_mto_after = self.filtered("is_mto")
25+
templates_not_mto_after = self - templates_mto_after
26+
27+
templates_mto_added = templates_not_mto_before & templates_mto_after
28+
templates_mto_removed = templates_not_mto_after & templates_mto_before
29+
2530
(
2631
templates_mto_added | templates_mto_removed
2732
).product_variant_ids._compute_is_mto()
33+
2834
return res
2935

3036
@api.onchange("route_ids")
3137
def onchange_route_ids(self):
32-
mto_route = self.env.ref("stock.route_warehouse0_mto", raise_if_not_found=False)
33-
if (
34-
mto_route not in self._origin.route_ids
35-
and mto_route in self.route_ids._origin
36-
):
38+
mto_routes = self.env["stock.route"].search([("is_mto", "=", True)])
39+
if not mto_routes:
40+
return
41+
42+
origin_routes = (
43+
self._origin.route_ids if self._origin else self.env["stock.route"]
44+
)
45+
current_routes = (
46+
self.route_ids._origin if self.route_ids else self.env["stock.route"]
47+
)
48+
49+
added_routes = current_routes - origin_routes
50+
if set(mto_routes.ids) & set(added_routes.ids):
3751
# Return warning activating MTO route
3852
return {
3953
"warning": {
@@ -44,10 +58,9 @@ def onchange_route_ids(self):
4458
),
4559
}
4660
}
47-
if (
48-
mto_route in self._origin.route_ids
49-
and mto_route not in self.route_ids._origin
50-
):
61+
62+
removed_routes = origin_routes - current_routes
63+
if set(mto_routes.ids) & set(removed_routes.ids):
5164
# Return warning deactivating MTO route
5265
return {
5366
"warning": {
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
- Matthieu Méquignon \<<[email protected]>\>
22
- Akim Juillerat \<<[email protected]>\>
33
- Chau Le \<<[email protected]>\>
4+
- Jacques-Etienne Baudoux (BCIM) \<<[email protected]>\>
Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
1-
This module allows to define if a product variant can use the Make To
2-
Order route without any dependency on its template routes settings.
1+
This module allows to set a product variant as MTO (enabling the Make To
2+
Order route on that variant only) while the related product is not MTO.
3+
However, you cannot mark a variant has non MTO when the template is MTO.

product_variant_route_mto/tests/test_mto_variant.py

Lines changed: 14 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
33
import logging
44

5+
from odoo.exceptions import ValidationError
6+
57
from .common import TestMTOVariantCommon
68

79
onchange_logger = "odoo.tests.form.onchange"
@@ -32,9 +34,8 @@ def test_variants_mto(self):
3234
self.add_route(pen_template, self.mto_route)
3335
self.assertVariantsMTO(pens)
3436
# Disable mto route for black_pen
35-
self.toggle_is_mto(black_pen)
36-
self.assertVariantsNotMTO(black_pen)
37-
self.assertVariantsMTO(blue_pen | green_pen | red_pen)
37+
with self.assertRaises(ValidationError):
38+
self.toggle_is_mto(black_pen)
3839
# Disable mto route on the template, reset is_mto on variants
3940
with self.assertLogs(onchange_logger, level="WARNING"):
4041
self.remove_route(pen_template, self.mto_route)
@@ -49,20 +50,18 @@ def test_template_routes_updated(self):
4950
green_pen = self.green_pen
5051
black_pen = self.black_pen
5152
self.assertVariantsNotMTO(pens)
52-
# If template is set to MTO, all variants are updated
53-
with self.assertLogs(onchange_logger, level="WARNING"):
54-
self.add_route(pen_template, self.mto_route)
55-
self.assertVariantsMTO(pens)
56-
# Now toggle a few variants to is_mto == False
57-
self.toggle_is_mto(black_pen | blue_pen)
58-
self.assertVariantsMTO(green_pen | red_pen)
59-
self.assertVariantsNotMTO(black_pen | blue_pen)
53+
# Now toggle a variant to is_mto
54+
self.toggle_is_mto(black_pen)
55+
self.assertVariantsMTO(black_pen)
56+
self.assertVariantsNotMTO(green_pen | red_pen | blue_pen)
6057
# Now modifying template.route_ids to trigger variant's _compute_is_mto
6158
random_route = self.mto_route.create({"name": "loutourout de la vit"})
6259
self.add_route(pen_template, random_route)
63-
# Template is still MTO, but variants is_mto shouldn't have changed
64-
self.assertVariantsMTO(green_pen | red_pen)
65-
self.assertVariantsNotMTO(black_pen | blue_pen)
60+
self.assertVariantsMTO(black_pen)
61+
self.assertVariantsNotMTO(green_pen | red_pen | blue_pen)
62+
self.remove_route(pen_template, random_route)
63+
self.assertVariantsMTO(black_pen)
64+
self.assertVariantsNotMTO(green_pen | red_pen | blue_pen)
6665

6766
def test_template_warnings(self):
6867
# instanciate variables
@@ -85,19 +84,13 @@ def test_template_warnings(self):
8584
self.assertIn("Activating MTO route will reset", log_catcher.output[0])
8685
self.assertVariantsMTO(pens)
8786

88-
# Disable mto route for black pen
89-
self.toggle_is_mto(black_pen)
90-
self.assertVariantsNotMTO(black_pen)
91-
self.assertVariantsMTO(blue_pen | green_pen | red_pen)
92-
9387
# Enable unrelated route does not raise warning nor reset
9488
random_route = self.mto_route.create({"name": "loutourout de la vit"})
9589
with self.assertLogs(onchange_logger) as log_catcher:
9690
self.add_route(pen_template, random_route)
9791
_logger.info("No warning raised")
9892
self.assertNotIn("WARNING", log_catcher.output[0])
99-
self.assertVariantsNotMTO(black_pen)
100-
self.assertVariantsMTO(blue_pen | green_pen | red_pen)
93+
self.assertVariantsMTO(pens)
10194

10295
# Disable mto route on the template,
10396
# raise warning as is_mto is reset on variants

product_variant_route_mto/views/product_product.xml

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,6 @@
22
<!-- Copyright 2023 Camptocamp SA
33
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -->
44
<odoo>
5-
<record id="product_normal_form_view" model="ir.ui.view">
6-
<field name="name">product.product.form.inherit</field>
7-
<field name="model">product.product</field>
8-
<field name="inherit_id" ref="product.product_normal_form_view" />
9-
<field name="arch" type="xml">
10-
<group name="operations" position="inside">
11-
<field name="is_mto" />
12-
</group>
13-
</field>
14-
</record>
15-
165
<record id="product_variant_easy_edit_view" model="ir.ui.view">
176
<field name="name">product.product.form.easy.inherit</field>
187
<field name="model">product.product</field>

0 commit comments

Comments
 (0)