diff --git a/product_internal_reference_generator/README.rst b/product_internal_reference_generator/README.rst new file mode 100644 index 000000000..65f30a5a6 --- /dev/null +++ b/product_internal_reference_generator/README.rst @@ -0,0 +1,119 @@ +==================================== +Product Internal Reference Generator +==================================== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:51e734f0f57e0645a964c53b10702eb4caf53ee09526af6935dcf708f47b0454 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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--variant-lightgray.png?logo=github + :target: https://github.com/OCA/product-variant/tree/14.0/product_internal_reference_generator + :alt: OCA/product-variant +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/product-variant-14-0/product-variant-14-0-product_internal_reference_generator + :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-variant&target_branch=14.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module allows to generate internal references for Product templates and variants using sequences, setting codes as read-only. + +In product template, it's possible to choose among different Internal Reference Templates related to a sequence, and then generate an internal reference with the following structure: + +Internal Reference Prefix + progressive number for variant, eg: “Main0001001”, where: + +"Main0001" is the prefix generated by sequence and assigned to product template, and + +"001" the variant identifier. + + +Every time a new variant is created, a new internal reference is automatically assigned with progressive variant code. + + +A specific access rights allows specific users to change internal reference template for a product template once an internal reference has been generated, as well as editing existing ones. + +**Table of contents** + +.. contents:: + :local: + +Usage +===== + +Go to inventory > Configuration > Internal Reference Templates and set: + +the sequence to be used for Internal Reference Prefix generation + +the number of digits to be used for variants code (standard is 3) + +Now go to product template, select an Internal Reference Template and generate an Internal Reference Prefix. Internal reference field is now read-only. + +Each time a new variant is created for this product template, an internal reference is automatically assigned. + +Known issues / Roadmap +====================== + +Due to odoo's behavior in handling internal reference for variants (see for example https://github.com/odoo/odoo/issues/140356 ), removing all attributes from a product template with variants will generate a new internal reference for product template. + +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 +~~~~~~~ + +* Ilyas +* Ooops + +Contributors +~~~~~~~~~~~~ + +* `Ooops404 `__: + + * Ilyas + +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-ilyasProgrammer| image:: https://github.com/ilyasProgrammer.png?size=40px + :target: https://github.com/ilyasProgrammer + :alt: ilyasProgrammer + +Current `maintainer `__: + +|maintainer-ilyasProgrammer| + +This module is part of the `OCA/product-variant `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/product_internal_reference_generator/__init__.py b/product_internal_reference_generator/__init__.py new file mode 100644 index 000000000..0650744f6 --- /dev/null +++ b/product_internal_reference_generator/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/product_internal_reference_generator/__manifest__.py b/product_internal_reference_generator/__manifest__.py new file mode 100644 index 000000000..d899aea61 --- /dev/null +++ b/product_internal_reference_generator/__manifest__.py @@ -0,0 +1,19 @@ +{ + "name": "Product Internal Reference Generator", + "summary": """Product template and variant reference based on sequence""", + "author": "Ilyas, Ooops, Odoo Community Association (OCA)", + "maintainers": ["ilyasProgrammer"], + "license": "AGPL-3", + "website": "https://github.com/OCA/product-variant", + "category": "Sale", + "version": "14.0.1.0.0", + "depends": ["stock", "base_view_inheritance_extension"], + "data": [ + "security/groups.xml", + "security/ir.model.access.csv", + "views/product.xml", + ], + "demo": ["demo/product_code_seq_demo.xml"], + "installable": True, + "application": False, +} diff --git a/product_internal_reference_generator/demo/product_code_seq_demo.xml b/product_internal_reference_generator/demo/product_code_seq_demo.xml new file mode 100644 index 000000000..a156e3954 --- /dev/null +++ b/product_internal_reference_generator/demo/product_code_seq_demo.xml @@ -0,0 +1,34 @@ + + + + + Good products sequence + GOOD + 4 + + + + Bad products sequence + BAD + 5 + + + + Good products + 3 + + + + + Bad products + 4 + + + + diff --git a/product_internal_reference_generator/models/__init__.py b/product_internal_reference_generator/models/__init__.py new file mode 100644 index 000000000..46826c77a --- /dev/null +++ b/product_internal_reference_generator/models/__init__.py @@ -0,0 +1,3 @@ +from . import product_code_sequence +from . import product_template +from . import product_product diff --git a/product_internal_reference_generator/models/product_code_sequence.py b/product_internal_reference_generator/models/product_code_sequence.py new file mode 100644 index 000000000..80fd7aac5 --- /dev/null +++ b/product_internal_reference_generator/models/product_code_sequence.py @@ -0,0 +1,12 @@ +# Copyright 2023 Ooops - Ilyas +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +from odoo import fields, models + + +class ProductCodeSequence(models.Model): + _name = "product.code.sequence" + _description = "Internal Reference Template" + + name = fields.Char(required=True) + sequence_id = fields.Many2one("ir.sequence", required=True) + variant_reference_numbers = fields.Integer("Digits", default=3, required=True) diff --git a/product_internal_reference_generator/models/product_product.py b/product_internal_reference_generator/models/product_product.py new file mode 100644 index 000000000..bf2e2f794 --- /dev/null +++ b/product_internal_reference_generator/models/product_product.py @@ -0,0 +1,16 @@ +# Copyright 2023 Ooops - Ilyas +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +from odoo import api, models + + +class ProductProduct(models.Model): + _inherit = "product.product" + + @api.model_create_multi + def create(self, vals_list): + for vals in vals_list: + if vals.get("product_tmpl_id"): + pt = self.env["product.template"].browse(vals["product_tmpl_id"]) + if pt.variants_sequence_id: + vals["default_code"] = pt.get_variant_next_default_code() + return super().create(vals_list) diff --git a/product_internal_reference_generator/models/product_template.py b/product_internal_reference_generator/models/product_template.py new file mode 100644 index 000000000..54b5a575c --- /dev/null +++ b/product_internal_reference_generator/models/product_template.py @@ -0,0 +1,46 @@ +# Copyright 2023 Ooops - Ilyas +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +from odoo import api, fields, models + + +class ProductTemplate(models.Model): + _inherit = "product.template" + + int_ref_template_id = fields.Many2one( + "product.code.sequence", "Internal Reference Template" + ) + variants_sequence_id = fields.Many2one("ir.sequence") + variants_prefix = fields.Char( + "Internal Reference Prefix", readonly=True, tracking=True + ) + + @api.onchange("int_ref_template_id") + def onchange_int_ref_template_id(self): + self.variants_prefix = False + + def btn_generate_sequence(self): + self.ensure_one() + int_ref_next_val = self.int_ref_template_id.sequence_id.next_by_id() + var_seq = self.env["ir.sequence"].create( + { + "name": "variants " + int_ref_next_val, + "padding": self.int_ref_template_id.variant_reference_numbers, + } + ) + self.write( + { + "variants_prefix": int_ref_next_val, + "variants_sequence_id": var_seq.id, + "default_code": int_ref_next_val + var_seq.get_next_char(0), + } + ) + self.update_variants_default_code() + + def update_variants_default_code(self): + for pp in self.product_variant_ids.filtered(lambda p: not p.default_code): + pp.default_code = self.get_variant_next_default_code() + + def get_variant_next_default_code(self): + if self.variants_sequence_id: + return self.variants_prefix + self.variants_sequence_id.next_by_id() + return False diff --git a/product_internal_reference_generator/readme/CONTRIBUTORS.rst b/product_internal_reference_generator/readme/CONTRIBUTORS.rst new file mode 100644 index 000000000..c29a9c72e --- /dev/null +++ b/product_internal_reference_generator/readme/CONTRIBUTORS.rst @@ -0,0 +1,3 @@ +* `Ooops404 `__: + + * Ilyas diff --git a/product_internal_reference_generator/readme/DESCRIPTION.rst b/product_internal_reference_generator/readme/DESCRIPTION.rst new file mode 100644 index 000000000..d1a21cb27 --- /dev/null +++ b/product_internal_reference_generator/readme/DESCRIPTION.rst @@ -0,0 +1,15 @@ +This module allows to generate internal references for Product templates and variants using sequences, setting codes as read-only. + +In product template, it's possible to choose among different Internal Reference Templates related to a sequence, and then generate an internal reference with the following structure: + +Internal Reference Prefix + progressive number for variant, eg: “Main0001001”, where: + +"Main0001" is the prefix generated by sequence and assigned to product template, and + +"001" the variant identifier. + + +Every time a new variant is created, a new internal reference is automatically assigned with progressive variant code. + + +A specific access rights allows specific users to change internal reference template for a product template once an internal reference has been generated, as well as editing existing ones. diff --git a/product_internal_reference_generator/readme/ROADMAP.rst b/product_internal_reference_generator/readme/ROADMAP.rst new file mode 100644 index 000000000..d247f9518 --- /dev/null +++ b/product_internal_reference_generator/readme/ROADMAP.rst @@ -0,0 +1 @@ +Due to odoo's behavior in handling internal reference for variants (see for example https://github.com/odoo/odoo/issues/140356 ), removing all attributes from a product template with variants will generate a new internal reference for product template. diff --git a/product_internal_reference_generator/readme/USAGE.rst b/product_internal_reference_generator/readme/USAGE.rst new file mode 100644 index 000000000..a53d8f230 --- /dev/null +++ b/product_internal_reference_generator/readme/USAGE.rst @@ -0,0 +1,9 @@ +Go to inventory > Configuration > Internal Reference Templates and set: + +the sequence to be used for Internal Reference Prefix generation + +the number of digits to be used for variants code (standard is 3) + +Now go to product template, select an Internal Reference Template and generate an Internal Reference Prefix. Internal reference field is now read-only. + +Each time a new variant is created for this product template, an internal reference is automatically assigned. diff --git a/product_internal_reference_generator/security/groups.xml b/product_internal_reference_generator/security/groups.xml new file mode 100644 index 000000000..f5359480e --- /dev/null +++ b/product_internal_reference_generator/security/groups.xml @@ -0,0 +1,7 @@ + + + + Internal reference template always visible + + + diff --git a/product_internal_reference_generator/security/ir.model.access.csv b/product_internal_reference_generator/security/ir.model.access.csv new file mode 100644 index 000000000..4f399317e --- /dev/null +++ b/product_internal_reference_generator/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 +pcs1,pcs1,model_product_code_sequence,base.group_user,1,1,0,0 +pcs2,pcs2,model_product_code_sequence,base.group_system,1,1,1,1 diff --git a/product_internal_reference_generator/tests/__init__.py b/product_internal_reference_generator/tests/__init__.py new file mode 100644 index 000000000..bb7bd326a --- /dev/null +++ b/product_internal_reference_generator/tests/__init__.py @@ -0,0 +1,3 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from . import test_product_internal_reference_generator diff --git a/product_internal_reference_generator/tests/test_product_internal_reference_generator.py b/product_internal_reference_generator/tests/test_product_internal_reference_generator.py new file mode 100644 index 000000000..eadefa7e3 --- /dev/null +++ b/product_internal_reference_generator/tests/test_product_internal_reference_generator.py @@ -0,0 +1,68 @@ +# Copyright 2023 Ooops - Ilyas +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +from odoo.tests import Form, SavepointCase + + +class TestProductInternalReferenceGenerator(SavepointCase): + @classmethod + def setUpClass(cls): + super(TestProductInternalReferenceGenerator, cls).setUpClass() + attr_model = cls.env["product.attribute"] + val_model = cls.env["product.attribute.value"] + pt_model = cls.env["product.template"] + cls.group_int_ref = cls.env.ref( + "product_internal_reference_generator.group_int_ref_template_always_visible" + ) + cls.pt_seq = cls.env.ref("product_internal_reference_generator.demo_pcs_1") + cls.attr1 = attr_model.create({"name": "Wheels", "create_variant": "always"}) + cls.attr2 = attr_model.create({"name": "Frame"}) + cls.val1 = val_model.create({"name": "Big", "attribute_id": cls.attr1.id}) + cls.val2 = val_model.create({"name": "Very Big", "attribute_id": cls.attr1.id}) + cls.val3 = val_model.create({"name": "Carbon", "attribute_id": cls.attr2.id}) + cls.val4 = val_model.create({"name": "Magnesium", "attribute_id": cls.attr2.id}) + cls.pt_bicycle = pt_model.create({"name": "Bicycle"}) + cls.pt_car = pt_model.create({"name": "Car"}) + cls.pt_plane = pt_model.create({"name": "Plane"}) + + def test_all(self): + with Form(self.pt_bicycle) as pt: + pt.int_ref_template_id = self.pt_seq + pt.save() + # trigger onchange_int_ref_template_id + self.assertFalse(pt.variants_prefix, msg="Prefix must be empty.") + self.pt_bicycle.btn_generate_sequence() + # default_code must be generated for variant + self._check_default_code("000", 0, self.pt_bicycle) + + with Form(self.pt_car) as pt_car: + pt_car.int_ref_template_id = self.pt_seq + pt_car.save() + with pt_car.attribute_line_ids.new() as line: + line.attribute_id = self.attr1 + line.value_ids.add(self.val1) + line.value_ids.add(self.val2) + pt_car.save() + self.pt_car.btn_generate_sequence() + # default_code must be generated for multiple variants + self._check_default_code("001", 0, self.pt_car) + self._check_default_code("002", 1, self.pt_car) + + with pt_car.attribute_line_ids.new() as line: + line.attribute_id = self.attr2 + line.value_ids.add(self.val3) + line.value_ids.add(self.val4) + pt_car.save() + # default_code must be generated after more attributes was added + self._check_default_code("003", 0, self.pt_car) + self._check_default_code("004", 1, self.pt_car) + self._check_default_code("005", 2, self.pt_car) + self._check_default_code("006", 3, self.pt_car) + # no code if no sequence template + self.assertFalse(self.pt_plane.get_variant_next_default_code()) + + def _check_default_code(self, sequence_str, ind, pt): + self.assertIn( + str(self.pt_seq.sequence_id.number_next_actual - 1) + sequence_str, + pt.product_variant_ids[ind].default_code, + msg="Internal reference mismatch.", + ) diff --git a/product_internal_reference_generator/views/product.xml b/product_internal_reference_generator/views/product.xml new file mode 100644 index 000000000..e65237e81 --- /dev/null +++ b/product_internal_reference_generator/views/product.xml @@ -0,0 +1,149 @@ + + + + product.template + + + + + + + product.code.sequence.form.view + product.code.sequence + +
+ + + + + + + + + +
+
+
+ + + product.code.sequence.tree.view + product.code.sequence + + + + + + + + + + + Internal Reference Templates + ir.actions.act_window + product.code.sequence + tree,form + + + + + + product.product + + + + + + + + [('default_code', '!=', False)] + + !product_internal_reference_generator.group_int_ref_template_always_visible + + + + + + product.product + + + + + + + + [('default_code', '!=', False)] + + !product_internal_reference_generator.group_int_ref_template_always_visible + + + + +
diff --git a/setup/product_internal_reference_generator/odoo/addons/product_internal_reference_generator b/setup/product_internal_reference_generator/odoo/addons/product_internal_reference_generator new file mode 120000 index 000000000..ee2c7dfac --- /dev/null +++ b/setup/product_internal_reference_generator/odoo/addons/product_internal_reference_generator @@ -0,0 +1 @@ +../../../../product_internal_reference_generator \ No newline at end of file diff --git a/setup/product_internal_reference_generator/setup.py b/setup/product_internal_reference_generator/setup.py new file mode 100644 index 000000000..28c57bb64 --- /dev/null +++ b/setup/product_internal_reference_generator/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +)