1 function openerp_pos_widgets(instance, module){ //module is instance.point_of_sale
2 var QWeb = instance.web.qweb;
3 var _t = instance.web._t;
5 module.DomCache = instance.web.Class.extend({
6 init: function(options){
7 options = options || {};
8 this.max_size = options.max_size || 2000;
11 this.access_time = {};
14 cache_node: function(key,node){
15 var cached = this.cache[key];
16 this.cache[key] = node;
17 this.access_time[key] = new Date().getTime();
20 while(this.size >= this.max_size){
21 var oldest_key = null;
22 var oldest_time = new Date().getTime();
23 for(var key in this.cache){
24 var time = this.access_time[key];
25 if(time <= oldest_time){
31 delete this.cache[oldest_key];
32 delete this.access_time[oldest_key];
39 get_node: function(key){
40 var cached = this.cache[key];
42 this.access_time[key] = new Date().getTime();
48 module.NumpadWidget = module.PosBaseWidget.extend({
49 template:'NumpadWidget',
50 init: function(parent, options) {
52 this.state = new module.NumpadState();
53 window.numpadstate = this.state;
57 this.state.bind('change:mode', this.changedMode, this);
59 this.$el.find('.numpad-backspace').click(_.bind(this.clickDeleteLastChar, this));
60 this.$el.find('.numpad-minus').click(_.bind(this.clickSwitchSign, this));
61 this.$el.find('.number-char').click(_.bind(this.clickAppendNewChar, this));
62 this.$el.find('.mode-button').click(_.bind(this.clickChangeMode, this));
64 clickDeleteLastChar: function() {
65 return this.state.deleteLastChar();
67 clickSwitchSign: function() {
68 return this.state.switchSign();
70 clickAppendNewChar: function(event) {
72 newChar = event.currentTarget.innerText || event.currentTarget.textContent;
73 return this.state.appendNewChar(newChar);
75 clickChangeMode: function(event) {
76 var newMode = event.currentTarget.attributes['data-mode'].nodeValue;
77 return this.state.changeMode(newMode);
79 changedMode: function() {
80 var mode = this.state.get('mode');
81 $('.selected-mode').removeClass('selected-mode');
82 $(_.str.sprintf('.mode-button[data-mode="%s"]', mode), this.$el).addClass('selected-mode');
86 // The paypad allows to select the payment method (cashregisters)
87 // used to pay the order.
88 module.PaypadWidget = module.PosBaseWidget.extend({
89 template: 'PaypadWidget',
90 renderElement: function() {
94 _.each(this.pos.cashregisters,function(cashregister) {
95 var button = new module.PaypadButtonWidget(self,{
97 pos_widget : self.pos_widget,
98 cashregister: cashregister,
100 button.appendTo(self.$el);
105 module.PaypadButtonWidget = module.PosBaseWidget.extend({
106 template: 'PaypadButtonWidget',
107 init: function(parent, options){
108 this._super(parent, options);
109 this.cashregister = options.cashregister;
111 renderElement: function() {
115 this.$el.click(function(){
116 if (self.pos.get('selectedOrder').get('screen') === 'receipt'){ //TODO Why ?
117 console.warn('TODO should not get there...?');
120 self.pos.get('selectedOrder').addPaymentline(self.cashregister);
121 self.pos_widget.screen_selector.set_current_screen('payment');
126 module.OrderWidget = module.PosBaseWidget.extend({
127 template:'OrderWidget',
128 init: function(parent, options) {
130 this._super(parent,options);
131 this.editable = false;
132 this.pos.bind('change:selectedOrder', this.change_selected_order, this);
133 this.bind_orderline_events();
134 this.line_click_handler = function(event){
138 self.pos.get('selectedOrder').selectLine(this.orderline);
139 self.pos_widget.numpad.state.reset();
142 enable_numpad: function(){
143 this.disable_numpad(); //ensure we don't register the callbacks twice
144 this.numpad_state = this.pos_widget.numpad.state;
145 if(this.numpad_state){
146 this.numpad_state.reset();
147 this.numpad_state.bind('set_value', this.set_value, this);
151 disable_numpad: function(){
152 if(this.numpad_state){
153 this.numpad_state.unbind('set_value', this.set_value);
154 this.numpad_state.reset();
157 set_editable: function(editable){
158 this.editable = editable;
160 this.enable_numpad();
162 this.disable_numpad();
163 this.pos.get('selectedOrder').deselectLine();
166 set_value: function(val) {
167 var order = this.pos.get('selectedOrder');
168 if (this.editable && order.getSelectedLine()) {
169 var mode = this.numpad_state.get('mode');
170 if( mode === 'quantity'){
171 order.getSelectedLine().set_quantity(val);
172 }else if( mode === 'discount'){
173 order.getSelectedLine().set_discount(val);
174 }else if( mode === 'price'){
175 order.getSelectedLine().set_unit_price(val);
179 change_selected_order: function() {
180 this.bind_orderline_events();
181 this.renderElement();
183 bind_orderline_events: function() {
184 var lines = this.pos.get('selectedOrder').get('orderLines');
186 lines.bind('add', function(){
187 this.numpad_state.reset();
188 this.renderElement(true);
190 lines.bind('remove', function(line){
191 this.remove_orderline(line);
192 this.numpad_state.reset();
193 this.update_summary();
195 lines.bind('change', function(line){
196 this.rerender_orderline(line);
197 this.update_summary();
200 render_orderline: function(orderline){
201 var el_str = openerp.qweb.render('Orderline',{widget:this, line:orderline});
202 var el_node = document.createElement('div');
203 el_node.innerHTML = _.str.trim(el_str);
204 el_node = el_node.childNodes[0];
205 el_node.orderline = orderline;
206 el_node.addEventListener('click',this.line_click_handler);
208 orderline.node = el_node;
211 remove_orderline: function(order_line){
212 if(this.pos.get('selectedOrder').get('orderLines').length === 0){
213 this.renderElement();
215 order_line.node.parentNode.removeChild(order_line.node);
218 rerender_orderline: function(order_line){
219 var node = order_line.node;
220 var replacement_line = this.render_orderline(order_line);
221 node.parentNode.replaceChild(replacement_line,node);
223 // overriding the openerp framework replace method for performance reasons
224 replace: function($target){
225 this.renderElement();
226 var target = $target[0];
227 target.parentNode.replaceChild(this.el,target);
229 renderElement: function(scrollbottom){
230 this.pos_widget.numpad.state.reset();
232 var order = this.pos.get('selectedOrder');
233 var orderlines = order.get('orderLines').models;
235 var el_str = openerp.qweb.render('OrderWidget',{widget:this, order:order, orderlines:orderlines});
236 var el_node = document.createElement('div');
237 el_node.innerHTML = _.str.trim(el_str);
238 el_node = el_node.childNodes[0];
240 var list_container = el_node.querySelector('.orderlines');
241 for(var i = 0, len = orderlines.length; i < len; i++){
242 var orderline = this.render_orderline(orderlines[i]);
243 list_container.appendChild(orderline);
246 if(this.el && this.el.parentNode){
247 this.el.parentNode.replaceChild(el_node,this.el);
250 this.update_summary();
253 this.el.querySelector('.order-scroller').scrollTop = 100 * orderlines.length;
256 update_summary: function(){
257 var order = this.pos.get('selectedOrder');
258 var total = order ? order.getTotalTaxIncluded() : 0;
259 var taxes = order ? total - order.getTotalTaxExcluded() : 0;
261 this.el.querySelector('.summary .total > .value').innerText = this.format_currency(total);
262 this.el.querySelector('.summary .total .subentry .value').innerText = this.format_currency(taxes);
266 module.OrderButtonWidget = module.PosBaseWidget.extend({
267 template:'OrderButtonWidget',
268 init: function(parent, options) {
269 this._super(parent,options);
272 this.order = options.order;
273 this.order.bind('destroy',this.destroy, this );
274 this.order.bind('change', this.renderElement, this );
275 this.pos.bind('change:selectedOrder', this.renderElement,this );
277 renderElement:function(){
280 this.$el.click(function(){
283 if( this.order === this.pos.get('selectedOrder') ){
284 this.$el.addClass('selected');
287 selectOrder: function(event) {
289 selectedOrder: this.order
293 this.order.unbind('destroy', this.destroy, this);
294 this.order.unbind('change', this.renderElement, this);
295 this.pos.unbind('change:selectedOrder', this.renderElement, this);
300 module.ActionButtonWidget = instance.web.Widget.extend({
301 template:'ActionButtonWidget',
302 icon_template:'ActionButtonWidgetWithIcon',
303 init: function(parent, options){
304 this._super(parent, options);
305 this.label = options.label || 'button';
306 this.rightalign = options.rightalign || false;
307 this.click_action = options.click;
308 this.disabled = options.disabled || false;
310 this.icon = options.icon;
311 this.template = this.icon_template;
314 set_disabled: function(disabled){
315 if(this.disabled != disabled){
316 this.disabled = !!disabled;
317 this.renderElement();
320 renderElement: function(){
322 if(this.click_action && !this.disabled){
323 this.$el.click(_.bind(this.click_action, this));
328 module.ActionBarWidget = instance.web.Widget.extend({
329 template:'ActionBarWidget',
330 init: function(parent, options){
331 this._super(parent,options);
332 this.button_list = [];
334 this.visibility = {};
336 set_element_visible: function(element, visible, action){
337 if(visible != this.visibility[element]){
338 this.visibility[element] = !!visible;
340 this.$('.'+element).removeClass('oe_hidden');
342 this.$('.'+element).addClass('oe_hidden');
345 if(visible && action){
346 this.action[element] = action;
347 this.$('.'+element).off('click').click(action);
350 set_button_disabled: function(name, disabled){
351 var b = this.buttons[name];
353 b.set_disabled(disabled);
356 destroy_buttons:function(){
357 for(var i = 0; i < this.button_list.length; i++){
358 this.button_list[i].destroy();
360 this.button_list = [];
364 get_button_count: function(){
365 return this.button_list.length;
367 add_new_button: function(button_options){
368 var button = new module.ActionButtonWidget(this,button_options);
369 this.button_list.push(button);
370 if(button_options.name){
371 this.buttons[button_options.name] = button;
373 button.appendTo(this.$('.pos-actionbar-button-list'));
377 this.$el.removeClass('oe_hidden');
380 this.$el.addClass('oe_hidden');
384 module.ProductCategoriesWidget = module.PosBaseWidget.extend({
385 template: 'ProductCategoriesWidget',
386 init: function(parent, options){
388 this._super(parent,options);
389 this.product_type = options.product_type || 'all'; // 'all' | 'weightable'
390 this.onlyWeightable = options.onlyWeightable || false;
391 this.category = this.pos.root_category;
392 this.breadcrumb = [];
393 this.subcategories = [];
394 this.product_list_widget = options.product_list_widget || null;
395 this.category_cache = new module.DomCache();
398 this.switch_category_handler = function(event){
399 self.set_category(self.pos.db.get_category_by_id(Number(this.dataset['categoryId'])));
400 self.renderElement();
403 this.clear_search_handler = function(event){
407 var search_timeout = null;
408 this.search_handler = function(event){
409 clearTimeout(search_timeout);
411 var query = this.value;
413 search_timeout = setTimeout(function(){
414 self.perform_search(self.category, query, event.which === 13);
419 // changes the category. if undefined, sets to root category
420 set_category : function(category){
421 var db = this.pos.db;
423 this.category = db.get_category_by_id(db.root_category_id);
425 this.category = category;
427 this.breadcrumb = [];
428 var ancestors_ids = db.get_category_ancestors_ids(this.category.id);
429 for(var i = 1; i < ancestors_ids.length; i++){
430 this.breadcrumb.push(db.get_category_by_id(ancestors_ids[i]));
432 if(this.category.id !== db.root_category_id){
433 this.breadcrumb.push(this.category);
435 this.subcategories = db.get_category_by_id(db.get_category_childs_ids(this.category.id));
438 get_image_url: function(category){
439 return window.location.origin + '/web/binary/image?model=pos.category&field=image_medium&id='+category.id;
442 render_category: function( category, with_image ){
443 var cached = this.category_cache.get_node(category.id);
446 var image_url = this.get_image_url(category);
447 var category_html = QWeb.render('CategoryButton',{
450 image_url: this.get_image_url(category),
452 category_html = _.str.trim(category_html);
453 var category_node = document.createElement('div');
454 category_node.innerHTML = category_html;
455 category_node = category_node.childNodes[0];
457 var category_html = QWeb.render('CategorySimpleButton',{
461 category_html = _.str.trim(category_html);
462 var category_node = document.createElement('div');
463 category_node.innerHTML = category_html;
464 category_node = category_node.childNodes[0];
466 this.category_cache.cache_node(category.id,category_node);
467 return category_node;
472 replace: function($target){
473 this.renderElement();
474 var target = $target[0];
475 target.parentNode.replaceChild(this.el,target);
478 renderElement: function(){
481 var el_str = openerp.qweb.render(this.template, {widget: this});
482 var el_node = document.createElement('div');
483 el_node.innerHTML = el_str;
484 el_node = el_node.childNodes[1];
486 if(this.el && this.el.parentNode){
487 this.el.parentNode.replaceChild(el_node,this.el);
492 var hasimages = false; //if none of the subcategories have images, we don't display buttons with icons
493 for(var i = 0; i < this.subcategories.length; i++){
494 if(this.subcategories[i].image){
500 var list_container = el_node.querySelector('.category-list');
501 for(var i = 0, len = this.subcategories.length; i < len; i++){
502 list_container.appendChild(this.render_category(this.subcategories[i],hasimages));
505 var buttons = el_node.querySelectorAll('.js-category-switch');
506 for(var i = 0; i < buttons.length; i++){
507 buttons[i].addEventListener('click',this.switch_category_handler);
510 var products = this.pos.db.get_product_by_category(this.category.id);
511 this.product_list_widget.set_product_list(products);
513 this.el.querySelector('.searchbox input').addEventListener('keyup',this.search_handler);
515 this.el.querySelector('.search-clear').addEventListener('click',this.clear_search_handler);
517 if(this.pos.config.iface_vkeyboard && this.pos_widget.onscreen_keyboard){
518 this.pos_widget.onscreen_keyboard.connect($(this.el.querySelector('.searchbox input')));
522 // resets the current category to the root category
523 reset_category: function(){
525 this.renderElement();
528 // empties the content of the search box
529 clear_search: function(){
530 var products = this.pos.db.get_product_by_category(this.category.id);
531 this.product_list_widget.set_product_list(products);
532 var input = this.el.querySelector('.searchbox input');
536 perform_search: function(category, query, buy_result){
538 var products = this.pos.db.search_product_in_category(category.id,query)
539 if(buy_result && products.length === 1){
540 this.pos.get('selectedOrder').addProduct(products[0]);
543 this.product_list_widget.set_product_list(products);
546 var products = this.pos.db.get_product_by_category(this.category.id);
547 this.product_list_widget.set_product_list(products);
553 module.ProductListWidget = module.PosBaseWidget.extend({
554 template:'ProductListWidget',
555 init: function(parent, options) {
557 this._super(parent,options);
558 this.model = options.model;
559 this.productwidgets = [];
560 this.weight = options.weight || 0;
561 this.show_scale = options.show_scale || false;
562 this.next_screen = options.next_screen || false;
564 this.click_product_handler = function(event){
565 var product = self.pos.db.get_product_by_id(this.dataset['productId']);
566 options.click_product_action(product);
569 this.product_list = options.product_list || [];
570 this.product_cache = new module.DomCache();
572 set_product_list: function(product_list){
573 this.product_list = product_list;
574 this.renderElement();
576 get_product_image_url: function(product){
577 return window.location.origin + '/web/binary/image?model=product.product&field=image_medium&id='+product.id;
579 replace: function($target){
580 this.renderElement();
581 var target = $target[0];
582 target.parentNode.replaceChild(this.el,target);
585 render_product: function(product){
586 var cached = this.product_cache.get_node(product.id);
588 var image_url = this.get_product_image_url(product);
589 var product_html = QWeb.render('Product',{
592 image_url: this.get_product_image_url(product),
594 var product_node = document.createElement('div');
595 product_node.innerHTML = product_html;
596 product_node = product_node.childNodes[1];
597 this.product_cache.cache_node(product.id,product_node);
603 renderElement: function() {
607 var el_str = openerp.qweb.render(this.template, {widget: this});
608 var el_node = document.createElement('div');
609 el_node.innerHTML = el_str;
610 el_node = el_node.childNodes[1];
612 if(this.el && this.el.parentNode){
613 this.el.parentNode.replaceChild(el_node,this.el);
617 var list_container = el_node.querySelector('.product-list');
618 for(var i = 0, len = this.product_list.length; i < len; i++){
619 var product_node = this.render_product(this.product_list[i]);
620 product_node.addEventListener('click',this.click_product_handler);
621 list_container.appendChild(product_node);
626 module.UsernameWidget = module.PosBaseWidget.extend({
627 template: 'UsernameWidget',
628 init: function(parent, options){
629 var options = options || {};
630 this._super(parent,options);
631 this.mode = options.mode || 'cashier';
633 set_user_mode: function(mode){
638 this.renderElement();
640 get_name: function(){
642 if(this.mode === 'cashier'){
643 user = this.pos.cashier || this.pos.user;
645 user = this.pos.get('selectedOrder').get_client() || this.pos.user;
655 module.HeaderButtonWidget = module.PosBaseWidget.extend({
656 template: 'HeaderButtonWidget',
657 init: function(parent, options){
658 options = options || {};
659 this._super(parent, options);
660 this.action = options.action;
661 this.label = options.label;
663 renderElement: function(){
667 this.$el.click(function(){
672 show: function(){ this.$el.removeClass('oe_hidden'); },
673 hide: function(){ this.$el.addClass('oe_hidden'); },
676 // The debug widget lets the user control and monitor the hardware and software status
677 // without the use of the proxy
678 module.DebugWidget = module.PosBaseWidget.extend({
679 template: "DebugWidget",
681 admin_badge: '0410100000006',
682 client_badge: '0420200000004',
683 invalid_ean: '1232456',
684 soda_33cl: '5449000000996',
685 oranges_kg: '2100002031410',
686 lemon_price: '2301000001560',
687 unknown_product: '9900000000004',
691 'scan_item_error_unrecognized',
700 init: function(parent,options){
701 this._super(parent,options);
704 this.minimized = false;
706 // for dragging the debug widget around
707 this.dragging = false;
708 this.dragpos = {x:0, y:0};
710 function eventpos(event){
711 if(event.touches && event.touches[0]){
712 return {x: event.touches[0].screenX, y: event.touches[0].screenY};
714 return {x: event.screenX, y: event.screenY};
718 this.dragend_handler = function(event){
719 self.dragging = false;
721 this.dragstart_handler = function(event){
722 self.dragging = true;
723 self.dragpos = eventpos(event);
725 this.dragmove_handler = function(event){
727 var top = this.offsetTop;
728 var left = this.offsetLeft;
729 var pos = eventpos(event);
730 var dx = pos.x - self.dragpos.x;
731 var dy = pos.y - self.dragpos.y;
735 this.style.right = 'auto';
736 this.style.bottom = 'auto';
737 this.style.left = left + dx + 'px';
738 this.style.top = top + dy + 'px';
740 event.preventDefault();
741 event.stopPropagation();
747 this.el.addEventListener('mouseleave', this.dragend_handler);
748 this.el.addEventListener('mouseup', this.dragend_handler);
749 this.el.addEventListener('touchend', this.dragend_handler);
750 this.el.addEventListener('touchcancel',this.dragend_handler);
751 this.el.addEventListener('mousedown', this.dragstart_handler);
752 this.el.addEventListener('touchstart', this.dragstart_handler);
753 this.el.addEventListener('mousemove', this.dragmove_handler);
754 this.el.addEventListener('touchmove', this.dragmove_handler);
756 this.$('.toggle').click(function(){
757 var content = self.$('.content');
760 content.animate({'height':'0'},200);
762 content.css({'height':'auto'});
764 self.minimized = !self.minimized;
766 this.$('.button.accept_payment').click(function(){
767 self.pos.proxy.debug_accept_payment();
769 this.$('.button.reject_payment').click(function(){
770 self.pos.proxy.debug_reject_payment();
772 this.$('.button.set_weight').click(function(){
773 var kg = Number(self.$('input.weight').val());
775 self.pos.proxy.debug_set_weight(kg);
778 this.$('.button.reset_weight').click(function(){
779 self.$('input.weight').val('');
780 self.pos.proxy.debug_reset_weight();
782 this.$('.button.custom_ean').click(function(){
783 var ean = self.pos.barcode_reader.sanitize_ean(self.$('input.ean').val() || '0');
784 self.$('input.ean').val(ean);
785 self.pos.barcode_reader.scan('ean13',ean);
787 this.$('.button.reference').click(function(){
788 self.pos.barcode_reader.scan('reference',self.$('input.ean').val());
790 _.each(this.eans, function(ean, name){
791 self.$('.button.'+name).click(function(){
792 self.$('input.ean').val(ean);
793 self.pos.barcode_reader.scan('ean13',ean);
796 _.each(this.events, function(name){
797 self.pos.proxy.add_notification(name,function(){
798 self.$('.event.'+name).stop().clearQueue().css({'background-color':'#6CD11D'});
799 self.$('.event.'+name).animate({'background-color':'#1E1E1E'},2000);
802 self.pos.proxy.add_notification('help_needed',function(){
803 self.$('.status.help_needed').addClass('on');
805 self.pos.proxy.add_notification('help_canceled',function(){
806 self.$('.status.help_needed').removeClass('on');
808 self.pos.proxy.add_notification('transaction_start',function(){
809 self.$('.status.transaction').addClass('on');
811 self.pos.proxy.add_notification('transaction_end',function(){
812 self.$('.status.transaction').removeClass('on');
814 self.pos.proxy.add_notification('weighting_start',function(){
815 self.$('.status.weighting').addClass('on');
817 self.pos.proxy.add_notification('weighting_end',function(){
818 self.$('.status.weighting').removeClass('on');
823 // ---------- Main Point of Sale Widget ----------
825 module.StatusWidget = module.PosBaseWidget.extend({
826 status: ['connected','connecting','disconnected','warning'],
827 set_status: function(status,msg){
829 for(var i = 0; i < this.status.length; i++){
830 this.$('.js_'+this.status[i]).addClass('oe_hidden');
832 this.$('.js_'+status).removeClass('oe_hidden');
835 this.$('.js_msg').removeClass('oe_hidden').html(msg);
837 this.$('.js_msg').addClass('oe_hidden').html('');
842 // this is used to notify the user that data is being synchronized on the network
843 module.SynchNotificationWidget = module.StatusWidget.extend({
844 template: 'SynchNotificationWidget',
847 this.pos.bind('change:synch', function(pos,synch){
848 self.set_status(synch.state, synch.pending);
850 this.$el.click(function(){
856 // this is used to notify the user if the pos is connected to the proxy
857 module.ProxyStatusWidget = module.StatusWidget.extend({
858 template: 'ProxyStatusWidget',
859 set_smart_status: function(status){
860 if(status.status === 'connected'){
863 if(this.pos.config.iface_scan_via_proxy){
864 var scanner = status.drivers.scanner ? status.drivers.scanner.status : false;
865 if( scanner != 'connected' && scanner != 'connecting'){
867 msg += _t('Scanner');
870 if( this.pos.config.iface_print_via_proxy ||
871 this.pos.config.iface_cashdrawer ){
872 var printer = status.drivers.escpos ? status.drivers.escpos.status : false;
873 if( printer != 'connected' && printer != 'connecting'){
875 msg = msg ? msg + ' & ' : msg;
876 msg += _t('Printer');
879 msg = msg ? msg + ' ' + _t('Offline') : msg;
880 this.set_status(warning ? 'warning' : 'connected', msg);
882 this.set_status(status.status,'');
888 this.set_smart_status(this.pos.get('proxy_status'));
890 this.pos.bind('change:proxy_status', function(pos,status){
891 self.set_smart_status(status);
894 this.$el.click(function(){
895 self.pos.connect_to_proxy();
901 // The PosWidget is the main widget that contains all other widgets in the PointOfSale.
902 // It is mainly composed of :
903 // - a header, containing the list of orders
904 // - a leftpane, containing the list of bought products (orderlines)
905 // - a rightpane, containing the screens (see pos_screens.js)
906 // - an actionbar on the bottom, containing various action buttons
908 // - an onscreen keyboard
909 // a screen_selector which controls the switching between screens and the showing/closing of popups
911 module.PosWidget = module.PosBaseWidget.extend({
912 template: 'PosWidget',
914 this._super(arguments[0],{});
916 this.pos = new module.PosModel(this.session,{pos_widget:this});
917 this.pos_widget = this; //So that pos_widget's childs have pos_widget set automatically
919 this.numpad_visible = true;
920 this.left_action_bar_visible = true;
921 this.leftpane_visible = true;
922 this.leftpane_width = '440px';
923 this.cashier_controls_visible = true;
925 FastClick.attach(document.body);
929 disable_rubberbanding: function(){
930 // prevent the pos body from being scrollable.
931 document.body.addEventListener('touchmove',function(event){
932 var node = event.target;
934 if(node.classList && node.classList.contains('touch-scrollable')){
937 node = node.parentNode;
939 event.preventDefault();
945 return self.pos.ready.done(function() {
946 $('.oe_tooltip').remove(); // remove tooltip from the start session button
948 // remove default webclient handlers that induce click delay
953 $(self.$el).parent().off();
955 $('.oe_web_client').off();
956 $('.openerp_webclient_container').off();
958 self.build_currency_template();
959 self.renderElement();
961 self.$('.neworder-button').click(function(){
962 self.pos.add_new_order();
965 self.$('.deleteorder-button').click(function(){
966 self.pos.delete_current_order();
969 //when a new order is created, add an order button widget
970 self.pos.get('orders').bind('add', function(new_order){
971 var new_order_button = new module.OrderButtonWidget(null, {
975 new_order_button.appendTo(this.$('.orders'));
976 new_order_button.selectOrder();
979 self.pos.add_new_order();
981 self.build_widgets();
983 if(self.pos.config.iface_big_scrollbars){
984 self.$el.addClass('big-scrollbars');
987 self.screen_selector.set_default_screen();
989 self.pos.barcode_reader.connect();
991 instance.webclient.set_content_full_screen(true);
993 if (!self.pos.session) {
994 self.screen_selector.show_popup('error', 'Sorry, we could not create a user session');
995 }else if(!self.pos.config){
996 self.screen_selector.show_popup('error', 'Sorry, we could not find any PoS Configuration for this session');
999 self.$('.loader').animate({opacity:0},1500,'swing',function(){self.$('.loader').addClass('oe_hidden');});
1003 }).fail(function(){ // error when loading models data from the backend
1004 return new instance.web.Model("ir.model.data").get_func("search_read")([['name', '=', 'action_pos_session_opening']], ['res_id'])
1005 .pipe( _.bind(function(res){
1006 return instance.session.rpc('/web/action/load', {'action_id': res[0]['res_id']})
1007 .pipe(_.bind(function(result){
1008 var action = result.result;
1009 this.do_action(action);
1014 loading_progress: function(fac){
1015 this.$('.loader .loader-feedback').removeClass('oe_hidden');
1016 this.$('.loader .progress').css({'width': ''+Math.floor(fac*100)+'%'});
1018 loading_message: function(msg,progress){
1019 this.$('.loader .loader-feedback').removeClass('oe_hidden');
1020 this.$('.loader .message').text(msg);
1021 if(typeof progress !== 'undefined'){
1022 this.loading_progress(progress);
1025 loading_skip: function(callback){
1027 this.$('.loader .loader-feedback').removeClass('oe_hidden');
1028 this.$('.loader .button.skip').removeClass('oe_hidden');
1029 this.$('.loader .button.skip').off('click');
1030 this.$('.loader .button.skip').click(callback);
1032 this.$('.loader .button.skip').addClass('oe_hidden');
1035 // This method instantiates all the screens, widgets, etc. If you want to add new screens change the
1036 // startup screen, etc, override this method.
1037 build_widgets: function() {
1040 // -------- Screens ---------
1042 this.product_screen = new module.ProductScreenWidget(this,{});
1043 this.product_screen.appendTo(this.$('.screens'));
1045 this.receipt_screen = new module.ReceiptScreenWidget(this, {});
1046 this.receipt_screen.appendTo(this.$('.screens'));
1048 this.payment_screen = new module.PaymentScreenWidget(this, {});
1049 this.payment_screen.appendTo(this.$('.screens'));
1051 this.welcome_screen = new module.WelcomeScreenWidget(this,{});
1052 this.welcome_screen.appendTo(this.$('.screens'));
1054 this.client_payment_screen = new module.ClientPaymentScreenWidget(this, {});
1055 this.client_payment_screen.appendTo(this.$('.screens'));
1057 this.scale_invite_screen = new module.ScaleInviteScreenWidget(this, {});
1058 this.scale_invite_screen.appendTo(this.$('.screens'));
1060 this.scale_screen = new module.ScaleScreenWidget(this,{});
1061 this.scale_screen.appendTo(this.$('.screens'));
1063 // -------- Popups ---------
1065 this.help_popup = new module.HelpPopupWidget(this, {});
1066 this.help_popup.appendTo(this.$el);
1068 this.error_popup = new module.ErrorPopupWidget(this, {});
1069 this.error_popup.appendTo(this.$el);
1071 this.error_product_popup = new module.ProductErrorPopupWidget(this, {});
1072 this.error_product_popup.appendTo(this.$el);
1074 this.error_session_popup = new module.ErrorSessionPopupWidget(this, {});
1075 this.error_session_popup.appendTo(this.$el);
1077 this.choose_receipt_popup = new module.ChooseReceiptPopupWidget(this, {});
1078 this.choose_receipt_popup.appendTo(this.$el);
1080 this.error_negative_price_popup = new module.ErrorNegativePricePopupWidget(this, {});
1081 this.error_negative_price_popup.appendTo(this.$el);
1083 this.error_no_client_popup = new module.ErrorNoClientPopupWidget(this, {});
1084 this.error_no_client_popup.appendTo(this.$el);
1086 this.error_invoice_transfer_popup = new module.ErrorInvoiceTransferPopupWidget(this, {});
1087 this.error_invoice_transfer_popup.appendTo(this.$el);
1089 // -------- Misc ---------
1091 this.close_button = new module.HeaderButtonWidget(this,{
1093 action: function(){ self.close(); },
1095 this.close_button.appendTo(this.$('.pos-rightheader'));
1097 this.notification = new module.SynchNotificationWidget(this,{});
1098 this.notification.appendTo(this.$('.pos-rightheader'));
1100 if(this.pos.config.use_proxy){
1101 this.proxy_status = new module.ProxyStatusWidget(this,{});
1102 this.proxy_status.appendTo(this.$('.pos-rightheader'));
1105 this.username = new module.UsernameWidget(this,{});
1106 this.username.replace(this.$('.placeholder-UsernameWidget'));
1108 this.action_bar = new module.ActionBarWidget(this);
1109 this.action_bar.replace(this.$(".placeholder-RightActionBar"));
1111 this.left_action_bar = new module.ActionBarWidget(this);
1112 this.left_action_bar.replace(this.$('.placeholder-LeftActionBar'));
1114 this.paypad = new module.PaypadWidget(this, {});
1115 this.paypad.replace(this.$('.placeholder-PaypadWidget'));
1117 this.numpad = new module.NumpadWidget(this);
1118 this.numpad.replace(this.$('.placeholder-NumpadWidget'));
1120 this.order_widget = new module.OrderWidget(this, {});
1121 this.order_widget.replace(this.$('.placeholder-OrderWidget'));
1123 this.onscreen_keyboard = new module.OnscreenKeyboardWidget(this, {
1124 'keyboard_model': 'simple'
1126 this.onscreen_keyboard.replace(this.$('.placeholder-OnscreenKeyboardWidget'));
1128 this.client_button = new module.HeaderButtonWidget(this,{
1129 label: _t('Self-Checkout'),
1130 action: function(){ self.screen_selector.set_user_mode('client'); },
1132 this.client_button.appendTo(this.$('.pos-rightheader'));
1135 // -------- Screen Selector ---------
1137 this.screen_selector = new module.ScreenSelector({
1140 'products': this.product_screen,
1141 'payment' : this.payment_screen,
1142 'client_payment' : this.client_payment_screen,
1143 'scale_invite' : this.scale_invite_screen,
1144 'scale': this.scale_screen,
1145 'receipt' : this.receipt_screen,
1146 'welcome' : this.welcome_screen,
1149 'help': this.help_popup,
1150 'error': this.error_popup,
1151 'error-product': this.error_product_popup,
1152 'error-session': this.error_session_popup,
1153 'error-negative-price': this.error_negative_price_popup,
1154 'choose-receipt': this.choose_receipt_popup,
1155 'error-no-client': this.error_no_client_popup,
1156 'error-invoice-transfer': this.error_invoice_transfer_popup,
1158 default_client_screen: 'welcome',
1159 default_cashier_screen: 'products',
1160 default_mode: this.pos.config.iface_self_checkout ? 'client' : 'cashier',
1164 this.debug_widget = new module.DebugWidget(this);
1165 this.debug_widget.appendTo(this.$('.pos-content'));
1168 this.disable_rubberbanding();
1172 changed_pending_operations: function () {
1174 this.synch_notification.on_change_nbr_pending(self.pos.get('nbr_pending_operations').length);
1176 // shows or hide the numpad and related controls like the paypad.
1177 set_numpad_visible: function(visible){
1178 if(visible !== this.numpad_visible){
1179 this.numpad_visible = visible;
1181 this.set_left_action_bar_visible(false);
1190 set_left_action_bar_visible: function(visible){
1191 if(visible !== this.left_action_bar_visible){
1192 this.left_action_bar_visible = visible;
1194 this.set_numpad_visible(false);
1195 this.left_action_bar.show();
1197 this.left_action_bar.hide();
1201 //shows or hide the leftpane (contains the list of orderlines, the numpad, the paypad, etc.)
1202 set_leftpane_visible: function(visible){
1203 if(visible !== this.leftpane_visible){
1204 this.leftpane_visible = visible;
1206 this.$('.pos-leftpane').removeClass('oe_hidden').animate({'width':this.leftpane_width},500,'swing');
1207 this.$('.pos-rightpane').animate({'left':this.leftpane_width},500,'swing');
1209 var leftpane = this.$('.pos-leftpane');
1210 leftpane.animate({'width':'0px'},500,'swing', function(){ leftpane.addClass('oe_hidden'); });
1211 this.$('.pos-rightpane').animate({'left':'0px'},500,'swing');
1215 //shows or hide the controls in the PosWidget that are specific to the cashier ( Orders, close button, etc. )
1216 set_cashier_controls_visible: function(visible){
1217 if(visible !== this.cashier_controls_visible){
1218 this.cashier_controls_visible = visible;
1220 this.$('.pos-rightheader').removeClass('oe_hidden');
1222 this.$('.pos-rightheader').addClass('oe_hidden');
1230 return new instance.web.Model("ir.model.data").get_func("search_read")([['name', '=', 'action_client_pos_menu']], ['res_id']).pipe(function(res) {
1231 window.location = '/web#action=' + res[0]['res_id'];
1235 var draft_order = _.find( self.pos.get('orders').models, function(order){
1236 return order.get('orderLines').length !== 0 && order.get('paymentLines').length === 0;
1239 if (confirm(_t("Pending orders will be lost.\nAre you sure you want to leave this session?"))) {
1246 destroy: function() {
1248 instance.webclient.set_content_full_screen(false);