Merge branch 'master-pos-wms-fva'
[odoo/odoo.git] / addons / barcodes / static / src / js / barcode_parser.js
diff --git a/addons/barcodes/static/src/js/barcode_parser.js b/addons/barcodes/static/src/js/barcode_parser.js
new file mode 100644 (file)
index 0000000..3ce07be
--- /dev/null
@@ -0,0 +1,184 @@
+openerp.barcodes = function(instance) {
+    "use strict";
+
+    instance.barcodes = {};
+    var module = instance.barcodes;
+
+    // The BarcodeParser is used to detect what is the category
+    // of a barcode (product, partner, ...) and extract an encoded value
+    // (like weight, price, etc.)
+    module.BarcodeParser = instance.web.Class.extend({
+        init: function(attributes) {
+            var self = this;
+            this.nomenclature_id = attributes.nomenclature_id;
+            this.loaded = this.load();
+        },
+
+        // This loads the barcode nomenclature and barcode rules which are
+        // necessary to parse the barcodes. The BarcodeParser is operational
+        // only when those data have been loaded
+        load: function(){
+            var self = this;
+            return new instance.web.Model('barcode.nomenclature')
+                .query(['name','rule_ids','strict_ean'])
+                .filter([['id','=',this.nomenclature_id[0]]])
+                .first()
+                .then(function(nomenclature){
+                    self.nomenclature = nomenclature;
+
+                    return new instance.web.Model('barcode.rule')
+                        .query(['name','sequence','type','encoding','pattern','alias'])
+                        .filter([['barcode_nomenclature_id','=',self.nomenclature.id ]])
+                        .all()
+                }).then(function(rules){
+                    rules = rules.sort(function(a,b){ return a.sequence - b.sequence; });
+                    self.nomenclature.rules = rules;
+                });
+        },
+
+        // resolves when the barcode parser is operational.
+        is_loaded: function() {
+            return self.loaded;
+        },
+
+        // returns the checksum of the ean13, or -1 if the ean has not the correct length, ean must be a string
+        ean_checksum: function(ean){
+            var code = ean.split('');
+            if(code.length !== 13){
+                return -1;
+            }
+            var oddsum = 0, evensum = 0, total = 0;
+            code = code.reverse().splice(1);
+            for(var i = 0; i < code.length; i++){
+                if(i % 2 == 0){
+                    oddsum += Number(code[i]);
+                }else{
+                    evensum += Number(code[i]);
+                }
+            }
+            total = oddsum * 3 + evensum;
+            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 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);
+
+            for(var n = 0, count = (13 - ean.length); n < count; n++){
+                ean = ean + '0';
+            }
+            return ean.substr(0,12) + this.ean_checksum(ean);
+        },
+                
+        // attempts to interpret a barcode (string encoding a barcode Code-128)
+        // it will return an object containing various information about the barcode.
+        // most importantly : 
+        // - code    : the barcode
+        // - type   : the type of the barcode (e.g. alias, unit product, weighted product...)
+        //
+        // - value  : if the barcode encodes a numerical value, it will be put there
+        // - 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',  
+                code:barcode,
+                base_code: barcode,
+                value: 0,
+            };
+            if (!self.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;
+            for (var i = 0; i < rules.length; i++) {
+                var cur_barcode = barcode;
+                if (prepend_zero && rules[i].encoding == 'ean13'){
+                    cur_barcode = '0'+cur_barcode;
+                }
+                var match = match_pattern(cur_barcode,rules[i].pattern);
+                if (match.match) {
+                    if(rules[i].type === 'alias') {
+                        barcode = rules[i].alias;
+                        parsed_result.code = barcode;
+                        parsed_result.type = 'alias';
+                    }
+                    else {
+                        parsed_result.encoding  = rules[i].encoding;
+                        parsed_result.type      = rules[i].type;
+                        parsed_result.value     = match.value
+                        parsed_result.code      = cur_barcode;
+                        if (rules[i].encoding === "ean13"){
+                            parsed_result.base_code = this.sanitize_ean(match.base_code);
+                        }
+                        else{
+                            parsed_result.base_code = match.base_code;
+                        }
+                        return parsed_result;
+                    }
+                }
+            }
+            return parsed_result;
+        },
+    });
+}