1f6b29439aba3c31a873885893ebe449635988ee
[odoo/odoo.git] / addons / point_of_sale / static / src / js / pos_widgets.js
1 function openerp_pos_widgets(instance, module){ //module is instance.point_of_sale
2     var QWeb = instance.web.qweb;
3
4     module.NumpadWidget = module.PosBaseWidget.extend({
5         template:'NumpadWidget',
6         init: function(parent, options) {
7             this._super(parent);
8             this.state = new module.NumpadState();
9         },
10         start: function() {
11             this.state.bind('change:mode', this.changedMode, this);
12             this.changedMode();
13             this.$element.find('button#numpad-backspace').click(_.bind(this.clickDeleteLastChar, this));
14             this.$element.find('button#numpad-minus').click(_.bind(this.clickSwitchSign, this));
15             this.$element.find('button.number-char').click(_.bind(this.clickAppendNewChar, this));
16             this.$element.find('button.mode-button').click(_.bind(this.clickChangeMode, this));
17         },
18         clickDeleteLastChar: function() {
19             return this.state.deleteLastChar();
20         },
21         clickSwitchSign: function() {
22             return this.state.switchSign();
23         },
24         clickAppendNewChar: function(event) {
25             var newChar;
26             newChar = event.currentTarget.innerText || event.currentTarget.textContent;
27             return this.state.appendNewChar(newChar);
28         },
29         clickChangeMode: function(event) {
30             var newMode = event.currentTarget.attributes['data-mode'].nodeValue;
31             return this.state.changeMode(newMode);
32         },
33         changedMode: function() {
34             var mode = this.state.get('mode');
35             $('.selected-mode').removeClass('selected-mode');
36             $(_.str.sprintf('.mode-button[data-mode="%s"]', mode), this.$element).addClass('selected-mode');
37         },
38     });
39
40     // The paypad allows to select the payment method (cashRegisters) 
41     // used to pay the order.
42     module.PaypadWidget = module.PosBaseWidget.extend({
43         template: 'PaypadWidget',
44         renderElement: function() {
45             var self = this;
46             this._super();
47
48             this.pos.get('cashRegisters').each(function(cashRegister) {
49                 var button = new module.PaypadButtonWidget(self,{
50                     pos: self.pos,
51                     pos_widget : self.pos_widget,
52                     cashRegister: cashRegister,
53                 });
54                 button.appendTo(self.$element);
55             });
56         }
57     });
58
59     module.PaypadButtonWidget = module.PosBaseWidget.extend({
60         template: 'PaypadButtonWidget',
61         init: function(parent, options){
62             this._super(parent, options);
63             this.cashRegister = options.cashRegister;
64         },
65         renderElement: function() {
66             var self = this;
67             this._super();
68
69             this.$element.click(function(){
70                 if (self.pos.get('selectedOrder').get('screen') === 'receipt'){  //TODO Why ?
71                     console.warn('TODO should not get there...?');
72                     return;
73                 }
74                 self.pos.get('selectedOrder').addPaymentLine(self.cashRegister);
75                 self.pos_widget.screen_selector.set_current_screen('payment');
76             });
77         },
78     });
79
80     module.OrderlineWidget = module.PosBaseWidget.extend({
81         template: 'OrderlineWidget',
82         init: function(parent, options) {
83             this._super(parent,options);
84
85             this.model = options.model;
86             this.order = options.order;
87
88             this.model.bind('change', _.bind( function() {
89                 this.refresh();
90             }, this));
91         },
92         click_handler: function() {
93             this.order.selectLine(this.model);
94             this.on_selected();
95         },
96         renderElement: function() {
97             this._super();
98             this.$element.click(_.bind(this.click_handler, this));
99             if(this.model.is_selected()){
100                 this.$element.addClass('selected');
101             }
102         },
103         refresh: function(){
104             this.renderElement();
105             this.on_refresh();
106         },
107         on_selected: function() {},
108         on_refresh: function(){},
109     });
110     
111     module.OrderWidget = module.PosBaseWidget.extend({
112         template:'OrderWidget',
113         init: function(parent, options) {
114             this._super(parent,options);
115             this.display_mode = options.display_mode || 'numpad';   // 'maximized' | 'actionbar' | 'numpad'
116             this.set_numpad_state(options.numpadState);
117             this.pos.bind('change:selectedOrder', this.change_selected_order, this);
118             this.bind_orderline_events();
119         },
120         set_numpad_state: function(numpadState) {
121                 if (this.numpadState) {
122                         this.numpadState.unbind('set_value', this.set_value);
123                 }
124                 this.numpadState = numpadState;
125                 if (this.numpadState) {
126                         this.numpadState.bind('set_value', this.set_value, this);
127                         this.numpadState.reset();
128                 }
129         },
130         set_value: function(val) {
131                 var order = this.pos.get('selectedOrder');
132                 if (order.get('orderLines').length !== 0) {
133                 var mode = this.numpadState.get('mode');
134                 if( mode === 'quantity'){
135                     order.getSelectedLine().set_quantity(val);
136                 }else if( mode === 'discount'){
137                     order.getSelectedLine().set_discount(val);
138                 }else if( mode === 'list_price'){
139                     order.getSelectedLine().set_list_price(val);
140                 }
141                 } else {
142                     this.pos.get('selectedOrder').destroy();
143                 }
144         },
145         change_selected_order: function() {
146             this.currentOrderLines.unbind();
147             this.bind_orderline_events();
148             this.renderElement();
149         },
150         bind_orderline_events: function() {
151             this.currentOrderLines = (this.pos.get('selectedOrder')).get('orderLines');
152             this.currentOrderLines.bind('add', this.renderElement, this);
153             this.currentOrderLines.bind('remove', this.renderElement, this);
154         },
155         update_numpad: function() {
156                 var reset = false;
157                 if (this.selected_line !== this.pos.get('selectedOrder').getSelectedLine()) {
158                         reset = true;
159                 }
160                 this.selected_line = this.pos.get('selectedOrder').getSelectedLine();
161                 if (reset && this.numpadState)
162                         this.numpadState.reset();
163         },
164         renderElement: function() {
165             var self = this;
166             this._super();
167
168             if(this.display_mode === 'maximized'){
169                 $('.point-of-sale .order-container').css({'bottom':'0px'});
170             }else if(this.display_mode === 'actionbar'){
171                 $('.point-of-sale .order-container').css({'bottom':'105px'});
172             }else if(this.display_mode !== 'numpad'){
173                 console.error('ERROR: OrderWidget renderElement(): wrong display_mode:',this.display_mode);
174             }
175
176             var $content = this.$('.orderlines');
177             this.currentOrderLines.each(_.bind( function(orderLine) {
178                 var line = new module.OrderlineWidget(this, {
179                         model: orderLine,
180                         order: this.pos.get('selectedOrder'),
181                 });
182                 line.on_selected.add(_.bind(this.update_numpad, this));
183                 line.on_refresh.add(_.bind(this.update_summary, this));
184                 line.appendTo($content);
185             }, this));
186             this.update_numpad();
187             this.update_summary();
188
189             var position = this.scrollbar ? this.scrollbar.get_position() : 0;
190             var at_bottom = this.scrollbar ? this.scrollbar.is_at_bottom() : false;
191             
192             this.scrollbar = new module.ScrollbarWidget(this,{
193                 target_widget:   this,
194                 target_selector: '.order-scroller',
195                 name: 'order',
196                 track_bottom: true,
197                 on_show: function(){
198                     self.$('.order-scroller').css({'width':'89%'},100);
199                 },
200                 on_hide: function(){
201                     self.$('.order-scroller').css({'width':'100%'},100);
202                 },
203             });
204
205             this.scrollbar.replace(this.$('.placeholder-ScrollbarWidget'));
206             this.scrollbar.set_position(position);
207
208             if( at_bottom ){
209                 this.scrollbar.set_position(Number.MAX_VALUE, false);
210             }
211
212         },
213         update_summary: function(){
214             var order = this.pos.get('selectedOrder');
215             var total = order ? order.getTotal() : 0;
216             this.$('.summary .value.total').html(this.format_currency(total));
217         },
218         set_display_mode: function(mode){
219             if(this.display_mode !== mode){
220                 this.display_mode = mode;
221                 this.renderElement();
222             }
223         },
224     });
225
226     module.ProductWidget = module.PosBaseWidget.extend({
227         template: 'ProductWidget',
228         init: function(parent, options) {
229             this._super(parent,options);
230             this.model = options.model;
231             this.model.attributes.weight = options.weight;
232             this.next_screen = options.next_screen;
233             this.click_product_action = options.click_product_action;
234         },
235         add_to_order: function(event) {
236             /* Preserve the category URL */
237             event.preventDefault();
238             return (this.pos.get('selectedOrder')).addProduct(this.model);
239         },
240         set_weight: function(weight){
241             this.model.attributes.weight = weight;
242             this.renderElement();
243         },
244         get_image_url: function() {
245             return '/web/binary/image?session_id='+instance.connection.session_id+'&model=product.product&field=image&id='+this.model.get('id');
246         },
247         renderElement: function() {
248             this._super();
249             var self = this;
250             $("a", this.$element).click(function(e){
251                 if(self.click_product_action){
252                     self.click_product_action(self.model);
253                 }
254             });
255         },
256     });
257
258     module.PaymentlineWidget = module.PosBaseWidget.extend({
259         template: 'PaymentlineWidget',
260         init: function(parent, options) {
261             this._super(parent,options);
262             this.payment_line = options.payment_line;
263             this.payment_line.bind('change', this.changedAmount, this);
264         },
265         on_delete: function() {},
266         changeAmount: function(event) {
267             var newAmount;
268             newAmount = event.currentTarget.value;
269             if (newAmount && !isNaN(newAmount)) {
270                 this.amount = parseFloat(newAmount);
271                 this.payment_line.set_amount(this.amount);
272             }
273         },
274         changedAmount: function() {
275                 if (this.amount !== this.payment_line.get_amount())
276                         this.renderElement();
277         },
278         renderElement: function() {
279             this.name =   this.payment_line.get_cashregister().get('journal_id')[1];
280             this._super();
281             this.$('input').keyup(_.bind(this.changeAmount, this));
282             this.$('.delete-payment-line').click(this.on_delete);
283         },
284     });
285
286     module.OrderButtonWidget = module.PosBaseWidget.extend({
287         template:'OrderButtonWidget',
288         init: function(parent, options) {
289             this._super(parent,options);
290             this.order = options.order;
291             this.order.bind('destroy', _.bind( function() {
292                 this.destroy();
293             }, this));
294             this.pos.bind('change:selectedOrder', _.bind( function(pos) {
295                 var selectedOrder;
296                 selectedOrder = pos.get('selectedOrder');
297                 if (this.order === selectedOrder) {
298                     this.setButtonSelected();
299                 }
300             }, this));
301         },
302         renderElement:function(){
303             this._super();
304             this.$('button.select-order').click(_.bind(this.selectOrder, this));
305             this.$('button.close-order').click(_.bind(this.closeOrder, this));
306         },
307         selectOrder: function(event) {
308             this.pos.set({
309                 selectedOrder: this.order
310             });
311         },
312         setButtonSelected: function() {
313             $('.selected-order').removeClass('selected-order');
314             this.$element.addClass('selected-order');
315         },
316         closeOrder: function(event) {
317             this.order.destroy();
318         },
319     });
320
321     module.ActionButtonWidget = instance.web.Widget.extend({
322         template:'ActionButtonWidget',
323         icon_template:'ActionButtonWidgetWithIcon',
324         init: function(parent, options){
325             this._super(parent, options);
326             this.label = options.label || 'button';
327             this.rightalign = options.rightalign || false;
328             this.click_action = options.click;
329             if(options.icon){
330                 this.icon = options.icon;
331                 this.template = this.icon_template;
332             }
333         },
334         renderElement: function(){
335             this._super();
336             if(this.click_action){
337                 this.$element.click(_.bind(this.click_action, this));
338             }
339         },
340     });
341
342     module.ActionBarWidget = instance.web.Widget.extend({
343         template:'ActionBarWidget',
344         init: function(parent, options){
345             this._super(parent,options);
346             this.button_list = [];
347             this.fake_buttons  = {};
348             this.visibility = {};
349         },
350         set_element_visible: function(element, visible, action){
351             if(visible != this.visibility[element]){
352                 this.visibility[element] = visible;
353                 if(visible){
354                     this.$('.'+element).show();
355                 }else{
356                     this.$('.'+element).hide();
357                 }
358             }
359             if(visible && action){
360                 this.$('.'+element).off('click').click(action);
361             }
362         },
363         destroy_buttons:function(){
364             for(var i = 0; i < this.button_list.length; i++){
365                 this.button_list[i].destroy();
366             }
367             this.button_list = [];
368             return this;
369         },
370         get_button_count: function(){
371             return this.button_list.length;
372         },
373         add_new_button: function(button_options){
374             var button = new module.ActionButtonWidget(this,button_options);
375             this.button_list.push(button);
376             button.appendTo(this.$('.pos-actionbar-button-list'));
377             return button;
378         },
379         show:function(){
380             this.$element.show();
381         },
382         hide:function(){
383             this.$element.hide();
384         },
385     });
386
387     module.ProductCategoriesWidget = module.PosBaseWidget.extend({
388         template: 'ProductCategoriesWidget',
389         init: function(parent, options){
390             var self = this;
391             this._super(parent,options);
392             this.product_type = options.product_type || 'all';  // 'all' | 'weightable'
393             this.onlyWeightable = options.onlyWeightable || false;
394             this.category = this.pos.root_category;
395             this.breadcrumb = [];
396             this.subcategories = [];
397             this.set_category();
398         },
399
400         // changes the category. if undefined, sets to root category
401         set_category : function(category){
402             var db = this.pos.db;
403             if(!category){
404                 this.category = db.get_category_by_id(db.root_category_id);
405             }else{
406                 this.category = category;
407             }
408             this.breadcrumb = [];
409             var ancestors_ids = db.get_category_ancestors_ids(this.category.id);
410             for(var i = 1; i < ancestors_ids.length; i++){
411                 this.breadcrumb.push(db.get_category_by_id(ancestors_ids[i]));
412             }
413             if(this.category.id !== db.root_category_id){
414                 this.breadcrumb.push(this.category);
415             }
416             this.subcategories = db.get_category_by_id(db.get_category_childs_ids(this.category.id));
417         },
418
419         renderElement: function(){
420             var self = this;
421             this._super();
422
423             var hasimages = false;  //if none of the subcategories have images, we don't display buttons with icons
424             _.each(this.subcategories, function(category){
425                 if(category.image){
426                     hasimages = true;
427                 }
428             });
429
430             _.each(this.subcategories, function(category){
431                 if(hasimages){
432                     var button = QWeb.render('CategoryButton',{category:category});
433                 }else{
434                     var button = QWeb.render('CategorySimpleButton',{category:category});
435                 }
436                 button = _.str.trim(button);    // we remove whitespace between buttons to fix spacing
437
438                 $(button).appendTo(this.$('.category-list')).click(function(event){
439                     var id = category.id;
440                     var cat = self.pos.db.get_category_by_id(id);
441                     self.set_category(cat);
442                     self.renderElement();
443                     self.search_and_categories(cat);
444                 });
445             });
446             // breadcrumb click actions
447             this.$(".oe-pos-categories-list a").click(function(event){
448                 var id = $(event.target).data("category-id");
449                 var category = self.pos.db.get_category_by_id(id);
450                 self.set_category(category);
451                 self.renderElement();
452                 self.search_and_categories(category);
453             });
454             this.search_and_categories();
455         },
456         
457         set_product_type: function(type){       // 'all' | 'weightable'
458             this.product_type = type;
459             this.reset_category();
460         },
461
462         // resets the current category to the root category
463         reset_category: function(){
464             this.set_category();
465             this.renderElement();
466             this.search_and_categories();
467         },
468
469         // filters the products, and sets up the search callbacks
470         search_and_categories: function(category){
471             var self = this;
472
473             // find all products belonging to the current category
474             var products = this.pos.db.get_product_by_category(this.category.id);
475             self.pos.get('products').reset(products);
476
477             // filter the products according to the search string
478             this.$('.searchbox input').keyup(function(){
479                 query = $(this).val().toLowerCase();
480                 if(query){
481                     var products = self.pos.db.search_product_in_category(self.category.id, ['name','ean13'], query);
482                     self.pos.get('products').reset(products);
483                     self.$('.search-clear').fadeIn();
484                 }else{
485                     var products = self.pos.db.get_product_by_category(self.category.id);
486                     self.pos.get('products').reset(products);
487                     self.$('.search-clear').fadeOut();
488                 }
489             });
490
491             this.$('.searchbox input').click(function(){}); //Why ???
492
493             //reset the search when clicking on reset
494             this.$('.search-clear').click(function(){
495                 var products = self.pos.db.get_product_by_category(self.category.id);
496                 self.pos.get('products').reset(products);
497                 self.$('.searchbox input').val('').focus();
498                 self.$('.search-clear').fadeOut();
499             });
500         },
501     });
502
503     module.ProductListWidget = module.ScreenWidget.extend({
504         template:'ProductListWidget',
505         init: function(parent, options) {
506             var self = this;
507             this._super(parent,options);
508             this.model = options.model;
509             this.product_list = [];
510             this.weight = options.weight || 0;
511             this.show_scale = options.show_scale || false;
512             this.next_screen = options.next_screen || false;
513             this.click_product_action = options.click_product_action;
514
515             this.pos.get('products').bind('reset', function(){
516                 self.renderElement();
517             });
518         },
519         set_weight: function(weight){
520             for(var i = 0; i < this.product_list.length; i++){
521                 this.product_list[i].set_weight(weight);
522             }
523         },
524         renderElement: function() {
525             var self = this;
526             this._super();
527             this.product_list = []; 
528             this.pos.get('products')
529                 .chain()
530                 .map(function(product) {
531                     var product = new module.ProductWidget(self, {
532                             model: product,
533                             weight: self.weight,
534                             click_product_action: self.click_product_action,
535                     })
536                     self.product_list.push(product);
537                     return product;
538                 })
539                 .invoke('appendTo', this.$('.product-list'));
540
541             this.scrollbar = new module.ScrollbarWidget(this,{
542                 target_widget:   this,
543                 target_selector: '.product-list-scroller',
544                 on_show: function(){
545                     self.$('.product-list-scroller').css({'padding-right':'62px'},100);
546                 },
547                 on_hide: function(){
548                     self.$('.product-list-scroller').css({'padding-right':'0px'},100);
549                 },
550             });
551
552             this.scrollbar.replace(this.$('.placeholder-ScrollbarWidget'));
553
554         },
555     });
556
557     module.UsernameWidget = module.PosBaseWidget.extend({
558         template: 'UsernameWidget',
559         init: function(parent, options){
560             var options = options || {};
561             this._super(parent,options);
562             this.mode = options.mode || 'cashier';
563         },
564         set_user_mode: function(mode){
565             this.mode = mode;
566             this.refresh();
567         },
568         refresh: function(){
569             this.renderElement();
570         },
571         get_name: function(){
572             var user;
573             if(this.mode === 'cashier'){
574                 user = this.pos.get('cashier') || this.pos.get('user');
575             }else{
576                 user = this.pos.get('selectedOrder').get_client()  || this.pos.get('user');
577             }
578             if(user){
579                 return user.name;
580             }else{
581                 return "";
582             }
583         },
584     });
585
586     module.HeaderButtonWidget = module.PosBaseWidget.extend({
587         template: 'HeaderButtonWidget',
588         init: function(parent, options){
589             options = options || {};
590             this._super(parent, options);
591             this.action = options.action;
592             this.label   = options.label;
593         },
594         renderElement: function(){
595             var self = this;
596             this._super();
597             if(this.action){
598                 this.$element.click(function(){ self.action(); });
599             }
600         },
601         show: function(){ this.$element.show(); },
602         hide: function(){ this.$element.hide(); },
603
604     });
605
606
607 // ---------- Main Point of Sale Widget ----------
608
609     // this is used to notify the user that data is being synchronized on the network
610     module.SynchNotificationWidget = module.PosBaseWidget.extend({
611         template: "SynchNotificationWidget",
612         init: function(parent, options){
613             options = options || {};
614             this._super(parent, options);
615         },
616         renderElement: function() {
617             var self = this;
618             this._super();
619             this.$('.oe_pos_synch-notification-button').click(function(){
620                 self.pos.flush();
621             });
622         },
623         start: function(){
624             var self = this;
625             this.pos.bind('change:nbr_pending_operations', function(){
626                 self.renderElement();
627             });
628         },
629         get_nbr_pending: function(){
630             return this.pos.get('nbr_pending_operations');
631         },
632     });
633
634     // The PosWidget is the main widget that contains all other widgets in the PointOfSale.
635     // It is mainly composed of :
636     // - a header, containing the list of orders
637     // - a leftpane, containing the list of bought products (orderlines) 
638     // - a rightpane, containing the screens (see pos_screens.js)
639     // - an actionbar on the bottom, containing various action buttons
640     // - popups
641     // - an onscreen keyboard
642     // a screen_selector which controls the switching between screens and the showing/closing of popups
643
644     module.PosWidget = module.PosBaseWidget.extend({
645         template: 'PosWidget',
646         init: function() { 
647             this._super(arguments[0],{});
648             
649             this.pos = new module.PosModel(this.session);
650             this.pos_widget = this; //So that pos_widget's childs have pos_widget set automatically
651
652             this.numpad_visible = true;
653             this.left_action_bar_visible = true;
654             this.leftpane_visible = true;
655             this.leftpane_width   = '440px';
656             this.cashier_controls_visible = true;
657
658             /*
659              //Epileptic mode
660             setInterval(function(){ 
661                 $('body').css({'-webkit-filter':'hue-rotate('+Math.random()*360+'deg)' });
662             },100);
663             */
664             
665         },
666       
667         start: function() {
668             var self = this;
669             return self.pos.ready.then(function() {
670                 self.build_currency_template();
671                 self.renderElement();
672                 
673                 self.$('.neworder-button').click(function(){
674                     self.pos.add_new_order();
675                 });
676                 
677                 //when a new order is created, add an order button widget
678                 self.pos.get('orders').bind('add', function(new_order){
679                     var new_order_button = new module.OrderButtonWidget(null, {
680                         order: new_order,
681                         pos: self.pos
682                     });
683                     new_order_button.appendTo($('#orders'));
684                     new_order_button.selectOrder();
685                 }, self);
686
687                 self.pos.get('orders').add(new module.Order({ pos: self.pos }));
688
689                 self.build_widgets();
690
691                 self.screen_selector.set_default_screen();
692
693                 self.pos.barcode_reader.connect();
694
695                 instance.webclient.set_content_full_screen(true);
696
697                 if (!self.pos.get('pos_session')) {
698                     self.screen_selector.show_popup('error', 'Sorry, we could not create a user session');
699                 }else if(!self.pos.get('pos_config')){
700                     self.screen_selector.show_popup('error', 'Sorry, we could not find any PoS Configuration for this session');
701                 }
702             
703                 self.$('.loader').animate({opacity:0},1500,'swing',function(){self.$('.loader').hide();});
704                 self.$('.loader img').hide();
705
706                 if(jQuery.deparam(jQuery.param.querystring()).debug !== undefined){
707                     window.pos = self.pos;
708                     window.pos_widget = self.pos_widget;
709                 }
710
711             },function(){   // error when loading models data from the backend
712                 self.$('.loader img').hide();
713                 return new instance.web.Model("ir.model.data").get_func("search_read")([['name', '=', 'action_pos_session_opening']], ['res_id'])
714                     .pipe( _.bind(function(res){
715                         return instance.connection.rpc('/web/action/load', {'action_id': res[0]['res_id']})
716                             .pipe(_.bind(function(result){
717                                 var action = result.result;
718                                 this.do_action(action);
719                             }, this));
720                     }, self));
721             });
722         },
723         
724         // This method instantiates all the screens, widgets, etc. If you want to add new screens change the
725         // startup screen, etc, override this method.
726         build_widgets: function() {
727             var self = this;
728
729             // --------  Screens ---------
730
731             this.product_screen = new module.ProductScreenWidget(this,{});
732             this.product_screen.appendTo($('#rightpane'));
733
734             this.receipt_screen = new module.ReceiptScreenWidget(this, {});
735             this.receipt_screen.appendTo($('#rightpane'));
736
737             this.payment_screen = new module.PaymentScreenWidget(this, {});
738             this.payment_screen.appendTo($('#rightpane'));
739
740             this.welcome_screen = new module.WelcomeScreenWidget(this,{});
741             this.welcome_screen.appendTo($('#rightpane'));
742
743             this.client_payment_screen = new module.ClientPaymentScreenWidget(this, {});
744             this.client_payment_screen.appendTo($('#rightpane'));
745
746             this.scale_invite_screen = new module.ScaleInviteScreenWidget(this, {});
747             this.scale_invite_screen.appendTo($('#rightpane'));
748
749             this.scale_screen = new module.ScaleScreenWidget(this,{});
750             this.scale_screen.appendTo($('#rightpane'));
751
752             // --------  Popups ---------
753
754             this.help_popup = new module.HelpPopupWidget(this, {});
755             this.help_popup.appendTo($('.point-of-sale'));
756
757             this.error_popup = new module.ErrorPopupWidget(this, {});
758             this.error_popup.appendTo($('.point-of-sale'));
759
760             this.error_product_popup = new module.ErrorProductNotRecognizedPopupWidget(this, {});
761             this.error_product_popup.appendTo($('.point-of-sale'));
762
763             this.error_session_popup = new module.ErrorNoSessionPopupWidget(this, {});
764             this.error_session_popup.appendTo($('.point-of-sale'));
765
766             // --------  Misc ---------
767
768             this.notification = new module.SynchNotificationWidget(this,{});
769             this.notification.appendTo(this.$('#rightheader'));
770
771             this.username   = new module.UsernameWidget(this,{});
772             this.username.replace(this.$('.placeholder-UsernameWidget'));
773
774             this.action_bar = new module.ActionBarWidget(this);
775             this.action_bar.appendTo($(".point-of-sale #rightpane"));
776
777             this.left_action_bar = new module.ActionBarWidget(this);
778             this.left_action_bar.appendTo($(".point-of-sale #leftpane"));
779
780             this.paypad = new module.PaypadWidget(this, {});
781             this.paypad.replace($('#placeholder-PaypadWidget'));
782
783             this.numpad = new module.NumpadWidget(this);
784             this.numpad.replace($('#placeholder-NumpadWidget'));
785
786             this.order_widget = new module.OrderWidget(this, {});
787             this.order_widget.replace($('#placeholder-OrderWidget'));
788
789             this.onscreen_keyboard = new module.OnscreenKeyboardWidget(this, {
790                 'keyboard_model': 'simple'
791             });
792             this.onscreen_keyboard.appendTo($(".point-of-sale #content")); 
793
794             this.close_button = new module.HeaderButtonWidget(this,{
795                 label:'Close',
796                 action: function(){ self.try_close(); },
797             });
798             this.close_button.appendTo(this.$('#rightheader'));
799
800             this.client_button = new module.HeaderButtonWidget(this,{
801                 label:'Self-Checkout',
802                 action: function(){ self.screen_selector.set_user_mode('client'); },
803             });
804             this.client_button.appendTo(this.$('#rightheader'));
805
806             
807             // --------  Screen Selector ---------
808
809             this.screen_selector = new module.ScreenSelector({
810                 pos: this.pos,
811                 screen_set:{
812                     'products': this.product_screen,
813                     'payment' : this.payment_screen,
814                     'client_payment' : this.client_payment_screen,
815                     'scale_invite' : this.scale_invite_screen,
816                     'scale':    this.scale_screen,
817                     'receipt' : this.receipt_screen,
818                     'welcome' : this.welcome_screen,
819                 },
820                 popup_set:{
821                     'help': this.help_popup,
822                     'error': this.error_popup,
823                     'error-product': this.error_product_popup,
824                     'error-session': this.error_session_popup,
825                 },
826                 default_client_screen: 'welcome',
827                 default_cashier_screen: 'products',
828                 default_mode: this.pos.use_selfcheckout ?  'client' : 'cashier',
829             });
830
831         },
832
833         changed_pending_operations: function () {
834             var self = this;
835             this.synch_notification.on_change_nbr_pending(self.pos.get('nbr_pending_operations').length);
836         },
837         // shows or hide the numpad and related controls like the paypad.
838         set_numpad_visible: function(visible){
839             if(visible !== this.numpad_visible){
840                 this.numpad_visible = visible;
841                 if(visible){
842                     this.set_left_action_bar_visible(false);
843                     this.numpad.show();
844                     this.paypad.show();
845                     this.order_widget.set_display_mode('numpad');
846                 }else{
847                     this.numpad.hide();
848                     this.paypad.hide();
849                     if(this.order_widget.display_mode === 'numpad'){
850                         this.order_widget.set_display_mode('maximized');
851                     }
852                 }
853             }
854         },
855         set_left_action_bar_visible: function(visible){
856             if(visible !== this.left_action_bar_visible){
857                 this.left_action_bar_visible = visible;
858                 if(visible){
859                     this.set_numpad_visible(false);
860                     this.left_action_bar.show();
861                     this.order_widget.set_display_mode('actionbar');
862                 }else{
863                     this.left_action_bar.hide();
864                     if(this.order_widget.display_mode === 'actionbar'){
865                         this.order_widget.set_display_mode('maximized');
866                     }
867                 }
868             }
869         },
870
871         //shows or hide the leftpane (contains the list of orderlines, the numpad, the paypad, etc.)
872         set_leftpane_visible: function(visible){
873             if(visible !== this.leftpane_visible){
874                 this.leftpane_visible = visible;
875                 if(visible){
876                     $('#leftpane').show().animate({'width':this.leftpane_width},500,'swing');
877                     $('#rightpane').animate({'left':this.leftpane_width},500,'swing');
878                 }else{
879                     var leftpane = $('#leftpane');
880                     $('#leftpane').animate({'width':'0px'},500,'swing', function(){ leftpane.hide(); });
881                     $('#rightpane').animate({'left':'0px'},500,'swing');
882                 }
883             }
884         },
885         //shows or hide the controls in the PosWidget that are specific to the cashier ( Orders, close button, etc. ) 
886         set_cashier_controls_visible: function(visible){
887             if(visible !== this.cashier_controls_visible){
888                 this.cashier_controls_visible = visible;
889                 if(visible){
890                     $('#loggedas').show();
891                     $('#rightheader').show();
892                 }else{
893                     $('#loggedas').hide();
894                     $('#rightheader').hide();
895                 }
896             }
897         },
898         try_close: function() {
899             var self = this;
900             self.pos.flush().then(function() {
901                 self.close();
902             });
903         },
904         close: function() {
905             var self = this;
906             this.pos.barcode_reader.disconnect();
907             return new instance.web.Model("ir.model.data").get_func("search_read")([['name', '=', 'action_client_pos_menu']], ['res_id']).pipe(
908                     _.bind(function(res) {
909                 return this.rpc('/web/action/load', {'action_id': res[0]['res_id']}).pipe(_.bind(function(result) {
910                     var action = result.result;
911                     action.context = _.extend(action.context || {}, {'cancel_action': {type: 'ir.actions.client', tag: 'reload'}});
912                     //self.destroy();
913                     this.do_action(action);
914                 }, this));
915             }, this));
916         },
917         destroy: function() {
918             instance.webclient.set_content_full_screen(false);
919             self.pos = undefined;
920             this._super();
921         }
922     });
923 }