[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
1
2 // this file contains the screens definitions. Screens are the
3 // content of the right pane of the pos, containing the main functionalities. 
4 // screens are contained in the PosWidget, in pos_widget.js
5 // all screens are present in the dom at all time, but only one is shown at the
6 // same time. 
7 //
8 // transition between screens is made possible by the use of the screen_selector,
9 // which is responsible of hiding and showing the screens, as well as maintaining
10 // the state of the screens between different orders.
11 //
12 // all screens inherit from ScreenWidget. the only addition from the base widgets
13 // are show() and hide() which shows and hides the screen but are also used to 
14 // bind and unbind actions on widgets and devices. The screen_selector guarantees
15 // that only one screen is shown at the same time and that show() is called after all
16 // hide()s
17
18 openerp.point_of_sale.load_screens = function load_screens(instance, module){ //module is instance.point_of_sale
19     "use strict";
20
21     var QWeb = instance.web.qweb,
22     _t = instance.web._t;
23
24     var round_pr = instance.web.round_precision
25
26     module.ScreenSelector = instance.web.Class.extend({
27         init: function(options){
28             this.pos = options.pos;
29             this.screen_set     = options.screen_set || {};
30             this.popup_set      = options.popup_set || {};
31             this.default_screen = options.default_screen;
32             this.startup_screen = options.startup_screen;
33             this.current_popup  = null;
34             this.current_mode   = options.default_mode || 'cashier';
35             this.current_screen = null; 
36
37             for (var screen_name in this.screen_set) {
38                 this.screen_set[screen_name].hide();
39             }
40             
41             for (var popup_name in this.popup_set) {
42                 this.popup_set[popup_name].hide();
43             }
44
45             if (this.pos.get_order()) {
46                 this.pos.get_order().set_screen_data({
47                     'screen': this.default_screen,
48                 });
49             }
50
51             this.pos.bind('change:selectedOrder', this.load_saved_screen, this);
52         },
53         add_screen: function(screen_name, screen){
54             screen.hide();
55             this.screen_set[screen_name] = screen;
56             return this;
57         },
58         show_popup: function(name,options){
59             if(this.current_popup){
60                 this.close_popup();
61             }
62             this.current_popup = this.popup_set[name];
63             this.current_popup.show(options);
64         },
65         close_popup: function(){
66             if(this.current_popup){
67                 this.current_popup.close();
68                 this.current_popup.hide();
69                 this.current_popup = null;
70             }
71         },
72         load_saved_screen:  function(options){
73             options = options || {};
74             this.close_popup();
75             var selectedOrder = this.pos.get_order();
76             // FIXME : this changing screen behaviour is sometimes confusing ... 
77             this.set_current_screen(selectedOrder.get_screen_data('screen') || options.default_screen || this.default_screen,null,'refresh');
78             //this.set_current_screen(this.default_screen,null,'refresh');
79             
80         },
81         set_user_mode: function(user_mode){
82             if(user_mode !== this.current_mode){
83                 this.close_popup();
84                 this.current_mode = user_mode;
85                 this.load_saved_screen();
86             }
87         },
88         get_user_mode: function(){
89             return this.current_mode;
90         },
91         set_current_screen: function(screen_name,params,refresh){
92             var screen = this.screen_set[screen_name];
93             if(!screen){
94                 console.error("ERROR: set_current_screen("+screen_name+") : screen not found");
95             }
96
97             this.close_popup();
98
99             var order = this.pos.get_order();
100             if (order) {
101                 var old_screen_name = order.get_screen_data('screen');
102
103                 order.set_screen_data('screen',screen_name);
104
105                 if(params){
106                     order.set_screen_data('params',params);
107                 }
108
109                 if( screen_name !== old_screen_name ){
110                     order.set_screen_data('previous-screen',old_screen_name);
111                 }
112             }
113
114             if ( refresh || screen !== this.current_screen){
115                 if(this.current_screen){
116                     this.current_screen.close();
117                     this.current_screen.hide();
118                 }
119                 this.current_screen = screen;
120                 this.current_screen.show();
121             }
122         },
123         get_current_screen: function(){
124             return this.pos.get_order().get_screen_data('screen') || this.default_screen;
125         },
126         back: function(){
127             var previous = this.pos.get_order().get_screen_data('previous-screen');
128             if(previous){
129                 this.set_current_screen(previous);
130             }
131         },
132         get_current_screen_param: function(param){
133             var params = this.pos.get_order().get_screen_data('params');
134             return params ? params[param] : undefined;
135         },
136         set_default_screen: function(){
137             this.set_current_screen(this.default_screen);
138         },
139         change_default_screen: function(screen){ 
140             this.default_screen = screen;
141         },
142     });
143
144     module.ScreenWidget = module.PosBaseWidget.extend({
145
146         show_numpad:     true,  
147         show_leftpane:   true,
148
149         init: function(parent,options){
150             this._super(parent,options);
151             this.hidden = false;
152         },
153
154         help_button_action: function(){
155             this.pos_widget.screen_selector.show_popup('help');
156         },
157
158         barcode_product_screen:         'products',     //if defined, this screen will be loaded when a product is scanned
159
160         hotkeys_handlers: {},
161
162         // what happens when a product is scanned : 
163         // it will add the product to the order and go to barcode_product_screen. 
164         barcode_product_action: function(code){
165             var self = this;
166             if(self.pos.scan_product(code)){
167                 if(self.barcode_product_screen){ 
168                     self.pos_widget.screen_selector.set_current_screen(self.barcode_product_screen);
169                 }
170             }else{
171                 self.pos_widget.screen_selector.show_popup('error-barcode',code.code);
172             }
173         },
174
175         // what happens when a cashier id barcode is scanned.
176         // the default behavior is the following : 
177         // - if there's a user with a matching barcode, put it as the active 'cashier', go to cashier mode, and return true
178         // - else : do nothing and return false. You probably want to extend this to show and appropriate error popup... 
179         barcode_cashier_action: function(code){
180             var users = this.pos.users;
181             for(var i = 0, len = users.length; i < len; i++){
182                 if(users[i].barcode === code.code){
183                     this.pos.set_cashier(users[i]);
184                     this.pos_widget.username.renderElement();
185                     return true;
186                 }
187             }
188             this.pos_widget.screen_selector.show_popup('error-barcode',code.code);
189             return false;
190         },
191         
192         // what happens when a client id barcode is scanned.
193         // the default behavior is the following : 
194         // - if there's a user with a matching barcode, put it as the active 'client' and return true
195         // - else : return false. 
196         barcode_client_action: function(code){
197             var partner = this.pos.db.get_partner_by_barcode(code.code);
198             if(partner){
199                 this.pos.get_order().set_client(partner);
200                 return true;
201             }
202             this.pos_widget.screen_selector.show_popup('error-barcode',code.code);
203             return false;
204         },
205         
206         // what happens when a discount barcode is scanned : the default behavior
207         // is to set the discount on the last order.
208         barcode_discount_action: function(code){
209             var last_orderline = this.pos.get_order().get_last_orderline();
210             if(last_orderline){
211                 last_orderline.set_discount(code.value)
212             }
213         },
214         // What happens when an invalid barcode is scanned : shows an error popup.
215         barcode_error_action: function(code){
216             this.pos_widget.screen_selector.show_popup('error-barcode',code.code);
217         },
218
219         // this method shows the screen and sets up all the widget related to this screen. Extend this method
220         // if you want to alter the behavior of the screen.
221         show: function(){
222             var self = this;
223
224             this.hidden = false;
225             if(this.$el){
226                 this.$el.removeClass('oe_hidden');
227             }
228
229             var self = this;
230
231             this.pos_widget.set_numpad_visible(this.show_numpad);
232             this.pos_widget.set_leftpane_visible(this.show_leftpane);
233
234             this.pos_widget.username.set_user_mode(this.pos_widget.screen_selector.get_user_mode());
235             this.pos.barcode_reader.set_action_callback({
236                 'cashier': self.barcode_cashier_action ? function(code){ self.barcode_cashier_action(code); } : undefined ,
237                 'product': self.barcode_product_action ? function(code){ self.barcode_product_action(code); } : undefined ,
238                 'client' : self.barcode_client_action ?  function(code){ self.barcode_client_action(code);  } : undefined ,
239                 'discount': self.barcode_discount_action ? function(code){ self.barcode_discount_action(code); } : undefined,
240                 'error'   : self.barcode_error_action ?  function(code){ self.barcode_error_action(code);   } : undefined,
241             });
242         },
243
244         // this method is called when the screen is closed to make place for a new screen. this is a good place
245         // to put your cleanup stuff as it is guaranteed that for each show() there is one and only one close()
246         close: function(){
247             if(this.pos.barcode_reader){
248                 this.pos.barcode_reader.reset_action_callbacks();
249             }
250         },
251
252         // this methods hides the screen. It's not a good place to put your cleanup stuff as it is called on the
253         // POS initialization.
254         hide: function(){
255             this.hidden = true;
256             if(this.$el){
257                 this.$el.addClass('oe_hidden');
258             }
259         },
260
261         // we need this because some screens re-render themselves when they are hidden
262         // (due to some events, or magic, or both...)  we must make sure they remain hidden.
263         // the good solution would probably be to make them not re-render themselves when they
264         // are hidden. 
265         renderElement: function(){
266             this._super();
267             if(this.hidden){
268                 if(this.$el){
269                     this.$el.addClass('oe_hidden');
270                 }
271             }
272         },
273     });
274
275     module.PopUpWidget = module.PosBaseWidget.extend({
276         show: function(){
277             if(this.$el){
278                 this.$el.removeClass('oe_hidden');
279             }
280         },
281         /* called before hide, when a popup is closed */
282         close: function(){
283         },
284         /* hides the popup. keep in mind that this is called in the initialization pass of the 
285          * pos instantiation, so you don't want to do anything fancy in here */
286         hide: function(){
287             if(this.$el){
288                 this.$el.addClass('oe_hidden');
289             }
290         },
291     });
292
293     module.FullscreenPopup = module.PopUpWidget.extend({
294         template:'FullscreenPopupWidget',
295         show: function(){
296             var self = this;
297             this._super();
298             this.renderElement();
299             this.$('.button.fullscreen').off('click').click(function(){
300                 window.document.body.webkitRequestFullscreen();
301                 self.pos_widget.screen_selector.close_popup();
302             });
303             this.$('.button.cancel').off('click').click(function(){
304                 self.pos_widget.screen_selector.close_popup();
305             });
306         },
307         ismobile: function(){
308             return typeof window.orientation !== 'undefined'; 
309         }
310     });
311
312
313     module.ErrorPopupWidget = module.PopUpWidget.extend({
314         template:'ErrorPopupWidget',
315         show: function(options){
316             options = options || {};
317             var self = this;
318             this._super();
319
320             $('body').append('<audio src="/point_of_sale/static/src/sounds/error.wav" autoplay="true"></audio>');
321
322             this.message = options.message || _t('Error');
323             this.comment = options.comment || '';
324
325             this.renderElement();
326
327             this.pos.barcode_reader.save_callbacks();
328             this.pos.barcode_reader.reset_action_callbacks();
329
330             this.$('.footer .button').click(function(){
331                 self.pos_widget.screen_selector.close_popup();
332                 if ( options.confirm ) {
333                     options.confirm.call(self);
334                 }
335             });
336         },
337         close:function(){
338             this._super();
339             this.pos.barcode_reader.restore_callbacks();
340         },
341     });
342
343     module.ErrorTracebackPopupWidget = module.ErrorPopupWidget.extend({
344         template:'ErrorTracebackPopupWidget',
345     });
346
347     module.ErrorBarcodePopupWidget = module.ErrorPopupWidget.extend({
348         template:'ErrorBarcodePopupWidget',
349         show: function(barcode){
350             this.barcode = barcode;
351             this._super();
352         },
353     });
354
355     module.ConfirmPopupWidget = module.PopUpWidget.extend({
356         template: 'ConfirmPopupWidget',
357         show: function(options){
358             options = options || {};
359             var self = this;
360             this._super();
361
362             this.message = options.message || '';
363             this.comment = options.comment || '';
364             this.renderElement();
365             
366             this.$('.button.cancel').click(function(){
367                 self.pos_widget.screen_selector.close_popup();
368                 if( options.cancel ){
369                     options.cancel.call(self);
370                 }
371             });
372
373             this.$('.button.confirm').click(function(){
374                 self.pos_widget.screen_selector.close_popup();
375                 if( options.confirm ){
376                     options.confirm.call(self);
377                 }
378             });
379         },
380     });
381
382     /**
383      * A popup that allows the user to select one item from a list. 
384      *
385      * show_popup('selection',{
386      *  message: 'Pick an Option',
387      *      message: "Popup Title",
388      *      list: [
389      *          { label: 'foobar',  item: 45 },
390      *          { label: 'bar foo', item: 'stuff' },
391      *      ],
392      *      confirm: function(item) {
393      *          // get the item selected by the user.
394      *      },
395      *      cancel: function(){
396      *          // user chose nothing
397      *      }
398      *  });
399      */
400
401     module.SelectionPopupWidget = module.PopUpWidget.extend({
402         template: 'SelectionPopupWidget',
403         show: function(options){
404             options = options || {};
405             var self = this;
406             this._super();
407
408             this.message = options.message || '';
409             this.list    = options.list    || [];
410             this.renderElement();
411
412             this.$('.button.cancel').click(function(){
413                 self.pos_widget.screen_selector.close_popup();
414                 if (options.cancel){
415                     options.cancel.call(self);
416                 }
417             });
418
419             this.$('.selection-item').click(function(){
420                 self.pos_widget.screen_selector.close_popup();
421                 if (options.confirm) {
422                     var item = self.list[parseInt($(this).data('item-index'))];
423                     item = item ? item.item : item;
424                     options.confirm.call(self,item);
425                 }
426             });
427         },
428     });
429
430     module.TextInputPopupWidget = module.PopUpWidget.extend({
431         template: 'TextInputPopupWidget',
432         show: function(options){
433             options = options || {};
434             var self = this;
435             this._super();
436
437             this.message = options.message || '';
438             this.comment = options.comment || '';
439             this.value   = options.value   || '';
440             this.renderElement();
441             this.$('input,textarea').focus();
442             
443             this.$('.button.cancel').click(function(){
444                 self.pos_widget.screen_selector.close_popup();
445                 if( options.cancel ){
446                     options.cancel.call(self);
447                 }
448             });
449
450             this.$('.button.confirm').click(function(){
451                 self.pos_widget.screen_selector.close_popup();
452                 var value = self.$('input,textarea').val();
453                 if( options.confirm ){
454                     options.confirm.call(self,value);
455                 }
456             });
457         },
458     });
459
460     module.TextAreaPopupWidget = module.TextInputPopupWidget.extend({
461         template: 'TextAreaPopupWidget',
462     });
463
464     module.NumberPopupWidget = module.PopUpWidget.extend({
465         template: 'NumberPopupWidget',
466         click_numpad_button: function($el,event){
467             this.numpad_input($el.data('action'));
468         },
469         numpad_input: function(input) { //FIXME -> Deduplicate code
470             var oldbuf = this.inputbuffer.slice(0);
471
472             if (input === '.') {
473                 if (this.firstinput) {
474                     this.inputbuffer = "0.";
475                 }else if (!this.inputbuffer.length || this.inputbuffer === '-') {
476                     this.inputbuffer += "0.";
477                 } else if (this.inputbuffer.indexOf('.') < 0){
478                     this.inputbuffer = this.inputbuffer + '.';
479                 }
480             } else if (input === 'CLEAR') {
481                 this.inputbuffer = ""; 
482             } else if (input === 'BACKSPACE') { 
483                 this.inputbuffer = this.inputbuffer.substring(0,this.inputbuffer.length - 1);
484             } else if (input === '+') {
485                 if ( this.inputbuffer[0] === '-' ) {
486                     this.inputbuffer = this.inputbuffer.substring(1,this.inputbuffer.length);
487                 }
488             } else if (input === '-') {
489                 if ( this.inputbuffer[0] === '-' ) {
490                     this.inputbuffer = this.inputbuffer.substring(1,this.inputbuffer.length);
491                 } else {
492                     this.inputbuffer = '-' + this.inputbuffer;
493                 }
494             } else if (input[0] === '+' && !isNaN(parseFloat(input))) {
495                 this.inputbuffer = '' + ((parseFloat(this.inputbuffer) || 0) + parseFloat(input));
496             } else if (!isNaN(parseInt(input))) {
497                 if (this.firstinput) {
498                     this.inputbuffer = '' + input;
499                 } else {
500                     this.inputbuffer += input;
501                 }
502             }
503
504             this.firstinput = this.inputbuffer.length === 0;
505
506             if (this.inputbuffer !== oldbuf) {
507                 this.$('.value').text(this.inputbuffer);
508             }
509         },
510         show: function(options){
511             options = options || {};
512             var self = this;
513             this._super();
514
515             this.message = options.message || '';
516             this.comment = options.comment || '';
517             this.inputbuffer = options.value   || '';
518             this.renderElement();
519             this.firstinput = true;
520             
521             this.$('.input-button,.mode-button').click(function(event){
522                 self.click_numpad_button($(this),event);
523             });
524             this.$('.button.cancel').click(function(){
525                 self.pos_widget.screen_selector.close_popup();
526                 if( options.cancel ){
527                     options.cancel.call(self);
528                 }
529             });
530
531             this.$('.button.confirm').click(function(){
532                 self.pos_widget.screen_selector.close_popup();
533                 if( options.confirm ){
534                     options.confirm.call(self,self.inputbuffer);
535                 }
536             });
537         },
538     });
539
540     module.PasswordPopupWidget = module.NumberPopupWidget.extend({
541         renderElement: function(){
542             this._super();
543             this.$('.popup').addClass('popup-password');    // HELLO HACK !
544         },
545     });
546
547     module.ErrorNoClientPopupWidget = module.ErrorPopupWidget.extend({
548         template: 'ErrorNoClientPopupWidget',
549     });
550
551     module.ErrorInvoiceTransferPopupWidget = module.ErrorPopupWidget.extend({
552         template: 'ErrorInvoiceTransferPopupWidget',
553     });
554
555     module.UnsentOrdersPopupWidget = module.ConfirmPopupWidget.extend({
556         template: 'UnsentOrdersPopupWidget',
557     });
558
559     module.UnpaidOrdersPopupWidget = module.ConfirmPopupWidget.extend({
560         template: 'UnpaidOrdersPopupWidget',
561     });
562
563     module.ScaleScreenWidget = module.ScreenWidget.extend({
564         template:'ScaleScreenWidget',
565
566         next_screen: 'products',
567         previous_screen: 'products',
568
569         show_leftpane:   false,
570
571         show: function(){
572             this._super();
573             var self = this;
574             var queue = this.pos.proxy_queue;
575
576             this.set_weight(0);
577             this.renderElement();
578
579             this.hotkey_handler = function(event){
580                 if(event.which === 13){
581                     self.order_product();
582                     self.pos_widget.screen_selector.set_current_screen(self.next_screen);
583                 }else if(event.which === 27){
584                     self.pos_widget.screen_selector.set_current_screen(self.previous_screen);
585                 }
586             };
587
588             $('body').on('keyup',this.hotkey_handler);
589
590             this.$('.back').click(function(){
591                 self.pos_widget.screen_selector.set_current_screen(self.previous_screen);
592             });
593
594             this.$('.next,.buy-product').click(function(){
595                 self.order_product();
596                 self.pos_widget.screen_selector.set_current_screen(self.next_screen);
597             });
598
599             queue.schedule(function(){
600                 return self.pos.proxy.scale_read().then(function(weight){
601                     self.set_weight(weight.weight);
602                 });
603             },{duration:50, repeat: true});
604
605         },
606         get_product: function(){
607             var ss = this.pos_widget.screen_selector;
608             if(ss){
609                 return ss.get_current_screen_param('product');
610             }else{
611                 return undefined;
612             }
613         },
614         order_product: function(){
615             this.pos.get_order().add_product(this.get_product(),{ quantity: this.weight });
616         },
617         get_product_name: function(){
618             var product = this.get_product();
619             return (product ? product.display_name : undefined) || 'Unnamed Product';
620         },
621         get_product_price: function(){
622             var product = this.get_product();
623             return (product ? product.price : 0) || 0;
624         },
625         set_weight: function(weight){
626             this.weight = weight;
627             this.$('.weight').text(this.get_product_weight_string());
628             this.$('.computed-price').text(this.get_computed_price_string());
629         },
630         get_product_weight_string: function(){
631             var product = this.get_product();
632             var defaultstr = (this.weight || 0).toFixed(3) + ' Kg';
633             if(!product || !this.pos){
634                 return defaultstr;
635             }
636             var unit_id = product.uom_id;
637             if(!unit_id){
638                 return defaultstr;
639             }
640             var unit = this.pos.units_by_id[unit_id[0]];
641             var weight = round_pr(this.weight || 0, unit.rounding);
642             var weightstr = weight.toFixed(Math.ceil(Math.log(1.0/unit.rounding) / Math.log(10) ));
643                 weightstr += ' Kg';
644             return weightstr;
645         },
646         get_computed_price_string: function(){
647             return this.format_currency(this.get_product_price() * this.weight);
648         },
649         close: function(){
650             var self = this;
651             this._super();
652             $('body').off('keyup',this.hotkey_handler);
653
654             this.pos.proxy_queue.clear();
655         },
656     });
657
658     module.ProductScreenWidget = module.ScreenWidget.extend({
659         template:'ProductScreenWidget',
660
661         show_numpad:     true,
662         show_leftpane:   true,
663
664         start: function(){ //FIXME this should work as renderElement... but then the categories aren't properly set. explore why
665             var self = this;
666
667             this.product_list_widget = new module.ProductListWidget(this,{
668                 click_product_action: function(product){
669                     if(product.to_weight && self.pos.config.iface_electronic_scale){
670                         self.pos_widget.screen_selector.set_current_screen('scale',{product: product});
671                     }else{
672                         self.pos.get_order().add_product(product);
673                     }
674                 },
675                 product_list: this.pos.db.get_product_by_category(0)
676             });
677             this.product_list_widget.replace(this.$('.placeholder-ProductListWidget'));
678
679             this.product_categories_widget = new module.ProductCategoriesWidget(this,{
680                 product_list_widget: this.product_list_widget,
681             });
682             this.product_categories_widget.replace(this.$('.placeholder-ProductCategoriesWidget'));
683         },
684
685         show: function(){
686             this._super();
687             var self = this;
688
689             this.product_categories_widget.reset_category();
690
691             this.pos_widget.order_widget.set_editable(true);
692         },
693
694         close: function(){
695             this._super();
696
697             this.pos_widget.order_widget.set_editable(false);
698
699             if(this.pos.config.iface_vkeyboard && this.pos_widget.onscreen_keyboard){
700                 this.pos_widget.onscreen_keyboard.hide();
701             }
702         },
703     });
704
705     module.ClientListScreenWidget = module.ScreenWidget.extend({
706         template: 'ClientListScreenWidget',
707
708         init: function(parent, options){
709             this._super(parent, options);
710             this.partner_cache = new module.DomCache();
711         },
712
713         show_leftpane: false,
714
715         auto_back: true,
716
717         show: function(){
718             var self = this;
719             this._super();
720
721             this.renderElement();
722             this.details_visible = false;
723             this.old_client = this.pos.get_order().get_client()
724             this.new_client = this.old_client;
725
726             this.$('.back').click(function(){
727                 self.pos_widget.screen_selector.back();
728             });
729
730             this.$('.next').click(function(){
731                 self.save_changes();
732                 self.pos_widget.screen_selector.back();
733             });
734
735             this.$('.new-customer').click(function(){
736                 self.display_client_details('edit',{
737                     'country_id': self.pos.company.country_id,
738                 });
739             });
740
741             var partners = this.pos.db.get_partners_sorted(1000);
742             this.render_list(partners);
743             
744             this.reload_partners();
745
746             if( this.old_client ){
747                 this.display_client_details('show',this.old_client,0);
748             }
749
750             this.$('.client-list-contents').delegate('.client-line','click',function(event){
751                 self.line_select(event,$(this),parseInt($(this).data('id')));
752             });
753
754             var search_timeout = null;
755
756             if(this.pos.config.iface_vkeyboard && this.pos_widget.onscreen_keyboard){
757                 this.pos_widget.onscreen_keyboard.connect(this.$('.searchbox input'));
758             }
759
760             this.$('.searchbox input').on('keyup',function(event){
761                 clearTimeout(search_timeout);
762
763                 var query = this.value;
764
765                 search_timeout = setTimeout(function(){
766                     self.perform_search(query,event.which === 13);
767                 },70);
768             });
769
770             this.$('.searchbox .search-clear').click(function(){
771                 self.clear_search();
772             });
773         },
774         barcode_client_action: function(code){
775             if (this.editing_client) {
776                 this.$('.detail.barcode').val(code.code);
777             } else if (this.pos.db.get_partner_by_barcode(code.code)) {
778                 this.display_client_details('show',this.pos.db.get_partner_by_barcode(code.code));
779             }
780         },
781         perform_search: function(query, associate_result){
782             if(query){
783                 var customers = this.pos.db.search_partner(query);
784                 this.display_client_details('hide');
785                 if ( associate_result && customers.length === 1){
786                     this.new_client = customers[0];
787                     this.save_changes();
788                     this.pos_widget.screen_selector.back();
789                 }
790                 this.render_list(customers);
791             }else{
792                 var customers = this.pos.db.get_partners_sorted();
793                 this.render_list(customers);
794             }
795         },
796         clear_search: function(){
797             var customers = this.pos.db.get_partners_sorted(1000);
798             this.render_list(customers);
799             this.$('.searchbox input')[0].value = '';
800             this.$('.searchbox input').focus();
801         },
802         render_list: function(partners){
803             var contents = this.$el[0].querySelector('.client-list-contents');
804             contents.innerHTML = "";
805             for(var i = 0, len = Math.min(partners.length,1000); i < len; i++){
806                 var partner    = partners[i];
807                 var clientline = this.partner_cache.get_node(partner.id);
808                 if(!clientline){
809                     var clientline_html = QWeb.render('ClientLine',{widget: this, partner:partners[i]});
810                     var clientline = document.createElement('tbody');
811                     clientline.innerHTML = clientline_html;
812                     clientline = clientline.childNodes[1];
813                     this.partner_cache.cache_node(partner.id,clientline);
814                 }
815                 if( partners === this.new_client ){
816                     clientline.classList.add('highlight');
817                 }else{
818                     clientline.classList.remove('highlight');
819                 }
820                 contents.appendChild(clientline);
821             }
822         },
823         save_changes: function(){
824             if( this.has_client_changed() ){
825                 this.pos.get_order().set_client(this.new_client);
826             }
827         },
828         has_client_changed: function(){
829             if( this.old_client && this.new_client ){
830                 return this.old_client.id !== this.new_client.id;
831             }else{
832                 return !!this.old_client !== !!this.new_client;
833             }
834         },
835         toggle_save_button: function(){
836             var $button = this.$('.button.next');
837             if (this.editing_client) {
838                 $button.addClass('oe_hidden');
839                 return;
840             } else if( this.new_client ){
841                 if( !this.old_client){
842                     $button.text(_t('Set Customer'));
843                 }else{
844                     $button.text(_t('Change Customer'));
845                 }
846             }else{
847                 $button.text(_t('Deselect Customer'));
848             }
849             $button.toggleClass('oe_hidden',!this.has_client_changed());
850         },
851         line_select: function(event,$line,id){
852             var partner = this.pos.db.get_partner_by_id(id);
853             this.$('.client-list .lowlight').removeClass('lowlight');
854             if ( $line.hasClass('highlight') ){
855                 $line.removeClass('highlight');
856                 $line.addClass('lowlight');
857                 this.display_client_details('hide',partner);
858                 this.new_client = null;
859                 this.toggle_save_button();
860             }else{
861                 this.$('.client-list .highlight').removeClass('highlight');
862                 $line.addClass('highlight');
863                 var y = event.pageY - $line.parent().offset().top
864                 this.display_client_details('show',partner,y);
865                 this.new_client = partner;
866                 this.toggle_save_button();
867             }
868         },
869         partner_icon_url: function(id){
870             return '/web/binary/image?model=res.partner&id='+id+'&field=image_small';
871         },
872
873         // ui handle for the 'edit selected customer' action
874         edit_client_details: function(partner) {
875             this.display_client_details('edit',partner);
876         },
877
878         // ui handle for the 'cancel customer edit changes' action
879         undo_client_details: function(partner) {
880             if (!partner.id) {
881                 this.display_client_details('hide');
882             } else {
883                 this.display_client_details('show',partner);
884             }
885         },
886
887         // what happens when we save the changes on the client edit form -> we fetch the fields, sanitize them,
888         // send them to the backend for update, and call saved_client_details() when the server tells us the
889         // save was successfull.
890         save_client_details: function(partner) {
891             var self = this;
892             
893             var fields = {}
894             this.$('.client-details-contents .detail').each(function(idx,el){
895                 fields[el.name] = el.value;
896             });
897
898             if (!fields.name) {
899                 this.pos_widget.screen_selector.show_popup('error',{
900                     message: _t('A Customer Name Is Required'),
901                 });
902                 return;
903             }
904             
905             if (this.uploaded_picture) {
906                 fields.image = this.uploaded_picture;
907             }
908
909             fields.id           = partner.id || false;
910             fields.country_id   = fields.country_id || false;
911             fields.barcode        = fields.barcode ? this.pos.barcode_reader.sanitize_ean(fields.barcode) : false; 
912
913             new instance.web.Model('res.partner').call('create_from_ui',[fields]).then(function(partner_id){
914                 self.saved_client_details(partner_id);
915             });
916         },
917         
918         // what happens when we've just pushed modifications for a partner of id partner_id
919         saved_client_details: function(partner_id){
920             var self = this;
921             this.reload_partners().then(function(){
922                 var partner = self.pos.db.get_partner_by_id(partner_id);
923                 if (partner) {
924                     self.new_client = partner;
925                     self.toggle_save_button();
926                     self.display_client_details('show',partner);
927                 } else {
928                     // should never happen, because create_from_ui must return the id of the partner it
929                     // has created, and reload_partner() must have loaded the newly created partner. 
930                     self.display_client_details('hide');
931                 }
932             });
933         },
934
935         // resizes an image, keeping the aspect ratio intact,
936         // the resize is useful to avoid sending 12Mpixels jpegs
937         // over a wireless connection.
938         resize_image_to_dataurl: function(img, maxwidth, maxheight, callback){
939             img.onload = function(){
940                 var png = new Image();
941                 var canvas = document.createElement('canvas');
942                 var ctx    = canvas.getContext('2d');
943                 var ratio  = 1;
944
945                 if (img.width > maxwidth) {
946                     ratio = maxwidth / img.width;
947                 }
948                 if (img.height * ratio > maxheight) {
949                     ratio = maxheight / img.height;
950                 }
951                 var width  = Math.floor(img.width * ratio);
952                 var height = Math.floor(img.height * ratio);
953
954                 canvas.width  = width;
955                 canvas.height = height;
956                 ctx.drawImage(img,0,0,width,height);
957
958                 var dataurl = canvas.toDataURL();
959                 callback(dataurl);
960             }
961         },
962
963         // Loads and resizes a File that contains an image.
964         // callback gets a dataurl in case of success.
965         load_image_file: function(file, callback){
966             var self = this;
967             if (!file.type.match(/image.*/)) {
968                 this.pos_widget.screen_selector.show_popup('error',{
969                     message:_t('Unsupported File Format'),
970                     comment:_t('Only web-compatible Image formats such as .png or .jpeg are supported'),
971                 });
972                 return;
973             }
974             
975             var reader = new FileReader();
976             reader.onload = function(event){
977                 var dataurl = event.target.result;
978                 var img     = new Image();
979                 img.src = dataurl;
980                 self.resize_image_to_dataurl(img,800,600,callback);
981             }
982             reader.onerror = function(){
983                 self.pos_widget.screen_selector.show_popup('error',{
984                     message:_t('Could Not Read Image'),
985                     comment:_t('The provided file could not be read due to an unknown error'),
986                 });
987             };
988             reader.readAsDataURL(file);
989         },
990
991         // This fetches partner changes on the server, and in case of changes, 
992         // rerenders the affected views
993         reload_partners: function(){
994             var self = this;
995             return this.pos.load_new_partners().then(function(){
996                 self.render_list(self.pos.db.get_partners_sorted(1000));
997                 
998                 // update the currently assigned client if it has been changed in db.
999                 var curr_client = self.pos.get_order().get_client();
1000                 if (curr_client) {
1001                     self.pos.get_order().set_client(self.pos.db.get_partner_by_id(curr_client.id));
1002                 }
1003             });
1004         },
1005
1006         // Shows,hides or edit the customer details box :
1007         // visibility: 'show', 'hide' or 'edit'
1008         // partner:    the partner object to show or edit
1009         // clickpos:   the height of the click on the list (in pixel), used
1010         //             to maintain consistent scroll.
1011         display_client_details: function(visibility,partner,clickpos){
1012             var self = this;
1013             var contents = this.$('.client-details-contents');
1014             var parent   = this.$('.client-list').parent();
1015             var scroll   = parent.scrollTop();
1016             var height   = contents.height();
1017
1018             contents.off('click','.button.edit'); 
1019             contents.off('click','.button.save'); 
1020             contents.off('click','.button.undo'); 
1021             contents.on('click','.button.edit',function(){ self.edit_client_details(partner); });
1022             contents.on('click','.button.save',function(){ self.save_client_details(partner); });
1023             contents.on('click','.button.undo',function(){ self.undo_client_details(partner); });
1024             this.editing_client = false;
1025             this.uploaded_picture = null;
1026
1027             if(visibility === 'show'){
1028                 contents.empty();
1029                 contents.append($(QWeb.render('ClientDetails',{widget:this,partner:partner})));
1030
1031                 var new_height   = contents.height();
1032
1033                 if(!this.details_visible){
1034                     if(clickpos < scroll + new_height + 20 ){
1035                         parent.scrollTop( clickpos - 20 );
1036                     }else{
1037                         parent.scrollTop(parent.scrollTop() + new_height);
1038                     }
1039                 }else{
1040                     parent.scrollTop(parent.scrollTop() - height + new_height);
1041                 }
1042
1043                 this.details_visible = true;
1044                 this.toggle_save_button();
1045             } else if (visibility === 'edit') {
1046                 this.editing_client = true;
1047                 contents.empty();
1048                 contents.append($(QWeb.render('ClientDetailsEdit',{widget:this,partner:partner})));
1049                 this.toggle_save_button();
1050
1051                 contents.find('.image-uploader').on('change',function(){
1052                     self.load_image_file(event.target.files[0],function(res){
1053                         if (res) {
1054                             contents.find('.client-picture img, .client-picture .fa').remove();
1055                             contents.find('.client-picture').append("<img src='"+res+"'>");
1056                             contents.find('.detail.picture').remove();
1057                             self.uploaded_picture = res;
1058                         }
1059                     });
1060                 });
1061             } else if (visibility === 'hide') {
1062                 contents.empty();
1063                 if( height > scroll ){
1064                     contents.css({height:height+'px'});
1065                     contents.animate({height:0},400,function(){
1066                         contents.css({height:''});
1067                     });
1068                 }else{
1069                     parent.scrollTop( parent.scrollTop() - height);
1070                 }
1071                 this.details_visible = false;
1072                 this.toggle_save_button();
1073             }
1074         },
1075         close: function(){
1076             this._super();
1077         },
1078     });
1079
1080     module.ReceiptScreenWidget = module.ScreenWidget.extend({
1081         template: 'ReceiptScreenWidget',
1082         show_numpad:     false,
1083         show_leftpane:   false,
1084
1085         show: function(){
1086             this._super();
1087             var self = this;
1088
1089             this.refresh();
1090
1091             if (!this.pos.get_order()._printed && this.pos.config.iface_print_auto) {
1092                 this.print();
1093             }
1094
1095             // The problem is that in chrome the print() is asynchronous and doesn't
1096             // execute until all rpc are finished. So it conflicts with the rpc used
1097             // to send the orders to the backend, and the user is able to go to the next 
1098             // screen before the printing dialog is opened. The problem is that what's 
1099             // printed is whatever is in the page when the dialog is opened and not when it's called,
1100             // and so you end up printing the product list instead of the receipt... 
1101             //
1102             // Fixing this would need a re-architecturing
1103             // of the code to postpone sending of orders after printing.
1104             //
1105             // But since the print dialog also blocks the other asynchronous calls, the
1106             // button enabling in the setTimeout() is blocked until the printing dialog is 
1107             // closed. But the timeout has to be big enough or else it doesn't work
1108             // 2 seconds is the same as the default timeout for sending orders and so the dialog
1109             // should have appeared before the timeout... so yeah that's not ultra reliable. 
1110
1111             this.lock_screen(true);
1112             setTimeout(function(){
1113                 self.lock_screen(false);
1114             }, 2000);
1115         },
1116         lock_screen: function(locked) {
1117             this._locked = locked;
1118             if (locked) {
1119                 this.$('.next').removeClass('highlight');
1120             } else {
1121                 this.$('.next').addClass('highlight');
1122             }
1123         },
1124         print: function() {
1125             this.pos.get_order()._printed = true;
1126             window.print();
1127         },
1128         finish_order: function() {
1129             if (!this._locked) {
1130                 this.pos.get_order().finalize();
1131             }
1132         },
1133         renderElement: function() {
1134             var self = this;
1135             this._super();
1136             this.$('.next').click(function(){
1137                 self.finish_order();
1138             });
1139             this.$('.button.print').click(function(){
1140                 self.print();
1141             });
1142         },
1143         refresh: function() {
1144             var order = this.pos.get_order();
1145             this.$('.pos-receipt-container').html(QWeb.render('PosTicket',{
1146                     widget:this,
1147                     order: order,
1148                     receipt: order.export_for_printing(),
1149                     orderlines: order.get_orderlines(),
1150                     paymentlines: order.get_paymentlines(),
1151                 }));
1152         },
1153     });
1154
1155     module.PaymentScreenWidget = module.ScreenWidget.extend({
1156         template:      'PaymentScreenWidget',
1157         back_screen:   'product',
1158         next_screen:   'receipt',
1159         show_leftpane: false,
1160         show_numpad:   false,
1161         init: function(parent, options) {
1162             var self = this;
1163             this._super(parent, options);
1164
1165             this.pos.bind('change:selectedOrder',function(){
1166                     this.renderElement();
1167                     this.watch_order_changes();
1168                 },this);
1169             this.watch_order_changes();
1170
1171             this.inputbuffer = "";
1172             this.firstinput  = true;
1173             this.keyboard_handler = function(event){
1174                 var key = '';
1175                 if ( event.keyCode === 13 ) {         // Enter
1176                     self.validate_order();
1177                 } else if ( event.keyCode === 190 ) { // Dot
1178                     key = '.';
1179                 } else if ( event.keyCode === 46 ) {  // Delete
1180                     key = 'CLEAR';
1181                 } else if ( event.keyCode === 8 ) {   // Backspace 
1182                     key = 'BACKSPACE';
1183                     event.preventDefault(); // Prevents history back nav
1184                 } else if ( event.keyCode >= 48 && event.keyCode <= 57 ){       // Numbers
1185                     key = '' + (event.keyCode - 48);
1186                 } else if ( event.keyCode >= 96 && event.keyCode <= 105 ){      // Numpad Numbers
1187                     key = '' + (event.keyCode - 96);
1188                 } else if ( event.keyCode === 189 || event.keyCode === 109 ) {  // Minus
1189                     key = '-';
1190                 } else if ( event.keyCode === 107 ) { // Plus
1191                     key = '+';
1192                 }
1193
1194                 self.payment_input(key);
1195
1196             };
1197         },
1198         // resets the current input buffer
1199         reset_input: function(){
1200             var line = this.pos.get_order().selected_paymentline;
1201             this.firstinput  = true;
1202             if (line) {
1203                 this.inputbuffer = this.format_currency_no_symbol(line.get_amount());
1204             } else {
1205                 this.inputbuffer = "";
1206             }
1207         },
1208         // handle both keyboard and numpad input. Accepts
1209         // a string that represents the key pressed.
1210         payment_input: function(input) {
1211             var oldbuf = this.inputbuffer.slice(0);
1212
1213             if (input === '.') {
1214                 if (this.firstinput) {
1215                     this.inputbuffer = "0.";
1216                 }else if (!this.inputbuffer.length || this.inputbuffer === '-') {
1217                     this.inputbuffer += "0.";
1218                 } else if (this.inputbuffer.indexOf('.') < 0){
1219                     this.inputbuffer = this.inputbuffer + '.';
1220                 }
1221             } else if (input === 'CLEAR') {
1222                 this.inputbuffer = ""; 
1223             } else if (input === 'BACKSPACE') { 
1224                 this.inputbuffer = this.inputbuffer.substring(0,this.inputbuffer.length - 1);
1225             } else if (input === '+') {
1226                 if ( this.inputbuffer[0] === '-' ) {
1227                     this.inputbuffer = this.inputbuffer.substring(1,this.inputbuffer.length);
1228                 }
1229             } else if (input === '-') {
1230                 if ( this.inputbuffer[0] === '-' ) {
1231                     this.inputbuffer = this.inputbuffer.substring(1,this.inputbuffer.length);
1232                 } else {
1233                     this.inputbuffer = '-' + this.inputbuffer;
1234                 }
1235             } else if (input[0] === '+' && !isNaN(parseFloat(input))) {
1236                 this.inputbuffer = '' + ((parseFloat(this.inputbuffer) || 0) + parseFloat(input));
1237             } else if (!isNaN(parseInt(input))) {
1238                 if (this.firstinput) {
1239                     this.inputbuffer = '' + input;
1240                 } else {
1241                     this.inputbuffer += input;
1242                 }
1243             }
1244
1245             this.firstinput = this.inputbuffer.length === 0;
1246
1247             if (this.inputbuffer !== oldbuf) {
1248                 var order = this.pos.get_order();
1249                 if (order.selected_paymentline) {
1250                     order.selected_paymentline.set_amount(parseFloat(this.inputbuffer));
1251                     this.order_changes();
1252                     this.render_paymentlines();
1253                     this.$('.paymentline.selected .edit').text(this.inputbuffer);
1254                 }
1255             }
1256         },
1257         click_numpad: function(button) {
1258             this.payment_input(button.data('action'));
1259         },
1260         render_numpad: function() {
1261             var self = this;
1262             var numpad = $(QWeb.render('PaymentScreen-Numpad', { widget:this }));
1263             numpad.on('click','button',function(){
1264                 self.click_numpad($(this));
1265             });
1266             return numpad;
1267         },
1268         click_delete_paymentline: function(cid){
1269             var lines = this.pos.get_order().get_paymentlines();
1270             for ( var i = 0; i < lines.length; i++ ) {
1271                 if (lines[i].cid === cid) {
1272                     this.pos.get_order().remove_paymentline(lines[i]);
1273                     this.reset_input();
1274                     this.render_paymentlines();
1275                     return;
1276                 }
1277             }
1278         },
1279         click_paymentline: function(cid){
1280             var lines = this.pos.get_order().get_paymentlines();
1281             for ( var i = 0; i < lines.length; i++ ) {
1282                 if (lines[i].cid === cid) {
1283                     this.pos.get_order().select_paymentline(lines[i]);
1284                     this.reset_input();
1285                     this.render_paymentlines();
1286                     return;
1287                 }
1288             }
1289         },
1290         render_paymentlines: function() {
1291             var self  = this;
1292             var order = this.pos.get_order();
1293             if (!order) {
1294                 return;
1295             }
1296
1297             var lines = order.get_paymentlines();
1298
1299             this.$('.paymentlines-container').empty();
1300             var lines = $(QWeb.render('PaymentScreen-Paymentlines', { 
1301                 widget: this, 
1302                 order: order,
1303                 paymentlines: lines,
1304             }));
1305
1306             lines.on('click','.delete-button',function(){
1307                 self.click_delete_paymentline($(this).data('cid'));
1308             });
1309
1310             lines.on('click','.paymentline',function(){
1311                 self.click_paymentline($(this).data('cid'));
1312             });
1313                 
1314             lines.appendTo(this.$('.paymentlines-container'));
1315         },
1316         click_paymentmethods: function(id) {
1317             var cashregister = null;
1318             for ( var i = 0; i < this.pos.cashregisters.length; i++ ) {
1319                 if ( this.pos.cashregisters[i].journal_id[0] === id ){
1320                     cashregister = this.pos.cashregisters[i];
1321                     break;
1322                 }
1323             }
1324             this.pos.get_order().add_paymentline( cashregister );
1325             this.reset_input();
1326             this.render_paymentlines();
1327         },
1328         render_paymentmethods: function() {
1329             var self = this;
1330             var methods = $(QWeb.render('PaymentScreen-Paymentmethods', { widget:this }));
1331                 methods.on('click','.paymentmethod',function(){
1332                     self.click_paymentmethods($(this).data('id'));
1333                 });
1334             return methods;
1335         },
1336         click_invoice: function(){
1337             var order = this.pos.get_order();
1338             order.set_to_invoice(!order.is_to_invoice());
1339             if (order.is_to_invoice()) {
1340                 this.$('.js_invoice').addClass('highlight');
1341             } else {
1342                 this.$('.js_invoice').removeClass('highlight');
1343             }
1344         },
1345         click_set_customer: function(){
1346             this.pos_widget.screen_selector.set_current_screen('clientlist');
1347         },
1348         click_back: function(){
1349             this.pos_widget.screen_selector.set_current_screen('products');
1350         },
1351         renderElement: function() {
1352             var self = this;
1353             this._super();
1354
1355             var numpad = this.render_numpad();
1356             numpad.appendTo(this.$('.payment-numpad'));
1357
1358             var methods = this.render_paymentmethods();
1359             methods.appendTo(this.$('.paymentmethods-container'));
1360
1361             this.render_paymentlines();
1362
1363             this.$('.back').click(function(){
1364                 self.click_back();
1365             });
1366
1367             this.$('.next').click(function(){
1368                 self.validate_order();
1369             });
1370
1371             this.$('.js_set_customer').click(function(){
1372                 self.click_set_customer();
1373             });
1374             this.$('.js_invoice').click(function(){
1375                 self.click_invoice();
1376             });
1377
1378         },
1379         show: function(){
1380             this.pos.get_order().clean_empty_paymentlines();
1381             this.reset_input();
1382             this.render_paymentlines();
1383             this.order_changes();
1384             window.document.body.addEventListener('keydown',this.keyboard_handler);
1385             this._super();
1386         },
1387         hide: function(){
1388             window.document.body.removeEventListener('keydown',this.keyboard_handler);
1389             this._super();
1390         },
1391         // sets up listeners to watch for order changes
1392         watch_order_changes: function() {
1393             var self = this;
1394             var order = this.pos.get_order();
1395             if (!order) {
1396                 return;
1397             }
1398             if(this.old_order){
1399                 this.old_order.unbind(null,null,this);
1400             }
1401             order.bind('all',function(){
1402                 self.order_changes();
1403             });
1404             this.old_order = order;
1405         },
1406         // called when the order is changed, used to show if
1407         // the order is paid or not
1408         order_changes: function(){
1409             var self = this;
1410             var order = this.pos.get_order();
1411             if (!order) {
1412                 return;
1413             } else if (order.is_paid()) {
1414                 self.$('.next').addClass('highlight');
1415             }else{
1416                 self.$('.next').removeClass('highlight');
1417             }
1418         },
1419         print_escpos_receipt: function(){
1420             var env = {
1421                 widget:  this,
1422                 pos:     this.pos,
1423                 order:   this.pos.get_order(),
1424                 receipt: this.pos.get_order().export_for_printing(),
1425             };
1426
1427             this.pos.proxy.print_receipt(QWeb.render('XmlReceipt',env));
1428         },
1429
1430         // Check if the order is paid, then sends it to the backend,
1431         // and complete the sale process
1432         validate_order: function() {
1433             var self = this;
1434
1435             var order = this.pos.get_order();
1436
1437             // FIXME: this check is there because the backend is unable to
1438             // process empty orders. This is not the right place to fix it.
1439             if (order.get_orderlines().length === 0) {
1440                 this.pos_widget.screen_selector.show_popup('error',{
1441                     'message': _t('Empty Order'),
1442                     'comment': _t('There must be at least one product in your order before it can be validated'),
1443                 });
1444                 return;
1445             }
1446
1447             if (!order.is_paid() || this.invoicing) {
1448                 return;
1449             }
1450
1451             // The exact amount must be paid if there is no cash payment method defined.
1452             if (Math.abs(order.get_total_with_tax() - order.get_total_paid()) > 0.00001) {
1453                 var cash = false;
1454                 for (var i = 0; i < this.pos.cashregisters.length; i++) {
1455                     cash = cash || (this.pos.cashregisters[i].journal.type === 'cash');
1456                 }
1457                 if (!cash) {
1458                     this.pos_widget.screen_selector.show_popup('error',{
1459                         message: _t('Cannot return change without a cash payment method'),
1460                         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'),
1461                     });
1462                     return;
1463                 }
1464             }
1465
1466             if (order.is_paid_with_cash() && this.pos.config.iface_cashdrawer) { 
1467
1468                     this.pos.proxy.open_cashbox();
1469             }
1470
1471             if (order.is_to_invoice()) {
1472                 var invoiced = this.pos.push_and_invoice_order(order);
1473                 this.invoicing = true;
1474
1475                 invoiced.fail(function(error){
1476                     self.invoicing = false;
1477                     if (error === 'error-no-client') {
1478                         self.pos_widget.screen_selector.show_popup('confirm',{
1479                             message: _t('Please select the Customer'),
1480                             comment: _t('You need to select the customer before you can invoice an order.'),
1481                             confirm: function(){
1482                                 self.pos_widget.screen_selector.set_current_screen('clientlist');
1483                             },
1484                         });
1485                     } else {
1486                         self.pos_widget.screen_selector.show_popup('error',{
1487                             message: _t('The order could not be sent'),
1488                             comment: _t('Check your internet connection and try again.'),
1489                         });
1490                     }
1491                 });
1492
1493                 invoiced.done(function(){
1494                     self.invoicing = false;
1495                     order.finalize();
1496                 });
1497             } else {
1498                 this.pos.push_order(order) 
1499                 if (this.pos.config.iface_print_via_proxy) {
1500                     this.print_escpos_receipt();
1501                     order.finalize();    //finish order and go back to scan screen
1502                 } else {
1503                     this.pos_widget.screen_selector.set_current_screen(this.next_screen);
1504                 }
1505             }
1506         },
1507     });
1508
1509 }
1510