1 function openerp_pos_widgets(instance, module){ //module is instance.point_of_sale
2 var QWeb = instance.web.qweb,
5 // The ImageCache is used to hide the latency of the application cache on-disk access in chrome
6 // that causes annoying flickering on product pictures. Why the hell a simple access to
7 // the application cache involves such latency is beyond me, hopefully one day this can be
9 module.ImageCache = instance.web.Class.extend({
10 init: function(options){
11 options = options || {};
12 this.max_size = options.max_size || 500;
15 this.access_time = {};
18 get_image_uncached: function(url){
19 var img = new Image();
23 // returns a DOM Image object from an url, and cache the last 500 (by default) results
24 get_image: function(url){
25 var cached = this.cache[url];
27 this.access_time[url] = (new Date()).getTime();
30 var img = new Image();
32 while(this.size >= this.max_size){
34 var oldestTime = (new Date()).getTime();
35 for(var url in this.cache){
36 var time = this.access_time[url];
37 if(time <= oldestTime){
43 delete this.cache[oldestUrl];
44 delete this.access_time[oldestUrl];
48 this.cache[url] = img;
49 this.access_time[url] = (new Date()).getTime();
56 module.NumpadWidget = module.PosBaseWidget.extend({
57 template:'NumpadWidget',
58 init: function(parent, options) {
60 this.state = new module.NumpadState();
63 this.state.bind('change:mode', this.changedMode, this);
65 this.$el.find('.numpad-backspace').click(_.bind(this.clickDeleteLastChar, this));
66 this.$el.find('.numpad-minus').click(_.bind(this.clickSwitchSign, this));
67 this.$el.find('.number-char').click(_.bind(this.clickAppendNewChar, this));
68 this.$el.find('.mode-button').click(_.bind(this.clickChangeMode, this));
70 clickDeleteLastChar: function() {
71 return this.state.deleteLastChar();
73 clickSwitchSign: function() {
74 return this.state.switchSign();
76 clickAppendNewChar: function(event) {
78 newChar = event.currentTarget.innerText || event.currentTarget.textContent;
79 return this.state.appendNewChar(newChar);
81 clickChangeMode: function(event) {
82 var newMode = event.currentTarget.attributes['data-mode'].nodeValue;
83 return this.state.changeMode(newMode);
85 changedMode: function() {
86 var mode = this.state.get('mode');
87 $('.selected-mode').removeClass('selected-mode');
88 $(_.str.sprintf('.mode-button[data-mode="%s"]', mode), this.$el).addClass('selected-mode');
92 // The paypad allows to select the payment method (cashRegisters)
93 // used to pay the order.
94 module.PaypadWidget = module.PosBaseWidget.extend({
95 template: 'PaypadWidget',
96 renderElement: function() {
100 this.pos.get('cashRegisters').each(function(cashRegister) {
101 var button = new module.PaypadButtonWidget(self,{
103 pos_widget : self.pos_widget,
104 cashRegister: cashRegister,
106 button.appendTo(self.$el);
111 module.PaypadButtonWidget = module.PosBaseWidget.extend({
112 template: 'PaypadButtonWidget',
113 init: function(parent, options){
114 this._super(parent, options);
115 this.cashRegister = options.cashRegister;
117 renderElement: function() {
121 this.$el.click(function(){
122 if (self.pos.get('selectedOrder').get('screen') === 'receipt'){ //TODO Why ?
123 console.warn('TODO should not get there...?');
126 self.pos.get('selectedOrder').addPaymentLine(self.cashRegister);
127 self.pos_widget.screen_selector.set_current_screen('payment');
132 module.OrderlineWidget = module.PosBaseWidget.extend({
133 template: 'OrderlineWidget',
134 init: function(parent, options) {
135 this._super(parent,options);
137 this.model = options.model;
138 this.order = options.order;
140 this.model.bind('change', this.refresh, this);
142 renderElement: function() {
145 this.$el.click(function(){
146 self.order.selectLine(self.model);
147 self.trigger('order_line_selected');
149 if(this.model.is_selected()){
150 this.$el.addClass('selected');
154 this.renderElement();
155 this.trigger('order_line_refreshed');
158 this.model.unbind('change',this.refresh,this);
163 module.OrderWidget = module.PosBaseWidget.extend({
164 template:'OrderWidget',
165 init: function(parent, options) {
166 this._super(parent,options);
167 this.display_mode = options.display_mode || 'numpad'; // 'maximized' | 'actionbar' | 'numpad'
168 this.set_numpad_state(options.numpadState);
169 this.pos.bind('change:selectedOrder', this.change_selected_order, this);
170 this.bind_orderline_events();
171 this.orderlinewidgets = [];
173 set_numpad_state: function(numpadState) {
174 if (this.numpadState) {
175 this.numpadState.unbind('set_value', this.set_value);
177 this.numpadState = numpadState;
178 if (this.numpadState) {
179 this.numpadState.bind('set_value', this.set_value, this);
180 this.numpadState.reset();
183 set_value: function(val) {
184 var order = this.pos.get('selectedOrder');
185 if (order.get('orderLines').length !== 0) {
186 var mode = this.numpadState.get('mode');
187 if( mode === 'quantity'){
188 order.getSelectedLine().set_quantity(val);
189 }else if( mode === 'discount'){
190 order.getSelectedLine().set_discount(val);
191 }else if( mode === 'price'){
192 order.getSelectedLine().set_unit_price(val);
196 change_selected_order: function() {
197 this.currentOrderLines.unbind();
198 this.bind_orderline_events();
199 this.renderElement();
201 bind_orderline_events: function() {
202 this.currentOrderLines = (this.pos.get('selectedOrder')).get('orderLines');
203 this.currentOrderLines.bind('add', function(){ this.renderElement(true);}, this);
204 this.currentOrderLines.bind('remove', this.renderElement, this);
206 update_numpad: function() {
207 this.selected_line = this.pos.get('selectedOrder').getSelectedLine();
208 if (this.numpadState)
209 this.numpadState.reset();
211 renderElement: function(goto_bottom) {
213 var scroller = this.$('.order-scroller')[0];
214 var scrollbottom = true;
217 var overflow_bottom = scroller.scrollHeight - scroller.clientHeight;
218 scrollTop = scroller.scrollTop;
219 if( !goto_bottom && scrollTop < 0.9 * overflow_bottom){
220 scrollbottom = false;
225 // freeing subwidgets
227 for(var i = 0, len = this.orderlinewidgets.length; i < len; i++){
228 this.orderlinewidgets[i].destroy();
230 this.orderlinewidgets = [];
232 var $content = this.$('.orderlines');
233 this.currentOrderLines.each(_.bind( function(orderLine) {
234 var line = new module.OrderlineWidget(this, {
236 order: this.pos.get('selectedOrder'),
238 line.on('order_line_selected', self, self.update_numpad);
239 line.on('order_line_refreshed', self, self.update_summary);
240 line.appendTo($content);
241 self.orderlinewidgets.push(line);
243 this.update_numpad();
244 this.update_summary();
246 scroller = this.$('.order-scroller')[0];
248 //scroller.scrollTop = 1000000;
251 scroller.scrollTop = scroller.scrollHeight - scroller.clientHeight;
253 scroller.scrollTop = scrollTop;
257 update_summary: function(){
258 var order = this.pos.get('selectedOrder');
259 var total = order ? order.getTotalTaxIncluded() : 0;
260 var taxes = order ? total - order.getTotalTaxExcluded() : 0;
261 this.$('.summary .total > .value').html(this.format_currency(total));
262 this.$('.summary .total .subentry .value').html(this.format_currency(taxes));
264 set_display_mode: function(mode){
265 if(this.display_mode !== mode){
266 this.display_mode = mode;
267 this.renderElement();
273 module.PaymentlineWidget = module.PosBaseWidget.extend({
274 template: 'PaymentlineWidget',
275 init: function(parent, options) {
276 this._super(parent,options);
277 this.payment_line = options.payment_line;
278 this.payment_line.bind('change', this.changedAmount, this);
280 changeAmount: function(event) {
281 var newAmount = event.currentTarget.value;
282 var amount = parseFloat(newAmount);
284 this.amount = amount;
285 this.payment_line.set_amount(amount);
288 checkAmount: function(e){
289 if (e.which !== 0 && e.charCode !== 0) {
290 if(isNaN(String.fromCharCode(e.charCode))){
291 return (String.fromCharCode(e.charCode) === "." && e.currentTarget.value.toString().split(".").length < 2)?true:false;
296 changedAmount: function() {
297 if (this.amount !== this.payment_line.get_amount()){
298 this.renderElement();
301 renderElement: function() {
303 this.name = this.payment_line.get_cashregister().get('journal_id')[1];
305 this.$('input').keypress(_.bind(this.checkAmount, this))
306 .keyup(function(event){
307 self.changeAmount(event);
309 this.$('.delete-payment-line').click(function() {
310 self.trigger('delete_payment_line', self);
314 var val = this.$('input')[0].value;
315 this.$('input')[0].focus();
316 this.$('input')[0].value = val;
317 this.$('input')[0].select();
321 module.OrderButtonWidget = module.PosBaseWidget.extend({
322 template:'OrderButtonWidget',
323 init: function(parent, options) {
324 this._super(parent,options);
327 this.order = options.order;
328 this.order.bind('destroy',this.destroy, this );
329 this.order.bind('change', this.renderElement, this );
330 this.pos.bind('change:selectedOrder', this.renderElement,this );
332 renderElement:function(){
335 this.$el.click(function(){
338 if( this.order === this.pos.get('selectedOrder') ){
339 this.$el.addClass('selected-order');
342 selectOrder: function(event) {
344 selectedOrder: this.order
348 this.order.unbind('destroy', this.destroy, this);
349 this.order.unbind('change', this.renderElement, this);
350 this.pos.unbind('change:selectedOrder', this.renderElement, this);
355 module.ActionButtonWidget = instance.web.Widget.extend({
356 template:'ActionButtonWidget',
357 icon_template:'ActionButtonWidgetWithIcon',
358 init: function(parent, options){
359 this._super(parent, options);
360 this.label = options.label || 'button';
361 this.rightalign = options.rightalign || false;
362 this.click_action = options.click;
363 this.disabled = options.disabled || false;
365 this.icon = options.icon;
366 this.template = this.icon_template;
369 set_disabled: function(disabled){
370 if(this.disabled != disabled){
371 this.disabled = !!disabled;
372 this.renderElement();
375 renderElement: function(){
377 if(this.click_action && !this.disabled){
378 this.$el.click(_.bind(this.click_action, this));
383 module.ActionBarWidget = instance.web.Widget.extend({
384 template:'ActionBarWidget',
385 init: function(parent, options){
386 this._super(parent,options);
387 this.button_list = [];
389 this.visibility = {};
391 set_element_visible: function(element, visible, action){
392 if(visible != this.visibility[element]){
393 this.visibility[element] = !!visible;
395 this.$('.'+element).removeClass('oe_hidden');
397 this.$('.'+element).addClass('oe_hidden');
400 if(visible && action){
401 this.action[element] = action;
402 this.$('.'+element).off('click').click(action);
405 set_button_disabled: function(name, disabled){
406 var b = this.buttons[name];
408 b.set_disabled(disabled);
411 destroy_buttons:function(){
412 for(var i = 0; i < this.button_list.length; i++){
413 this.button_list[i].destroy();
415 this.button_list = [];
419 get_button_count: function(){
420 return this.button_list.length;
422 add_new_button: function(button_options){
423 var button = new module.ActionButtonWidget(this,button_options);
424 this.button_list.push(button);
425 if(button_options.name){
426 this.buttons[button_options.name] = button;
428 button.appendTo(this.$('.pos-actionbar-button-list'));
432 this.$el.removeClass('oe_hidden');
435 this.$el.addClass('oe_hidden');
439 module.CategoryButton = module.PosBaseWidget.extend({
441 module.ProductCategoriesWidget = module.PosBaseWidget.extend({
442 template: 'ProductCategoriesWidget',
443 init: function(parent, options){
445 this._super(parent,options);
446 this.product_type = options.product_type || 'all'; // 'all' | 'weightable'
447 this.onlyWeightable = options.onlyWeightable || false;
448 this.category = this.pos.root_category;
449 this.breadcrumb = [];
450 this.subcategories = [];
454 // changes the category. if undefined, sets to root category
455 set_category : function(category){
456 var db = this.pos.db;
458 this.category = db.get_category_by_id(db.root_category_id);
460 this.category = category;
462 this.breadcrumb = [];
463 var ancestors_ids = db.get_category_ancestors_ids(this.category.id);
464 for(var i = 1; i < ancestors_ids.length; i++){
465 this.breadcrumb.push(db.get_category_by_id(ancestors_ids[i]));
467 if(this.category.id !== db.root_category_id){
468 this.breadcrumb.push(this.category);
470 this.subcategories = db.get_category_by_id(db.get_category_childs_ids(this.category.id));
473 get_image_url: function(category){
474 return instance.session.url('/web/binary/image', {model: 'pos.category', field: 'image_medium', id: category.id});
477 renderElement: function(){
481 var hasimages = false; //if none of the subcategories have images, we don't display buttons with icons
482 _.each(this.subcategories, function(category){
488 _.each(this.subcategories, function(category){
490 var button = QWeb.render('CategoryButton',{category:category});
491 var button = _.str.trim(button);
492 var button = $(button);
493 button.find('img').replaceWith(self.pos_widget.image_cache.get_image(self.get_image_url(category)));
495 var button = QWeb.render('CategorySimpleButton',{category:category});
496 button = _.str.trim(button); // we remove whitespace between buttons to fix spacing
497 var button = $(button);
500 button.appendTo(this.$('.category-list')).click(function(event){
501 var id = category.id;
502 var cat = self.pos.db.get_category_by_id(id);
503 self.set_category(cat);
504 self.renderElement();
507 // breadcrumb click actions
508 this.$(".oe-pos-categories-list a").click(function(event){
509 var id = $(event.target).data("category-id");
510 var category = self.pos.db.get_category_by_id(id);
511 self.set_category(category);
512 self.renderElement();
515 this.search_and_categories();
517 if(this.pos.iface_vkeyboard && this.pos_widget.onscreen_keyboard){
518 this.pos_widget.onscreen_keyboard.connect(this.$('.searchbox input'));
522 set_product_type: function(type){ // 'all' | 'weightable'
523 this.product_type = type;
524 this.reset_category();
527 // resets the current category to the root category
528 reset_category: function(){
530 this.renderElement();
533 // empties the content of the search box
534 clear_search: function(){
535 var products = this.pos.db.get_product_by_category(this.category.id);
536 this.pos.get('products').reset(products);
537 this.$('.searchbox input').val('').focus();
538 this.$('.search-clear').fadeOut();
541 // filters the products, and sets up the search callbacks
542 search_and_categories: function(category){
545 // find all products belonging to the current category
546 var products = this.pos.db.get_product_by_category(this.category.id);
547 self.pos.get('products').reset(products);
550 var searchtimeout = null;
551 // filter the products according to the search string
552 this.$('.searchbox input').keyup(function(event){
554 clearTimeout(searchtimeout);
556 var query = $(this).val().toLowerCase();
558 searchtimeout = setTimeout(function(){
560 if(event.which === 13){
561 if( self.pos.get('products').size() === 1 ){
562 self.pos.get('selectedOrder').addProduct(self.pos.get('products').at(0));
566 var products = self.pos.db.search_product_in_category(self.category.id, query);
567 self.pos.get('products').reset(products);
568 self.$('.search-clear').fadeIn();
571 var products = self.pos.db.get_product_by_category(self.category.id);
572 self.pos.get('products').reset(products);
573 self.$('.search-clear').fadeOut();
578 //reset the search when clicking on reset
579 this.$('.search-clear').click(function(){
585 module.ProductListWidget = module.ScreenWidget.extend({
586 template:'ProductListWidget',
587 init: function(parent, options) {
589 this._super(parent,options);
590 this.model = options.model;
591 this.productwidgets = [];
592 this.weight = options.weight || 0;
593 this.show_scale = options.show_scale || false;
594 this.next_screen = options.next_screen || false;
595 this.click_product_action = options.click_product_action;
597 this.pos.get('products').bind('reset', function(){
598 self.renderElement();
601 renderElement: function() {
605 var products = this.pos.get('products').models || [];
607 _.each(products,function(product,i){
608 var $product = $(QWeb.render('Product',{ widget:self, product: products[i] }));
609 $product.find('img').replaceWith(self.pos_widget.image_cache.get_image(products[i].get_image_url()));
610 $product.appendTo(self.$('.product-list'));
612 this.$el.delegate('a','click',function(){
613 self.click_product_action(new module.Product(self.pos.db.get_product_by_id(+$(this).data('product-id'))));
619 module.UsernameWidget = module.PosBaseWidget.extend({
620 template: 'UsernameWidget',
621 init: function(parent, options){
622 var options = options || {};
623 this._super(parent,options);
624 this.mode = options.mode || 'cashier';
626 set_user_mode: function(mode){
631 this.renderElement();
633 get_name: function(){
635 if(this.mode === 'cashier'){
636 user = this.pos.get('cashier') || this.pos.get('user');
638 user = this.pos.get('selectedOrder').get_client() || this.pos.get('user');
648 module.HeaderButtonWidget = module.PosBaseWidget.extend({
649 template: 'HeaderButtonWidget',
650 init: function(parent, options){
651 options = options || {};
652 this._super(parent, options);
653 this.action = options.action;
654 this.label = options.label;
656 renderElement: function(){
660 this.$el.click(function(){
665 show: function(){ this.$el.removeClass('oe_hidden'); },
666 hide: function(){ this.$el.addClass('oe_hidden'); },
669 // The debug widget lets the user control and monitor the hardware and software status
670 // without the use of the proxy
671 module.DebugWidget = module.PosBaseWidget.extend({
672 template: "DebugWidget",
674 admin_badge: '0410100000006',
675 client_badge: '0420200000004',
676 invalid_ean: '1232456',
677 soda_33cl: '5449000000996',
678 oranges_kg: '2100002031410',
679 lemon_price: '2301000001560',
680 unknown_product: '9900000000004',
684 'scan_item_error_unrecognized',
696 this.$el.draggable();
697 this.$('.toggle').click(function(){
698 var content = self.$('.content');
701 content.animate({'height':'0'},200);
703 content.css({'height':'auto'});
705 self.minimized = !self.minimized;
707 this.$('.button.accept_payment').click(function(){
708 self.pos.proxy.debug_accept_payment();
710 this.$('.button.reject_payment').click(function(){
711 self.pos.proxy.debug_reject_payment();
713 this.$('.button.set_weight').click(function(){
714 var kg = Number(self.$('input.weight').val());
716 self.pos.proxy.debug_set_weight(kg);
719 this.$('.button.reset_weight').click(function(){
720 self.$('input.weight').val('');
721 self.pos.proxy.debug_reset_weight();
723 this.$('.button.custom_ean').click(function(){
724 var ean = self.pos.barcode_reader.sanitize_ean(self.$('input.ean').val() || '0');
725 self.$('input.ean').val(ean);
726 self.pos.barcode_reader.scan('ean13',ean);
728 this.$('.button.reference').click(function(){
729 self.pos.barcode_reader.scan('reference',self.$('input.ean').val());
731 _.each(this.eans, function(ean, name){
732 self.$('.button.'+name).click(function(){
733 self.$('input.ean').val(ean);
734 self.pos.barcode_reader.scan('ean13',ean);
737 _.each(this.events, function(name){
738 self.pos.proxy.add_notification(name,function(){
739 self.$('.event.'+name).stop().clearQueue().css({'background-color':'#6CD11D'});
740 self.$('.event.'+name).animate({'background-color':'#1E1E1E'},2000);
743 self.pos.proxy.add_notification('help_needed',function(){
744 self.$('.status.help_needed').addClass('on');
746 self.pos.proxy.add_notification('help_canceled',function(){
747 self.$('.status.help_needed').removeClass('on');
749 self.pos.proxy.add_notification('transaction_start',function(){
750 self.$('.status.transaction').addClass('on');
752 self.pos.proxy.add_notification('transaction_end',function(){
753 self.$('.status.transaction').removeClass('on');
755 self.pos.proxy.add_notification('weighting_start',function(){
756 self.$('.status.weighting').addClass('on');
758 self.pos.proxy.add_notification('weighting_end',function(){
759 self.$('.status.weighting').removeClass('on');
764 // ---------- Main Point of Sale Widget ----------
766 // this is used to notify the user that data is being synchronized on the network
767 module.SynchNotificationWidget = module.PosBaseWidget.extend({
768 template: "SynchNotificationWidget",
769 init: function(parent, options){
770 options = options || {};
771 this._super(parent, options);
773 renderElement: function() {
776 this.$el.click(function(){
782 this.pos.bind('change:nbr_pending_operations', function(){
783 self.renderElement();
786 get_nbr_pending: function(){
787 return this.pos.get('nbr_pending_operations');
792 // The PosWidget is the main widget that contains all other widgets in the PointOfSale.
793 // It is mainly composed of :
794 // - a header, containing the list of orders
795 // - a leftpane, containing the list of bought products (orderlines)
796 // - a rightpane, containing the screens (see pos_screens.js)
797 // - an actionbar on the bottom, containing various action buttons
799 // - an onscreen keyboard
800 // a screen_selector which controls the switching between screens and the showing/closing of popups
802 module.PosWidget = module.PosBaseWidget.extend({
803 template: 'PosWidget',
805 this._super(arguments[0],{});
807 instance.web.blockUI();
809 this.pos = new module.PosModel(this.session);
810 this.pos.pos_widget = this;
811 this.pos_widget = this; //So that pos_widget's childs have pos_widget set automatically
813 this.numpad_visible = true;
814 this.left_action_bar_visible = true;
815 this.leftpane_visible = true;
816 this.leftpane_width = '440px';
817 this.cashier_controls_visible = true;
818 this.image_cache = new module.ImageCache(); // for faster products image display
820 FastClick.attach(document.body);
826 return self.pos.ready.done(function() {
827 $('.oe_tooltip').remove(); // remove tooltip from the start session button
829 self.build_currency_template();
830 self.renderElement();
832 self.$('.neworder-button').click(function(){
833 self.pos.add_new_order();
836 self.$('.deleteorder-button').click(function(){
837 self.pos.delete_current_order();
840 $('body').on('keyup',function(event){
841 if(event.which === 13){
842 self.set_fullscreen();
846 //when a new order is created, add an order button widget
847 self.pos.get('orders').bind('add', function(new_order){
848 var new_order_button = new module.OrderButtonWidget(null, {
852 new_order_button.appendTo(this.$('.orders'));
853 new_order_button.selectOrder();
856 self.pos.add_new_order();
858 self.build_widgets();
860 if(self.pos.iface_big_scrollbars){
861 self.$el.addClass('big-scrollbars');
864 self.screen_selector.set_default_screen();
867 self.pos.barcode_reader.connect();
869 if(!$('#oe-fullscreenwidget-viewport').length){
870 $('head').append('<meta id="oe-pos-viewport" name="viewport" content=" width=1024px; user-scalable=no;">');
871 $('head').append('<meta id="oe-pos-apple-mobile" name="apple-mobile-web-capable" content="yes">');
874 $('.oe_leftbar').addClass('oe_hidden');
876 instance.webclient.set_content_full_screen(true);
878 if (!self.pos.get('pos_session')) {
879 self.screen_selector.show_popup('error', 'Sorry, we could not create a user session');
880 }else if(!self.pos.get('pos_config')){
881 self.screen_selector.show_popup('error', 'Sorry, we could not find any PoS Configuration for this session');
884 instance.web.unblockUI();
885 self.$('.loader').animate({opacity:0},1500,'swing',function(){self.$('.loader').addClass('oe_hidden');});
889 }).fail(function(){ // error when loading models data from the backend
890 instance.web.unblockUI();
891 return new instance.web.Model("ir.model.data").get_func("search_read")([['name', '=', 'action_pos_session_opening']], ['res_id'])
892 .pipe( _.bind(function(res){
893 return instance.session.rpc('/web/action/load', {'action_id': res[0]['res_id']})
894 .pipe(_.bind(function(result){
895 var action = result.result;
896 this.do_action(action);
902 // This method instantiates all the screens, widgets, etc. If you want to add new screens change the
903 // startup screen, etc, override this method.
904 build_widgets: function() {
907 // -------- Screens ---------
909 this.product_screen = new module.ProductScreenWidget(this,{});
910 this.product_screen.appendTo(this.$('.screens'));
912 this.receipt_screen = new module.ReceiptScreenWidget(this, {});
913 this.receipt_screen.appendTo(this.$('.screens'));
915 this.payment_screen = new module.PaymentScreenWidget(this, {});
916 this.payment_screen.appendTo(this.$('.screens'));
918 this.welcome_screen = new module.WelcomeScreenWidget(this,{});
919 this.welcome_screen.appendTo(this.$('.screens'));
921 this.client_payment_screen = new module.ClientPaymentScreenWidget(this, {});
922 this.client_payment_screen.appendTo(this.$('.screens'));
924 this.scale_invite_screen = new module.ScaleInviteScreenWidget(this, {});
925 this.scale_invite_screen.appendTo(this.$('.screens'));
927 this.scale_screen = new module.ScaleScreenWidget(this,{});
928 this.scale_screen.appendTo(this.$('.screens'));
930 // -------- Popups ---------
932 this.help_popup = new module.HelpPopupWidget(this, {});
933 this.help_popup.appendTo(this.$el);
935 this.error_popup = new module.ErrorPopupWidget(this, {});
936 this.error_popup.appendTo(this.$el);
938 this.error_product_popup = new module.ProductErrorPopupWidget(this, {});
939 this.error_product_popup.appendTo(this.$el);
941 this.error_session_popup = new module.ErrorSessionPopupWidget(this, {});
942 this.error_session_popup.appendTo(this.$el);
944 this.choose_receipt_popup = new module.ChooseReceiptPopupWidget(this, {});
945 this.choose_receipt_popup.appendTo(this.$el);
947 this.error_negative_price_popup = new module.ErrorNegativePricePopupWidget(this, {});
948 this.error_negative_price_popup.appendTo(this.$el);
950 this.error_no_client_popup = new module.ErrorNoClientPopupWidget(this, {});
951 this.error_no_client_popup.appendTo(this.$el);
953 this.error_invoice_transfer_popup = new module.ErrorInvoiceTransferPopupWidget(this, {});
954 this.error_invoice_transfer_popup.appendTo(this.$el);
956 // -------- Misc ---------
958 this.notification = new module.SynchNotificationWidget(this,{});
959 this.notification.appendTo(this.$('.pos-rightheader'));
961 this.username = new module.UsernameWidget(this,{});
962 this.username.replace(this.$('.placeholder-UsernameWidget'));
964 this.action_bar = new module.ActionBarWidget(this);
965 this.action_bar.replace(this.$(".placeholder-RightActionBar"));
967 this.left_action_bar = new module.ActionBarWidget(this);
968 this.left_action_bar.replace(this.$('.placeholder-LeftActionBar'));
970 this.paypad = new module.PaypadWidget(this, {});
971 this.paypad.replace(this.$('.placeholder-PaypadWidget'));
973 this.numpad = new module.NumpadWidget(this);
974 this.numpad.replace(this.$('.placeholder-NumpadWidget'));
976 this.order_widget = new module.OrderWidget(this, {});
977 this.order_widget.replace(this.$('.placeholder-OrderWidget'));
979 this.onscreen_keyboard = new module.OnscreenKeyboardWidget(this, {
980 'keyboard_model': 'simple'
982 this.onscreen_keyboard.replace(this.$('.placeholder-OnscreenKeyboardWidget'));
984 this.close_button = new module.HeaderButtonWidget(this,{
986 action: function(){ self.close(); },
988 this.close_button.appendTo(this.$('.pos-rightheader'));
990 this.client_button = new module.HeaderButtonWidget(this,{
991 label: _t('Self-Checkout'),
992 action: function(){ self.screen_selector.set_user_mode('client'); },
994 this.client_button.appendTo(this.$('.pos-rightheader'));
997 // -------- Screen Selector ---------
999 this.screen_selector = new module.ScreenSelector({
1002 'products': this.product_screen,
1003 'payment' : this.payment_screen,
1004 'client_payment' : this.client_payment_screen,
1005 'scale_invite' : this.scale_invite_screen,
1006 'scale': this.scale_screen,
1007 'receipt' : this.receipt_screen,
1008 'welcome' : this.welcome_screen,
1011 'help': this.help_popup,
1012 'error': this.error_popup,
1013 'error-product': this.error_product_popup,
1014 'error-session': this.error_session_popup,
1015 'error-negative-price': this.error_negative_price_popup,
1016 'choose-receipt': this.choose_receipt_popup,
1017 'error-no-client': this.error_no_client_popup,
1018 'error-invoice-transfer': this.error_invoice_transfer_popup,
1020 default_client_screen: 'welcome',
1021 default_cashier_screen: 'products',
1022 default_mode: this.pos.iface_self_checkout ? 'client' : 'cashier',
1026 this.debug_widget = new module.DebugWidget(this);
1027 this.debug_widget.appendTo(this.$('.pos-content'));
1031 changed_pending_operations: function () {
1033 this.synch_notification.on_change_nbr_pending(self.pos.get('nbr_pending_operations').length);
1035 // shows or hide the numpad and related controls like the paypad.
1036 set_numpad_visible: function(visible){
1037 if(visible !== this.numpad_visible){
1038 this.numpad_visible = visible;
1040 this.set_left_action_bar_visible(false);
1043 this.order_widget.set_display_mode('numpad');
1047 if(this.order_widget.display_mode === 'numpad'){
1048 this.order_widget.set_display_mode('maximized');
1053 set_left_action_bar_visible: function(visible){
1054 if(visible !== this.left_action_bar_visible){
1055 this.left_action_bar_visible = visible;
1057 this.set_numpad_visible(false);
1058 this.left_action_bar.show();
1059 this.order_widget.set_display_mode('actionbar');
1061 this.left_action_bar.hide();
1062 if(this.order_widget.display_mode === 'actionbar'){
1063 this.order_widget.set_display_mode('maximized');
1068 set_fullscreen: function(){
1069 if(this.el.webkitRequestFullscreen){
1070 this.el.webkitRequestFullscreen();
1073 //shows or hide the leftpane (contains the list of orderlines, the numpad, the paypad, etc.)
1074 set_leftpane_visible: function(visible){
1075 if(visible !== this.leftpane_visible){
1076 this.leftpane_visible = visible;
1078 this.$('.pos-leftpane').removeClass('oe_hidden').animate({'width':this.leftpane_width},500,'swing');
1079 this.$('.pos-rightpane').animate({'left':this.leftpane_width},500,'swing');
1081 var leftpane = this.$('.pos-leftpane');
1082 leftpane.animate({'width':'0px'},500,'swing', function(){ leftpane.addClass('oe_hidden'); });
1083 this.$('.pos-rightpane').animate({'left':'0px'},500,'swing');
1087 //shows or hide the controls in the PosWidget that are specific to the cashier ( Orders, close button, etc. )
1088 set_cashier_controls_visible: function(visible){
1089 if(visible !== this.cashier_controls_visible){
1090 this.cashier_controls_visible = visible;
1092 this.$('.pos-rightheader').removeClass('oe_hidden');
1094 this.$('.pos-rightheader').addClass('oe_hidden');
1102 return new instance.web.Model("ir.model.data").get_func("search_read")([['name', '=', 'action_client_pos_menu']], ['res_id']).pipe(
1103 _.bind(function(res) {
1104 return this.rpc('/web/action/load', {'action_id': res[0]['res_id']}).pipe(_.bind(function(result) {
1105 var action = result;
1106 action.context = _.extend(action.context || {}, {'cancel_action': {type: 'ir.actions.client', tag: 'reload'}});
1108 this.do_action(action);
1113 var draft_order = _.find( self.pos.get('orders').models, function(order){
1114 return order.get('orderLines').length !== 0 && order.get('paymentLines').length === 0;
1117 if (confirm(_t("Pending orders will be lost.\nAre you sure you want to leave this session?"))) {
1124 destroy: function() {
1126 instance.webclient.set_content_full_screen(false);
1127 $('.oe_leftbar').removeClass('oe_hidden');
1128 $('#oe-pos-viewport').remove();
1129 $('#oe-pos-apple-mobile').remove();