2 // this file contains the screens definitions. Screens are the
3 // content of the right pane of the pos, containing the main functionalities.
4 // screens are contained in the PosWidget, in pos_widget.js
5 // all screens are present in the dom at all time, but only one is shown at the
8 // transition between screens is made possible by the use of the screen_selector,
9 // which is responsible of hiding and showing the screens, as well as maintaining
10 // the state of the screens between different orders.
12 // all screens inherit from ScreenWidget. the only addition from the base widgets
13 // are show() and hide() which shows and hides the screen but are also used to
14 // bind and unbind actions on widgets and devices. The screen_selector guarantees
15 // that only one screen is shown at the same time and that show() is called after all
18 function openerp_pos_screens(instance, module){ //module is instance.point_of_sale
19 var QWeb = instance.web.qweb,
22 module.ScreenSelector = instance.web.Class.extend({
23 init: function(options){
24 this.pos = options.pos;
26 this.screen_set = options.screen_set || {};
28 this.popup_set = options.popup_set || {};
30 this.default_client_screen = options.default_client_screen;
31 this.default_cashier_screen = options.default_cashier_screen;
33 this.current_popup = null;
35 this.current_mode = options.default_mode || 'client';
37 this.current_screen = null;
39 for(screen_name in this.screen_set){
40 this.screen_set[screen_name].hide();
43 for(popup_name in this.popup_set){
44 this.popup_set[popup_name].hide();
47 this.selected_order = this.pos.get('selectedOrder');
48 this.selected_order.set_screen_data({
49 client_screen: this.default_client_screen,
50 cashier_screen: this.default_cashier_screen,
53 this.pos.bind('change:selectedOrder', this.load_saved_screen, this);
55 add_screen: function(screen_name, screen){
57 this.screen_set[screen_name] = screen;
60 show_popup: function(name){
61 if(this.current_popup){
64 this.current_popup = this.popup_set[name];
65 this.current_popup.show();
67 close_popup: function(){
68 if(this.current_popup){
69 this.current_popup.close();
70 this.current_popup.hide();
71 this.current_popup = null;
74 load_saved_screen: function(){
77 var selectedOrder = this.pos.get('selectedOrder');
79 if(this.current_mode === 'client'){
80 this.set_current_screen(selectedOrder.get_screen_data('client_screen') || this.default_client_screen,null,'refresh');
81 }else if(this.current_mode === 'cashier'){
82 this.set_current_screen(selectedOrder.get_screen_data('cashier_screen') || this.default_cashier_screen,null,'refresh');
84 this.selected_order = selectedOrder;
86 set_user_mode: function(user_mode){
87 if(user_mode !== this.current_mode){
89 this.current_mode = user_mode;
90 this.load_saved_screen();
93 get_user_mode: function(){
94 return this.current_mode;
96 set_current_screen: function(screen_name,params,refresh){
97 var screen = this.screen_set[screen_name];
99 console.error("ERROR: set_current_screen("+screen_name+") : screen not found");
103 var selectedOrder = this.pos.get('selectedOrder');
104 if(this.current_mode === 'client'){
105 selectedOrder.set_screen_data('client_screen',screen_name);
107 selectedOrder.set_screen_data('client_screen_params',params);
110 selectedOrder.set_screen_data('cashier_screen',screen_name);
112 selectedOrder.set_screen_data('cashier_screen_params',params);
116 if(screen && (refresh || screen !== this.current_screen)){
117 if(this.current_screen){
118 this.current_screen.close();
119 this.current_screen.hide();
121 this.current_screen = screen;
122 this.current_screen.show();
125 get_current_screen_param: function(param){
126 var selected_order = this.pos.get('selectedOrder');
127 if(this.current_mode === 'client'){
128 var params = selected_order.get_screen_data('client_screen_params');
130 var params = selected_order.get_screen_data('cashier_screen_params');
133 return params[param];
138 set_default_screen: function(){
139 this.set_current_screen(this.current_mode === 'client' ? this.default_client_screen : this.default_cashier_screen);
143 module.ScreenWidget = module.PosBaseWidget.extend({
148 init: function(parent,options){
149 this._super(parent,options);
153 help_button_action: function(){
154 this.pos_widget.screen_selector.show_popup('help');
157 barcode_product_screen: 'products', //if defined, this screen will be loaded when a product is scanned
158 barcode_product_error_popup: 'error-product', //if defined, this popup will be loaded when there's an error in the popup
160 hotkeys_handlers: {},
162 // what happens when a product is scanned :
163 // it will add the product to the order and go to barcode_product_screen. Or show barcode_product_error_popup if
165 barcode_product_action: function(code){
167 if(self.pos.scan_product(code)){
168 self.pos.proxy.scan_item_success(code);
169 if(self.barcode_product_screen){
170 self.pos_widget.screen_selector.set_current_screen(self.barcode_product_screen);
173 self.pos.proxy.scan_item_error_unrecognized(code);
174 if(self.barcode_product_error_popup && self.pos_widget.screen_selector.get_user_mode() !== 'cashier'){
175 self.pos_widget.screen_selector.show_popup(self.barcode_product_error_popup);
180 // what happens when a cashier id barcode is scanned.
181 // the default behavior is the following :
182 // - if there's a user with a matching ean, put it as the active 'cashier', go to cashier mode, and return true
183 // - else : do nothing and return false. You probably want to extend this to show and appropriate error popup...
184 barcode_cashier_action: function(code){
185 var users = this.pos.users;
186 for(var i = 0, len = users.length; i < len; i++){
187 if(users[i].ean13 === code.code){
188 this.pos.cashier = users[i];
189 this.pos_widget.username.refresh();
190 this.pos.proxy.cashier_mode_activated();
191 this.pos_widget.screen_selector.set_user_mode('cashier');
195 this.pos.proxy.scan_item_error_unrecognized(code);
199 // what happens when a client id barcode is scanned.
200 // the default behavior is the following :
201 // - if there's a user with a matching ean, put it as the active 'client' and return true
202 // - else : return false.
203 barcode_client_action: function(code){
204 var partners = this.pos.partners;
205 for(var i = 0, len = partners.length; i < len; i++){
206 if(partners[i].ean13 === code.code){
207 this.pos.get('selectedOrder').set_client(partners[i]);
208 this.pos_widget.username.refresh();
209 this.pos.proxy.scan_item_success(code);
213 this.pos.proxy.scan_item_error_unrecognized(code);
215 //TODO start the transaction
218 // what happens when a discount barcode is scanned : the default behavior
219 // is to set the discount on the last order.
220 barcode_discount_action: function(code){
221 this.pos.proxy.scan_item_success(code);
222 var last_orderline = this.pos.get('selectedOrder').getLastOrderline();
224 last_orderline.set_discount(code.value)
228 // shows an action bar on the screen. The actionbar is automatically shown when you add a button
229 // with add_action_button()
230 show_action_bar: function(){
231 this.pos_widget.action_bar.show();
234 // hides the action bar. The actionbar is automatically hidden when it is empty
235 hide_action_bar: function(){
236 this.pos_widget.action_bar.hide();
239 // adds a new button to the action bar. The button definition takes three parameters, all optional :
240 // - label: the text below the button
241 // - icon: a small icon that will be shown
242 // - click: a callback that will be executed when the button is clicked.
243 // the method returns a reference to the button widget, and automatically show the actionbar.
244 add_action_button: function(button_def){
245 this.show_action_bar();
246 return this.pos_widget.action_bar.add_new_button(button_def);
249 // this method shows the screen and sets up all the widget related to this screen. Extend this method
250 // if you want to alter the behavior of the screen.
256 this.$el.removeClass('oe_hidden');
259 if(this.pos_widget.action_bar.get_button_count() > 0){
260 this.show_action_bar();
262 this.hide_action_bar();
265 // we add the help button by default. we do this because the buttons are cleared on each refresh so that
266 // the button stay local to each screen
267 this.pos_widget.left_action_bar.add_new_button({
269 icon: '/point_of_sale/static/src/img/icons/png48/help.png',
270 click: function(){ self.help_button_action(); },
274 this.cashier_mode = this.pos_widget.screen_selector.get_user_mode() === 'cashier';
276 this.pos_widget.set_numpad_visible(this.show_numpad && this.cashier_mode);
277 this.pos_widget.set_leftpane_visible(this.show_leftpane);
278 this.pos_widget.set_left_action_bar_visible(this.show_leftpane && !this.cashier_mode);
279 this.pos_widget.set_cashier_controls_visible(this.cashier_mode);
281 if(this.cashier_mode && this.pos.config.iface_self_checkout){
282 this.pos_widget.client_button.show();
284 this.pos_widget.client_button.hide();
286 if(this.cashier_mode){
287 this.pos_widget.close_button.show();
289 this.pos_widget.close_button.hide();
292 this.pos_widget.username.set_user_mode(this.pos_widget.screen_selector.get_user_mode());
294 this.pos.barcode_reader.set_action_callback({
295 'cashier': self.barcode_cashier_action ? function(code){ self.barcode_cashier_action(code); } : undefined ,
296 'product': self.barcode_product_action ? function(code){ self.barcode_product_action(code); } : undefined ,
297 'client' : self.barcode_client_action ? function(code){ self.barcode_client_action(code); } : undefined ,
298 'discount': self.barcode_discount_action ? function(code){ self.barcode_discount_action(code); } : undefined,
302 // this method is called when the screen is closed to make place for a new screen. this is a good place
303 // to put your cleanup stuff as it is guaranteed that for each show() there is one and only one close()
305 if(this.pos.barcode_reader){
306 this.pos.barcode_reader.reset_action_callbacks();
308 this.pos_widget.action_bar.destroy_buttons();
309 this.pos_widget.left_action_bar.destroy_buttons();
312 // this methods hides the screen. It's not a good place to put your cleanup stuff as it is called on the
313 // POS initialization.
317 this.$el.addClass('oe_hidden');
321 // we need this because some screens re-render themselves when they are hidden
322 // (due to some events, or magic, or both...) we must make sure they remain hidden.
323 // the good solution would probably be to make them not re-render themselves when they
325 renderElement: function(){
329 this.$el.addClass('oe_hidden');
335 module.PopUpWidget = module.PosBaseWidget.extend({
338 this.$el.removeClass('oe_hidden');
341 /* called before hide, when a popup is closed */
344 /* hides the popup. keep in mind that this is called in the initialization pass of the
345 * pos instantiation, so you don't want to do anything fancy in here */
348 this.$el.addClass('oe_hidden');
353 module.HelpPopupWidget = module.PopUpWidget.extend({
354 template:'HelpPopupWidget',
357 this.pos.proxy.help_needed();
360 this.$el.find('.button').off('click').click(function(){
361 self.pos_widget.screen_selector.close_popup();
365 this.pos.proxy.help_canceled();
369 module.ChooseReceiptPopupWidget = module.PopUpWidget.extend({
370 template:'ChooseReceiptPopupWidget',
373 this.renderElement();
375 var currentOrder = self.pos.get('selectedOrder');
377 this.$('.button.receipt').off('click').click(function(){
378 currentOrder.set_receipt_type('receipt');
379 self.pos_widget.screen_selector.set_current_screen('products');
382 this.$('.button.invoice').off('click').click(function(){
383 currentOrder.set_receipt_type('invoice');
384 self.pos_widget.screen_selector.set_current_screen('products');
387 get_client_name: function(){
388 var client = this.pos.get('selectedOrder').get_client();
397 module.ErrorPopupWidget = module.PopUpWidget.extend({
398 template:'ErrorPopupWidget',
402 this.pos.proxy.help_needed();
403 this.pos.proxy.scan_item_error_unrecognized();
405 this.pos.barcode_reader.save_callbacks();
406 this.pos.barcode_reader.reset_action_callbacks();
407 this.pos.barcode_reader.set_action_callback({
408 'cashier': function(code){
409 clearInterval(this.intervalID);
410 self.pos.proxy.cashier_mode_activated();
411 self.pos_widget.screen_selector.set_user_mode('cashier');
414 this.$('.footer .button').off('click').click(function(){
415 self.pos_widget.screen_selector.close_popup();
420 this.pos.proxy.help_canceled();
421 this.pos.barcode_reader.restore_callbacks();
425 module.ProductErrorPopupWidget = module.ErrorPopupWidget.extend({
426 template:'ProductErrorPopupWidget',
429 module.ErrorSessionPopupWidget = module.ErrorPopupWidget.extend({
430 template:'ErrorSessionPopupWidget',
433 module.ErrorNegativePricePopupWidget = module.ErrorPopupWidget.extend({
434 template:'ErrorNegativePricePopupWidget',
437 module.ErrorNoClientPopupWidget = module.ErrorPopupWidget.extend({
438 template: 'ErrorNoClientPopupWidget',
441 module.ErrorInvoiceTransferPopupWidget = module.ErrorPopupWidget.extend({
442 template: 'ErrorInvoiceTransferPopupWidget',
445 module.ScaleInviteScreenWidget = module.ScreenWidget.extend({
446 template:'ScaleInviteScreenWidget',
449 previous_screen:'products',
454 var queue = this.pos.proxy_queue;
456 queue.schedule(function(){
457 return self.pos.proxy.weighting_start();
458 },{ important: true });
460 queue.schedule(function(){
461 return self.pos.proxy.weighting_read_kg().then(function(weight){
463 self.pos_widget.screen_selector.set_current_screen(self.next_screen);
466 },{duration: 100, repeat: true});
468 this.add_action_button({
470 icon: '/point_of_sale/static/src/img/icons/png48/go-previous.png',
472 self.pos_widget.screen_selector.set_current_screen(self.previous_screen);
479 this.pos.proxy_queue.clear();
480 this.pos.proxy_queue.schedule(function(){
481 return self.pos.proxy.weighting_end();
482 },{ important: true });
486 module.ScaleScreenWidget = module.ScreenWidget.extend({
487 template:'ScaleScreenWidget',
489 next_screen: 'products',
490 previous_screen: 'products',
495 var queue = this.pos.proxy_queue;
498 this.renderElement();
500 this.hotkey_handler = function(event){
501 if(event.which === 13){
502 self.order_product();
503 self.pos_widget.screen_selector.set_current_screen(self.next_screen);
504 }else if(event.which === 27){
505 self.pos_widget.screen_selector.set_current_screen(self.previous_screen);
509 $('body').on('keyup',this.hotkey_handler);
511 this.add_action_button({
513 icon: '/point_of_sale/static/src/img/icons/png48/go-previous.png',
515 self.pos_widget.screen_selector.set_current_screen(self.previous_screen);
519 this.validate_button = this.add_action_button({
520 label: _t('Validate'),
521 icon: '/point_of_sale/static/src/img/icons/png48/validate.png',
523 self.order_product();
524 self.pos_widget.screen_selector.set_current_screen(self.next_screen);
528 queue.schedule(function(){
529 return self.pos.proxy.weighting_start()
530 },{ important: true });
532 queue.schedule(function(){
533 return self.pos.proxy.weighting_read_kg().then(function(weight){
534 self.set_weight(weight);
536 },{duration:50, repeat: true});
539 renderElement: function(){
542 this.$('.product-picture').click(function(){
543 self.order_product();
544 self.pos_widget.screen_selector.set_current_screen(self.next_screen);
547 get_product: function(){
548 var ss = this.pos_widget.screen_selector;
550 return ss.get_current_screen_param('product');
555 order_product: function(){
556 this.pos.get('selectedOrder').addProduct(this.get_product(),{ quantity: this.weight });
558 get_product_name: function(){
559 var product = this.get_product();
560 return (product ? product.name : undefined) || 'Unnamed Product';
562 get_product_price: function(){
563 var product = this.get_product();
564 return (product ? product.price : 0) || 0;
566 set_weight: function(weight){
567 this.weight = weight;
568 this.$('.js-weight').text(this.get_product_weight_string());
570 get_product_weight_string: function(){
571 return (this.weight || 0).toFixed(3) + ' Kg';
573 get_product_image_url: function(){
574 var product = this.get_product();
576 return window.location.origin + '/web/binary/image?model=product.product&field=image_medium&id='+product.id;
584 $('body').off('keyup',this.hotkey_handler);
586 this.pos.proxy_queue.clear();
587 this.pos.proxy_queue.schedule(function(){
588 self.pos.proxy.weighting_end();
589 },{ important: true });
594 module.ClientPaymentScreenWidget = module.ScreenWidget.extend({
595 template:'ClientPaymentScreenWidget',
597 next_screen: 'welcome',
598 previous_screen: 'products',
604 this.queue = new module.JobQueue();
605 this.canceled = false;
608 // initiates the connection to the payment terminal and starts the update requests
609 this.start = function(){
610 var def = new $.Deferred();
611 self.pos.proxy.payment_request(self.pos.get('selectedOrder').getDueLeft())
614 self.queue.schedule(self.update);
615 }else if(ack.indexOf('error') === 0){
616 console.error('cannot make payment. TODO');
618 console.error('unknown payment request return value:',ack);
625 // gets updated status from the payment terminal and performs the appropriate consequences
626 this.update = function(){
627 var def = new $.Deferred();
629 return def.resolve();
631 self.pos.proxy.payment_status()
632 .done(function(status){
633 if(status.status === 'paid'){
635 var currentOrder = self.pos.get('selectedOrder');
637 //we get the first cashregister marked as self-checkout
638 var selfCheckoutRegisters = [];
639 for(var i = 0; i < self.pos.cashregisters.length; i++){
640 var cashregister = self.pos.cashregisters[i];
641 if(cashregister.self_checkout_payment_method){
642 selfCheckoutRegisters.push(cashregister);
646 var cashregister = selfCheckoutRegisters[0] || self.pos.cashregisters[0];
647 currentOrder.addPaymentline(cashregister);
648 self.pos.push_order(currentOrder)
649 currentOrder.destroy();
650 self.pos.proxy.transaction_end();
651 self.pos_widget.screen_selector.set_current_screen(self.next_screen);
653 }else if(status.status.indexOf('error') === 0){
654 console.error('error in payment request. TODO');
655 }else if(status.status === 'waiting'){
656 self.queue.schedule(self.update,200);
658 console.error('unknown status value:',status.status);
665 // cancels a payment.
666 this.cancel = function(){
667 if(!self.paid && !self.canceled){
668 self.canceled = true;
669 self.pos.proxy.payment_cancel();
670 self.pos_widget.screen_selector.set_current_screen(self.previous_screen);
673 return (new $.Deferred()).resolve();
676 if(this.pos.get('selectedOrder').getDueLeft() <= 0){
677 this.pos_widget.screen_selector.show_popup('error-negative-price');
679 this.queue.schedule(this.start);
682 this.add_action_button({
684 icon: '/point_of_sale/static/src/img/icons/png48/go-previous.png',
686 self.queue.schedule(self.cancel);
687 self.pos_widget.screen_selector.set_current_screen(self.previous_screen);
693 this.queue.schedule(this.cancel);
700 module.WelcomeScreenWidget = module.ScreenWidget.extend({
701 template:'WelcomeScreenWidget',
703 next_screen: 'products',
706 show_leftpane: false,
709 $('.goodbye-message').click(function(){
710 $(this).addClass('oe_hidden');
714 barcode_product_action: function(code){
715 this.pos.proxy.transaction_start();
719 barcode_client_action: function(code){
720 this.pos.proxy.transaction_start();
722 $('.goodbye-message').addClass('oe_hidden');
723 this.pos_widget.screen_selector.show_popup('choose-receipt');
730 this.add_action_button({
732 icon: '/point_of_sale/static/src/img/icons/png48/help.png',
734 $('.goodbye-message').css({opacity:1}).addClass('oe_hidden');
735 self.help_button_action();
739 $('.goodbye-message').css({opacity:1}).removeClass('oe_hidden');
740 setTimeout(function(){
741 $('.goodbye-message').animate({opacity:0},500,'swing',function(){$('.goodbye-message').addClass('oe_hidden');});
746 module.ProductScreenWidget = module.ScreenWidget.extend({
747 template:'ProductScreenWidget',
749 scale_screen: 'scale',
750 client_scale_screen : 'scale_invite',
751 client_next_screen: 'client_payment',
756 start: function(){ //FIXME this should work as renderElement... but then the categories aren't properly set. explore why
759 this.product_list_widget = new module.ProductListWidget(this,{
760 click_product_action: function(product){
761 if(product.to_weight && self.pos.config.iface_electronic_scale){
762 self.pos_widget.screen_selector.set_current_screen( self.cashier_mode ? self.scale_screen : self.client_scale_screen, {product: product});
764 self.pos.get('selectedOrder').addProduct(product);
767 product_list: this.pos.db.get_product_by_category(0)
769 this.product_list_widget.replace($('.placeholder-ProductListWidget'));
771 this.product_categories_widget = new module.ProductCategoriesWidget(this,{
772 product_list_widget: this.product_list_widget,
774 this.product_categories_widget.replace($('.placeholder-ProductCategoriesWidget'));
781 this.product_categories_widget.reset_category();
783 this.pos_widget.order_widget.set_editable(true);
785 if(this.pos_widget.screen_selector.current_mode === 'client'){
786 this.add_action_button({
788 icon: '/point_of_sale/static/src/img/icons/png48/go-next.png',
790 self.pos_widget.screen_selector.set_current_screen(self.client_next_screen);
799 this.pos_widget.order_widget.set_editable(false);
801 if(this.pos.config.iface_vkeyboard && this.pos_widget.onscreen_keyboard){
802 this.pos_widget.onscreen_keyboard.hide();
807 module.ReceiptScreenWidget = module.ScreenWidget.extend({
808 template: 'ReceiptScreenWidget',
817 var print_button = this.add_action_button({
819 icon: '/point_of_sale/static/src/img/icons/png48/printer.png',
820 click: function(){ self.print(); },
823 var finish_button = this.add_action_button({
824 label: _t('Next Order'),
825 icon: '/point_of_sale/static/src/img/icons/png48/go-next.png',
826 click: function() { self.finishOrder(); },
833 // The problem is that in chrome the print() is asynchronous and doesn't
834 // execute until all rpc are finished. So it conflicts with the rpc used
835 // to send the orders to the backend, and the user is able to go to the next
836 // screen before the printing dialog is opened. The problem is that what's
837 // printed is whatever is in the page when the dialog is opened and not when it's called,
838 // and so you end up printing the product list instead of the receipt...
840 // Fixing this would need a re-architecturing
841 // of the code to postpone sending of orders after printing.
843 // But since the print dialog also blocks the other asynchronous calls, the
844 // button enabling in the setTimeout() is blocked until the printing dialog is
845 // closed. But the timeout has to be big enough or else it doesn't work
846 // 2 seconds is the same as the default timeout for sending orders and so the dialog
847 // should have appeared before the timeout... so yeah that's not ultra reliable.
849 finish_button.set_disabled(true);
850 setTimeout(function(){
851 finish_button.set_disabled(false);
857 finishOrder: function() {
858 this.pos.get('selectedOrder').destroy();
860 refresh: function() {
861 var order = this.pos.get('selectedOrder');
862 $('.pos-receipt-container', this.$el).html(QWeb.render('PosTicket',{
865 orderlines: order.get('orderLines').models,
866 paymentlines: order.get('paymentLines').models,
874 module.PaymentScreenWidget = module.ScreenWidget.extend({
875 template: 'PaymentScreenWidget',
876 back_screen: 'products',
877 next_screen: 'receipt',
878 init: function(parent, options) {
880 this._super(parent,options);
882 this.pos.bind('change:selectedOrder',function(){
884 this.renderElement();
889 this.line_delete_handler = function(event){
891 while(node && !node.classList.contains('paymentline')){
892 node = node.parentNode;
895 self.pos.get('selectedOrder').removePaymentline(node.line)
897 event.stopPropagation();
900 this.line_change_handler = function(event){
902 while(node && !node.classList.contains('paymentline')){
903 node = node.parentNode;
906 node.line.set_amount(this.value);
911 this.line_click_handler = function(event){
913 while(node && !node.classList.contains('paymentline')){
914 node = node.parentNode;
917 self.pos.get('selectedOrder').selectPaymentline(node.line);
921 this.hotkey_handler = function(event){
922 if(event.which === 13){
923 self.validate_order();
924 }else if(event.which === 27){
934 this.enable_numpad();
935 this.focus_selected_line();
937 document.body.addEventListener('keyup', this.hotkey_handler);
941 this.add_action_button({
943 icon: '/point_of_sale/static/src/img/icons/png48/go-previous.png',
949 this.add_action_button({
950 label: _t('Validate'),
952 icon: '/point_of_sale/static/src/img/icons/png48/validate.png',
954 self.validate_order();
958 if( this.pos.config.iface_invoicing ){
959 this.add_action_button({
962 icon: '/point_of_sale/static/src/img/icons/png48/invoice.png',
964 self.validate_order({invoice: true});
969 if( this.pos.config.iface_cashdrawer ){
970 this.add_action_button({
973 icon: '/point_of_sale/static/src/img/open-cashbox.png',
975 self.pos.proxy.open_cashbox();
980 this.update_payment_summary();
985 this.disable_numpad();
986 document.body.removeEventListener('keyup',this.hotkey_handler);
988 remove_empty_lines: function(){
989 var order = this.pos.get('selectedOrder');
990 var lines = order.get('paymentLines').models.slice(0);
991 for(var i = 0; i < lines.length; i++){
993 if(line.get_amount() === 0){
994 order.removePaymentline(line);
999 this.remove_empty_lines();
1000 this.pos_widget.screen_selector.set_current_screen(this.back_screen);
1002 bind_events: function() {
1004 this.old_order.unbind(null,null,this);
1006 var order = this.pos.get('selectedOrder');
1007 order.bind('change:selected_paymentline',this.focus_selected_line,this);
1009 this.old_order = order;
1011 if(this.old_paymentlines){
1012 this.old_paymentlines.unbind(null,null,this);
1014 var paymentlines = order.get('paymentLines');
1015 paymentlines.bind('add', this.add_paymentline, this);
1016 paymentlines.bind('change:selected', this.rerender_paymentline, this);
1017 paymentlines.bind('change:amount', function(line){
1018 if(!line.selected && line.node){
1019 line.node.value = line.amount.toFixed(2);
1021 this.update_payment_summary();
1023 paymentlines.bind('remove', this.remove_paymentline, this);
1024 paymentlines.bind('all', this.update_payment_summary, this);
1026 this.old_paymentlines = paymentlines;
1028 if(this.old_orderlines){
1029 this.old_orderlines.unbind(null,null,this);
1031 var orderlines = order.get('orderLines');
1032 orderlines.bind('all', this.update_payment_summary, this);
1034 this.old_orderlines = orderlines;
1036 focus_selected_line: function(){
1037 var line = this.pos.get('selectedOrder').selected_paymentline;
1039 var input = line.node.querySelector('input');
1043 var value = input.value;
1046 if(this.numpad_state){
1047 this.numpad_state.reset();
1050 if(Number(value) === 0){
1053 input.value = value;
1058 add_paymentline: function(line) {
1059 var list_container = this.el.querySelector('.payment-lines');
1060 list_container.appendChild(this.render_paymentline(line));
1062 if(this.numpad_state){
1063 this.numpad_state.reset();
1066 render_paymentline: function(line){
1067 var el_html = openerp.qweb.render('Paymentline',{widget: this, line: line});
1068 el_html = _.str.trim(el_html);
1070 var el_node = document.createElement('tbody');
1071 el_node.innerHTML = el_html;
1072 el_node = el_node.childNodes[0];
1073 el_node.line = line;
1074 el_node.querySelector('.paymentline-delete')
1075 .addEventListener('click', this.line_delete_handler);
1076 el_node.addEventListener('click', this.line_click_handler);
1077 el_node.querySelector('input')
1078 .addEventListener('keyup', this.line_change_handler);
1080 line.node = el_node;
1084 rerender_paymentline: function(line){
1085 var old_node = line.node;
1086 var new_node = this.render_paymentline(line);
1088 old_node.parentNode.replaceChild(new_node,old_node);
1090 remove_paymentline: function(line){
1091 line.node.parentNode.removeChild(line.node);
1092 line.node = undefined;
1094 renderElement: function(){
1097 var paymentlines = this.pos.get('selectedOrder').get('paymentLines').models;
1098 var list_container = this.el.querySelector('.payment-lines');
1100 for(var i = 0; i < paymentlines.length; i++){
1101 list_container.appendChild(this.render_paymentline(paymentlines[i]));
1104 this.update_payment_summary();
1106 update_payment_summary: function() {
1107 var currentOrder = this.pos.get('selectedOrder');
1108 var paidTotal = currentOrder.getPaidTotal();
1109 var dueTotal = currentOrder.getTotalTaxIncluded();
1110 var remaining = dueTotal > paidTotal ? dueTotal - paidTotal : 0;
1111 var change = paidTotal > dueTotal ? paidTotal - dueTotal : 0;
1113 this.$('.payment-due-total').html(this.format_currency(dueTotal));
1114 this.$('.payment-paid-total').html(this.format_currency(paidTotal));
1115 this.$('.payment-remaining').html(this.format_currency(remaining));
1116 this.$('.payment-change').html(this.format_currency(change));
1117 if(currentOrder.selected_orderline === undefined){
1118 remaining = 1; // What is this ?
1121 if(this.pos_widget.action_bar){
1122 this.pos_widget.action_bar.set_button_disabled('validation', !this.is_paid());
1123 this.pos_widget.action_bar.set_button_disabled('invoice', !this.is_paid());
1126 is_paid: function(){
1127 var currentOrder = this.pos.get('selectedOrder');
1128 return (currentOrder.getTotalTaxIncluded() < 0.000001
1129 || currentOrder.getPaidTotal() + 0.000001 >= currentOrder.getTotalTaxIncluded());
1132 validate_order: function(options) {
1134 options = options || {};
1136 var currentOrder = this.pos.get('selectedOrder');
1138 if(!this.is_paid()){
1142 if( this.pos.config.iface_cashdrawer
1143 && this.pos.get('selectedOrder').get('paymentLines').find( function(pl){
1144 return pl.cashregister.journal.type === 'cash';
1146 this.pos.proxy.open_cashbox();
1149 if(options.invoice){
1150 // deactivate the validation button while we try to send the order
1151 this.pos_widget.action_bar.set_button_disabled('validation',true);
1152 this.pos_widget.action_bar.set_button_disabled('invoice',true);
1154 var invoiced = this.pos.push_and_invoice_order(currentOrder);
1156 invoiced.fail(function(error){
1157 if(error === 'error-no-client'){
1158 self.pos_widget.screen_selector.show_popup('error-no-client');
1160 self.pos_widget.screen_selector.show_popup('error-invoice-transfer');
1162 self.pos_widget.action_bar.set_button_disabled('validation',false);
1163 self.pos_widget.action_bar.set_button_disabled('invoice',false);
1166 invoiced.done(function(){
1167 self.pos_widget.action_bar.set_button_disabled('validation',false);
1168 self.pos_widget.action_bar.set_button_disabled('invoice',false);
1169 self.pos.get('selectedOrder').destroy();
1173 this.pos.push_order(currentOrder)
1174 if(this.pos.config.iface_print_via_proxy){
1175 this.pos.proxy.print_receipt(currentOrder.export_for_printing());
1176 this.pos.get('selectedOrder').destroy(); //finish order and go back to scan screen
1178 this.pos_widget.screen_selector.set_current_screen(this.next_screen);
1182 // hide onscreen (iOS) keyboard
1183 setTimeout(function(){
1184 document.activeElement.blur();
1188 enable_numpad: function(){
1189 this.disable_numpad(); //ensure we don't register the callbacks twice
1190 this.numpad_state = this.pos_widget.numpad.state;
1191 if(this.numpad_state){
1192 this.numpad_state.reset();
1193 this.numpad_state.changeMode('payment');
1194 this.numpad_state.bind('set_value', this.set_value, this);
1195 this.numpad_state.bind('change:mode', this.set_mode_back_to_payment, this);
1199 disable_numpad: function(){
1200 if(this.numpad_state){
1201 this.numpad_state.unbind('set_value', this.set_value);
1202 this.numpad_state.unbind('change:mode',this.set_mode_back_to_payment);
1205 set_mode_back_to_payment: function() {
1206 this.numpad_state.set({mode: 'payment'});
1208 set_value: function(val) {
1209 var selected_line =this.pos.get('selectedOrder').selected_paymentline;
1211 selected_line.set_amount(val);
1212 selected_line.node.querySelector('input').value = selected_line.amount.toFixed(2);