Files
odoo_source/addons/barcodes/tests/test_barcode_nomenclature.py
T
Adrien Widart 320025ee63 [FIX] base: convert barcode type if incorrect encoding
To reproduce the issue:
(Need stock_barcode)
1. Create a product:
    - Barcode: 1234567890123
2. Print the label
3. Try to scan the barcode and check the value read.

Error: The value is 1234567890128, the last digit is incorrect (8
instead of 3)

When printing a barcode, we use the library 'report-lab' to generate a
barcode image from a value and a barcode type. In case of EAN-13, if the
value contains a non-digit character, it will raise an error. We then
catch the error and retry to generate the barcode according to the
barcode type Code128:
https://github.com/odoo/odoo/blob/87698d90f02bfe93c6e643f4876a5ccd74788eff/odoo/addons/base/models/ir_actions_report.py#L569-L575
However, if the value contains only digits, the method will use the 12
first digits:
https://github.com/mattjmorrison/ReportLab/blob/dade0f303cb6fcdbe535c4cc92e6102c2417b699/src/reportlab/graphics/barcode/eanbc.py#L187-L188
and will then add the last one, the check digit, which is computed by
the library. This explains why, in the above use case, the barcode value
returned by the scanner is not the same than the expected one.

Note: Similar behavior with type EAN-8

OPW-2902150

closes odoo/odoo#97052

X-original-commit: a2c7470f8fd46b2520f7b8f0750c63216273ce96
Related: odoo/enterprise#30160
Signed-off-by: Steve Van Essche <svs@odoo.com>
Signed-off-by: Adrien Widart <awt@odoo.com>
2022-08-11 12:51:05 +02:00

250 lines
11 KiB
Python

# -*- coding: utf-8 -*-
from odoo.exceptions import ValidationError
from odoo.tests import common
class TestBarcodeNomenclature(common.TransactionCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
# Creates an empty nomenclature (rules will be added in the tests).
cls.nomenclature = cls.env['barcode.nomenclature'].create({
'name': 'Barcode Nomenclature Test',
})
def test_barcode_nomenclature_parse_barcode_ean8_01(self):
""" Parses some barcodes with a simple EAN-8 barcode rule and checks the result.
"""
self.env['barcode.rule'].create({
'name': 'Rule Test #1',
'barcode_nomenclature_id': self.nomenclature.id,
'encoding': 'ean8',
'pattern': '........',
})
# Must fail because too short.
res = self.nomenclature.parse_barcode('0002')
self.assertEqual(res['code'], '0002')
self.assertEqual(res['type'], 'error', "Must fail because the barcode is too short")
self.assertEqual(res['encoding'], '')
self.assertEqual(res['base_code'], '0002')
self.assertEqual(res['value'], 0)
# Must fail because wrong checksum (last digit).
res = self.nomenclature.parse_barcode('12345678')
self.assertEqual(res['code'], '12345678')
self.assertEqual(res['type'], 'error', "Must fail because the checksum digit is wrong")
self.assertEqual(res['encoding'], '')
self.assertEqual(res['base_code'], '12345678')
self.assertEqual(res['value'], 0)
# Must pass (right number of digits, right checksum).
res = self.nomenclature.parse_barcode('12345670')
self.assertEqual(res['code'], '12345670')
self.assertEqual(res['type'], 'product')
self.assertEqual(res['encoding'], 'ean8')
self.assertEqual(res['base_code'], '12345670')
self.assertEqual(res['value'], 0, "No value must be located into the barcode")
# Must pass (right number of digits, right checksum).
res = self.nomenclature.parse_barcode('02003405')
self.assertEqual(res['code'], '02003405')
self.assertEqual(res['type'], 'product')
self.assertEqual(res['encoding'], 'ean8')
self.assertEqual(res['base_code'], '02003405')
self.assertEqual(res['value'], 0, "No value must be located into the barcode")
def test_barcode_nomenclature_parse_barcode_ean8_02_validation_error(self):
""" Try to parse a barcode with a wrong barcode rule.
"""
barcode_rule = self.env['barcode.rule'].create({
'name': 'Rule Test #1',
'barcode_nomenclature_id': self.nomenclature.id,
'encoding': 'ean8',
})
with self.assertRaises(ValidationError), self.cr.savepoint():
# Must fail because empty braces.
barcode_rule.pattern = '......{}..'
with self.assertRaises(ValidationError), self.cr.savepoint():
# Must fail because decimal can't be before integer.
barcode_rule.pattern = '......{DN}'
with self.assertRaises(ValidationError), self.cr.savepoint():
# Must fail because a pattern can't have multiple braces group.
barcode_rule.pattern = '....{NN}{DD}'
with self.assertRaises(ValidationError), self.cr.savepoint():
# Must fail because '*' isn't accepted (should be '.*' instead).
barcode_rule.pattern = '*'
def test_barcode_nomenclature_parse_barcode_ean8_03_value(self):
""" Parses some barcodes with a EAN-8 barcode rule who convert the
barcode into value and checks the result.
"""
self.env['barcode.rule'].create({
'name': 'Rule Test #2',
'barcode_nomenclature_id': self.nomenclature.id,
'encoding': 'ean8',
'pattern': '{NNNNNNNN}',
})
res = self.nomenclature.parse_barcode('0002')
self.assertEqual(res['code'], '0002')
self.assertEqual(res['type'], 'error', "Must fail because the barcode is too short")
self.assertEqual(res['encoding'], '')
self.assertEqual(res['base_code'], '0002')
self.assertEqual(res['value'], 0)
res = self.nomenclature.parse_barcode('12345678')
self.assertEqual(res['code'], '12345678')
self.assertEqual(res['type'], 'error', "Must fail because the checksum digit is wrong")
self.assertEqual(res['encoding'], '')
self.assertEqual(res['base_code'], '12345678')
self.assertEqual(res['value'], 0)
# Must pass (right number of digits, right checksum).
res = self.nomenclature.parse_barcode('12345670')
self.assertEqual(res['code'], '12345670')
self.assertEqual(res['type'], 'product')
self.assertEqual(res['encoding'], 'ean8')
self.assertEqual(res['base_code'], '00000000',
"All the barcode should be consumed into the value")
self.assertEqual(res['value'], 12345670.0, "The barcode must be converted into value")
# Must pass (right number of digits, right checksum).
res = self.nomenclature.parse_barcode('02003405')
self.assertEqual(res['code'], '02003405')
self.assertEqual(res['type'], 'product')
self.assertEqual(res['encoding'], 'ean8')
self.assertEqual(res['base_code'], '00000000',
"All the barcode should be consumed into the value")
self.assertEqual(res['value'], 2003405.0, "The barcode must be converted into value")
def test_barcode_nomenclature_parse_barcode_ean8_04_multiple_rules(self):
""" Parses some barcodes with a nomenclature containing multiple EAN-8
barcode rule and checks the right one is took depending of the pattern.
"""
self.env['barcode.rule'].create({
'name': 'Rule Test #1',
'barcode_nomenclature_id': self.nomenclature.id,
'encoding': 'ean8',
'pattern': '11.....{N}',
})
self.env['barcode.rule'].create({
'name': 'Rule Test #1',
'barcode_nomenclature_id': self.nomenclature.id,
'encoding': 'ean8',
'pattern': '66{NN}....',
})
# Only fits the second barcode rule.
res = self.nomenclature.parse_barcode('11012344')
self.assertEqual(res['code'], '11012344')
self.assertEqual(res['type'], 'product')
self.assertEqual(res['encoding'], 'ean8')
self.assertEqual(res['base_code'], '11012340')
self.assertEqual(res['value'], 4)
# Only fits the second barcode rule.
res = self.nomenclature.parse_barcode('66012344')
self.assertEqual(res['code'], '66012344')
self.assertEqual(res['type'], 'product')
self.assertEqual(res['encoding'], 'ean8')
self.assertEqual(res['base_code'], '66002344')
self.assertEqual(res['value'], 1)
# Doesn't fit any barcode rule.
res = self.nomenclature.parse_barcode('16012344')
self.assertEqual(res['code'], '16012344')
self.assertEqual(res['type'], 'error')
self.assertEqual(res['encoding'], '')
self.assertEqual(res['base_code'], '16012344')
self.assertEqual(res['value'], 0)
def test_barcode_nomenclature_parse_barcode_ean13_01(self):
""" Parses some barcodes with a EAN-13 barcode rule who contains a value
and checks the result.
"""
self.env['barcode.rule'].create({
'name': 'Rule Test #3',
'barcode_nomenclature_id': self.nomenclature.id,
'encoding': 'ean13',
'pattern': '1........{NND}.',
})
# Must fail because too short.
res = self.nomenclature.parse_barcode('0002')
self.assertEqual(res['code'], '0002')
self.assertEqual(res['type'], 'error', "Must fail because the barcode is too short")
self.assertEqual(res['encoding'], '')
self.assertEqual(res['base_code'], '0002')
self.assertEqual(res['value'], 0)
# Must fail because wrong checksum (last digit).
res = self.nomenclature.parse_barcode('12345678')
self.assertEqual(res['code'], '12345678')
self.assertEqual(res['type'], 'error', "Must fail because the checksum digit is wrong")
self.assertEqual(res['encoding'], '')
self.assertEqual(res['base_code'], '12345678')
self.assertEqual(res['value'], 0)
# Must pass (right number of digits, right checksum).
res = self.nomenclature.parse_barcode('1020034051259')
self.assertEqual(res['code'], '1020034051259')
self.assertEqual(res['type'], 'product')
self.assertEqual(res['encoding'], 'ean13')
self.assertEqual(res['base_code'], '1020034050009')
self.assertEqual(res['value'], 12.5, "Should taken only the value part (NND)")
def test_barcode_nomenclature_parse_barcode_ean13_02_sequence(self):
""" Parses some barcodes with a nomenclature containing two EAN-13
barcode rule and checks the good one is took depending of its sequence.
"""
first_created_rule = self.env['barcode.rule'].create({
'name': 'Rule Test #1',
'barcode_nomenclature_id': self.nomenclature.id,
'encoding': 'ean13',
'pattern': '.....{NNNDDDD}.',
'sequence': 3,
})
self.env['barcode.rule'].create({
'name': 'Rule Test #2',
'barcode_nomenclature_id': self.nomenclature.id,
'encoding': 'ean13',
'pattern': '22......{NNDD}.',
'sequence': 2,
})
# Invalids the cache to reset the nomenclature barcode rules' order.
self.nomenclature.invalidate_recordset(['rule_ids'])
# Only fits the second barcode rule.
res = self.nomenclature.parse_barcode('2012345610255')
self.assertEqual(res['code'], '2012345610255')
self.assertEqual(res['type'], 'product')
self.assertEqual(res['encoding'], 'ean13')
self.assertEqual(res['base_code'], '2012300000008')
self.assertEqual(res['value'], 456.1025)
# Fits the two barcode rules, but should take the second one (lower sequence).
res = self.nomenclature.parse_barcode('2212345610259')
self.assertEqual(res['code'], '2212345610259')
self.assertEqual(res['type'], 'product')
self.assertEqual(res['encoding'], 'ean13')
self.assertEqual(res['base_code'], '2212345600007')
self.assertEqual(res['value'], 10.25)
first_created_rule.sequence = 1
# Invalids the cache to reset the nomenclature barcode rules' order.
self.nomenclature.invalidate_recordset(['rule_ids'])
# Should take the first one now (lower sequence).
res = self.nomenclature.parse_barcode('2212345610259')
self.assertEqual(res['code'], '2212345610259')
self.assertEqual(res['type'], 'product')
self.assertEqual(res['encoding'], 'ean13')
self.assertEqual(res['base_code'], '2212300000002')
self.assertEqual(res['value'], 456.1025)