diff --git a/product_merge/README.rst b/product_merge/README.rst new file mode 100644 index 00000000000..6932060ccea --- /dev/null +++ b/product_merge/README.rst @@ -0,0 +1,93 @@ +============= +Product Merge +============= + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:f61a26838528b39a7681c2417bec10f1a57716112590f61aab0608f392d26c04 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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_merge + :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_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/product-attribute&target_branch=16.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module allows users to efficiently merge multiple product templates +into one. This merge process ensures that attributes and variants from +all selected products are consolidated into the primary product +template, without creating any new variants. This approach is +particularly important for maintaining data integrity and avoiding +unnecessary database load. + +By not creating new variants during the merge, the module helps to +prevent heavy updates on existing tables, making it ideal for +large-scale databases. + +**Table of contents** + +.. contents:: + :local: + +Usage +===== + +- In the product template tree view, select multiple products. +- Chose "Merge products" action +- At least two products must be selected, and each should have at most + one variant. + +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 +------- + +* ACSONE SA/NV + +Contributors +------------ + +- Souheil Bejaoui souheil.bejaoui@acsone.eu + +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_merge/__init__.py b/product_merge/__init__.py new file mode 100644 index 00000000000..976591c996e --- /dev/null +++ b/product_merge/__init__.py @@ -0,0 +1,2 @@ +from . import wizards +from . import models diff --git a/product_merge/__manifest__.py b/product_merge/__manifest__.py new file mode 100644 index 00000000000..31712c6fb57 --- /dev/null +++ b/product_merge/__manifest__.py @@ -0,0 +1,18 @@ +# Copyright 2024 ACSONE SA/NV +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +{ + "name": "Product Merge", + "version": "16.0.1.0.0", + "license": "AGPL-3", + "author": "ACSONE SA/NV,Odoo Community Association (OCA)", + "website": "https://github.com/OCA/product-attribute", + "depends": ["product"], + "data": [ + "security/groups.xml", + "security/ir.model.access.csv", + "wizards/product_merge_wizard.xml", + ], + "demo": [], + "external_dependencies": {"python": ["openupgradelib"]}, +} diff --git a/product_merge/models/__init__.py b/product_merge/models/__init__.py new file mode 100644 index 00000000000..e8fa8f6bf1e --- /dev/null +++ b/product_merge/models/__init__.py @@ -0,0 +1 @@ +from . import product_template diff --git a/product_merge/models/product_template.py b/product_merge/models/product_template.py new file mode 100644 index 00000000000..6f1dc2fc4e4 --- /dev/null +++ b/product_merge/models/product_template.py @@ -0,0 +1,15 @@ +# Copyright 2024 ACSONE SA/NV +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import models + + +class ProductTemplate(models.Model): + + _inherit = "product.template" + + def _create_variant_ids(self): + """prevent variant creation at product merge process""" + if self.env.context.get("product_merge"): + return + return super()._create_variant_ids() diff --git a/product_merge/readme/CONTRIBUTORS.md b/product_merge/readme/CONTRIBUTORS.md new file mode 100644 index 00000000000..136fa1e17e3 --- /dev/null +++ b/product_merge/readme/CONTRIBUTORS.md @@ -0,0 +1,2 @@ +- Souheil Bejaoui + diff --git a/product_merge/readme/DESCRIPTION.md b/product_merge/readme/DESCRIPTION.md new file mode 100644 index 00000000000..1df2e409b07 --- /dev/null +++ b/product_merge/readme/DESCRIPTION.md @@ -0,0 +1,8 @@ +This module allows users to efficiently merge multiple product templates into one. +This merge process ensures that attributes and variants from all selected products +are consolidated into the primary product template, without creating any new variants. +This approach is particularly important for maintaining data integrity and +avoiding unnecessary database load. + +By not creating new variants during the merge, the module helps to prevent +heavy updates on existing tables, making it ideal for large-scale databases. diff --git a/product_merge/readme/USAGE.md b/product_merge/readme/USAGE.md new file mode 100644 index 00000000000..0c255642cfa --- /dev/null +++ b/product_merge/readme/USAGE.md @@ -0,0 +1,3 @@ +- In the product template tree view, select multiple products. +- Chose "Merge products" action +- At least two products must be selected, and each should have at most one variant. diff --git a/product_merge/security/groups.xml b/product_merge/security/groups.xml new file mode 100644 index 00000000000..d67216e98e5 --- /dev/null +++ b/product_merge/security/groups.xml @@ -0,0 +1,14 @@ + + + + + Can merge products + + + + + diff --git a/product_merge/security/ir.model.access.csv b/product_merge/security/ir.model.access.csv new file mode 100644 index 00000000000..475e62f0954 --- /dev/null +++ b/product_merge/security/ir.model.access.csv @@ -0,0 +1,3 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_product_merge_wizard,access_product_merge_wizard,model_product_merge_wizard,group_can_merge_products,1,1,1,1 +access_product_merge_wizard_line,access_product_merge_wizard_line,model_product_merge_wizard_line,group_can_merge_products,1,1,1,1 diff --git a/product_merge/static/description/icon.png b/product_merge/static/description/icon.png new file mode 100644 index 00000000000..3a0328b516c Binary files /dev/null and b/product_merge/static/description/icon.png differ diff --git a/product_merge/static/description/icon.png.oca b/product_merge/static/description/icon.png.oca new file mode 100644 index 00000000000..3a0328b516c Binary files /dev/null and b/product_merge/static/description/icon.png.oca differ diff --git a/product_merge/static/description/index.html b/product_merge/static/description/index.html new file mode 100644 index 00000000000..bc0f3cf7ff3 --- /dev/null +++ b/product_merge/static/description/index.html @@ -0,0 +1,441 @@ + + + + + +Product Merge + + + +
+

Product Merge

+ + +

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

+

This module allows users to efficiently merge multiple product templates +into one. This merge process ensures that attributes and variants from +all selected products are consolidated into the primary product +template, without creating any new variants. This approach is +particularly important for maintaining data integrity and avoiding +unnecessary database load.

+

By not creating new variants during the merge, the module helps to +prevent heavy updates on existing tables, making it ideal for +large-scale databases.

+

Table of contents

+ +
+

Usage

+
    +
  • In the product template tree view, select multiple products.
  • +
  • Chose “Merge products” action
  • +
  • At least two products must be selected, and each should have at most +one variant.
  • +
+
+
+

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

+
    +
  • ACSONE SA/NV
  • +
+
+
+

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_merge/tests/__init__.py b/product_merge/tests/__init__.py new file mode 100644 index 00000000000..40822643a7a --- /dev/null +++ b/product_merge/tests/__init__.py @@ -0,0 +1 @@ +from . import test_product_merge diff --git a/product_merge/tests/test_product_merge.py b/product_merge/tests/test_product_merge.py new file mode 100644 index 00000000000..125719b851a --- /dev/null +++ b/product_merge/tests/test_product_merge.py @@ -0,0 +1,243 @@ +# Copyright 2024 ACSONE SA/NV +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from psycopg2.errors import UniqueViolation + +from odoo import Command +from odoo.exceptions import ValidationError +from odoo.tests.common import Form, TransactionCase + + +class TestProductMerge(TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + # Create test products + cls.product_r = cls.env["product.template"].create( + {"name": "Product: Color RED"} + ) + cls.product_b = cls.env["product.template"].create( + {"name": "Product: Color Blue"} + ) + + # Create test attributes + cls.color_attr = cls.env["product.attribute"].create( + {"name": "Color", "create_variant": "dynamic"} + ) + cls.color_attr_value_r = cls.env["product.attribute.value"].create( + {"name": "Red", "attribute_id": cls.color_attr.id} + ) + cls.color_attr_value_b = cls.env["product.attribute.value"].create( + {"name": "Blue", "attribute_id": cls.color_attr.id} + ) + cls.variant_r = cls.product_r.product_variant_ids + cls.variant_b = cls.product_b.product_variant_ids + cls.existing_variants = cls.variant_r | cls.variant_b + cls.supplierinfo_r = cls.env["product.supplierinfo"].create( + { + "partner_id": cls.env["res.partner"].create({"name": "Supplier A"}).id, + "product_tmpl_id": cls.product_r.id, + } + ) + cls.supplierinfo_b = cls.env["product.supplierinfo"].create( + { + "partner_id": cls.env["res.partner"].create({"name": "Supplier B"}).id, + "product_tmpl_id": cls.product_b.id, + } + ) + cls.pricelist = cls.env["product.pricelist"].create( + { + "name": "Test Pricelist", + "item_ids": [ + Command.create( + { + "applied_on": "1_product", + "product_tmpl_id": cls.product_r.id, + "fixed_price": 70.0, + } + ), + Command.create( + { + "applied_on": "1_product", + "product_tmpl_id": cls.product_b.id, + "fixed_price": 50.0, + } + ), + ], + } + ) + + def test_0(self): + """ + Test the default_get method of the product merge wizard. + + This test validates that: + - `product_ids` is correctly populated from the context. + - `line_ids` is computed correctly based on the selected products. + - Attribute values are assigned correctly to the wizard lines. + """ + with Form( + self.env["product.merge.wizard"].with_context( + active_model="product.template", + active_ids=[self.product_r.id, self.product_b.id], + ) + ) as wizard_form: + wizard_form.product_tmpl_id = self.product_r + wizard = wizard_form.save() + self.assertEqual(wizard.product_ids, self.product_r | self.product_b) + self.assertEqual(len(wizard.line_ids), 2) + self.assertEqual(wizard.line_ids.product_id, self.existing_variants) + line_r = wizard.line_ids.filtered( + lambda line: line.product_id == self.variant_r + ) + line_b = wizard.line_ids.filtered( + lambda line: line.product_id == self.variant_b + ) + line_r.attribute_value_ids = self.color_attr_value_r + line_b.attribute_value_ids = self.color_attr_value_b + self.wizard = wizard + self.line_r = line_r + self.line_b = line_b + self.wizard.attribute_ids = self.color_attr + self.line_r.attribute_value_ids = self.color_attr_value_r + self.line_b.attribute_value_ids = self.color_attr_value_b + + def test_action_merge_products(self): + """ + Test the `action_merge_products` method of the wizard: + - The primary product template remains active after the merge. + - The other product template is deactivated. + - The attributes and variants are correctly merged without creating duplicates. + - Variants are assigned the correct attribute values after the merge. + """ + self.test_0() + # Execute the merge + self.wizard.action_merge_products() + + # Check that product_tmpl_id retains active status + self.assertTrue(self.product_r.active) + + # Check that other products are archived + self.assertFalse(self.product_b.active) + + # Check that attributes are updated in product_tmpl_id + attribute_lines = self.product_r.attribute_line_ids + self.assertEqual(len(attribute_lines), 1) + variants = self.product_r.product_variant_ids + # check variants are now related to on template + self.assertEqual(len(variants), 2) + # check variants are the same and the system didn't create new one + self.assertEqual(self.existing_variants, variants) + # check values are assigned to variants according to the mapping + variant_r_tmpl_value = self.variant_r.product_template_attribute_value_ids + variant_b_tmpl_value = self.variant_b.product_template_attribute_value_ids + self.assertEqual( + variant_r_tmpl_value.product_attribute_value_id, self.color_attr_value_r + ) + self.assertEqual( + variant_b_tmpl_value.product_attribute_value_id, self.color_attr_value_b + ) + + def test_action_merge_products_same_attribute_value(self): + """ + ensures that an error is raised when two variants are merged with the same + attribute value combination + """ + self.test_0() + self.wizard.attribute_ids = self.color_attr + self.line_b.attribute_value_ids = self.color_attr_value_r + # Execute the merge + with self.assertRaises(UniqueViolation, msg="Combination exists"): + self.wizard.action_merge_products() + + def test_minimum_two_products_constraint(self): + wizard = self.env["product.merge.wizard"].create( + { + "product_tmpl_id": self.product_r.id, + "product_ids": [Command.link(self.product_r.id)], + } + ) + with self.assertRaises( + ValidationError, + msg="At least two products must be added to the wizard to perform a merge.", + ): + wizard.action_merge_products() + + def test_products_with_max_one_variant(self): + # Create a product template with multiple variants + self.color_attr.create_variant = "always" + product_multi_variant = self.env["product.template"].create( + { + "name": "Product with Multiple Variants", + "attribute_line_ids": [ + Command.create( + { + "attribute_id": self.color_attr.id, + "value_ids": [ + Command.set( + [ + self.color_attr_value_r.id, + self.color_attr_value_b.id, + ], + ) + ], + }, + ) + ], + } + ) + + # Attempt to create a wizard with a multi-variant product + wizard = self.env["product.merge.wizard"].create( + { + "product_tmpl_id": self.product_r.id, + "product_ids": [ + Command.link(self.product_r.id), + Command.link(product_multi_variant.id), + ], + } + ) + with self.assertRaises( + ValidationError, msg="All added products must have at most one variant." + ): + wizard.action_merge_products() + + def test_update_supplier_info(self): + """ + Test the `_update_supplier_info` method: + - Ensures that supplier information is correctly updated when products are merged. + - Checks that the supplierinfo records are transferred from the merged product + templates to the target product template and variant. + """ + self.test_0() + self.wizard.action_merge_products() + + # Check supplierinfo for product_r + updated_supplierinfo_r = self.env["product.supplierinfo"].search( + [("product_id", "=", self.variant_r.id)] + ) + self.assertEqual(updated_supplierinfo_r, self.supplierinfo_r) + self.assertEqual(updated_supplierinfo_r.product_tmpl_id, self.product_r) + updated_supplierinfo_b = self.env["product.supplierinfo"].search( + [("product_id", "=", self.variant_b.id)] + ) + self.assertEqual(updated_supplierinfo_b, self.supplierinfo_b) + self.assertEqual(updated_supplierinfo_b.product_tmpl_id, self.product_r) + self.assertEqual(len(self.product_r.seller_ids), 2) + + def test_different_type_merge(self): + """ + Test that merging products of different types raises a ValidationError. + """ + self.product_r.type = "service" + with self.assertRaises(ValidationError): + self.test_0() + + def test_update_price_list(self): + self.test_0() + self.wizard.action_merge_products() + self.assertEqual(self.pricelist.item_ids.product_tmpl_id, self.product_r) + self.assertEqual(self.pricelist.item_ids[0].applied_on, "0_product_variant") + self.assertEqual( + self.pricelist.item_ids.product_id, self.variant_r | self.variant_b + ) diff --git a/product_merge/wizards/__init__.py b/product_merge/wizards/__init__.py new file mode 100644 index 00000000000..7d60e64b697 --- /dev/null +++ b/product_merge/wizards/__init__.py @@ -0,0 +1,2 @@ +from . import product_merge_wizard +from . import product_merge_wizard_line diff --git a/product_merge/wizards/product_merge_wizard.py b/product_merge/wizards/product_merge_wizard.py new file mode 100644 index 00000000000..ecaf879d915 --- /dev/null +++ b/product_merge/wizards/product_merge_wizard.py @@ -0,0 +1,208 @@ +# Copyright 2024 ACSONE SA/NV +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from openupgradelib.openupgrade_merge_records import merge_records + +from odoo import Command, _, api, fields, models +from odoo.exceptions import ValidationError + + +class ProductMergeWizard(models.TransientModel): + _name = "product.merge.wizard" + _description = "Merge Products Wizard" + + product_tmpl_id = fields.Many2one( + comodel_name="product.template", + string="Product Model", + domain="[('id', 'in', product_ids)]", + required=True, + ondelete="cascade", + ) + product_ids = fields.Many2many( + comodel_name="product.template", string="Products to Merge", required=True + ) + attribute_ids = fields.Many2many( + comodel_name="product.attribute", string="Attributes", required=True + ) + line_ids = fields.One2many( + comodel_name="product.merge.wizard.line", + inverse_name="wizard_id", + string="Attribute Mapping", + compute="_compute_line_ids", + store=True, + readonly=False, + ) + + def _check_minimum_products(self): + """ + Ensure that at least two products are selected for merging. + """ + for wizard in self: + if len(wizard.product_ids) < 2: + raise ValidationError( + _( + "At least two products must be added to the wizard to perform a merge." + ) + ) + + def _check_products_max_one_variant(self): + for wizard in self: + for product in wizard.product_ids: + if len(product.product_variant_ids) > 1: + raise ValidationError( + _( + "All added products must have at most one variant. " + "Product '%(product)s' has multiple variants.", + product=product.name, + ) + ) + + @api.constrains("product_ids") + def _check_product_types(self): + for wizard in self: + if len(wizard.product_ids) > 1: + types = wizard.product_ids.mapped("type") + if len(set(types)) > 1: + raise ValidationError( + _( + "All products to merge must be of the same type " + "(e.g., consumable, service, or storable)." + ) + ) + + @api.model + def default_get(self, fields_list): + res = super().default_get(fields_list) + if self.env.context.get( + "active_model" + ) != "product.template" or not self.env.context.get("active_ids"): + return res + res["product_ids"] = [Command.set(self.env.context["active_ids"])] + return res + + @api.model + def _get_merge_field_spec(self): + """This method defines the merge strategy for each field. By default, we choose + to force the target value, but this method can be inherited to change the + behavior of the merge. + See the _adjust_merged_values_orm method documentation in OpenUpgradeLib for + more details.""" + return { + field_name: "target" for field_name in self.product_tmpl_id._fields.keys() + } + + def action_merge_products(self): + self.ensure_one() + self._check_minimum_products() + self._check_products_max_one_variant() + self.product_tmpl_id.with_context(product_merge=True).write( + { + "attribute_line_ids": [ + Command.create( + { + "attribute_id": attribute.id, + "value_ids": [ + Command.link(value.id) for value in attribute.value_ids + ], + } + ) + for attribute in self.attribute_ids + ], + } + ) + other_templates = ( + self.line_ids.product_id.product_tmpl_id - self.product_tmpl_id + ) + for line in self.line_ids: + product_variant = line.product_id + self._update_pricelist_item(product_variant) + self._update_supplier_info(product_variant) + self._move_variant_to_template(product_variant, line.attribute_value_ids) + merge_records( + self.env, + self.product_tmpl_id._name, + other_templates.ids, + self.product_tmpl_id.id, + field_spec=self._get_merge_field_spec(), + delete=False, + ) + other_templates.write({"active": False}) + archived_links = "
  • ".join( + f'{template.name}
  • ' + for template in other_templates + ) + self.product_tmpl_id.message_post( + body=_( + "The following products were merged and archived:" + "
      %(archived_links)s
        ", + archived_links=archived_links, + ), + subtype_xmlid="mail.mt_note", + ) + return { + "type": "ir.actions.act_window", + "res_model": "product.template", + "res_id": self.product_tmpl_id.id, + "view_mode": "form", + "target": "current", + } + + def _move_variant_to_template(self, product_variant, attribute_values): + template_attribute_value = self.env["product.template.attribute.value"].search( + [ + ("product_tmpl_id", "=", self.product_tmpl_id.id), + ("product_attribute_value_id", "in", attribute_values.ids), + ] + ) + product_variant.write( + { + "product_tmpl_id": self.product_tmpl_id.id, + "product_template_attribute_value_ids": [ + Command.set(template_attribute_value.ids) + ], + } + ) + product_variant._compute_combination_indices() + + @api.depends("product_ids") + def _compute_line_ids(self): + for rec in self: + rec.update( + { + "line_ids": [ + Command.create({"product_id": p.id}) + for p in rec.product_ids.product_variant_ids + ] + } + ) + + def _update_pricelist_item(self, product_variant): + pricelist_items = self.env["product.pricelist.item"].search( + [ + ("applied_on", "=", "1_product"), + ("product_tmpl_id", "=", product_variant.product_tmpl_id.id), + ] + ) + pricelist_items.write( + { + "applied_on": "0_product_variant", + "product_id": product_variant.id, + "product_tmpl_id": self.product_tmpl_id.id, + } + ) + + def _update_supplier_info(self, product_variant): + """ + Updates supplier information by transferring supplierinfo from the merged + products to the target product template. + """ + self.ensure_one() + supplier_infos = self.env["product.supplierinfo"].search( + [("product_tmpl_id", "=", product_variant.product_tmpl_id.id)] + ) + supplier_infos.write({"product_id": product_variant.id}) + supplier_infos = self.env["product.supplierinfo"].search( + [("product_id", "=", product_variant.id)] + ) + supplier_infos.write({"product_tmpl_id": self.product_tmpl_id.id}) diff --git a/product_merge/wizards/product_merge_wizard.xml b/product_merge/wizards/product_merge_wizard.xml new file mode 100644 index 00000000000..4ce83325521 --- /dev/null +++ b/product_merge/wizards/product_merge_wizard.xml @@ -0,0 +1,62 @@ + + + + + + product.merge.wizard + +
        + + + + + + + + + + + + + + + + +
        +
        +
        +
        +
        + + + Merge products + product.merge.wizard + form + {} + new + + + +
        diff --git a/product_merge/wizards/product_merge_wizard_line.py b/product_merge/wizards/product_merge_wizard_line.py new file mode 100644 index 00000000000..fd69aaf781c --- /dev/null +++ b/product_merge/wizards/product_merge_wizard_line.py @@ -0,0 +1,33 @@ +# Copyright 2024 ACSONE SA/NV +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import api, fields, models + + +class ProductMergeWizardLine(models.TransientModel): + + _name = "product.merge.wizard.line" + _description = "Merge Products Wizard Line" + + wizard_id = fields.Many2one( + "product.merge.wizard", string="Wizard", required=True, ondelete="cascade" + ) + product_id = fields.Many2one( + comodel_name="product.product", + string="Product", + required=True, + ondelete="cascade", + ) + attribute_value_ids = fields.Many2many( + comodel_name="product.attribute.value", + string="Attribute Values", + domain="attribute_value_domain", + ) + attribute_value_domain = fields.Binary(compute="_compute_attribute_value_domain") + + @api.depends("wizard_id.attribute_ids", "wizard_id.line_ids.attribute_value_ids") + def _compute_attribute_value_domain(self): + for rec in self: + rec.attribute_value_domain = [ + ("attribute_id", "in", rec.wizard_id.attribute_ids.ids) + ] diff --git a/setup/product_merge/odoo/addons/product_merge b/setup/product_merge/odoo/addons/product_merge new file mode 120000 index 00000000000..e615d0ce089 --- /dev/null +++ b/setup/product_merge/odoo/addons/product_merge @@ -0,0 +1 @@ +../../../../product_merge \ No newline at end of file diff --git a/setup/product_merge/setup.py b/setup/product_merge/setup.py new file mode 100644 index 00000000000..28c57bb6403 --- /dev/null +++ b/setup/product_merge/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +)