From b38c27103158a751e408994c6d0e0cc03df1bf7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20van=20der=20Essen?= Date: Wed, 11 Sep 2013 17:41:55 +0200 Subject: [PATCH] [IMP] point_of_sale: big reworking of the scale screens: - no welcome scale in cashier mode, as it was an unnecessary delay for people who use it all day - no more parallel calls to the proxy which could reorder or make the driver crash - auto throttling of the proxy calls -> max 20 reads per seconds, but can go slower as well. - faster and cleaner update of the weight in the scale screen bzr revid: fva@openerp.com-20130911154155-rs600cixvftvkhoi --- addons/point_of_sale/controllers/main.py | 39 ++++- addons/point_of_sale/static/src/js/devices.js | 138 +++++++++++++----- addons/point_of_sale/static/src/js/models.js | 1 + addons/point_of_sale/static/src/js/screens.js | 103 ++++++------- 4 files changed, 179 insertions(+), 102 deletions(-) diff --git a/addons/point_of_sale/controllers/main.py b/addons/point_of_sale/controllers/main.py index db5d5a25018..e0bd19545a6 100644 --- a/addons/point_of_sale/controllers/main.py +++ b/addons/point_of_sale/controllers/main.py @@ -3,12 +3,17 @@ import logging import simplejson import os import openerp +import time +import random from openerp.addons.web import http from openerp.addons.web.http import request from openerp.addons.web.controllers.main import manifest_list, module_boot, html_template class PointOfSaleController(http.Controller): + def __init__(self): + self.scale = 'closed' + self.scale_weight = 0.0 @http.route('/pos/app', type='http', auth='admin') def app(self): @@ -64,6 +69,10 @@ class PointOfSaleController(http.Controller): return m + @http.route('/pos/test_connection', type='json', auth='admin') + def test_connection(self): + return + @http.route('/pos/scan_item_success', type='json', auth='admin') def scan_item_success(self, ean): """ @@ -98,18 +107,38 @@ class PointOfSaleController(http.Controller): @http.route('/pos/weighting_start', type='json', auth='admin') def weighting_start(self): - print "weighting_start" + if self.scale == 'closed': + print "Opening (Fake) Connection to Scale..." + self.scale = 'open' + self.scale_weight = 0.0 + time.sleep(0.1) + print "... Scale Open." + else: + print "WARNING: Scale already Connected !!!" return @http.route('/pos/weighting_read_kg', type='json', auth='admin') def weighting_read_kg(self): - print "weighting_read_kg" - return 3.14 + if self.scale == 'open': + print "Reading Scale..." + time.sleep(0.025) + self.scale_weight += 0.01 + print "... Done." + return self.scale_weight + else: + print "WARNING: Reading closed scale !!!" + return 0.0 @http.route('/pos/weighting_end', type='json', auth='admin') def weighting_end(self): - print "weighting_end" - return + if self.scale == 'open': + print "Closing Connection to Scale ..." + self.scale = 'closed' + self.scale_weight = 0.0 + time.sleep(0.1) + print "... Scale Closed." + else: + print "WARNING: Scale already Closed !!!" @http.route('/pos/payment_request', type='json', auth='admin') def payment_request(self, price): diff --git a/addons/point_of_sale/static/src/js/devices.js b/addons/point_of_sale/static/src/js/devices.js index 7e847abdc32..2be9cb74da2 100644 --- a/addons/point_of_sale/static/src/js/devices.js +++ b/addons/point_of_sale/static/src/js/devices.js @@ -1,6 +1,81 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sale + // the JobQueue schedules a sequence of 'jobs'. each job is + // a function returning a deferred. the queue waits for each job to finish + // before launching the next. Each job can also be scheduled with a delay. + // the is used to prevent parallel requests to the proxy. + + module.JobQueue = function(){ + var queue = []; + var running = false; + var scheduled_end_time = 0; + var end_of_queue = (new $.Deferred()).resolve(); + var stoprepeat = false; + + var run = function(){ + if(end_of_queue.state() === 'resolved'){ + end_of_queue = new $.Deferred(); + } + if(queue.length > 0){ + running = true; + var job = queue[0]; + if(!job.opts.repeat || stoprepeat){ + queue.shift(); + stoprepeat = false; + } + + // the time scheduled for this job + scheduled_end_time = (new Date()).getTime() + (job.opts.duration || 0); + + // we run the job and put in def when it finishes + var def = job.fun() || (new $.Deferred()).resolve(); + + // we don't care if a job fails ... + def.always(function(){ + // we run the next job after the scheduled_end_time, even if it finishes before + setTimeout(function(){ + run(); + }, Math.max(0, scheduled_end_time - (new Date()).getTime()) ); + }); + }else{ + running = false; + end_of_queue.resolve(); + } + }; + + // adds a job to the schedule. + // opts : { + // duration : the job is guaranteed to finish no quicker than this (milisec) + // repeat : if true, the job will be endlessly repeated + // important : if true, the scheduled job cannot be canceled by a queue.clear() + // } + this.schedule = function(fun, opts){ + queue.push({fun:fun, opts:opts || {}}); + if(!running){ + run(); + } + } + + // remove all jobs from the schedule (except the ones marked as important) + this.clear = function(){ + queue = _.filter(queue,function(job){job.opts.important === true}); + }; + + // end the repetition of the current job + this.stoprepeat = function(){ + stoprepeat = true; + }; + + // returns a deferred that resolves when all scheduled + // jobs have been run. + // ( jobs added after the call to this method are considered as well ) + this.finished = function(){ + return end_of_queue; + } + + }; + // this object interfaces with the local proxy to communicate to the various hardware devices // connected to the Point of Sale. As the communication only goes from the POS to the proxy, // methods are used both to signal an event, and to fetch information. @@ -10,7 +85,6 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal options = options || {}; url = options.url || 'http://localhost:8069'; - this.weight = 0; this.weighting = false; this.debug_weight = 0; this.use_debug_weight = false; @@ -35,18 +109,11 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal this.connection.destroy(); }, message : function(name,params){ - var ret = new $.Deferred(); var callbacks = this.notifications[name] || []; for(var i = 0; i < callbacks.length; i++){ callbacks[i](params); } - - this.connection.rpc('/pos/' + name, params || {}).done(function(result) { - ret.resolve(result); - }).fail(function(error) { - ret.reject(error); - }); - return ret; + return this.connection.rpc('/pos/' + name, params || {}); }, // this allows the client to be notified when a proxy call is made. The notification @@ -82,14 +149,32 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal //the client is starting to weight weighting_start: function(){ + var ret = new $.Deferred(); if(!this.weighting){ this.weighting = true; - this.weight = 0; - return this.message('weighting_start'); + this.message('weighting_start').always(function(){ + ret.resolve(); + }); }else{ console.error('Weighting already started!!!'); - this.weight = 0; + ret.resolve(); } + return ret; + }, + + // the client has finished weighting products + weighting_end: function(){ + var ret = new $.Deferred(); + if(this.weighting){ + this.weighting = false; + this.message('weighting_end').always(function(){ + ret.resolve(); + }); + }else{ + console.error('Weighting already ended !!!'); + ret.resolve(); + } + return ret; }, //returns the weight on the scale. @@ -97,20 +182,14 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal // and a weighting_end() weighting_read_kg: function(){ var self = this; - if(!this.weighting){ - console.error('Weighting while not started!!!'); - } + var ret = new $.Deferred(); this.message('weighting_read_kg',{}) - .done(function(weight){ - if(self.weighting){ - if(self.use_debug_weight){ - self.weight = self.debug_weight; - }else{ - self.weight = weight; - } - } + .then(function(weight){ + ret.resolve(self.use_debug_weight ? self.debug_weight : weight); + }, function(){ //failed to read weight + ret.resolve(self.use_debug_weight ? self.debug_weight : 0.0); }); - return this.weight; + return ret; }, // sets a custom weight, ignoring the proxy returned value. @@ -125,17 +204,6 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal this.debug_weight = 0; }, - // the client has finished weighting products - weighting_end: function(){ - if(this.weighting){ - this.weight = 0; - this.weighting = false; - this.message('weighting_end'); - }else{ - console.error('Weighting already ended !!!'); - this.weight = 0; - } - }, // the pos asks the client to pay 'price' units payment_request: function(price){ diff --git a/addons/point_of_sale/static/src/js/models.js b/addons/point_of_sale/static/src/js/models.js index cfaecf80a70..fbbb15af9c7 100644 --- a/addons/point_of_sale/static/src/js/models.js +++ b/addons/point_of_sale/static/src/js/models.js @@ -24,6 +24,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal this.barcode_reader = new module.BarcodeReader({'pos': this}); // used to read barcodes this.proxy = new module.ProxyDevice(); // used to communicate to the hardware devices via a local proxy + this.proxy_queue = new module.JobQueue(); // used to prevent parallels communications to the proxy this.db = new module.PosLS(); // a database used to store the products and categories this.db.clear('products','categories'); this.debug = jQuery.deparam(jQuery.param.querystring()).debug !== undefined; //debug mode diff --git a/addons/point_of_sale/static/src/js/screens.js b/addons/point_of_sale/static/src/js/screens.js index f8bf0df1590..854c1e1adec 100644 --- a/addons/point_of_sale/static/src/js/screens.js +++ b/addons/point_of_sale/static/src/js/screens.js @@ -441,7 +441,7 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa module.ErrorInvoiceTransferPopupWidget = module.ErrorPopupWidget.extend({ template: 'ErrorInvoiceTransferPopupWidget', }); - + module.ScaleInviteScreenWidget = module.ScreenWidget.extend({ template:'ScaleInviteScreenWidget', @@ -451,30 +451,35 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa show: function(){ this._super(); var self = this; + var queue = this.pos.proxy_queue; - self.pos.proxy.weighting_start(); - - this.intervalID = setInterval(function(){ - var weight = self.pos.proxy.weighting_read_kg(); - if(weight > 0.001){ - clearInterval(this.intervalID); - self.pos_widget.screen_selector.set_current_screen(self.next_screen); - } - },50); + queue.schedule(function(){ + return self.pos.proxy.weighting_start(); + },{ unclearable: true }); + + queue.schedule(function(){ + return self.pos.proxy.weighting_read_kg().then(function(weight){ + if(weight > 0.001){ + self.pos_widget.screen_selector.set_current_screen(self.next_screen); + } + }); + },{duration: 100, repeat: true}); this.add_action_button({ label: _t('Back'), icon: '/point_of_sale/static/src/img/icons/png48/go-previous.png', click: function(){ - clearInterval(this.intervalID); self.pos_widget.screen_selector.set_current_screen(self.previous_screen); } }); }, close: function(){ this._super(); - clearInterval(this.intervalID); - this.pos.proxy.weighting_end(); + var self = this; + this.pos.proxy_queue.clear(); + this.pos.proxy_queue.schedule(function(){ + return self.pos.proxy.weighting_end(); + },{ unclearable: true }); }, }); @@ -486,9 +491,11 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa show: function(){ this._super(); - this.renderElement(); var self = this; + var queue = this.pos.proxy_queue; + this.set_weight(0); + this.renderElement(); this.add_action_button({ label: _t('Back'), @@ -507,14 +514,16 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa }, }); - this.pos.proxy.weighting_start(); - this.intervalID = setInterval(function(){ - var weight = self.pos.proxy.weighting_read_kg(); - if(weight != self.weight){ - self.weight = weight; - self.$('.js-weight').text(self.get_product_weight_string()); - } - },50); + queue.schedule(function(){ + return self.pos.proxy.weighting_start() + },{ unclearable: true }); + + queue.schedule(function(){ + return self.pos.proxy.weighting_read_kg().then(function(weight){ + self.set_weight(weight); + }); + },{duration:50, repeat: true}); + }, renderElement: function(){ var self = this; @@ -544,54 +553,24 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa var product = this.get_product(); return (product ? product.get('price') : 0) || 0; }, + set_weight: function(weight){ + this.weight = weight; + this.$('.js-weight').text(this.get_product_weight_string()); + }, get_product_weight_string: function(){ return (this.weight || 0).toFixed(3) + ' Kg'; }, close: function(){ + var self = this; this._super(); - clearInterval(this.intervalID); - this.pos.proxy.weighting_end(); + + this.pos.proxy_queue.clear(); + this.pos.proxy_queue.schedule(function(){ + self.pos.proxy.weighting_end(); + },{ unclearable: true }); }, }); - // the JobQueue schedules a sequence of 'jobs'. each job is - // a function returning a deferred. the queue waits for each job to finish - // before launching the next. Each job can also be scheduled with a delay. - // the queue jobqueue is used to prevent parallel requests to the payment terminal. - - module.JobQueue = function(){ - var queue = []; - var running = false; - var run = function(){ - if(queue.length > 0){ - running = true; - var job = queue.shift(); - setTimeout(function(){ - var def = job.fun(); - if(def){ - def.done(run); - }else{ - run(); - } - },job.delay || 0); - }else{ - running = false; - } - }; - - // adds a job to the schedule. - this.schedule = function(fun, delay){ - queue.push({fun:fun, delay:delay}); - if(!running){ - run(); - } - } - - // remove all jobs from the schedule - this.clear = function(){ - queue = []; - }; - }; module.ClientPaymentScreenWidget = module.ScreenWidget.extend({ template:'ClientPaymentScreenWidget',