barcode_nomenclature.py 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  1. import re
  2. from odoo import models, fields, api
  3. from odoo.tools import check_barcode_encoding, get_barcode_check_digit
  4. UPC_EAN_CONVERSIONS = [
  5. ('none', 'Never'),
  6. ('ean2upc', 'EAN-13 to UPC-A'),
  7. ('upc2ean', 'UPC-A to EAN-13'),
  8. ('always', 'Always'),
  9. ]
  10. class BarcodeNomenclature(models.Model):
  11. _name = 'barcode.nomenclature'
  12. _description = 'Barcode Nomenclature'
  13. name = fields.Char(string='Barcode Nomenclature', size=32, required=True, help='An internal identification of the barcode nomenclature')
  14. rule_ids = fields.One2many('barcode.rule', 'barcode_nomenclature_id', string='Rules', help='The list of barcode rules')
  15. upc_ean_conv = fields.Selection(
  16. UPC_EAN_CONVERSIONS, string='UPC/EAN Conversion', required=True, default='always',
  17. 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.")
  18. @api.model
  19. def sanitize_ean(self, ean):
  20. """ Returns a valid zero padded EAN-13 from an EAN prefix.
  21. :type ean: str
  22. """
  23. ean = ean[0:13].zfill(13)
  24. return ean[0:-1] + str(get_barcode_check_digit(ean))
  25. @api.model
  26. def sanitize_upc(self, upc):
  27. """ Returns a valid zero padded UPC-A from a UPC-A prefix.
  28. :type upc: str
  29. """
  30. return self.sanitize_ean('0' + upc)[1:]
  31. def match_pattern(self, barcode, pattern):
  32. """Checks barcode matches the pattern and retrieves the optional numeric value in barcode.
  33. :param barcode:
  34. :type barcode: str
  35. :param pattern:
  36. :type pattern: str
  37. :return: an object containing:
  38. - value: the numerical value encoded in the barcode (0 if no value encoded)
  39. - base_code: the barcode in which numerical content is replaced by 0's
  40. - match: boolean
  41. :rtype: dict
  42. """
  43. match = {
  44. 'value': 0,
  45. 'base_code': barcode,
  46. 'match': False,
  47. }
  48. barcode = barcode.replace('\\', '\\\\').replace('{', '\\{').replace('}', '\\}').replace('.', '\\.')
  49. numerical_content = re.search("[{][N]*[D]*[}]", pattern) # look for numerical content in pattern
  50. if numerical_content: # the pattern encodes a numerical content
  51. num_start = numerical_content.start() # start index of numerical content
  52. num_end = numerical_content.end() # end index of numerical content
  53. value_string = barcode[num_start:num_end - 2] # numerical content in barcode
  54. whole_part_match = re.search("[{][N]*[D}]", numerical_content.group()) # looks for whole part of numerical content
  55. decimal_part_match = re.search("[{N][D]*[}]", numerical_content.group()) # looks for decimal part
  56. whole_part = value_string[:whole_part_match.end() - 2] # retrieve whole part of numerical content in barcode
  57. decimal_part = "0." + value_string[decimal_part_match.start():decimal_part_match.end() - 1] # retrieve decimal part
  58. if whole_part == '':
  59. whole_part = '0'
  60. if whole_part.isdigit():
  61. match['value'] = int(whole_part) + float(decimal_part)
  62. match['base_code'] = barcode[:num_start] + (num_end - num_start - 2) * "0" + barcode[num_end - 2:] # replace numerical content by 0's in barcode
  63. match['base_code'] = match['base_code'].replace("\\\\", "\\").replace("\\{", "{").replace("\\}", "}").replace("\\.", ".")
  64. pattern = pattern[:num_start] + (num_end - num_start - 2) * "0" + pattern[num_end:] # replace numerical content by 0's in pattern to match
  65. match['match'] = re.match(pattern, match['base_code'][:len(pattern)])
  66. return match
  67. def parse_barcode(self, barcode):
  68. """ Attempts to interpret and parse a barcode.
  69. :param barcode:
  70. :type barcode: str
  71. :return: A object containing various information about the barcode, like as:
  72. - code: the barcode
  73. - type: the barcode's type
  74. - value: if the id encodes a numerical value, it will be put there
  75. - base_code: the barcode code with all the encoding parts set to
  76. zero; the one put on the product in the backend
  77. :rtype: dict
  78. """
  79. parsed_result = {
  80. 'encoding': '',
  81. 'type': 'error',
  82. 'code': barcode,
  83. 'base_code': barcode,
  84. 'value': 0,
  85. }
  86. for rule in self.rule_ids:
  87. cur_barcode = barcode
  88. if rule.encoding == 'ean13' and check_barcode_encoding(barcode, 'upca') and self.upc_ean_conv in ['upc2ean', 'always']:
  89. cur_barcode = '0' + cur_barcode
  90. elif rule.encoding == 'upca' and check_barcode_encoding(barcode, 'ean13') and barcode[0] == '0' and self.upc_ean_conv in ['ean2upc', 'always']:
  91. cur_barcode = cur_barcode[1:]
  92. if not check_barcode_encoding(barcode, rule.encoding):
  93. continue
  94. match = self.match_pattern(cur_barcode, rule.pattern)
  95. if match['match']:
  96. if rule.type == 'alias':
  97. barcode = rule.alias
  98. parsed_result['code'] = barcode
  99. else:
  100. parsed_result['encoding'] = rule.encoding
  101. parsed_result['type'] = rule.type
  102. parsed_result['value'] = match['value']
  103. parsed_result['code'] = cur_barcode
  104. if rule.encoding == "ean13":
  105. parsed_result['base_code'] = self.sanitize_ean(match['base_code'])
  106. elif rule.encoding == "upca":
  107. parsed_result['base_code'] = self.sanitize_upc(match['base_code'])
  108. else:
  109. parsed_result['base_code'] = match['base_code']
  110. return parsed_result
  111. return parsed_result