Skip to content
Snippets Groups Projects
Commit 4a48d483 authored by boan-odoo's avatar boan-odoo
Browse files

[IMP] stock: show related products on duplicated barcode

Previously, product barcodes uniqueness was enforced
by a SQL constraint, resulting in a generic error message.

The SQL constraint has been replaced by a Python check,
which allows the listing of conflicting barcodes in the
error message, including those resulting from batch edits.

Task: 2654703-7
Part-of: odoo/odoo#84056
parent 03c959d8
No related branches found
No related tags found
No related merge requests found
......@@ -2,6 +2,7 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import re
from operator import itemgetter
from odoo import api, fields, models, tools, _
from odoo.exceptions import ValidationError
......@@ -168,9 +169,35 @@ class ProductProduct(models.Model):
self.env.cr.execute("CREATE UNIQUE INDEX IF NOT EXISTS product_product_combination_unique ON %s (product_tmpl_id, combination_indices) WHERE active is true"
% self._table)
_sql_constraints = [
('barcode_uniq', 'unique(barcode)', "A barcode can only be assigned to one product !"),
]
@api.constrains("barcode")
def _check_barcode_unique(self):
# Collect `product.product.id` of objects that share a barcode with any of those in `self`,
# and group them by barcode.
candidate_duplicates = self.env['product.product'].sudo().read_group(
domain=[("barcode", "in", list(set(product.barcode for product in self if product.barcode)))],
fields=['ids:array_agg(id)', 'barcode'],
groupby=['barcode']
)
# Keep only barcodes that match more than one `product.product` object.
duplicates = [candidate for candidate in candidate_duplicates if candidate['barcode_count'] > 1]
if duplicates:
# Prepare a mapping of "{ `product.barcode`: [`product.display_name`, ... ] }" to show the user.
duplicates_joined = {dup['barcode']: ', '.join(
sorted([product.display_name for product in
self.env['product.product'].browse(dup['ids'])])
) for dup in sorted(duplicates, key=itemgetter('barcode'))}
# Transform the mapping into a prettified string and raise.
duplicates_as_str = "\n".join(
f"Barcode \"{barcode}\" already assigned to product(s): {products}"
for barcode, products in duplicates_joined.items()
)
raise ValidationError(
"Barcode(s) already assigned:\n\n{duplicates}".format(duplicates=duplicates_as_str)
)
@api.constrains('barcode')
def _check_barcode_uniqueness(self):
......
......@@ -7,3 +7,4 @@ from . import test_pricelist
from . import test_product_pricelist
from . import test_seller
from . import test_product_attribute_value_config
from . import test_barcode
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import exceptions
from odoo.tests.common import TransactionCase
class TestProductBarcode(TransactionCase):
def setUp(self):
super().setUp()
self.env['product.product'].create({'name': 'BC1', 'barcode': '1'})
self.env['product.product'].create({'name': 'BC2', 'barcode': '2'})
def test_blank_barcodes_allowed(self):
"""Makes sure duplicated blank barcodes are allowed."""
for i in range(2):
self.env['product.product'].create({'name': f'BC_{i}'})
def test_false_barcodes_allowed(self):
"""Makes sure duplicated False barcodes are allowed."""
for i in range(2):
self.env['product.product'].create({'name': f'BC_{i}', 'barcode': False})
def test_duplicated_barcode(self):
"""Tests for simple duplication."""
with self.assertRaises(exceptions.ValidationError):
self.env['product.product'].create({'name': 'BC3', 'barcode': '1'})
def test_duplicated_barcode_in_batch_edit(self):
"""Tests for duplication in batch edits."""
batch = [
{'name': 'BC3', 'barcode': '3'},
{'name': 'BC4', 'barcode': '4'},
]
self.env['product.product'].create(batch)
batch.append({'name': 'BC5', 'barcode': '1'})
with self.assertRaises(exceptions.ValidationError):
self.env['product.product'].create(batch)
def test_test_duplicated_barcode_error_msg_content(self):
"""Validates the error message shown when duplicated barcodes are found."""
batch = [
{'name': 'BC3', 'barcode': '3'},
{'name': 'BC4', 'barcode': '3'},
{'name': 'BC5', 'barcode': '4'},
{'name': 'BC6', 'barcode': '4'},
{'name': 'BC7', 'barcode': '1'},
]
try:
self.env['product.product'].create(batch)
except exceptions.ValidationError as exc:
assert 'Barcode "3" already assigned to product(s): BC3, BC4' in exc.args[0]
assert 'Barcode "4" already assigned to product(s): BC5, BC6' in exc.args[0]
assert 'Barcode "1" already assigned to product(s): BC1' in exc.args[0]
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment