From 4c7bc96fba9afab1594fb5cee160206102702d39 Mon Sep 17 00:00:00 2001 From: Simone Rubino Date: Tue, 7 Jan 2025 17:41:22 +0100 Subject: [PATCH 1/2] [ADD] product_configurator_custom_value_variant --- .../README.rst | 71 +++ .../__init__.py | 3 + .../__manifest__.py | 19 + .../models/__init__.py | 4 + .../models/product_attribute.py | 26 ++ .../models/product_config_session.py | 78 ++++ .../readme/DESCRIPTION.rst | 1 + .../static/description/index.html | 416 ++++++++++++++++++ .../tests/__init__.py | 3 + .../tests/test_configure.py | 92 ++++ .../views/product_attribute_views.xml | 22 + .../product_configurator_custom_value_variant | 1 + .../setup.py | 6 + 13 files changed, 742 insertions(+) create mode 100644 product_configurator_custom_value_variant/README.rst create mode 100644 product_configurator_custom_value_variant/__init__.py create mode 100644 product_configurator_custom_value_variant/__manifest__.py create mode 100644 product_configurator_custom_value_variant/models/__init__.py create mode 100644 product_configurator_custom_value_variant/models/product_attribute.py create mode 100644 product_configurator_custom_value_variant/models/product_config_session.py create mode 100644 product_configurator_custom_value_variant/readme/DESCRIPTION.rst create mode 100644 product_configurator_custom_value_variant/static/description/index.html create mode 100644 product_configurator_custom_value_variant/tests/__init__.py create mode 100644 product_configurator_custom_value_variant/tests/test_configure.py create mode 100644 product_configurator_custom_value_variant/views/product_attribute_views.xml create mode 120000 setup/product_configurator_custom_value_variant/odoo/addons/product_configurator_custom_value_variant create mode 100644 setup/product_configurator_custom_value_variant/setup.py diff --git a/product_configurator_custom_value_variant/README.rst b/product_configurator_custom_value_variant/README.rst new file mode 100644 index 0000000000..90eafc6b85 --- /dev/null +++ b/product_configurator_custom_value_variant/README.rst @@ -0,0 +1,71 @@ +==================================================================== +Product Configurator Sale - Create product variant from custom value +==================================================================== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:7573027e875a83e706040e15f005280aedfc622c5f140774594e002abaf12a2f + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |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--configurator-lightgray.png?logo=github + :target: https://github.com/OCA/product-configurator/tree/16.0/product_configurator_custom_value_variant + :alt: OCA/product-configurator +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/product-configurator-16-0/product-configurator-16-0-product_configurator_custom_value_variant + :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-configurator&target_branch=16.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +Create variants when custom attributes are used during product configuration. + +**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 +~~~~~~~ + +* Aion Tech + +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. + +This module is part of the `OCA/product-configurator `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/product_configurator_custom_value_variant/__init__.py b/product_configurator_custom_value_variant/__init__.py new file mode 100644 index 0000000000..31660d6a96 --- /dev/null +++ b/product_configurator_custom_value_variant/__init__.py @@ -0,0 +1,3 @@ +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from . import models diff --git a/product_configurator_custom_value_variant/__manifest__.py b/product_configurator_custom_value_variant/__manifest__.py new file mode 100644 index 0000000000..1d413037d1 --- /dev/null +++ b/product_configurator_custom_value_variant/__manifest__.py @@ -0,0 +1,19 @@ +# Copyright 2025 Simone Rubino - Aion Tech +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +{ + "name": "Product Configurator Sale - Create product variant from custom value", + "version": "16.0.1.0.0", + "category": "Sales/Sales", + "summary": "Allow to create variant for custom values for configurable products.", + "author": "Aion Tech, Odoo Community Association (OCA)", + "license": "AGPL-3", + "website": "https://github.com/OCA/product-configurator", + "depends": [ + "product_configurator_sale", + "product_attribute_custom_value_variant", + ], + "auto_install": True, + "data": [ + "views/product_attribute_views.xml", + ], +} diff --git a/product_configurator_custom_value_variant/models/__init__.py b/product_configurator_custom_value_variant/models/__init__.py new file mode 100644 index 0000000000..79a786720a --- /dev/null +++ b/product_configurator_custom_value_variant/models/__init__.py @@ -0,0 +1,4 @@ +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from . import product_attribute +from . import product_config_session diff --git a/product_configurator_custom_value_variant/models/product_attribute.py b/product_configurator_custom_value_variant/models/product_attribute.py new file mode 100644 index 0000000000..ce021f003b --- /dev/null +++ b/product_configurator_custom_value_variant/models/product_attribute.py @@ -0,0 +1,26 @@ +# Copyright 2025 Simone Rubino - Aion Tech +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import _, api, fields, models +from odoo.exceptions import ValidationError + + +class ProductAttribute(models.Model): + _inherit = "product.attribute" + + create_attribute_value = fields.Boolean( + string="Create attribute value", + help="When this custom attribute is used, \ + a new value will be generated.", + ) + + @api.constrains( + "create_attribute_value", + "val_custom", + ) + def _constrain_create_attribute_value(self): + for value in self: + if value.create_attribute_value and not value.val_custom: + raise ValidationError( + _("'Create attribute value' can only be set on custom values") + ) diff --git a/product_configurator_custom_value_variant/models/product_config_session.py b/product_configurator_custom_value_variant/models/product_config_session.py new file mode 100644 index 0000000000..6a912819ec --- /dev/null +++ b/product_configurator_custom_value_variant/models/product_config_session.py @@ -0,0 +1,78 @@ +# Copyright 2025 Simone Rubino - Aion Tech +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import models + + +class ProductConfigSession(models.Model): + _inherit = "product.config.session" + + def _create_custom_attribute_values(self, value_ids=None, custom_vals=None): + """Create new attribute values and assign them to `self`.""" + if value_ids is None: + value_ids = self.value_ids.ids + + if custom_vals is None: + custom_vals = self._get_custom_vals_dict() + + new_attribute_values = self.env["product.attribute.value"].browse() + session_custom_values_to_unlink = self.env[ + "product.config.session.custom.value" + ].browse() + for attribute_id, custom_value in custom_vals.copy().items(): + attribute = self.env["product.attribute"].browse(attribute_id) + if attribute.create_attribute_value: + custom_attribute_value = attribute._get_variant_custom_attribute_value( + str(custom_value) + ) + + value_ids.append(custom_attribute_value.id) + new_attribute_values |= custom_attribute_value + + custom_vals.pop(attribute_id, None) + session_custom_value_to_unlink = self.custom_value_ids.filtered( + lambda session_custom_value: session_custom_value.attribute_id + == attribute + ) + if session_custom_value_to_unlink: + session_custom_values_to_unlink |= session_custom_value_to_unlink + + if new_attribute_values: + self.value_ids |= new_attribute_values + self.value_ids -= self.get_custom_value_id() + for attribute in new_attribute_values.attribute_id: + attribute_line = self.product_tmpl_id.attribute_line_ids.filtered( + lambda ptav: ptav.attribute_id == attribute + ) + attribute_line.value_ids |= new_attribute_values.filtered( + lambda pav: pav.attribute_id == attribute + ) + + if session_custom_values_to_unlink: + session_custom_values_to_unlink.unlink() + + return new_attribute_values + + def create_get_variant( + self, + value_ids=None, + custom_vals=None, + ): + new_attribute_values = self._create_custom_attribute_values( + value_ids=value_ids, + custom_vals=custom_vals, + ) + variant = super().create_get_variant( + value_ids=value_ids, + custom_vals=custom_vals, + ) + + if new_attribute_values: + # The new attribute values must not be available for new models + for attribute_line in self.product_tmpl_id.attribute_line_ids: + attribute_line.with_context( + no_remove_custom_variants=variant.ids, + ).value_ids -= ( + attribute_line.value_ids & new_attribute_values + ) + return variant diff --git a/product_configurator_custom_value_variant/readme/DESCRIPTION.rst b/product_configurator_custom_value_variant/readme/DESCRIPTION.rst new file mode 100644 index 0000000000..e6c45d6671 --- /dev/null +++ b/product_configurator_custom_value_variant/readme/DESCRIPTION.rst @@ -0,0 +1 @@ +Create variants when custom attributes are used during product configuration. diff --git a/product_configurator_custom_value_variant/static/description/index.html b/product_configurator_custom_value_variant/static/description/index.html new file mode 100644 index 0000000000..4425ba39d9 --- /dev/null +++ b/product_configurator_custom_value_variant/static/description/index.html @@ -0,0 +1,416 @@ + + + + + +Product Configurator Sale - Create product variant from custom value + + + +
+

Product Configurator Sale - Create product variant from custom value

+ + +

Beta License: AGPL-3 OCA/product-configurator Translate me on Weblate Try me on Runboat

+

Create variants when custom attributes are used during product configuration.

+

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

+
    +
  • Aion Tech
  • +
+
+
+

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.

+

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

+

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

+
+
+
+ + diff --git a/product_configurator_custom_value_variant/tests/__init__.py b/product_configurator_custom_value_variant/tests/__init__.py new file mode 100644 index 0000000000..087d8fd4fd --- /dev/null +++ b/product_configurator_custom_value_variant/tests/__init__.py @@ -0,0 +1,3 @@ +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from . import test_configure diff --git a/product_configurator_custom_value_variant/tests/test_configure.py b/product_configurator_custom_value_variant/tests/test_configure.py new file mode 100644 index 0000000000..b0c5e2344a --- /dev/null +++ b/product_configurator_custom_value_variant/tests/test_configure.py @@ -0,0 +1,92 @@ +# Copyright 2025 Simone Rubino - Aion Tech +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo.tests import Form +from odoo.tools.safe_eval import safe_eval + +from odoo.addons.base.tests.common import BaseCommon + + +class TestConfigure(BaseCommon): + @classmethod + def setUpClass(cls): + super().setUpClass() + # The product attribute view only shows configuration fields + # (such as `val_custom`) + # when called with a specific context + # that is set by this action + configuration_attributes_action = cls.env.ref( + "product_configurator.action_attributes_view" + ) + action_eval_context = configuration_attributes_action._get_eval_context() + configuration_attribute_context = safe_eval( + configuration_attributes_action.context, globals_dict=action_eval_context + ) + configuration_attribute_model = cls.env["product.attribute"].with_context( + **configuration_attribute_context + ) + + length_attribute_form = Form(configuration_attribute_model) + length_attribute_form.name = "Length" + with length_attribute_form.value_ids.new() as value: + value.name = "5" + with length_attribute_form.value_ids.new() as value: + value.name = "10" + length_attribute_form.val_custom = True + length_attribute_form.create_attribute_value = True + cls.length_attribute = length_attribute_form.save() + cls.custom_attribute_value = cls.env.ref( + "product_configurator.custom_attribute_value" + ) + + glass_product_template_form = Form(cls.env["product.template"]) + glass_product_template_form.name = "Glass" + with glass_product_template_form.attribute_line_ids.new() as length_attribute_line: + length_attribute_line.attribute_id = cls.length_attribute + for value in cls.length_attribute.value_ids: + length_attribute_line.value_ids.add(value) + glass_product_template_form.config_ok = True + cls.glass_product_template = glass_product_template_form.save() + + def test_custom_value_creates_value(self): + """If the custom attribute is "Create attribute value", + when used a new variant will be created, linked to a new attribute value. + """ + # Arrange + product_template = self.glass_product_template + product_variants = product_template.product_variant_ids + custom_attribute = self.length_attribute + custom_attribute_value = self.custom_attribute_value + custom_value = "15" + # pre-condition + self.assertTrue(custom_attribute.create_attribute_value) + + # Act: configure the product + wizard_action = product_template.configure_product() + wizard = self.env[wizard_action["res_model"]].browse(wizard_action["res_id"]) + self.assertEqual(wizard.state, "select") + wizard.action_next_step() + self.assertEqual(wizard.state, "configure") + fields_prefixes = wizard._prefixes + field_prefix = fields_prefixes.get("field_prefix") + custom_field_prefix = fields_prefixes.get("custom_field_prefix") + wizard.write( + { + field_prefix + str(custom_attribute.id): custom_attribute_value.id, + custom_field_prefix + str(custom_attribute.id): custom_value, + } + ) + wizard.action_config_done() + + # Assert + configured_session = wizard.config_session_id + configured_variant = configured_session.product_id + self.assertNotIn(configured_variant, product_variants) + self.assertIn(configured_variant, product_template.product_variant_ids) + + new_attribute_value = configured_session.value_ids + self.assertFalse(configured_session.custom_value_ids) + self.assertIn(new_attribute_value, configured_session.value_ids) + self.assertNotIn( + new_attribute_value, configured_variant.attribute_line_ids.value_ids + ) diff --git a/product_configurator_custom_value_variant/views/product_attribute_views.xml b/product_configurator_custom_value_variant/views/product_attribute_views.xml new file mode 100644 index 0000000000..efe381cad5 --- /dev/null +++ b/product_configurator_custom_value_variant/views/product_attribute_views.xml @@ -0,0 +1,22 @@ + + + + + Add create custom value to product attribute form view + product.attribute + + + + + + + + diff --git a/setup/product_configurator_custom_value_variant/odoo/addons/product_configurator_custom_value_variant b/setup/product_configurator_custom_value_variant/odoo/addons/product_configurator_custom_value_variant new file mode 120000 index 0000000000..8e92182515 --- /dev/null +++ b/setup/product_configurator_custom_value_variant/odoo/addons/product_configurator_custom_value_variant @@ -0,0 +1 @@ +../../../../product_configurator_custom_value_variant \ No newline at end of file diff --git a/setup/product_configurator_custom_value_variant/setup.py b/setup/product_configurator_custom_value_variant/setup.py new file mode 100644 index 0000000000..28c57bb640 --- /dev/null +++ b/setup/product_configurator_custom_value_variant/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +) From ec8cdd35b99acabe2ce202369dacf0265411e8f0 Mon Sep 17 00:00:00 2001 From: Simone Rubino Date: Thu, 9 Jan 2025 16:06:04 +0100 Subject: [PATCH 2/2] [DON'T MERGE] test-requirements.txt --- test-requirements.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 test-requirements.txt diff --git a/test-requirements.txt b/test-requirements.txt new file mode 100644 index 0000000000..45d1adfb25 --- /dev/null +++ b/test-requirements.txt @@ -0,0 +1,2 @@ +# https://github.com/OCA/product-attribute/pull/1839 +odoo-addon-product_attribute_custom_value_variant @ git+https://github.com/OCA/product-attribute.git@refs/pull/1839/head#subdirectory=setup/product_attribute_custom_value_variant