diff --git a/base_product_merge/README.rst b/base_product_merge/README.rst new file mode 100644 index 000000000..d4fa446f6 --- /dev/null +++ b/base_product_merge/README.rst @@ -0,0 +1,91 @@ +=================== +Base Products Merge +=================== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:b7b89aa2d12bfa897bc0a6587324fb6a013b3410c47e7e2733779da135813019 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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%2Fstock--logistics--warehouse-lightgray.png?logo=github + :target: https://github.com/OCA/stock-logistics-warehouse/tree/16.0/base_product_merge + :alt: OCA/stock-logistics-warehouse +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/stock-logistics-warehouse-16-0/stock-logistics-warehouse-16-0-base_product_merge + :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/stock-logistics-warehouse&target_branch=16.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +We can merge duplicates products into single product + +**Table of contents** + +.. contents:: + :local: + +Usage +===== + +Select products or templates then click on Merge products in Action menu. +Select Destination product in which need to merge all other products +Then click on Merge Product, it will merge all the products. + +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 +~~~~~~~ + +* ForgeFlow + +Contributors +~~~~~~~~~~~~ + +* Jasmin Solanki + +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. + +.. |maintainer-JasminSForgeFlow| image:: https://github.com/JasminSForgeFlow.png?size=40px + :target: https://github.com/JasminSForgeFlow + :alt: JasminSForgeFlow + +Current `maintainer `__: + +|maintainer-JasminSForgeFlow| + +This module is part of the `OCA/stock-logistics-warehouse `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/base_product_merge/__init__.py b/base_product_merge/__init__.py new file mode 100644 index 000000000..c0bc62fe0 --- /dev/null +++ b/base_product_merge/__init__.py @@ -0,0 +1,3 @@ +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +from . import wizard diff --git a/base_product_merge/__manifest__.py b/base_product_merge/__manifest__.py new file mode 100644 index 000000000..50e78e415 --- /dev/null +++ b/base_product_merge/__manifest__.py @@ -0,0 +1,23 @@ +# Copyright 2023 ForgeFlow S.L. (https://www.forgeflow.com) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +{ + "name": "Base Products Merge", + "summary": "Merge duplicate products", + "version": "16.0.1.0.0", + "license": "AGPL-3", + "website": "https://github.com/OCA/stock-logistics-warehouse", + "author": "ForgeFlow, " "Odoo Community Association (OCA)", + "category": "Sales/Sales", + "depends": ["product"], + "data": [ + "security/res_groups.xml", + "security/ir.model.access.csv", + "wizard/base_product_merge_view.xml", + ], + "installable": True, + "external_dependencies": { + "python": ["openupgradelib"], + }, + "maintainers": ["JasminSForgeFlow"], +} diff --git a/base_product_merge/i18n/base_product_merge.pot b/base_product_merge/i18n/base_product_merge.pot new file mode 100644 index 000000000..7754743b5 --- /dev/null +++ b/base_product_merge/i18n/base_product_merge.pot @@ -0,0 +1,135 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * base_product_merge +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 15.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: base_product_merge +#: model_terms:ir.ui.view,arch_db:base_product_merge.view_base_product_merge_form +msgid "Cancel" +msgstr "" + +#. module: base_product_merge +#: model:ir.model.fields,field_description:base_product_merge.field_base_product_merge__create_uid +msgid "Created by" +msgstr "" + +#. module: base_product_merge +#: model:ir.model.fields,field_description:base_product_merge.field_base_product_merge__create_date +msgid "Created on" +msgstr "" + +#. module: base_product_merge +#: model:ir.model.fields,field_description:base_product_merge.field_base_product_merge__dst_product_id +#: model:ir.model.fields,field_description:base_product_merge.field_base_product_merge__dst_product_tmpl_id +msgid "Destination product" +msgstr "" + +#. module: base_product_merge +#: model:ir.model.fields,field_description:base_product_merge.field_base_product_merge__display_name +msgid "Display Name" +msgstr "" + +#. module: base_product_merge +#: code:addons/base_product_merge/wizard/base_product_merge.py:0 +#, python-format +msgid "Error occurred while merging products." +msgstr "" + +#. module: base_product_merge +#: model:ir.model.fields,field_description:base_product_merge.field_base_product_merge__id +msgid "ID" +msgstr "" + +#. module: base_product_merge +#: model:ir.model.fields,field_description:base_product_merge.field_base_product_merge____last_update +msgid "Last Modified on" +msgstr "" + +#. module: base_product_merge +#: model:ir.model.fields,field_description:base_product_merge.field_base_product_merge__write_uid +msgid "Last Updated by" +msgstr "" + +#. module: base_product_merge +#: model:ir.model.fields,field_description:base_product_merge.field_base_product_merge__write_date +msgid "Last Updated on" +msgstr "" + +#. module: base_product_merge +#: model:ir.model.fields,field_description:base_product_merge.field_base_product_merge__merge_method +msgid "Merge Method" +msgstr "" + +#. module: base_product_merge +#: model:ir.actions.act_window,name:base_product_merge.action_product_merge +#: model:ir.actions.act_window,name:base_product_merge.action_product_template_merge +#: model_terms:ir.ui.view,arch_db:base_product_merge.view_base_product_merge_form +msgid "Merge Products" +msgstr "" + +#. module: base_product_merge +#: model:res.groups,name:base_product_merge.res_group_merge_duplicate_product +msgid "Merge duplicate products" +msgstr "" + +#. module: base_product_merge +#: model:ir.model,name:base_product_merge.model_base_product_merge +msgid "Merges two products" +msgstr "" + +#. module: base_product_merge +#: model:ir.model.fields.selection,name:base_product_merge.selection__base_product_merge__merge_method__orm +msgid "ORM" +msgstr "" + +#. module: base_product_merge +#: model:ir.model.fields.selection,name:base_product_merge.selection__base_product_merge__ptype__product_product +msgid "Product" +msgstr "" + +#. module: base_product_merge +#: model:ir.model.fields,field_description:base_product_merge.field_base_product_merge__product_ids +#: model:ir.model.fields,field_description:base_product_merge.field_base_product_merge__product_tmpl_ids +msgid "Products to merge" +msgstr "" + +#. module: base_product_merge +#: model:ir.model.fields,field_description:base_product_merge.field_base_product_merge__ptype +msgid "Ptype" +msgstr "" + +#. module: base_product_merge +#: model:ir.model.fields.selection,name:base_product_merge.selection__base_product_merge__merge_method__sql +msgid "SQL" +msgstr "" + +#. module: base_product_merge +#: model:ir.model.fields.selection,name:base_product_merge.selection__base_product_merge__ptype__product_template +msgid "Template" +msgstr "" + +#. module: base_product_merge +#: model_terms:ir.ui.view,arch_db:base_product_merge.view_base_product_merge_form +msgid "" +"The selected products will be merged together. All\n" +" documents linking to one of these products will be\n" +" redirected to the aggregated product. You can remove\n" +" products from this list to avoid merging them." +msgstr "" + +#. module: base_product_merge +#: code:addons/base_product_merge/wizard/base_product_merge.py:0 +#: code:addons/base_product_merge/wizard/base_product_merge.py:0 +#, python-format +msgid "You cannot merge product to it self." +msgstr "" diff --git a/base_product_merge/readme/CONFIGURE.rst b/base_product_merge/readme/CONFIGURE.rst new file mode 100644 index 000000000..e69de29bb diff --git a/base_product_merge/readme/CONTRIBUTORS.rst b/base_product_merge/readme/CONTRIBUTORS.rst new file mode 100644 index 000000000..205106e55 --- /dev/null +++ b/base_product_merge/readme/CONTRIBUTORS.rst @@ -0,0 +1 @@ +* Jasmin Solanki diff --git a/base_product_merge/readme/DESCRIPTION.rst b/base_product_merge/readme/DESCRIPTION.rst new file mode 100644 index 000000000..d98c9742c --- /dev/null +++ b/base_product_merge/readme/DESCRIPTION.rst @@ -0,0 +1 @@ +We can merge duplicates products into single product diff --git a/base_product_merge/readme/USAGE.rst b/base_product_merge/readme/USAGE.rst new file mode 100644 index 000000000..e5276c949 --- /dev/null +++ b/base_product_merge/readme/USAGE.rst @@ -0,0 +1,3 @@ +Select products or templates then click on Merge products in Action menu. +Select Destination product in which need to merge all other products +Then click on Merge Product, it will merge all the products. diff --git a/base_product_merge/security/ir.model.access.csv b/base_product_merge/security/ir.model.access.csv new file mode 100644 index 000000000..898a13489 --- /dev/null +++ b/base_product_merge/security/ir.model.access.csv @@ -0,0 +1,2 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +base_product_merge.access_base_product_merge,access_base_product_merge,base_product_merge.model_base_product_merge,base_product_merge.res_group_merge_duplicate_product,1,1,1,1 diff --git a/base_product_merge/security/res_groups.xml b/base_product_merge/security/res_groups.xml new file mode 100644 index 000000000..d1dfc8d2e --- /dev/null +++ b/base_product_merge/security/res_groups.xml @@ -0,0 +1,6 @@ + + + + Merge duplicate products + + diff --git a/base_product_merge/static/description/icon.png b/base_product_merge/static/description/icon.png new file mode 100644 index 000000000..3a0328b51 Binary files /dev/null and b/base_product_merge/static/description/icon.png differ diff --git a/base_product_merge/static/description/index.html b/base_product_merge/static/description/index.html new file mode 100644 index 000000000..601b1c29e --- /dev/null +++ b/base_product_merge/static/description/index.html @@ -0,0 +1,432 @@ + + + + + +Base Products Merge + + + +
+

Base Products Merge

+ + +

Beta License: AGPL-3 OCA/stock-logistics-warehouse Translate me on Weblate Try me on Runboat

+

We can merge duplicates products into single product

+

Table of contents

+ +
+

Usage

+

Select products or templates then click on Merge products in Action menu. +Select Destination product in which need to merge all other products +Then click on Merge Product, it will merge all the products.

+
+
+

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

+
    +
  • ForgeFlow
  • +
+
+
+

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.

+

Current maintainer:

+

JasminSForgeFlow

+

This module is part of the OCA/stock-logistics-warehouse project on GitHub.

+

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

+
+
+
+ + diff --git a/base_product_merge/tests/__init__.py b/base_product_merge/tests/__init__.py new file mode 100644 index 000000000..287c6c789 --- /dev/null +++ b/base_product_merge/tests/__init__.py @@ -0,0 +1,4 @@ +# Copyright 2023 ForgeFlow S.L. (https://www.forgeflow.com) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +from . import test_base_product_merge diff --git a/base_product_merge/tests/test_base_product_merge.py b/base_product_merge/tests/test_base_product_merge.py new file mode 100644 index 000000000..da45fd77b --- /dev/null +++ b/base_product_merge/tests/test_base_product_merge.py @@ -0,0 +1,37 @@ +# Copyright 2023 ForgeFlow S.L. (https://www.forgeflow.com) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +from odoo.tests.common import TransactionCase + + +class TestBaseProductMerge(TransactionCase): + @classmethod + def setUpClass(cls): + super(TestBaseProductMerge, cls).setUpClass() + cls.product_model = cls.env["product.product"] + cls.base_product_merge_model = cls.env["base.product.merge"] + + def test_product_merge(self): + # take current product count + total_products = len(self.product_model.search([])) + # create products + product_1 = self.product_model.create({"name": "test product 1"}) + product_2 = self.product_model.create({"name": "test product 2"}) + product_3 = self.product_model.create({"name": "test product 3"}) + product_4 = self.product_model.create({"name": "test product 4"}) + + # check product count before merge + self.assertEqual(len(self.product_model.search([])), total_products + 4) + + # merge product_2 and product_4 with product_1 + product_merge = self.base_product_merge_model.with_context( + active_ids=[product_1.id, product_2.id, product_4.id], + active_model="product.product", + ).create({}) + product_merge.dst_product_id = product_1 + product_merge.action_merge() + + # check product count before merge + self.assertEqual(len(self.product_model.search([])), total_products + 2) + # check product_3 exists + self.assertTrue(product_3.exists()) diff --git a/base_product_merge/wizard/__init__.py b/base_product_merge/wizard/__init__.py new file mode 100644 index 000000000..06e9996fa --- /dev/null +++ b/base_product_merge/wizard/__init__.py @@ -0,0 +1,4 @@ +# Copyright 2023 ForgeFlow S.L. (https://www.forgeflow.com) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +from . import base_product_merge diff --git a/base_product_merge/wizard/base_product_merge.py b/base_product_merge/wizard/base_product_merge.py new file mode 100644 index 000000000..d9dcfd229 --- /dev/null +++ b/base_product_merge/wizard/base_product_merge.py @@ -0,0 +1,91 @@ +# Copyright 2023 ForgeFlow S.L. (https://www.forgeflow.com) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +import logging + +from openupgradelib import openupgrade_merge_records + +from odoo import _, api, fields, models +from odoo.exceptions import UserError + +_logger = logging.getLogger(__name__) + + +class BaseProductMerge(models.Model): + _name = "base.product.merge" + _description = "Merges two products" + + @api.model + def default_get(self, fields): + rec = super().default_get(fields) + active_ids = self.env.context.get("active_ids", False) + active_model = self.env.context.get("active_model", False) + ptype = active_model + rec.update({"ptype": ptype}) + if ptype == "product.product": + products = self.env[active_model].browse(active_ids) + rec.update({"product_ids": [(6, 0, products.ids)]}) + else: + product_templates = self.env[active_model].browse(active_ids) + rec.update({"product_tmpl_ids": [(6, 0, product_templates.ids)]}) + return rec + + dst_product_id = fields.Many2one("product.product", string="Destination product") + product_ids = fields.Many2many( + "product.product", + "product_rel", + "product_merge_id", + "product_id", + string="Products to merge", + ) + ptype = fields.Selection( + [("product.product", "Product"), ("product.template", "Template")] + ) + dst_product_tmpl_id = fields.Many2one( + "product.template", string="Destination product" + ) + product_tmpl_ids = fields.Many2many( + "product.template", + "product_tmpl_rel", + "product_tmpl_merge_id", + "product_tmpl_id", + string="Products to merge", + ) + merge_method = fields.Selection([("sql", "SQL"), ("orm", "ORM")], default="sql") + + def action_merge(self): + if self.ptype == "product.product": + dst_product = self.dst_product_id + products_to_merge = self.product_ids - dst_product + else: + dst_product = self.dst_product_tmpl_id + products_to_merge = self.product_tmpl_ids - dst_product + # merge product first when there is template with single product + if not any( + products_to_merge.product_variant_ids.mapped("combination_indices") + ): + dst_product_product = self.dst_product_tmpl_id.product_variant_id + product_product_to_merge = ( + self.product_tmpl_ids.product_variant_ids - dst_product_product + ) + if not product_product_to_merge: + raise UserError(_("You cannot merge product to it self.")) + self.merge_products( + "product.product", product_product_to_merge, dst_product_product + ) + self.merge_products(self.ptype, products_to_merge, dst_product) + + def merge_products(self, model, products_to_merge, dst_product): + try: + if not products_to_merge: + raise UserError(_("You cannot merge product to it self.")) + openupgrade_merge_records.merge_records( + self.env, + model, + products_to_merge.ids, + dst_product.id, + method=self.merge_method, + ) + except Exception as e: + _logger.warning(e) + raise UserError(_("Error occurred while merging products.")) from e diff --git a/base_product_merge/wizard/base_product_merge_view.xml b/base_product_merge/wizard/base_product_merge_view.xml new file mode 100644 index 000000000..e87fac147 --- /dev/null +++ b/base_product_merge/wizard/base_product_merge_view.xml @@ -0,0 +1,82 @@ + + + + + base.product.merge.form + base.product.merge + form + +
+ +

+ The selected products will be merged together. All + documents linking to one of these products will be + redirected to the aggregated product. You can remove + products from this list to avoid merging them. +

+ + + + + + + + + + + + + + + +
+
+
+
+
+
+ + + + Merge Products + + base.product.merge + form + new + + + + Merge Products + + base.product.merge + form + new + +
diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 000000000..180fc4978 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +# generated from manifests external_dependencies +openupgradelib diff --git a/setup/base_product_merge/odoo/addons/base_product_merge b/setup/base_product_merge/odoo/addons/base_product_merge new file mode 120000 index 000000000..2e2f3204e --- /dev/null +++ b/setup/base_product_merge/odoo/addons/base_product_merge @@ -0,0 +1 @@ +../../../../base_product_merge \ No newline at end of file diff --git a/setup/base_product_merge/setup.py b/setup/base_product_merge/setup.py new file mode 100644 index 000000000..28c57bb64 --- /dev/null +++ b/setup/base_product_merge/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +)