From 8bbb20650464b5784fd3afa0eb219ed1802e2a4f Mon Sep 17 00:00:00 2001 From: "ddm@openerp.com" <> Date: Thu, 13 Feb 2014 16:24:49 +0100 Subject: [PATCH 1/6] [IMP] save new orders only bzr revid: ddm@openerp.com-20140213152449-tarl15agw9r3teqg --- addons/point_of_sale/point_of_sale.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/addons/point_of_sale/point_of_sale.py b/addons/point_of_sale/point_of_sale.py index 30fcff2e5e6..dec4431a15c 100644 --- a/addons/point_of_sale/point_of_sale.py +++ b/addons/point_of_sale/point_of_sale.py @@ -513,13 +513,18 @@ class pos_order(osv.osv): _order = "id desc" def create_from_ui(self, cr, uid, orders, context=None): - #_logger.info("orders: %r", orders) + + # Keep only new orders + submitted_references = [o['data']['name'] for o in orders] + existing_orders = self.search_read(cr, uid, [('pos_reference', 'in', submitted_references)], context) + existing_references = set([o['pos_reference'] for o in existing_orders]) + orders_to_save = [o for o in orders if o['data']['name'] not in existing_references] + order_ids = [] - for tmp_order in orders: + for tmp_order in orders_to_save: to_invoice = tmp_order['to_invoice'] order = tmp_order['data'] - order_id = self.create(cr, uid, { 'name': order['name'], 'user_id': order['user_id'] or False, From e7f238615ca1c79700a0deacb1d1799b797a0472 Mon Sep 17 00:00:00 2001 From: "ddm@openerp.com" <> Date: Thu, 13 Feb 2014 17:38:49 +0100 Subject: [PATCH 2/6] [IMP] fetch only relevant fields when checking for dupes bzr revid: ddm@openerp.com-20140213163849-8v485vq5v4xmeocg --- addons/point_of_sale/point_of_sale.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/point_of_sale/point_of_sale.py b/addons/point_of_sale/point_of_sale.py index 9e78a29cfec..01e7a49ec8c 100644 --- a/addons/point_of_sale/point_of_sale.py +++ b/addons/point_of_sale/point_of_sale.py @@ -517,7 +517,7 @@ class pos_order(osv.osv): # Keep only new orders submitted_references = [o['data']['name'] for o in orders] - existing_orders = self.search_read(cr, uid, [('pos_reference', 'in', submitted_references)], context) + existing_orders = self.search_read(cr, uid, domain=[('pos_reference', 'in', submitted_references)], fields=['pos_reference'], context=context) existing_references = set([o['pos_reference'] for o in existing_orders]) orders_to_save = [o for o in orders if o['data']['name'] not in existing_references] From 3ea931c48637265183ce17b8cfb21c858f90a56d Mon Sep 17 00:00:00 2001 From: "ddm@openerp.com" <> Date: Fri, 14 Feb 2014 15:49:30 +0100 Subject: [PATCH 3/6] [IMP] save all orders at once (client) bzr revid: ddm@openerp.com-20140214144930-5zpn3miys1hwrdoa --- addons/point_of_sale/static/src/js/models.js | 97 ++++++++++---------- 1 file changed, 51 insertions(+), 46 deletions(-) diff --git a/addons/point_of_sale/static/src/js/models.js b/addons/point_of_sale/static/src/js/models.js index 6b54c14113d..90aa5fece05 100644 --- a/addons/point_of_sale/static/src/js/models.js +++ b/addons/point_of_sale/static/src/js/models.js @@ -421,62 +421,67 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal // it is therefore important to only call this method from inside a mutex // this method returns a deferred indicating wether the sending was successful or not // there is a timeout parameter which is set to 2 seconds by default. - _flush_order: function(order_id, options){ - var self = this; - options = options || {}; - timeout = typeof options.timeout === 'number' ? options.timeout : 7500; - - this.set('synch',{state:'connecting', pending: this.get('synch').pending}); - - var order = this.db.get_order(order_id); - order.to_invoice = options.to_invoice || false; - - if(!order){ - // flushing a non existing order always fails - return (new $.Deferred()).reject(); - } - - // we try to send the order. shadow prevents a spinner if it takes too long. (unless we are sending an invoice, - // then we want to notify the user that we are waiting on something ) - var rpc = (new instance.web.Model('pos.order')).call('create_from_ui',[[order]],undefined,{shadow: !options.to_invoice, timeout:timeout}); - - rpc.fail(function(unused,event){ - // prevent an error popup creation by the rpc failure - // we want the failure to be silent as we send the orders in the background - event.preventDefault(); - console.error('Failed to send order:',order); - }); - - rpc.done(function(){ - self.db.remove_order(order_id); - var pending = self.db.get_orders().length; - self.set('synch',{state: pending ? 'connecting' : 'connected', pending:pending}); - }); - - return rpc; + _flush_order: function( order_id, options) { + return this._flush_all_orders([this.db.get_order(order_id)], options); }, // attempts to send all the locally stored orders. As with _flush_order, it should only be // called from within a mutex. // this method returns a deferred that always succeeds when all orders have been tried to be sent, // even if none of them could actually be sent. - _flush_all_orders: function(){ + _flush_all_orders: function () { var self = this; - var orders = this.db.get_orders(); - var tried_all = new $.Deferred(); + self.set('synch', { + state: 'connecting', + pending: self.get('synch').pending + }); + return self._save_to_server(self.db.get_orders()).done(function () { + var pending = self.db.get_orders().length; + self.set('synch', { + state: pending ? 'connecting' : 'connected', + pending: pending + }); + }); + }, - function rec_flush(index){ - if(index < orders.length){ - self._flush_order(orders[index].id).always(function(){ - rec_flush(index+1); - }) - }else{ - tried_all.resolve(); - } + // send an array of orders to the server + // available options: + // - timeout: timeout for the rpc call in ms + _save_to_server: function (orders, options) { + if (!orders || !orders.length) { + var result = $.Deferred(); + result.resolve(); + return result; } - rec_flush(0); + + options = options || {}; - return tried_all; + var self = this; + var timeout = typeof options.timeout === 'number' ? options.timeout : 7500; + + // we try to send the order. shadow prevents a spinner if it takes too long. (unless we are sending an invoice, + // then we want to notify the user that we are waiting on something ) + var posOrderModel = new instance.web.Model('pos.order'); + return posOrderModel.call('create_from_ui', + [_.map(orders, function (order) { + order.to_invoice = options.to_invoice || false; + return order; + })], + undefined, + { + shadow: !options.to_invoice, + timeout: timeout + } + ).then(function () { + _.each(orders, function (order) { + self.db.remove_order(order.id); + }); + }).fail(function (unused, event){ + // prevent an error popup creation by the rpc failure + // we want the failure to be silent as we send the orders in the background + event.preventDefault(); + console.error('Failed to send orders:', orders); + }); }, scan_product: function(parsed_code){ From 95f2c5537d2e5ad95969cf7c12f799aeeccf5229 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20van=20der=20Essen?= Date: Mon, 24 Feb 2014 15:41:43 +0100 Subject: [PATCH 4/6] [IMP] point_of_sale: make the order flush timeout dependant on the number of orders to be flushed bzr revid: fva@openerp.com-20140224144143-4fhlswjk2y4skd6j --- addons/point_of_sale/static/src/js/models.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/point_of_sale/static/src/js/models.js b/addons/point_of_sale/static/src/js/models.js index 90aa5fece05..6c65b0bcfbf 100644 --- a/addons/point_of_sale/static/src/js/models.js +++ b/addons/point_of_sale/static/src/js/models.js @@ -457,7 +457,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal options = options || {}; var self = this; - var timeout = typeof options.timeout === 'number' ? options.timeout : 7500; + var timeout = typeof options.timeout === 'number' ? options.timeout : 7500 * orders.length; // we try to send the order. shadow prevents a spinner if it takes too long. (unless we are sending an invoice, // then we want to notify the user that we are waiting on something ) From 8d5398d6617d2e7125dcc9996b14d519847a201e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20van=20der=20Essen?= Date: Mon, 24 Feb 2014 16:08:30 +0100 Subject: [PATCH 5/6] [FIX] point_of_sale: if an order fails to mark as PAID, leave it at NEW so that the pos client can process new orders. The error has to be manually corrected by the user before session closing bzr revid: fva@openerp.com-20140224150830-5uddv7i1djff2aip --- addons/point_of_sale/point_of_sale.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/addons/point_of_sale/point_of_sale.py b/addons/point_of_sale/point_of_sale.py index 01e7a49ec8c..912a1402da5 100644 --- a/addons/point_of_sale/point_of_sale.py +++ b/addons/point_of_sale/point_of_sale.py @@ -561,7 +561,11 @@ class pos_order(osv.osv): 'journal': cash_journal.id, }, context=context) order_ids.append(order_id) - self.signal_paid(cr, uid, [order_id]) + + try: + self.signal_paid(cr, uid, [order_id]) + except Exception as e: + _logger.error('ERROR: Could not mark POS Order as Paid.\n'+str(e)) if to_invoice: self.action_invoice(cr, uid, [order_id], context) From 3d4f063e519d5c7255aec6f268df89f21bcd62a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20van=20der=20Essen?= Date: Mon, 24 Feb 2014 17:01:18 +0100 Subject: [PATCH 6/6] [FIX] point_of_sale: accept ean13 barcodes with a stripped leading zero bzr revid: fva@openerp.com-20140224160118-44bnm5kxs53n6siy --- addons/point_of_sale/static/src/js/devices.js | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/addons/point_of_sale/static/src/js/devices.js b/addons/point_of_sale/static/src/js/devices.js index 683cfaeb08b..5db91924bce 100644 --- a/addons/point_of_sale/static/src/js/devices.js +++ b/addons/point_of_sale/static/src/js/devices.js @@ -672,7 +672,7 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal // returns true if the ean is a valid EAN codebar number by checking the control digit. // ean must be a string check_ean: function(ean){ - return this.ean_checksum(ean) === Number(ean[ean.length-1]); + 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){ @@ -757,8 +757,13 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal scan: function(code){ if(code.length < 3){ return; - }else if(code.length === 13 && /^\d+$/.test(code)){ + }else if(code.length === 13 && this.check_ean(code)){ var parse_result = this.parse_ean(code); + }else if(code.length === 12 && this.check_ean('0'+code)){ + // many barcode scanners strip the leading zero of ean13 barcodes. + // This is because ean-13 are UCP-A with an additional zero at the beginning, + // so by stripping zeros you get retrocompatibility with UCP-A systems. + var parse_result = this.parse_ean('0'+code); }else if(this.pos.db.get_product_by_reference(code)){ var parse_result = { encoding: 'reference', @@ -767,12 +772,16 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal prefix: '', }; }else{ + var parse_result = { + encoding: 'error', + type: 'error', + code: code, + prefix: '', + }; return; } - if (parse_result.type === 'error') { //most likely a checksum error, raise warning - console.warn('WARNING: barcode checksum error:',parse_result); - }else if(parse_result.type in {'unit':'', 'weight':'', 'price':''}){ //ean is associated to a product + if(parse_result.type in {'unit':'', 'weight':'', 'price':''}){ //ean is associated to a product if(this.action_callback['product']){ this.action_callback['product'](parse_result); }