From 1b94756d08ad6a8e78426fcbbc7af305b83a8dd2 Mon Sep 17 00:00:00 2001 From: phucph Date: Thu, 29 Jan 2026 18:09:44 +0700 Subject: [PATCH] [IMP] product_analytic: Analytic Distribution Model in product.template model --- product_analytic/README.rst | 24 +-- product_analytic/__manifest__.py | 5 +- product_analytic/models/__init__.py | 1 + product_analytic/models/product_template.py | 45 ++++++ product_analytic/tests/__init__.py | 1 + .../tests/test_product_template.py | 150 ++++++++++++++++++ .../views/product_template_view.xml | 48 ++++++ product_analytic/views/product_view.xml | 6 +- 8 files changed, 266 insertions(+), 14 deletions(-) create mode 100644 product_analytic/models/product_template.py create mode 100644 product_analytic/tests/test_product_template.py create mode 100644 product_analytic/views/product_template_view.xml diff --git a/product_analytic/README.rst b/product_analytic/README.rst index 2365961951..8af50298e6 100644 --- a/product_analytic/README.rst +++ b/product_analytic/README.rst @@ -121,18 +121,18 @@ Authors Contributors ------------ -- Alexis de Lattre -- Javier Iniesta -- Luis M. Ontalba -- David Vidal -- Thore Baden -- Pimolnat Suntian -- Reyes4711 -- Denis Roussel -- Darius Žižys -- Jacques-Etienne Baudoux (BCIM) -- Saran Lim. -- Thiago Mulero +- Alexis de Lattre +- Javier Iniesta +- Luis M. Ontalba +- David Vidal +- Thore Baden +- Pimolnat Suntian +- Reyes4711 +- Denis Roussel +- Darius Žižys +- Jacques-Etienne Baudoux (BCIM) +- Saran Lim. +- Thiago Mulero Maintainers ----------- diff --git a/product_analytic/__manifest__.py b/product_analytic/__manifest__.py index 8314140c92..703bff44ec 100644 --- a/product_analytic/__manifest__.py +++ b/product_analytic/__manifest__.py @@ -12,7 +12,10 @@ "author": "Akretion, Tecnativa, Odoo Community Association (OCA)", "website": "https://github.com/OCA/account-analytic", "depends": ["account"], - "data": ["views/product_view.xml"], + "data": [ + "views/product_view.xml", + "views/product_template_view.xml", + ], "demo": ["demo/product_demo.xml"], "installable": True, } diff --git a/product_analytic/models/__init__.py b/product_analytic/models/__init__.py index 7c3891ee6b..8177f25001 100644 --- a/product_analytic/models/__init__.py +++ b/product_analytic/models/__init__.py @@ -2,3 +2,4 @@ from . import product from . import product_category +from . import product_template diff --git a/product_analytic/models/product_template.py b/product_analytic/models/product_template.py new file mode 100644 index 0000000000..8b85238333 --- /dev/null +++ b/product_analytic/models/product_template.py @@ -0,0 +1,45 @@ +from odoo import api, fields, models + + +class ProductTemplate(models.Model): + _inherit = "product.template" + + analytic_distribution_model_ids = fields.One2many( + "account.analytic.distribution.model", + string="Analytic Distribution Models", + compute="_compute_analytic_distribution_models", + inverse="_inverse_analytic_distribution_models", + ) + + @api.depends( + "product_variant_ids", "product_variant_ids.analytic_distribution_model_ids" + ) + def _compute_analytic_distribution_models(self): + for tmpl in self: + tmpl.analytic_distribution_model_ids = ( + tmpl.product_variant_id.analytic_distribution_model_ids + ) + + def _inverse_analytic_distribution_models(self): + for tmpl in self: + if tmpl.product_variant_count > 1: + continue + product_variant = tmpl.product_variant_id + product_variant.analytic_distribution_model_ids = ( + tmpl.analytic_distribution_model_ids + ) + + @api.model_create_multi + def create(self, vals_list): + templates = super().create(vals_list) + for template, vals in zip(templates, vals_list, strict=False): + distribution_cmds = vals.get("analytic_distribution_model_ids", False) + if not distribution_cmds or template.product_variant_count > 1: + continue + product_variant = template.product_variant_id + product_variant.write( + { + "analytic_distribution_model_ids": distribution_cmds, + } + ) + return templates diff --git a/product_analytic/tests/__init__.py b/product_analytic/tests/__init__.py index d72e9d6bd7..ed528599de 100644 --- a/product_analytic/tests/__init__.py +++ b/product_analytic/tests/__init__.py @@ -1,3 +1,4 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). from . import test_account_move +from . import test_product_template diff --git a/product_analytic/tests/test_product_template.py b/product_analytic/tests/test_product_template.py new file mode 100644 index 0000000000..333d3bd499 --- /dev/null +++ b/product_analytic/tests/test_product_template.py @@ -0,0 +1,150 @@ +# Copyright 2025 Your Name +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from odoo import Command +from odoo.tests.common import TransactionCase + + +class TestProductTemplate(TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.default_plan = cls.env["account.analytic.plan"].create( + {"name": "Default Plan", "applicability_ids": False} + ) + cls.analytic_account1 = cls.env["account.analytic.account"].create( + {"name": "test analytic_account1", "plan_id": cls.default_plan.id} + ) + cls.analytic_account2 = cls.env["account.analytic.account"].create( + {"name": "test analytic_account2", "plan_id": cls.default_plan.id} + ) + cls.distribution_model1 = cls.env["account.analytic.distribution.model"].create( + { + "account_prefix": "TEST", + "analytic_distribution": {cls.analytic_account1.id: 100}, + } + ) + cls.distribution_model2 = cls.env["account.analytic.distribution.model"].create( + { + "account_prefix": "DEMO", + "analytic_distribution": {cls.analytic_account2.id: 100}, + } + ) + + def test_compute_analytic_distribution_models(self): + """Test that analytic_distribution_model_ids is computed from product variant""" + template = self.env["product.template"].create({"name": "Test Template"}) + # Add distribution models to the product variant + template.product_variant_id.write( + { + "analytic_distribution_model_ids": [ + Command.set( + [self.distribution_model1.id, self.distribution_model2.id] + ) + ] + } + ) + + # Check that template gets the models from variant + self.assertEqual(len(template.analytic_distribution_model_ids), 2) + self.assertIn( + self.distribution_model1, template.analytic_distribution_model_ids + ) + self.assertIn( + self.distribution_model2, template.analytic_distribution_model_ids + ) + + def test_inverse_analytic_distribution_models_single_variant(self): + """Test inverse method for single variant template""" + template = self.env["product.template"].create({"name": "Test Template"}) + # Set distribution models via template + template.write( + { + "analytic_distribution_model_ids": [ + Command.set([self.distribution_model1.id]) + ] + } + ) + + # Check that variant gets the models + self.assertEqual( + len(template.product_variant_id.analytic_distribution_model_ids), 1 + ) + self.assertIn( + self.distribution_model1, + template.product_variant_id.analytic_distribution_model_ids, + ) + + def test_inverse_analytic_distribution_models_multi_variant(self): + """Test inverse method doesn't affect multi-variant templates""" + template = self.env["product.template"].create( + { + "name": "Test Template", + } + ) + # Add a variant to make it multi-variant + self.env["product.product"].create( + { + "product_tmpl_id": template.id, + "name": "Variant 2", + } + ) + # Get original variant models + original_models = ( + template.product_variant_id.analytic_distribution_model_ids.ids + ) + # Try to set distribution models via template (should be ignored) + template.write( + { + "analytic_distribution_model_ids": [ + Command.set([self.distribution_model1.id]) + ] + } + ) + # Check that variant models are unchanged + self.assertEqual( + template.product_variant_id.analytic_distribution_model_ids.ids, + original_models, + ) + + def test_create_with_analytic_distribution_models(self): + """Test creating template with distribution models""" + template = self.env["product.template"].create( + { + "name": "Test Template", + "analytic_distribution_model_ids": [ + Command.link(self.distribution_model1.id), + Command.link(self.distribution_model2.id), + ], + } + ) + # Check that variant gets the models + self.assertEqual( + len(template.product_variant_id.analytic_distribution_model_ids), 2 + ) + self.assertIn( + self.distribution_model1, + template.product_variant_id.analytic_distribution_model_ids, + ) + self.assertIn( + self.distribution_model2, + template.product_variant_id.analytic_distribution_model_ids, + ) + + def test_create_multi_variant_with_distribution_models(self): + """Test creating multi-variant with distribution models (should be ignored)""" + template = self.env["product.template"].create( + { + "name": "Test Template", + } + ) + # Add second variant + self.env["product.product"].create( + { + "product_tmpl_id": template.id, + "name": "Variant 2", + } + ) + # Check that no distribution models were set on variants + self.assertEqual( + len(template.product_variant_id.analytic_distribution_model_ids), 0 + ) diff --git a/product_analytic/views/product_template_view.xml b/product_analytic/views/product_template_view.xml new file mode 100644 index 0000000000..6ac0eadfb9 --- /dev/null +++ b/product_analytic/views/product_template_view.xml @@ -0,0 +1,48 @@ + + + + product.template.only.form.view.product.analytic + product.template + + + + + + + + + + + + + + + + + + + + + diff --git a/product_analytic/views/product_view.xml b/product_analytic/views/product_view.xml index 2c27658e2c..7a1b9e921b 100644 --- a/product_analytic/views/product_view.xml +++ b/product_analytic/views/product_view.xml @@ -10,7 +10,11 @@ - +