[IMP] point_of_sale: put the POS js files include function into the openerp namespace
[odoo/odoo.git] / addons / point_of_sale / static / src / js / screens.js
index b326a63..2ec6288 100644 (file)
@@ -15,7 +15,9 @@
 // that only one screen is shown at the same time and that show() is called after all
 // hide()s
 
-function openerp_pos_screens(instance, module){ //module is instance.point_of_sale
+openerp.point_of_sale.load_screens = function load_screens(instance, module){ //module is instance.point_of_sale
+    "use strict";
+
     var QWeb = instance.web.qweb,
     _t = instance.web._t;
 
@@ -24,30 +26,27 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
     module.ScreenSelector = instance.web.Class.extend({
         init: function(options){
             this.pos = options.pos;
-
-            this.screen_set = options.screen_set || {};
-
-            this.popup_set = options.popup_set || {};
-
+            this.screen_set     = options.screen_set || {};
+            this.popup_set      = options.popup_set || {};
             this.default_screen = options.default_screen;
-
-            this.current_popup = null;
-
-            this.current_mode = options.default_mode || 'cashier';
-
+            this.startup_screen = options.startup_screen;
+            this.current_popup  = null;
+            this.current_mode   = options.default_mode || 'cashier';
             this.current_screen = null; 
 
-            for(screen_name in this.screen_set){
+            for (var screen_name in this.screen_set) {
                 this.screen_set[screen_name].hide();
             }
             
-            for(popup_name in this.popup_set){
+            for (var popup_name in this.popup_set) {
                 this.popup_set[popup_name].hide();
             }
 
-            this.pos.get('selectedOrder').set_screen_data({
-                'screen': this.default_screen,
-            });
+            if (this.pos.get_order()) {
+                this.pos.get_order().set_screen_data({
+                    'screen': this.default_screen,
+                });
+            }
 
             this.pos.bind('change:selectedOrder', this.load_saved_screen, this);
         },
@@ -70,11 +69,13 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
                 this.current_popup = null;
             }
         },
-        load_saved_screen:  function(){
+        load_saved_screen:  function(options){
+            options = options || {};
             this.close_popup();
-            var selectedOrder = this.pos.get('selectedOrder');
-            // this.set_current_screen(selectedOrder.get_screen_data('screen') || this.default_screen,null,'refresh');
-            this.set_current_screen(this.default_screen,null,'refresh');
+            var selectedOrder = this.pos.get_order();
+            // FIXME : this changing screen behaviour is sometimes confusing ... 
+            this.set_current_screen(selectedOrder.get_screen_data('screen') || options.default_screen || this.default_screen,null,'refresh');
+            //this.set_current_screen(this.default_screen,null,'refresh');
             
         },
         set_user_mode: function(user_mode){
@@ -95,17 +96,19 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
 
             this.close_popup();
 
-            var order = this.pos.get('selectedOrder');
-            var old_screen_name = order.get_screen_data('screen');
+            var order = this.pos.get_order();
+            if (order) {
+                var old_screen_name = order.get_screen_data('screen');
 
-            order.set_screen_data('screen',screen_name);
+                order.set_screen_data('screen',screen_name);
 
-            if(params){
-                order.set_screen_data('params',params);
-            }
+                if(params){
+                    order.set_screen_data('params',params);
+                }
 
-            if( screen_name !== old_screen_name ){
-                order.set_screen_data('previous-screen',old_screen_name);
+                if( screen_name !== old_screen_name ){
+                    order.set_screen_data('previous-screen',old_screen_name);
+                }
             }
 
             if ( refresh || screen !== this.current_screen){
@@ -118,21 +121,24 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
             }
         },
         get_current_screen: function(){
-            return this.pos.get('selectedOrder').get_screen_data('screen') || this.default_screen;
+            return this.pos.get_order().get_screen_data('screen') || this.default_screen;
         },
         back: function(){
-            var previous = this.pos.get('selectedOrder').get_screen_data('previous-screen');
+            var previous = this.pos.get_order().get_screen_data('previous-screen');
             if(previous){
                 this.set_current_screen(previous);
             }
         },
         get_current_screen_param: function(param){
-            var params = this.pos.get('selectedOrder').get_screen_data('params');
+            var params = this.pos.get_order().get_screen_data('params');
             return params ? params[param] : undefined;
         },
         set_default_screen: function(){
             this.set_current_screen(this.default_screen);
         },
+        change_default_screen: function(screen){ 
+            this.default_screen = screen;
+        },
     });
 
     module.ScreenWidget = module.PosBaseWidget.extend({
@@ -168,14 +174,14 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
 
         // what happens when a cashier id barcode is scanned.
         // the default behavior is the following : 
-        // - if there's a user with a matching ean, put it as the active 'cashier', go to cashier mode, and return true
+        // - if there's a user with a matching barcode, put it as the active 'cashier', go to cashier mode, and return true
         // - else : do nothing and return false. You probably want to extend this to show and appropriate error popup... 
         barcode_cashier_action: function(code){
             var users = this.pos.users;
             for(var i = 0, len = users.length; i < len; i++){
-                if(users[i].ean13 === code.code){
-                    this.pos.cashier = users[i];
-                    this.pos_widget.username.refresh();
+                if(users[i].barcode === code.code){
+                    this.pos.set_cashier(users[i]);
+                    this.pos_widget.username.renderElement();
                     return true;
                 }
             }
@@ -185,13 +191,12 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
         
         // what happens when a client id barcode is scanned.
         // the default behavior is the following : 
-        // - if there's a user with a matching ean, put it as the active 'client' and return true
+        // - if there's a user with a matching barcode, put it as the active 'client' and return true
         // - else : return false. 
         barcode_client_action: function(code){
-            var partner = this.pos.db.get_partner_by_ean13(code.code);
+            var partner = this.pos.db.get_partner_by_barcode(code.code);
             if(partner){
-                this.pos.get('selectedOrder').set_client(partner);
-                this.pos_widget.username.refresh();
+                this.pos.get_order().set_client(partner);
                 return true;
             }
             this.pos_widget.screen_selector.show_popup('error-barcode',code.code);
@@ -201,7 +206,7 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
         // what happens when a discount barcode is scanned : the default behavior
         // is to set the discount on the last order.
         barcode_discount_action: function(code){
-            var last_orderline = this.pos.get('selectedOrder').getLastOrderline();
+            var last_orderline = this.pos.get_order().get_last_orderline();
             if(last_orderline){
                 last_orderline.set_discount(code.value)
             }
@@ -210,26 +215,6 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
         barcode_error_action: function(code){
             this.pos_widget.screen_selector.show_popup('error-barcode',code.code);
         },
-        // shows an action bar on the screen. The actionbar is automatically shown when you add a button
-        // with add_action_button()
-        show_action_bar: function(){
-            this.pos_widget.action_bar.show();
-        },
-
-        // hides the action bar. The actionbar is automatically hidden when it is empty
-        hide_action_bar: function(){
-            this.pos_widget.action_bar.hide();
-        },
-
-        // adds a new button to the action bar. The button definition takes three parameters, all optional :
-        // - label: the text below the button
-        // - icon:  a small icon that will be shown
-        // - click: a callback that will be executed when the button is clicked.
-        // the method returns a reference to the button widget, and automatically show the actionbar.
-        add_action_button: function(button_def){
-            this.show_action_bar();
-            return this.pos_widget.action_bar.add_new_button(button_def);
-        },
 
         // this method shows the screen and sets up all the widget related to this screen. Extend this method
         // if you want to alter the behavior of the screen.
@@ -241,19 +226,12 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
                 this.$el.removeClass('oe_hidden');
             }
 
-            if(this.pos_widget.action_bar.get_button_count() > 0){
-                this.show_action_bar();
-            }else{
-                this.hide_action_bar();
-            }
-            
             var self = this;
 
             this.pos_widget.set_numpad_visible(this.show_numpad);
             this.pos_widget.set_leftpane_visible(this.show_leftpane);
 
             this.pos_widget.username.set_user_mode(this.pos_widget.screen_selector.get_user_mode());
-
             this.pos.barcode_reader.set_action_callback({
                 'cashier': self.barcode_cashier_action ? function(code){ self.barcode_cashier_action(code); } : undefined ,
                 'product': self.barcode_product_action ? function(code){ self.barcode_product_action(code); } : undefined ,
@@ -269,7 +247,6 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
             if(this.pos.barcode_reader){
                 this.pos.barcode_reader.reset_action_callbacks();
             }
-            this.pos_widget.action_bar.destroy_buttons();
         },
 
         // this methods hides the screen. It's not a good place to put your cleanup stuff as it is called on the
@@ -313,7 +290,6 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
         },
     });
 
-
     module.FullscreenPopup = module.PopUpWidget.extend({
         template:'FullscreenPopupWidget',
         show: function(){
@@ -334,51 +310,28 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
     });
 
 
-    module.ChooseReceiptPopupWidget = module.PopUpWidget.extend({
-        template:'ChooseReceiptPopupWidget',
-        show: function(){
-            this._super();
-            this.renderElement();
-            var self = this;
-            var currentOrder = self.pos.get('selectedOrder');
-            
-            this.$('.button.receipt').off('click').click(function(){
-                currentOrder.set_receipt_type('receipt');
-                self.pos_widget.screen_selector.set_current_screen('products');
-            });
-
-            this.$('.button.invoice').off('click').click(function(){
-                currentOrder.set_receipt_type('invoice');
-                self.pos_widget.screen_selector.set_current_screen('products');
-            });
-        },
-        get_client_name: function(){
-            var client = this.pos.get('selectedOrder').get_client();
-            if( client ){
-                return client.name;
-            }else{
-                return '';
-            }
-        },
-    });
-
     module.ErrorPopupWidget = module.PopUpWidget.extend({
         template:'ErrorPopupWidget',
-        show: function(text){
+        show: function(options){
+            options = options || {};
             var self = this;
             this._super();
 
             $('body').append('<audio src="/point_of_sale/static/src/sounds/error.wav" autoplay="true"></audio>');
 
-            if( text && (text.message || text.comment) ){
-                this.$('.message').text(text.message);
-                this.$('.comment').text(text.comment);
-            }
+            this.message = options.message || _t('Error');
+            this.comment = options.comment || '';
+
+            this.renderElement();
 
             this.pos.barcode_reader.save_callbacks();
             this.pos.barcode_reader.reset_action_callbacks();
-            this.$('.footer .button').off('click').click(function(){
+
+            this.$('.footer .button').click(function(){
                 self.pos_widget.screen_selector.close_popup();
+                if ( options.confirm ) {
+                    options.confirm.call(self);
+                }
             });
         },
         close:function(){
@@ -387,23 +340,22 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
         },
     });
 
-
-    module.ErrorSessionPopupWidget = module.ErrorPopupWidget.extend({
-        template:'ErrorSessionPopupWidget',
+    module.ErrorTracebackPopupWidget = module.ErrorPopupWidget.extend({
+        template:'ErrorTracebackPopupWidget',
     });
 
     module.ErrorBarcodePopupWidget = module.ErrorPopupWidget.extend({
         template:'ErrorBarcodePopupWidget',
         show: function(barcode){
+            this.barcode = barcode;
             this._super();
-            this.$('.barcode').text(barcode);
-
         },
     });
 
     module.ConfirmPopupWidget = module.PopUpWidget.extend({
         template: 'ConfirmPopupWidget',
         show: function(options){
+            options = options || {};
             var self = this;
             this._super();
 
@@ -427,6 +379,171 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
         },
     });
 
+    /**
+     * A popup that allows the user to select one item from a list. 
+     *
+     * show_popup('selection',{
+     *  message: 'Pick an Option',
+     *      message: "Popup Title",
+     *      list: [
+     *          { label: 'foobar',  item: 45 },
+     *          { label: 'bar foo', item: 'stuff' },
+     *      ],
+     *      confirm: function(item) {
+     *          // get the item selected by the user.
+     *      },
+     *      cancel: function(){
+     *          // user chose nothing
+     *      }
+     *  });
+     */
+
+    module.SelectionPopupWidget = module.PopUpWidget.extend({
+        template: 'SelectionPopupWidget',
+        show: function(options){
+            options = options || {};
+            var self = this;
+            this._super();
+
+            this.message = options.message || '';
+            this.list    = options.list    || [];
+            this.renderElement();
+
+            this.$('.button.cancel').click(function(){
+                self.pos_widget.screen_selector.close_popup();
+                if (options.cancel){
+                    options.cancel.call(self);
+                }
+            });
+
+            this.$('.selection-item').click(function(){
+                self.pos_widget.screen_selector.close_popup();
+                if (options.confirm) {
+                    var item = self.list[parseInt($(this).data('item-index'))];
+                    item = item ? item.item : item;
+                    options.confirm.call(self,item);
+                }
+            });
+        },
+    });
+
+    module.TextInputPopupWidget = module.PopUpWidget.extend({
+        template: 'TextInputPopupWidget',
+        show: function(options){
+            options = options || {};
+            var self = this;
+            this._super();
+
+            this.message = options.message || '';
+            this.comment = options.comment || '';
+            this.value   = options.value   || '';
+            this.renderElement();
+            this.$('input,textarea').focus();
+            
+            this.$('.button.cancel').click(function(){
+                self.pos_widget.screen_selector.close_popup();
+                if( options.cancel ){
+                    options.cancel.call(self);
+                }
+            });
+
+            this.$('.button.confirm').click(function(){
+                self.pos_widget.screen_selector.close_popup();
+                var value = self.$('input,textarea').val();
+                if( options.confirm ){
+                    options.confirm.call(self,value);
+                }
+            });
+        },
+    });
+
+    module.TextAreaPopupWidget = module.TextInputPopupWidget.extend({
+        template: 'TextAreaPopupWidget',
+    });
+
+    module.NumberPopupWidget = module.PopUpWidget.extend({
+        template: 'NumberPopupWidget',
+        click_numpad_button: function($el,event){
+            this.numpad_input($el.data('action'));
+        },
+        numpad_input: function(input) { //FIXME -> Deduplicate code
+            var oldbuf = this.inputbuffer.slice(0);
+
+            if (input === '.') {
+                if (this.firstinput) {
+                    this.inputbuffer = "0.";
+                }else 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))) {
+                if (this.firstinput) {
+                    this.inputbuffer = '' + input;
+                } else {
+                    this.inputbuffer += input;
+                }
+            }
+
+            this.firstinput = this.inputbuffer.length === 0;
+
+            if (this.inputbuffer !== oldbuf) {
+                this.$('.value').text(this.inputbuffer);
+            }
+        },
+        show: function(options){
+            options = options || {};
+            var self = this;
+            this._super();
+
+            this.message = options.message || '';
+            this.comment = options.comment || '';
+            this.inputbuffer = options.value   || '';
+            this.renderElement();
+            this.firstinput = true;
+            
+            this.$('.input-button,.mode-button').click(function(event){
+                self.click_numpad_button($(this),event);
+            });
+            this.$('.button.cancel').click(function(){
+                self.pos_widget.screen_selector.close_popup();
+                if( options.cancel ){
+                    options.cancel.call(self);
+                }
+            });
+
+            this.$('.button.confirm').click(function(){
+                self.pos_widget.screen_selector.close_popup();
+                if( options.confirm ){
+                    options.confirm.call(self,self.inputbuffer);
+                }
+            });
+        },
+    });
+
+    module.PasswordPopupWidget = module.NumberPopupWidget.extend({
+        renderElement: function(){
+            this._super();
+            this.$('.popup').addClass('popup-password');    // HELLO HACK !
+        },
+    });
+
     module.ErrorNoClientPopupWidget = module.ErrorPopupWidget.extend({
         template: 'ErrorNoClientPopupWidget',
     });
@@ -435,6 +552,14 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
         template: 'ErrorInvoiceTransferPopupWidget',
     });
 
+    module.UnsentOrdersPopupWidget = module.ConfirmPopupWidget.extend({
+        template: 'UnsentOrdersPopupWidget',
+    });
+
+    module.UnpaidOrdersPopupWidget = module.ConfirmPopupWidget.extend({
+        template: 'UnpaidOrdersPopupWidget',
+    });
+
     module.ScaleScreenWidget = module.ScreenWidget.extend({
         template:'ScaleScreenWidget',
 
@@ -487,11 +612,11 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
             }
         },
         order_product: function(){
-            this.pos.get('selectedOrder').addProduct(this.get_product(),{ quantity: this.weight });
+            this.pos.get_order().add_product(this.get_product(),{ quantity: this.weight });
         },
         get_product_name: function(){
             var product = this.get_product();
-            return (product ? product.name : undefined) || 'Unnamed Product';
+            return (product ? product.display_name : undefined) || 'Unnamed Product';
         },
         get_product_price: function(){
             var product = this.get_product();
@@ -508,7 +633,7 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
             if(!product || !this.pos){
                 return defaultstr;
             }
-            var unit_id = product.uos_id || product.uom_id;
+            var unit_id = product.uom_id;
             if(!unit_id){
                 return defaultstr;
             }
@@ -544,7 +669,7 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
                     if(product.to_weight && self.pos.config.iface_electronic_scale){
                         self.pos_widget.screen_selector.set_current_screen('scale',{product: product});
                     }else{
-                        self.pos.get('selectedOrder').addProduct(product);
+                        self.pos.get_order().add_product(product);
                     }
                 },
                 product_list: this.pos.db.get_product_by_category(0)
@@ -580,6 +705,11 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
     module.ClientListScreenWidget = module.ScreenWidget.extend({
         template: 'ClientListScreenWidget',
 
+        init: function(parent, options){
+            this._super(parent, options);
+            this.partner_cache = new module.DomCache();
+        },
+
         show_leftpane: false,
 
         auto_back: true,
@@ -590,7 +720,7 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
 
             this.renderElement();
             this.details_visible = false;
-            this.old_client = this.pos.get('selectedOrder').get('client');
+            this.old_client = this.pos.get_order().get_client()
             this.new_client = this.old_client;
 
             this.$('.back').click(function(){
@@ -602,19 +732,31 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
                 self.pos_widget.screen_selector.back();
             });
 
-            var partners = this.pos.db.get_partners_sorted();
+            this.$('.new-customer').click(function(){
+                self.display_client_details('edit',{
+                    'country_id': self.pos.company.country_id,
+                });
+            });
+
+            var partners = this.pos.db.get_partners_sorted(1000);
             this.render_list(partners);
+            
+            this.reload_partners();
 
             if( this.old_client ){
                 this.display_client_details('show',this.old_client,0);
             }
 
             this.$('.client-list-contents').delegate('.client-line','click',function(event){
-                console.log('uh');
                 self.line_select(event,$(this),parseInt($(this).data('id')));
             });
 
             var search_timeout = null;
+
+            if(this.pos.config.iface_vkeyboard && this.pos_widget.onscreen_keyboard){
+                this.pos_widget.onscreen_keyboard.connect(this.$('.searchbox input'));
+            }
+
             this.$('.searchbox input').on('keyup',function(event){
                 clearTimeout(search_timeout);
 
@@ -629,6 +771,13 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
                 self.clear_search();
             });
         },
+        barcode_client_action: function(code){
+            if (this.editing_client) {
+                this.$('.detail.barcode').val(code.code);
+            } else if (this.pos.db.get_partner_by_barcode(code.code)) {
+                this.display_client_details('show',this.pos.db.get_partner_by_barcode(code.code));
+            }
+        },
         perform_search: function(query, associate_result){
             if(query){
                 var customers = this.pos.db.search_partner(query);
@@ -645,25 +794,35 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
             }
         },
         clear_search: function(){
-            var customers = this.pos.db.get_partners_sorted();
+            var customers = this.pos.db.get_partners_sorted(1000);
             this.render_list(customers);
             this.$('.searchbox input')[0].value = '';
             this.$('.searchbox input').focus();
         },
         render_list: function(partners){
-            var contents = this.$('.client-list-contents');
-            contents.empty();
-            for(var i = 0, len = partners.length; i < len; i++){
-                var clientline = $(QWeb.render('ClientLine',{partner:partners[i]}));
-                if( partners[i] === this.new_client ){
-                    clientline.addClass('highlight');
+            var contents = this.$el[0].querySelector('.client-list-contents');
+            contents.innerHTML = "";
+            for(var i = 0, len = Math.min(partners.length,1000); i < len; i++){
+                var partner    = partners[i];
+                var clientline = this.partner_cache.get_node(partner.id);
+                if(!clientline){
+                    var clientline_html = QWeb.render('ClientLine',{widget: this, partner:partners[i]});
+                    var clientline = document.createElement('tbody');
+                    clientline.innerHTML = clientline_html;
+                    clientline = clientline.childNodes[1];
+                    this.partner_cache.cache_node(partner.id,clientline);
+                }
+                if( partners === this.new_client ){
+                    clientline.classList.add('highlight');
+                }else{
+                    clientline.classList.remove('highlight');
                 }
-                contents.append(clientline);
+                contents.appendChild(clientline);
             }
         },
         save_changes: function(){
             if( this.has_client_changed() ){
-                this.pos.get('selectedOrder').set_client(this.new_client);
+                this.pos.get_order().set_client(this.new_client);
             }
         },
         has_client_changed: function(){
@@ -675,7 +834,10 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
         },
         toggle_save_button: function(){
             var $button = this.$('.button.next');
-            if( this.new_client ){
+            if (this.editing_client) {
+                $button.addClass('oe_hidden');
+                return;
+            } else if( this.new_client ){
                 if( !this.old_client){
                     $button.text(_t('Set Customer'));
                 }else{
@@ -707,43 +869,207 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
         partner_icon_url: function(id){
             return '/web/binary/image?model=res.partner&id='+id+'&field=image_small';
         },
+
+        // ui handle for the 'edit selected customer' action
+        edit_client_details: function(partner) {
+            this.display_client_details('edit',partner);
+        },
+
+        // ui handle for the 'cancel customer edit changes' action
+        undo_client_details: function(partner) {
+            if (!partner.id) {
+                this.display_client_details('hide');
+            } else {
+                this.display_client_details('show',partner);
+            }
+        },
+
+        // what happens when we save the changes on the client edit form -> we fetch the fields, sanitize them,
+        // send them to the backend for update, and call saved_client_details() when the server tells us the
+        // save was successfull.
+        save_client_details: function(partner) {
+            var self = this;
+            
+            var fields = {}
+            this.$('.client-details-contents .detail').each(function(idx,el){
+                fields[el.name] = el.value;
+            });
+
+            if (!fields.name) {
+                this.pos_widget.screen_selector.show_popup('error',{
+                    message: _t('A Customer Name Is Required'),
+                });
+                return;
+            }
+            
+            if (this.uploaded_picture) {
+                fields.image = this.uploaded_picture;
+            }
+
+            fields.id           = partner.id || false;
+            fields.country_id   = fields.country_id || false;
+            fields.barcode        = fields.barcode ? this.pos.barcode_reader.sanitize_ean(fields.barcode) : false; 
+
+            new instance.web.Model('res.partner').call('create_from_ui',[fields]).then(function(partner_id){
+                self.saved_client_details(partner_id);
+            });
+        },
+        
+        // what happens when we've just pushed modifications for a partner of id partner_id
+        saved_client_details: function(partner_id){
+            var self = this;
+            this.reload_partners().then(function(){
+                var partner = self.pos.db.get_partner_by_id(partner_id);
+                if (partner) {
+                    self.new_client = partner;
+                    self.toggle_save_button();
+                    self.display_client_details('show',partner);
+                } else {
+                    // should never happen, because create_from_ui must return the id of the partner it
+                    // has created, and reload_partner() must have loaded the newly created partner. 
+                    self.display_client_details('hide');
+                }
+            });
+        },
+
+        // resizes an image, keeping the aspect ratio intact,
+        // the resize is useful to avoid sending 12Mpixels jpegs
+        // over a wireless connection.
+        resize_image_to_dataurl: function(img, maxwidth, maxheight, callback){
+            img.onload = function(){
+                var png = new Image();
+                var canvas = document.createElement('canvas');
+                var ctx    = canvas.getContext('2d');
+                var ratio  = 1;
+
+                if (img.width > maxwidth) {
+                    ratio = maxwidth / img.width;
+                }
+                if (img.height * ratio > maxheight) {
+                    ratio = maxheight / img.height;
+                }
+                var width  = Math.floor(img.width * ratio);
+                var height = Math.floor(img.height * ratio);
+
+                canvas.width  = width;
+                canvas.height = height;
+                ctx.drawImage(img,0,0,width,height);
+
+                var dataurl = canvas.toDataURL();
+                callback(dataurl);
+            }
+        },
+
+        // Loads and resizes a File that contains an image.
+        // callback gets a dataurl in case of success.
+        load_image_file: function(file, callback){
+            var self = this;
+            if (!file.type.match(/image.*/)) {
+                this.pos_widget.screen_selector.show_popup('error',{
+                    message:_t('Unsupported File Format'),
+                    comment:_t('Only web-compatible Image formats such as .png or .jpeg are supported'),
+                });
+                return;
+            }
+            
+            var reader = new FileReader();
+            reader.onload = function(event){
+                var dataurl = event.target.result;
+                var img     = new Image();
+                img.src = dataurl;
+                self.resize_image_to_dataurl(img,800,600,callback);
+            }
+            reader.onerror = function(){
+                self.pos_widget.screen_selector.show_popup('error',{
+                    message:_t('Could Not Read Image'),
+                    comment:_t('The provided file could not be read due to an unknown error'),
+                });
+            };
+            reader.readAsDataURL(file);
+        },
+
+        // This fetches partner changes on the server, and in case of changes, 
+        // rerenders the affected views
+        reload_partners: function(){
+            var self = this;
+            return this.pos.load_new_partners().then(function(){
+                self.render_list(self.pos.db.get_partners_sorted(1000));
+                
+                // update the currently assigned client if it has been changed in db.
+                var curr_client = self.pos.get_order().get_client();
+                if (curr_client) {
+                    self.pos.get_order().set_client(self.pos.db.get_partner_by_id(curr_client.id));
+                }
+            });
+        },
+
+        // Shows,hides or edit the customer details box :
+        // visibility: 'show', 'hide' or 'edit'
+        // partner:    the partner object to show or edit
+        // clickpos:   the height of the click on the list (in pixel), used
+        //             to maintain consistent scroll.
         display_client_details: function(visibility,partner,clickpos){
+            var self = this;
+            var contents = this.$('.client-details-contents');
+            var parent   = this.$('.client-list').parent();
+            var scroll   = parent.scrollTop();
+            var height   = contents.height();
+
+            contents.off('click','.button.edit'); 
+            contents.off('click','.button.save'); 
+            contents.off('click','.button.undo'); 
+            contents.on('click','.button.edit',function(){ self.edit_client_details(partner); });
+            contents.on('click','.button.save',function(){ self.save_client_details(partner); });
+            contents.on('click','.button.undo',function(){ self.undo_client_details(partner); });
+            this.editing_client = false;
+            this.uploaded_picture = null;
+
             if(visibility === 'show'){
-                var contents = this.$('.client-details-contents');
-                var parent   = this.$('.client-list').parent();
-                var old_scroll   = parent.scrollTop();
-                var old_height   = contents.height();
                 contents.empty();
                 contents.append($(QWeb.render('ClientDetails',{widget:this,partner:partner})));
+
                 var new_height   = contents.height();
 
                 if(!this.details_visible){
-                    if(clickpos < old_scroll + new_height + 20 ){
+                    if(clickpos < scroll + new_height + 20 ){
                         parent.scrollTop( clickpos - 20 );
                     }else{
                         parent.scrollTop(parent.scrollTop() + new_height);
                     }
                 }else{
-                    parent.scrollTop(parent.scrollTop() - old_height + new_height);
+                    parent.scrollTop(parent.scrollTop() - height + new_height);
                 }
 
                 this.details_visible = true;
-            }else if(visibility === 'hide'){
-                var contents = this.$('.client-details-contents');
-                var parent   = this.$('.client-list').parent();
-                var scroll   = parent.scrollTop();
-                var height   = contents.height();
+                this.toggle_save_button();
+            } else if (visibility === 'edit') {
+                this.editing_client = true;
+                contents.empty();
+                contents.append($(QWeb.render('ClientDetailsEdit',{widget:this,partner:partner})));
+                this.toggle_save_button();
+
+                contents.find('.image-uploader').on('change',function(){
+                    self.load_image_file(event.target.files[0],function(res){
+                        if (res) {
+                            contents.find('.client-picture img, .client-picture .fa').remove();
+                            contents.find('.client-picture').append("<img src='"+res+"'>");
+                            contents.find('.detail.picture').remove();
+                            self.uploaded_picture = res;
+                        }
+                    });
+                });
+            } else if (visibility === 'hide') {
                 contents.empty();
                 if( height > scroll ){
                     contents.css({height:height+'px'});
                     contents.animate({height:0},400,function(){
                         contents.css({height:''});
                     });
-                    //parent.scrollTop(0);
                 }else{
                     parent.scrollTop( parent.scrollTop() - height);
                 }
                 this.details_visible = false;
+                this.toggle_save_button();
             }
         },
         close: function(){
@@ -753,30 +1079,19 @@ 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();
             var self = this;
 
-            var print_button = this.add_action_button({
-                    label: _t('Print'),
-                    icon: '/point_of_sale/static/src/img/icons/png48/printer.png',
-                    click: function(){ self.print(); },
-                });
-
-            var finish_button = this.add_action_button({
-                    label: _t('Next Order'),
-                    icon: '/point_of_sale/static/src/img/icons/png48/go-next.png',
-                    click: function() { self.finishOrder(); },
-                });
-
             this.refresh();
-            this.print();
 
-            //
+            if (!this.pos.get_order()._printed && this.pos.config.iface_print_auto) {
+                this.print();
+            }
+
             // The problem is that in chrome the print() is asynchronous and doesn't
             // execute until all rpc are finished. So it conflicts with the rpc used
             // to send the orders to the backend, and the user is able to go to the next 
@@ -793,372 +1108,403 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
             // 2 seconds is the same as the default timeout for sending orders and so the dialog
             // should have appeared before the timeout... so yeah that's not ultra reliable. 
 
-            finish_button.set_disabled(true);   
+            this.lock_screen(true);
             setTimeout(function(){
-                finish_button.set_disabled(false);
+                self.lock_screen(false);
             }, 2000);
         },
+        lock_screen: function(locked) {
+            this._locked = locked;
+            if (locked) {
+                this.$('.next').removeClass('highlight');
+            } else {
+                this.$('.next').addClass('highlight');
+            }
+        },
         print: function() {
+            this.pos.get_order()._printed = true;
             window.print();
         },
-        finishOrder: function() {
-            this.pos.get('selectedOrder').destroy();
+        finish_order: function() {
+            if (!this._locked) {
+                this.pos.get_order().finalize();
+            }
+        },
+        renderElement: function() {
+            var self = this;
+            this._super();
+            this.$('.next').click(function(){
+                self.finish_order();
+            });
+            this.$('.button.print').click(function(){
+                self.print();
+            });
         },
         refresh: function() {
-            var order = this.pos.get('selectedOrder');
-            $('.pos-receipt-container', this.$el).html(QWeb.render('PosTicket',{
+            var order = this.pos.get_order();
+            this.$('.pos-receipt-container').html(QWeb.render('PosTicket',{
                     widget:this,
                     order: order,
-                    orderlines: order.get('orderLines').models,
-                    paymentlines: order.get('paymentLines').models,
+                    receipt: order.export_for_printing(),
+                    orderlines: order.get_orderlines(),
+                    paymentlines: order.get_paymentlines(),
                 }));
         },
-        close: function(){
-            this._super();
-        }
     });
 
-
     module.PaymentScreenWidget = module.ScreenWidget.extend({
-        template: 'PaymentScreenWidget',
-        back_screen: 'products',
-        next_screen: 'receipt',
+        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._super(parent, options);
 
             this.pos.bind('change:selectedOrder',function(){
-                    this.bind_events();
                     this.renderElement();
+                    this.watch_order_changes();
                 },this);
+            this.watch_order_changes();
 
-            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)   
+            this.inputbuffer = "";
+            this.firstinput  = true;
+            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 = '+';
                 }
-                event.stopPropagation();
-            };
 
-            this.line_change_handler = function(event){
-                var node = this;
-                while(node && !node.classList.contains('paymentline')){
-                    node = node.parentNode;
+                self.payment_input(key);
+
+            };
+        },
+        // resets the current input buffer
+        reset_input: function(){
+            var line = this.pos.get_order().selected_paymentline;
+            this.firstinput  = true;
+            if (line) {
+                this.inputbuffer = this.format_currency_no_symbol(line.get_amount());
+            } else {
+                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.firstinput) {
+                    this.inputbuffer = "0.";
+                }else if (!this.inputbuffer.length || this.inputbuffer === '-') {
+                    this.inputbuffer += "0.";
+                } else if (this.inputbuffer.indexOf('.') < 0){
+                    this.inputbuffer = this.inputbuffer + '.';
                 }
-                if(node){
-                    node.line.set_amount(this.value);
+            } 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);
                 }
-            };
-
-            this.line_click_handler = function(event){
-                var node = this;
-                while(node && !node.classList.contains('paymentline')){
-                    node = node.parentNode;
+            } else if (input === '-') {
+                if ( this.inputbuffer[0] === '-' ) {
+                    this.inputbuffer = this.inputbuffer.substring(1,this.inputbuffer.length);
+                } else {
+                    this.inputbuffer = '-' + this.inputbuffer;
                 }
-                if(node){
-                    self.pos.get('selectedOrder').selectPaymentline(node.line);
+            } else if (input[0] === '+' && !isNaN(parseFloat(input))) {
+                this.inputbuffer = '' + ((parseFloat(this.inputbuffer) || 0) + parseFloat(input));
+            } else if (!isNaN(parseInt(input))) {
+                if (this.firstinput) {
+                    this.inputbuffer = '' + input;
+                } else {
+                    this.inputbuffer += input;
                 }
-            };
+            }
 
-            this.hotkey_handler = function(event){
-                if(event.which === 13){
-                    self.validate_order();
-                }else if(event.which === 27){
-                    self.back();
-                }
-            };
+            this.firstinput = this.inputbuffer.length === 0;
 
+            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);
+                }
+            }
         },
-        show: function(){
-            this._super();
+        click_numpad: function(button) {
+            this.payment_input(button.data('action'));
+        },
+        render_numpad: function() {
             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();
-                        },
-                    });
+            var numpad = $(QWeb.render('PaymentScreen-Numpad', { widget:this }));
+            numpad.on('click','button',function(){
+                self.click_numpad($(this));
+            });
+            return numpad;
+        },
+        click_delete_paymentline: function(cid){
+            var lines = this.pos.get_order().get_paymentlines();
+            for ( var i = 0; i < lines.length; i++ ) {
+                if (lines[i].cid === cid) {
+                    this.pos.get_order().remove_paymentline(lines[i]);
+                    this.reset_input();
+                    this.render_paymentlines();
+                    return;
+                }
             }
-
-            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);
+        click_paymentline: function(cid){
+            var lines = this.pos.get_order().get_paymentlines();
+            for ( var i = 0; i < lines.length; i++ ) {
+                if (lines[i].cid === cid) {
+                    this.pos.get_order().select_paymentline(lines[i]);
+                    this.reset_input();
+                    this.render_paymentlines();
+                    return;
                 }
             }
         },
-        back: function() {
-            this.remove_empty_lines();
-            this.pos_widget.screen_selector.set_current_screen(this.back_screen);
-        },
-        bind_events: function() {
-            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);
-
-            this.old_order = order;
-
-            if(this.old_paymentlines){
-                this.old_paymentlines.unbind(null,null,this);
+        render_paymentlines: function() {
+            var self  = this;
+            var order = this.pos.get_order();
+            if (!order) {
+                return;
             }
-            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;
+            var lines = order.get_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.$('.paymentlines-container').empty();
+            var lines = $(QWeb.render('PaymentScreen-Paymentlines', { 
+                widget: this, 
+                order: order,
+                paymentlines: lines,
+            }));
 
-            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();
-                }
+            lines.on('click','.delete-button',function(){
+                self.click_delete_paymentline($(this).data('cid'));
+            });
 
-                if(Number(value) === 0){
-                    input.value = '';
-                }else{
-                    input.value = value;
-                    input.select();
+            lines.on('click','.paymentline',function(){
+                self.click_paymentline($(this).data('cid'));
+            });
+                
+            lines.appendTo(this.$('.paymentlines-container'));
+        },
+        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 ){
+                    cashregister = this.pos.cashregisters[i];
+                    break;
                 }
             }
+            this.pos.get_order().add_paymentline( cashregister );
+            this.reset_input();
+            this.render_paymentlines();
         },
-        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_paymentmethods: function() {
+            var self = this;
+            var methods = $(QWeb.render('PaymentScreen-Paymentmethods', { widget:this }));
+                methods.on('click','.paymentmethod',function(){
+                    self.click_paymentmethods($(this).data('id'));
+                });
+            return methods;
+        },
+        click_invoice: function(){
+            var order = this.pos.get_order();
+            order.set_to_invoice(!order.is_to_invoice());
+            if (order.is_to_invoice()) {
+                this.$('.js_invoice').addClass('highlight');
+            } else {
+                this.$('.js_invoice').removeClass('highlight');
             }
         },
-        render_paymentline: function(line){
-            var el_html  = openerp.qweb.render('Paymentline',{widget: this, line: line});
-                el_html  = _.str.trim(el_html);
+        click_set_customer: function(){
+            this.pos_widget.screen_selector.set_current_screen('clientlist');
+        },
+        click_back: function(){
+            this.pos_widget.screen_selector.set_current_screen('products');
+        },
+        renderElement: function() {
+            var self = this;
+            this._super();
 
-            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);
+            var numpad = this.render_numpad();
+            numpad.appendTo(this.$('.payment-numpad'));
 
-            line.node = el_node;
+            var methods = this.render_paymentmethods();
+            methods.appendTo(this.$('.paymentmethods-container'));
+
+            this.render_paymentlines();
+
+            this.$('.back').click(function(){
+                self.click_back();
+            });
+
+            this.$('.next').click(function(){
+                self.validate_order();
+            });
+
+            this.$('.js_set_customer').click(function(){
+                self.click_set_customer();
+            });
+            this.$('.js_invoice').click(function(){
+                self.click_invoice();
+            });
 
-            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;
+        show: function(){
+            this.pos.get_order().clean_empty_paymentlines();
+            this.reset_input();
+            this.render_paymentlines();
+            this.order_changes();
+            window.document.body.addEventListener('keydown',this.keyboard_handler);
+            this._super();
         },
-        renderElement: function(){
+        hide: function(){
+            window.document.body.removeEventListener('keydown',this.keyboard_handler);
             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]));
+        },
+        // sets up listeners to watch for order changes
+        watch_order_changes: function() {
+            var self = this;
+            var order = this.pos.get_order();
+            if (!order) {
+                return;
             }
-            
-            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.old_order){
+                this.old_order.unbind(null,null,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());
+            order.bind('all',function(){
+                self.order_changes();
+            });
+            this.old_order = order;
+        },
+        // called when the order is changed, used to show if
+        // the order is paid or not
+        order_changes: function(){
+            var self = this;
+            var order = this.pos.get_order();
+            if (!order) {
+                return;
+            } else if (order.is_paid()) {
+                self.$('.next').addClass('highlight');
+            }else{
+                self.$('.next').removeClass('highlight');
             }
         },
-        is_paid: function(){
-            var currentOrder = this.pos.get('selectedOrder');
-            return (currentOrder.getTotalTaxIncluded() < 0.000001 
-                   || currentOrder.getPaidTotal() + 0.000001 >= currentOrder.getTotalTaxIncluded());
+        print_escpos_receipt: function(){
+            var env = {
+                widget:  this,
+                pos:     this.pos,
+                order:   this.pos.get_order(),
+                receipt: this.pos.get_order().export_for_printing(),
+            };
 
+            this.pos.proxy.print_receipt(QWeb.render('XmlReceipt',env));
         },
-        validate_order: function(options) {
+
+        // Check if the order is paid, then sends it to the backend,
+        // and complete the sale process
+        validate_order: function() {
             var self = this;
-            options = options || {};
 
-            var currentOrder = this.pos.get('selectedOrder');
+            var order = this.pos.get_order();
 
-            if(!this.is_paid()){
+            // FIXME: this check is there because the backend is unable to
+            // process empty orders. This is not the right place to fix it.
+            if (order.get_orderlines().length === 0) {
+                this.pos_widget.screen_selector.show_popup('error',{
+                    'message': _t('Empty Order'),
+                    'comment': _t('There must be at least one product in your order before it can be validated'),
+                });
                 return;
             }
 
-            if(    this.pos.config.iface_cashdrawer 
-                && this.pos.get('selectedOrder').get('paymentLines').find( function(pl){ 
-                           return pl.cashregister.journal.type === 'cash'; 
-                   })){
-                    this.pos.proxy.open_cashbox();
+            if (!order.is_paid() || this.invoicing) {
+                return;
+            }
+
+            // The exact amount must be paid if there is no cash payment method defined.
+            if (Math.abs(order.get_total_with_tax() - order.get_total_paid()) > 0.00001) {
+                var cash = false;
+                for (var i = 0; i < this.pos.cashregisters.length; i++) {
+                    cash = cash || (this.pos.cashregisters[i].journal.type === 'cash');
+                }
+                if (!cash) {
+                    this.pos_widget.screen_selector.show_popup('error',{
+                        message: _t('Cannot return change without a cash payment method'),
+                        comment: _t('There is no cash payment method available in this point of sale to handle the change.\n\n Please pay the exact amount or add a cash payment method in the point of sale configuration'),
+                    });
+                    return;
+                }
             }
 
-            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);
+            if (order.is_paid_with_cash() && this.pos.config.iface_cashdrawer) { 
+
+                    this.pos.proxy.open_cashbox();
+            }
 
-                var invoiced = this.pos.push_and_invoice_order(currentOrder);
+            if (order.is_to_invoice()) {
+                var invoiced = this.pos.push_and_invoice_order(order);
+                this.invoicing = true;
 
                 invoiced.fail(function(error){
-                    if(error === 'error-no-client'){
-                        self.pos_widget.screen_selector.show_popup('error-no-client');
-                    }else{
-                        self.pos_widget.screen_selector.show_popup('error-invoice-transfer');
+                    self.invoicing = false;
+                    if (error === 'error-no-client') {
+                        self.pos_widget.screen_selector.show_popup('confirm',{
+                            message: _t('Please select the Customer'),
+                            comment: _t('You need to select the customer before you can invoice an order.'),
+                            confirm: function(){
+                                self.pos_widget.screen_selector.set_current_screen('clientlist');
+                            },
+                        });
+                    } else {
+                        self.pos_widget.screen_selector.show_popup('error',{
+                            message: _t('The order could not be sent'),
+                            comment: _t('Check your internet connection and try again.'),
+                        });
                     }
-                    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){
-                    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{
+            } else {
+                this.pos.push_order(order) 
+                if (this.pos.config.iface_print_via_proxy) {
+                    this.print_escpos_receipt();
+                    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);
-            }
         },
     });
+
 }
+