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(module, instance){ //module is instance.point_of_sale
19 var QWeb = instance.web.qweb;
21 var qweb_template = function(template,pos){
23 if(!pos){ //this is a huge hack that needs to be removed ... TODO
24 var HackPosModel = Backbone.Model.extend({
25 initialize:function(){
27 'currency': {symbol: '$', position: 'after'},
31 pos = new HackPosModel();
33 return QWeb.render(template, _.extend({}, ctx,{
34 'currency': pos.get('currency'),
35 'format_amount': function(amount) {
36 if (pos.get('currency').position == 'after') {
37 return amount + ' ' + pos.get('currency').symbol;
39 return pos.get('currency').symbol + ' ' + amount;
46 module.ScreenSelector = instance.web.Class.extend({
47 init: function(options){
48 this.pos = options.pos;
50 this.screen_set = options.screen_set || {};
52 this.popup_set = options.popup_set || {};
54 this.default_client_screen = options.default_client_screen;
55 this.default_cashier_screen = options.default_cashier_screen;
57 this.current_client_screen = this.screen_set[this.default_client_screen];
59 this.current_cashier_screen = this.screen_set[this.default_client_screen];
61 this.current_popup = null;
63 this.current_mode = options.default_mode || 'client';
65 this.current_screen = this.current_mode === 'client' ?
66 this.current_client_screen:
67 this.current_cashier_screen;
70 for(screen_name in this.screen_set){
71 var screen = this.screen_set[screen_name];
72 if(screen === this.current_screen){
79 for(popup_name in this.popup_set){
80 this.popup_set[popup_name].hide();
87 this.selected_order = this.pos.get('selectedOrder');
88 this.selected_order.set({
89 user_mode : this.current_mode,
90 client_screen: this.default_client_screen,
91 cashier_screen: this.default_cashier_screen,
94 this.pos.bind('change:selectedOrder', this.load_saved_screen, this);
96 add_screen: function(screen_name, screen){
98 this.screen_set[screen_name] = screen;
101 show_popup: function(name){
102 if(this.current_popup){
105 this.current_popup = this.popup_set[name];
106 this.current_popup.show();
108 close_popup: function(){
109 if(this.current_popup){
110 this.current_popup.hide();
111 this.current_popup = null;
114 load_saved_screen: function(){
117 var selectedOrder = this.pos.get('selectedOrder');
119 if(this.current_mode === 'client'){
120 this.set_current_screen(selectedOrder.get('client_screen') || this.default_client_screen);
121 }else if(this.current_mode === 'cashier'){
122 this.set_current_screen(selectedOrder.get('cashier_screen') || this.default_cashier_screen);
124 this.selected_order = selectedOrder;
126 set_user_mode: function(user_mode){
127 if(user_mode !== this.current_mode){
129 this.current_mode = user_mode;
130 this.load_saved_screen();
133 get_user_mode: function(){
134 return this.current_mode;
136 set_current_screen: function(screen_name){
137 var screen = this.screen_set[screen_name];
140 var selectedOrder = this.pos.get('selectedOrder');
141 if(this.current_mode === 'client'){
142 selectedOrder.set({'client_screen': screen_name});
144 selectedOrder.set({'cashier_screen': screen_name});
147 if(screen && screen !== this.current_screen){
148 if(this.current_screen){
149 this.current_screen.hide();
151 this.current_screen = screen;
152 this.current_screen.show();
157 module.ScreenWidget = instance.web.Widget.extend({
158 init: function(parent, options){
159 this._super(parent, options);
160 options = options || {};
161 this.pos = options.pos;
162 this.pos_widget = options.pos_widget;
166 this.$element.show();
171 this.$element.hide();
173 if(this.pos.barcode_reader){
174 this.pos.barcode_reader.reset_action_callbacks();
176 if(this.pos_widget.action_bar){
177 this.pos_widget.action_bar.destroy_buttons();
182 module.PopUpWidget = module.ScreenWidget.extend({
185 this.$element.hide();
190 module.HelpPopupWidget = module.PopUpWidget.extend({
191 template:'HelpPopupWidget',
194 this.pos.proxy.help_needed();
197 this.$element.find('.button').off('click').click(function(){
198 self.pos.screen_selector.close_popup();
199 self.pos.proxy.help_canceled();
204 module.ReceiptPopupWidget = module.PopUpWidget.extend({
205 template:'ReceiptPopupWidget',
209 this.$element.find('.receipt').off('click').click(function(){
210 console.log('receipt!'); //TODO
211 self.pos.screen_selector.set_current_screen('scan');
213 this.$element.find('.invoice').off('click').click(function(){
214 console.log('invoice!'); //TODO
215 self.pos.screen_selector.set_current_screen('scan');
220 module.ErrorPopupWidget = module.PopUpWidget.extend({
221 template:'ErrorPopupWidget',
225 this.pos.proxy.help_needed();
226 this.pos.proxy.scan_item_error_unrecognized();
228 this.pos.barcode_reader.save_callbacks();
229 this.pos.barcode_reader.reset_action_callbacks();
230 this.pos.barcode_reader.set_action_callbacks({
231 'cashier': function(ean){
232 clearInterval(this.intervalID);
233 self.pos.proxy.cashier_mode_activated();
234 self.pos.screen_selector.set_user_mode('cashier');
240 this.pos.proxy.help_canceled();
241 this.pos.barcode_reader.restore_callbacks();
245 module.ScaleInviteScreenWidget = module.ScreenWidget.extend({
246 template:'ScaleInviteScreenWidget',
251 this.pos_widget.set_numpad_visible(false);
252 this.pos_widget.set_leftpane_visible(true);
253 this.pos_widget.set_cashier_controls_visible(false);
254 this.pos_widget.action_bar.set_total_visible(true);
255 this.pos_widget.action_bar.set_help_visible(true,function(){self.pos.screen_selector.show_popup('help');});
256 this.pos_widget.action_bar.set_logout_visible(false);
258 self.pos.proxy.weighting_start();
260 this.intervalID = setInterval(function(){
261 var weight = self.pos.proxy.weighting_read_kg();
263 clearInterval(this.intervalID);
264 self.pos.screen_selector.set_current_screen('scale_product');
268 this.pos_widget.action_bar.add_new_button(
271 icon: '/point_of_sale/static/src/img/icons/png48/go-previous.png',
273 clearInterval(this.intervalID);
274 self.pos.proxy.weighting_end();
275 self.pos.screen_selector.set_current_screen('scan');
280 this.pos.barcode_reader.set_action_callbacks({
281 'cashier': function(ean){
282 self.pos.proxy.cashier_mode_activated();
283 self.pos.screen_selector.set_user_mode('cashier');
285 'product': function(ean){
286 if(self.pos_widget.scan_product(ean)){
287 self.pos.proxy.scan_item_success();
288 self.pos.screen_selector.set_current_screen('scan');
290 self.pos.screen_selector.show_popup('error');
297 clearInterval(this.intervalID);
301 module.ScaleProductScreenWidget = module.ScreenWidget.extend({
302 template:'ScaleProductSelectionScreenWidget',
304 this.product_categories_widget = new module.ProductCategoriesWidget(null,{
307 this.product_categories_widget.replace($('.placeholder-ProductCategoriesWidget'));
309 this.product_list_widget = new module.ProductListWidget(null,{
311 weight: this.pos.proxy.weighting_read_kg(),
313 this.product_list_widget.replace($('.placeholder-ProductListWidget'));
318 if(this.pos.screen_selector.get_user_mode() === 'client'){
319 this.pos_widget.set_numpad_visible(false);
320 this.pos_widget.set_leftpane_visible(true);
321 this.pos_widget.set_cashier_controls_visible(false);
322 this.pos_widget.action_bar.set_total_visible(true);
323 this.pos_widget.action_bar.set_help_visible(true,function(){self.pos.screen_selector.show_popup('help');});
324 this.pos_widget.action_bar.set_logout_visible(false);
326 this.pos_widget.orderView.setNumpadState(this.pos_widget.numpadView.state);
327 this.pos_widget.action_bar.add_new_button(
330 icon: '/point_of_sale/static/src/img/icons/png48/go-previous.png',
332 self.pos.screen_selector.set_current_screen('scan');
336 this.pos.barcode_reader.set_action_callbacks({
337 'cashier': function(ean){
338 self.pos.proxy.cashier_mode_activated();
339 self.pos.screen_selector.set_user_mode('cashier');
341 'product': function(ean){
342 if(self.pos_widget.scan_product(ean)){
343 self.pos.proxy.scan_item_success();
344 self.pos.screen_selector.set_current_screen('scan');
346 self.pos.screen_selector.show_popup('error');
350 this.product_list_widget.set_next_screen('scan');
351 }else{ // user_mode === 'cashier'
352 this.pos_widget.set_numpad_visible(true);
353 this.pos_widget.set_leftpane_visible(true);
354 this.pos_widget.set_cashier_controls_visible(true);
355 this.pos_widget.action_bar.set_total_visible(true);
356 this.pos_widget.action_bar.set_help_visible(false);
358 this.pos_widget.orderView.setNumpadState(this.pos_widget.numpadView.state);
359 this.pos_widget.action_bar.add_new_button(
362 icon: '/point_of_sale/static/src/img/icons/png48/go-previous.png',
364 self.pos.screen_selector.set_current_screen('products');
368 this.product_list_widget.set_next_screen('undefined');
371 this.pos.proxy.weighting_start();
372 this.last_weight = this.product_list_widget.weight;
373 this.intervalID = setInterval(function(){
374 var weight = self.pos.proxy.weighting_read_kg();
375 if(weight != self.last_weight){
376 self.product_list_widget.set_weight(weight);
377 self.last_weight = weight;
383 this.pos_widget.orderView.setNumpadState(null);
384 this.pos_widget.payment_screen.setNumpadState(null);
385 clearInterval(this.intervalID);
386 this.pos.proxy.weighting_end();
390 module.ClientPaymentScreenWidget = module.ScreenWidget.extend({
391 template:'ClientPaymentScreenWidget',
396 this.pos_widget.set_numpad_visible(false);
397 this.pos_widget.set_leftpane_visible(true);
398 this.pos_widget.set_cashier_controls_visible(false);
399 this.pos_widget.action_bar.set_total_visible(true);
400 this.pos_widget.action_bar.set_help_visible(true,function(){self.pos.screen_selector.show_popup('help');});
401 this.pos_widget.action_bar.set_logout_visible(false);
403 this.pos.proxy.payment_request(this.pos.get('selectedOrder').getTotal(),'card','info'); //TODO TOTAL
405 this.intervalID = setInterval(function(){
406 var payment = self.pos.proxy.is_payment_accepted();
407 if(payment === 'payment_accepted'){
408 clearInterval(this.intervalID);
409 var currentOrder = self.pos.get('selectedOrder');
410 self.pos.push_order(currentOrder.exportAsJSON()).then(function() {
411 currentOrder.destroy();
412 self.pos.proxy.transaction_end();
413 self.pos.screen_selector.set_current_screen('welcome');
415 }else if(payment === 'payment_rejected'){
416 clearInterval(this.intervalID);
417 //TODO show a tryagain thingie ?
421 this.pos_widget.action_bar.add_new_button(
424 icon: '/point_of_sale/static/src/img/icons/png48/go-previous.png',
425 click: function(){ //TODO Go to ask for weighting screen
426 clearInterval(this.intervalID);
427 self.pos.proxy.payment_canceled();
428 self.pos.screen_selector.set_current_screen('scan');
433 this.pos.barcode_reader.set_action_callbacks({
434 'cashier': function(ean){
435 clearInterval(this.intervalID);
436 self.pos.proxy.cashier_mode_activated();
437 self.pos.screen_selector.set_user_mode('cashier');
443 clearInterval(this.intervalID);
447 module.WelcomeScreenWidget = module.ScreenWidget.extend({
448 template:'WelcomeScreenWidget',
453 this.pos_widget.set_numpad_visible(false);
454 this.pos_widget.set_leftpane_visible(false);
455 this.pos_widget.set_cashier_controls_visible(false);
456 this.pos_widget.action_bar.set_total_visible(false);
457 this.pos_widget.action_bar.set_help_visible(true,function(){self.pos.screen_selector.show_popup('help');});
458 this.pos_widget.action_bar.set_logout_visible(false);
460 this.pos_widget.action_bar.add_new_button(
464 self.pos.screen_selector.set_current_screen('scan');
468 icon: '/point_of_sale/static/src/img/icons/png48/scale.png',
469 click: function(){ //TODO Go to ask for weighting screen
470 self.pos.screen_selector.set_current_screen('scale_invite');
474 this.pos.barcode_reader.set_action_callbacks({
475 'product': function(ean){
476 self.pos.proxy.transaction_start();
477 if(self.pos_widget.scan_product(ean)){
478 self.pos.proxy.scan_item_success();
479 self.pos.screen_selector.set_current_screen('scan');
481 self.pos.screen_selector.show_popup('error');
484 'cashier': function(ean){
485 //TODO 'switch to cashier mode'
486 self.pos.proxy.cashier_mode_activated();
487 self.pos.screen_selector.set_user_mode('cashier');
489 'client': function(ean){
490 self.pos.proxy.transaction_start();
491 //TODO 'log the client'
492 self.pos.screen_selector.show_popup('receipt');
494 'discount': function(ean){
495 // TODO : what to do in this case ????
501 this.pos.barcode_reader.reset_action_callbacks();
502 this.pos_widget.action_bar.destroy_buttons();
506 module.ScanProductScreenWidget = module.ScreenWidget.extend({
507 template:'ScanProductScreenWidget',
512 this.pos_widget.set_numpad_visible(false);
513 this.pos_widget.set_leftpane_visible(true);
514 this.pos_widget.set_cashier_controls_visible(false);
515 this.pos_widget.action_bar.set_total_visible(true);
516 this.pos_widget.action_bar.set_help_visible(true,function(){self.pos.screen_selector.show_popup('help');});
517 this.pos_widget.action_bar.set_logout_visible(false);
519 this.pos_widget.action_bar.add_new_button(
522 icon: '/point_of_sale/static/src/img/icons/png48/scale.png',
523 click: function(){ //TODO Go to ask for weighting screen
524 self.pos.screen_selector.set_current_screen('scale_invite');
528 icon: '/point_of_sale/static/src/img/icons/png48/go-next.png',
530 self.pos.screen_selector.set_current_screen('client_payment'); //TODO what stuff ?
534 this.pos.barcode_reader.set_action_callbacks({
535 'product': function(ean){
536 if(self.pos_widget.scan_product(ean)){
537 self.pos.proxy.scan_item_success();
539 self.pos.screen_selector.show_popup('error');
542 'cashier': function(ean){
543 self.pos.proxy.cashier_mode_activated();
544 self.pos.screen_selector.set_user_mode('cashier');
546 'discount': function(ean){
547 // TODO : handle the discount
553 module.SearchProductScreenWidget = module.ScreenWidget.extend({
554 template:'SearchProductScreenWidget',
556 this.product_categories_widget = new module.ProductCategoriesWidget(null,{
559 this.product_categories_widget.replace($('.placeholder-ProductCategoriesWidget'));
561 this.product_list_widget = new module.ProductListWidget(null,{
564 this.product_list_widget.replace($('.placeholder-ProductListWidget'));
570 this.pos_widget.set_numpad_visible(true);
571 this.pos_widget.set_leftpane_visible(true);
572 this.pos_widget.set_cashier_controls_visible(true);
573 this.pos_widget.action_bar.set_total_visible(true);
574 this.pos_widget.action_bar.set_help_visible(false);
575 this.pos_widget.action_bar.set_logout_visible(true, function(){
576 self.pos.screen_selector.set_user_mode('client');
579 this.pos_widget.orderView.setNumpadState(this.pos_widget.numpadView.state);
580 this.pos_widget.action_bar.add_new_button(
583 icon: '/point_of_sale/static/src/img/icons/png48/scale.png',
585 self.pos.screen_selector.set_current_screen('scale_product');
589 this.pos.barcode_reader.set_action_callbacks({
590 'product': function(ean){
591 if(self.pos_widget.scan_product(ean)){
592 self.pos.proxy.scan_item_success();
594 self.pos.screen_selector.show_popup('error');
597 'cashier': function(ean){
598 self.pos.proxy.cashier_mode_activated();
599 self.pos.screen_selector.set_user_mode('cashier');
601 'discount': function(ean){
602 // TODO : handle the discount
608 this.pos_widget.orderView.setNumpadState(null);
609 this.pos_widget.payment_screen.setNumpadState(null);
614 module.ReceiptScreenWidget = module.ScreenWidget.extend({
615 template: 'ReceiptScreenWidget',
616 init: function(parent, options) {
617 this._super(parent,options);
618 this.model = options.model;
619 this.user = this.pos.get('user');
620 this.company = this.pos.get('company');
621 this.shop_obj = this.pos.get('shop');
624 this.pos.bind('change:selectedOrder', this.changeSelectedOrder, this);
625 this.changeSelectedOrder();
626 $('button#pos-finish-order', this.$element).click(_.bind(this.finishOrder, this));
627 $('button#print-the-ticket', this.$element).click(_.bind(this.print, this));
633 this.pos_widget.set_numpad_visible(true);
634 this.pos_widget.set_leftpane_visible(true);
635 this.pos_widget.set_cashier_controls_visible(true);
636 this.pos_widget.action_bar.set_total_visible(true);
637 this.pos_widget.action_bar.set_help_visible(false);
638 this.pos_widget.action_bar.set_logout_visible(true, function(){
639 self.pos.screen_selector.set_user_mode('client');
645 finishOrder: function() {
646 this.pos.get('selectedOrder').destroy();
648 changeSelectedOrder: function() {
649 if (this.currentOrderLines)
650 this.currentOrderLines.unbind();
651 this.currentOrderLines = (this.pos.get('selectedOrder')).get('orderLines');
652 this.currentOrderLines.bind('add', this.refresh, this);
653 this.currentOrderLines.bind('change', this.refresh, this);
654 this.currentOrderLines.bind('remove', this.refresh, this);
655 if (this.currentPaymentLines)
656 this.currentPaymentLines.unbind();
657 this.currentPaymentLines = (this.pos.get('selectedOrder')).get('paymentLines');
658 this.currentPaymentLines.bind('all', this.refresh, this);
661 refresh: function() {
662 this.currentOrder = this.pos.get('selectedOrder');
663 $('.pos-receipt-container', this.$element).html(qweb_template('pos-ticket')({widget:this}));
667 module.PaymentScreenWidget = module.ScreenWidget.extend({
668 template_fct: qweb_template('PaymentScreenWidget'),
669 init: function(parent, options) {
670 this._super(parent,options);
671 this.model = options.model;
672 this.pos.bind('change:selectedOrder', this.changeSelectedOrder, this);
673 this.bindPaymentLineEvents();
674 this.bindOrderLineEvents();
680 this.pos_widget.set_numpad_visible(true);
681 this.pos_widget.set_leftpane_visible(true);
682 this.pos_widget.set_cashier_controls_visible(true);
683 this.pos_widget.action_bar.set_total_visible(true);
684 this.pos_widget.action_bar.set_help_visible(false);
685 this.pos_widget.action_bar.set_logout_visible(true, function(){
686 self.pos.screen_selector.set_user_mode('client');
689 this.setNumpadState(this.pos_widget.numpadView.state);
693 this.pos_widget.orderView.setNumpadState(null);
694 this.pos_widget.payment_screen.setNumpadState(null);
696 paymentLineList: function() {
697 return this.$element.find('#paymentlines');
700 this.pos.screen_selector.set_current_screen('products');
702 validateCurrentOrder: function() {
703 var callback, currentOrder;
704 currentOrder = this.pos.get('selectedOrder');
705 $('button#validate-order', this.$element).attr('disabled', 'disabled');
706 this.pos.push_order(currentOrder.exportAsJSON()).then(_.bind(function() {
707 $('button#validate-order', this.$element).removeAttr('disabled');
708 return currentOrder.set({
713 bindPaymentLineEvents: function() {
714 this.currentPaymentLines = (this.pos.get('selectedOrder')).get('paymentLines');
715 this.currentPaymentLines.bind('add', this.addPaymentLine, this);
716 this.currentPaymentLines.bind('remove', this.renderElement, this);
717 this.currentPaymentLines.bind('all', this.updatePaymentSummary, this);
719 bindOrderLineEvents: function() {
720 this.currentOrderLines = (this.pos.get('selectedOrder')).get('orderLines');
721 this.currentOrderLines.bind('all', this.updatePaymentSummary, this);
723 changeSelectedOrder: function() {
724 this.currentPaymentLines.unbind();
725 this.bindPaymentLineEvents();
726 this.currentOrderLines.unbind();
727 this.bindOrderLineEvents();
728 this.renderElement();
730 addPaymentLine: function(newPaymentLine) {
731 var x = new module.PaymentlineWidget(null, {
732 model: newPaymentLine
734 x.on_delete.add(_.bind(this.deleteLine, this, x));
735 x.appendTo(this.paymentLineList());
737 renderElement: function() {
739 this.$element.html(this.template_fct());
740 this.paymentLineList().empty();
741 this.currentPaymentLines.each(_.bind( function(paymentLine) {
742 this.addPaymentLine(paymentLine);
744 this.updatePaymentSummary();
745 $('button#validate-order', this.$element).click(_.bind(this.validateCurrentOrder, this));
746 $('.oe-back-to-products', this.$element).click(_.bind(this.back, this));
748 deleteLine: function(lineWidget) {
749 this.currentPaymentLines.remove([lineWidget.model]);
751 updatePaymentSummary: function() {
752 var currentOrder, dueTotal, paidTotal, remaining, remainingAmount;
753 currentOrder = this.pos.get('selectedOrder');
754 paidTotal = currentOrder.getPaidTotal();
755 dueTotal = currentOrder.getTotal();
756 this.$element.find('#payment-due-total').html(dueTotal.toFixed(2));
757 this.$element.find('#payment-paid-total').html(paidTotal.toFixed(2));
758 remainingAmount = dueTotal - paidTotal;
759 remaining = remainingAmount > 0 ? 0 : (-remainingAmount).toFixed(2);
760 $('#payment-remaining').html(remaining);
762 setNumpadState: function(numpadState) {
763 if (this.numpadState) {
764 this.numpadState.unbind('setValue', this.setValue);
765 this.numpadState.unbind('change:mode', this.setNumpadMode);
767 this.numpadState = numpadState;
768 if (this.numpadState) {
769 this.numpadState.bind('setValue', this.setValue, this);
770 this.numpadState.bind('change:mode', this.setNumpadMode, this);
771 this.numpadState.reset();
772 this.setNumpadMode();
775 setNumpadMode: function() {
776 this.numpadState.set({mode: 'payment'});
778 setValue: function(val) {
779 this.currentPaymentLines.last().set({amount: val});