1 function openerp_pos_widgets(instance, module){ //module is instance.point_of_sale
2 var QWeb = instance.web.qweb;
4 module.NumpadWidget = instance.web.Widget.extend({
5 template:'NumpadWidget',
6 init: function(parent, options) {
8 this.state = new module.NumpadState();
11 this.state.bind('change:mode', this.changedMode, this);
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));
18 clickDeleteLastChar: function() {
19 return this.state.deleteLastChar();
21 clickSwitchSign: function() {
22 return this.state.switchSign();
24 clickAppendNewChar: function(event) {
26 newChar = event.currentTarget.innerText || event.currentTarget.textContent;
27 return this.state.appendNewChar(newChar);
29 clickChangeMode: function(event) {
30 var newMode = event.currentTarget.attributes['data-mode'].nodeValue;
31 return this.state.changeMode(newMode);
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');
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() {
47 console.log('PaypadWidget:',this);
49 this.pos.get('cashRegisters').each(function(cashRegister) {
50 var button = new module.PaypadButtonWidget(self,{
52 pos_widget : self.pos_widget,
53 cashRegister: cashRegister,
55 button.appendTo(self.$element);
60 module.PaypadButtonWidget = module.PosBaseWidget.extend({
61 template: 'PaypadButtonWidget',
62 init: function(parent, options){
63 this._super(parent, options);
64 this.cashRegister = options.cashRegister;
66 renderElement: function() {
70 this.$element.click(function(){
71 if (self.pos.get('selectedOrder').get('screen') === 'receipt'){ //TODO Why ?
72 console.log('TODO should not get there...?');
75 self.pos.get('selectedOrder').addPaymentLine(self.cashRegister);
76 self.pos_widget.screen_selector.set_current_screen('payment');
81 // ---------- "Shopping Carts" ----------
83 module.OrderlineWidget = module.PosBaseWidget.extend({
84 template: 'OrderlineWidget',
85 init: function(parent, options) {
86 this._super(parent,options);
87 this.model = options.model;
88 this.model.bind('change', _.bind( function() {
91 this.model.bind('remove', _.bind( function() {
92 this.$element.remove();
94 this.order = options.order;
97 this.$element.click(_.bind(this.clickHandler, this));
100 clickHandler: function() {
103 renderElement: function() {
107 refresh: function() {
108 this.renderElement();
109 var heights = _.map(this.$element.prevAll(), function(el) {return $(el).outerHeight();});
110 heights.push($('#current-order thead').outerHeight());
111 var position = _.reduce(heights, function(memo, num){ return memo + num; }, 0);
112 $('#current-order').scrollTop(position);
115 $('tr.selected').removeClass('selected');
116 this.$element.addClass('selected');
117 this.order.selected = this.model;
120 on_selected: function() {},
123 module.OrderWidget = module.PosBaseWidget.extend({
124 template:'OrderWidget',
125 init: function(parent, options) {
126 this._super(parent,options);
127 console.log('OrderWidget init:',options)
128 this.set_numpad_state(options.numpadState);
129 this.pos.bind('change:selectedOrder', this.change_selected_order, this);
130 this.bind_orderline_events();
132 set_numpad_state: function(numpadState) {
133 if (this.numpadState) {
134 this.numpadState.unbind('set_value', this.set_value);
136 this.numpadState = numpadState;
137 if (this.numpadState) {
138 this.numpadState.bind('set_value', this.set_value, this);
139 this.numpadState.reset();
142 set_value: function(val) {
144 param[this.numpadState.get('mode')] = val;
145 var order = this.pos.get('selectedOrder');
146 if (order.get('orderLines').length !== 0) {
147 order.selected.set(param);
149 this.pos.get('selectedOrder').destroy();
152 change_selected_order: function() {
153 this.currentOrderLines.unbind();
154 this.bind_orderline_events();
155 this.renderElement();
157 bind_orderline_events: function() {
158 this.currentOrderLines = (this.pos.get('selectedOrder')).get('orderLines');
159 this.currentOrderLines.bind('add', this.add_line, this);
160 this.currentOrderLines.bind('remove', this.renderElement, this);
162 add_line: function(newLine) {
163 var line = new module.OrderlineWidget(null, {
166 order: this.pos.get('selectedOrder')
168 line.on_selected.add(_.bind(this.selected_line, this));
169 this.selected_line();
170 line.appendTo(this.$element.find('#current-order-content'));
171 this.update_summary();
173 selected_line: function() {
175 if (this.currentSelected !== this.pos.get('selectedOrder').selected) {
178 this.currentSelected = this.pos.get('selectedOrder').selected;
179 if (reset && this.numpadState)
180 this.numpadState.reset();
181 this.update_summary();
183 renderElement: function() {
185 var $content = this.$element.find('#current-order-content');
187 this.currentOrderLines.each(_.bind( function(orderLine) {
188 var line = new module.OrderlineWidget(null, {
190 order: this.pos.get('selectedOrder')
192 line.on_selected.add(_.bind(this.selected_line, this));
193 line.appendTo($content);
195 this.update_summary();
197 update_summary: function() {
198 var currentOrder, tax, total, totalTaxExcluded;
199 currentOrder = this.pos.get('selectedOrder');
200 total = currentOrder.getTotal();
201 totalTaxExcluded = currentOrder.getTotalTaxExcluded();
202 tax = currentOrder.getTax();
203 this.pos_widget.action_bar.set_total_value(Math.round(total*100)/100);
204 $('#subtotal').html(totalTaxExcluded.toFixed(2)).hide().fadeIn();
205 $('#tax').html(tax.toFixed(2)).hide().fadeIn();
206 $('#total').html(total.toFixed(2)).hide().fadeIn();
210 // ---------- Product Screen ----------
213 module.ProductWidget = module.PosBaseWidget.extend({
214 template: 'ProductWidget',
215 init: function(parent, options) {
216 this._super(parent,options);
217 this.model = options.model;
218 this.model.attributes.weight = options.weight || undefined;
219 this.next_screen = options.next_screen || undefined;
221 addToOrder: function(event) {
222 /* Preserve the category URL */
223 event.preventDefault();
224 return (this.pos.get('selectedOrder')).addProduct(this.model);
226 set_weight: function(weight){
227 this.model.attributes.weight = weight;
228 this.renderElement();
230 set_next_screen: function(screen){
231 this.next_screen = screen;
233 renderElement: function() {
236 $("a", this.$element).click(function(e){
238 if(self.next_screen){
239 self.pos_widget.screen_selector.set_current_screen(self.next_screen); //FIXME There ought to be a better way to do this ...
245 module.PaymentlineWidget = module.PosBaseWidget.extend({
246 template: 'PaymentlineWidget',
247 init: function(parent, options) {
248 this._super(parent,options);
249 this.payment_line = options.payment_line;
250 this.payment_line.bind('change', this.changedAmount, this);
252 on_delete: function() {},
253 changeAmount: function(event) {
255 newAmount = event.currentTarget.value;
256 if (newAmount && !isNaN(newAmount)) {
257 this.amount = parseFloat(newAmount);
258 this.payment_line.set({
263 changedAmount: function() {
264 if (this.amount !== this.payment_line.get('amount'))
265 this.renderElement();
267 renderElement: function() {
268 this.name = this.payment_line.get('journal_id')[1];
270 $('input', this.$element).keyup(_.bind(this.changeAmount, this));
271 $('.delete-payment-line', this.$element).click(this.on_delete);
275 module.OrderButtonWidget = module.PosBaseWidget.extend({
276 template:'OrderButtonWidget',
277 init: function(parent, options) {
278 this._super(parent,options);
279 this.order = options.order;
280 this.order.bind('destroy', _.bind( function() {
283 this.pos.bind('change:selectedOrder', _.bind( function(pos) {
285 selectedOrder = pos.get('selectedOrder');
286 if (this.order === selectedOrder) {
287 this.setButtonSelected();
292 $('button.select-order', this.$element).click(_.bind(this.selectOrder, this));
293 $('button.close-order', this.$element).click(_.bind(this.closeOrder, this));
295 selectOrder: function(event) {
297 selectedOrder: this.order
300 setButtonSelected: function() {
301 $('.selected-order').removeClass('selected-order');
302 this.$element.addClass('selected-order');
304 closeOrder: function(event) {
305 this.order.destroy();
309 module.ActionButtonWidget = instance.web.Widget.extend({
310 template:'ActionButtonWidget',
311 init: function(parent, options){
312 this._super(parent, options);
313 this.label = options.label || 'button';
314 this.rightalign = options.rightalign || false;
315 this.click_action = options.click;
317 this.icon = options.icon;
318 this.template = 'ActionButtonWidgetWithIcon';
322 if(this.click_action){
323 this.$element.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 = [];
333 this.fake_buttons = {};
334 this.visibility = {};
335 this.total_visibility = true;
336 this.help_visibility = true;
337 this.logout_visibility = true;
338 this.close_visibility = true;
340 set_element_visible: function(element, visible, action){
341 if(visible != this.visibility[element]){
342 this.visibility[element] = visible;
344 this.$('.'+element).show();
346 this.$('.'+element).hide();
349 if(visible && action){
350 this.$('.'+element).off('click').click(action);
353 set_total_value: function(value){
354 this.$('.value').html(value);
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 = [];
363 add_new_button: function(button_options){
364 if(arguments.length == 1){
365 var button = new module.ActionButtonWidget(this,button_options);
366 this.button_list.push(button);
367 button.appendTo($('.pos-actionbar-button-list'));
370 for(var i = 0; i < arguments.length; i++){
371 this.add_new_button(arguments[i]);
378 module.ProductCategoriesWidget = module.PosBaseWidget.extend({
379 template: 'ProductCategoriesWidget',
380 init: function(parent, options){
382 this._super(parent,options);
383 this.product_type = options.product_type || 'all'; // 'all' | 'weightable'
384 this.onlyWeightable = options.onlyWeightable || false;
385 this.category = this.pos.root_category;
386 this.breadcrumb = [];
387 this.subcategories = [];
392 this.search_and_categories();
395 // changes the category. if undefined, sets to root category
396 set_category : function(category){
398 this.category = this.pos.root_category;
400 this.category = category;
402 this.breadcrumb = [];
403 for(var i = 1; i < this.category.ancestors.length; i++){
404 this.breadcrumb.push(this.category.ancestors[i]);
406 if(this.category !== this.pos.root_category){
407 this.breadcrumb.push(this.category);
409 if(this.product_type === 'weightable'){
410 this.subcategories = [];
411 for(var i = 0; i < this.category.childrens.length; i++){
412 if(this.category.childrens[i].weightable_product_list.length > 0){
413 this.subcategories.push( this.category.childrens[i]);
417 this.subcategories = this.category.childrens || [];
421 renderElement: function(){
424 this.$element.find(".oe-pos-categories-list a").click(function(event){
425 var id = $(event.target).data("category-id");
426 var category = self.pos.categories_by_id[id];
427 self.set_category(category);
428 self.renderElement();
429 self.search_and_categories(category);
433 set_product_type: function(type){ // 'all' | 'weightable'
434 this.product_type = type;
435 this.reset_category();
438 // resets the current category to the root category
439 reset_category: function(){
441 this.renderElement();
442 this.search_and_categories();
445 // filters the products, and sets up the search callbacks
446 search_and_categories: function(category){
449 var all_products = this.pos.get('product_list');
450 var all_packages = this.pos.get('product.packaging');
452 // find all products belonging to the current category
454 if(this.product_type === 'weightable'){
455 products = all_products.filter( function(product){
456 return self.category.weightable_product_set[product.id];
459 products = all_products.filter( function(product){
460 return self.category.product_set[product.id];
464 // product lists watch for reset events on 'products' to re-render.
465 // FIXME that means all productlist widget re-render... even the hidden ones !
466 this.pos.get('products').reset(products);
468 // find all the products whose name match the query in the searchbox
469 this.$('.searchbox input').keyup(function(){
470 var results, search_str;
471 search_str = $(this).val().toLowerCase();
473 results = products.filter( function(p){
474 return p.name.toLowerCase().indexOf(search_str) != -1 ||
475 (p.ean13 && p.ean13.indexOf(search_str) != -1);
477 self.$element.find('.search-clear').fadeIn();
480 self.$element.find('.search-clear').fadeOut();
482 self.pos.get('products').reset(results);
484 this.$('.searchbox input').click(function(){
487 //reset the search when clicking on reset
488 this.$('.search-clear').click(function(){
489 self.pos.get('products').reset(products);
490 self.$('.searchbox input').val('').focus();
491 self.$('.search-clear').fadeOut();
496 module.ProductListWidget = module.ScreenWidget.extend({
497 template:'ProductListWidget',
498 init: function(parent, options) {
500 this._super(parent,options);
501 this.model = options.model;
502 this.product_list = [];
503 this.weight = options.weight || 0;
504 this.show_scale = options.show_scale || false;
505 this.next_screen = options.next_screen || false;
507 this.pos.get('products').bind('reset', function(){
508 self.renderElement();
511 set_weight: function(weight){
512 for(var i = 0; i < this.product_list.length; i++){
513 this.product_list[i].set_weight(weight);
516 set_next_screen: function(screen){
517 for(var i = 0; i < this.product_list.length; i++){
518 this.product_list[i].set_next_screen(screen);
521 renderElement: function() {
524 this.product_list = [];
525 this.pos.get('products')
527 .map(function(product) {
528 var product = new module.ProductWidget(self, {
532 self.product_list.push(product);
535 .invoke('appendTo', this.$element);
539 // ---------- OnScreen Keyboard Widget ----------
541 // A Widget that displays an onscreen keyboard.
542 // There are two options when creating the widget :
544 // * 'keyboard_model' : 'simple' | 'full' (default)
545 // The 'full' emulates a PC keyboard, while 'simple' emulates an 'android' one.
547 // * 'input_selector : (default: '.searchbox input')
548 // defines the dom element that the keyboard will write to.
550 // The widget is initially hidden. It can be shown with this.show(), and is
551 // automatically shown when the input_selector gets focused.
553 module.OnscreenKeyboardWidget = instance.web.Widget.extend({
554 template: 'OnscreenKeyboardSimple',
555 init: function(parent, options){
557 this._super(parent,options);
558 options = options || {};
560 this.keyboard_model = options.keyboard_model || 'full';
561 if(this.keyboard_model === 'full'){
562 this.template = 'OnscreenKeyboardFull';
565 this.input_selector = options.input_selector || '.searchbox input';
567 //show the keyboard when the input zone is clicked.
568 $(this.input_selector).focus(function(){self.show();});
571 this.capslock = false;
573 this.numlock = false;
576 connect : function(){
578 $(this.input_selector).focus(function(){self.show();});
581 // Write a character to the input zone
582 writeCharacter: function(character){
583 var $input = $(this.input_selector);
584 $input[0].value += character;
589 // Sends a 'return' character to the input zone. TODO
590 sendReturn: function(){
593 // Removes the last character from the input zone.
594 deleteCharacter: function(){
595 var $input = $(this.input_selector);
596 var input_value = $input[0].value;
597 $input[0].value = input_value.substr(0, input_value.length - 1);
602 // Clears the content of the input zone.
603 deleteAllCharacters: function(){
604 var $input = $(this.input_selector);
605 $input[0].value = "";
610 // Makes the keyboard show and slide from the bottom of the screen.
612 $('.keyboard_frame').show().animate({'height':'235px'}, 500, 'swing');
615 // Makes the keyboard hide by sliding to the bottom of the screen.
618 var frame = $('.keyboard_frame');
619 frame.animate({'height':'0'}, 500, 'swing', function(){ frame.hide(); self.reset(); });
622 //What happens when the shift key is pressed : toggle case, remove capslock
623 toggleShift: function(){
624 $('.letter').toggleClass('uppercase');
625 $('.symbol span').toggle();
627 self.shift = (self.shift === true) ? false : true;
628 self.capslock = false;
631 //what happens when capslock is pressed : toggle case, set capslock
632 toggleCapsLock: function(){
633 $('.letter').toggleClass('uppercase');
634 self.capslock = true;
637 //What happens when numlock is pressed : toggle symbols and numlock label
638 toggleNumLock: function(){
639 $('.symbol span').toggle();
640 $('.numlock span').toggle();
641 self.numlock = (self.numlock === true ) ? false : true;
644 //After a key is pressed, shift is disabled.
645 removeShift: function(){
646 if (self.shift === true) {
647 $('.symbol span').toggle();
648 if (this.capslock === false) $('.letter').toggleClass('uppercase');
654 // Resets the keyboard to its original state; capslock: false, shift: false, numlock: false
660 this.toggleCapsLock();
663 this.toggleNumLock();
667 //called after the keyboard is in the DOM, sets up the key bindings.
674 $('.close_button').click(function(){
675 self.deleteAllCharacters();
679 // Keyboard key click handling
680 $('.keyboard li').click(function(){
683 character = $this.html(); // If it's a lowercase letter, nothing happens to this variable
685 if ($this.hasClass('left-shift') || $this.hasClass('right-shift')) {
690 if ($this.hasClass('capslock')) {
691 self.toggleCapsLock();
695 if ($this.hasClass('delete')) {
696 self.deleteCharacter();
700 if ($this.hasClass('numlock')){
701 self.toggleNumLock();
705 // Special characters
706 if ($this.hasClass('symbol')) character = $('span:visible', $this).html();
707 if ($this.hasClass('space')) character = ' ';
708 if ($this.hasClass('tab')) character = "\t";
709 if ($this.hasClass('return')) character = "\n";
712 if ($this.hasClass('uppercase')) character = character.toUpperCase();
714 // Remove shift once a key is clicked.
717 self.writeCharacter(character);
722 // ---------- Main Point of Sale Widget ----------
724 // this is used to notify the user that data is being synchronized on the network
725 module.SynchNotificationWidget = instance.web.Widget.extend({
726 template: "SynchNotificationWidget",
727 init: function(parent) {
729 this.nbr_pending = 0;
731 renderElement: function() {
733 $('.oe_pos_synch-notification-button', this.$element).click(this.on_synch);
735 on_change_nbr_pending: function(nbr_pending) {
736 this.nbr_pending = nbr_pending;
737 this.renderElement();
739 on_synch: function() {}
742 // The PosWidget is the main widget that contains all other widgets in the PointOfSale.
743 // It is mainly composed of :
744 // - a header, containing the list of orders
745 // - a leftpane, containing the list of bought products (orderlines)
746 // - a rightpane, containing the screens (see pos_screens.js)
747 // - an actionbar on the bottom, containing various action buttons
749 // - an onscreen keyboard
750 // a screen_selector which controls the switching between screens and the showing/closing of popups
752 module.PosWidget = module.PosBaseWidget.extend({
753 template: 'PosWidget',
755 console.log('PosArguments:',arguments);
756 this._super(arguments[0],{});
758 this.pos = new module.PosModel(this.session);
759 window.pos = this.pos;
760 window.pos_widget = this;
761 this.pos_widget = this; //So that pos_widget's childs have pos_widget set automatically
763 this.numpad_visible = true;
764 this.leftpane_visible = true;
765 this.leftpane_width = '440px';
766 this.cashier_controls_visible = true;
771 return self.pos.ready.then(function() {
772 self.build_currency_template();
773 self.renderElement();
774 self.synch_notification = new module.SynchNotificationWidget(this);
775 self.synch_notification.replace($('.placeholder-SynchNotificationWidget', self.$element));
776 self.synch_notification.on_synch.add(_.bind(self.pos.flush, self.pos));
778 self.pos.bind('change:nbr_pending_operations', self.changed_pending_operations, self);
779 self.changed_pending_operations();
781 self.$element.find("#loggedas button").click(function() {
785 self.$('button#neworder-button').click(_.bind(self.create_new_order, self));
787 //when a new order is created, add an order button widget
788 self.pos.get('orders').bind('add', function(new_order){
789 var new_order_button = new module.OrderButtonWidget(null, {
793 new_order_button.appendTo($('#orders'));
794 new_order_button.selectOrder();
797 self.pos.get('orders').add(new module.Order({ pos: self.pos }));
799 self.build_widgets();
801 instance.webclient.set_content_full_screen(true);
803 if (!self.pos.get('pos_session')) {
804 self.screen_selector.show_popup('error',
805 'Sorry, we could not create a user session');
806 //}else if (!self.pos.get('bank_statements') || self.pos.get('bank_statements').length === 0){
807 // self.screen_selector.show_popup('error',
808 // 'Sorry, we could not find any accounting journals in the configuration');
809 }else if(!self.pos.get('pos_config')){
810 self.screen_selector.show_popup('error',
811 'Sorry, we could not find any PoS Configuration for this session');
814 $('.loader').animate({opacity:0},3000,'swing',function(){$('.loader').hide();});
815 $('.loader img').hide();
817 },function(){ // error when loading models data from the backend
818 $('.loader img').hide();
819 return new instance.web.Model("ir.model.data").get_func("search_read")([['name', '=', 'action_pos_session_opening']], ['res_id'])
820 .pipe( _.bind(function(res){
821 return instance.connection.rpc('/web/action/load', {'action_id': res[0]['res_id']})
822 .pipe(_.bind(function(result){
823 var action = result.result;
824 this.do_action(action);
830 build_widgets: function() {
832 // -------- Screens ---------
834 this.search_product_screen = new module.SearchProductScreenWidget(this,{});
835 this.search_product_screen.appendTo($('#rightpane'));
837 this.scan_product_screen = new module.ScanProductScreenWidget(this,{});
838 this.scan_product_screen.appendTo($('#rightpane'));
840 this.receipt_screen = new module.ReceiptScreenWidget(this, {});
841 this.receipt_screen.appendTo($('#rightpane'));
843 this.payment_screen = new module.PaymentScreenWidget(this, {});
844 this.payment_screen.appendTo($('#rightpane'));
846 this.welcome_screen = new module.WelcomeScreenWidget(this,{});
847 this.welcome_screen.appendTo($('#rightpane'));
849 this.client_payment_screen = new module.ClientPaymentScreenWidget(this, {});
850 this.client_payment_screen.appendTo($('#rightpane'));
852 this.scale_invite_screen = new module.ScaleInviteScreenWidget(this, {});
853 this.scale_invite_screen.appendTo($('#rightpane'));
855 this.scale_product_screen = new module.ScaleProductScreenWidget(this, {});
856 this.scale_product_screen.appendTo($('#rightpane'));
858 // -------- Popups ---------
860 this.help_popup = new module.HelpPopupWidget(this, {});
861 this.help_popup.appendTo($('.point-of-sale'));
863 this.receipt_popup = new module.ReceiptPopupWidget(this, {});
864 this.receipt_popup.appendTo($('.point-of-sale'));
866 this.error_popup = new module.ErrorPopupWidget(this, {});
867 this.error_popup.appendTo($('.point-of-sale'));
869 this.error_product_popup = new module.ErrorProductNotRecognizedPopupWidget(this, {});
870 this.error_product_popup.appendTo($('.point-of-sale'));
872 this.error_session_popup = new module.ErrorNoSessionPopupWidget(this, {});
873 this.error_session_popup.appendTo($('.point-of-sale'));
875 // -------- Misc ---------
877 this.action_bar = new module.ActionBarWidget(this);
878 this.action_bar.appendTo($(".point-of-sale #content"));
880 this.paypad = new module.PaypadWidget(this, {});
881 this.paypad.replace($('#placeholder-PaypadWidget'));
883 this.numpad = new module.NumpadWidget(this);
884 this.numpad.replace($('#placeholder-NumpadWidget'));
886 this.order_widget = new module.OrderWidget(this, {});
887 this.order_widget.replace($('#placeholder-OrderWidget'));
889 this.onscreen_keyboard = new module.OnscreenKeyboardWidget(this, {
890 'keyboard_model': 'simple'
892 this.onscreen_keyboard.appendTo($(".point-of-sale #content"));
894 // -------- Screen Selector ---------
896 this.screen_selector = new module.ScreenSelector({
899 'products': this.search_product_screen,
900 'scan': this.scan_product_screen,
901 'payment' : this.payment_screen,
902 'client_payment' : this.client_payment_screen,
903 'scale_invite' : this.scale_invite_screen,
904 'scale_product' : this.scale_product_screen,
905 'receipt' : this.receipt_screen,
906 'welcome' : this.welcome_screen,
909 'help': this.help_popup,
910 'error': this.error_popup,
911 'error-product': this.error_product_popup,
912 'error-session': this.error_session_popup,
913 'receipt': this.receipt_popup,
915 default_client_screen: 'welcome',
916 default_cashier_screen: 'products',
917 default_mode: this.pos.use_selfcheckout ? 'client' : 'cashier',
919 this.screen_selector.set_default_screen();
921 window.screen_selector = this.screen_selector; //DEBUG
923 this.pos.barcode_reader.connect();
927 //FIXME this method is probably not at the right place ...
928 scan_product: function(parsed_ean){
929 var selectedOrder = this.pos.get('selectedOrder');
930 var scannedProductModel = this.get_product_by_ean(parsed_ean);
931 if (!scannedProductModel){
934 selectedOrder.addProduct(new module.Product(scannedProductModel));
939 // returns a product that has a packaging with an EAN matching to provided parsed ean .
940 // returns undefined if no such product is found.
941 get_product_by_ean: function(parsed_ean) {
942 var allProducts = this.pos.get('product_list');
943 var allPackages = this.pos.get('product.packaging');
944 var scannedProductModel = undefined;
946 if (parsed_ean.type === 'price') {
947 var itemCode = parsed_ean.id;
948 console.log('price! id:',itemCode);
949 var scannedPackaging = _.detect(allPackages, function(pack) {
950 return pack.ean && pack.ean.substring(0,7) === itemCode;
952 if (scannedPackaging) {
953 console.log('found matching package, finding matching product...');
954 scannedProductModel = _.detect(allProducts, function(pc) { return pc.id === scannedPackaging.product_id[0];});
956 console.log('matching package not found, finding matching product...');
957 scannedProductModel = _.detect(allProducts, function(pc) { return pc.ean13 && (pc.ean13.substring(0,7) === parsed_ean.id);});
959 if(scannedProductModel){
960 scannedProductModel.list_price = parsed_ean.value;
962 } else if (parsed_ean.type === 'weight') {
963 var weight = parsed_ean.value;
964 var itemCode = parsed_ean.id;
965 var scannedPackaging = _.detect(allPackages, function(pack) {
966 return pack.ean && pack.ean.substring(0,7) === itemCode;
968 if (scannedPackaging){
969 console.log('found matching package, finding matching product...');
970 scannedProductModel = _.detect(allProducts, function(pc) { return pc.id === scannedPackaging.product_id[0];});
972 console.log('matching package not found, finding matching product...');
973 scannedProductModel = _.detect(allProducts, function(pc) { return pc.ean13 && (pc.ean13.substring(0,7) === parsed_ean.id);});
975 if(scannedProductModel){
976 scannedProductModel.list_price *= weight;
977 scannedProductModel.name += ' - ' + weight + ' Kg.';
979 } else if(parsed_ean.type === 'unit'){
980 scannedProductModel = _.detect(allProducts, function(pc) { return pc.ean13 === parsed_ean.ean;}); //TODO DOES NOT SCALE
982 return scannedProductModel;
984 // creates a new order, and add it to the list of orders.
985 create_new_order: function() {
987 new_order = new module.Order({ pos: this.pos });
988 this.pos.get('orders').add(new_order);
989 this.pos.set({ selectedOrder: new_order });
991 changed_pending_operations: function () {
993 this.synch_notification.on_change_nbr_pending(self.pos.get('nbr_pending_operations').length);
995 // shows or hide the numpad and related controls like the paypad.
996 set_numpad_visible: function(visible){
997 if(visible != this.numpad_visible){
998 this.numpad_visible = visible;
1000 $('#numpad').show();
1001 $('#paypad').show();
1002 $('#current-order').css({'bottom':'271px'});
1004 $('#numpad').hide();
1005 $('#paypad').hide();
1006 $('#current-order').css({'bottom':'0px'});
1010 //shows or hide the leftpane (contains the list of orderlines, the numpad, the paypad, etc.)
1011 set_leftpane_visible: function(visible){
1012 if(visible != this.leftpane_visible){
1013 this.leftpane_visible = visible;
1015 $('#leftpane').show().animate({'width':this.leftpane_width},500,'swing');
1016 $('#rightpane').animate({'left':this.leftpane_width},500,'swing');
1018 var leftpane = $('#leftpane');
1019 $('#leftpane').animate({'width':'0px'},500,'swing', function(){ leftpane.hide(); });
1020 $('#rightpane').animate({'left':'0px'},500,'swing');
1024 //shows or hide the controls in the PosWidget that are specific to the cashier ( Orders, close button, etc. )
1025 set_cashier_controls_visible: function(visible){
1026 if(visible != this.cashier_controls_visible){
1027 this.cashier_controls_visible = visible;
1029 $('#loggedas').show();
1030 $('#rightheader').show();
1032 $('#loggedas').hide();
1033 $('#rightheader').hide();
1037 try_close: function() {
1039 self.pos.flush().then(_.bind(function() {
1040 var close = _.bind(this.close, this);
1041 if (self.pos.get('nbr_pending_operations').length > 0) {
1042 var confirm = false;
1043 $(QWeb.render('PosCloseWarning')).dialog({
1051 $( this ).dialog( "close" );
1054 $( this ).dialog( "close" );
1069 this.pos.barcode_reader.disconnect();
1070 return new session.web.Model("ir.model.data").get_func("search_read")([['name', '=', 'action_pos_close_statement']], ['res_id']).pipe(
1071 _.bind(function(res) {
1072 return this.rpc('/web/action/load', {'action_id': res[0]['res_id']}).pipe(_.bind(function(result) {
1073 var action = result.result;
1074 action.context = _.extend(action.context || {}, {'cancel_action': {type: 'ir.actions.client', tag: 'default_home'}});
1075 this.do_action(action);
1079 destroy: function() {
1080 instance.webclient.set_content_full_screen(false);
1081 self.pos = undefined;