diff --git a/addons/barcodes/barcodes.py b/addons/barcodes/barcodes.py index ad72084bc03..c9b9118728a 100644 --- a/addons/barcodes/barcodes.py +++ b/addons/barcodes/barcodes.py @@ -9,22 +9,27 @@ from openerp.exceptions import ValidationError _logger = logging.getLogger(__name__) + +UPC_EAN_CONVERSIONS = [ + ('none','Never'), + ('ean2upc','EAN-13 to UPC-A'), + ('upc2ean','UPC-A to EAN-13'), + ('always','Always'), +] + class barcode_nomenclature(osv.osv): _name = 'barcode.nomenclature' _columns = { 'name': fields.char('Nomenclature Name', size=32, required=True, help='An internal identification of the barcode nomenclature'), 'rule_ids': fields.one2many('barcode.rule','barcode_nomenclature_id','Rules', help='The list of barcode rules'), - 'strict_ean': fields.boolean('Use strict EAN13', - 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.') + 'upc_ean_conv': fields.selection(UPC_EAN_CONVERSIONS, 'UPC/EAN Conversion', required=True, + 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.'), } _defaults = { - 'strict_ean': False, + 'upc_ean_conv': 'always', } - def use_strict_ean(self): - return self.strict_ean - # returns the checksum of the ean13, or -1 if the ean has not the correct length, ean must be a string def ean_checksum(self, ean): code = list(ean) @@ -41,9 +46,34 @@ class barcode_nomenclature(osv.osv): total = oddsum * 3 + evensum return int((10 - total % 10) % 10) + # returns the checksum of the ean8, or -1 if the ean has not the correct length, ean must be a string + def ean8_checksum(self,ean): + code = list(ean) + if len(code) != 8: + return -1 + + sum1 = ean[1] + ean[3] + ean[5] + sum2 = ean[0] + ean[2] + ean[4] + ean[6] + total = sum1 + 3 * sum2 + return int((10 - total % 10) % 10) + # returns true if the barcode is a valid EAN barcode def check_ean(self, ean): - return re.match("^\d+$", ean) and self.ean_checksum(ean) == int(ean[len(ean)-1]) + return re.match("^\d+$", ean) and self.ean_checksum(ean) == int(ean[-1]) + + # returns true if the barcode string is encoded with the provided encoding. + def check_encoding(self, barcode, encoding): + if encoding == 'ean13': + return len(barcode) == 13 and re.match("^\d+$", barcode) and self.ean_checksum(barcode) == int(barcode[-1]) + elif encoding == 'ean8': + return len(barcode) == 8 and re.match("^\d+$", barcode) and self.ean8_checksum(barcode) == int(barcode[-1]) + elif encoding == 'upca': + return len(barcode) == 12 and re.match("^\d+$", barcode) and self.ean_checksum("0"+barcode) == int(barcode[-1]) + elif encoding == 'any': + return True + else: + return False + # Returns a valid zero padded ean13 from an ean prefix. the ean prefix must be a string. def sanitize_ean(self, ean): @@ -51,6 +81,47 @@ class barcode_nomenclature(osv.osv): ean = ean + (13-len(ean))*'0' return ean[0:12] + str(self.ean_checksum(ean)) + # Returns a valid zero padded UPC-A from a UPC-A prefix. the UPC-A prefix must be a string. + def sanitize_upc(self, upc): + return self.sanitize_ean('0'+upc)[1:] + + # Checks if barcode matches the pattern + # Additionnaly retrieves the optional numerical content in barcode + # Returns 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 + def match_pattern(self, barcode, pattern): + 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 + # Attempts to interpret an barcode (string encoding a barcode) # It will return an object containing various information about the barcode. # most importantly : @@ -65,62 +136,24 @@ class barcode_nomenclature(osv.osv): 'type': 'error', 'code': barcode, 'base_code': barcode, - 'value': 0} - - # Checks if barcode matches the pattern - # Additionnaly retrieves the optional numerical content in barcode - # Returns 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 - def match_pattern(barcode, pattern): - 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 - + 'value': 0, + } rules = [] for rule in self.rule_ids: rules.append({'type': rule.type, 'encoding': rule.encoding, 'sequence': rule.sequence, 'pattern': rule.pattern, 'alias': rule.alias}) - # If the nomenclature does not use strict EAN, prepend the barcode with a 0 if it seems - # that it has been striped by the barcode scanner, when trying to match an EAN13 rule - prepend_zero = False - if not self.strict_ean and len(barcode) == 12 and self.check_ean("0"+barcode): - prepend_zero = True - for rule in rules: cur_barcode = barcode - if prepend_zero and rule['encoding'] == "ean13": + if rule['encoding'] == 'ean13' and self.check_encoding(barcode,'upca') and self.upc_ean_conv in ['upc2ean','always']: cur_barcode = '0'+cur_barcode + elif rule['encoding'] == 'upca' and self.check_encoding(barcode,'ean13') and barcode[0] == '0' and self.upc_ean_conv in ['ean2upc','always']: + cur_barcode = cur_barcode[1:] - match = match_pattern(cur_barcode, rule['pattern']) + if not self.check_encoding(barcode,rule['encoding']): + continue + + match = self.match_pattern(cur_barcode, rule['pattern']) if match['match']: if rule['type'] == 'alias': barcode = rule['alias'] @@ -132,12 +165,21 @@ class barcode_nomenclature(osv.osv): 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 +ENCODINGS = [ + ('any','Any'), + ('ean13','EAN-13'), + ('ean8','EAN-8'), + ('upca','UPC-A'), +] + class barcode_rule(models.Model): _name = 'barcode.rule' _order = 'sequence asc' @@ -150,15 +192,15 @@ class barcode_rule(models.Model): 'name': fields.char('Rule Name', size=32, required=True, help='An internal identification for this barcode nomenclature rule'), 'barcode_nomenclature_id': fields.many2one('barcode.nomenclature','Barcode Nomenclature'), 'sequence': fields.integer('Sequence', help='Used to order rules such that rules with a smaller sequence match first'), - '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'), + 'encoding': fields.selection(ENCODINGS,'Encoding',required=True,help='This rule will apply only if the barcode is encoded with the specified encoding'), 'type': fields.selection('_get_type_selection','Type', required=True), - 'pattern': fields.char('Barcode Pattern', size=32, help="The barcode matching pattern"), - 'alias': fields.char('Alias',size=32,help='The matched pattern will alias to this barcode',required=True), + 'pattern': fields.char('Barcode Pattern', size=32, help="The barcode matching pattern", required=True), + 'alias': fields.char('Alias',size=32,help='The matched pattern will alias to this barcode', required=True), } _defaults = { 'type': 'product', - 'pattern': '*', + 'pattern': '.*', 'encoding': 'any', 'alias': "0", } @@ -175,3 +217,7 @@ class barcode_rule(models.Model): raise ValidationError(_("There is a syntax error in the barcode pattern ") + self.pattern + _(": empty braces.")) elif len(findall) != 0: raise ValidationError(_("There is a syntax error in the barcode pattern ") + self.pattern + _(": a rule can only contain one pair of braces.")) + elif p == '*': + raise ValidationError(_(" '*' is not a valid Regex Barcode Pattern. Did you mean '.*' ?")) + + diff --git a/addons/barcodes/barcodes_view.xml b/addons/barcodes/barcodes_view.xml index e3089837674..70c5c657381 100644 --- a/addons/barcodes/barcodes_view.xml +++ b/addons/barcodes/barcodes_view.xml @@ -10,7 +10,7 @@ - +

diff --git a/addons/barcodes/data/barcodes_data.xml b/addons/barcodes/data/barcodes_data.xml index 2c8b711ba67..69ac657e432 100644 --- a/addons/barcodes/data/barcodes_data.xml +++ b/addons/barcodes/data/barcodes_data.xml @@ -11,7 +11,7 @@ 90 product any - * + .* diff --git a/addons/barcodes/static/src/js/barcode_parser.js b/addons/barcodes/static/src/js/barcode_parser.js index 3ce07be1755..13052288f8f 100644 --- a/addons/barcodes/static/src/js/barcode_parser.js +++ b/addons/barcodes/static/src/js/barcode_parser.js @@ -20,7 +20,7 @@ openerp.barcodes = function(instance) { load: function(){ var self = this; return new instance.web.Model('barcode.nomenclature') - .query(['name','rule_ids','strict_ean']) + .query(['name','rule_ids','upc_ean_conv']) .filter([['id','=',this.nomenclature_id[0]]]) .first() .then(function(nomenclature){ @@ -60,12 +60,44 @@ openerp.barcodes = function(instance) { return Number((10 - total % 10) % 10); }, + // returns the checksum of the ean8, or -1 if the ean has not the correct length, ean must be a string + ean8_checskum: function(ean){ + var code = ean.split(''); + if (code.length !== 8) { + return -1; + } + var sum1 = Number(code[1]) + Number(code[3]) + Number(code[5]); + var sum2 = Number(code[0]) + Number(code[2]) + Number(code[4]) + Number(code[6]); + var total = sum1 + 3 * sum2; + return Number((10 - total % 10) % 10); + }, + + // returns true if the ean is a valid EAN barcode number by checking the control digit. // ean must be a string check_ean: function(ean){ return /^\d+$/.test(ean) && this.ean_checksum(ean) === Number(ean[ean.length-1]); }, + // returns true if the barcode string is encoded with the provided encoding. + check_encoding: function(barcode, encoding) { + var len = barcode.length; + var allnum = /^\d+$/.test(barcode); + var check = Number(barcode[len-1]); + + if (encoding === 'ean13') { + return len === 13 && allnum && this.ean_checksum(barcode) === check; + } else if (encoding === 'ean8') { + return len === 8 && allnum && this.ean8_checksum(barcode) === check; + } else if (encoding === 'upca') { + return len === 12 && allnum && this.ean_checksum('0'+barcode) === check; + } else if (encoding === 'any') { + return true; + } else { + return false; + } + }, + // returns a valid zero padded ean13 from an ean prefix. the ean prefix must be a string. sanitize_ean: function(ean){ ean = ean.substr(0,13); @@ -75,6 +107,57 @@ openerp.barcodes = function(instance) { } return ean.substr(0,12) + this.ean_checksum(ean); }, + + // Returns a valid zero padded UPC-A from a UPC-A prefix. the UPC-A prefix must be a string. + sanitize_upc: function(upc) { + return this.sanitize_ean('0'+upc).substr(1,12); + }, + + // Checks if barcode matches the pattern + // Additionnaly retrieves the optional numerical content in barcode + // Returns 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 + match_pattern: function (barcode, pattern){ + var match = { + value: 0, + base_code: barcode, + match: false, + }; + barcode = barcode.replace("\\", "\\\\").replace("{", '\{').replace("}", "\}").replace(".", "\."); + + var numerical_content = pattern.match(/[{][N]*[D]*[}]/); // look for numerical content in pattern + var base_pattern = pattern; + if(numerical_content){ // the pattern encodes a numerical content + var num_start = numerical_content.index; // start index of numerical content + var num_length = numerical_content[0].length; // length of numerical content + var value_string = barcode.substr(num_start, num_length-2); // numerical content in barcode + var whole_part_match = numerical_content[0].match("[{][N]*[D}]"); // looks for whole part of numerical content + var decimal_part_match = numerical_content[0].match("[{N][D]*[}]"); // looks for decimal part + var whole_part = value_string.substr(0, whole_part_match.index+whole_part_match[0].length-2); // retrieve whole part of numerical content in barcode + var decimal_part = "0." + value_string.substr(decimal_part_match.index, decimal_part_match[0].length-1); // retrieve decimal part + if (whole_part === ''){ + whole_part = '0'; + } + match['value'] = parseInt(whole_part) + parseFloat(decimal_part); + + // replace numerical content by 0's in barcode and pattern + match['base_code'] = barcode.substr(0,num_start); + var base_pattern = pattern.substr(0,num_start); + for(var i=0;i<(num_length-2);i++) { + match['base_code'] += "0"; + base_pattern += "0"; + } + match['base_code'] += barcode.substr(num_start+num_length-2,barcode.length-1); + base_pattern += pattern.substr(num_start+num_length,pattern.length-1); + + match['base_code'] = match['base_code'].replace("\\\\", "\\").replace("\{", "{").replace("\}","}").replace("\.","."); + } + + match['match'] = match['base_code'].substr(0,base_pattern.length).match(base_pattern); + return match; + }, // attempts to interpret a barcode (string encoding a barcode Code-128) // it will return an object containing various information about the barcode. @@ -86,7 +169,6 @@ openerp.barcodes = function(instance) { // - base_code : the barcode with all the encoding parts set to zero; the one put on // the product in the backend parse_barcode: function(barcode){ - var self = this; var parsed_result = { encoding: '', type:'error', @@ -94,69 +176,32 @@ openerp.barcodes = function(instance) { base_code: barcode, value: 0, }; - if (!self.nomenclature) { + + if (!this.nomenclature) { return parsed_result; } - // Checks if barcode matches the pattern - // Additionnaly retrieves the optional numerical content in barcode - // Returns 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 - function match_pattern(barcode, pattern){ - var match = { - value: 0, - base_code: barcode, - match: false, - }; - barcode = barcode.replace("\\", "\\\\").replace("{", '\{').replace("}", "\}").replace(".", "\."); - - var numerical_content = pattern.match(/[{][N]*[D]*[}]/); // look for numerical content in pattern - var base_pattern = pattern; - if(numerical_content){ // the pattern encodes a numerical content - var num_start = numerical_content.index; // start index of numerical content - var num_length = numerical_content[0].length; // length of numerical content - var value_string = barcode.substr(num_start, num_length-2); // numerical content in barcode - var whole_part_match = numerical_content[0].match("[{][N]*[D}]"); // looks for whole part of numerical content - var decimal_part_match = numerical_content[0].match("[{N][D]*[}]"); // looks for decimal part - var whole_part = value_string.substr(0, whole_part_match.index+whole_part_match[0].length-2); // retrieve whole part of numerical content in barcode - var decimal_part = "0." + value_string.substr(decimal_part_match.index, decimal_part_match[0].length-1); // retrieve decimal part - if (whole_part === ''){ - whole_part = '0'; - } - match['value'] = parseInt(whole_part) + parseFloat(decimal_part); - - // replace numerical content by 0's in barcode and pattern - match['base_code'] = barcode.substr(0,num_start); - var base_pattern = pattern.substr(0,num_start); - for(var i=0;i<(num_length-2);i++) { - match['base_code'] += "0"; - base_pattern += "0"; - } - match['base_code'] += barcode.substr(num_start+num_length-2,barcode.length-1); - base_pattern += pattern.substr(num_start+num_length,pattern.length-1); - - match['base_code'] = match['base_code'].replace("\\\\", "\\").replace("\{", "{").replace("\}","}").replace("\.","."); - } - - match['match'] = match['base_code'].substr(0,base_pattern.length).match(base_pattern); - return match; - } - - // If the nomenclature does not use strict EAN, prepend the barcode with a 0 if it seems - // that it has been striped by the barcode scanner, when trying to match an EAN13 rule - var prepend_zero = false; - if(!self.strict_ean && barcode.length === 12 && self.check_ean("0"+barcode)){ - prepend_zero = true; - } - var rules = self.nomenclature.rules; + var rules = this.nomenclature.rules; for (var i = 0; i < rules.length; i++) { + var rule = rules[i]; var cur_barcode = barcode; - if (prepend_zero && rules[i].encoding == 'ean13'){ - cur_barcode = '0'+cur_barcode; + + if ( rule.encoding === 'ean13' && + this.check_encoding(barcode,'upca') && + this.nomenclature.upc_ean_conv in {'upc2ean':'','always':''} ){ + cur_barcode = '0' + cur_barcode; + } else if (rule.encoding === 'upca' && + this.check_encoding(barcode,'ean13') && + barcode[0] === '0' && + this.upc_ean_conv in {'ean2upc':'','always':''} ){ + cur_barcode = cur_barcode.substr(1,12); } - var match = match_pattern(cur_barcode,rules[i].pattern); + + if (!this.check_encoding(cur_barcode,rule.encoding)) { + continue; + } + + var match = this.match_pattern(cur_barcode,rules[i].pattern); if (match.match) { if(rules[i].type === 'alias') { barcode = rules[i].alias; diff --git a/addons/point_of_sale/static/src/js/chrome.js b/addons/point_of_sale/static/src/js/chrome.js index ebfb0e8a84c..2e9d6f17885 100644 --- a/addons/point_of_sale/static/src/js/chrome.js +++ b/addons/point_of_sale/static/src/js/chrome.js @@ -235,11 +235,11 @@ openerp.point_of_sale.load_chrome = function load_chrome(instance, module){ //mo self.pos.proxy.debug_reset_weight(); }); this.$('.button.custom_ean').click(function(){ - var ean = self.pos.barcode_reader.sanitize_ean(self.$('input.ean').val() || '0'); + var ean = self.pos.barcode_reader.barcode_parser.sanitize_ean(self.$('input.ean').val() || '0'); self.$('input.ean').val(ean); self.pos.barcode_reader.scan(ean); }); - this.$('.button.reference').click(function(){ + this.$('.button.barcode').click(function(){ self.pos.barcode_reader.scan(self.$('input.ean').val()); }); this.$('.button.show_orders').click(function(){ @@ -268,12 +268,6 @@ openerp.point_of_sale.load_chrome = function load_chrome(instance, module){ //mo }, }); }); - _.each(this.eans, function(ean, name){ - self.$('.button.'+name).click(function(){ - self.$('input.ean').val(ean); - self.pos.barcode_reader.scan(ean); - }); - }); _.each(this.events, function(name){ self.pos.proxy.add_notification(name,function(){ self.$('.event.'+name).stop().clearQueue().css({'background-color':'#6CD11D'}); @@ -417,6 +411,8 @@ openerp.point_of_sale.load_chrome = function load_chrome(instance, module){ //mo this.widget = {}; // contains references to subwidgets instances + this.cleanup_dom(); + this.pos.ready.done(function(){ self.build_chrome(); self.build_widgets(); @@ -428,7 +424,7 @@ openerp.point_of_sale.load_chrome = function load_chrome(instance, module){ //mo }); }, - build_chrome: function() { + cleanup_dom: function() { // remove default webclient handlers that induce click delay $(document).off(); $(window).off(); @@ -438,7 +434,9 @@ openerp.point_of_sale.load_chrome = function load_chrome(instance, module){ //mo $('document').off(); $('.oe_web_client').off(); $('.openerp_webclient_container').off(); + }, + build_chrome: function() { FastClick.attach(document.body); instance.webclient.set_content_full_screen(true); diff --git a/addons/point_of_sale/static/src/xml/pos.xml b/addons/point_of_sale/static/src/xml/pos.xml index 4a273fb84a2..5547a4c70f7 100644 --- a/addons/point_of_sale/static/src/xml/pos.xml +++ b/addons/point_of_sale/static/src/xml/pos.xml @@ -1111,15 +1111,8 @@

Barcode Scanner

Unsent Orders