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 console.log('default_cashier_screen:',this.default_cashier_screen);
123 this.set_current_screen(selectedOrder.get('cashier_screen') || this.default_cashier_screen);
125 this.selected_order = selectedOrder;
127 set_user_mode: function(user_mode){
128 if(user_mode !== this.current_mode){
130 this.current_mode = user_mode;
131 this.load_saved_screen();
134 get_user_mode: function(){
135 return this.current_mode;
137 set_current_screen: function(screen_name){
138 var screen = this.screen_set[screen_name];
141 var selectedOrder = this.pos.get('selectedOrder');
142 if(this.current_mode === 'client'){
143 selectedOrder.set({'client_screen': screen_name});
145 selectedOrder.set({'cashier_screen': screen_name});
148 if(screen && screen !== this.current_screen){
149 if(this.current_screen){
150 this.current_screen.hide();
152 this.current_screen = screen;
153 this.current_screen.show();
158 module.ScreenWidget = instance.web.Widget.extend({
159 init: function(parent, options){
160 this._super(parent, options);
161 options = options || {};
162 this.pos = options.pos;
163 this.pos_widget = options.pos_widget;
167 this.$element.show();
172 this.$element.hide();
174 if(this.pos.barcode_reader){
175 this.pos.barcode_reader.reset_action_callbacks();
177 if(this.pos_widget.action_bar){
178 this.pos_widget.action_bar.destroy_buttons();
183 module.PopUpWidget = module.ScreenWidget.extend({
186 this.$element.hide();
191 module.HelpPopupWidget = module.PopUpWidget.extend({
192 template:'HelpPopupWidget',
195 this.pos.proxy.help_needed();
198 this.$element.find('.button').off('click').click(function(){
199 self.pos.screen_selector.close_popup();
200 self.pos.proxy.help_canceled();
205 module.ReceiptPopupWidget = module.PopUpWidget.extend({
206 template:'ReceiptPopupWidget',
210 this.$element.find('.receipt').off('click').click(function(){
211 console.log('receipt!'); //TODO
212 self.pos.screen_selector.set_current_screen('scan');
214 this.$element.find('.invoice').off('click').click(function(){
215 console.log('invoice!'); //TODO
216 self.pos.screen_selector.set_current_screen('scan');
221 module.ErrorPopupWidget = module.PopUpWidget.extend({
222 template:'ErrorPopupWidget',
225 this.pos.proxy.help_needed();
228 this.$element.find('.button').off('click').click(function(){
229 self.pos.screen_selector.close_popup();
230 self.pos.proxy.help_canceled();
235 this.pos.proxy.help_canceled();
239 module.ScaleInviteScreenWidget = module.ScreenWidget.extend({
240 template:'ScaleInviteScreenWidget',
245 this.pos_widget.set_numpad_visible(false);
246 this.pos_widget.set_leftpane_visible(true);
247 this.pos_widget.set_cashier_controls_visible(false);
248 this.pos_widget.action_bar.set_total_visible(true);
249 this.pos_widget.action_bar.set_help_visible(true,function(){self.pos.screen_selector.show_popup('help');});
250 this.pos_widget.action_bar.set_logout_visible(false);
252 self.pos.proxy.weighting_start();
254 this.intervalID = setInterval(function(){
255 var weight = self.pos.proxy.weighting_read_kg();
257 clearInterval(this.intervalID);
258 self.pos.screen_selector.set_current_screen('scale_product');
262 this.pos_widget.action_bar.add_new_button(
265 icon: '/point_of_sale/static/src/img/icons/png48/go-previous.png',
267 clearInterval(this.intervalID);
268 self.pos.proxy.weighting_end();
269 self.pos.screen_selector.set_current_screen('scan');
276 clearInterval(this.intervalID);
280 module.ScaleProductScreenWidget = module.ScreenWidget.extend({
281 template:'ScaleProductSelectionScreenWidget',
283 this.product_categories_widget = new module.ProductCategoriesWidget(null,{
286 this.product_categories_widget.replace($('.placeholder-ProductCategoriesWidget'));
288 this.product_list_widget = new module.ProductListWidget(null,{
290 weight: this.pos.proxy.weighting_read_kg(),
292 this.product_list_widget.replace($('.placeholder-ProductListWidget'));
297 if(this.pos.screen_selector.get_user_mode() === 'client'){
298 this.pos_widget.set_numpad_visible(false);
299 this.pos_widget.set_leftpane_visible(true);
300 this.pos_widget.set_cashier_controls_visible(false);
301 this.pos_widget.action_bar.set_total_visible(true);
302 this.pos_widget.action_bar.set_help_visible(true,function(){self.pos.screen_selector.show_popup('help');});
303 this.pos_widget.action_bar.set_logout_visible(false);
305 this.pos_widget.orderView.setNumpadState(this.pos_widget.numpadView.state);
306 this.pos_widget.action_bar.add_new_button(
309 icon: '/point_of_sale/static/src/img/icons/png48/go-previous.png',
311 self.pos.screen_selector.set_current_screen('scan');
315 this.pos.barcode_reader.set_action_callbacks({
316 'cashier': function(ean){
317 self.proxy.cashier_mode_activated();
320 this.product_list_widget.set_next_screen('scan');
321 }else{ // user_mode === 'cashier'
322 this.pos_widget.set_numpad_visible(true);
323 this.pos_widget.set_leftpane_visible(true);
324 this.pos_widget.set_cashier_controls_visible(true);
325 this.pos_widget.action_bar.set_total_visible(true);
326 this.pos_widget.action_bar.set_help_visible(false);
328 this.pos_widget.orderView.setNumpadState(this.pos_widget.numpadView.state);
329 this.pos_widget.action_bar.add_new_button(
332 icon: '/point_of_sale/static/src/img/icons/png48/go-previous.png',
334 self.pos.screen_selector.set_current_screen('products');
338 this.product_list_widget.set_next_screen('undefined');
341 this.pos.proxy.weighting_start();
342 this.last_weight = this.product_list_widget.weight;
343 this.intervalID = setInterval(function(){
344 var weight = self.pos.proxy.weighting_read_kg();
345 if(weight != self.last_weight){
346 self.product_list_widget.set_weight(weight);
347 self.last_weight = weight;
353 this.pos_widget.orderView.setNumpadState(null);
354 this.pos_widget.payment_screen.setNumpadState(null);
355 clearInterval(this.intervalID);
356 this.pos.proxy.weighting_end();
360 module.ClientPaymentScreenWidget = module.ScreenWidget.extend({
361 template:'ClientPaymentScreenWidget',
366 this.pos_widget.set_numpad_visible(false);
367 this.pos_widget.set_leftpane_visible(true);
368 this.pos_widget.set_cashier_controls_visible(false);
369 this.pos_widget.action_bar.set_total_visible(true);
370 this.pos_widget.action_bar.set_help_visible(true,function(){self.pos.screen_selector.show_popup('help');});
371 this.pos_widget.action_bar.set_logout_visible(false);
373 this.pos.proxy.payment_request(0,'card','info'); //TODO TOTAL
375 this.intervalID = setInterval(function(){
376 var payment = self.pos.proxy.is_payment_accepted();
377 if(payment === 'payment_accepted'){
378 clearInterval(this.intervalID);
379 //TODO process the payment stuff
380 self.pos.proxy.transaction_end();
381 self.pos.screen_selector.set_current_screen('welcome');
382 }else if(payment === 'payment_rejected'){
383 clearInterval(this.intervalID);
384 //TODO show a tryagain thingie ?
388 this.pos_widget.action_bar.add_new_button(
391 icon: '/point_of_sale/static/src/img/icons/png48/go-previous.png',
392 click: function(){ //TODO Go to ask for weighting screen
393 clearInterval(this.intervalID);
394 self.pos.proxy.payment_canceled();
395 self.pos.screen_selector.set_current_screen('scan');
400 this.pos.barcode_reader.set_action_callbacks({
401 'cashier': function(ean){
402 //TODO 'switch to cashier mode'
403 clearInterval(this.intervalID);
404 self.proxy.cashier_mode_activated();
405 self.pos.screen_selector.set_current_screen('products');
411 clearInterval(this.intervalID);
415 module.WelcomeScreenWidget = module.ScreenWidget.extend({
416 template:'WelcomeScreenWidget',
421 this.pos_widget.set_numpad_visible(false);
422 this.pos_widget.set_leftpane_visible(false);
423 this.pos_widget.set_cashier_controls_visible(false);
424 this.pos_widget.action_bar.set_total_visible(false);
425 this.pos_widget.action_bar.set_help_visible(true,function(){self.pos.screen_selector.show_popup('help');});
426 this.pos_widget.action_bar.set_logout_visible(false);
428 this.pos_widget.action_bar.add_new_button(
432 self.pos.screen_selector.set_current_screen('scan');
436 icon: '/point_of_sale/static/src/img/icons/png48/scale.png',
437 click: function(){ //TODO Go to ask for weighting screen
438 self.pos.screen_selector.set_current_screen('scale_invite');
442 this.pos.barcode_reader.set_action_callbacks({
443 'product': function(ean){
444 console.log('product!');
445 self.pos.proxy.transaction_start();
446 self.pos.barcode_reader.scan_product_callback(ean);
447 self.pos.screen_selector.set_current_screen('products');
449 'cashier': function(ean){
450 //TODO 'switch to cashier mode'
451 self.pos.proxy.cashier_mode_activated();
452 self.pos.screen_selector.set_current_screen('products');
454 'client': function(ean){
455 self.pos.proxy.transaction_start();
456 //TODO 'log the client'
457 self.pos.screen_selector.show_popup('receipt');
459 'discount': function(ean){
460 // TODO : what to do in this case ????
466 this.pos.barcode_reader.reset_action_callbacks();
467 this.pos_widget.action_bar.destroy_buttons();
471 module.ScanProductScreenWidget = module.ScreenWidget.extend({
472 template:'ScanProductScreenWidget',
477 this.pos_widget.set_numpad_visible(false);
478 this.pos_widget.set_leftpane_visible(true);
479 this.pos_widget.set_cashier_controls_visible(false);
480 this.pos_widget.action_bar.set_total_visible(true);
481 this.pos_widget.action_bar.set_help_visible(true,function(){self.pos.screen_selector.show_popup('help');});
482 this.pos_widget.action_bar.set_logout_visible(false);
484 this.pos_widget.action_bar.add_new_button(
487 icon: '/point_of_sale/static/src/img/icons/png48/scale.png',
488 click: function(){ //TODO Go to ask for weighting screen
489 self.pos.screen_selector.set_current_screen('scale_invite');
493 icon: '/point_of_sale/static/src/img/icons/png48/go-next.png',
495 self.pos.screen_selector.set_current_screen('client_payment'); //TODO what stuff ?
499 this.pos.barcode_reader.set_action_callbacks({
500 'product': function(ean){
501 var success = self.pos.barcode_reader.scan_product_callback(ean);
503 self.proxy.scan_item_success();
505 self.proxy.scan_item_error_unrecognized();
508 'cashier': function(ean){
509 //TODO 'switch to cashier mode'
510 self.proxy.cashier_mode_activated();
512 'discount': function(ean){
513 // TODO : handle the discount
519 module.SearchProductScreenWidget = module.ScreenWidget.extend({
520 template:'SearchProductScreenWidget',
522 this.product_categories_widget = new module.ProductCategoriesWidget(null,{
525 this.product_categories_widget.replace($('.placeholder-ProductCategoriesWidget'));
527 this.product_list_widget = new module.ProductListWidget(null,{
530 this.product_list_widget.replace($('.placeholder-ProductListWidget'));
536 this.pos_widget.set_numpad_visible(true);
537 this.pos_widget.set_leftpane_visible(true);
538 this.pos_widget.set_cashier_controls_visible(true);
539 this.pos_widget.action_bar.set_total_visible(true);
540 this.pos_widget.action_bar.set_help_visible(false);
541 this.pos_widget.action_bar.set_logout_visible(true, function(){
542 self.pos.screen_selector.set_user_mode('client');
545 this.pos_widget.orderView.setNumpadState(this.pos_widget.numpadView.state);
546 this.pos_widget.action_bar.add_new_button(
549 icon: '/point_of_sale/static/src/img/icons/png48/scale.png',
551 self.pos.screen_selector.set_current_screen('scale_product');
555 this.pos.barcode_reader.set_action_callbacks({
556 'product': function(ean){
557 var success = self.pos.barcode_reader.scan_product_callback(ean);
559 self.proxy.scan_item_success();
561 self.proxy.scan_item_error_unrecognized();
564 'cashier': function(ean){
565 self.proxy.cashier_mode_activated();
567 'discount': function(ean){
568 // TODO : handle the discount
574 this.pos_widget.orderView.setNumpadState(null);
575 this.pos_widget.payment_screen.setNumpadState(null);
580 module.ReceiptScreenWidget = module.ScreenWidget.extend({
581 template: 'ReceiptScreenWidget',
582 init: function(parent, options) {
583 this._super(parent,options);
584 this.model = options.model;
585 this.user = this.pos.get('user');
586 this.company = this.pos.get('company');
587 this.shop_obj = this.pos.get('shop');
590 this.pos.bind('change:selectedOrder', this.changeSelectedOrder, this);
591 this.changeSelectedOrder();
592 $('button#pos-finish-order', this.$element).click(_.bind(this.finishOrder, this));
593 $('button#print-the-ticket', this.$element).click(_.bind(this.print, this));
599 this.pos_widget.set_numpad_visible(true);
600 this.pos_widget.set_leftpane_visible(true);
601 this.pos_widget.set_cashier_controls_visible(true);
602 this.pos_widget.action_bar.set_total_visible(true);
603 this.pos_widget.action_bar.set_help_visible(false);
604 this.pos_widget.action_bar.set_logout_visible(true, function(){
605 self.pos.screen_selector.set_user_mode('client');
611 finishOrder: function() {
612 this.pos.get('selectedOrder').destroy();
614 changeSelectedOrder: function() {
615 if (this.currentOrderLines)
616 this.currentOrderLines.unbind();
617 this.currentOrderLines = (this.pos.get('selectedOrder')).get('orderLines');
618 this.currentOrderLines.bind('add', this.refresh, this);
619 this.currentOrderLines.bind('change', this.refresh, this);
620 this.currentOrderLines.bind('remove', this.refresh, this);
621 if (this.currentPaymentLines)
622 this.currentPaymentLines.unbind();
623 this.currentPaymentLines = (this.pos.get('selectedOrder')).get('paymentLines');
624 this.currentPaymentLines.bind('all', this.refresh, this);
627 refresh: function() {
628 this.currentOrder = this.pos.get('selectedOrder');
629 $('.pos-receipt-container', this.$element).html(qweb_template('pos-ticket')({widget:this}));
633 module.PaymentScreenWidget = module.ScreenWidget.extend({
634 template_fct: qweb_template('PaymentScreenWidget'),
635 init: function(parent, options) {
636 this._super(parent,options);
637 this.model = options.model;
638 this.pos.bind('change:selectedOrder', this.changeSelectedOrder, this);
639 this.bindPaymentLineEvents();
640 this.bindOrderLineEvents();
646 this.pos_widget.set_numpad_visible(true);
647 this.pos_widget.set_leftpane_visible(true);
648 this.pos_widget.set_cashier_controls_visible(true);
649 this.pos_widget.action_bar.set_total_visible(true);
650 this.pos_widget.action_bar.set_help_visible(false);
651 this.pos_widget.action_bar.set_logout_visible(true, function(){
652 self.pos.screen_selector.set_user_mode('client');
655 this.setNumpadState(this.pos_widget.numpadView.state);
659 this.pos_widget.orderView.setNumpadState(null);
660 this.pos_widget.payment_screen.setNumpadState(null);
662 paymentLineList: function() {
663 return this.$element.find('#paymentlines');
667 this.pos.screen_selector.set_current_screen('products');
669 validateCurrentOrder: function() {
670 var callback, currentOrder;
671 currentOrder = this.pos.get('selectedOrder');
672 $('button#validate-order', this.$element).attr('disabled', 'disabled');
673 this.pos.push_order(currentOrder.exportAsJSON()).then(_.bind(function() {
674 $('button#validate-order', this.$element).removeAttr('disabled');
675 return currentOrder.set({
680 bindPaymentLineEvents: function() {
681 this.currentPaymentLines = (this.pos.get('selectedOrder')).get('paymentLines');
682 this.currentPaymentLines.bind('add', this.addPaymentLine, this);
683 this.currentPaymentLines.bind('remove', this.renderElement, this);
684 this.currentPaymentLines.bind('all', this.updatePaymentSummary, this);
686 bindOrderLineEvents: function() {
687 this.currentOrderLines = (this.pos.get('selectedOrder')).get('orderLines');
688 this.currentOrderLines.bind('all', this.updatePaymentSummary, this);
690 changeSelectedOrder: function() {
691 this.currentPaymentLines.unbind();
692 this.bindPaymentLineEvents();
693 this.currentOrderLines.unbind();
694 this.bindOrderLineEvents();
695 this.renderElement();
697 addPaymentLine: function(newPaymentLine) {
698 var x = new module.PaymentlineWidget(null, {
699 model: newPaymentLine
701 x.on_delete.add(_.bind(this.deleteLine, this, x));
702 x.appendTo(this.paymentLineList());
704 renderElement: function() {
706 this.$element.html(this.template_fct());
707 this.paymentLineList().empty();
708 this.currentPaymentLines.each(_.bind( function(paymentLine) {
709 this.addPaymentLine(paymentLine);
711 this.updatePaymentSummary();
712 $('button#validate-order', this.$element).click(_.bind(this.validateCurrentOrder, this));
713 $('.oe-back-to-products', this.$element).click(_.bind(this.back, this));
715 deleteLine: function(lineWidget) {
716 this.currentPaymentLines.remove([lineWidget.model]);
718 updatePaymentSummary: function() {
719 var currentOrder, dueTotal, paidTotal, remaining, remainingAmount;
720 currentOrder = this.pos.get('selectedOrder');
721 paidTotal = currentOrder.getPaidTotal();
722 dueTotal = currentOrder.getTotal();
723 this.$element.find('#payment-due-total').html(dueTotal.toFixed(2));
724 this.$element.find('#payment-paid-total').html(paidTotal.toFixed(2));
725 remainingAmount = dueTotal - paidTotal;
726 remaining = remainingAmount > 0 ? 0 : (-remainingAmount).toFixed(2);
727 $('#payment-remaining').html(remaining);
729 setNumpadState: function(numpadState) {
730 if (this.numpadState) {
731 this.numpadState.unbind('setValue', this.setValue);
732 this.numpadState.unbind('change:mode', this.setNumpadMode);
734 this.numpadState = numpadState;
735 if (this.numpadState) {
736 this.numpadState.bind('setValue', this.setValue, this);
737 this.numpadState.bind('change:mode', this.setNumpadMode, this);
738 this.numpadState.reset();
739 this.setNumpadMode();
742 setNumpadMode: function() {
743 this.numpadState.set({mode: 'payment'});
745 setValue: function(val) {
746 this.currentPaymentLines.last().set({amount: val});