From 06fc55abfae91ed245cd555382932f9e0eee0311 Mon Sep 17 00:00:00 2001 From: Eric Antones Date: Sun, 18 Nov 2018 17:11:22 +0100 Subject: [PATCH 01/11] [MIG] product_unique_internal_reference: Migration to 11.0 --- product_unique_internal_reference/README.rst | 32 ++++++++++++++++++ product_unique_internal_reference/__init__.py | 6 ++++ .../__manifest__.py | 19 +++++++++++ product_unique_internal_reference/hooks.py | 31 +++++++++++++++++ .../models/__init__.py | 5 +++ .../models/models.py | 15 ++++++++ .../static/description/icon.png | Bin 0 -> 6342 bytes 7 files changed, 108 insertions(+) create mode 100644 product_unique_internal_reference/README.rst create mode 100644 product_unique_internal_reference/__init__.py create mode 100644 product_unique_internal_reference/__manifest__.py create mode 100644 product_unique_internal_reference/hooks.py create mode 100644 product_unique_internal_reference/models/__init__.py create mode 100644 product_unique_internal_reference/models/models.py create mode 100644 product_unique_internal_reference/static/description/icon.png diff --git a/product_unique_internal_reference/README.rst b/product_unique_internal_reference/README.rst new file mode 100644 index 000000000..eae16d37c --- /dev/null +++ b/product_unique_internal_reference/README.rst @@ -0,0 +1,32 @@ +.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 + +================================= +Product unique internal reference +================================= + +* This module ensures that you enter a 'Unique' Internal Reference (default_code) for your 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 smashing it by providing a detailed and welcomed feedback. + +Credits +======= + +Contributors +------------ + +* Eric Antones + + + + + + + diff --git a/product_unique_internal_reference/__init__.py b/product_unique_internal_reference/__init__.py new file mode 100644 index 000000000..6f2ec1e8e --- /dev/null +++ b/product_unique_internal_reference/__init__.py @@ -0,0 +1,6 @@ +# Copyright NuoBiT Solutions, S.L. () +# Eric Antones +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) + +from . import models +from .hooks import pre_init_hook_internal_reference_check diff --git a/product_unique_internal_reference/__manifest__.py b/product_unique_internal_reference/__manifest__.py new file mode 100644 index 000000000..3b843684f --- /dev/null +++ b/product_unique_internal_reference/__manifest__.py @@ -0,0 +1,19 @@ +# Copyright NuoBiT Solutions, S.L. () +# Eric Antones +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) + +{ + 'name': 'Product unique internal reference', + 'summary': 'This module ensures that you enter a Unique Internal Reference (default_code) for your Products', + 'version': '11.0.0.1.0', + 'category': 'Sales', + 'author': 'NuoBiT Solutions, S.L., Eric Antones', + 'website': 'https://www.nuobit.com', + 'license': 'AGPL-3', + 'depends': [ + 'product_variant_company_aware', + ], + 'pre_init_hook': 'pre_init_hook_internal_reference_check', + 'installable': True, + 'auto_install': False, +} diff --git a/product_unique_internal_reference/hooks.py b/product_unique_internal_reference/hooks.py new file mode 100644 index 000000000..8e9fdd44e --- /dev/null +++ b/product_unique_internal_reference/hooks.py @@ -0,0 +1,31 @@ +# Copyright NuoBiT Solutions, S.L. () +# Eric Antones +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) + +from odoo import _ +from odoo.exceptions import ValidationError + + +def pre_init_hook_internal_reference_check(cr): + """This hook will look to see if any conflicting internal references exist before + the module is installed + :param odoo.sql_db.Cursor cr: + Database cursor. + """ + with cr.savepoint(): + cr.execute("""SELECT distinct p0.company_id, p0.default_code + FROM product_product p0 + WHERE EXISTS ( + SELECT 1 + FROM product_product p1 + where p1.company_id = p0.company_id and + p1.default_code = p0.default_code AND + p1.id != p0.id + ) + """) + + products = sorted(['[%i] %s' % p for p in cr.fetchall()]) + if products: + raise ValidationError( + _('Conflicting internal references exist: %s' % ', '.join(products)) + ) diff --git a/product_unique_internal_reference/models/__init__.py b/product_unique_internal_reference/models/__init__.py new file mode 100644 index 000000000..38b2ba65b --- /dev/null +++ b/product_unique_internal_reference/models/__init__.py @@ -0,0 +1,5 @@ +# Copyright NuoBiT Solutions, S.L. () +# Eric Antones +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) + +from . import models diff --git a/product_unique_internal_reference/models/models.py b/product_unique_internal_reference/models/models.py new file mode 100644 index 000000000..b3cf460e5 --- /dev/null +++ b/product_unique_internal_reference/models/models.py @@ -0,0 +1,15 @@ +# Copyright NuoBiT Solutions, S.L. () +# Eric Antones +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) + +from odoo import models +from odoo.tools.translate import _ + + +class ProductProduct(models.Model): + _inherit = "product.product" + + _sql_constraints = [ + ('default_code_uniq', 'unique(company_id, default_code)', + _("A internal reference can only be assigned to one product per company!")), + ] diff --git a/product_unique_internal_reference/static/description/icon.png b/product_unique_internal_reference/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..1cd641e792c30455187ca30940bc0f329ce8bbb0 GIT binary patch literal 6342 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 literal 0 HcmV?d00001 From 00d49f9d5000ac87e9879350a8833a4dcd8124cd Mon Sep 17 00:00:00 2001 From: Eric Antones Date: Wed, 2 Jan 2019 18:24:38 +0100 Subject: [PATCH 02/11] [REF] product_unique_internal_reference: integrity by soft constraint --- product_unique_internal_reference/README.rst | 4 +++- .../__manifest__.py | 4 ++-- product_unique_internal_reference/hooks.py | 12 ++++++---- .../models/models.py | 24 ++++++++++++++----- 4 files changed, 30 insertions(+), 14 deletions(-) diff --git a/product_unique_internal_reference/README.rst b/product_unique_internal_reference/README.rst index eae16d37c..eea067a7f 100644 --- a/product_unique_internal_reference/README.rst +++ b/product_unique_internal_reference/README.rst @@ -6,7 +6,9 @@ Product unique internal reference ================================= -* This module ensures that you enter a 'Unique' Internal Reference (default_code) for your Products +* This module ensures that you enter a 'Unique' Internal Reference (default_code) + for your Products. +* Multi company aware Bug Tracker =========== diff --git a/product_unique_internal_reference/__manifest__.py b/product_unique_internal_reference/__manifest__.py index 3b843684f..37fc00066 100644 --- a/product_unique_internal_reference/__manifest__.py +++ b/product_unique_internal_reference/__manifest__.py @@ -5,13 +5,13 @@ { 'name': 'Product unique internal reference', 'summary': 'This module ensures that you enter a Unique Internal Reference (default_code) for your Products', - 'version': '11.0.0.1.0', + 'version': '11.0.0.2.0', 'category': 'Sales', 'author': 'NuoBiT Solutions, S.L., Eric Antones', 'website': 'https://www.nuobit.com', 'license': 'AGPL-3', 'depends': [ - 'product_variant_company_aware', + 'product', ], 'pre_init_hook': 'pre_init_hook_internal_reference_check', 'installable': True, diff --git a/product_unique_internal_reference/hooks.py b/product_unique_internal_reference/hooks.py index 8e9fdd44e..91ddda361 100644 --- a/product_unique_internal_reference/hooks.py +++ b/product_unique_internal_reference/hooks.py @@ -13,12 +13,14 @@ def pre_init_hook_internal_reference_check(cr): Database cursor. """ with cr.savepoint(): - cr.execute("""SELECT distinct p0.company_id, p0.default_code - FROM product_product p0 - WHERE EXISTS ( + cr.execute("""SELECT distinct t0.company_id, p0.default_code + FROM product_product p0, product_template t0 + WHERE p0.product_tmpl_id = t0.id AND + EXISTS ( SELECT 1 - FROM product_product p1 - where p1.company_id = p0.company_id and + FROM product_product p1, product_template t1 + where p1.product_tmpl_id = t1.id AND + t1.company_id = t0.company_id and p1.default_code = p0.default_code AND p1.id != p0.id ) diff --git a/product_unique_internal_reference/models/models.py b/product_unique_internal_reference/models/models.py index b3cf460e5..1d088ee63 100644 --- a/product_unique_internal_reference/models/models.py +++ b/product_unique_internal_reference/models/models.py @@ -2,14 +2,26 @@ # Eric Antones # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) -from odoo import models +from odoo import models, api from odoo.tools.translate import _ +from odoo.exceptions import ValidationError class ProductProduct(models.Model): - _inherit = "product.product" + _inherit = 'product.product' - _sql_constraints = [ - ('default_code_uniq', 'unique(company_id, default_code)', - _("A internal reference can only be assigned to one product per company!")), - ] + @api.constrains('default_code') + def _check_default_code(self): + for record in self: + if record.default_code: + default_code = self.search([ + ('product_tmpl_id.company_id', '=', record.product_tmpl_id.company_id.id), + ('default_code', '=', record.default_code), + ('id', '!=', record.id), + ], limit=1) + + if default_code: + raise ValidationError( + _("Error! The Default Code %s already exists" % + record.default_code) + ) From 25d9b8dcc4fd258c6bd9806755d56d54ca47debc Mon Sep 17 00:00:00 2001 From: Eric Antones Date: Fri, 17 Jan 2020 18:46:58 +0100 Subject: [PATCH 03/11] [FIX] product_unique_internal_reference: check duplicates in inactive products --- product_unique_internal_reference/__manifest__.py | 2 +- product_unique_internal_reference/models/models.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/product_unique_internal_reference/__manifest__.py b/product_unique_internal_reference/__manifest__.py index 37fc00066..5a1077075 100644 --- a/product_unique_internal_reference/__manifest__.py +++ b/product_unique_internal_reference/__manifest__.py @@ -5,7 +5,7 @@ { 'name': 'Product unique internal reference', 'summary': 'This module ensures that you enter a Unique Internal Reference (default_code) for your Products', - 'version': '11.0.0.2.0', + 'version': '11.0.0.2.1', 'category': 'Sales', 'author': 'NuoBiT Solutions, S.L., Eric Antones', 'website': 'https://www.nuobit.com', diff --git a/product_unique_internal_reference/models/models.py b/product_unique_internal_reference/models/models.py index 1d088ee63..ea0ae202d 100644 --- a/product_unique_internal_reference/models/models.py +++ b/product_unique_internal_reference/models/models.py @@ -14,7 +14,7 @@ class ProductProduct(models.Model): def _check_default_code(self): for record in self: if record.default_code: - default_code = self.search([ + default_code = self.with_context(active_test=False).search([ ('product_tmpl_id.company_id', '=', record.product_tmpl_id.company_id.id), ('default_code', '=', record.default_code), ('id', '!=', record.id), @@ -22,6 +22,6 @@ def _check_default_code(self): if default_code: raise ValidationError( - _("Error! The Default Code %s already exists" % + _("Error! The Default Code %s already exists. Check also the archived ones." % record.default_code) ) From fcd90cfc590c0a4be8bad928b129a62954de867f Mon Sep 17 00:00:00 2001 From: Eric Antones Date: Fri, 17 Jan 2020 18:58:40 +0100 Subject: [PATCH 04/11] [TRL] product_unique_internal_reference: added spanish translation --- .../__manifest__.py | 2 +- product_unique_internal_reference/i18n/es.po | 33 +++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 product_unique_internal_reference/i18n/es.po diff --git a/product_unique_internal_reference/__manifest__.py b/product_unique_internal_reference/__manifest__.py index 5a1077075..40b7d68ce 100644 --- a/product_unique_internal_reference/__manifest__.py +++ b/product_unique_internal_reference/__manifest__.py @@ -5,7 +5,7 @@ { 'name': 'Product unique internal reference', 'summary': 'This module ensures that you enter a Unique Internal Reference (default_code) for your Products', - 'version': '11.0.0.2.1', + 'version': '11.0.0.2.2', 'category': 'Sales', 'author': 'NuoBiT Solutions, S.L., Eric Antones', 'website': 'https://www.nuobit.com', diff --git a/product_unique_internal_reference/i18n/es.po b/product_unique_internal_reference/i18n/es.po new file mode 100644 index 000000000..a2932e17e --- /dev/null +++ b/product_unique_internal_reference/i18n/es.po @@ -0,0 +1,33 @@ +# This file contains the translation of the following modules: +# * product_unique_internal_reference +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 11.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2020-01-17 17:48+0000\n" +"PO-Revision-Date: 2020-01-17 17:48+0000\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: product_unique_internal_reference +#: code:addons/product_unique_internal_reference/hooks.py:32 +#, python-format +msgid "Conflicting internal references exist: %s, " +msgstr "Existe un conflicto de referencias internas: %s, " + +#. module: product_unique_internal_reference +#: code:addons/product_unique_internal_reference/models/models.py:25 +#, python-format +msgid "Error! The Default Code %s already exists. Check also the archived ones." +msgstr "Error! La referencia interna %s ya existe. Comprobar también los productos archivados." + +#. module: product_unique_internal_reference +#: model:ir.model,name:product_unique_internal_reference.model_product_product +msgid "Product" +msgstr "Producto" + From 34b864e0af4b8533bf2b952805e52ffd943f0fac Mon Sep 17 00:00:00 2001 From: Eric Antones Date: Mon, 29 Mar 2021 23:41:08 +0200 Subject: [PATCH 05/11] [IMP] product_unique_internal_reference: black, isort, prettier --- .../__manifest__.py | 24 +++++++------- product_unique_internal_reference/hooks.py | 14 +++++---- .../models/models.py | 31 ++++++++++++------- 3 files changed, 40 insertions(+), 29 deletions(-) diff --git a/product_unique_internal_reference/__manifest__.py b/product_unique_internal_reference/__manifest__.py index 40b7d68ce..58c247b08 100644 --- a/product_unique_internal_reference/__manifest__.py +++ b/product_unique_internal_reference/__manifest__.py @@ -3,17 +3,17 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) { - 'name': 'Product unique internal reference', - 'summary': 'This module ensures that you enter a Unique Internal Reference (default_code) for your Products', - 'version': '11.0.0.2.2', - 'category': 'Sales', - 'author': 'NuoBiT Solutions, S.L., Eric Antones', - 'website': 'https://www.nuobit.com', - 'license': 'AGPL-3', - 'depends': [ - 'product', + "name": "Product unique internal reference", + "summary": "This module ensures that you enter a Unique Internal Reference (default_code) for your Products", + "version": "11.0.0.2.2", + "category": "Sales", + "author": "NuoBiT Solutions, S.L., Eric Antones", + "website": "https://github.com/OCA/pms", + "license": "AGPL-3", + "depends": [ + "product", ], - 'pre_init_hook': 'pre_init_hook_internal_reference_check', - 'installable': True, - 'auto_install': False, + "pre_init_hook": "pre_init_hook_internal_reference_check", + "installable": True, + "auto_install": False, } diff --git a/product_unique_internal_reference/hooks.py b/product_unique_internal_reference/hooks.py index 91ddda361..b92fb42e9 100644 --- a/product_unique_internal_reference/hooks.py +++ b/product_unique_internal_reference/hooks.py @@ -13,21 +13,23 @@ def pre_init_hook_internal_reference_check(cr): Database cursor. """ with cr.savepoint(): - cr.execute("""SELECT distinct t0.company_id, p0.default_code + cr.execute( + """SELECT distinct t0.company_id, p0.default_code FROM product_product p0, product_template t0 WHERE p0.product_tmpl_id = t0.id AND EXISTS ( SELECT 1 FROM product_product p1, product_template t1 - where p1.product_tmpl_id = t1.id AND + where p1.product_tmpl_id = t1.id AND t1.company_id = t0.company_id and - p1.default_code = p0.default_code AND + p1.default_code = p0.default_code AND p1.id != p0.id ) - """) + """ + ) - products = sorted(['[%i] %s' % p for p in cr.fetchall()]) + products = sorted(["[%i] %s" % p for p in cr.fetchall()]) if products: raise ValidationError( - _('Conflicting internal references exist: %s' % ', '.join(products)) + _("Conflicting internal references exist: %s" % ", ".join(products)) ) diff --git a/product_unique_internal_reference/models/models.py b/product_unique_internal_reference/models/models.py index ea0ae202d..f06c64a0b 100644 --- a/product_unique_internal_reference/models/models.py +++ b/product_unique_internal_reference/models/models.py @@ -2,26 +2,35 @@ # Eric Antones # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) -from odoo import models, api -from odoo.tools.translate import _ +from odoo import api, models from odoo.exceptions import ValidationError +from odoo.tools.translate import _ class ProductProduct(models.Model): - _inherit = 'product.product' + _inherit = "product.product" - @api.constrains('default_code') + @api.constrains("default_code") def _check_default_code(self): for record in self: if record.default_code: - default_code = self.with_context(active_test=False).search([ - ('product_tmpl_id.company_id', '=', record.product_tmpl_id.company_id.id), - ('default_code', '=', record.default_code), - ('id', '!=', record.id), - ], limit=1) + default_code = self.with_context(active_test=False).search( + [ + ( + "product_tmpl_id.company_id", + "=", + record.product_tmpl_id.company_id.id, + ), + ("default_code", "=", record.default_code), + ("id", "!=", record.id), + ], + limit=1, + ) if default_code: raise ValidationError( - _("Error! The Default Code %s already exists. Check also the archived ones." % - record.default_code) + _( + "Error! The Default Code %s already exists. Check also the archived ones." + % record.default_code + ) ) From a16045a3810be9e0eaedc92bda38addae39584f8 Mon Sep 17 00:00:00 2001 From: Eric Antones Date: Tue, 30 Mar 2021 00:00:40 +0200 Subject: [PATCH 06/11] [MIG] product_unique_internal_reference: Migration to 12.0 --- product_unique_internal_reference/README.rst | 3 +- product_unique_internal_reference/__init__.py | 2 +- .../__manifest__.py | 8 +- product_unique_internal_reference/hooks.py | 31 +- product_unique_internal_reference/i18n/es.po | 4 +- .../models/__init__.py | 3 +- .../models/models.py | 36 -- .../models/product_product.py | 46 ++ .../models/product_template.py | 26 + .../tests/__init__.py | 5 + .../test_product_unique_internal_reference.py | 554 ++++++++++++++++++ 11 files changed, 658 insertions(+), 60 deletions(-) delete mode 100644 product_unique_internal_reference/models/models.py create mode 100644 product_unique_internal_reference/models/product_product.py create mode 100644 product_unique_internal_reference/models/product_template.py create mode 100644 product_unique_internal_reference/tests/__init__.py create mode 100644 product_unique_internal_reference/tests/test_product_unique_internal_reference.py diff --git a/product_unique_internal_reference/README.rst b/product_unique_internal_reference/README.rst index eea067a7f..fc594f93b 100644 --- a/product_unique_internal_reference/README.rst +++ b/product_unique_internal_reference/README.rst @@ -24,7 +24,8 @@ Credits Contributors ------------ -* Eric Antones +* `NuoBiT Solutions, S.L.`: + * Eric Antones diff --git a/product_unique_internal_reference/__init__.py b/product_unique_internal_reference/__init__.py index 6f2ec1e8e..7b362e13a 100644 --- a/product_unique_internal_reference/__init__.py +++ b/product_unique_internal_reference/__init__.py @@ -3,4 +3,4 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) from . import models -from .hooks import pre_init_hook_internal_reference_check +from .hooks import internal_reference_duplicate_check diff --git a/product_unique_internal_reference/__manifest__.py b/product_unique_internal_reference/__manifest__.py index 58c247b08..185b6489f 100644 --- a/product_unique_internal_reference/__manifest__.py +++ b/product_unique_internal_reference/__manifest__.py @@ -4,8 +4,9 @@ { "name": "Product unique internal reference", - "summary": "This module ensures that you enter a Unique Internal Reference (default_code) for your Products", - "version": "11.0.0.2.2", + "summary": "This module ensures that you enter a " + "Unique Internal Reference (default_code) for your Products", + "version": "12.0.1.0.0", "category": "Sales", "author": "NuoBiT Solutions, S.L., Eric Antones", "website": "https://github.com/OCA/pms", @@ -13,7 +14,6 @@ "depends": [ "product", ], - "pre_init_hook": "pre_init_hook_internal_reference_check", + "pre_init_hook": "internal_reference_duplicate_check", "installable": True, - "auto_install": False, } diff --git a/product_unique_internal_reference/hooks.py b/product_unique_internal_reference/hooks.py index b92fb42e9..f02df58c2 100644 --- a/product_unique_internal_reference/hooks.py +++ b/product_unique_internal_reference/hooks.py @@ -6,7 +6,7 @@ from odoo.exceptions import ValidationError -def pre_init_hook_internal_reference_check(cr): +def internal_reference_duplicate_check(cr): """This hook will look to see if any conflicting internal references exist before the module is installed :param odoo.sql_db.Cursor cr: @@ -14,21 +14,22 @@ def pre_init_hook_internal_reference_check(cr): """ with cr.savepoint(): cr.execute( - """SELECT distinct t0.company_id, p0.default_code - FROM product_product p0, product_template t0 - WHERE p0.product_tmpl_id = t0.id AND - EXISTS ( - SELECT 1 - FROM product_product p1, product_template t1 - where p1.product_tmpl_id = t1.id AND - t1.company_id = t0.company_id and - p1.default_code = p0.default_code AND - p1.id != p0.id - ) - """ + """ + SELECT p0.id, p0.default_code, t0.company_id + FROM product_product p0, product_template t0 + WHERE p0.product_tmpl_id = t0.id AND + EXISTS ( + SELECT 1 + FROM product_product p1, product_template t1 + WHERE p1.product_tmpl_id = t1.id AND + (t1.company_id is null OR t0.company_id is null OR + t1.company_id = t0.company_id ) AND + p1.default_code = p0.default_code AND + p1.id != p0.id + ) + """ ) - - products = sorted(["[%i] %s" % p for p in cr.fetchall()]) + products = sorted(["[%i] %s (%i)" % p for p in cr.fetchall()]) if products: raise ValidationError( _("Conflicting internal references exist: %s" % ", ".join(products)) diff --git a/product_unique_internal_reference/i18n/es.po b/product_unique_internal_reference/i18n/es.po index a2932e17e..d71c7e466 100644 --- a/product_unique_internal_reference/i18n/es.po +++ b/product_unique_internal_reference/i18n/es.po @@ -3,7 +3,7 @@ # msgid "" msgstr "" -"Project-Id-Version: Odoo Server 11.0\n" +"Project-Id-Version: Odoo Server 12.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2020-01-17 17:48+0000\n" "PO-Revision-Date: 2020-01-17 17:48+0000\n" @@ -21,7 +21,7 @@ msgid "Conflicting internal references exist: %s, " msgstr "Existe un conflicto de referencias internas: %s, " #. module: product_unique_internal_reference -#: code:addons/product_unique_internal_reference/models/models.py:25 +#: code:addons/product_unique_internal_reference/models/product_product.py:25 #, python-format msgid "Error! The Default Code %s already exists. Check also the archived ones." msgstr "Error! La referencia interna %s ya existe. Comprobar también los productos archivados." diff --git a/product_unique_internal_reference/models/__init__.py b/product_unique_internal_reference/models/__init__.py index 38b2ba65b..664f6356b 100644 --- a/product_unique_internal_reference/models/__init__.py +++ b/product_unique_internal_reference/models/__init__.py @@ -2,4 +2,5 @@ # Eric Antones # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) -from . import models +from . import product_product +from . import product_template diff --git a/product_unique_internal_reference/models/models.py b/product_unique_internal_reference/models/models.py deleted file mode 100644 index f06c64a0b..000000000 --- a/product_unique_internal_reference/models/models.py +++ /dev/null @@ -1,36 +0,0 @@ -# Copyright NuoBiT Solutions, S.L. () -# Eric Antones -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) - -from odoo import api, models -from odoo.exceptions import ValidationError -from odoo.tools.translate import _ - - -class ProductProduct(models.Model): - _inherit = "product.product" - - @api.constrains("default_code") - def _check_default_code(self): - for record in self: - if record.default_code: - default_code = self.with_context(active_test=False).search( - [ - ( - "product_tmpl_id.company_id", - "=", - record.product_tmpl_id.company_id.id, - ), - ("default_code", "=", record.default_code), - ("id", "!=", record.id), - ], - limit=1, - ) - - if default_code: - raise ValidationError( - _( - "Error! The Default Code %s already exists. Check also the archived ones." - % record.default_code - ) - ) diff --git a/product_unique_internal_reference/models/product_product.py b/product_unique_internal_reference/models/product_product.py new file mode 100644 index 000000000..86ba3c70d --- /dev/null +++ b/product_unique_internal_reference/models/product_product.py @@ -0,0 +1,46 @@ +# Copyright NuoBiT Solutions, S.L. () +# Eric Antones +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) + +from odoo import api, models +from odoo.exceptions import ValidationError +from odoo.tools.translate import _ + + +class ProductProduct(models.Model): + _inherit = "product.product" + + @api.constrains("default_code") + def _check_default_code(self, template_default_code=None): + for record in self: + default_code = template_default_code or record.default_code + if default_code: + domain = [ + ("default_code", "=", default_code), + ("id", "!=", record.id), + ] + if record.product_tmpl_id.company_id: + domain += [ + "|", + ( + "product_tmpl_id.company_id", + "=", + record.product_tmpl_id.company_id.id, + ), + ("product_tmpl_id.company_id", "=", False), + ] + product = ( + self.sudo() + .with_context(active_test=False) + .search( + domain, + limit=1, + ) + ) + if product: + raise ValidationError( + _( + "Error! The Default Code %s already exists. " + "Check also the archived ones." % product.default_code + ) + ) diff --git a/product_unique_internal_reference/models/product_template.py b/product_unique_internal_reference/models/product_template.py new file mode 100644 index 000000000..6fb772685 --- /dev/null +++ b/product_unique_internal_reference/models/product_template.py @@ -0,0 +1,26 @@ +# Copyright NuoBiT Solutions, S.L. () +# Eric Antones +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) + +from odoo import api, models + + +class ProductTemplate(models.Model): + _inherit = "product.template" + + @api.constrains("default_code", "company_id") + def _check_default_code(self): + for record in self: + products = ( + self.env["product.product"] + .sudo() + .with_context(active_test=False) + .search( + [ + ("product_tmpl_id", "=", record.id), + ("default_code", "!=", False), + ] + ) + ) + for product in products: + product._check_default_code(record.default_code) diff --git a/product_unique_internal_reference/tests/__init__.py b/product_unique_internal_reference/tests/__init__.py new file mode 100644 index 000000000..af7b35919 --- /dev/null +++ b/product_unique_internal_reference/tests/__init__.py @@ -0,0 +1,5 @@ +# Copyright NuoBiT Solutions, S.L. () +# Eric Antones +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) + +from . import test_product_unique_internal_reference diff --git a/product_unique_internal_reference/tests/test_product_unique_internal_reference.py b/product_unique_internal_reference/tests/test_product_unique_internal_reference.py new file mode 100644 index 000000000..618857103 --- /dev/null +++ b/product_unique_internal_reference/tests/test_product_unique_internal_reference.py @@ -0,0 +1,554 @@ +# Copyright 2021 Eric Antones +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +import logging + +from odoo import _ +from odoo.exceptions import ValidationError +from odoo.tests.common import SavepointCase + +_logger = logging.getLogger(__name__) + + +class TestProductUniqueInternalReference(SavepointCase): + def test_01_template_vs_template_ok_same_company(self): + """ + PRE: - template1 exists + - template1 has default_code 'sku1' + - template1 has company1 + ACT: - create template2 + - template2 has default_code 'sku2' + POST: - template2 is created correctly + """ + # ARRANGE + company1 = self.env["res.company"].create( + { + "name": "Company 1", + } + ) + self.env["product.template"].create( + { + "name": "Product 1", + "default_code": "sku1", + "company_id": company1.id, + } + ) + # ACT & ASSERT + try: + self.env["product.template"].create( + { + "name": "Product 2", + "default_code": "sku2", + "company_id": company1.id, + } + ) + except ValidationError: + self.fail( + _( + "The Internal References are not equal, " + "this should have not raised an Exception" + ) + ) + + def test_02_template_vs_template_ok_diff_company(self): + """ + PRE: - template1 exists + - template1 has default_code 'sku1' + - template1 has company1 + ACT: - create template2 + - template2 has company2 + - template2 has default_code 'sku2' + POST: - template2 is created correctly + """ + # ARRANGE + company1 = self.env["res.company"].create( + { + "name": "Company 1", + } + ) + self.env["product.template"].create( + { + "name": "Product 1", + "default_code": "sku1", + "company_id": company1.id, + } + ) + company2 = self.env["res.company"].create( + { + "name": "Company 2", + } + ) + # ACT & ASSERT + try: + self.env["product.template"].create( + { + "name": "Product 2", + "default_code": "sku2", + "company_id": company2.id, + } + ) + except ValidationError: + self.fail( + _( + "The Internal References are not equal, " + "this should have not raised an Exception" + ) + ) + + def test_03_template_vs_template_dup_same_company(self): + """ + PRE: - template1 exists + - template1 has default_code 'sku1' + - template1 has company1 + ACT: - create template2 + - template2 has default_code 'sku1' + - template2 has company1 + POST: - template2 is not created because exists + template1 with same default_code 'sku1' + on the same company1 + """ + # ARRANGE + company1 = self.env["res.company"].create( + { + "name": "Company 1", + } + ) + self.env["product.template"].create( + { + "name": "Product 1", + "default_code": "sku1", + "company_id": company1.id, + } + ) + # ACT & ASSERT + try: + with self.assertRaises(ValidationError): + self.env["product.template"].create( + { + "name": "Product 2", + "default_code": "sku1", + "company_id": company1.id, + } + ) + except AssertionError: + self.fail( + _( + "It has been possible to create two product templates " + "with the same Internal Reference" + ) + ) + + def test_04_template_vs_template_dup_diff_company(self): + """ + PRE: - template1 exists + - template1 has default_code 'sku1' + - template1 has company1 + ACT: - create template2 + - template2 has default_code 'sku1' + - template2 has company2 + POST: - template2 is created correctly + """ + # ARRANGE + company1 = self.env["res.company"].create( + { + "name": "Company 1", + } + ) + self.env["product.template"].create( + { + "name": "Product 1", + "default_code": "sku1", + "company_id": company1.id, + } + ) + company2 = self.env["res.company"].create( + { + "name": "Company 2", + } + ) + # ACT & ASSERT + try: + self.env["product.template"].create( + { + "name": "Product 2", + "default_code": "sku1", + "company_id": company2.id, + } + ) + except ValidationError: + self.fail( + _( + "The Internal References are not equal, " + "this should have not raised an Exception" + ) + ) + + def test_05_template_vs_template_dup_company_vs_nocompany(self): + """ + PRE: - template1 exists + - template1 has default_code 'sku1' + - template1 has company1 + ACT: - create template2 + - template2 has default_code 'sku1' + - template2 has no company + POST: - template2 is not created because exists + template1 with same default_code 'sku1' + on company1 and template2 has no company + which means 'all companies' + """ + # ARRANGE + company1 = self.env["res.company"].create( + { + "name": "Company 1", + } + ) + self.env["product.template"].create( + { + "name": "Product 1", + "default_code": "sku1", + "company_id": company1.id, + } + ) + # ACT & ASSERT + try: + with self.assertRaises(ValidationError): + self.env["product.template"].create( + { + "name": "Product 2", + "default_code": "sku1", + "company_id": False, + } + ) + except AssertionError: + self.fail( + _( + "It has been possible to create a product template on " + "one company and another without company with the same " + "Internal Reference" + ) + ) + + def test_06_product_vs_template_uniq_same_company(self): + """ + PRE: - template1 exists + - template1 has variant11 and variant12 + - variant11 has default_code 'sku1' + - variant12 has default_code 'sku2' + - template1 has company1 + ACT: - create template2 + - template2 has default_code 'sku2' + - template2 has company1 + POST: - template2 is not created because exists + variant12 with same default_code 'sku1' + on the same company1 + """ + # ARRANGE + company1 = self.env["res.company"].create( + { + "name": "Company 1", + } + ) + attribute = self.env["product.attribute"].create( + { + "name": "Attribute 1", + "create_variant": "always", + "value_ids": [ + (0, 0, {"name": "Attribute Value 1"}), + (0, 0, {"name": "Attribute Value 2"}), + ], + } + ) + template = self.env["product.template"].create( + { + "name": "Product 1", + "attribute_line_ids": [ + ( + 0, + 0, + { + "attribute_id": attribute.id, + "value_ids": [(6, 0, attribute.value_ids.mapped("id"))], + }, + ) + ], + "company_id": company1.id, + } + ) + template.product_variant_ids[0].default_code = "sku1" + template.product_variant_ids[1].default_code = "sku2" + # ACT & ASSERT + try: + with self.assertRaises(ValidationError): + self.env["product.template"].create( + { + "name": "Product 2", + "default_code": "sku2", + "company_id": company1.id, + } + ) + except AssertionError: + self.fail( + _( + "It has been possible to create a product template " + "when a variant of another template has the same " + "Internal Reference" + ) + ) + + def test_07_product_vs_product_uniq_same_company(self): + """ + PRE: - template1 exists + - template1 has variant11 and variant12 + - variant11 has default_code 'sku1' + - variant12 has default_code 'sku2' + - template1 has company1 + ACT: - create template2 + - template2 has company1 + - template2 has variant21 and variant22 and variant23 + - variant21 has default_code 'sku4' + - variant22 has default_code 'sku3' + - variant23 has default_code 'sku2' + POST: - template2 is not created because exists + variant23 with same default_code 'sku1' + as variant12 on the same company1 + """ + # ARRANGE + company1 = self.env["res.company"].create( + { + "name": "Company 1", + } + ) + attribute1 = self.env["product.attribute"].create( + { + "name": "Attribute 1", + "create_variant": "always", + "value_ids": [ + (0, 0, {"name": "Attribute Value 1"}), + (0, 0, {"name": "Attribute Value 2"}), + ], + } + ) + template1 = self.env["product.template"].create( + { + "name": "Product 1", + "attribute_line_ids": [ + ( + 0, + 0, + { + "attribute_id": attribute1.id, + "value_ids": [(6, 0, attribute1.value_ids.mapped("id"))], + }, + ) + ], + "company_id": company1.id, + } + ) + template1.product_variant_ids[0].default_code = "sku1" + template1.product_variant_ids[1].default_code = "sku2" + + attribute2 = self.env["product.attribute"].create( + { + "name": "Attribute 2", + "create_variant": "always", + "value_ids": [ + (0, 0, {"name": "Attribute Value 3"}), + (0, 0, {"name": "Attribute Value 4"}), + (0, 0, {"name": "Attribute Value 5"}), + ], + } + ) + template2 = self.env["product.template"].create( + { + "name": "Product 2", + "attribute_line_ids": [ + ( + 0, + 0, + { + "attribute_id": attribute2.id, + "value_ids": [(6, 0, attribute2.value_ids.mapped("id"))], + }, + ) + ], + "company_id": company1.id, + } + ) + + # ACT & ASSERT + try: + with self.assertRaises(ValidationError): + template2.product_variant_ids[0].default_code = "sku4" + template2.product_variant_ids[1].default_code = "sku3" + template2.product_variant_ids[2].default_code = "sku2" + except AssertionError: + self.fail( + _( + "It has been possible to create a product variant " + "when a variant of another template has the same " + "Internal Reference" + ) + ) + + def test_08_product_vs_product_dup_company_vs_nocompany(self): + """ + PRE: - template1 exists + - template1 has variant11 and variant12 + - variant11 has default_code 'sku1' + - variant12 has default_code 'sku2' + - template1 has company1 + - template2 exists + - template2 has no company + - template2 has variant21 and variant22 and variant23 + - variant21, variant22, variant23 has no default_code + ACT: - change variant21 default_code to 'sku4' + - change variant22 default_code to 'sku3' + - change variant23 default_code to 'sku2' + POST: - variant default_code are not updated because + variant23 has the same default_code as variant12 'sku2' + and they belong to the same company1 because + template2 has no company and it means 'all companies' + """ + # ARRANGE + company1 = self.env["res.company"].create( + { + "name": "Company 1", + } + ) + attribute1 = self.env["product.attribute"].create( + { + "name": "Attribute 1", + "create_variant": "always", + "value_ids": [ + (0, 0, {"name": "Attribute Value 1"}), + (0, 0, {"name": "Attribute Value 2"}), + ], + } + ) + template1 = self.env["product.template"].create( + { + "name": "Product 1", + "attribute_line_ids": [ + ( + 0, + 0, + { + "attribute_id": attribute1.id, + "value_ids": [(6, 0, attribute1.value_ids.mapped("id"))], + }, + ) + ], + "company_id": company1.id, + } + ) + template1.product_variant_ids[0].default_code = "sku1" + template1.product_variant_ids[1].default_code = "sku2" + + attribute2 = self.env["product.attribute"].create( + { + "name": "Attribute 2", + "create_variant": "always", + "value_ids": [ + (0, 0, {"name": "Attribute Value 3"}), + (0, 0, {"name": "Attribute Value 4"}), + (0, 0, {"name": "Attribute Value 5"}), + ], + } + ) + template2 = self.env["product.template"].create( + { + "name": "Product 2", + "attribute_line_ids": [ + ( + 0, + 0, + { + "attribute_id": attribute2.id, + "value_ids": [(6, 0, attribute2.value_ids.mapped("id"))], + }, + ) + ], + "company_id": False, + } + ) + + # ACT & ASSERT + try: + with self.assertRaises(ValidationError): + template2.product_variant_ids[0].default_code = "sku4" + template2.product_variant_ids[1].default_code = "sku3" + template2.product_variant_ids[2].default_code = "sku2" + except AssertionError: + self.fail( + _( + "It has been possible to create a product variant " + "when a variant of another template has the same " + "Internal Reference" + ) + ) + + def test_09_template_vs_product_dup_archived(self): + """ + PRE: - template1 exists + - template1 has variant11 and variant12 and variant13 + - variant11 has default_code 'sku1' + - variant12 has default_code 'sku2' and is archived + - variant13 has default_code 'sku3' and is archived + ACT: - change template1 default_code to 'sku1' + POST: - template1 is not updated because + variant11 has the same default_code 'sku1' + """ + # ARRANGE + company1 = self.env["res.company"].create( + { + "name": "Company 1", + } + ) + attribute1 = self.env["product.attribute"].create( + { + "name": "Attribute 1", + "create_variant": "always", + "value_ids": [ + (0, 0, {"name": "Attribute Value 1"}), + (0, 0, {"name": "Attribute Value 2"}), + (0, 0, {"name": "Attribute Value 3"}), + ], + } + ) + template1 = self.env["product.template"].create( + { + "name": "Product 1", + "attribute_line_ids": [ + ( + 0, + 0, + { + "attribute_id": attribute1.id, + "value_ids": [(6, 0, attribute1.value_ids.mapped("id"))], + }, + ) + ], + "company_id": company1.id, + } + ) + variant1 = template1.product_variant_ids[0] + variant2 = template1.product_variant_ids[1] + variant3 = template1.product_variant_ids[2] + + variant1.default_code = "sku1" + variant2.default_code = "sku2" + variant3.default_code = "sku3" + + variant2.active = False + variant3.active = False + + # ACT & ASSERT + try: + with self.assertRaises(ValidationError): + template1.default_code = "sku1" + except AssertionError: + self.fail( + _( + "It has been possible to change the template " + "Internal Reference existing a variant with the same one" + ) + ) From e9ca60afc6f046a3cc84c8891520e9ad1aa94493 Mon Sep 17 00:00:00 2001 From: mreficent Date: Mon, 30 Aug 2021 12:12:03 +0200 Subject: [PATCH 07/11] [IMP] product_unique_internal_reference: black, isort, prettier --- .../__manifest__.py | 2 +- product_unique_internal_reference/hooks.py | 18 ++++++++---------- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/product_unique_internal_reference/__manifest__.py b/product_unique_internal_reference/__manifest__.py index 185b6489f..bc63c369b 100644 --- a/product_unique_internal_reference/__manifest__.py +++ b/product_unique_internal_reference/__manifest__.py @@ -9,7 +9,7 @@ "version": "12.0.1.0.0", "category": "Sales", "author": "NuoBiT Solutions, S.L., Eric Antones", - "website": "https://github.com/OCA/pms", + "website": "https://github.com/nuobit/odoo-addons", "license": "AGPL-3", "depends": [ "product", diff --git a/product_unique_internal_reference/hooks.py b/product_unique_internal_reference/hooks.py index f02df58c2..571c93b48 100644 --- a/product_unique_internal_reference/hooks.py +++ b/product_unique_internal_reference/hooks.py @@ -18,16 +18,14 @@ def internal_reference_duplicate_check(cr): SELECT p0.id, p0.default_code, t0.company_id FROM product_product p0, product_template t0 WHERE p0.product_tmpl_id = t0.id AND - EXISTS ( - SELECT 1 - FROM product_product p1, product_template t1 - WHERE p1.product_tmpl_id = t1.id AND - (t1.company_id is null OR t0.company_id is null OR - t1.company_id = t0.company_id ) AND - p1.default_code = p0.default_code AND - p1.id != p0.id - ) - """ + EXISTS ( + SELECT 1 + FROM product_product p1, product_template t1 + WHERE p1.product_tmpl_id = t1.id AND + (t1.company_id is null OR t0.company_id is null OR + t1.company_id = t0.company_id) AND + p1.default_code = p0.default_code AND p1.id != p0.id + )""" ) products = sorted(["[%i] %s (%i)" % p for p in cr.fetchall()]) if products: From 78473a21e9b4c2e05a75a77a6dbb97c432902e3d Mon Sep 17 00:00:00 2001 From: mreficent Date: Mon, 30 Aug 2021 12:17:33 +0200 Subject: [PATCH 08/11] [MIG] product_unique_internal_reference: Migration to 14.0 --- product_unique_internal_reference/README.rst | 11 +-- product_unique_internal_reference/__init__.py | 2 - .../__manifest__.py | 2 +- product_unique_internal_reference/i18n/es.po | 95 +++++++++++++++++-- .../models/__init__.py | 2 - .../test_product_unique_internal_reference.py | 12 +-- 6 files changed, 95 insertions(+), 29 deletions(-) diff --git a/product_unique_internal_reference/README.rst b/product_unique_internal_reference/README.rst index fc594f93b..7d51542e3 100644 --- a/product_unique_internal_reference/README.rst +++ b/product_unique_internal_reference/README.rst @@ -1,5 +1,5 @@ -.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg - :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html +.. image:: https://img.shields.io/badge/license-AGPL--3-blue.png + :target: https://www.gnu.org/licenses/agpl :alt: License: AGPL-3 ================================= @@ -26,10 +26,3 @@ Contributors * `NuoBiT Solutions, S.L.`: * Eric Antones - - - - - - - diff --git a/product_unique_internal_reference/__init__.py b/product_unique_internal_reference/__init__.py index 7b362e13a..7c418c51c 100644 --- a/product_unique_internal_reference/__init__.py +++ b/product_unique_internal_reference/__init__.py @@ -1,5 +1,3 @@ -# Copyright NuoBiT Solutions, S.L. () -# Eric Antones # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) from . import models diff --git a/product_unique_internal_reference/__manifest__.py b/product_unique_internal_reference/__manifest__.py index bc63c369b..7f2900d67 100644 --- a/product_unique_internal_reference/__manifest__.py +++ b/product_unique_internal_reference/__manifest__.py @@ -6,7 +6,7 @@ "name": "Product unique internal reference", "summary": "This module ensures that you enter a " "Unique Internal Reference (default_code) for your Products", - "version": "12.0.1.0.0", + "version": "14.0.1.0.0", "category": "Sales", "author": "NuoBiT Solutions, S.L., Eric Antones", "website": "https://github.com/nuobit/odoo-addons", diff --git a/product_unique_internal_reference/i18n/es.po b/product_unique_internal_reference/i18n/es.po index d71c7e466..a6ce1d678 100644 --- a/product_unique_internal_reference/i18n/es.po +++ b/product_unique_internal_reference/i18n/es.po @@ -1,13 +1,14 @@ +# Translation of Odoo Server. # This file contains the translation of the following modules: -# * product_unique_internal_reference +# * product_unique_internal_reference # msgid "" msgstr "" -"Project-Id-Version: Odoo Server 12.0\n" +"Project-Id-Version: Odoo Server 14.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2020-01-17 17:48+0000\n" -"PO-Revision-Date: 2020-01-17 17:48+0000\n" -"Last-Translator: <>\n" +"POT-Creation-Date: 2021-12-16 14:21+0000\n" +"PO-Revision-Date: 2021-12-16 14:21+0000\n" +"Last-Translator: \n" "Language-Team: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -15,19 +16,95 @@ msgstr "" "Plural-Forms: \n" #. module: product_unique_internal_reference -#: code:addons/product_unique_internal_reference/hooks.py:32 +#: code:addons/product_unique_internal_reference/hooks.py:0 #, python-format msgid "Conflicting internal references exist: %s, " msgstr "Existe un conflicto de referencias internas: %s, " #. module: product_unique_internal_reference -#: code:addons/product_unique_internal_reference/models/product_product.py:25 +#: model:ir.model.fields,field_description:product_unique_internal_reference.field_product_product__display_name +#: model:ir.model.fields,field_description:product_unique_internal_reference.field_product_template__display_name +msgid "Display Name" +msgstr "Nombre mostrado" + +#. module: product_unique_internal_reference +#: code:addons/product_unique_internal_reference/models/product_product.py:0 +#, python-format +msgid "" +"Error! The Default Code %s already exists. Check also the archived ones." +msgstr "" +"Error! La referencia interna %s ya existe. Comprobar también los productos " +"archivados." + +#. module: product_unique_internal_reference +#: model:ir.model.fields,field_description:product_unique_internal_reference.field_product_product__id +#: model:ir.model.fields,field_description:product_unique_internal_reference.field_product_template__id +msgid "ID" +msgstr "" + +#. module: product_unique_internal_reference +#: code:addons/product_unique_internal_reference/tests/test_product_unique_internal_reference.py:0 +#, python-format +msgid "" +"It has been possible to change the template Internal Reference existing a " +"variant with the same one" +msgstr "" + +#. module: product_unique_internal_reference +#: code:addons/product_unique_internal_reference/tests/test_product_unique_internal_reference.py:0 +#, python-format +msgid "" +"It has been possible to create a product template on one company and another" +" without company with the same Internal Reference" +msgstr "" + +#. module: product_unique_internal_reference +#: code:addons/product_unique_internal_reference/tests/test_product_unique_internal_reference.py:0 #, python-format -msgid "Error! The Default Code %s already exists. Check also the archived ones." -msgstr "Error! La referencia interna %s ya existe. Comprobar también los productos archivados." +msgid "" +"It has been possible to create a product template when a variant of another " +"template has the same Internal Reference" +msgstr "" + +#. module: product_unique_internal_reference +#: code:addons/product_unique_internal_reference/tests/test_product_unique_internal_reference.py:0 +#: code:addons/product_unique_internal_reference/tests/test_product_unique_internal_reference.py:0 +#, python-format +msgid "" +"It has been possible to create a product variant when a variant of another " +"template has the same Internal Reference" +msgstr "" + +#. module: product_unique_internal_reference +#: code:addons/product_unique_internal_reference/tests/test_product_unique_internal_reference.py:0 +#, python-format +msgid "" +"It has been possible to create two product templates with the same Internal " +"Reference" +msgstr "" + +#. module: product_unique_internal_reference +#: model:ir.model.fields,field_description:product_unique_internal_reference.field_product_product____last_update +#: model:ir.model.fields,field_description:product_unique_internal_reference.field_product_template____last_update +msgid "Last Modified on" +msgstr "Última modificación el" #. module: product_unique_internal_reference #: model:ir.model,name:product_unique_internal_reference.model_product_product msgid "Product" msgstr "Producto" +#. module: product_unique_internal_reference +#: model:ir.model,name:product_unique_internal_reference.model_product_template +msgid "Product Template" +msgstr "Plantilla de producto" + +#. module: product_unique_internal_reference +#: code:addons/product_unique_internal_reference/tests/test_product_unique_internal_reference.py:0 +#: code:addons/product_unique_internal_reference/tests/test_product_unique_internal_reference.py:0 +#: code:addons/product_unique_internal_reference/tests/test_product_unique_internal_reference.py:0 +#, python-format +msgid "" +"The Internal References are not equal, this should have not raised an " +"Exception" +msgstr "" diff --git a/product_unique_internal_reference/models/__init__.py b/product_unique_internal_reference/models/__init__.py index 664f6356b..0d8e30d3f 100644 --- a/product_unique_internal_reference/models/__init__.py +++ b/product_unique_internal_reference/models/__init__.py @@ -1,5 +1,3 @@ -# Copyright NuoBiT Solutions, S.L. () -# Eric Antones # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) from . import product_product diff --git a/product_unique_internal_reference/tests/test_product_unique_internal_reference.py b/product_unique_internal_reference/tests/test_product_unique_internal_reference.py index 618857103..5d5dbc3e3 100644 --- a/product_unique_internal_reference/tests/test_product_unique_internal_reference.py +++ b/product_unique_internal_reference/tests/test_product_unique_internal_reference.py @@ -266,7 +266,7 @@ def test_06_product_vs_template_uniq_same_company(self): 0, { "attribute_id": attribute.id, - "value_ids": [(6, 0, attribute.value_ids.mapped("id"))], + "value_ids": [(6, 0, attribute.value_ids.ids)], }, ) ], @@ -336,7 +336,7 @@ def test_07_product_vs_product_uniq_same_company(self): 0, { "attribute_id": attribute1.id, - "value_ids": [(6, 0, attribute1.value_ids.mapped("id"))], + "value_ids": [(6, 0, attribute1.value_ids.ids)], }, ) ], @@ -366,7 +366,7 @@ def test_07_product_vs_product_uniq_same_company(self): 0, { "attribute_id": attribute2.id, - "value_ids": [(6, 0, attribute2.value_ids.mapped("id"))], + "value_ids": [(6, 0, attribute2.value_ids.ids)], }, ) ], @@ -433,7 +433,7 @@ def test_08_product_vs_product_dup_company_vs_nocompany(self): 0, { "attribute_id": attribute1.id, - "value_ids": [(6, 0, attribute1.value_ids.mapped("id"))], + "value_ids": [(6, 0, attribute1.value_ids.ids)], }, ) ], @@ -463,7 +463,7 @@ def test_08_product_vs_product_dup_company_vs_nocompany(self): 0, { "attribute_id": attribute2.id, - "value_ids": [(6, 0, attribute2.value_ids.mapped("id"))], + "value_ids": [(6, 0, attribute2.value_ids.ids)], }, ) ], @@ -523,7 +523,7 @@ def test_09_template_vs_product_dup_archived(self): 0, { "attribute_id": attribute1.id, - "value_ids": [(6, 0, attribute1.value_ids.mapped("id"))], + "value_ids": [(6, 0, attribute1.value_ids.ids)], }, ) ], From a6e3f778b1f6a396bb4f024a40b45cc230eeedb0 Mon Sep 17 00:00:00 2001 From: bijaya Date: Tue, 11 Mar 2025 10:47:23 +0100 Subject: [PATCH 09/11] [MIG] product_unique_internal_reference: Migration to 16.0 --- product_unique_internal_reference/README.rst | 63 ++- product_unique_internal_reference/__init__.py | 2 - .../__manifest__.py | 9 +- product_unique_internal_reference/hooks.py | 4 +- .../models/__init__.py | 2 - .../models/product_product.py | 13 +- .../models/product_template.py | 49 +- .../readme/CONTRIBUTORS.md | 3 + .../readme/DESCRIPTION.md | 3 + .../static/description/index.html | 425 ++++++++++++++++++ .../tests/__init__.py | 4 - .../test_product_unique_internal_reference.py | 26 +- 12 files changed, 546 insertions(+), 57 deletions(-) create mode 100644 product_unique_internal_reference/readme/CONTRIBUTORS.md create mode 100644 product_unique_internal_reference/readme/DESCRIPTION.md create mode 100644 product_unique_internal_reference/static/description/index.html diff --git a/product_unique_internal_reference/README.rst b/product_unique_internal_reference/README.rst index 7d51542e3..d266bef2c 100644 --- a/product_unique_internal_reference/README.rst +++ b/product_unique_internal_reference/README.rst @@ -1,28 +1,65 @@ -.. image:: https://img.shields.io/badge/license-AGPL--3-blue.png - :target: https://www.gnu.org/licenses/agpl - :alt: License: AGPL-3 - ================================= Product unique internal reference ================================= -* This module ensures that you enter a 'Unique' Internal Reference (default_code) - for your Products. -* Multi company aware +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:976812157684886bd583c3621ad271c233ae90a365cbb84fa272c9c9fc0f9075 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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-nuobit%2Fodoo--addons-lightgray.png?logo=github + :target: https://github.com/nuobit/odoo-addons/tree/16.0/product_unique_internal_reference + :alt: nuobit/odoo-addons + +|badge1| |badge2| |badge3| + +- This module ensures that you enter a 'Unique' Internal Reference + (default_code) for your Products. +- Multi company aware + +**Table of contents** + +.. contents:: + :local: 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 smashing it by providing a detailed and welcomed feedback. +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 +------- + +* NuoBiT Solutions SL + Contributors ------------ -* `NuoBiT Solutions, S.L.`: - * Eric Antones +- `NuoBiT `__: + + - Eric Antones eantones@nuobit.com + - Bijaya Kumal bkumal@nuobit.com + +Maintainers +----------- + +This module is part of the `nuobit/odoo-addons `_ project on GitHub. + +You are welcome to contribute. diff --git a/product_unique_internal_reference/__init__.py b/product_unique_internal_reference/__init__.py index 7c418c51c..43e266a34 100644 --- a/product_unique_internal_reference/__init__.py +++ b/product_unique_internal_reference/__init__.py @@ -1,4 +1,2 @@ -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) - from . import models from .hooks import internal_reference_duplicate_check diff --git a/product_unique_internal_reference/__manifest__.py b/product_unique_internal_reference/__manifest__.py index 7f2900d67..8ffbe1165 100644 --- a/product_unique_internal_reference/__manifest__.py +++ b/product_unique_internal_reference/__manifest__.py @@ -1,19 +1,18 @@ -# Copyright NuoBiT Solutions, S.L. () -# Eric Antones +# Copyright NuoBiT - Eric Antones +# Copyright NuoBiT 2025 - Bijaya Kumal # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) { "name": "Product unique internal reference", "summary": "This module ensures that you enter a " "Unique Internal Reference (default_code) for your Products", - "version": "14.0.1.0.0", + "version": "16.0.1.0.0", "category": "Sales", - "author": "NuoBiT Solutions, S.L., Eric Antones", + "author": "NuoBiT Solutions SL", "website": "https://github.com/nuobit/odoo-addons", "license": "AGPL-3", "depends": [ "product", ], "pre_init_hook": "internal_reference_duplicate_check", - "installable": True, } diff --git a/product_unique_internal_reference/hooks.py b/product_unique_internal_reference/hooks.py index 571c93b48..6ffde173d 100644 --- a/product_unique_internal_reference/hooks.py +++ b/product_unique_internal_reference/hooks.py @@ -1,7 +1,7 @@ -# Copyright NuoBiT Solutions, S.L. () -# Eric Antones +# Copyright NuoBiT - Eric Antones # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) + from odoo import _ from odoo.exceptions import ValidationError diff --git a/product_unique_internal_reference/models/__init__.py b/product_unique_internal_reference/models/__init__.py index 0d8e30d3f..18b37e853 100644 --- a/product_unique_internal_reference/models/__init__.py +++ b/product_unique_internal_reference/models/__init__.py @@ -1,4 +1,2 @@ -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) - from . import product_product from . import product_template diff --git a/product_unique_internal_reference/models/product_product.py b/product_unique_internal_reference/models/product_product.py index 86ba3c70d..785f42932 100644 --- a/product_unique_internal_reference/models/product_product.py +++ b/product_unique_internal_reference/models/product_product.py @@ -1,7 +1,7 @@ -# Copyright NuoBiT Solutions, S.L. () -# Eric Antones +# Copyright NuoBiT - Eric Antones # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) + from odoo import api, models from odoo.exceptions import ValidationError from odoo.tools.translate import _ @@ -11,12 +11,11 @@ class ProductProduct(models.Model): _inherit = "product.product" @api.constrains("default_code") - def _check_default_code(self, template_default_code=None): + def _check_default_code(self): for record in self: - default_code = template_default_code or record.default_code - if default_code: + if record.default_code: domain = [ - ("default_code", "=", default_code), + ("default_code", "=", record.default_code), ("id", "!=", record.id), ] if record.product_tmpl_id.company_id: @@ -41,6 +40,6 @@ def _check_default_code(self, template_default_code=None): raise ValidationError( _( "Error! The Default Code %s already exists. " - "Check also the archived ones." % product.default_code + "Check also the archived ones." % record.default_code ) ) diff --git a/product_unique_internal_reference/models/product_template.py b/product_unique_internal_reference/models/product_template.py index 6fb772685..8bd74062c 100644 --- a/product_unique_internal_reference/models/product_template.py +++ b/product_unique_internal_reference/models/product_template.py @@ -1,8 +1,9 @@ -# Copyright NuoBiT Solutions, S.L. () -# Eric Antones +# Copyright NuoBiT - Eric Antones # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) -from odoo import api, models + +from odoo import _, api, models +from odoo.exceptions import ValidationError class ProductTemplate(models.Model): @@ -11,16 +12,34 @@ class ProductTemplate(models.Model): @api.constrains("default_code", "company_id") def _check_default_code(self): for record in self: - products = ( - self.env["product.product"] - .sudo() - .with_context(active_test=False) - .search( - [ - ("product_tmpl_id", "=", record.id), - ("default_code", "!=", False), - ] + if record.default_code: + # all products with the same default_code + products_all = ( + self.env["product.product"] + .sudo() + .with_context(active_test=False) + .search( + [ + ("default_code", "=", record.default_code), + ] + ) + ) + # products inside the same template + products_inside = products_all.filtered( + lambda p: p.product_tmpl_id == record ) - ) - for product in products: - product._check_default_code(record.default_code) + # products outside the same template + products_outside = products_all - products_inside + if record.company_id: + products_outside = products_outside.filtered( + lambda p: not p.product_tmpl_id.company_id + or p.product_tmpl_id.company_id == record.company_id + ) + # check if the default code is used in other templates + if len(products_inside) > 1 or products_outside: + raise ValidationError( + _( + "Error! The Default Code %s already exists. " + "Check also the archived ones." % record.default_code + ) + ) diff --git a/product_unique_internal_reference/readme/CONTRIBUTORS.md b/product_unique_internal_reference/readme/CONTRIBUTORS.md new file mode 100644 index 000000000..09afb8b5c --- /dev/null +++ b/product_unique_internal_reference/readme/CONTRIBUTORS.md @@ -0,0 +1,3 @@ +- [NuoBiT](https://www.nuobit.com): + - Eric Antones + - Bijaya Kumal diff --git a/product_unique_internal_reference/readme/DESCRIPTION.md b/product_unique_internal_reference/readme/DESCRIPTION.md new file mode 100644 index 000000000..7b3ac697a --- /dev/null +++ b/product_unique_internal_reference/readme/DESCRIPTION.md @@ -0,0 +1,3 @@ +* This module ensures that you enter a 'Unique' Internal Reference (default_code) + for your Products. +* Multi company aware diff --git a/product_unique_internal_reference/static/description/index.html b/product_unique_internal_reference/static/description/index.html new file mode 100644 index 000000000..4773042d5 --- /dev/null +++ b/product_unique_internal_reference/static/description/index.html @@ -0,0 +1,425 @@ + + + + + + +Product unique internal reference + + + +
+

Product unique internal reference

+ + +

Beta License: AGPL-3 nuobit/odoo-addons

+
    +
  • This module ensures that you enter a ‘Unique’ Internal Reference +(default_code) for your Products.
  • +
  • Multi company aware
  • +
+

Table of contents

+ +
+

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

+
    +
  • NuoBiT Solutions SL
  • +
+
+
+

Contributors

+ +
+
+

Maintainers

+

This module is part of the nuobit/odoo-addons project on GitHub.

+

You are welcome to contribute.

+
+
+
+ + diff --git a/product_unique_internal_reference/tests/__init__.py b/product_unique_internal_reference/tests/__init__.py index af7b35919..d5fb3a44d 100644 --- a/product_unique_internal_reference/tests/__init__.py +++ b/product_unique_internal_reference/tests/__init__.py @@ -1,5 +1 @@ -# Copyright NuoBiT Solutions, S.L. () -# Eric Antones -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) - from . import test_product_unique_internal_reference diff --git a/product_unique_internal_reference/tests/test_product_unique_internal_reference.py b/product_unique_internal_reference/tests/test_product_unique_internal_reference.py index 5d5dbc3e3..dae3dbfba 100644 --- a/product_unique_internal_reference/tests/test_product_unique_internal_reference.py +++ b/product_unique_internal_reference/tests/test_product_unique_internal_reference.py @@ -1,15 +1,16 @@ -# Copyright 2021 Eric Antones -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +# Copyright NuoBiT - Eric Antones +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) + import logging from odoo import _ from odoo.exceptions import ValidationError -from odoo.tests.common import SavepointCase +from odoo.tests.common import TransactionCase _logger = logging.getLogger(__name__) -class TestProductUniqueInternalReference(SavepointCase): +class TestProductUniqueInternalReference(TransactionCase): def test_01_template_vs_template_ok_same_company(self): """ PRE: - template1 exists @@ -23,6 +24,7 @@ def test_01_template_vs_template_ok_same_company(self): company1 = self.env["res.company"].create( { "name": "Company 1", + "email": "email1@company1.com", } ) self.env["product.template"].create( @@ -63,6 +65,7 @@ def test_02_template_vs_template_ok_diff_company(self): company1 = self.env["res.company"].create( { "name": "Company 1", + "email": "email1@company1.com", } ) self.env["product.template"].create( @@ -75,6 +78,7 @@ def test_02_template_vs_template_ok_diff_company(self): company2 = self.env["res.company"].create( { "name": "Company 2", + "email": "email1@company2.com", } ) # ACT & ASSERT @@ -110,6 +114,7 @@ def test_03_template_vs_template_dup_same_company(self): company1 = self.env["res.company"].create( { "name": "Company 1", + "email": "email1@company1.com", } ) self.env["product.template"].create( @@ -151,6 +156,7 @@ def test_04_template_vs_template_dup_diff_company(self): company1 = self.env["res.company"].create( { "name": "Company 1", + "email": "email1@company1.com", } ) self.env["product.template"].create( @@ -163,6 +169,7 @@ def test_04_template_vs_template_dup_diff_company(self): company2 = self.env["res.company"].create( { "name": "Company 2", + "email": "email1@company2.com", } ) # ACT & ASSERT @@ -199,6 +206,7 @@ def test_05_template_vs_template_dup_company_vs_nocompany(self): company1 = self.env["res.company"].create( { "name": "Company 1", + "email": "email1@company1.com", } ) self.env["product.template"].create( @@ -245,6 +253,7 @@ def test_06_product_vs_template_uniq_same_company(self): company1 = self.env["res.company"].create( { "name": "Company 1", + "email": "email1@company1.com", } ) attribute = self.env["product.attribute"].create( @@ -315,6 +324,7 @@ def test_07_product_vs_product_uniq_same_company(self): company1 = self.env["res.company"].create( { "name": "Company 1", + "email": "email1@company1.com", } ) attribute1 = self.env["product.attribute"].create( @@ -412,6 +422,7 @@ def test_08_product_vs_product_dup_company_vs_nocompany(self): company1 = self.env["res.company"].create( { "name": "Company 1", + "email": "email1@company1.com", } ) attribute1 = self.env["product.attribute"].create( @@ -493,14 +504,15 @@ def test_09_template_vs_product_dup_archived(self): - variant11 has default_code 'sku1' - variant12 has default_code 'sku2' and is archived - variant13 has default_code 'sku3' and is archived - ACT: - change template1 default_code to 'sku1' + ACT: - change template1 default_code to 'sku2' POST: - template1 is not updated because - variant11 has the same default_code 'sku1' + variant12 has the same default_code 'sku1' """ # ARRANGE company1 = self.env["res.company"].create( { "name": "Company 1", + "email": "email1@company1.com", } ) attribute1 = self.env["product.attribute"].create( @@ -544,7 +556,7 @@ def test_09_template_vs_product_dup_archived(self): # ACT & ASSERT try: with self.assertRaises(ValidationError): - template1.default_code = "sku1" + template1.default_code = "sku2" except AssertionError: self.fail( _( From 56b3b18f3f93a6d57c8f1257af8abbcf6ed72e03 Mon Sep 17 00:00:00 2001 From: ??? Date: Wed, 1 Oct 2025 12:59:20 +0200 Subject: [PATCH 10/11] [IMP] product_unique_internal_reference: pre-commit auto fixes --- product_unique_internal_reference/README.rst | 12 ++++++------ product_unique_internal_reference/__manifest__.py | 2 +- product_unique_internal_reference/pyproject.toml | 3 +++ .../static/description/index.html | 9 ++++----- 4 files changed, 14 insertions(+), 12 deletions(-) create mode 100644 product_unique_internal_reference/pyproject.toml diff --git a/product_unique_internal_reference/README.rst b/product_unique_internal_reference/README.rst index d266bef2c..16080a72d 100644 --- a/product_unique_internal_reference/README.rst +++ b/product_unique_internal_reference/README.rst @@ -16,9 +16,9 @@ Product unique internal reference .. |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-nuobit%2Fodoo--addons-lightgray.png?logo=github - :target: https://github.com/nuobit/odoo-addons/tree/16.0/product_unique_internal_reference - :alt: nuobit/odoo-addons +.. |badge3| image:: https://img.shields.io/badge/github-NuoBiT%2Fodoo--addons-lightgray.png?logo=github + :target: https://github.com/NuoBiT/odoo-addons/tree/18.0/product_unique_internal_reference + :alt: NuoBiT/odoo-addons |badge1| |badge2| |badge3| @@ -34,10 +34,10 @@ Product unique internal reference Bug Tracker =========== -Bugs are tracked on `GitHub Issues `_. +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 `_. +`feedback `_. Do not contact contributors directly about support or help with technical issues. @@ -60,6 +60,6 @@ Contributors Maintainers ----------- -This module is part of the `nuobit/odoo-addons `_ project on GitHub. +This module is part of the `NuoBiT/odoo-addons `_ project on GitHub. You are welcome to contribute. diff --git a/product_unique_internal_reference/__manifest__.py b/product_unique_internal_reference/__manifest__.py index 8ffbe1165..c437adce9 100644 --- a/product_unique_internal_reference/__manifest__.py +++ b/product_unique_internal_reference/__manifest__.py @@ -9,7 +9,7 @@ "version": "16.0.1.0.0", "category": "Sales", "author": "NuoBiT Solutions SL", - "website": "https://github.com/nuobit/odoo-addons", + "website": "https://github.com/NuoBiT/odoo-addons", "license": "AGPL-3", "depends": [ "product", diff --git a/product_unique_internal_reference/pyproject.toml b/product_unique_internal_reference/pyproject.toml new file mode 100644 index 000000000..4231d0ccc --- /dev/null +++ b/product_unique_internal_reference/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/product_unique_internal_reference/static/description/index.html b/product_unique_internal_reference/static/description/index.html index 4773042d5..dd700dff8 100644 --- a/product_unique_internal_reference/static/description/index.html +++ b/product_unique_internal_reference/static/description/index.html @@ -1,4 +1,3 @@ - @@ -370,7 +369,7 @@

Product unique internal reference

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !! source digest: sha256:976812157684886bd583c3621ad271c233ae90a365cbb84fa272c9c9fc0f9075 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! --> -

Beta License: AGPL-3 nuobit/odoo-addons

+

Beta License: AGPL-3 NuoBiT/odoo-addons

  • This module ensures that you enter a ‘Unique’ Internal Reference (default_code) for your Products.
  • @@ -390,10 +389,10 @@

    Product unique internal reference

    Bug Tracker

    -

    Bugs are tracked on GitHub Issues. +

    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.

    +feedback.

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

    @@ -416,7 +415,7 @@

    Contributors

    Maintainers

    -

    This module is part of the nuobit/odoo-addons project on GitHub.

    +

    This module is part of the NuoBiT/odoo-addons project on GitHub.

    You are welcome to contribute.

    From 77ff40ca1c9739428a6db9779ae497e3c201e526 Mon Sep 17 00:00:00 2001 From: ??? Date: Wed, 1 Oct 2025 13:07:28 +0200 Subject: [PATCH 11/11] [MIG] product_unique_internal_reference: Migration to 18.0 --- product_unique_internal_reference/README.rst | 1 + .../__manifest__.py | 3 ++- product_unique_internal_reference/hooks.py | 16 +++++++++------- product_unique_internal_reference/i18n/es.po | 12 ++++++++++++ .../models/product_product.py | 7 ++++--- .../models/product_template.py | 11 ++++++----- .../readme/CONTRIBUTORS.md | 1 + .../static/description/index.html | 1 + 8 files changed, 36 insertions(+), 16 deletions(-) diff --git a/product_unique_internal_reference/README.rst b/product_unique_internal_reference/README.rst index 16080a72d..5431e0424 100644 --- a/product_unique_internal_reference/README.rst +++ b/product_unique_internal_reference/README.rst @@ -56,6 +56,7 @@ Contributors - Eric Antones eantones@nuobit.com - Bijaya Kumal bkumal@nuobit.com + - Deniz Gallo dgallo@nuobit.com Maintainers ----------- diff --git a/product_unique_internal_reference/__manifest__.py b/product_unique_internal_reference/__manifest__.py index c437adce9..f32efc43a 100644 --- a/product_unique_internal_reference/__manifest__.py +++ b/product_unique_internal_reference/__manifest__.py @@ -1,12 +1,13 @@ # Copyright NuoBiT - Eric Antones # Copyright NuoBiT 2025 - Bijaya Kumal +# Copyright NuoBiT 2025 - Deniz Gallo # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) { "name": "Product unique internal reference", "summary": "This module ensures that you enter a " "Unique Internal Reference (default_code) for your Products", - "version": "16.0.1.0.0", + "version": "18.0.1.0.0", "category": "Sales", "author": "NuoBiT Solutions SL", "website": "https://github.com/NuoBiT/odoo-addons", diff --git a/product_unique_internal_reference/hooks.py b/product_unique_internal_reference/hooks.py index 6ffde173d..4caa29b80 100644 --- a/product_unique_internal_reference/hooks.py +++ b/product_unique_internal_reference/hooks.py @@ -1,4 +1,5 @@ # Copyright NuoBiT - Eric Antones +# Copyright NuoBiT 2025 - Deniz Gallo # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) @@ -6,14 +7,13 @@ from odoo.exceptions import ValidationError -def internal_reference_duplicate_check(cr): +def internal_reference_duplicate_check(env): """This hook will look to see if any conflicting internal references exist before the module is installed - :param odoo.sql_db.Cursor cr: - Database cursor. + :param Odoo env: Odoo environment """ - with cr.savepoint(): - cr.execute( + with env.cr.savepoint(): + env.cr.execute( """ SELECT p0.id, p0.default_code, t0.company_id FROM product_product p0, product_template t0 @@ -27,8 +27,10 @@ def internal_reference_duplicate_check(cr): p1.default_code = p0.default_code AND p1.id != p0.id )""" ) - products = sorted(["[%i] %s (%i)" % p for p in cr.fetchall()]) + products = sorted(["[%i] %s (%i)" % p for p in env.cr.fetchall()]) if products: raise ValidationError( - _("Conflicting internal references exist: %s" % ", ".join(products)) + _("Conflicting internal references exist: {}").format( + ", ".join(products) + ) ) diff --git a/product_unique_internal_reference/i18n/es.po b/product_unique_internal_reference/i18n/es.po index a6ce1d678..c494b278d 100644 --- a/product_unique_internal_reference/i18n/es.po +++ b/product_unique_internal_reference/i18n/es.po @@ -49,6 +49,8 @@ msgid "" "It has been possible to change the template Internal Reference existing a " "variant with the same one" msgstr "" +"Ha sido posible cambiar la referencia interna de la plantilla existiendo una " +"variante con la misma" #. module: product_unique_internal_reference #: code:addons/product_unique_internal_reference/tests/test_product_unique_internal_reference.py:0 @@ -57,6 +59,8 @@ msgid "" "It has been possible to create a product template on one company and another" " without company with the same Internal Reference" msgstr "" +"Ha sido posible crear una plantilla de producto en una compañía y otra sin " +"compañía con la misma referencia interna" #. module: product_unique_internal_reference #: code:addons/product_unique_internal_reference/tests/test_product_unique_internal_reference.py:0 @@ -65,6 +69,8 @@ msgid "" "It has been possible to create a product template when a variant of another " "template has the same Internal Reference" msgstr "" +"Ha sido posible crear una plantilla de producto cuando una variante de otra " +"plantilla tiene la misma referencia interna" #. module: product_unique_internal_reference #: code:addons/product_unique_internal_reference/tests/test_product_unique_internal_reference.py:0 @@ -74,6 +80,8 @@ msgid "" "It has been possible to create a product variant when a variant of another " "template has the same Internal Reference" msgstr "" +"Ha sido posible crear una variante de producto cuando una variante de otra " +"plantilla tiene la misma referencia interna" #. module: product_unique_internal_reference #: code:addons/product_unique_internal_reference/tests/test_product_unique_internal_reference.py:0 @@ -82,6 +90,8 @@ msgid "" "It has been possible to create two product templates with the same Internal " "Reference" msgstr "" +"Ha sido posible crear dos plantillas de producto con la misma referencia " +"interna" #. module: product_unique_internal_reference #: model:ir.model.fields,field_description:product_unique_internal_reference.field_product_product____last_update @@ -108,3 +118,5 @@ msgid "" "The Internal References are not equal, this should have not raised an " "Exception" msgstr "" +"Las referencias internas no son iguales, esto no debería haber lanzado una " +"excepción" diff --git a/product_unique_internal_reference/models/product_product.py b/product_unique_internal_reference/models/product_product.py index 785f42932..287ade360 100644 --- a/product_unique_internal_reference/models/product_product.py +++ b/product_unique_internal_reference/models/product_product.py @@ -1,4 +1,5 @@ # Copyright NuoBiT - Eric Antones +# Copyright NuoBiT 2025 - Deniz Gallo # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) @@ -39,7 +40,7 @@ def _check_default_code(self): if product: raise ValidationError( _( - "Error! The Default Code %s already exists. " - "Check also the archived ones." % record.default_code - ) + "Error! The Default Code {} already exists. " + "Check also the archived ones." + ).format(record.default_code) ) diff --git a/product_unique_internal_reference/models/product_template.py b/product_unique_internal_reference/models/product_template.py index 8bd74062c..5342797ec 100644 --- a/product_unique_internal_reference/models/product_template.py +++ b/product_unique_internal_reference/models/product_template.py @@ -1,4 +1,5 @@ # Copyright NuoBiT - Eric Antones +# Copyright NuoBiT 2025 - Deniz Gallo # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) @@ -26,20 +27,20 @@ def _check_default_code(self): ) # products inside the same template products_inside = products_all.filtered( - lambda p: p.product_tmpl_id == record + lambda p, record=record: p.product_tmpl_id == record ) # products outside the same template products_outside = products_all - products_inside if record.company_id: products_outside = products_outside.filtered( - lambda p: not p.product_tmpl_id.company_id + lambda p, record=record: not p.product_tmpl_id.company_id or p.product_tmpl_id.company_id == record.company_id ) # check if the default code is used in other templates if len(products_inside) > 1 or products_outside: raise ValidationError( _( - "Error! The Default Code %s already exists. " - "Check also the archived ones." % record.default_code - ) + "Error! The Default Code {} already exists. " + "Check also the archived ones." + ).format(record.default_code) ) diff --git a/product_unique_internal_reference/readme/CONTRIBUTORS.md b/product_unique_internal_reference/readme/CONTRIBUTORS.md index 09afb8b5c..325bee193 100644 --- a/product_unique_internal_reference/readme/CONTRIBUTORS.md +++ b/product_unique_internal_reference/readme/CONTRIBUTORS.md @@ -1,3 +1,4 @@ - [NuoBiT](https://www.nuobit.com): - Eric Antones - Bijaya Kumal + - Deniz Gallo diff --git a/product_unique_internal_reference/static/description/index.html b/product_unique_internal_reference/static/description/index.html index dd700dff8..1afa609d7 100644 --- a/product_unique_internal_reference/static/description/index.html +++ b/product_unique_internal_reference/static/description/index.html @@ -409,6 +409,7 @@

    Contributors

  • NuoBiT: