diff --git a/product_attribute_value_dependent_mixin/README.rst b/product_attribute_value_dependent_mixin/README.rst new file mode 100644 index 00000000000..65815d6ff4d --- /dev/null +++ b/product_attribute_value_dependent_mixin/README.rst @@ -0,0 +1,83 @@ +======================================= +Product Attribute Value Dependent Mixin +======================================= + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:ba5d1efad0e1d29166513c1a23b430a0477e8fc7f44fe2afe6dfea4f6718ea82 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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--attribute-lightgray.png?logo=github + :target: https://github.com/OCA/product-attribute/tree/16.0/product_attribute_value_dependent_mixin + :alt: OCA/product-attribute +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/product-attribute-16-0/product-attribute-16-0-product_attribute_value_dependent_mixin + :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-attribute&target_branch=16.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This technical module introduces a reusable mixin designed to enable any model to establish dependencies on specific product attribute values. +By inheriting from this mixin, developers can easily link business rules, configurations, or records to precise product variants without duplicating complex filtering logic. + +- Automatically computes available products and attribute values based on the selected product.template. +- Supports domain construction to filter attribute values based on context. +- Matching Logic: Includes a is_matching_product(product) method to validate whether a specific product variant satisfies the configured attribute constraints. + +**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 +~~~~~~~ + +* Akretion + +Contributors +~~~~~~~~~~~~ + +* `Akretion `_ + + * Chafique Delli + +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-attribute `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/product_attribute_value_dependent_mixin/__init__.py b/product_attribute_value_dependent_mixin/__init__.py new file mode 100644 index 00000000000..83e553ac462 --- /dev/null +++ b/product_attribute_value_dependent_mixin/__init__.py @@ -0,0 +1,3 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from . import models diff --git a/product_attribute_value_dependent_mixin/__manifest__.py b/product_attribute_value_dependent_mixin/__manifest__.py new file mode 100644 index 00000000000..643dba70339 --- /dev/null +++ b/product_attribute_value_dependent_mixin/__manifest__.py @@ -0,0 +1,16 @@ +# Copyright 2023 Akretion +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +{ + "name": "Product Attribute Value Dependent Mixin", + "summary": "Mixin to make product attribute values fields on models", + "author": "Akretion,Odoo Community Association (OCA)", + "website": "https://github.com/OCA/product-attribute", + "license": "AGPL-3", + "application": False, + "installable": True, + "category": "Product", + "version": "16.0.1.0.0", + "depends": ["product"], + "data": [], +} diff --git a/product_attribute_value_dependent_mixin/models/__init__.py b/product_attribute_value_dependent_mixin/models/__init__.py new file mode 100644 index 00000000000..781a514fa88 --- /dev/null +++ b/product_attribute_value_dependent_mixin/models/__init__.py @@ -0,0 +1,2 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from . import product_attribute_value_dependent_mixin diff --git a/product_attribute_value_dependent_mixin/models/product_attribute_value_dependent_mixin.py b/product_attribute_value_dependent_mixin/models/product_attribute_value_dependent_mixin.py new file mode 100644 index 00000000000..be93bcbd145 --- /dev/null +++ b/product_attribute_value_dependent_mixin/models/product_attribute_value_dependent_mixin.py @@ -0,0 +1,91 @@ +# Copyright 2023-2026 Akretion +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import itertools +import json + +from odoo import api, fields, models + + +class AttributeValueDependantMixin(models.AbstractModel): + _name = "attribute.value.dependant.mixin" + _description = "Attribute Value Dependant Mixin" + + product_tmpl_id = fields.Many2one( + comodel_name="product.template", + string="Product Template", + ) + product_id = fields.Many2one( + comodel_name="product.product", + string="Product", + domain="[('id', 'in', available_product_ids)]", + ) + available_product_ids = fields.Many2many( + comodel_name="product.product", + string="Available Products", + compute="_compute_available_product_ids", + ) + + attribute_value_ids = fields.Many2many( + comodel_name="product.attribute.value", + string="Attribute Values", + ) + + available_attribute_value_domain = fields.Char( + compute="_compute_available_attribute_value_domain", + ) + + @api.depends("product_tmpl_id") + def _compute_available_product_ids(self): + for rec in self: + rec.available_product_ids = self.env["product.product"].search( + [("product_tmpl_id", "=", self.product_tmpl_id._origin.id)] + ) + + @api.depends( + "product_tmpl_id", + "product_tmpl_id.attribute_line_ids.value_ids", + "available_attribute_value_domain", + ) + def _compute_available_attribute_value_ids(self): + for rec in self: + if rec.product_tmpl_id: + rec.available_attribute_value_ids = rec.product_tmpl_id.mapped( + "attribute_line_ids.value_ids" + ).filtered_domain(json.loads(rec.available_attribute_value_domain)) + else: + rec.available_attribute_value_ids = None + + @api.depends("product_tmpl_id", "product_tmpl_id.attribute_line_ids.value_ids") + def _compute_available_attribute_value_domain(self): + for rec in self: + domain = [] + if rec.product_tmpl_id: + domain = [ + ("id", "in", rec.product_tmpl_id.attribute_line_ids.value_ids.ids) + ] + rec.available_attribute_value_domain = json.dumps(domain) + + def is_matching_product(self, product): + self.ensure_one() + if self.product_tmpl_id != product.product_tmpl_id: + return False + elif self.product_id: + if self.product_id == product: + return True + else: + return False + elif self.attribute_value_ids: + ptav = product.product_template_attribute_value_ids + attr2vals = { + attribute: set(values) + for attribute, values in itertools.groupby( + self.attribute_value_ids, lambda pav: pav.attribute_id + ) + } + for attribute in attr2vals: + if attribute not in ptav.attribute_id: + return False + elif not attr2vals[attribute] & set(ptav.product_attribute_value_id): + return False + return True diff --git a/product_attribute_value_dependent_mixin/readme/CONTRIBUTORS.rst b/product_attribute_value_dependent_mixin/readme/CONTRIBUTORS.rst new file mode 100644 index 00000000000..4d6f88298d7 --- /dev/null +++ b/product_attribute_value_dependent_mixin/readme/CONTRIBUTORS.rst @@ -0,0 +1,3 @@ +* `Akretion `_ + + * Chafique Delli diff --git a/product_attribute_value_dependent_mixin/readme/DESCRIPTION.rst b/product_attribute_value_dependent_mixin/readme/DESCRIPTION.rst new file mode 100644 index 00000000000..ef501c94724 --- /dev/null +++ b/product_attribute_value_dependent_mixin/readme/DESCRIPTION.rst @@ -0,0 +1,6 @@ +This technical module introduces a reusable mixin designed to enable any model to establish dependencies on specific product attribute values. +By inheriting from this mixin, developers can easily link business rules, configurations, or records to precise product variants without duplicating complex filtering logic. + +- Automatically computes available products and attribute values based on the selected product.template. +- Supports domain construction to filter attribute values based on context. +- Matching Logic: Includes a is_matching_product(product) method to validate whether a specific product variant satisfies the configured attribute constraints. diff --git a/product_attribute_value_dependent_mixin/static/description/icon.png b/product_attribute_value_dependent_mixin/static/description/icon.png new file mode 100644 index 00000000000..3a0328b516c Binary files /dev/null and b/product_attribute_value_dependent_mixin/static/description/icon.png differ diff --git a/product_attribute_value_dependent_mixin/static/description/index.html b/product_attribute_value_dependent_mixin/static/description/index.html new file mode 100644 index 00000000000..6f4738d7e33 --- /dev/null +++ b/product_attribute_value_dependent_mixin/static/description/index.html @@ -0,0 +1,432 @@ + + + + + +Product Attribute Value Dependent Mixin + + + +
+

Product Attribute Value Dependent Mixin

+ + +

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

+

This technical module introduces a reusable mixin designed to enable any model to establish dependencies on specific product attribute values. +By inheriting from this mixin, developers can easily link business rules, configurations, or records to precise product variants without duplicating complex filtering logic.

+
    +
  • Automatically computes available products and attribute values based on the selected product.template.
  • +
  • Supports domain construction to filter attribute values based on context.
  • +
  • Matching Logic: Includes a is_matching_product(product) method to validate whether a specific product variant satisfies the configured attribute constraints.
  • +
+

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

+
    +
  • Akretion
  • +
+
+
+

Contributors

+ +
+
+

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-attribute project on GitHub.

+

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

+
+
+
+ + diff --git a/product_attribute_value_dependent_mixin/tests/__init__.py b/product_attribute_value_dependent_mixin/tests/__init__.py new file mode 100644 index 00000000000..f000bbc7f7b --- /dev/null +++ b/product_attribute_value_dependent_mixin/tests/__init__.py @@ -0,0 +1 @@ +from . import test_product_attribute_value_dependent_mixin diff --git a/product_attribute_value_dependent_mixin/tests/models.py b/product_attribute_value_dependent_mixin/tests/models.py new file mode 100644 index 00000000000..f70e7621a61 --- /dev/null +++ b/product_attribute_value_dependent_mixin/tests/models.py @@ -0,0 +1,12 @@ +# Copyright 2023 Akretion +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import fields, models + + +class ProductSupplierinfoFake(models.Model): + _name = "product.supplierinfo.fake" + _inherit = ["product.supplierinfo", "attribute.value.dependant.mixin"] + _description = "Product supplierinfo fake model for tests" + + name = fields.Char() diff --git a/product_attribute_value_dependent_mixin/tests/test_product_attribute_value_dependent_mixin.py b/product_attribute_value_dependent_mixin/tests/test_product_attribute_value_dependent_mixin.py new file mode 100644 index 00000000000..315948300ea --- /dev/null +++ b/product_attribute_value_dependent_mixin/tests/test_product_attribute_value_dependent_mixin.py @@ -0,0 +1,40 @@ +# Copyright 2023 Akretion +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo_test_helper import FakeModelLoader + +from odoo.tests import TransactionCase + + +class TestProductAttributeValueDependentMixin(TransactionCase, FakeModelLoader): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.loader = FakeModelLoader(cls.env, cls.__module__) + cls.loader.backup_registry() + from .models import ProductSupplierinfoFake + + cls.loader.update_registry((ProductSupplierinfoFake,)) + + # Fake model which inherit from + cls.product_supplierinfo_fake = cls.env["product.supplierinfo.fake"].create( + { + "partner_id": cls.env.ref("base.res_partner_1").id, + "product_tmpl_id": cls.env.ref( + "product.product_product_4_product_template" + ).id, + "price": 100.00, + "currency_id": cls.env.ref("base.USD").id, + "min_qty": 1.0, + "delay": 1.0, + } + ) + + @classmethod + def tearDownClass(cls): + cls.loader.restore_registry() + super(TestProductAttributeValueDependentMixin, cls).tearDownClass() + + def test_product_attribute_value_dependent_mixin(self): + fake_model = self.product_supplierinfo_fake + self.assertTrue(fake_model.available_attribute_value_domain) diff --git a/setup/product_attribute_value_dependent_mixin/odoo/addons/product_attribute_value_dependent_mixin b/setup/product_attribute_value_dependent_mixin/odoo/addons/product_attribute_value_dependent_mixin new file mode 120000 index 00000000000..e460190373d --- /dev/null +++ b/setup/product_attribute_value_dependent_mixin/odoo/addons/product_attribute_value_dependent_mixin @@ -0,0 +1 @@ +../../../../product_attribute_value_dependent_mixin \ No newline at end of file diff --git a/setup/product_attribute_value_dependent_mixin/setup.py b/setup/product_attribute_value_dependent_mixin/setup.py new file mode 100644 index 00000000000..28c57bb6403 --- /dev/null +++ b/setup/product_attribute_value_dependent_mixin/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +)