From 068aa4f02297a6487c8f0a3aa942d4f1d6b62fc8 Mon Sep 17 00:00:00 2001 From: Carlos Roca Date: Mon, 23 Mar 2026 09:11:08 +0100 Subject: [PATCH] [IMP] product_pricelist_direct_print: Add support for grouping product template fields and m2m Before these changes, fields were always taken from the variant model, regardless of whether grouping by variants was selected or not, which could lead to errors. Additionally, grouping by many-to-many (m2m) fields was not allowed. With these changes, it is now possible to group by m2m fields, and products are distinguished from variants when retrieving their fields, ensuring that errors do not occur. --- .../test_product_pricelist_direct_print.py | 34 +++++++++++++++++ .../wizards/product_pricelist_print.py | 37 +++++++++++++------ .../wizards/product_pricelist_print_view.xml | 7 +++- 3 files changed, 66 insertions(+), 12 deletions(-) diff --git a/product_pricelist_direct_print/tests/test_product_pricelist_direct_print.py b/product_pricelist_direct_print/tests/test_product_pricelist_direct_print.py index 5ac4a6b06c2..61680f02e6b 100644 --- a/product_pricelist_direct_print/tests/test_product_pricelist_direct_print.py +++ b/product_pricelist_direct_print/tests/test_product_pricelist_direct_print.py @@ -1,6 +1,7 @@ # Copyright 2017 Carlos Dauden # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). +from odoo import Command from odoo.exceptions import ValidationError from odoo.tests.common import TransactionCase, tagged @@ -38,11 +39,25 @@ def setUpClass(cls): cls.category_child = cls.env["product.category"].create( {"name": "Test category child", "parent_id": cls.category.id} ) + cls.tag_01 = cls.env["product.tag"].create({"name": "Test 01"}) + cls.tag_02 = cls.env["product.tag"].create({"name": "Test 02"}) cls.product = cls.env["product.product"].create( { "name": "Product for test", "categ_id": cls.category.id, "default_code": "TESTPROD01", + "product_tag_ids": [ + Command.link(cls.tag_01.id), + Command.link(cls.tag_02.id), + ], + } + ) + cls.product2 = cls.env["product.product"].create( + { + "name": "Product for test New", + "categ_id": cls.category.id, + "default_code": "TESTPRODNEW", + "product_tag_ids": [Command.link(cls.tag_02.id)], } ) cls.partner = cls.env["res.partner"].create( @@ -176,6 +191,25 @@ def test_show_only_defined_products(self): products = wiz.get_products_to_print() self.assertIn(self.product, products) + def test_group_field_m2m(self): + wiz = self.wiz_obj.with_context( + active_model="product.pricelist", + active_id=self.pricelist.id, + ).create({}) + wiz.group_field_template = "product_tag_ids" + groups = wiz.get_groups_to_print() + group_names = [x["group_name"] for x in groups] + self.assertIn("Test 01", group_names) + self.assertIn("Test 02", group_names) + for group in groups: + if group["group_name"] == "Test 01": + self.assertEqual(group["products"], self.product.product_tmpl_id) + if group["group_name"] == "Test 02": + self.assertEqual( + group["products"], + self.product.product_tmpl_id + self.product2.product_tmpl_id, + ) + def test_parent_categories(self): product_category_child = self.env["product.template"].create( { diff --git a/product_pricelist_direct_print/wizards/product_pricelist_print.py b/product_pricelist_direct_print/wizards/product_pricelist_print.py index 739ef74311b..aa2d3ec3ed7 100644 --- a/product_pricelist_direct_print/wizards/product_pricelist_print.py +++ b/product_pricelist_direct_print/wizards/product_pricelist_print.py @@ -61,6 +61,11 @@ class ProductPricelistPrint(models.TransientModel): default="categ_id", required=True, ) + group_field_template = fields.Selection( + selection=lambda x: x._selection_group_field(model="product.template"), + default="categ_id", + required=True, + ) partner_count = fields.Integer(compute="_compute_partner_count") date = fields.Datetime(required=True, default=fields.Datetime.now) last_ordered_products = fields.Integer( @@ -173,14 +178,14 @@ def default_get(self, fields): res["categ_ids"] = [(6, 0, category_items.mapped("categ_id").ids)] return res - def _selection_group_field(self): + def _selection_group_field(self, model="product.product"): fields = ( self.env["ir.model.fields"] .sudo() .search( [ - ("model", "=", "product.product"), - ("ttype", "=", "many2one"), + ("model", "=", model), + ("ttype", "in", ["many2one", "many2many"]), ] ) ) @@ -374,13 +379,22 @@ def get_products_to_print(self): return products def get_group_key(self, product): - group_field = getattr(product, self.group_field) - complete_name = getattr(group_field, "complete_name", group_field.name) or _( - "Undefined" + group_field_name = ( + self.group_field if self.show_variants else self.group_field_template ) - if not self.max_categ_level: - return complete_name - return " / ".join(complete_name.split(" / ")[: self.max_categ_level]) + group_fields = getattr(product, group_field_name) + res = [] + for group_field in group_fields: + complete_name = getattr(group_field, "complete_name", group_field.name) + if not self.max_categ_level: + res.append(complete_name) + else: + res.append( + " / ".join(complete_name.split(" / ")[: self.max_categ_level]) + ) + if len(res) == 0: + res.append(_("Undefined")) + return res def get_sorted_products(self, products): if self.order_field: @@ -395,8 +409,9 @@ def get_groups_to_print(self): return [] group_dict = defaultdict(lambda: products.browse()) for product in products: - key = self.get_group_key(product) - group_dict[key] |= product + keys = self.get_group_key(product) + for key in keys: + group_dict[key] |= product group_list = [] for key in sorted(group_dict.keys()): group_list.append( diff --git a/product_pricelist_direct_print/wizards/product_pricelist_print_view.xml b/product_pricelist_direct_print/wizards/product_pricelist_print_view.xml index bec0edae558..a3b8c8c46ef 100644 --- a/product_pricelist_direct_print/wizards/product_pricelist_print_view.xml +++ b/product_pricelist_direct_print/wizards/product_pricelist_print_view.xml @@ -29,7 +29,12 @@ - + +