diff --git a/mrp_bom_attribute_match_duplicate_kit/README.rst b/mrp_bom_attribute_match_duplicate_kit/README.rst new file mode 100644 index 000000000..c38ff8616 --- /dev/null +++ b/mrp_bom_attribute_match_duplicate_kit/README.rst @@ -0,0 +1,111 @@ +================================= +BOM Attribute Match Duplicate Kit +================================= + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:799dde4fcb103960a4af12b6bd212c0913ffd0f478293ff3c0fd559245ecae1a + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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%2Fmanufacture-lightgray.png?logo=github + :target: https://github.com/OCA/manufacture/tree/14.0/mrp_bom_attribute_match_duplicate_kit + :alt: OCA/manufacture +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/manufacture-14-0/manufacture-14-0-mrp_bom_attribute_match_duplicate_kit + :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/manufacture&target_branch=14.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module allows to duplicate a product with variants, at the same time creating a BoM with type "Kit" for the new product mapping variants of the original product. + +This is useful when managing products that only exist as a "front" for a product with another name, to avoid having to manually map variants in BOM. + +**Table of contents** + +.. contents:: + :local: + +Use Cases / Context +=================== + +The use case is that customer sells the same product with two different names, prices, etc - but on the stock side, **Product 1 (copy)** only exists as a kit of **Product 1** (they're physically the same product) + +Usage +===== + +* Open "Product 1" with variants +* Use server action: "Duplicate with attribute match kit" +* A wizard opens up with field "New product template name" +* Inputs name for duplicated product (eg: "Fancy Product 1") +* Click Confirm + +A new product is created with name "Fancy Product 1" and a new BOM of type "kit" for this product; + + If product does not have variants, when using server action, display error message: "This server action can be used only on products with variants". + +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 +~~~~~~~ + +* Cetmix +* Ooops + +Contributors +~~~~~~~~~~~~ + +* Ooops404 + + * Francesco Foresti + +* Cetmix + + * Maksim Shurupov + +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-aleuffre| image:: https://github.com/aleuffre.png?size=40px + :target: https://github.com/aleuffre + :alt: aleuffre + +Current `maintainer `__: + +|maintainer-aleuffre| + +This module is part of the `OCA/manufacture `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/mrp_bom_attribute_match_duplicate_kit/__init__.py b/mrp_bom_attribute_match_duplicate_kit/__init__.py new file mode 100644 index 000000000..9b4296142 --- /dev/null +++ b/mrp_bom_attribute_match_duplicate_kit/__init__.py @@ -0,0 +1,2 @@ +from . import models +from . import wizard diff --git a/mrp_bom_attribute_match_duplicate_kit/__manifest__.py b/mrp_bom_attribute_match_duplicate_kit/__manifest__.py new file mode 100644 index 000000000..9d9700aa0 --- /dev/null +++ b/mrp_bom_attribute_match_duplicate_kit/__manifest__.py @@ -0,0 +1,19 @@ +# Copyright (C) 2023 Cetmix OÜ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +{ + "name": "BOM Attribute Match Duplicate Kit", + "version": "14.0.1.0.0", + "category": "Manufacturing", + "author": "Cetmix, Ooops, Odoo Community Association (OCA)", + "summary": "BOM Attribute Match Duplicate Kit", + "maintainers": ["aleuffre"], + "depends": ["mrp_bom_attribute_match"], + "license": "AGPL-3", + "website": "https://github.com/OCA/manufacture", + "data": [ + "security/ir.model.access.csv", + "views/product_template_views.xml", + "wizard/product_template_kit_wizard.xml", + ], +} diff --git a/mrp_bom_attribute_match_duplicate_kit/models/__init__.py b/mrp_bom_attribute_match_duplicate_kit/models/__init__.py new file mode 100644 index 000000000..e8fa8f6bf --- /dev/null +++ b/mrp_bom_attribute_match_duplicate_kit/models/__init__.py @@ -0,0 +1 @@ +from . import product_template diff --git a/mrp_bom_attribute_match_duplicate_kit/models/product_template.py b/mrp_bom_attribute_match_duplicate_kit/models/product_template.py new file mode 100644 index 000000000..c940f6b8d --- /dev/null +++ b/mrp_bom_attribute_match_duplicate_kit/models/product_template.py @@ -0,0 +1,26 @@ +# Copyright (C) 2023 Cetmix OÜ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import _, models + + +class ProductTemplate(models.Model): + _inherit = "product.template" + + def action_duplicate_with_kit(self): + self.ensure_one() + if self.product_variant_count == 1: + raise models.UserError( + _("This server action can be used only on products with variants") + ) + return { + "name": _("Duplicate with attribute match kit"), + "type": "ir.actions.act_window", + "view_mode": "form", + "res_model": "product.template.kit.wizard", + "context": { + "default_product_tmpl_id": self.id, + "default_new_product_template_name": "{} 1".format(self.name), + }, + "target": "new", + } diff --git a/mrp_bom_attribute_match_duplicate_kit/readme/CONTEXT.rst b/mrp_bom_attribute_match_duplicate_kit/readme/CONTEXT.rst new file mode 100644 index 000000000..f2d2b2511 --- /dev/null +++ b/mrp_bom_attribute_match_duplicate_kit/readme/CONTEXT.rst @@ -0,0 +1 @@ +The use case is that customer sells the same product with two different names, prices, etc - but on the stock side, **Product 1 (copy)** only exists as a kit of **Product 1** (they're physically the same product) diff --git a/mrp_bom_attribute_match_duplicate_kit/readme/CONTRIBUTORS.rst b/mrp_bom_attribute_match_duplicate_kit/readme/CONTRIBUTORS.rst new file mode 100644 index 000000000..d58aa2ec7 --- /dev/null +++ b/mrp_bom_attribute_match_duplicate_kit/readme/CONTRIBUTORS.rst @@ -0,0 +1,7 @@ +* Ooops404 + + * Francesco Foresti + +* Cetmix + + * Maksim Shurupov diff --git a/mrp_bom_attribute_match_duplicate_kit/readme/DESCRIPTION.rst b/mrp_bom_attribute_match_duplicate_kit/readme/DESCRIPTION.rst new file mode 100644 index 000000000..480947f8c --- /dev/null +++ b/mrp_bom_attribute_match_duplicate_kit/readme/DESCRIPTION.rst @@ -0,0 +1,3 @@ +This module allows to duplicate a product with variants, at the same time creating a BoM with type "Kit" for the new product mapping variants of the original product. + +This is useful when managing products that only exist as a "front" for a product with another name, to avoid having to manually map variants in BOM. diff --git a/mrp_bom_attribute_match_duplicate_kit/readme/HISTORY.rst b/mrp_bom_attribute_match_duplicate_kit/readme/HISTORY.rst new file mode 100644 index 000000000..e69de29bb diff --git a/mrp_bom_attribute_match_duplicate_kit/readme/USAGE.rst b/mrp_bom_attribute_match_duplicate_kit/readme/USAGE.rst new file mode 100644 index 000000000..a11280583 --- /dev/null +++ b/mrp_bom_attribute_match_duplicate_kit/readme/USAGE.rst @@ -0,0 +1,9 @@ +* Open "Product 1" with variants +* Use server action: "Duplicate with attribute match kit" +* A wizard opens up with field "New product template name" +* Inputs name for duplicated product (eg: "Fancy Product 1") +* Click Confirm + +A new product is created with name "Fancy Product 1" and a new BOM of type "kit" for this product; + + If product does not have variants, when using server action, display error message: "This server action can be used only on products with variants". diff --git a/mrp_bom_attribute_match_duplicate_kit/security/ir.model.access.csv b/mrp_bom_attribute_match_duplicate_kit/security/ir.model.access.csv new file mode 100644 index 000000000..cc8696391 --- /dev/null +++ b/mrp_bom_attribute_match_duplicate_kit/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 +access_product_template_kit_wizard,access.product.template.kit.wizard,model_product_template_kit_wizard,mrp.group_mrp_user,1,1,1,1 diff --git a/mrp_bom_attribute_match_duplicate_kit/static/description/index.html b/mrp_bom_attribute_match_duplicate_kit/static/description/index.html new file mode 100644 index 000000000..d6ec0a56f --- /dev/null +++ b/mrp_bom_attribute_match_duplicate_kit/static/description/index.html @@ -0,0 +1,451 @@ + + + + + + +BOM Attribute Match Duplicate Kit + + + +
+

BOM Attribute Match Duplicate Kit

+ + +

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

+

This module allows to duplicate a product with variants, at the same time creating a BoM with type “Kit” for the new product mapping variants of the original product.

+

This is useful when managing products that only exist as a “front” for a product with another name, to avoid having to manually map variants in BOM.

+

Table of contents

+ +
+

Use Cases / Context

+

The use case is that customer sells the same product with two different names, prices, etc - but on the stock side, Product 1 (copy) only exists as a kit of Product 1 (they’re physically the same product)

+
+
+

Usage

+
    +
  • Open “Product 1” with variants
  • +
  • Use server action: “Duplicate with attribute match kit”
  • +
  • A wizard opens up with field “New product template name”
  • +
  • Inputs name for duplicated product (eg: “Fancy Product 1”)
  • +
  • Click Confirm
  • +
+

A new product is created with name “Fancy Product 1” and a new BOM of type “kit” for this product;

+
+If product does not have variants, when using server action, display error message: “This server action can be used only on products with variants”.
+
+
+

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

+
    +
  • Cetmix
  • +
  • Ooops
  • +
+
+
+

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:

+

aleuffre

+

This module is part of the OCA/manufacture project on GitHub.

+

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

+
+
+
+ + diff --git a/mrp_bom_attribute_match_duplicate_kit/tests/__init__.py b/mrp_bom_attribute_match_duplicate_kit/tests/__init__.py new file mode 100644 index 000000000..f498d4b25 --- /dev/null +++ b/mrp_bom_attribute_match_duplicate_kit/tests/__init__.py @@ -0,0 +1,2 @@ +from . import test_product_template +from . import test_product_template_kit_wizard diff --git a/mrp_bom_attribute_match_duplicate_kit/tests/common.py b/mrp_bom_attribute_match_duplicate_kit/tests/common.py new file mode 100644 index 000000000..8f0e29cea --- /dev/null +++ b/mrp_bom_attribute_match_duplicate_kit/tests/common.py @@ -0,0 +1,50 @@ +# Copyright (C) 2023 Cetmix OÜ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo.tests import Form, SavepointCase + + +class TestProductTemplateKitCommon(SavepointCase): + @classmethod + def setUpClass(cls): + super(TestProductTemplateKitCommon, cls).setUpClass() + # Attribute Color + cls.attribute_color = cls.env.ref("product.product_attribute_2") + + # Attribute Color Values + cls.attribute_color_white = cls.env.ref("product.product_attribute_value_3") + cls.attribute_color_black = cls.env.ref("product.product_attribute_value_4") + + # Attribute Legs + cls.attribute_legs = cls.env.ref("product.product_attribute_1") + + # Attribute Legs Values + cls.attribute_legs_steel = cls.env.ref("product.product_attribute_value_1") + cls.attribute_legs_aluminium = cls.env.ref("product.product_attribute_value_2") + + # Product Without Attributes + cls.product_template_without_attributes = cls.env["product.template"].create( + {"name": "Product Without Attributes"} + ) + + # Product With One Attribute + form = Form(cls.env["product.template"]) + form.name = "Product With One Attribute" + with form.attribute_line_ids.new() as line: + line.attribute_id = cls.attribute_color + line.value_ids.add(cls.attribute_color_white) + line.value_ids.add(cls.attribute_color_black) + cls.product_template_one_attribute = form.save() + + # Product With Many Attributes + form = Form(cls.env["product.template"]) + form.name = "Product With One Attribute" + with form.attribute_line_ids.new() as line: + line.attribute_id = cls.attribute_color + line.value_ids.add(cls.attribute_color_white) + line.value_ids.add(cls.attribute_color_black) + with form.attribute_line_ids.new() as line: + line.attribute_id = cls.attribute_legs + line.value_ids.add(cls.attribute_legs_steel) + line.value_ids.add(cls.attribute_legs_aluminium) + cls.product_template_two_attributes = form.save() diff --git a/mrp_bom_attribute_match_duplicate_kit/tests/test_product_template.py b/mrp_bom_attribute_match_duplicate_kit/tests/test_product_template.py new file mode 100644 index 000000000..516754948 --- /dev/null +++ b/mrp_bom_attribute_match_duplicate_kit/tests/test_product_template.py @@ -0,0 +1,50 @@ +# Copyright (C) 2023 Cetmix OÜ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo.exceptions import UserError + +from .common import TestProductTemplateKitCommon + + +class TestProductTemplate(TestProductTemplateKitCommon): + def test_action_duplicate_attribute_match_kit_invalid(self): + """ + Test flow that catch an error when call + action_duplicate_attribute_match_kit function + for product without variants + """ + with self.assertRaises(UserError): + self.product_template_without_attributes.action_duplicate_with_kit() + + def test_action_duplicate_attribute_match_Kit_valid(self): + """ + Test flow that returned wizard action, if product + has attributes + """ + product = self.product_template_one_attribute + self.assertEqual( + product.product_variant_count, + 2, + "Product attribute variants count must be equal to 2", + ) + action = product.action_duplicate_with_kit() + self.assertIsInstance(action, dict, msg="Returned object must be dict") + self.assertEqual( + action["res_model"], + "product.template.kit.wizard", + "Res model must be equal to 'product.template.kit.wizard'", + ) + context = action["context"] + self.assertIsInstance(context, dict, "Context must be dict") + self.assertEqual( + context["default_product_tmpl_id"], + product.id, + "Default product template id must be equal to ID {}".format(product.id), + ) + self.assertEqual( + context["default_new_product_template_name"], + product.name + " 1", + "Default New product template name must be equal to '{} 1'".format( + product.name + ), + ) diff --git a/mrp_bom_attribute_match_duplicate_kit/tests/test_product_template_kit_wizard.py b/mrp_bom_attribute_match_duplicate_kit/tests/test_product_template_kit_wizard.py new file mode 100644 index 000000000..fb71adac6 --- /dev/null +++ b/mrp_bom_attribute_match_duplicate_kit/tests/test_product_template_kit_wizard.py @@ -0,0 +1,77 @@ +# Copyright (C) 2023 Cetmix OÜ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo.exceptions import UserError +from odoo.tests import Form + +from .common import TestProductTemplateKitCommon + + +class TestProductTemplateKitWizard(TestProductTemplateKitCommon): + def test_action_confirm_invalid(self): + """ + Test flow that catch error when product template name + and new product template name is the same + """ + context = { + "default_product_tmpl_id": self.product_template_one_attribute.id, + } + form = Form(self.env["product.template.kit.wizard"].with_context(**context)) + form.new_product_template_name = self.product_template_one_attribute.name + wizard = form.save() + + with self.assertRaises(UserError): + wizard.action_confirm() + + def test_action_confirm_flow(self): + """Test flow that run full workflow""" + product = self.product_template_two_attributes + context = { + "default_product_tmpl_id": product.id, + "default_new_product_template_name": "{} 1".format(product.name), + } + + wizard = Form( + self.env["product.template.kit.wizard"].with_context(**context) + ).save() + action = wizard.action_confirm() + + self.assertIsInstance(action, dict, "Action must be dict") + + new_product_name = "{} 1".format(product.name) + self.assertEqual( + action["name"], + new_product_name, + "Action name must be equal to {}".format(new_product_name), + ) + self.assertTrue(action.get("res_id"), "Res ID key must be exists") + + product_tmpl = self.env["product.template"].browse(action["res_id"]) + + self.assertEqual(product_tmpl.name, new_product_name, "Names must be the same") + + boms = product_tmpl.bom_ids + self.assertTrue(boms, "BOM's must be exists") + self.assertEqual(len(boms), 1, "Count BOM's must be equal to 1") + self.assertEqual( + boms.product_tmpl_id.id, + product_tmpl.id, + "BOMs product and new product must be the same", + ) + self.assertEqual(boms.type, "phantom") + self.assertEqual(boms.product_qty, 1, "Product Qty must be equal to 1") + self.assertTrue(boms.bom_line_ids, "BOM must have Lines") + self.assertEqual(len(boms.bom_line_ids), 1, msg="BOM must have one line") + + line = boms.bom_line_ids + self.assertEqual( + line.component_template_id.id, + product.id, + "Component product template ID must be equal to ID {}".format(product.id), + ) + self.assertListEqual( + line.match_on_attribute_ids.ids, + [self.attribute_legs.id, self.attribute_color.id], + "Attributes must be the same", + ) + self.assertEqual(line.product_qty, 1, "Product Qty must be equal to 1") diff --git a/mrp_bom_attribute_match_duplicate_kit/views/product_template_views.xml b/mrp_bom_attribute_match_duplicate_kit/views/product_template_views.xml new file mode 100644 index 000000000..b2259423a --- /dev/null +++ b/mrp_bom_attribute_match_duplicate_kit/views/product_template_views.xml @@ -0,0 +1,16 @@ + + + + + Duplicate with attribute match kit + + + form + code + + if records: + action = records.action_duplicate_with_kit() + + + + diff --git a/mrp_bom_attribute_match_duplicate_kit/wizard/__init__.py b/mrp_bom_attribute_match_duplicate_kit/wizard/__init__.py new file mode 100644 index 000000000..4d8081315 --- /dev/null +++ b/mrp_bom_attribute_match_duplicate_kit/wizard/__init__.py @@ -0,0 +1 @@ +from . import product_template_kit_wizard diff --git a/mrp_bom_attribute_match_duplicate_kit/wizard/product_template_kit_wizard.py b/mrp_bom_attribute_match_duplicate_kit/wizard/product_template_kit_wizard.py new file mode 100644 index 000000000..c4bbc03b5 --- /dev/null +++ b/mrp_bom_attribute_match_duplicate_kit/wizard/product_template_kit_wizard.py @@ -0,0 +1,54 @@ +# Copyright (C) 2023 Cetmix OÜ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import _, fields, models + + +class ProductTemplateKitWizard(models.TransientModel): + _name = "product.template.kit.wizard" + _description = "Duplicate with attribute match kit" + + product_tmpl_id = fields.Many2one(comodel_name="product.template", required=True) + new_product_template_name = fields.Char(required=True) + + def action_confirm(self): + """Duplicate product with bom""" + if self.product_tmpl_id.name == self.new_product_template_name: + raise models.UserError( + _("The name of the new product template should not be the same name") + ) + new_product = self.product_tmpl_id.copy( + default={"name": self.new_product_template_name} + ) + self.env["mrp.bom"].create( + { + "product_tmpl_id": new_product.id, + "type": "phantom", + "product_qty": 1, + "bom_line_ids": [ + ( + 0, + 0, + { + "component_template_id": self.product_tmpl_id.id, + "match_on_attribute_ids": [ + ( + 6, + 0, + self.product_tmpl_id.attribute_line_ids.mapped( + "attribute_id" + ).ids, + ) + ], + }, + ) + ], + } + ) + return { + "type": "ir.actions.act_window", + "name": self.new_product_template_name, + "view_mode": "form", + "res_model": "product.template", + "res_id": new_product.id, + } diff --git a/mrp_bom_attribute_match_duplicate_kit/wizard/product_template_kit_wizard.xml b/mrp_bom_attribute_match_duplicate_kit/wizard/product_template_kit_wizard.xml new file mode 100644 index 000000000..fe39814ac --- /dev/null +++ b/mrp_bom_attribute_match_duplicate_kit/wizard/product_template_kit_wizard.xml @@ -0,0 +1,27 @@ + + + + + product.template.kit.wizard + product.template.kit.wizard + +
+ + + + + +
+ +
+
+ +
diff --git a/setup/mrp_bom_attribute_match_duplicate_kit/odoo/addons/mrp_bom_attribute_match_duplicate_kit b/setup/mrp_bom_attribute_match_duplicate_kit/odoo/addons/mrp_bom_attribute_match_duplicate_kit new file mode 120000 index 000000000..e46fca01e --- /dev/null +++ b/setup/mrp_bom_attribute_match_duplicate_kit/odoo/addons/mrp_bom_attribute_match_duplicate_kit @@ -0,0 +1 @@ +../../../../mrp_bom_attribute_match_duplicate_kit \ No newline at end of file diff --git a/setup/mrp_bom_attribute_match_duplicate_kit/setup.py b/setup/mrp_bom_attribute_match_duplicate_kit/setup.py new file mode 100644 index 000000000..28c57bb64 --- /dev/null +++ b/setup/mrp_bom_attribute_match_duplicate_kit/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +)