[FIX] point_of_sale: correct currency rounding
[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 function openerp_pos_screens(instance, module){ //module is instance.point_of_sale
19     var QWeb = instance.web.qweb;
20
21     module.ScreenSelector = instance.web.Class.extend({
22         init: function(options){
23             this.pos = options.pos;
24
25             this.screen_set = options.screen_set || {};
26
27             this.popup_set = options.popup_set || {};
28
29             this.default_client_screen = options.default_client_screen;
30             this.default_cashier_screen = options.default_cashier_screen;
31
32             this.current_popup = null;
33
34             this.current_mode = options.default_mode || 'client';
35
36             this.current_screen = null; 
37
38             for(screen_name in this.screen_set){
39                 this.screen_set[screen_name].hide();
40             }
41             
42             for(popup_name in this.popup_set){
43                 this.popup_set[popup_name].hide();
44             }
45
46             this.selected_order = this.pos.get('selectedOrder');
47             this.selected_order.set_screen_data({
48                 client_screen: this.default_client_screen,
49                 cashier_screen: this.default_cashier_screen,
50             });
51
52             this.pos.bind('change:selectedOrder', this.load_saved_screen, this);
53         },
54         add_screen: function(screen_name, screen){
55             screen.hide();
56             this.screen_set[screen_name] = screen;
57             return this;
58         },
59         show_popup: function(name){
60             if(this.current_popup){
61                 this.close_popup();
62             }
63             this.current_popup = this.popup_set[name];
64             this.current_popup.show();
65         },
66         close_popup: function(){
67             if(this.current_popup){
68                 this.current_popup.close();
69                 this.current_popup.hide();
70                 this.current_popup = null;
71             }
72         },
73         load_saved_screen:  function(){
74             this.close_popup();
75
76             var selectedOrder = this.pos.get('selectedOrder');
77             
78             if(this.current_mode === 'client'){
79                 this.set_current_screen(selectedOrder.get_screen_data('client_screen') || this.default_client_screen,null,'refresh');
80             }else if(this.current_mode === 'cashier'){
81                 this.set_current_screen(selectedOrder.get_screen_data('cashier_screen') || this.default_cashier_screen,null,'refresh');
82             }
83             this.selected_order = selectedOrder;
84         },
85         set_user_mode: function(user_mode){
86             if(user_mode !== this.current_mode){
87                 this.close_popup();
88                 this.current_mode = user_mode;
89                 this.load_saved_screen();
90             }
91         },
92         get_user_mode: function(){
93             return this.current_mode;
94         },
95         set_current_screen: function(screen_name,params,refresh){
96             var screen = this.screen_set[screen_name];
97             if(!screen){
98                 console.error("ERROR: set_current_screen("+screen_name+") : screen not found");
99             }
100
101             this.close_popup();
102             var selectedOrder = this.pos.get('selectedOrder');
103             if(this.current_mode === 'client'){
104                 selectedOrder.set_screen_data('client_screen',screen_name);
105                 if(params){ 
106                     selectedOrder.set_screen_data('client_screen_params',params); 
107                 }
108             }else{
109                 selectedOrder.set_screen_data('cashier_screen',screen_name);
110                 if(params){
111                     selectedOrder.set_screen_data('cashier_screen_params',params);
112                 }
113             }
114
115             if(screen && (refresh || screen !== this.current_screen)){
116                 if(this.current_screen){
117                     this.current_screen.close();
118                     this.current_screen.hide();
119                 }
120                 this.current_screen = screen;
121                 this.current_screen.show();
122             }
123         },
124         get_current_screen_param: function(param){
125             var selected_order = this.pos.get('selectedOrder');
126             if(this.current_mode === 'client'){
127                 var params = selected_order.get_screen_data('client_screen_params');
128             }else{
129                 var params = selected_order.get_screen_data('cashier_screen_params');
130             }
131             if(params){
132                 return params[param];
133             }else{
134                 return undefined;
135             }
136         },
137         set_default_screen: function(){
138             this.set_current_screen(this.current_mode === 'client' ? this.default_client_screen : this.default_cashier_screen);
139         },
140     });
141
142     module.ScreenWidget = module.PosBaseWidget.extend({
143
144         show_numpad:     true,  
145         show_leftpane:   true,
146
147         init: function(parent,options){
148             this._super(parent,options);
149             this.hidden = false;
150         },
151
152         help_button_action: function(){
153             this.pos_widget.screen_selector.show_popup('help');
154         },
155
156         barcode_product_screen:         'products',     //if defined, this screen will be loaded when a product is scanned
157         barcode_product_error_popup:    'error-product',    //if defined, this popup will be loaded when there's an error in the popup
158
159         // what happens when a product is scanned : 
160         // it will add the product to the order and go to barcode_product_screen. Or show barcode_product_error_popup if 
161         // there's an error.
162         barcode_product_action: function(ean){
163             var self = this;
164             if(self.pos.scan_product(ean)){
165                 self.pos.proxy.scan_item_success(ean);
166                 if(self.barcode_product_screen){ 
167                     self.pos_widget.screen_selector.set_current_screen(self.barcode_product_screen);
168                 }
169             }else{
170                 self.pos.proxy.scan_item_error_unrecognized(ean);
171                 if(self.barcode_product_error_popup && self.pos_widget.screen_selector.get_user_mode() !== 'cashier'){
172                     self.pos_widget.screen_selector.show_popup(self.barcode_product_error_popup);
173                 }
174             }
175         },
176         
177         // what happens when a cashier id barcode is scanned.
178         // the default behavior is the following : 
179         // - if there's a user with a matching ean, put it as the active 'cashier', go to cashier mode, and return true
180         // - else : do nothing and return false. You probably want to extend this to show and appropriate error popup... 
181         barcode_cashier_action: function(ean){
182             var users = this.pos.get('user_list');
183             for(var i = 0, len = users.length; i < len; i++){
184                 if(users[i].ean13 === ean.ean){
185                     this.pos.set('cashier',users[i]);
186                     this.pos_widget.username.refresh();
187                     this.pos.proxy.cashier_mode_activated();
188                     this.pos_widget.screen_selector.set_user_mode('cashier');
189                     return true;
190                 }
191             }
192             this.pos.proxy.scan_item_error_unrecognized(ean);
193             return false;
194         },
195         
196         // what happens when a client id barcode is scanned.
197         // the default behavior is the following : 
198         // - if there's a user with a matching ean, put it as the active 'client' and return true
199         // - else : return false. 
200         barcode_client_action: function(ean){
201             var partners = this.pos.get('partner_list');
202             for(var i = 0, len = partners.length; i < len; i++){
203                 if(partners[i].ean13 === ean.ean){
204                     this.pos.get('selectedOrder').set_client(partners[i]);
205                     this.pos_widget.username.refresh();
206                     this.pos.proxy.scan_item_success(ean);
207                     return true;
208                 }
209             }
210             this.pos.proxy.scan_item_error_unrecognized(ean);
211             return false;
212             //TODO start the transaction
213         },
214         
215         // what happens when a discount barcode is scanned : the default behavior
216         // is to set the discount on the last order.
217         barcode_discount_action: function(ean){
218             this.pos.proxy.scan_item_success(ean);
219             var last_orderline = this.pos.get('selectedOrder').getLastOrderline();
220             if(last_orderline){
221                 last_orderline.set_discount(ean.value)
222             }
223         },
224
225         // shows an action bar on the screen. The actionbar is automatically shown when you add a button
226         // with add_action_button()
227         show_action_bar: function(){
228             this.pos_widget.action_bar.show();
229             this.$el.css({'bottom':'105px'});
230         },
231
232         // hides the action bar. The actionbar is automatically hidden when it is empty
233         hide_action_bar: function(){
234             this.pos_widget.action_bar.hide();
235             this.$el.css({'bottom':'0px'});
236         },
237
238         // adds a new button to the action bar. The button definition takes three parameters, all optional :
239         // - label: the text below the button
240         // - icon:  a small icon that will be shown
241         // - click: a callback that will be executed when the button is clicked.
242         // the method returns a reference to the button widget, and automatically show the actionbar.
243         add_action_button: function(button_def){
244             this.show_action_bar();
245             return this.pos_widget.action_bar.add_new_button(button_def);
246         },
247
248         // this method shows the screen and sets up all the widget related to this screen. Extend this method
249         // if you want to alter the behavior of the screen.
250         show: function(){
251             var self = this;
252
253             this.hidden = false;
254             if(this.$el){
255                 this.$el.show();
256             }
257
258             if(this.pos_widget.action_bar.get_button_count() > 0){
259                 this.show_action_bar();
260             }else{
261                 this.hide_action_bar();
262             }
263             
264             // we add the help button by default. we do this because the buttons are cleared on each refresh so that
265             // the button stay local to each screen
266             this.pos_widget.left_action_bar.add_new_button({
267                     label: 'help',
268                     icon: '/point_of_sale/static/src/img/icons/png48/help.png',
269                     click: function(){ self.help_button_action(); },
270                 });
271
272             var self = this;
273             var cashier_mode = this.pos_widget.screen_selector.get_user_mode() === 'cashier';
274
275             this.pos_widget.set_numpad_visible(this.show_numpad && cashier_mode);
276             this.pos_widget.set_leftpane_visible(this.show_leftpane);
277             this.pos_widget.set_left_action_bar_visible(this.show_leftpane && !cashier_mode);
278             this.pos_widget.set_cashier_controls_visible(cashier_mode);
279
280             if(cashier_mode && this.pos.iface_self_checkout){
281                 this.pos_widget.client_button.show();
282             }else{
283                 this.pos_widget.client_button.hide();
284             }
285             if(cashier_mode){
286                 this.pos_widget.close_button.show();
287             }else{
288                 this.pos_widget.close_button.hide();
289             }
290             
291             this.pos_widget.username.set_user_mode(this.pos_widget.screen_selector.get_user_mode());
292
293             this.pos.barcode_reader.set_action_callback({
294                 'cashier': self.barcode_cashier_action ? function(ean){ self.barcode_cashier_action(ean); } : undefined ,
295                 'product': self.barcode_product_action ? function(ean){ self.barcode_product_action(ean); } : undefined ,
296                 'client' : self.barcode_client_action ?  function(ean){ self.barcode_client_action(ean);  } : undefined ,
297                 'discount': self.barcode_discount_action ? function(ean){ self.barcode_discount_action(ean); } : undefined,
298             });
299         },
300
301         // this method is called when the screen is closed to make place for a new screen. this is a good place
302         // to put your cleanup stuff as it is guaranteed that for each show() there is one and only one close()
303         close: function(){
304             if(this.pos.barcode_reader){
305                 this.pos.barcode_reader.reset_action_callbacks();
306             }
307             this.pos_widget.action_bar.destroy_buttons();
308             this.pos_widget.left_action_bar.destroy_buttons();
309         },
310
311         // this methods hides the screen. It's not a good place to put your cleanup stuff as it is called on the
312         // POS initialization.
313         hide: function(){
314             this.hidden = true;
315             if(this.$el){
316                 this.$el.hide();
317             }
318         },
319
320         // we need this because some screens re-render themselves when they are hidden
321         // (due to some events, or magic, or both...)  we must make sure they remain hidden.
322         // the good solution would probably be to make them not re-render themselves when they
323         // are hidden. 
324         renderElement: function(){
325             this._super();
326             if(this.hidden){
327                 if(this.$el){
328                     this.$el.hide();
329                 }
330             }
331         },
332     });
333
334     module.PopUpWidget = module.PosBaseWidget.extend({
335         show: function(){
336             if(this.$el){
337                 this.$el.show();
338             }
339         },
340         /* called before hide, when a popup is closed */
341         close: function(){
342         },
343         /* hides the popup. keep in mind that this is called in the initialization pass of the 
344          * pos instantiation, so you don't want to do anything fancy in here */
345         hide: function(){
346             if(this.$el){
347                 this.$el.hide();
348             }
349         },
350     });
351
352     module.HelpPopupWidget = module.PopUpWidget.extend({
353         template:'HelpPopupWidget',
354         show: function(){
355             this._super();
356             this.pos.proxy.help_needed();
357             var self = this;
358             
359             this.$el.find('.button').off('click').click(function(){
360                 self.pos_widget.screen_selector.close_popup();
361             });
362         },
363         close:function(){
364             this.pos.proxy.help_canceled();
365         },
366     });
367
368     module.ChooseReceiptPopupWidget = module.PopUpWidget.extend({
369         template:'ChooseReceiptPopupWidget',
370         show: function(){
371             this._super();
372             this.renderElement();
373             var self = this;
374             var currentOrder = self.pos.get('selectedOrder');
375             
376             this.$('.button.receipt').off('click').click(function(){
377                 currentOrder.set_receipt_type('receipt');
378                 self.pos_widget.screen_selector.set_current_screen('products');
379             });
380
381             this.$('.button.invoice').off('click').click(function(){
382                 currentOrder.set_receipt_type('invoice');
383                 self.pos_widget.screen_selector.set_current_screen('products');
384             });
385         },
386         get_client_name: function(){
387             var client = this.pos.get('selectedOrder').get_client();
388             if( client ){
389                 return client.name;
390             }else{
391                 return '';
392             }
393         },
394     });
395
396     module.ErrorPopupWidget = module.PopUpWidget.extend({
397         template:'ErrorPopupWidget',
398         show: function(){
399             var self = this;
400             this._super();
401             this.pos.proxy.help_needed();
402             this.pos.proxy.scan_item_error_unrecognized();
403
404             this.pos.barcode_reader.save_callbacks();
405             this.pos.barcode_reader.reset_action_callbacks();
406             this.pos.barcode_reader.set_action_callback({
407                 'cashier': function(ean){
408                     clearInterval(this.intervalID);
409                     self.pos.proxy.cashier_mode_activated();
410                     self.pos_widget.screen_selector.set_user_mode('cashier');
411                 },
412             });
413             this.$('.footer .button').off('click').click(function(){
414                 self.pos_widget.screen_selector.close_popup();
415             });
416         },
417         close:function(){
418             this._super();
419             this.pos.proxy.help_canceled();
420             this.pos.barcode_reader.restore_callbacks();
421         },
422     });
423
424     module.ProductErrorPopupWidget = module.ErrorPopupWidget.extend({
425         template:'ProductErrorPopupWidget',
426     });
427
428     module.ErrorSessionPopupWidget = module.ErrorPopupWidget.extend({
429         template:'ErrorSessionPopupWidget',
430     });
431
432     module.ErrorNegativePricePopupWidget = module.ErrorPopupWidget.extend({
433         template:'ErrorNegativePricePopupWidget',
434     });
435
436     module.ScaleInviteScreenWidget = module.ScreenWidget.extend({
437         template:'ScaleInviteScreenWidget',
438
439         next_screen:'scale',
440         previous_screen:'products',
441
442         show: function(){
443             this._super();
444             var self = this;
445
446             self.pos.proxy.weighting_start();
447
448             this.intervalID = setInterval(function(){
449                 var weight = self.pos.proxy.weighting_read_kg();
450                 if(weight > 0.001){
451                     clearInterval(this.intervalID);
452                     self.pos_widget.screen_selector.set_current_screen(self.next_screen);
453                 }
454             },500);
455
456             this.add_action_button({
457                     label: 'back',
458                     icon: '/point_of_sale/static/src/img/icons/png48/go-previous.png',
459                     click: function(){  
460                         clearInterval(this.intervalID);
461                         self.pos.proxy.weighting_end();
462                         self.pos_widget.screen_selector.set_current_screen(self.previous_screen);
463                     }
464                 });
465         },
466         close: function(){
467             this._super();
468             clearInterval(this.intervalID);
469             this.pos.proxy.weighting_end();
470         },
471     });
472
473     module.ScaleScreenWidget = module.ScreenWidget.extend({
474         template:'ScaleScreenWidget',
475
476         next_screen: 'products',
477         previous_screen: 'products',
478
479         show: function(){
480             this._super();
481             this.renderElement();
482             var self = this;
483
484
485             this.add_action_button({
486                     label: 'back',
487                     icon: '/point_of_sale/static/src/img/icons/png48/go-previous.png',
488                     click: function(){
489                         self.pos_widget.screen_selector.set_current_screen(self.previous_screen);
490                     }
491                 });
492
493             this.validate_button = this.add_action_button({
494                     label: 'Validate',
495                     icon: '/point_of_sale/static/src/img/icons/png48/validate.png',
496                     click: function(){
497                         self.order_product();
498                         self.pos_widget.screen_selector.set_current_screen(self.next_screen);
499                     },
500                 });
501             
502             this.pos.proxy.weighting_start();
503             this.intervalID = setInterval(function(){
504                 var weight = self.pos.proxy.weighting_read_kg();
505                 if(weight != self.weight){
506                     self.weight = weight;
507                     self.renderElement();
508                 }
509             },200);
510         },
511         renderElement: function(){
512             var self = this;
513             this._super();
514             this.$('.product-picture').click(function(){
515                 self.order_product();
516                 self.pos_widget.screen_selector.set_current_screen(self.next_screen);
517             });
518         },
519         get_product: function(){
520             var ss = this.pos_widget.screen_selector;
521             if(ss){
522                 return ss.get_current_screen_param('product');
523             }else{
524                 return undefined;
525             }
526         },
527         order_product: function(){
528             var weight = this.pos.proxy.weighting_read_kg();
529             this.pos.get('selectedOrder').addProduct(this.get_product(),{ quantity:weight });
530         },
531         get_product_name: function(){
532             var product = this.get_product();
533             return (product ? product.get('name') : undefined) || 'Unnamed Product';
534         },
535         get_product_price: function(){
536             var product = this.get_product();
537             return (product ? product.get('price') : 0) || 0;
538         },
539         get_product_weight: function(){
540             return this.weight || 0;
541         },
542         close: function(){
543             this._super();
544             clearInterval(this.intervalID);
545             this.pos.proxy.weighting_end();
546         },
547     });
548
549     // the JobQueue schedules a sequence of 'jobs'. each job is
550     // a function returning a deferred. the queue waits for each job to finish 
551     // before launching the next. Each job can also be scheduled with a delay. 
552     // the queue jobqueue is used to prevent parallel requests to the payment terminal.
553
554     module.JobQueue = function(){
555         var queue = [];
556         var running = false;
557         var run = function(){
558             if(queue.length > 0){
559                 running = true;
560                 var job = queue.shift();
561                 setTimeout(function(){
562                     var def = job.fun();
563                     if(def){
564                         def.done(run);
565                     }else{
566                         run();
567                     }
568                 },job.delay || 0);
569             }else{
570                 running = false;
571             }
572         };
573         
574         // adds a job to the schedule.
575         this.schedule  = function(fun, delay){
576             queue.push({fun:fun, delay:delay});
577             if(!running){
578                 run();
579             }
580         }
581
582         // remove all jobs from the schedule
583         this.clear = function(){
584             queue = [];
585         };
586     };
587
588     module.ClientPaymentScreenWidget =  module.ScreenWidget.extend({
589         template:'ClientPaymentScreenWidget',
590
591         next_screen: 'welcome',
592         previous_screen: 'products',
593
594         show: function(){
595             this._super();
596             var self = this;
597            
598             this.queue = new module.JobQueue();
599             this.canceled = false;
600             this.paid     = false;
601
602             // initiates the connection to the payment terminal and starts the update requests
603             this.start = function(){
604                 var def = new $.Deferred();
605                 self.pos.proxy.payment_request(self.pos.get('selectedOrder').getDueLeft())
606                     .done(function(ack){
607                         if(ack === 'ok'){
608                             self.queue.schedule(self.update);
609                         }else if(ack.indexOf('error') === 0){
610                             console.error('cannot make payment. TODO');
611                         }else{
612                             console.error('unknown payment request return value:',ack);
613                         }
614                         def.resolve();
615                     });
616                 return def;
617             };
618             
619             // gets updated status from the payment terminal and performs the appropriate consequences
620             this.update = function(){
621                 var def = new $.Deferred();
622                 if(self.canceled){
623                     return def.resolve();
624                 }
625                 self.pos.proxy.payment_status()
626                     .done(function(status){
627                         if(status.status === 'paid'){
628
629                             var currentOrder = self.pos.get('selectedOrder');
630                             
631                             //we get the first cashregister marked as self-checkout
632                             var selfCheckoutRegisters = [];
633                             for(var i = 0; i < self.pos.get('cashRegisters').models.length; i++){
634                                 var cashregister = self.pos.get('cashRegisters').models[i];
635                                 if(cashregister.self_checkout_payment_method){
636                                     selfCheckoutRegisters.push(cashregister);
637                                 }
638                             }
639
640                             var cashregister = selfCheckoutRegisters[0] || self.pos.get('cashRegisters').models[0];
641                             currentOrder.addPaymentLine(cashregister);
642                             self.pos.push_order(currentOrder.exportAsJSON())
643                             currentOrder.destroy();
644                             self.pos.proxy.transaction_end();
645                             self.pos_widget.screen_selector.set_current_screen(self.next_screen);
646                             self.paid = true;
647                         }else if(status.status.indexOf('error') === 0){
648                             console.error('error in payment request. TODO');
649                         }else if(status.status === 'waiting'){
650                             self.queue.schedule(self.update,200);
651                         }else{
652                             console.error('unknown status value:',status.status);
653                         }
654                         def.resolve();
655                     });
656                 return def;
657             }
658             
659             // cancels a payment.
660             this.cancel = function(){
661                 if(!self.paid && !self.canceled){
662                     self.canceled = true;
663                     self.pos.proxy.payment_cancel();
664                     self.pos_widget.screen_selector.set_current_screen(self.previous_screen);
665                     self.queue.clear();
666                 }
667                 return (new $.Deferred()).resolve();
668             }
669             
670             if(this.pos.get('selectedOrder').getDueLeft() <= 0){
671                 this.pos_widget.screen_selector.show_popup('error-negative-price');
672             }else{
673                 this.queue.schedule(this.start);
674             }
675
676             this.add_action_button({
677                     label: 'back',
678                     icon: '/point_of_sale/static/src/img/icons/png48/go-previous.png',
679                     click: function(){  
680                        self.queue.schedule(self.cancel);
681                     }
682                 });
683         },
684         close: function(){
685             if(this.queue){
686                 this.queue.schedule(this.cancel);
687             }
688             //TODO CANCEL
689             this._super();
690         },
691     });
692
693     module.WelcomeScreenWidget = module.ScreenWidget.extend({
694         template:'WelcomeScreenWidget',
695
696         next_screen: 'products',
697
698         show_numpad:     false,
699         show_leftpane:   false,
700         barcode_product_action: function(ean){
701             this.pos.proxy.transaction_start();
702             this._super(ean);
703         },
704
705         barcode_client_action: function(ean){
706             this.pos.proxy.transaction_start();
707             this._super(ean);
708             $('.goodbye-message').hide();
709             this.pos_widget.screen_selector.show_popup('choose-receipt');
710         },
711         
712         show: function(){
713             this._super();
714             var self = this;
715
716             this.add_action_button({
717                     label: 'help',
718                     icon: '/point_of_sale/static/src/img/icons/png48/help.png',
719                     click: function(){ 
720                         $('.goodbye-message').css({opacity:1}).hide();
721                         self.help_button_action();
722                     },
723                 });
724
725             $('.goodbye-message').css({opacity:1}).show();
726             setTimeout(function(){
727                 $('.goodbye-message').animate({opacity:0},500,'swing',function(){$('.goodbye-message').hide();});
728             },5000);
729         },
730     });
731     
732     module.ProductScreenWidget = module.ScreenWidget.extend({
733         template:'ProductScreenWidget',
734
735         scale_screen: 'scale_invite',
736         client_next_screen:  'client_payment',
737
738         show_numpad:     true,
739         show_leftpane:   true,
740
741         start: function(){ //FIXME this should work as renderElement... but then the categories aren't properly set. explore why
742             var self = this;
743             this.product_categories_widget = new module.ProductCategoriesWidget(this,{});
744             this.product_categories_widget.replace($('.placeholder-ProductCategoriesWidget'));
745
746             this.product_list_widget = new module.ProductListWidget(this,{
747                 click_product_action: function(product){
748                     if(product.get('to_weight') && self.pos.iface_electronic_scale){
749                         self.pos_widget.screen_selector.set_current_screen(self.scale_screen, {product: product});
750                     }else{
751                         self.pos.get('selectedOrder').addProduct(product);
752                     }
753                 },
754             });
755             this.product_list_widget.replace($('.placeholder-ProductListWidget'));
756         },
757
758         show: function(){
759             this._super();
760             var self = this;
761
762             this.product_categories_widget.reset_category();
763
764             this.pos_widget.order_widget.set_numpad_state(this.pos_widget.numpad.state);
765             if(this.pos.iface_vkeyboard){
766                 this.pos_widget.onscreen_keyboard.connect();
767             }
768
769             if(this.pos_widget.screen_selector.current_mode === 'client'){ 
770                 this.add_action_button({
771                         label: 'pay',
772                         icon: '/point_of_sale/static/src/img/icons/png48/go-next.png',
773                         click: function(){  
774                             self.pos_widget.screen_selector.set_current_screen(self.client_next_screen);
775                         }
776                     });
777             }
778         },
779
780         close: function(){
781             this._super();
782             this.pos_widget.order_widget.set_numpad_state(null);
783             this.pos_widget.payment_screen.set_numpad_state(null);
784         },
785
786     });
787
788     module.ReceiptScreenWidget = module.ScreenWidget.extend({
789         template: 'ReceiptScreenWidget',
790
791         show_numpad:     true,
792         show_leftpane:   true,
793
794         init: function(parent, options) {
795             this._super(parent,options);
796             this.model = options.model;
797             this.user = this.pos.get('user');
798             this.company = this.pos.get('company');
799             this.shop_obj = this.pos.get('shop');
800         },
801         renderElement: function() {
802             this._super();
803             this.pos.bind('change:selectedOrder', this.change_selected_order, this);
804             this.change_selected_order();
805         },
806         show: function(){
807             this._super();
808             var self = this;
809
810             this.add_action_button({
811                     label: 'Print',
812                     icon: '/point_of_sale/static/src/img/icons/png48/printer.png',
813                     click: function(){ self.print(); },
814                 });
815
816             this.add_action_button({
817                     label: 'Next Order',
818                     icon: '/point_of_sale/static/src/img/icons/png48/go-next.png',
819                     click: function() { self.finishOrder(); },
820                 });
821
822             window.print();
823         },
824         print: function() {
825             window.print();
826         },
827         finishOrder: function() {
828             this.pos.get('selectedOrder').destroy();
829         },
830         change_selected_order: function() {
831             if (this.currentOrderLines)
832                 this.currentOrderLines.unbind();
833             this.currentOrderLines = (this.pos.get('selectedOrder')).get('orderLines');
834             this.currentOrderLines.bind('add', this.refresh, this);
835             this.currentOrderLines.bind('change', this.refresh, this);
836             this.currentOrderLines.bind('remove', this.refresh, this);
837             if (this.currentPaymentLines)
838                 this.currentPaymentLines.unbind();
839             this.currentPaymentLines = (this.pos.get('selectedOrder')).get('paymentLines');
840             this.currentPaymentLines.bind('all', this.refresh, this);
841             this.refresh();
842         },
843         refresh: function() {
844             this.currentOrder = this.pos.get('selectedOrder');
845             $('.pos-receipt-container', this.$el).html(QWeb.render('PosTicket',{widget:this}));
846         },
847     });
848
849     module.PaymentScreenWidget = module.ScreenWidget.extend({
850         template: 'PaymentScreenWidget',
851         back_screen: 'products',
852         next_screen: 'receipt',
853         init: function(parent, options) {
854             this._super(parent,options);
855             this.model = options.model;
856             this.pos.bind('change:selectedOrder', this.change_selected_order, this);
857             this.bindPaymentLineEvents();
858             this.bind_orderline_events();
859             this.paymentlinewidgets = [];
860         },
861         show: function(){
862             this._super();
863             var self = this;
864
865             if(this.pos.iface_cashdrawer){
866                 this.pos.proxy.open_cashbox();
867             }
868
869             this.set_numpad_state(this.pos_widget.numpad.state);
870             
871             this.back_button = this.add_action_button({
872                     label: 'Back',
873                     icon: '/point_of_sale/static/src/img/icons/png48/go-previous.png',
874                     click: function(){  
875                         self.pos_widget.screen_selector.set_current_screen(self.back_screen);
876                     },
877                 });
878             
879             this.validate_button = this.add_action_button({
880                     label: 'Validate',
881                     name: 'validation',
882                     icon: '/point_of_sale/static/src/img/icons/png48/validate.png',
883                     click: function(){
884                         self.validateCurrentOrder();
885                     },
886                 });
887
888             this.updatePaymentSummary();
889         },
890         close: function(){
891             this._super();
892             this.pos_widget.order_widget.set_numpad_state(null);
893             this.pos_widget.payment_screen.set_numpad_state(null);
894         },
895         back: function() {
896             this.pos_widget.screen_selector.set_current_screen(self.back_screen);
897         },
898         validateCurrentOrder: function() {
899             var currentOrder = this.pos.get('selectedOrder');
900
901             this.pos.push_order(currentOrder.exportAsJSON()) 
902             if(this.pos.iface_print_via_proxy){
903                 this.pos.proxy.print_receipt(currentOrder.export_for_printing());
904                 this.pos.get('selectedOrder').destroy();    //finish order and go back to scan screen
905             }else{
906                 this.pos_widget.screen_selector.set_current_screen(this.next_screen);
907             }
908         },
909         bindPaymentLineEvents: function() {
910             this.currentPaymentLines = (this.pos.get('selectedOrder')).get('paymentLines');
911             this.currentPaymentLines.bind('add', this.addPaymentLine, this);
912             this.currentPaymentLines.bind('remove', this.renderElement, this);
913             this.currentPaymentLines.bind('all', this.updatePaymentSummary, this);
914         },
915         bind_orderline_events: function() {
916             this.currentOrderLines = (this.pos.get('selectedOrder')).get('orderLines');
917             this.currentOrderLines.bind('all', this.updatePaymentSummary, this);
918         },
919         change_selected_order: function() {
920             this.currentPaymentLines.unbind();
921             this.bindPaymentLineEvents();
922             this.currentOrderLines.unbind();
923             this.bind_orderline_events();
924             this.renderElement();
925         },
926         addPaymentLine: function(newPaymentLine) {
927             var self = this;
928             var l = new module.PaymentlineWidget(null, {
929                     payment_line: newPaymentLine
930             });
931             l.on('delete_payment_line', self, function(r) {
932                 self.deleteLine(r);
933             });
934             l.appendTo(this.$('#paymentlines'));
935             this.paymentlinewidgets.push(l);
936             if(this.numpadState){
937                 this.numpadState.resetValue();
938             }
939         },
940         renderElement: function() {
941             this._super();
942             this.$('#paymentlines').empty();
943             for(var i = 0, len = this.paymentlinewidgets.length; i < len; i++){
944                 this.paymentlinewidgets[i].destroy();
945             }
946             this.paymentlinewidgets = [];
947             
948             this.currentPaymentLines.each(_.bind( function(paymentLine) {
949                 this.addPaymentLine(paymentLine);
950             }, this));
951             this.updatePaymentSummary();
952         },
953         deleteLine: function(lineWidget) {
954                 this.currentPaymentLines.remove([lineWidget.payment_line]);
955         },
956         updatePaymentSummary: function() {
957             var currentOrder = this.pos.get('selectedOrder');
958             var paidTotal = currentOrder.getPaidTotal();
959             var dueTotal = currentOrder.getTotalTaxIncluded();
960             var remaining = dueTotal > paidTotal ? dueTotal - paidTotal : 0;
961             var change = paidTotal > dueTotal ? paidTotal - dueTotal : 0;
962
963             this.$('#payment-due-total').html(this.format_currency(dueTotal));
964             this.$('#payment-paid-total').html(this.format_currency(paidTotal));
965             this.$('#payment-remaining').html(this.format_currency(remaining));
966             this.$('#payment-change').html(this.format_currency(change));
967             if(currentOrder.selected_orderline === undefined){
968                 remaining = 1;  // What is this ? 
969             }
970                 
971             if(this.pos_widget.action_bar){
972                 this.pos_widget.action_bar.set_button_disabled('validation', remaining > 0);
973             }
974         },
975         set_numpad_state: function(numpadState) {
976                 if (this.numpadState) {
977                         this.numpadState.unbind('set_value', this.set_value);
978                         this.numpadState.unbind('change:mode', this.setNumpadMode);
979                 }
980                 this.numpadState = numpadState;
981                 if (this.numpadState) {
982                         this.numpadState.bind('set_value', this.set_value, this);
983                         this.numpadState.bind('change:mode', this.setNumpadMode, this);
984                         this.numpadState.reset();
985                         this.setNumpadMode();
986                 }
987         },
988         setNumpadMode: function() {
989                 this.numpadState.set({mode: 'payment'});
990         },
991         set_value: function(val) {
992                 this.currentPaymentLines.last().set_amount(val);
993         },
994     });
995 }