1 # -*- coding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
7 # This program is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU Affero General Public License as
9 # published by the Free Software Foundation, either version 3 of the
10 # License, or (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU Affero General Public License for more details.
17 # You should have received a copy of the GNU Affero General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
20 ##############################################################################
26 from openerp import tools, models, fields, api
27 from openerp.osv import fields, osv
28 from openerp.tools.translate import _
29 from openerp.exceptions import ValidationError
31 _logger = logging.getLogger(__name__)
33 class barcode_nomenclature(osv.osv):
34 _name = 'barcode.nomenclature'
36 'name': fields.char('Nomenclature Name', size=32, required=True, help='An internal identification of the barcode nomenclature'),
37 'rule_ids': fields.one2many('barcode.rule','barcode_nomenclature_id','Rules', help='The list of barcode rules'),
38 'strict_ean': fields.boolean('Use strict EAN13',
39 help='Many barcode scanners strip the leading zero of EAN13 barcodes. By using strict EAN13, we consider the scanned barcode directly. Otherwise, we prepend scanned barcodes of length 12 by a zero before looking for the associated item.')
46 def use_strict_ean(self):
47 return self.strict_ean
49 # returns the checksum of the ean13, or -1 if the ean has not the correct length, ean must be a string
50 def ean_checksum(self, ean):
55 oddsum = evensum = total = 0
56 code = code[:-1] # Remove checksum
57 for i in range(len(code)):
59 evensum += int(code[i])
61 oddsum += int(code[i])
62 total = oddsum * 3 + evensum
63 return int((10 - total % 10) % 10)
65 # returns true if the barcode is a valid EAN barcode
66 def check_ean(self, ean):
67 return re.match("^\d+$", ean) and self.ean_checksum(ean) == int(ean[len(ean)-1])
69 # Returns a valid zero padded ean13 from an ean prefix. the ean prefix must be a string.
70 def sanitize_ean(self, ean):
72 ean = ean + (13-len(ean))*'0'
73 return ean[0:12] + str(self.ean_checksum(ean))
75 # Attempts to interpret an barcode (string encoding a barcode)
76 # It will return an object containing various information about the barcode.
78 # - code : the barcode
79 # - type : the type of the barcode:
80 # - value : if the id encodes a numerical value, it will be put there
81 # - base_code : the barcode code with all the encoding parts set to zero; the one put on
82 # the product in the backend
83 def parse_barcode(self, barcode):
91 # Checks if barcode matches the pattern
92 # Additionnaly retrieves the optional numerical content in barcode
93 # Returns an object containing:
94 # - value: the numerical value encoded in the barcode (0 if no value encoded)
95 # - base_code: the barcode in which numerical content is replaced by 0's
97 def match_pattern(barcode, pattern):
100 "base_code": barcode,
104 barcode = barcode.replace("\\", "\\\\").replace("{", '\{').replace("}", "\}").replace(".", "\.")
105 numerical_content = re.search("[{][N]*[D]*[}]", pattern) # look for numerical content in pattern
107 if numerical_content: # the pattern encodes a numerical content
108 num_start = numerical_content.start() # start index of numerical content
109 num_end = numerical_content.end() # end index of numerical content
110 value_string = barcode[num_start:num_end-2] # numerical content in barcode
112 whole_part_match = re.search("[{][N]*[D}]", numerical_content.group()) # looks for whole part of numerical content
113 decimal_part_match = re.search("[{N][D]*[}]", numerical_content.group()) # looks for decimal part
114 whole_part = value_string[:whole_part_match.end()-2] # retrieve whole part of numerical content in barcode
115 decimal_part = "0." + value_string[decimal_part_match.start():decimal_part_match.end()-1] # retrieve decimal part
118 match['value'] = int(whole_part) + float(decimal_part)
120 match['base_code'] = barcode[:num_start] + (num_end-num_start-2)*"0" + barcode[num_end-2:] # replace numerical content by 0's in barcode
121 match['base_code'] = match['base_code'].replace("\\\\", "\\").replace("\{", "{").replace("\}","}").replace("\.",".")
122 pattern = pattern[:num_start] + (num_end-num_start-2)*"0" + pattern[num_end:] # replace numerical content by 0's in pattern to match
124 match['match'] = re.match(pattern, match['base_code'][:len(pattern)])
130 for rule in self.rule_ids:
131 rules.append({'type': rule.type, 'encoding': rule.encoding, 'sequence': rule.sequence, 'pattern': rule.pattern, 'alias': rule.alias})
133 # If the nomenclature does not use strict EAN, prepend the barcode with a 0 if it seems
134 # that it has been striped by the barcode scanner, when trying to match an EAN13 rule
136 if not self.strict_ean and len(barcode) == 12 and self.check_ean("0"+barcode):
140 cur_barcode = barcode
141 if prepend_zero and rule['encoding'] == "ean13":
142 cur_barcode = '0'+cur_barcode
144 match = match_pattern(cur_barcode, rule['pattern'])
146 if rule['type'] == 'alias':
147 barcode = rule['alias']
148 parsed_result['code'] = barcode
150 parsed_result['encoding'] = rule['encoding']
151 parsed_result['type'] = rule['type']
152 parsed_result['value'] = match['value']
153 parsed_result['code'] = cur_barcode
154 if rule['encoding'] == "ean13":
155 parsed_result['base_code'] = self.sanitize_ean(match['base_code'])
157 parsed_result['base_code'] = match['base_code']
162 class barcode_rule(models.Model):
163 _name = 'barcode.rule'
164 _order = 'sequence asc'
167 def _get_type_selection(self):
168 return [('alias','Alias'),('product','Unit Product')]
171 'name': fields.char('Rule Name', size=32, required=True, help='An internal identification for this barcode nomenclature rule'),
172 'barcode_nomenclature_id': fields.many2one('barcode.nomenclature','Barcode Nomenclature'),
173 'sequence': fields.integer('Sequence', help='Used to order rules such that rules with a smaller sequence match first'),
174 'encoding': fields.selection([('any','Any'),('ean13','EAN-13')],'Encoding',required=True,help='This rule will apply only if the barcode is encoded with the specified encoding'),
175 'type': fields.selection('_get_type_selection','Type', required=True),
176 'pattern': fields.char('Barcode Pattern', size=32, help="The barcode matching pattern"),
177 'alias': fields.char('Alias',size=32,help='The matched pattern will alias to this barcode',required=True),
188 @api.constrains('pattern')
189 def _check_pattern(self):
190 p = self.pattern.replace("\\\\", "X").replace("\{", "X").replace("\}", "X")
191 findall = re.findall("[{]|[}]", p) # p does not contain escaped { or }
192 if len(findall) == 2:
193 if not re.search("[{][N]*[D]*[}]", p):
194 raise ValidationError(_("There is a syntax error in the barcode pattern ") + self.pattern + _(": braces can only contain N's followed by D's."))
195 elif re.search("[{][}]", p):
196 raise ValidationError(_("There is a syntax error in the barcode pattern ") + self.pattern + _(": empty braces."))
197 elif len(findall) != 0:
198 raise ValidationError(_("There is a syntax error in the barcode pattern ") + self.pattern + _(": a rule can only contain one pair of braces."))