diff --git a/addons/point_of_sale/controllers/main.py b/addons/point_of_sale/controllers/main.py index e6976dd319c..3d80fd53e24 100644 --- a/addons/point_of_sale/controllers/main.py +++ b/addons/point_of_sale/controllers/main.py @@ -119,21 +119,21 @@ class PointOfSaleController(openerpweb.Controller): return @openerpweb.jsonrequest - def payment_request(self, request, price, method, info): + def payment_request(self, request, price): """ The PoS will activate the method payment """ - print "payment_request: price:"+str(price)+" method:"+str(method)+" info:"+str(info) - return + print "payment_request: price:"+str(price) + return 'ok' @openerpweb.jsonrequest - def is_payment_accepted(self, request): - print "is_payment_accepted" - return 'waiting_for_payment' + def payment_status(self, request): + print "payment_status" + return { 'status':'waiting' } @openerpweb.jsonrequest - def payment_canceled(self, request): - print "payment_canceled" + def payment_cancel(self, request): + print "payment_cancel" return @openerpweb.jsonrequest diff --git a/addons/point_of_sale/static/src/js/devices.js b/addons/point_of_sale/static/src/js/devices.js index a26d8ca7154..142d35bd340 100644 --- a/addons/point_of_sale/static/src/js/devices.js +++ b/addons/point_of_sale/static/src/js/devices.js @@ -14,7 +14,14 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal this.weighting = false; this.paying = false; - this.payment_status = 'waiting_for_payment'; + this.default_payment_status = { + status: 'waiting', + message: '', + payment_method: undefined, + receipt_client: undefined, + receipt_shop: undefined, + }; + this.custom_payment_status = this.default_payment_status; this.connection = new instance.web.JsonRPC(); this.connection.setup(url); @@ -23,16 +30,21 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal this.notifications = {}; }, - message : function(name,params,success_callback, error_callback){ - success_callback = success_callback || function(){}; - error_callback = error_callback || function(){}; - + 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 || {}, success_callback, error_callback); + this.connection.rpc('/pos/'+name, params || {}, + function(result){ + ret.resolve(result); + }, + function(error){ + ret.reject(error); + }); + return ret; }, // this allows the client to be notified when a proxy call is made. The notification @@ -47,23 +59,23 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal //a product has been scanned and recognized with success // ean is a parsed ean object scan_item_success: function(ean){ - this.message('scan_item_success',{ean: ean}); + return this.message('scan_item_success',{ean: ean}); }, // a product has been scanned but not recognized // ean is a parsed ean object scan_item_error_unrecognized: function(ean){ - this.message('scan_item_error_unrecognized',{ean: ean}); + return this.message('scan_item_error_unrecognized',{ean: ean}); }, //the client is asking for help help_needed: function(){ - this.message('help_needed'); + return this.message('help_needed'); }, //the client does not need help anymore help_canceled: function(){ - this.message('help_canceled'); + return this.message('help_canceled'); }, //the client is starting to weight @@ -72,7 +84,7 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal this.weight = 0; this.weighting = true; this.bypass_proxy = false; - this.message('weighting_start'); + return this.message('weighting_start'); } }, @@ -84,11 +96,12 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal if(this.bypass_proxy){ return this.weight; }else{ - this.message('weighting_read_kg',{},function(weight){ - if(self.weighting && !self.bypass_proxy){ - self.weight = weight; - } - }); + this.message('weighting_read_kg',{}) + .then(function(weight){ + if(self.weighting && !self.bypass_proxy){ + self.weight = weight; + } + }); return this.weight; } }, @@ -104,77 +117,76 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal this.weight = 0; this.weighting = false; this.bypass_proxy = false; - this.message('weighting_end'); + return this.message('weighting_end'); }, // the pos asks the client to pay 'price' units - // method: 'mastercard' | 'cash' | ... ? TBD - // info: 'extra information to display on the payment terminal' ... ? TBD - payment_request: function(price, method, info){ + payment_request: function(price){ + var ret = new $.Deferred(); this.paying = true; - this.payment_status = 'waiting_for_payment'; - this.message('payment_request',{'price':price,'method':method,'info':info}); + this.custom_payment_status = this.default_payment_status; + return this.message('payment_request',{'price':price}); }, - // is called at regular interval after a payment request to see if the client - // has paid the required money - // returns 'waiting_for_payment' | 'payment_accepted' | 'payment_rejected' - is_payment_accepted: function(){ - var self = this; + payment_status: function(){ if(this.bypass_proxy){ this.bypass_proxy = false; - return this.payment_status; + return (new $.Deferred()).resolve(this.custom_payment_status); }else{ - this.message('is_payment_accepted', {}, function(payment_status){ - if(self.paying){ - self.payment_status = payment_status; - } - }); - return this.payment_status; + return this.message('payment_status'); } }, // override what the proxy says and accept the payment debug_accept_payment: function(){ this.bypass_proxy = true; - this.payment_status = 'payment_accepted'; + this.custom_payment_status = { + status: 'paid', + message: 'Successfull Payment, have a nice day', + payment_method: 'AMEX', + receipt_client: 'bla', + receipt_shop: 'bla', + }; }, // override what the proxy says and reject the payment debug_reject_payment: function(){ this.bypass_proxy = true; - this.payment_status = 'payment_rejected'; + this.custom_payment_status = { + status: 'error-rejected', + message: 'Sorry you don\'t have enough money :(', + }; }, // the client cancels his payment - payment_canceled: function(){ + payment_cancel: function(){ this.paying = false; - this.payment_status = 'waiting_for_payment'; - this.message('payment_canceled'); + this.custom_payment_status = 'waiting_for_payment'; + return this.message('payment_cancel'); }, // called when the client logs in or starts to scan product transaction_start: function(){ - this.message('transaction_start'); + return this.message('transaction_start'); }, // called when the clients has finished his interaction with the machine transaction_end: function(){ - this.message('transaction_end'); + return this.message('transaction_end'); }, // called when the POS turns to cashier mode cashier_mode_activated: function(){ - this.message('cashier_mode_activated'); + return this.message('cashier_mode_activated'); }, // called when the POS turns to client mode cashier_mode_deactivated: function(){ - this.message('cashier_mode_deactivated'); + return this.message('cashier_mode_deactivated'); }, // ask for the cashbox (the physical box where you store the cash) to be opened open_cashbox: function(){ - this.message('open_cashbox'); + return this.message('open_cashbox'); }, /* ask the printer to print a receipt @@ -216,12 +228,12 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal * } */ print_receipt: function(receipt){ - this.message('print_receipt',{receipt: receipt}); + return this.message('print_receipt',{receipt: receipt}); }, // asks the proxy to print an invoice in pdf form ( used to print invoices generated by the server ) print_pdf_invoice: function(pdfinvoice){ - this.message('print_pdf_invoice',{pdfinvoice: pdfinvoice}); + return this.message('print_pdf_invoice',{pdfinvoice: pdfinvoice}); }, }); diff --git a/addons/point_of_sale/static/src/js/screens.js b/addons/point_of_sale/static/src/js/screens.js index d2c79f19928..1ad03c5ead1 100644 --- a/addons/point_of_sale/static/src/js/screens.js +++ b/addons/point_of_sale/static/src/js/screens.js @@ -154,7 +154,7 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa }, barcode_product_screen: 'products', //if defined, this screen will be loaded when a product is scanned - barcode_product_error_popup: 'error', //if defined, this popup will be loaded when there's an error in the popup + barcode_product_error_popup: 'error-product', //if defined, this popup will be loaded when there's an error in the popup // what happens when a product is scanned : // it will add the product to the order and go to barcode_product_screen. Or show barcode_product_error_popup if @@ -411,6 +411,9 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa self.pos_widget.screen_selector.set_user_mode('cashier'); }, }); + this.$('.footer .button').off('click').click(function(){ + self.pos_widget.screen_selector.close_popup(); + }); }, close:function(){ this._super(); @@ -419,12 +422,12 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa }, }); - module.ErrorProductNotRecognizedPopupWidget = module.ErrorPopupWidget.extend({ - template:'ErrorProductNotRecognizedPopupWidget', + module.ProductErrorPopupWidget = module.ErrorPopupWidget.extend({ + template:'ProductErrorPopupWidget', }); - module.ErrorNoSessionPopupWidget = module.ErrorPopupWidget.extend({ - template:'ErrorNoSessionPopupWidget', + module.ErrorSessionPopupWidget = module.ErrorPopupWidget.extend({ + template:'ErrorSessionPopupWidget', }); module.ScaleInviteScreenWidget = module.ScreenWidget.extend({ @@ -539,6 +542,60 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa }, }); + // 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.then(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.BasicPaymentScreen = module.ScreenWidget.extend({ + queue: new JobQueue(), + start_payment_transaction: function(){ + }, + update_payment_transaction: function(){ + }, + cancel_payment_transaction: function(){ + }, + show: function(){ + this._super(); + }, + }); + */ + module.ClientPaymentScreenWidget = module.ScreenWidget.extend({ template:'ClientPaymentScreenWidget', @@ -548,50 +605,102 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa show: function(){ this._super(); var self = this; + + this.queue = new module.JobQueue(); + this.canceled = false; + this.paid = false; - this.pos.proxy.payment_request(this.pos.get('selectedOrder').getDueLeft(),'card','info'); //TODO TOTAL - - this.intervalID = setInterval(function(){ - var payment = self.pos.proxy.is_payment_accepted(); - if(payment === 'payment_accepted'){ - clearInterval(this.intervalID); - - var currentOrder = self.pos.get('selectedOrder'); - - //we get the first cashregister marked as self-checkout - var selfCheckoutRegisters = []; - for(var i = 0; i < self.pos.get('cashRegisters').models.length; i++){ - var cashregister = self.pos.get('cashRegisters').models[i]; - if(cashregister.self_checkout_payment_method){ - selfCheckoutRegisters.push(cashregister); + // initiates the connection to the payment terminal and starts the update requests + this.start = function(){ + var def = new $.Deferred(); + console.log("START"); + self.pos.proxy.payment_request(self.pos.get('selectedOrder').getDueLeft()) + .then(function(ack){ + if(ack === 'ok'){ + self.queue.schedule(self.update); + }else if(ack.indexOf('error') === 0){ + console.error('cannot make payment. TODO'); + }else{ + console.error('unknown payment request return value:',ack); } - } - - var cashregister = selfCheckoutRegisters[0] || self.pos.get('cashRegisters').models[0]; - currentOrder.addPaymentLine(cashregister); - self.pos.push_order(currentOrder.exportAsJSON()) - currentOrder.destroy(); - self.pos.proxy.transaction_end(); - self.pos_widget.screen_selector.set_current_screen(self.next_screen); - }else if(payment === 'payment_rejected'){ - clearInterval(self.intervalID); - //TODO show a tryagain thingie ? + console.log("START_END"); + def.resolve(); + }); + return def; + }; + + // gets updated status from the payment terminal and performs the appropriate consequences + this.update = function(){ + console.log("UPDATE"); + var def = new $.Deferred(); + if(self.canceled){ + console.log("UPDATE_END"); + return def.resolve(); } - },500); + self.pos.proxy.payment_status() + .then(function(status){ + if(status.status === 'paid'){ + + var currentOrder = self.pos.get('selectedOrder'); + + //we get the first cashregister marked as self-checkout + var selfCheckoutRegisters = []; + for(var i = 0; i < self.pos.get('cashRegisters').models.length; i++){ + var cashregister = self.pos.get('cashRegisters').models[i]; + if(cashregister.self_checkout_payment_method){ + selfCheckoutRegisters.push(cashregister); + } + } + + var cashregister = selfCheckoutRegisters[0] || self.pos.get('cashRegisters').models[0]; + currentOrder.addPaymentLine(cashregister); + self.pos.push_order(currentOrder.exportAsJSON()) + currentOrder.destroy(); + self.pos.proxy.transaction_end(); + self.pos_widget.screen_selector.set_current_screen(self.next_screen); + self.paid = true; + }else if(status.status.indexOf('error') === 0){ + console.error('error in payment request. TODO'); + }else if(status.status === 'waiting'){ + self.queue.schedule(self.update,200); + }else{ + console.error('unknown status value:',status.status); + } + console.log("UPDATE_END"); + def.resolve(); + }); + return def; + } + + // cancels a payment. + this.cancel = function(){ + console.log("CANCEL"); + if(!self.paid && !self.canceled){ + self.canceled = true; + self.pos.proxy.payment_cancel(); + self.pos_widget.screen_selector.set_current_screen(self.previous_screen); + self.queue.clear(); + } + console.log("CANCEL_END"); + return (new $.Deferred()).resolve(); + } + + this.queue.schedule(this.start); this.add_action_button({ label: 'back', icon: '/point_of_sale/static/src/img/icons/png48/go-previous.png', click: function(){ - clearInterval(this.intervalID); - self.pos.proxy.payment_canceled(); - self.pos_widget.screen_selector.set_current_screen(self.previous_screen); + self.queue.schedule(self.cancel); } }); }, close: function(){ + if(this.queue){ + this.queue.schedule(this.cancel); + } + //TODO CANCEL this._super(); - clearInterval(this.intervalID); }, }); diff --git a/addons/point_of_sale/static/src/js/widgets.js b/addons/point_of_sale/static/src/js/widgets.js index f4d16b527c8..47a527a010d 100644 --- a/addons/point_of_sale/static/src/js/widgets.js +++ b/addons/point_of_sale/static/src/js/widgets.js @@ -687,7 +687,7 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa 'print_receipt', 'print_pdf_invoice', 'weighting_read_kg', - 'is_payment_accepted', + 'payment_status', ], minimized: false, start: function(){ @@ -898,10 +898,10 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa this.error_popup = new module.ErrorPopupWidget(this, {}); this.error_popup.appendTo($('.point-of-sale')); - this.error_product_popup = new module.ErrorProductNotRecognizedPopupWidget(this, {}); + this.error_product_popup = new module.ProductErrorPopupWidget(this, {}); this.error_product_popup.appendTo($('.point-of-sale')); - this.error_session_popup = new module.ErrorNoSessionPopupWidget(this, {}); + this.error_session_popup = new module.ErrorSessionPopupWidget(this, {}); this.error_session_popup.appendTo($('.point-of-sale')); this.choose_receipt_popup = new module.ChooseReceiptPopupWidget(this, {}); diff --git a/addons/point_of_sale/static/src/xml/pos.xml b/addons/point_of_sale/static/src/xml/pos.xml index 7af3c2c2255..53177b5ac4e 100644 --- a/addons/point_of_sale/static/src/xml/pos.xml +++ b/addons/point_of_sale/static/src/xml/pos.xml @@ -327,15 +327,20 @@ - + + - +