Files
odoo_source/addons/barcodes/models/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

135 lines
5.9 KiB
Python

import re
from odoo import models, fields, api
from odoo.tools import check_barcode_encoding, get_barcode_check_digit
UPC_EAN_CONVERSIONS = [
('none', 'Never'),
('ean2upc', 'EAN-13 to UPC-A'),
('upc2ean', 'UPC-A to EAN-13'),
('always', 'Always'),
]
class BarcodeNomenclature(models.Model):
_name = 'barcode.nomenclature'
_description = 'Barcode Nomenclature'
name = fields.Char(string='Barcode Nomenclature', size=32, required=True, help='An internal identification of the barcode nomenclature')
rule_ids = fields.One2many('barcode.rule', 'barcode_nomenclature_id', string='Rules', help='The list of barcode rules')
upc_ean_conv = fields.Selection(
UPC_EAN_CONVERSIONS, string='UPC/EAN Conversion', required=True, default='always',
help="UPC Codes can be converted to EAN by prefixing them with a zero. This setting determines if a UPC/EAN barcode should be automatically converted in one way or another when trying to match a rule with the other encoding.")
@api.model
def sanitize_ean(self, ean):
""" Returns a valid zero padded EAN-13 from an EAN prefix.
:type ean: str
"""
ean = ean[0:13].zfill(13)
return ean[0:-1] + str(get_barcode_check_digit(ean))
@api.model
def sanitize_upc(self, upc):
""" Returns a valid zero padded UPC-A from a UPC-A prefix.
:type upc: str
"""
return self.sanitize_ean('0' + upc)[1:]
def match_pattern(self, barcode, pattern):
"""Checks barcode matches the pattern and retrieves the optional numeric value in barcode.
:param barcode:
:type barcode: str
:param pattern:
:type pattern: str
:return: an object containing:
- value: the numerical value encoded in the barcode (0 if no value encoded)
- base_code: the barcode in which numerical content is replaced by 0's
- match: boolean
:rtype: dict
"""
match = {
'value': 0,
'base_code': barcode,
'match': False,
}
barcode = barcode.replace('\\', '\\\\').replace('{', '\\{').replace('}', '\\}').replace('.', '\\.')
numerical_content = re.search("[{][N]*[D]*[}]", pattern) # look for numerical content in pattern
if numerical_content: # the pattern encodes a numerical content
num_start = numerical_content.start() # start index of numerical content
num_end = numerical_content.end() # end index of numerical content
value_string = barcode[num_start:num_end - 2] # numerical content in barcode
whole_part_match = re.search("[{][N]*[D}]", numerical_content.group()) # looks for whole part of numerical content
decimal_part_match = re.search("[{N][D]*[}]", numerical_content.group()) # looks for decimal part
whole_part = value_string[:whole_part_match.end() - 2] # retrieve whole part of numerical content in barcode
decimal_part = "0." + value_string[decimal_part_match.start():decimal_part_match.end() - 1] # retrieve decimal part
if whole_part == '':
whole_part = '0'
match['value'] = int(whole_part) + float(decimal_part)
match['base_code'] = barcode[:num_start] + (num_end - num_start - 2) * "0" + barcode[num_end - 2:] # replace numerical content by 0's in barcode
match['base_code'] = match['base_code'].replace("\\\\", "\\").replace("\\{", "{").replace("\\}", "}").replace("\\.", ".")
pattern = pattern[:num_start] + (num_end - num_start - 2) * "0" + pattern[num_end:] # replace numerical content by 0's in pattern to match
match['match'] = re.match(pattern, match['base_code'][:len(pattern)])
return match
def parse_barcode(self, barcode):
""" Attempts to interpret and parse a barcode.
:param barcode:
:type barcode: str
:return: A object containing various information about the barcode, like as:
- code: the barcode
- type: the barcode's type
- value: if the id encodes a numerical value, it will be put there
- base_code: the barcode code with all the encoding parts set to
zero; the one put on the product in the backend
:rtype: dict
"""
parsed_result = {
'encoding': '',
'type': 'error',
'code': barcode,
'base_code': barcode,
'value': 0,
}
for rule in self.rule_ids:
cur_barcode = barcode
if rule.encoding == 'ean13' and check_barcode_encoding(barcode, 'upca') and self.upc_ean_conv in ['upc2ean', 'always']:
cur_barcode = '0' + cur_barcode
elif rule.encoding == 'upca' and check_barcode_encoding(barcode, 'ean13') and barcode[0] == '0' and self.upc_ean_conv in ['ean2upc', 'always']:
cur_barcode = cur_barcode[1:]
if not check_barcode_encoding(barcode, rule.encoding):
continue
match = self.match_pattern(cur_barcode, rule.pattern)
if match['match']:
if rule.type == 'alias':
barcode = rule.alias
parsed_result['code'] = barcode
else:
parsed_result['encoding'] = rule.encoding
parsed_result['type'] = rule.type
parsed_result['value'] = match['value']
parsed_result['code'] = cur_barcode
if rule.encoding == "ean13":
parsed_result['base_code'] = self.sanitize_ean(match['base_code'])
elif rule.encoding == "upca":
parsed_result['base_code'] = self.sanitize_upc(match['base_code'])
else:
parsed_result['base_code'] = match['base_code']
return parsed_result
return parsed_result