[MERGE] from master
[odoo/odoo.git] / addons / point_of_sale / static / src / js / screens.js
index a133587..092c85a 100644 (file)
 // 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;
 
+    var round_pr = instance.web.round_precision
+
     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.default_client_screen = options.default_client_screen;
-            this.default_cashier_screen = options.default_cashier_screen;
-
-            this.current_popup = null;
-
-            this.current_mode = options.default_mode || 'client';
-
+            this.screen_set     = options.screen_set || {};
+            this.popup_set      = options.popup_set || {};
+            this.default_screen = options.default_screen;
+            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.selected_order = this.pos.get('selectedOrder');
-            this.selected_order.set_screen_data({
-                client_screen: this.default_client_screen,
-                cashier_screen: this.default_cashier_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);
         },
@@ -57,12 +55,12 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
             this.screen_set[screen_name] = screen;
             return this;
         },
-        show_popup: function(name){
+        show_popup: function(name,options){
             if(this.current_popup){
                 this.close_popup();
             }
             this.current_popup = this.popup_set[name];
-            this.current_popup.show();
+            this.current_popup.show(options);
         },
         close_popup: function(){
             if(this.current_popup){
@@ -71,17 +69,14 @@ 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');
+            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');
             
-            if(this.current_mode === 'client'){
-                this.set_current_screen(selectedOrder.get_screen_data('client_screen') || this.default_client_screen,null,'refresh');
-            }else if(this.current_mode === 'cashier'){
-                this.set_current_screen(selectedOrder.get_screen_data('cashier_screen') || this.default_cashier_screen,null,'refresh');
-            }
-            this.selected_order = selectedOrder;
         },
         set_user_mode: function(user_mode){
             if(user_mode !== this.current_mode){
@@ -100,20 +95,23 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
             }
 
             this.close_popup();
-            var selectedOrder = this.pos.get('selectedOrder');
-            if(this.current_mode === 'client'){
-                selectedOrder.set_screen_data('client_screen',screen_name);
-                if(params){ 
-                    selectedOrder.set_screen_data('client_screen_params',params); 
-                }
-            }else{
-                selectedOrder.set_screen_data('cashier_screen',screen_name);
+
+            var order = this.pos.get_order();
+            if (order) {
+                var old_screen_name = order.get_screen_data('screen');
+
+                order.set_screen_data('screen',screen_name);
+
                 if(params){
-                    selectedOrder.set_screen_data('cashier_screen_params',params);
+                    order.set_screen_data('params',params);
+                }
+
+                if( screen_name !== old_screen_name ){
+                    order.set_screen_data('previous-screen',old_screen_name);
                 }
             }
 
-            if(screen && (refresh || screen !== this.current_screen)){
+            if ( refresh || screen !== this.current_screen){
                 if(this.current_screen){
                     this.current_screen.close();
                     this.current_screen.hide();
@@ -122,21 +120,24 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
                 this.current_screen.show();
             }
         },
-        get_current_screen_param: function(param){
-            var selected_order = this.pos.get('selectedOrder');
-            if(this.current_mode === 'client'){
-                var params = selected_order.get_screen_data('client_screen_params');
-            }else{
-                var params = selected_order.get_screen_data('cashier_screen_params');
-            }
-            if(params){
-                return params[param];
-            }else{
-                return undefined;
+        get_current_screen: function(){
+            return this.pos.get_order().get_screen_data('screen') || this.default_screen;
+        },
+        back: function(){
+            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_order().get_screen_data('params');
+            return params ? params[param] : undefined;
+        },
         set_default_screen: function(){
-            this.set_current_screen(this.current_mode === 'client' ? this.default_client_screen : this.default_cashier_screen);
+            this.set_current_screen(this.default_screen);
+        },
+        change_default_screen: function(screen){ 
+            this.default_screen = screen;
         },
     });
 
@@ -155,93 +156,64 @@ 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-product',    //if defined, this popup will be loaded when there's an error in the popup
+
+        hotkeys_handlers: {},
 
         // 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 
-        // there's an error.
+        // it will add the product to the order and go to barcode_product_screen. 
         barcode_product_action: function(code){
             var self = this;
             if(self.pos.scan_product(code)){
-                self.pos.proxy.scan_item_success(code);
                 if(self.barcode_product_screen){ 
                     self.pos_widget.screen_selector.set_current_screen(self.barcode_product_screen);
                 }
             }else{
-                self.pos.proxy.scan_item_error_unrecognized(code);
-                if(self.barcode_product_error_popup && self.pos_widget.screen_selector.get_user_mode() !== 'cashier'){
-                    self.pos_widget.screen_selector.show_popup(self.barcode_product_error_popup);
-                }
+                self.pos_widget.screen_selector.show_popup('error-barcode',code.code);
             }
         },
 
         // 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.get('user_list');
+            var users = this.pos.users;
             for(var i = 0, len = users.length; i < len; i++){
-                if(users[i].ean13 === code.code){
-                    this.pos.set('cashier',users[i]);
-                    this.pos_widget.username.refresh();
-                    this.pos.proxy.cashier_mode_activated();
-                    this.pos_widget.screen_selector.set_user_mode('cashier');
+                if(users[i].barcode === code.code){
+                    this.pos.set_cashier(users[i]);
+                    this.pos_widget.username.renderElement();
                     return true;
                 }
             }
-            this.pos.proxy.scan_item_error_unrecognized(code);
+            this.pos_widget.screen_selector.show_popup('error-barcode',code.code);
             return false;
         },
         
         // 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 partners = this.pos.get('partner_list');
-            for(var i = 0, len = partners.length; i < len; i++){
-                if(partners[i].ean13 === code.code){
-                    this.pos.get('selectedOrder').set_client(partners[i]);
-                    this.pos_widget.username.refresh();
-                    this.pos.proxy.scan_item_success(code);
-                    return true;
-                }
+            var partner = this.pos.db.get_partner_by_barcode(code.code);
+            if(partner){
+                this.pos.get_order().set_client(partner);
+                return true;
             }
-            this.pos.proxy.scan_item_error_unrecognized(code);
+            this.pos_widget.screen_selector.show_popup('error-barcode',code.code);
             return false;
-            //TODO start the transaction
         },
         
         // 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){
-            this.pos.proxy.scan_item_success(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)
             }
         },
-
-        // 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);
+        // What happens when an invalid barcode is scanned : shows an error popup.
+        barcode_error_action: function(code){
+            this.pos_widget.screen_selector.show_popup('error-barcode',code.code);
         },
 
         // this method shows the screen and sets up all the widget related to this screen. Extend this method
@@ -254,46 +226,18 @@ 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();
-            }
-            
-            // we add the help button by default. we do this because the buttons are cleared on each refresh so that
-            // the button stay local to each screen
-            this.pos_widget.left_action_bar.add_new_button({
-                    label: _t('Help'),
-                    icon: '/point_of_sale/static/src/img/icons/png48/help.png',
-                    click: function(){ self.help_button_action(); },
-                });
-
             var self = this;
-            this.cashier_mode = this.pos_widget.screen_selector.get_user_mode() === 'cashier';
 
-            this.pos_widget.set_numpad_visible(this.show_numpad && this.cashier_mode);
+            this.pos_widget.set_numpad_visible(this.show_numpad);
             this.pos_widget.set_leftpane_visible(this.show_leftpane);
-            this.pos_widget.set_left_action_bar_visible(this.show_leftpane && !this.cashier_mode);
-            this.pos_widget.set_cashier_controls_visible(this.cashier_mode);
 
-            if(this.cashier_mode && this.pos.iface_self_checkout){
-                this.pos_widget.client_button.show();
-            }else{
-                this.pos_widget.client_button.hide();
-            }
-            if(this.cashier_mode){
-                this.pos_widget.close_button.show();
-            }else{
-                this.pos_widget.close_button.hide();
-            }
-            
             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 ,
                 'client' : self.barcode_client_action ?  function(code){ self.barcode_client_action(code);  } : undefined ,
                 'discount': self.barcode_discount_action ? function(code){ self.barcode_discount_action(code); } : undefined,
+                'error'   : self.barcode_error_action ?  function(code){ self.barcode_error_action(code);   } : undefined,
             });
         },
 
@@ -303,8 +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.pos_widget.left_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
@@ -348,88 +290,258 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
         },
     });
 
-    module.HelpPopupWidget = module.PopUpWidget.extend({
-        template:'HelpPopupWidget',
+    module.FullscreenPopup = module.PopUpWidget.extend({
+        template:'FullscreenPopupWidget',
         show: function(){
+            var self = this;
             this._super();
-            this.pos.proxy.help_needed();
+            this.renderElement();
+            this.$('.button.fullscreen').off('click').click(function(){
+                window.document.body.webkitRequestFullscreen();
+                self.pos_widget.screen_selector.close_popup();
+            });
+            this.$('.button.cancel').off('click').click(function(){
+                self.pos_widget.screen_selector.close_popup();
+            });
+        },
+        ismobile: function(){
+            return typeof window.orientation !== 'undefined'; 
+        }
+    });
+
+
+    module.ErrorPopupWidget = module.PopUpWidget.extend({
+        template:'ErrorPopupWidget',
+        show: function(options){
+            options = options || {};
             var self = this;
-            
-            this.$el.find('.button').off('click').click(function(){
+            this._super();
+
+            $('body').append('<audio src="/point_of_sale/static/src/sounds/error.wav" autoplay="true"></audio>');
+
+            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').click(function(){
                 self.pos_widget.screen_selector.close_popup();
+                if ( options.confirm ) {
+                    options.confirm.call(self);
+                }
             });
         },
         close:function(){
-            this.pos.proxy.help_canceled();
+            this._super();
+            this.pos.barcode_reader.restore_callbacks();
         },
     });
 
-    module.ChooseReceiptPopupWidget = module.PopUpWidget.extend({
-        template:'ChooseReceiptPopupWidget',
-        show: function(){
+    module.ErrorTracebackPopupWidget = module.ErrorPopupWidget.extend({
+        template:'ErrorTracebackPopupWidget',
+    });
+
+    module.ErrorBarcodePopupWidget = module.ErrorPopupWidget.extend({
+        template:'ErrorBarcodePopupWidget',
+        show: function(barcode){
+            this.barcode = barcode;
             this._super();
-            this.renderElement();
+        },
+    });
+
+    module.ConfirmPopupWidget = module.PopUpWidget.extend({
+        template: 'ConfirmPopupWidget',
+        show: function(options){
+            options = options || {};
             var self = this;
-            var currentOrder = self.pos.get('selectedOrder');
+            this._super();
+
+            this.message = options.message || '';
+            this.comment = options.comment || '';
+            this.renderElement();
             
-            this.$('.button.receipt').off('click').click(function(){
-                currentOrder.set_receipt_type('receipt');
-                self.pos_widget.screen_selector.set_current_screen('products');
+            this.$('.button.cancel').click(function(){
+                self.pos_widget.screen_selector.close_popup();
+                if( options.cancel ){
+                    options.cancel.call(self);
+                }
             });
 
-            this.$('.button.invoice').off('click').click(function(){
-                currentOrder.set_receipt_type('invoice');
-                self.pos_widget.screen_selector.set_current_screen('products');
+            this.$('.button.confirm').click(function(){
+                self.pos_widget.screen_selector.close_popup();
+                if( options.confirm ){
+                    options.confirm.call(self);
+                }
             });
         },
-        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(){
+    /**
+     * 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.pos.proxy.help_needed();
-            this.pos.proxy.scan_item_error_unrecognized();
 
-            this.pos.barcode_reader.save_callbacks();
-            this.pos.barcode_reader.reset_action_callbacks();
-            this.pos.barcode_reader.set_action_callback({
-                'cashier': function(code){
-                    clearInterval(this.intervalID);
-                    self.pos.proxy.cashier_mode_activated();
-                    self.pos_widget.screen_selector.set_user_mode('cashier');
-                },
+            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.$('.footer .button').off('click').click(function(){
+
+            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);
+                }
             });
         },
-        close:function(){
+    });
+
+    module.TextInputPopupWidget = module.PopUpWidget.extend({
+        template: 'TextInputPopupWidget',
+        show: function(options){
+            options = options || {};
+            var self = this;
             this._super();
-            this.pos.proxy.help_canceled();
-            this.pos.barcode_reader.restore_callbacks();
+
+            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.ProductErrorPopupWidget = module.ErrorPopupWidget.extend({
-        template:'ProductErrorPopupWidget',
+    module.TextAreaPopupWidget = module.TextInputPopupWidget.extend({
+        template: 'TextAreaPopupWidget',
     });
 
-    module.ErrorSessionPopupWidget = module.ErrorPopupWidget.extend({
-        template:'ErrorSessionPopupWidget',
+    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.ErrorNegativePricePopupWidget = module.ErrorPopupWidget.extend({
-        template:'ErrorNegativePricePopupWidget',
+    module.PasswordPopupWidget = module.NumberPopupWidget.extend({
+        renderElement: function(){
+            this._super();
+            this.$('.popup').addClass('popup-password');    // HELLO HACK !
+        },
     });
 
     module.ErrorNoClientPopupWidget = module.ErrorPopupWidget.extend({
@@ -439,46 +551,13 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
     module.ErrorInvoiceTransferPopupWidget = module.ErrorPopupWidget.extend({
         template: 'ErrorInvoiceTransferPopupWidget',
     });
-                
-    module.ScaleInviteScreenWidget = module.ScreenWidget.extend({
-        template:'ScaleInviteScreenWidget',
 
-        next_screen:'scale',
-        previous_screen:'products',
-
-        show: function(){
-            this._super();
-            var self = this;
-            var queue = this.pos.proxy_queue;
-
-            queue.schedule(function(){
-                return self.pos.proxy.weighting_start();
-            },{ important: true });
-            
-            queue.schedule(function(){
-                return self.pos.proxy.weighting_read_kg().then(function(weight){
-                    if(weight > 0.001){
-                        self.pos_widget.screen_selector.set_current_screen(self.next_screen);
-                    }
-                });
-            },{duration: 100, repeat: true});
+    module.UnsentOrdersPopupWidget = module.ConfirmPopupWidget.extend({
+        template: 'UnsentOrdersPopupWidget',
+    });
 
-            this.add_action_button({
-                    label: _t('Back'),
-                    icon: '/point_of_sale/static/src/img/icons/png48/go-previous.png',
-                    click: function(){  
-                        self.pos_widget.screen_selector.set_current_screen(self.previous_screen);
-                    }
-                });
-        },
-        close: function(){
-            this._super();
-            var self = this;
-            this.pos.proxy_queue.clear();
-            this.pos.proxy_queue.schedule(function(){
-                return self.pos.proxy.weighting_end();
-            },{ important: true });
-        },
+    module.UnpaidOrdersPopupWidget = module.ConfirmPopupWidget.extend({
+        template: 'UnpaidOrdersPopupWidget',
     });
 
     module.ScaleScreenWidget = module.ScreenWidget.extend({
@@ -487,6 +566,8 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
         next_screen: 'products',
         previous_screen: 'products',
 
+        show_leftpane:   false,
+
         show: function(){
             this._super();
             var self = this;
@@ -506,42 +587,22 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
 
             $('body').on('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.pos_widget.screen_selector.set_current_screen(self.previous_screen);
-                    }
-                });
+            this.$('.back').click(function(){
+                self.pos_widget.screen_selector.set_current_screen(self.previous_screen);
+            });
+
+            this.$('.next,.buy-product').click(function(){
+                self.order_product();
+                self.pos_widget.screen_selector.set_current_screen(self.next_screen);
+            });
 
-            this.validate_button = this.add_action_button({
-                    label: _t('Validate'),
-                    icon: '/point_of_sale/static/src/img/icons/png48/validate.png',
-                    click: function(){
-                        self.order_product();
-                        self.pos_widget.screen_selector.set_current_screen(self.next_screen);
-                    },
-                });
-            
-            queue.schedule(function(){
-                return self.pos.proxy.weighting_start()
-            },{ important: true });
-            
             queue.schedule(function(){
-                return self.pos.proxy.weighting_read_kg().then(function(weight){
-                    self.set_weight(weight);
+                return self.pos.proxy.scale_read().then(function(weight){
+                    self.set_weight(weight.weight);
                 });
             },{duration:50, repeat: true});
 
         },
-        renderElement: function(){
-            var self = this;
-            this._super();
-            this.$('.product-picture').click(function(){
-                self.order_product();
-                self.pos_widget.screen_selector.set_current_screen(self.next_screen);
-            });
-        },
         get_product: function(){
             var ss = this.pos_widget.screen_selector;
             if(ss){
@@ -551,22 +612,39 @@ 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.get('name') : undefined) || 'Unnamed Product';
+            return (product ? product.display_name : undefined) || 'Unnamed Product';
         },
         get_product_price: function(){
             var product = this.get_product();
-            return (product ? product.get('price') : 0) || 0;
+            return (product ? product.price : 0) || 0;
         },
         set_weight: function(weight){
             this.weight = weight;
-            this.$('.js-weight').text(this.get_product_weight_string());
+            this.$('.weight').text(this.get_product_weight_string());
+            this.$('.computed-price').text(this.get_computed_price_string());
         },
         get_product_weight_string: function(){
-            return (this.weight || 0).toFixed(3) + ' Kg';
+            var product = this.get_product();
+            var defaultstr = (this.weight || 0).toFixed(3) + ' Kg';
+            if(!product || !this.pos){
+                return defaultstr;
+            }
+            var unit_id = product.uom_id;
+            if(!unit_id){
+                return defaultstr;
+            }
+            var unit = this.pos.units_by_id[unit_id[0]];
+            var weight = round_pr(this.weight || 0, unit.rounding);
+            var weightstr = weight.toFixed(Math.ceil(Math.log(1.0/unit.rounding) / Math.log(10) ));
+                weightstr += ' Kg';
+            return weightstr;
+        },
+        get_computed_price_string: function(){
+            return this.format_currency(this.get_product_price() * this.weight);
         },
         close: function(){
             var self = this;
@@ -574,260 +652,452 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
             $('body').off('keyup',this.hotkey_handler);
 
             this.pos.proxy_queue.clear();
-            this.pos.proxy_queue.schedule(function(){
-                self.pos.proxy.weighting_end();
-            },{ important: true });
         },
     });
 
+    module.ProductScreenWidget = module.ScreenWidget.extend({
+        template:'ProductScreenWidget',
+
+        show_numpad:     true,
+        show_leftpane:   true,
 
-    module.ClientPaymentScreenWidget =  module.ScreenWidget.extend({
-        template:'ClientPaymentScreenWidget',
+        start: function(){ //FIXME this should work as renderElement... but then the categories aren't properly set. explore why
+            var self = this;
 
-        next_screen: 'welcome',
-        previous_screen: 'products',
+            this.product_list_widget = new module.ProductListWidget(this,{
+                click_product_action: function(product){
+                    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_order().add_product(product);
+                    }
+                },
+                product_list: this.pos.db.get_product_by_category(0)
+            });
+            this.product_list_widget.replace(this.$('.placeholder-ProductListWidget'));
+
+            this.product_categories_widget = new module.ProductCategoriesWidget(this,{
+                product_list_widget: this.product_list_widget,
+            });
+            this.product_categories_widget.replace(this.$('.placeholder-ProductCategoriesWidget'));
+        },
 
         show: function(){
             this._super();
             var self = this;
-           
-            this.queue = new module.JobQueue();
-            this.canceled = false;
-            this.paid     = false;
-
-            // initiates the connection to the payment terminal and starts the update requests
-            this.start = function(){
-                var def = new $.Deferred();
-                self.pos.proxy.payment_request(self.pos.get('selectedOrder').getDueLeft())
-                    .done(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);
-                        }
-                        def.resolve();
-                    });
-                return def;
-            };
-            
-            // gets updated status from the payment terminal and performs the appropriate consequences
-            this.update = function(){
-                var def = new $.Deferred();
-                if(self.canceled){
-                    return def.resolve();
-                }
-                self.pos.proxy.payment_status()
-                    .done(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)
-                            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);
-                        }
-                        def.resolve();
-                    });
-                return def;
-            }
-            
-            // cancels a payment.
-            this.cancel = function(){
-                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();
-                }
-                return (new $.Deferred()).resolve();
-            }
-            
-            if(this.pos.get('selectedOrder').getDueLeft() <= 0){
-                this.pos_widget.screen_selector.show_popup('error-negative-price');
-            }else{
-                this.queue.schedule(this.start);
-            }
 
-            this.add_action_button({
-                    label: _t('Back'),
-                    icon: '/point_of_sale/static/src/img/icons/png48/go-previous.png',
-                    click: function(){  
-                       self.queue.schedule(self.cancel);
-                       self.pos_widget.screen_selector.set_current_screen(self.previous_screen);
-                    }
-                });
+            this.product_categories_widget.reset_category();
+
+            this.pos_widget.order_widget.set_editable(true);
         },
+
         close: function(){
-            if(this.queue){
-                this.queue.schedule(this.cancel);
-            }
-            //TODO CANCEL
             this._super();
+
+            this.pos_widget.order_widget.set_editable(false);
+
+            if(this.pos.config.iface_vkeyboard && this.pos_widget.onscreen_keyboard){
+                this.pos_widget.onscreen_keyboard.hide();
+            }
         },
     });
 
-    module.WelcomeScreenWidget = module.ScreenWidget.extend({
-        template:'WelcomeScreenWidget',
+    module.ClientListScreenWidget = module.ScreenWidget.extend({
+        template: 'ClientListScreenWidget',
 
-        next_screen: 'products',
+        init: function(parent, options){
+            this._super(parent, options);
+            this.partner_cache = new module.DomCache();
+        },
 
-        show_numpad:     false,
-        show_leftpane:   false,
-        start: function(){
+        show_leftpane: false,
+
+        auto_back: true,
+
+        show: function(){
+            var self = this;
             this._super();
-            $('.goodbye-message').click(function(){
-                $(this).addClass('oe_hidden');
+
+            this.renderElement();
+            this.details_visible = false;
+            this.old_client = this.pos.get_order().get_client()
+            this.new_client = this.old_client;
+
+            this.$('.back').click(function(){
+                self.pos_widget.screen_selector.back();
+            });
+
+            this.$('.next').click(function(){
+                self.save_changes();
+                self.pos_widget.screen_selector.back();
+            });
+
+            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){
+                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);
+
+                var query = this.value;
+
+                search_timeout = setTimeout(function(){
+                    self.perform_search(query,event.which === 13);
+                },70);
             });
+
+            this.$('.searchbox .search-clear').click(function(){
+                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);
+                this.display_client_details('hide');
+                if ( associate_result && customers.length === 1){
+                    this.new_client = customers[0];
+                    this.save_changes();
+                    this.pos_widget.screen_selector.back();
+                }
+                this.render_list(customers);
+            }else{
+                var customers = this.pos.db.get_partners_sorted();
+                this.render_list(customers);
+            }
+        },
+        clear_search: function(){
+            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.$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.appendChild(clientline);
+            }
+        },
+        save_changes: function(){
+            if( this.has_client_changed() ){
+                this.pos.get_order().set_client(this.new_client);
+            }
+        },
+        has_client_changed: function(){
+            if( this.old_client && this.new_client ){
+                return this.old_client.id !== this.new_client.id;
+            }else{
+                return !!this.old_client !== !!this.new_client;
+            }
+        },
+        toggle_save_button: function(){
+            var $button = this.$('.button.next');
+            if (this.editing_client) {
+                $button.addClass('oe_hidden');
+                return;
+            } else if( this.new_client ){
+                if( !this.old_client){
+                    $button.text(_t('Set Customer'));
+                }else{
+                    $button.text(_t('Change Customer'));
+                }
+            }else{
+                $button.text(_t('Deselect Customer'));
+            }
+            $button.toggleClass('oe_hidden',!this.has_client_changed());
+        },
+        line_select: function(event,$line,id){
+            var partner = this.pos.db.get_partner_by_id(id);
+            this.$('.client-list .lowlight').removeClass('lowlight');
+            if ( $line.hasClass('highlight') ){
+                $line.removeClass('highlight');
+                $line.addClass('lowlight');
+                this.display_client_details('hide',partner);
+                this.new_client = null;
+                this.toggle_save_button();
+            }else{
+                this.$('.client-list .highlight').removeClass('highlight');
+                $line.addClass('highlight');
+                var y = event.pageY - $line.parent().offset().top
+                this.display_client_details('show',partner,y);
+                this.new_client = partner;
+                this.toggle_save_button();
+            }
+        },
+        partner_icon_url: function(id){
+            return '/web/binary/image?model=res.partner&id='+id+'&field=image_small';
         },
 
-        barcode_product_action: function(code){
-            this.pos.proxy.transaction_start();
-            this._super(code);
+        // ui handle for the 'edit selected customer' action
+        edit_client_details: function(partner) {
+            this.display_client_details('edit',partner);
         },
 
-        barcode_client_action: function(code){
-            this.pos.proxy.transaction_start();
-            this._super(code);
-            $('.goodbye-message').addClass('oe_hidden');
-            this.pos_widget.screen_selector.show_popup('choose-receipt');
+        // 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);
+            }
         },
-        
-        show: function(){
-            this._super();
+
+        // 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;
+            });
 
-            this.add_action_button({
-                    label: _t('Help'),
-                    icon: '/point_of_sale/static/src/img/icons/png48/help.png',
-                    click: function(){ 
-                        $('.goodbye-message').css({opacity:1}).addClass('oe_hidden');
-                        self.help_button_action();
-                    },
+            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;
+            }
 
-            $('.goodbye-message').css({opacity:1}).removeClass('oe_hidden');
-            setTimeout(function(){
-                $('.goodbye-message').animate({opacity:0},500,'swing',function(){$('.goodbye-message').addClass('oe_hidden');});
-            },5000);
+            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);
+            },function(err,event){
+                event.preventDefault();
+                self.pos_widget.screen_selector.show_popup('error',{
+                    'message':_t('Error: Could not Save Changes'),
+                    'comment':_t('Your Internet connection is probably down.'),
+                });
+            });
+        },
+        
+        // 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');
+                }
+            });
         },
-    });
-    
-    module.ProductScreenWidget = module.ScreenWidget.extend({
-        template:'ProductScreenWidget',
 
-        scale_screen: 'scale',
-        client_scale_screen : 'scale_invite',
-        client_next_screen:  'client_payment',
+        // 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;
 
-        show_numpad:     true,
-        show_leftpane:   true,
+                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);
 
-        start: function(){ //FIXME this should work as renderElement... but then the categories aren't properly set. explore why
-            var self = this;
-            this.product_categories_widget = new module.ProductCategoriesWidget(this,{});
-            this.product_categories_widget.replace($('.placeholder-ProductCategoriesWidget'));
+                canvas.width  = width;
+                canvas.height = height;
+                ctx.drawImage(img,0,0,width,height);
 
-            this.product_list_widget = new module.ProductListWidget(this,{
-                click_product_action: function(product){
-                    if(product.get('to_weight') && self.pos.iface_electronic_scale){
-                        self.pos_widget.screen_selector.set_current_screen( self.cashier_mode ? self.scale_screen : self.client_scale_screen, {product: product});
-                    }else{
-                        self.pos.get('selectedOrder').addProduct(product);
-                    }
-                },
-            });
-            this.product_list_widget.replace($('.placeholder-ProductListWidget'));
+                var dataurl = canvas.toDataURL();
+                callback(dataurl);
+            }
         },
 
-        show: function(){
-            this._super();
+        // 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.product_categories_widget.reset_category();
+        // 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));
+                }
+            });
+        },
 
-            this.pos_widget.order_widget.set_numpad_state(this.pos_widget.numpad.state);
+        // 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'){
+                contents.empty();
+                contents.append($(QWeb.render('ClientDetails',{widget:this,partner:partner})));
+
+                var new_height   = contents.height();
+
+                if(!this.details_visible){
+                    if(clickpos < scroll + new_height + 20 ){
+                        parent.scrollTop( clickpos - 20 );
+                    }else{
+                        parent.scrollTop(parent.scrollTop() + new_height);
+                    }
+                }else{
+                    parent.scrollTop(parent.scrollTop() - height + new_height);
+                }
 
-            if(this.pos_widget.screen_selector.current_mode === 'client'){ 
-                this.add_action_button({
-                        label: _t('Pay'),
-                        icon: '/point_of_sale/static/src/img/icons/png48/go-next.png',
-                        click: function(){  
-                            self.pos_widget.screen_selector.set_current_screen(self.client_next_screen);
+                this.details_visible = true;
+                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:''});
+                    });
+                }else{
+                    parent.scrollTop( parent.scrollTop() - height);
+                }
+                this.details_visible = false;
+                this.toggle_save_button();
             }
         },
-
         close: function(){
             this._super();
-            this.pos_widget.order_widget.set_numpad_state(null);
-            this.pos_widget.payment_screen.set_numpad_state(null);
-            if(this.pos.iface_vkeyboard && this.pos_widget.onscreen_keyboard){
-                this.pos_widget.onscreen_keyboard.hide();
-            }
         },
-
     });
 
     module.ReceiptScreenWidget = module.ScreenWidget.extend({
         template: 'ReceiptScreenWidget',
+        show_numpad:     false,
+        show_leftpane:   false,
 
-        show_numpad:     true,
-        show_leftpane:   true,
-
-        init: function(parent, options) {
-            this._super(parent,options);
-            this.model = options.model;
-            this.user = this.pos.get('user');
-            this.company = this.pos.get('company');
-            this.shop_obj = this.pos.get('shop');
-        },
-        renderElement: function() {
-            this._super();
-            this.pos.bind('change:selectedOrder', this.change_selected_order, this);
-            this.change_selected_order();
-        },
         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();
+            }
 
-            // THIS IS THE HACK OF THE CENTURY
-            //
             // 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 
@@ -844,266 +1114,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();
-        },
-        change_selected_order: function() {
-            if (this.currentOrderLines)
-                this.currentOrderLines.unbind();
-            this.currentOrderLines = (this.pos.get('selectedOrder')).get('orderLines');
-            this.currentOrderLines.bind('add', this.refresh, this);
-            this.currentOrderLines.bind('change', this.refresh, this);
-            this.currentOrderLines.bind('remove', this.refresh, this);
-            if (this.currentPaymentLines)
-                this.currentPaymentLines.unbind();
-            this.currentPaymentLines = (this.pos.get('selectedOrder')).get('paymentLines');
-            this.currentPaymentLines.bind('all', this.refresh, this);
-            this.refresh();
+        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() {
-            this.currentOrder = this.pos.get('selectedOrder');
-            $('.pos-receipt-container', this.$el).html(QWeb.render('PosTicket',{widget:this}));
+            var order = this.pos.get_order();
+            this.$('.pos-receipt-container').html(QWeb.render('PosTicket',{
+                    widget:this,
+                    order: order,
+                    receipt: order.export_for_printing(),
+                    orderlines: order.get_orderlines(),
+                    paymentlines: order.get_paymentlines(),
+                }));
         },
     });
 
     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) {
-            this._super(parent,options);
-            this.model = options.model;
-            this.pos.bind('change:selectedOrder', this.change_selected_order, this);
-            this.bindPaymentLineEvents();
-            this.bind_orderline_events();
-            this.paymentlinewidgets = [];
-            this.focusedLine = null;
-        },
-        show: function(){
-            this._super();
             var self = this;
-
-            this.hotkey_handler = function(event){
-                if(event.which === 13){
-                    self.validateCurrentOrder();
-                }else if(event.which === 27){
-                    self.back();
+            this._super(parent, options);
+
+            this.pos.bind('change:selectedOrder',function(){
+                    this.renderElement();
+                    this.watch_order_changes();
+                },this);
+            this.watch_order_changes();
+
+            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 = '+';
                 }
-            };
 
-            $('body').on('keyup',this.hotkey_handler);
+                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 + '.';
+                }
+            } 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.pos.iface_cashdrawer && this.pos.get('selectedOrder').get('paymentLines').find(                function(pl){ return pl.cashregister.get('journal').type === 'cash'; })){
-                    this.pos.proxy.open_cashbox();
+            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){
+            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;
+                }
+            }
+        },
+        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;
+                }
+            }
+        },
+        render_paymentlines: function() {
+            var self  = this;
+            var order = this.pos.get_order();
+            if (!order) {
+                return;
             }
 
-            this.set_numpad_state(this.pos_widget.numpad.state);
-            
-            this.add_action_button({
-                    label: _t('Back'),
-                    icon: '/point_of_sale/static/src/img/icons/png48/go-previous.png',
-                    click: function(){  
-                        self.back();
-                    },
-                });
+            var lines = order.get_paymentlines();
+
+            this.$('.paymentlines-container').empty();
+            var lines = $(QWeb.render('PaymentScreen-Paymentlines', { 
+                widget: this, 
+                order: order,
+                paymentlines: lines,
+            }));
+
+            lines.on('click','.delete-button',function(){
+                self.click_delete_paymentline($(this).data('cid'));
+            });
 
-            this.add_action_button({
-                    label: _t('Validate'),
-                    name: 'validation',
-                    icon: '/point_of_sale/static/src/img/icons/png48/validate.png',
-                    click: function(){
-                        self.validateCurrentOrder();
-                    },
+            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();
+        },
+        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'));
                 });
-           
-            if(this.pos.iface_invoicing){
-                this.add_action_button({
-                        label: 'Invoice',
-                        name: 'invoice',
-                        icon: '/point_of_sale/static/src/img/icons/png48/invoice.png',
-                        click: function(){
-                            self.validateCurrentOrder({invoice: true});
-                        },
-                    });
+            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');
             }
+        },
+        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();
 
-            if( this.pos.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 = this.render_numpad();
+            numpad.appendTo(this.$('.payment-numpad'));
+
+            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();
+            });
 
-            this.updatePaymentSummary();
-            this.line_refocus();
         },
-        close: function(){
+        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();
-            this.pos_widget.order_widget.set_numpad_state(null);
-            this.pos_widget.payment_screen.set_numpad_state(null);
-            $('body').off('keyup',this.hotkey_handler);
         },
-        back: function() {
-            _.each(this.paymentlinewidgets, function(widget){
-                if( widget.payment_line.get_amount() === 0 ){
-                    widget.payment_line.destroy();
-                }
+        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;
+            var order = this.pos.get_order();
+            if (!order) {
+                return;
+            }
+            if(this.old_order){
+                this.old_order.unbind(null,null,this);
+            }
+            order.bind('all',function(){
+                self.order_changes();
             });
-            this.pos_widget.screen_selector.set_current_screen(this.back_screen);
+            this.old_order = order;
         },
-        validateCurrentOrder: function(options) {
+        // 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');
+            }
+        },
+        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));
+        },
+
+        // 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(currentOrder.getPaidTotal() + 0.000001 < currentOrder.getTotalTaxIncluded()){
+            // 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(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() || this.invoicing) {
+                return;
+            }
 
-                var invoiced = this.pos.push_and_invoice_order(currentOrder);
+            // 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 (order.is_paid_with_cash() && this.pos.config.iface_cashdrawer) { 
+
+                    this.pos.proxy.open_cashbox();
+            }
+
+            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.iface_print_via_proxy){
-                    this.pos.proxy.print_receipt(currentOrder.export_for_printing());
-                    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);
                 }
             }
         },
-        bindPaymentLineEvents: function() {
-            this.currentPaymentLines = (this.pos.get('selectedOrder')).get('paymentLines');
-            this.currentPaymentLines.bind('add', this.addPaymentLine, this);
-            this.currentPaymentLines.bind('remove', this.renderElement, this);
-            this.currentPaymentLines.bind('all', this.updatePaymentSummary, this);
-        },
-        bind_orderline_events: function() {
-            this.currentOrderLines = (this.pos.get('selectedOrder')).get('orderLines');
-            this.currentOrderLines.bind('all', this.updatePaymentSummary, this);
-        },
-        change_selected_order: function() {
-            this.currentPaymentLines.unbind();
-            this.bindPaymentLineEvents();
-            this.currentOrderLines.unbind();
-            this.bind_orderline_events();
-            this.renderElement();
-        },
-        line_refocus: function(lineWidget){
-            if(lineWidget){
-                if(this.focusedLine !== lineWidget){
-                    this.focusedLine = lineWidget;
-                }
-            }
-            if(this.focusedLine){
-                this.focusedLine.focus();
-            }
-        },
-        addPaymentLine: function(newPaymentLine) {
-            var self = this;
-            var l = new module.PaymentlineWidget(this, {
-                    payment_line: newPaymentLine,
-            });
-            l.on('delete_payment_line', self, function(r) {
-                self.deleteLine(r);
-            });
-            l.appendTo(this.$('.paymentlines'));
-            this.paymentlinewidgets.push(l);
-            if(this.numpadState){
-                this.numpadState.resetValue();
-            }
-            this.line_refocus(l);
-        },
-        renderElement: function() {
-            this._super();
-            this.$('.paymentlines').empty();
-            for(var i = 0, len = this.paymentlinewidgets.length; i < len; i++){
-                this.paymentlinewidgets[i].destroy();
-            }
-            this.paymentlinewidgets = [];
-            
-            this.currentPaymentLines.each(_.bind( function(paymentLine) {
-                this.addPaymentLine(paymentLine);
-            }, this));
-            this.updatePaymentSummary();
-        },
-        deleteLine: function(lineWidget) {
-               this.currentPaymentLines.remove([lineWidget.payment_line]);
-            lineWidget.destroy();
-        },
-        updatePaymentSummary: 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', remaining > 0.000001);
-                this.pos_widget.action_bar.set_button_disabled('invoice', remaining > 0.000001);
-            }
-        },
-        set_numpad_state: function(numpadState) {
-               if (this.numpadState) {
-                       this.numpadState.unbind('set_value', this.set_value);
-                       this.numpadState.unbind('change:mode', this.setNumpadMode);
-               }
-               this.numpadState = numpadState;
-               if (this.numpadState) {
-                       this.numpadState.bind('set_value', this.set_value, this);
-                       this.numpadState.bind('change:mode', this.setNumpadMode, this);
-                       this.numpadState.reset();
-                       this.setNumpadMode();
-               }
-        },
-       setNumpadMode: function() {
-               this.numpadState.set({mode: 'payment'});
-       },
-        set_value: function(val) {
-               this.currentPaymentLines.last().set_amount(val);
-        },
     });
+
 }
+