diff --git a/product_assortment/README.rst b/product_assortment/README.rst index e59527f5e7c..5c8ab343727 100644 --- a/product_assortment/README.rst +++ b/product_assortment/README.rst @@ -1,7 +1,3 @@ -.. image:: https://odoo-community.org/readme-banner-image - :target: https://odoo-community.org/get-involved?utm_source=readme - :alt: Odoo Community Association - ================== Product Assortment ================== @@ -17,7 +13,7 @@ Product Assortment .. |badge1| image:: https://img.shields.io/badge/maturity-Production%2FStable-green.png :target: https://odoo-community.org/page/development-status :alt: Production/Stable -.. |badge2| image:: https://img.shields.io/badge/license-AGPL--3-blue.png +.. |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 @@ -49,7 +45,7 @@ Usage To use this module, you need to: -1. Enter the menu through Product Assortment Icon +1. Enter the menu through the Product Assortment Icon 2. Create a new filter where you can define your domain and add allowed and restricted products @@ -59,22 +55,36 @@ Changelog 10.0.1.0.0 (2018-08-27) ----------------------- -- [10.0][ADD] productassortment +- [10.0][ADD] productassortment 12.0.1.0.0 (2019-06-03) ----------------------- -- [12.0][MIG] productassortment +- [12.0][MIG] productassortment 14.0.1.0.0 (2019-06-03) ----------------------- -- [14.0][MIG] productassortment +- [14.0][MIG] productassortment 16.0.1.0.0 (2022-09-15) ----------------------- -- [16.0][MIG] productassortment +- [16.0][MIG] product_assortment + +18.0.1.0.0 (2025-03-06) +----------------------- + +- [18.0][MIG] product_assortment +- Forward port demo data +- Forward port Only Show assortments to managers +- Forward port Fix All assortments are applied to original partner when + partner is duplicated +- Adjust test code to new API behavior, for info: odoo/odoo@450f5c9 +- added test for combined black list and whitelisted product +- Fix: Navigating to the product assortment using the smartbutton on + the partner does not show all applicable assortments. (The + assortments with the partner defined as a domain where missing.) Bug Tracker =========== @@ -97,13 +107,13 @@ Authors Contributors ------------ -- Denis Roussel -- Cédric Pigeon -- Xavier Bouquiaux -- `Tecnativa `__: +- Denis Roussel +- Cédric Pigeon +- Xavier Bouquiaux +- `Tecnativa `__: - - Carlos Roca - - Sergio Teruel + - Carlos Roca + - Sergio Teruel Maintainers ----------- diff --git a/product_assortment/__manifest__.py b/product_assortment/__manifest__.py index 8cd58360da8..5c8746e197d 100644 --- a/product_assortment/__manifest__.py +++ b/product_assortment/__manifest__.py @@ -18,5 +18,6 @@ "views/product_assortment.xml", "views/res_partner_view.xml", ], + "demo": ["demo/assortments.xml"], "installable": True, } diff --git a/product_assortment/demo/assortments.xml b/product_assortment/demo/assortments.xml new file mode 100644 index 00000000000..f1f02a14419 --- /dev/null +++ b/product_assortment/demo/assortments.xml @@ -0,0 +1,40 @@ + + + + + + product.product + + Assortment Desk + + ["|","|",("default_code","ilike","desk"),("name","ilike","desk"),("barcode","ilike","desk")] + + + + product.product + + Assortment Chair + + ["|","|",("default_code","ilike","chair"),("name","ilike","chair"),("barcode","ilike","chair")] + + + + product.product + + Assortment Service + + + + + diff --git a/product_assortment/i18n/ca.po b/product_assortment/i18n/ca.po index acc0fc5d49a..375f25f8d9a 100644 --- a/product_assortment/i18n/ca.po +++ b/product_assortment/i18n/ca.po @@ -117,7 +117,7 @@ msgid "Partners to apply" msgstr "Socis a aplicar" #. module: product_assortment -#: model_terms:ir.ui.view,arch_db:product_assortment.product_product_view_tree +#: model_terms:ir.ui.view,arch_db:product_assortment.product_product_view_list msgid "Product" msgstr "Producte" diff --git a/product_assortment/i18n/de.po b/product_assortment/i18n/de.po index d2680cdcdba..4cdb4bd86df 100644 --- a/product_assortment/i18n/de.po +++ b/product_assortment/i18n/de.po @@ -124,7 +124,7 @@ msgid "Partners to apply" msgstr "" #. module: product_assortment -#: model_terms:ir.ui.view,arch_db:product_assortment.product_product_view_tree +#: model_terms:ir.ui.view,arch_db:product_assortment.product_product_view_list msgid "Product" msgstr "Produkt" diff --git a/product_assortment/i18n/fr.po b/product_assortment/i18n/fr.po index ebd062220a0..7d3686c522e 100644 --- a/product_assortment/i18n/fr.po +++ b/product_assortment/i18n/fr.po @@ -123,7 +123,7 @@ msgid "Partners to apply" msgstr "Appliquer aux partenaires" #. module: product_assortment -#: model_terms:ir.ui.view,arch_db:product_assortment.product_product_view_tree +#: model_terms:ir.ui.view,arch_db:product_assortment.product_product_view_list msgid "Product" msgstr "Produit" diff --git a/product_assortment/i18n/it.po b/product_assortment/i18n/it.po index ab71abd1bad..5af4acf6e87 100644 --- a/product_assortment/i18n/it.po +++ b/product_assortment/i18n/it.po @@ -124,7 +124,7 @@ msgid "Partners to apply" msgstr "Partner da applicare" #. module: product_assortment -#: model_terms:ir.ui.view,arch_db:product_assortment.product_product_view_tree +#: model_terms:ir.ui.view,arch_db:product_assortment.product_product_view_list msgid "Product" msgstr "Prodotto" diff --git a/product_assortment/i18n/nl.po b/product_assortment/i18n/nl.po index 05eb306e293..a82eed0ea15 100644 --- a/product_assortment/i18n/nl.po +++ b/product_assortment/i18n/nl.po @@ -123,7 +123,7 @@ msgid "Partners to apply" msgstr "Partners om toe te passen" #. module: product_assortment -#: model_terms:ir.ui.view,arch_db:product_assortment.product_product_view_tree +#: model_terms:ir.ui.view,arch_db:product_assortment.product_product_view_list msgid "Product" msgstr "Product" diff --git a/product_assortment/models/ir_filters.py b/product_assortment/models/ir_filters.py index 99766efb358..1c6e909a6f4 100644 --- a/product_assortment/models/ir_filters.py +++ b/product_assortment/models/ir_filters.py @@ -2,7 +2,7 @@ # Copyright 2023 Tecnativa - Carlos Dauden # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from odoo import _, api, fields, models +from odoo import api, fields, models from odoo.osv import expression from odoo.tools import ormcache from odoo.tools.safe_eval import datetime, safe_eval @@ -38,6 +38,7 @@ class IrFilters(models.Model): compute="_compute_all_partner_ids", # Make it store=True because we will need this field to search by involved # partners + compute_sudo=True, store=True, relation="ir_filter_all_partner_rel", column1="filter_id", @@ -50,11 +51,49 @@ class IrFilters(models.Model): string="Restricted product domain", default="[]", required=True ) + @api.model + def _search(self, args, offset=0, limit=None, order=None, count=False): + # Check if user is NOT a manager and NOT a superuser + if not self.env.is_superuser() and not self.env.user.has_group( + "product_assortment.group_product_assortment_manager" + ): + # Inject the domain directly into the search arguments + args = expression.AND([args, [("is_assortment", "=", False)]]) + + # Call the parent _search method without the count parameter + result = super()._search(args, offset=offset, limit=limit, order=order) + + # If count is requested, return the count of the result + if count: + # The result of _search is typically a list of IDs + if isinstance(result, list): + return len(result) + # If result is not a list, it might be a query object, + # so we need to handle differently + # In most cases, we should return the length of the result + try: + return len(result) + except TypeError: + # If we can't get the length, fall back to calling search_count + # with the modified args + return self.search_count(args) + + return result + + @api.model + def search_count(self, args): + # Apply the same access control as in _search + if not self.env.is_superuser() and not self.env.user.has_group( + "product_assortment.group_product_assortment_manager" + ): + # Inject the domain directly into the search arguments + args = expression.AND([args, [("is_assortment", "=", False)]]) + + return super().search_count(args) + @api.model def _get_default_is_assortment(self): - if self.env.context.get("product_assortment", False): - return True - return False + return self.env.context.get("product_assortment", False) @api.model def _update_assortment_default_values(self, vals_list): @@ -93,10 +132,11 @@ def _compute_all_partner_ids(self): for ir_filter in self: if not ir_filter.is_assortment: ir_filter.all_partner_ids = False - elif ir_filter.partner_domain != "[]": + continue + if ir_filter.partner_domain and ir_filter.partner_domain != []: + domain = ir_filter._get_eval_partner_domain() ir_filter.all_partner_ids = ( - self.env["res.partner"].search(ir_filter._get_eval_partner_domain()) - + ir_filter.partner_ids + self.env["res.partner"].search(domain) + ir_filter.partner_ids ) else: ir_filter.all_partner_ids = ir_filter.partner_ids @@ -169,7 +209,7 @@ def show_products(self): action.update( { "domain": self._get_eval_domain(), - "name": _("Products"), + "name": self.env._("Products"), "context": self.env.context, "target": "current", } diff --git a/product_assortment/models/res_partner.py b/product_assortment/models/res_partner.py index dd6721400cd..812011ddd2a 100644 --- a/product_assortment/models/res_partner.py +++ b/product_assortment/models/res_partner.py @@ -12,6 +12,7 @@ class ResPartner(models.Model): relation="ir_filter_all_partner_rel", column1="partner_id", column2="filter_id", + copy=False, ) def action_define_product_assortment(self): @@ -19,7 +20,7 @@ def action_define_product_assortment(self): xmlid = "product_assortment.actions_product_assortment_view" action = self.env["ir.actions.act_window"]._for_xml_id(xmlid) action["domain"] = [ - ("partner_ids", "in", self.ids), + ("all_partner_ids", "in", self.ids), ("is_assortment", "=", True), ] ctx = self.env.context.copy() @@ -41,9 +42,7 @@ def _update_partner_assortments(self): # Use ids instead of record to improve performance (Remove in next versions) partner_assortment_ids = [] for assortment in assortments: - if partner in assortment.partner_ids or partner.filtered_domain( - assortment._get_eval_partner_domain() - ): + if partner in assortment.all_partner_ids: partner_assortment_ids.append(assortment.id) partner.applied_assortment_ids = assortments.browse(partner_assortment_ids) diff --git a/product_assortment/readme/HISTORY.md b/product_assortment/readme/HISTORY.md index 0cc1722371d..589bbea5c99 100644 --- a/product_assortment/readme/HISTORY.md +++ b/product_assortment/readme/HISTORY.md @@ -12,4 +12,16 @@ ## 16.0.1.0.0 (2022-09-15) -- \[16.0\]\[MIG\] productassortment +- \[16.0\]\[MIG\] product_assortment + +## 18.0.1.0.0 (2025-03-06) + +- \[18.0\]\[MIG\] product_assortment +- Forward port demo data +- Forward port Only Show assortments to managers +- Forward port Fix All assortments are applied to original partner when partner is duplicated +- Adjust test code to new API behavior, for info: odoo/odoo@450f5c9 +- added test for combined black list and whitelisted product +- Fix: Navigating to the product assortment using the smartbutton on the partner does not show all applicable assortments. + (The assortments with the partner defined as a domain where missing.) + diff --git a/product_assortment/readme/USAGE.md b/product_assortment/readme/USAGE.md index dbc0b08586f..dc83a2ad52c 100644 --- a/product_assortment/readme/USAGE.md +++ b/product_assortment/readme/USAGE.md @@ -1,5 +1,5 @@ To use this module, you need to: -1. Enter the menu through Product Assortment Icon +1. Enter the menu through the Product Assortment Icon 2. Create a new filter where you can define your domain and add allowed and restricted products diff --git a/product_assortment/static/description/index.html b/product_assortment/static/description/index.html index f1fef7568d3..ce178c6db58 100644 --- a/product_assortment/static/description/index.html +++ b/product_assortment/static/description/index.html @@ -3,7 +3,7 @@ -README.rst +Product Assortment -
+
+

Product Assortment

- - -Odoo Community Association - -
-

Product Assortment

-

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

+

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

This addon intends to manage product assortment. In Odoo you can only define some filters defined by a domain but it can be sometimes really complicated. With this addon you will be able to define a domain but @@ -390,55 +385,71 @@

Product Assortment

  • 12.0.1.0.0 (2019-06-03)
  • 14.0.1.0.0 (2019-06-03)
  • 16.0.1.0.0 (2022-09-15)
  • +
  • 18.0.1.0.0 (2025-03-06)
  • -
  • Bug Tracker
  • -
  • Credits
  • -

    Usage

    +

    Usage

    To use this module, you need to:

      -
    1. Enter the menu through Product Assortment Icon
    2. +
    3. Enter the menu through the Product Assortment Icon
    4. Create a new filter where you can define your domain and add allowed and restricted products
    -

    Changelog

    +

    Changelog

    -

    10.0.1.0.0 (2018-08-27)

    +

    10.0.1.0.0 (2018-08-27)

    • [10.0][ADD] productassortment
    -

    12.0.1.0.0 (2019-06-03)

    +

    12.0.1.0.0 (2019-06-03)

    • [12.0][MIG] productassortment
    -

    14.0.1.0.0 (2019-06-03)

    +

    14.0.1.0.0 (2019-06-03)

    • [14.0][MIG] productassortment
    -

    16.0.1.0.0 (2022-09-15)

    +

    16.0.1.0.0 (2022-09-15)

    +
      +
    • [16.0][MIG] product_assortment
    • +
    +
    +
    +

    18.0.1.0.0 (2025-03-06)

      -
    • [16.0][MIG] productassortment
    • +
    • [18.0][MIG] product_assortment
    • +
    • Forward port demo data
    • +
    • Forward port Only Show assortments to managers
    • +
    • Forward port Fix All assortments are applied to original partner when +partner is duplicated
    • +
    • Adjust test code to new API behavior, for info: odoo/odoo@450f5c9
    • +
    • added test for combined black list and whitelisted product
    • +
    • Fix: Navigating to the product assortment using the smartbutton on +the partner does not show all applicable assortments. (The +assortments with the partner defined as a domain where missing.)
    -

    Bug Tracker

    +

    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 @@ -446,15 +457,15 @@

    Bug Tracker

    Do not contact contributors directly about support or help with technical issues.

    -

    Credits

    +

    Credits

    -

    Authors

    +

    Authors

    • ACSONE SA/NV
    -

    Contributors

    +

    Contributors

    -

    Maintainers

    +

    Maintainers

    This module is maintained by the OCA.

    Odoo Community Association @@ -480,6 +491,5 @@

    Maintainers

    -
    diff --git a/product_assortment/tests/test_product_assortment.py b/product_assortment/tests/test_product_assortment.py index 083f400fdca..367ad9a89dd 100644 --- a/product_assortment/tests/test_product_assortment.py +++ b/product_assortment/tests/test_product_assortment.py @@ -84,7 +84,10 @@ def test_search_assortment_with_partner(self): search_domain = self.partner.action_define_product_assortment()["domain"] self.assertEqual( search_domain, - [("partner_ids", "in", [self.partner.id]), ("is_assortment", "=", True)], + [ + ("all_partner_ids", "in", [self.partner.id]), + ("is_assortment", "=", True), + ], ) def test_product_assortment_view(self): @@ -115,6 +118,21 @@ def test_product_assortment_mixed_view(self): res = self.assortment.show_products() self.assertEqual(res["domain"], [("id", "not in", excluded_product.ids)]) + def test_product_assortment_filter_combination(self): + """Combine a whitelisted and a blacklisted product in order + to validate the combination of both filters. The result should be a + simple domain with the excluded product. + """ + # Add a default no product filter to the assortment + self.assortment.write({"domain": [("id", "=", 0)]}) + included_product = self.env.ref("product.product_product_7") + self.assortment.write({"whitelist_product_ids": [(4, included_product.id)]}) + excluded_product = self.env.ref("product.product_product_2") + self.assortment.write({"blacklist_product_ids": [(4, excluded_product.id)]}) + res = self.assortment.show_products() + self.assertIn(("id", "not in", [excluded_product.id]), res["domain"]) + self.assertIn(("id", "in", [included_product.id]), res["domain"]) + def test_record_count(self): products = self.product_obj.search([]) self.assertEqual(self.assortment.record_count, len(products))