From 3d483b14d4c56712ece57230c33f7e47a252730a Mon Sep 17 00:00:00 2001 From: Aaron Bohy Date: Fri, 31 Oct 2014 15:09:11 +0100 Subject: [PATCH] [IMP] stock: use of barcodes nomenclatures to scan weighted products --- addons/point_of_sale/point_of_sale.py | 6 +- addons/point_of_sale/static/src/js/models.js | 4 - addons/pos_barcodes/barcodes.py | 94 ++++++++++++++++++++ addons/stock/stock.py | 44 +++++++-- 4 files changed, 134 insertions(+), 14 deletions(-) diff --git a/addons/point_of_sale/point_of_sale.py b/addons/point_of_sale/point_of_sale.py index 5a43cc5bdb3..93f4ac17adf 100644 --- a/addons/point_of_sale/point_of_sale.py +++ b/addons/point_of_sale/point_of_sale.py @@ -169,10 +169,10 @@ class pos_config(osv.osv): return company_id def _get_default_nomenclature(self, cr, uid, context=None): - default_nom_obj = self.pool.get('barcode.nomenclature') - res = default_nom_obj.search(cr, uid, [('id', '=', 1)], limit=1, context=context) + nom_obj = self.pool.get('barcode.nomenclature') + res = nom_obj.search(cr, uid, [], limit=1, context=context) if res and res[0]: - return default_nom_obj.browse(cr, uid, res[0], context=context).id + return nom_obj.browse(cr, uid, res[0], context=context).id return False _defaults = { diff --git a/addons/point_of_sale/static/src/js/models.js b/addons/point_of_sale/static/src/js/models.js index 6f7dc4435ad..48c26923462 100644 --- a/addons/point_of_sale/static/src/js/models.js +++ b/addons/point_of_sale/static/src/js/models.js @@ -311,7 +311,6 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal fields: ['name','rule_ids'], domain: function(self){ return [] }, loaded: function(self,nomenclatures){ - console.log("PUSH"); if (self.config.barcode_nomenclature_id) { for (var i = 0; i < nomenclatures.length; i++) { if (nomenclatures[i].id === self.config.barcode_nomenclature_id[0]) { @@ -320,8 +319,6 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal } } self.nomenclature = self.nomenclature || null; - console.log(self.nomenclature); - console.log(self.attributes); }, }, { model: 'barcode.rule', @@ -329,7 +326,6 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal domain: function(self){ return [['barcode_nomenclature_id','=',self.nomenclature ? self.nomenclature.id : 0]]; }, loaded: function(self,rules){ if (self.nomenclature) { - console.log("PUSH2"); rules = rules.sort(function(a,b){ return a.sequence - b.sequence; }); self.nomenclature.rules = rules; for (var i = 0; i < rules.length; i++) { diff --git a/addons/pos_barcodes/barcodes.py b/addons/pos_barcodes/barcodes.py index 5a223ce0216..481d5743a3a 100644 --- a/addons/pos_barcodes/barcodes.py +++ b/addons/pos_barcodes/barcodes.py @@ -20,6 +20,7 @@ ############################################################################## import logging +import re import openerp from openerp import tools @@ -36,6 +37,99 @@ class barcode_nomenclature(osv.osv): 'rule_ids': fields.one2many('barcode.rule','barcode_nomenclature_id','Rules', help='The list of barcode rules'), } + # returns the checksum of the ean, or -1 if the ean has not the correct length, ean must be a string + def ean_checksum(self, ean): + code = list(ean) + if len(code) != 13: + return -1 + + oddsum = evensum = total = 0 + code = code[:-1] # Remove checksum + for i in range(len(code)): + if i % 2 == 0: + evensum += int(code[i]) + else: + oddsum += int(code[i]) + total = oddsum * 3 + evensum + return int((10 - total % 10) % 10) + + # Returns true if the ean is a valid EAN codebar number by checking the control digit. + # ean must be a string + def check_ean(self, ean): + return re.match(re.compile('\d+$'), ean) and (self.ean_checksum(ean) == int(ean[len(ean)-1])) + + # Returns a valid zero padded ean13 from an ean prefix. the ean prefix must be a string. + def sanitize_ean(self, ean): + ean = ean[0:13] + ean = ean + (13-len(ean))*'0' + return ean[0:12] + str(self.ean_checksum(ean)) + + # Attempts to interpret an ean (string encoding an ean) + # It will check its validity then return an object containing various + # information about the ean. + # most importantly : + # - code : the ean + # - type : the type of the ean: + # 'price' | 'weight' | 'product' | 'cashier' | 'client' | 'discount' | 'error' + # - value : if the id encodes a numerical value, it will be put there + # - base_code : the ean code with all the encoding parts set to zero; the one put on + # the product in the backend + def parse_ean(self, ean): + parse_result = {'encoding': 'ean13', 'type': 'error', 'code': ean, 'base_code': ean, 'value': 0} + + rules = [] + for rule in self.rule_ids: + rules.append({'type': rule.type, 'sequence': rule.sequence, 'pattern': rule.pattern}) + + if not self.check_ean(ean): + return parse_result + + def is_number(char): + n = ord(char) + return n >= 48 and n <= 57 + + def match_pattern(ean,pattern): + for i in range(len(pattern)): + p = pattern[i] + e = ean[i] + if is_number(p) and p != e: + return False + return True + + def get_value(ean,pattern): + value = 0 + decimals = 0 + for i in range(len(pattern)): + p = pattern[i] + v = int(ean[i]) + if p == 'N': + value *= 10 + value += v + elif p == 'D': #FIXME precision .... + decimals += 1 + value += v * pow(10,-decimals) + return value + + def get_basecode(ean,pattern): + base = '' + for i in range(len(pattern)): + p = pattern[i] + v = ean[i] + if p == '*' or is_number(p): + base += v + else: + base += '0' + return self.sanitize_ean(base) + + for rule in rules: + if match_pattern(ean, rule['pattern']): + parse_result['type'] = rule['type'] + parse_result['value'] = get_value(ean, rule['pattern']) + parse_result['base_code'] = get_basecode(ean, rule['pattern']) + return parse_result + + return parse_result + class barcode_rule(osv.osv): _name = 'barcode.rule' _order = 'sequence asc' diff --git a/addons/stock/stock.py b/addons/stock/stock.py index 59fd97749e3..c3fc1f220d7 100644 --- a/addons/stock/stock.py +++ b/addons/stock/stock.py @@ -818,8 +818,16 @@ class stock_picking(osv.osv): 'stock.picking': (lambda self, cr, uid, ids, ctx: ids, ['move_lines'], 10), 'stock.move': (_get_pickings, ['group_id', 'picking_id'], 10), }), + 'barcode_nomenclature_id': fields.many2one('barcode.nomenclature','Barcode Nomenclature', help='A barcode nomenclature', required="True"), } + def _get_default_nomenclature(self, cr, uid, context=None): + nom_obj = self.pool.get('barcode.nomenclature') + res = nom_obj.search(cr, uid, [], limit=1, context=context) + if res and res[0]: + return nom_obj.browse(cr, uid, res[0], context=context).id + return False + _defaults = { 'name': '/', 'state': 'draft', @@ -828,6 +836,7 @@ class stock_picking(osv.osv): 'date': fields.datetime.now, 'company_id': lambda self, cr, uid, c: self.pool.get('res.company')._company_default_get(cr, uid, 'stock.picking', context=c), 'recompute_pack_op': True, + 'barcode_nomenclature_id': _get_default_nomenclature, } _sql_constraints = [ ('name_uniq', 'unique(name, company_id)', 'Reference must be unique per company!'), @@ -1437,7 +1446,6 @@ class stock_picking(osv.osv): return self.pool.get('stock.pack.operation')._search_and_increment(cr, uid, picking_id, [('product_id', '=', product_id),('id', '=', op_id)], increment=increment, context=context) def process_barcode_from_ui(self, cr, uid, picking_id, barcode_str, visible_op_ids, context=None): - print barcode_str '''This function is called each time there barcode scanner reads an input''' lot_obj = self.pool.get('stock.production.lot') package_obj = self.pool.get('stock.quant.package') @@ -1445,6 +1453,26 @@ class stock_picking(osv.osv): stock_operation_obj = self.pool.get('stock.pack.operation') stock_location_obj = self.pool.get('stock.location') answer = {'filter_loc': False, 'operation_id': False} + + # Barcode Nomenclatures + this_picking = self.search(cr, uid, [('id', '=', picking_id)], context=context) + rec = self.browse(cr, uid, this_picking, context=context) + if not rec: + return answer # TODO: return specific error? + else: + barcode_nom = rec.barcode_nomenclature_id + parse_result = barcode_nom.parse_ean(barcode_str) + + #check if the barcode is a weighted barcode + if parse_result['type'] == 'weight': + matching_product_ids = product_obj.search(cr, uid, ['|', ('ean13', '=', parse_result['base_code']), ('default_code', '=', barcode_str)], context=context) + if matching_product_ids: + op_id = stock_operation_obj._search_and_increment(cr, uid, picking_id, [('product_id', '=', matching_product_ids[0])], filter_visible=True, visible_op_ids=visible_op_ids, weight=parse_result['value'], increment=True, context=context) + answer['operation_id'] = op_id + return answer + else: + print "Weighted barcode but product not found" #TODO: what to do here? + #check if the barcode correspond to a location matching_location_ids = stock_location_obj.search(cr, uid, [('loc_barcode', '=', barcode_str)], context=context) if matching_location_ids: @@ -3917,11 +3945,12 @@ class stock_pack_operation(osv.osv): new_lot_id = self.pool.get('stock.production.lot').create(cr, uid, val, context=context) self.write(cr, uid, id, {'lot_id': new_lot_id}, context=context) - def _search_and_increment(self, cr, uid, picking_id, domain, filter_visible=False, visible_op_ids=False, increment=True, context=None): - '''Search for an operation with given 'domain' in a picking, if it exists increment the qty (+1) otherwise create it + def _search_and_increment(self, cr, uid, picking_id, domain, filter_visible=False, visible_op_ids=False, weight=1, increment=True, context=None): + '''Search for an operation with given 'domain' in a picking, if it exists increment the qty (+weight) otherwise create it :param domain: list of tuple directly reusable as a domain context can receive a key 'current_package_id' with the package to consider for this operation + weight is used for weighted products for which quantity != 1 (not per unit but e.g. per kg) returns True ''' if context is None: @@ -3942,10 +3971,11 @@ class stock_pack_operation(osv.osv): op_obj = self.browse(cr, uid, operation_id, context=context) qty = op_obj.qty_done if increment: - qty += 1 + qty += weight else: - qty -= 1 if qty >= 1 else 0 - if qty == 0 and op_obj.product_qty == 0: + qty -= 1 #if qty >= 1 else 0 + #if qty == 0 and op_obj.product_qty == 0: + if qty < 0 and op_obj.product_qty == 0: #we have a line with 0 qty set, so delete it self.unlink(cr, uid, [operation_id], context=context) return False @@ -3959,7 +3989,7 @@ class stock_pack_operation(osv.osv): 'product_qty': 0, 'location_id': picking.location_id.id, 'location_dest_id': picking.location_dest_id.id, - 'qty_done': 1, + 'qty_done': weight, } for key in domain: var_name, dummy, value = key