From 1f39a7d640448acfb817fd83d9f68d80ecda68f1 Mon Sep 17 00:00:00 2001 From: Frederic van der Essen Date: Thu, 7 Aug 2014 17:28:46 +0200 Subject: [PATCH] [IMP] point_of_sale: new payment screen is now operational --- addons/point_of_sale/static/src/css/pos.css | 18 +- addons/point_of_sale/static/src/js/models.js | 8 + addons/point_of_sale/static/src/js/screens.js | 468 ++++++------------ addons/point_of_sale/static/src/js/widgets.js | 2 +- addons/point_of_sale/static/src/xml/pos.xml | 91 ++-- 5 files changed, 211 insertions(+), 376 deletions(-) diff --git a/addons/point_of_sale/static/src/css/pos.css b/addons/point_of_sale/static/src/css/pos.css index 15cf3112c3c..e828736e4d6 100644 --- a/addons/point_of_sale/static/src/css/pos.css +++ b/addons/point_of_sale/static/src/css/pos.css @@ -996,7 +996,7 @@ td { .pos .payment-numpad { display: inline-block; - width: 230px; + width: 232px; margin-top: 8px; margin-right: 8px; float: right; @@ -1024,7 +1024,7 @@ td { border-radius: 3px; } .pos .paymentline{ - font-size: 18px; + font-size: 22px; } .pos .paymentline.selected { font-size: 22px; @@ -1035,13 +1035,25 @@ td { background: white; color: #6EC89B; outline: 3px blue; - border: solid 4px #6EC89B; + box-shadow: 0px 0px 0px 3px #6EC89B; + position: relative; border-radius: 3px; } .pos .paymentline > *{ padding: 8px; } +.pos .paymentline .col-due, +.pos .paymentline .col-tendered, +.pos .paymentline .col-change { + min-width: 80px; +} +.pos .paymentline .col-change.highlight { + background: rgb(184, 152, 204); +} +.pos .paymentline .col-name { + font-size: 16px; +} .pos .paymentline .delete-button{ cursor: pointer; text-align: center; diff --git a/addons/point_of_sale/static/src/js/models.js b/addons/point_of_sale/static/src/js/models.js index 30699e7b237..02a84c61b51 100644 --- a/addons/point_of_sale/static/src/js/models.js +++ b/addons/point_of_sale/static/src/js/models.js @@ -1030,6 +1030,14 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal isPaid: function(){ return this.getDueLeft() === 0; }, + isPaidWithCash: function(){ + return !!this.get('paymentLines').find( function(pl){ + return pl.cashregister.journal.type === 'cash'; + }); + }, + finalize: function(){ + this.destroy(); + }, // sets the type of receipt 'receipt'(default) or 'invoice' set_receipt_type: function(type){ this.receipt_type = type; diff --git a/addons/point_of_sale/static/src/js/screens.js b/addons/point_of_sale/static/src/js/screens.js index 872a1fd1769..0e6d56881fb 100644 --- a/addons/point_of_sale/static/src/js/screens.js +++ b/addons/point_of_sale/static/src/js/screens.js @@ -783,9 +783,8 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa module.ReceiptScreenWidget = module.ScreenWidget.extend({ template: 'ReceiptScreenWidget', - - show_numpad: true, - show_leftpane: true, + show_numpad: false, + show_leftpane: false, show: function(){ this._super(); @@ -849,24 +848,102 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa }); - module.NewPaymentScreenWidget = module.ScreenWidget.extend({ - template: 'NewPaymentScreenWidget', + module.PaymentScreenWidget = module.ScreenWidget.extend({ + template: 'PaymentScreenWidget', back_screen: 'product', next_screen: 'receipt', show_leftpane: false, show_numpad: false, init: function(parent, options) { + var self = this; this._super(parent, options); + this.pos.bind('change:selectedOrder',function(){ - this.bind_events(); this.renderElement(); + this.watch_order_changes(); },this); + this.watch_order_changes(); + + this.inputbuffer = ""; + this.keyboard_handler = function(event){ + var key = ''; + if ( event.keyCode === 13 ) { // Enter + self.validate_order(); + } else if ( event.keyCode === 190 ) { // Dot + key = '.'; + } else if ( event.keyCode === 46 ) { // Delete + key = 'CLEAR'; + } else if ( event.keyCode === 8 ) { // Backspace + key = 'BACKSPACE'; + event.preventDefault(); // Prevents history back nav + } else if ( event.keyCode >= 48 && event.keyCode <= 57 ){ // Numbers + key = '' + (event.keyCode - 48); + } else if ( event.keyCode >= 96 && event.keyCode <= 105 ){ // Numpad Numbers + key = '' + (event.keyCode - 96); + } else if ( event.keyCode === 189 || event.keyCode === 109 ) { // Minus + key = '-'; + } else if ( event.keyCode === 107 ) { // Plus + key = '+'; + } + + self.payment_input(key); + + }; }, - click_numpad: function(button){ - console.log(button); + // resets the current input buffer + reset_input: function(){ + this.inputbuffer = ""; + }, + // handle both keyboard and numpad input. Accepts + // a string that represents the key pressed. + payment_input: function(input) { + var oldbuf = this.inputbuffer.slice(0); + + if (input === '.') { + if (!this.inputbuffer.length || this.inputbuffer === '-') { + this.inputbuffer += "0."; + } else if (this.inputbuffer.indexOf('.') < 0){ + this.inputbuffer = this.inputbuffer + '.'; + } + } else if (input === 'CLEAR') { + this.inputbuffer = ""; + } else if (input === 'BACKSPACE') { + this.inputbuffer = this.inputbuffer.substring(0,this.inputbuffer.length - 1); + } else if (input === '+') { + if ( this.inputbuffer[0] === '-' ) { + this.inputbuffer = this.inputbuffer.substring(1,this.inputbuffer.length); + } + } else if (input === '-') { + if ( this.inputbuffer[0] === '-' ) { + this.inputbuffer = this.inputbuffer.substring(1,this.inputbuffer.length); + } else { + this.inputbuffer = '-' + this.inputbuffer; + } + } else if (input[0] === '+' && !isNaN(parseFloat(input))) { + this.inputbuffer = '' + ((parseFloat(this.inputbuffer) || 0) + parseFloat(input)); + } else if (!isNaN(parseInt(input))) { + this.inputbuffer += input; + } + + if (this.inputbuffer !== oldbuf) { + var order = this.pos.get_order(); + if (order.selected_paymentline) { + order.selected_paymentline.set_amount(parseFloat(this.inputbuffer)); + this.order_changes(); + this.render_paymentlines(); + this.$('.paymentline.selected .edit').text(this.inputbuffer); + } + } + }, + click_numpad: function(button) { + this.payment_input(button.data('action')); }, render_numpad: function() { + var self = this; var numpad = $(QWeb.render('PaymentScreen-Numpad', { widget:this })); + numpad.on('click','button',function(){ + self.click_numpad($(this)); + }); return numpad; }, click_delete_paymentline: function(cid){ @@ -874,6 +951,18 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa for ( var i = 0; i < lines.length; i++ ) { if (lines[i].cid === cid) { this.pos.get_order().removePaymentline(lines[i]); + this.reset_input(); + this.render_paymentlines(); + return; + } + } + }, + click_paymentline: function(cid){ + var lines = this.pos.get_order().get('paymentLines').models; + for ( var i = 0; i < lines.length; i++ ) { + if (lines[i].cid === cid) { + this.pos.get_order().selectPaymentline(lines[i]); + this.reset_input(); this.render_paymentlines(); return; } @@ -894,10 +983,14 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa lines.on('click','.delete-button',function(){ self.click_delete_paymentline($(this).data('cid')); }); + + lines.on('click','.paymentline',function(){ + self.click_paymentline($(this).data('cid')); + }); lines.appendTo(this.$('.paymentlines-container')); }, - click_paymentmethods: function(id){ + click_paymentmethods: function(id) { var cashregister = null; for ( var i = 0; i < this.pos.cashregisters.length; i++ ) { if ( this.pos.cashregisters[i].journal_id[0] === id ){ @@ -906,6 +999,7 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa } } this.pos.get_order().addPaymentline( cashregister ); + this.reset_input(); this.render_paymentlines(); }, render_paymentmethods: function() { @@ -932,355 +1026,95 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa self.pos_widget.screen_selector.back(); }); + this.$('.next').click(function(){ + self.validate_order(); + }); + + this.$('.js_invoice').click(function(){ + self.validate_order({invoice: true}); + }); + }, show: function(){ this.pos.get_order().clean_empty_paymentlines(); + this.reset_input(); this.render_paymentlines(); + window.document.body.addEventListener('keydown',this.keyboard_handler); this._super(); }, - - }); - - module.PaymentScreenWidget = module.ScreenWidget.extend({ - template: 'PaymentScreenWidget', - back_screen: 'products', - next_screen: 'receipt', - init: function(parent, options) { + hide: function(){ + window.document.body.removeEventListener('keydown',this.keyboard_handler); + this._super(); + }, + // sets up listeners to watch for order changes + watch_order_changes: function() { var self = this; - this._super(parent,options); - - this.pos.bind('change:selectedOrder',function(){ - this.bind_events(); - this.renderElement(); - },this); - - this.bind_events(); - - this.line_delete_handler = function(event){ - var node = this; - while(node && !node.classList.contains('paymentline')){ - node = node.parentNode; - } - if(node){ - self.pos.get('selectedOrder').removePaymentline(node.line) - } - event.stopPropagation(); - }; - - this.line_change_handler = function(event){ - var node = this; - while(node && !node.classList.contains('paymentline')){ - node = node.parentNode; - } - if(node){ - node.line.set_amount(this.value); - } - }; - - this.line_click_handler = function(event){ - var node = this; - while(node && !node.classList.contains('paymentline')){ - node = node.parentNode; - } - if(node){ - self.pos.get('selectedOrder').selectPaymentline(node.line); - } - }; - - this.hotkey_handler = function(event){ - if(event.which === 13){ - self.validate_order(); - }else if(event.which === 27){ - self.back(); - } - }; - - }, - show: function(){ - this._super(); - var self = this; - - this.enable_numpad(); - this.focus_selected_line(); - - document.body.addEventListener('keyup', this.hotkey_handler); - - this.add_action_button({ - label: _t('Back'), - icon: '/point_of_sale/static/src/img/icons/png48/go-previous.png', - click: function(){ - self.back(); - }, - }); - - this.add_action_button({ - label: _t('Validate'), - name: 'validation', - icon: '/point_of_sale/static/src/img/icons/png48/validate.png', - click: function(){ - self.validate_order(); - }, - }); - - if( this.pos.config.iface_invoicing ){ - this.add_action_button({ - label: 'Invoice', - name: 'invoice', - icon: '/point_of_sale/static/src/img/icons/png48/invoice.png', - click: function(){ - self.validate_order({invoice: true}); - }, - }); - } - - if( this.pos.config.iface_cashdrawer ){ - this.add_action_button({ - label: _t('Cash'), - name: 'cashbox', - icon: '/point_of_sale/static/src/img/open-cashbox.png', - click: function(){ - self.pos.proxy.open_cashbox(); - }, - }); - } - - this.update_payment_summary(); - - }, - close: function(){ - this._super(); - this.disable_numpad(); - document.body.removeEventListener('keyup',this.hotkey_handler); - }, - remove_empty_lines: function(){ - var order = this.pos.get('selectedOrder'); - var lines = order.get('paymentLines').models.slice(0); - for(var i = 0; i < lines.length; i++){ - var line = lines[i]; - if(line.get_amount() === 0){ - order.removePaymentline(line); - } - } - }, - back: function() { - this.remove_empty_lines(); - this.pos_widget.screen_selector.set_current_screen(this.back_screen); - }, - bind_events: function() { + var order = this.pos.get_order(); if(this.old_order){ this.old_order.unbind(null,null,this); } - var order = this.pos.get('selectedOrder'); - order.bind('change:selected_paymentline',this.focus_selected_line,this); - + order.bind('all',function(){ + self.order_changes(); + }); this.old_order = order; - - if(this.old_paymentlines){ - this.old_paymentlines.unbind(null,null,this); - } - var paymentlines = order.get('paymentLines'); - paymentlines.bind('add', this.add_paymentline, this); - paymentlines.bind('change:selected', this.rerender_paymentline, this); - paymentlines.bind('change:amount', function(line){ - if(!line.selected && line.node){ - line.node.value = line.amount.toFixed(2); - } - this.update_payment_summary(); - },this); - paymentlines.bind('remove', this.remove_paymentline, this); - paymentlines.bind('all', this.update_payment_summary, this); - - this.old_paymentlines = paymentlines; - - if(this.old_orderlines){ - this.old_orderlines.unbind(null,null,this); - } - var orderlines = order.get('orderLines'); - orderlines.bind('all', this.update_payment_summary, this); - - this.old_orderlines = orderlines; }, - focus_selected_line: function(){ - var line = this.pos.get('selectedOrder').selected_paymentline; - if(line){ - var input = line.node.querySelector('input'); - if(!input){ - return; - } - var value = input.value; - input.focus(); - - if(this.numpad_state){ - this.numpad_state.reset(); - } - - if(Number(value) === 0){ - input.value = ''; - }else{ - input.value = value; - input.select(); - } + // called when the order is changed, used to show if + // the order is paid or not + order_changes: function(){ + var order = this.pos.get_order(); + console.log('change!'); + if (order.isPaid()) { + self.$('.next').addClass('highlight'); + }else{ + self.$('.next').removeClass('highlight'); } }, - add_paymentline: function(line) { - var list_container = this.el.querySelector('.payment-lines'); - list_container.appendChild(this.render_paymentline(line)); - - if(this.numpad_state){ - this.numpad_state.reset(); - } - }, - render_paymentline: function(line){ - var el_html = openerp.qweb.render('Paymentline',{widget: this, line: line}); - el_html = _.str.trim(el_html); - - var el_node = document.createElement('tbody'); - el_node.innerHTML = el_html; - el_node = el_node.childNodes[0]; - el_node.line = line; - el_node.querySelector('.paymentline-delete') - .addEventListener('click', this.line_delete_handler); - el_node.addEventListener('click', this.line_click_handler); - el_node.querySelector('input') - .addEventListener('keyup', this.line_change_handler); - - line.node = el_node; - - return el_node; - }, - rerender_paymentline: function(line){ - var old_node = line.node; - var new_node = this.render_paymentline(line); - - old_node.parentNode.replaceChild(new_node,old_node); - }, - remove_paymentline: function(line){ - line.node.parentNode.removeChild(line.node); - line.node = undefined; - }, - renderElement: function(){ - this._super(); - - var paymentlines = this.pos.get('selectedOrder').get('paymentLines').models; - var list_container = this.el.querySelector('.payment-lines'); - - for(var i = 0; i < paymentlines.length; i++){ - list_container.appendChild(this.render_paymentline(paymentlines[i])); - } - - this.update_payment_summary(); - }, - update_payment_summary: function() { - var currentOrder = this.pos.get('selectedOrder'); - var paidTotal = currentOrder.getPaidTotal(); - var dueTotal = currentOrder.getTotalTaxIncluded(); - var remaining = dueTotal > paidTotal ? dueTotal - paidTotal : 0; - var change = paidTotal > dueTotal ? paidTotal - dueTotal : 0; - - this.$('.payment-due-total').html(this.format_currency(dueTotal)); - this.$('.payment-paid-total').html(this.format_currency(paidTotal)); - this.$('.payment-remaining').html(this.format_currency(remaining)); - this.$('.payment-change').html(this.format_currency(change)); - if(currentOrder.selected_orderline === undefined){ - remaining = 1; // What is this ? - } - - if(this.pos_widget.action_bar){ - this.pos_widget.action_bar.set_button_disabled('validation', !this.is_paid()); - this.pos_widget.action_bar.set_button_disabled('invoice', !this.is_paid()); - } - }, - is_paid: function(){ - var currentOrder = this.pos.get('selectedOrder'); - return (currentOrder.getTotalTaxIncluded() < 0.000001 - || currentOrder.getPaidTotal() + 0.000001 >= currentOrder.getTotalTaxIncluded()); - - }, + // Check if the order is paid, then sends it to the backend, + // and complete the sale process validate_order: function(options) { var self = this; options = options || {}; - var currentOrder = this.pos.get('selectedOrder'); + var order = this.pos.get_order(); - if(!this.is_paid()){ + if (!order.isPaid() || this.invoicing) { return; } - if( this.pos.config.iface_cashdrawer - && this.pos.get('selectedOrder').get('paymentLines').find( function(pl){ - return pl.cashregister.journal.type === 'cash'; - })){ + if (order.isPaidWithCash() && this.pos.config.iface_cashdrawer) { this.pos.proxy.open_cashbox(); } - if(options.invoice){ - // deactivate the validation button while we try to send the order - this.pos_widget.action_bar.set_button_disabled('validation',true); - this.pos_widget.action_bar.set_button_disabled('invoice',true); - - var invoiced = this.pos.push_and_invoice_order(currentOrder); + if (options.invoice) { + var invoiced = this.pos.push_and_invoice_order(order); + this.invoicing = true; invoiced.fail(function(error){ - if(error === 'error-no-client'){ + self.invoicing = false; + if (error === 'error-no-client') { self.pos_widget.screen_selector.show_popup('error-no-client'); - }else{ + } else { self.pos_widget.screen_selector.show_popup('error-invoice-transfer'); } - self.pos_widget.action_bar.set_button_disabled('validation',false); - self.pos_widget.action_bar.set_button_disabled('invoice',false); }); invoiced.done(function(){ - self.pos_widget.action_bar.set_button_disabled('validation',false); - self.pos_widget.action_bar.set_button_disabled('invoice',false); - self.pos.get('selectedOrder').destroy(); + self.invoicing = false; + order.finalize(); }); - - }else{ - this.pos.push_order(currentOrder) - if(this.pos.config.iface_print_via_proxy){ + } else { + this.pos.push_order(order) + if (this.pos.config.iface_print_via_proxy) { var receipt = currentOrder.export_for_printing(); this.pos.proxy.print_receipt(QWeb.render('XmlReceipt',{ receipt: receipt, widget: self, })); - this.pos.get('selectedOrder').destroy(); //finish order and go back to scan screen - }else{ + order.finalize(); //finish order and go back to scan screen + } else { this.pos_widget.screen_selector.set_current_screen(this.next_screen); } } - - // hide onscreen (iOS) keyboard - setTimeout(function(){ - document.activeElement.blur(); - $("input").blur(); - },250); - }, - enable_numpad: function(){ - this.disable_numpad(); //ensure we don't register the callbacks twice - this.numpad_state = this.pos_widget.numpad.state; - if(this.numpad_state){ - this.numpad_state.reset(); - this.numpad_state.changeMode('payment'); - this.numpad_state.bind('set_value', this.set_value, this); - this.numpad_state.bind('change:mode', this.set_mode_back_to_payment, this); - } - - }, - disable_numpad: function(){ - if(this.numpad_state){ - this.numpad_state.unbind('set_value', this.set_value); - this.numpad_state.unbind('change:mode',this.set_mode_back_to_payment); - } - }, - set_mode_back_to_payment: function() { - this.numpad_state.set({mode: 'payment'}); - }, - set_value: function(val) { - var selected_line =this.pos.get('selectedOrder').selected_paymentline; - if(selected_line){ - selected_line.set_amount(val); - selected_line.node.querySelector('input').value = selected_line.amount.toFixed(2); - } }, }); + } diff --git a/addons/point_of_sale/static/src/js/widgets.js b/addons/point_of_sale/static/src/js/widgets.js index 9747848a3f6..c5943870f41 100644 --- a/addons/point_of_sale/static/src/js/widgets.js +++ b/addons/point_of_sale/static/src/js/widgets.js @@ -1067,7 +1067,7 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa this.receipt_screen = new module.ReceiptScreenWidget(this, {}); this.receipt_screen.appendTo(this.$('.screens')); - this.payment_screen = new module.NewPaymentScreenWidget(this, {}); + this.payment_screen = new module.PaymentScreenWidget(this, {}); this.payment_screen.appendTo(this.$('.screens')); this.clientlist_screen = new module.ClientListScreenWidget(this, {}); diff --git a/addons/point_of_sale/static/src/xml/pos.xml b/addons/point_of_sale/static/src/xml/pos.xml index 98317a167e6..0849244155e 100644 --- a/addons/point_of_sale/static/src/xml/pos.xml +++ b/addons/point_of_sale/static/src/xml/pos.xml @@ -423,19 +423,29 @@ - - - - + + + + + + + + + + - - - - - + + + + + + + + + @@ -448,25 +458,25 @@
- - - - + + + +
- - - - + + + +
- - - - + + + +
- - - - + + +
@@ -483,7 +493,7 @@
- +
@@ -530,35 +540,6 @@ - -
-
-
-
-
-
- - Paid: - - -
-
- - Remaining: - - -
-
- - Change: - - -
-
-
-
-
-