// that only one screen is shown at the same time and that show() is called after all
// hide()s
-function openerp_pos_screens(instance, module){ //module is instance.point_of_sale
+openerp.point_of_sale.load_screens = function load_screens(instance, module){ //module is instance.point_of_sale
+ "use strict";
+
var QWeb = instance.web.qweb,
_t = instance.web._t;
+ var round_pr = instance.web.round_precision
+
module.ScreenSelector = instance.web.Class.extend({
init: function(options){
this.pos = options.pos;
-
- this.screen_set = options.screen_set || {};
-
- this.popup_set = options.popup_set || {};
-
- this.default_client_screen = options.default_client_screen;
- this.default_cashier_screen = options.default_cashier_screen;
-
- this.current_popup = null;
-
- this.current_mode = options.default_mode || 'client';
-
+ this.screen_set = options.screen_set || {};
+ this.popup_set = options.popup_set || {};
+ this.default_screen = options.default_screen;
+ this.startup_screen = options.startup_screen;
+ this.current_popup = null;
+ this.current_mode = options.default_mode || 'cashier';
this.current_screen = null;
- for(screen_name in this.screen_set){
+ for (var screen_name in this.screen_set) {
this.screen_set[screen_name].hide();
}
- for(popup_name in this.popup_set){
+ for (var popup_name in this.popup_set) {
this.popup_set[popup_name].hide();
}
- this.selected_order = this.pos.get('selectedOrder');
- this.selected_order.set_screen_data({
- client_screen: this.default_client_screen,
- cashier_screen: this.default_cashier_screen,
- });
+ if (this.pos.get_order()) {
+ this.pos.get_order().set_screen_data({
+ 'screen': this.default_screen,
+ });
+ }
this.pos.bind('change:selectedOrder', this.load_saved_screen, this);
},
this.screen_set[screen_name] = screen;
return this;
},
- show_popup: function(name){
+ show_popup: function(name,options){
if(this.current_popup){
this.close_popup();
}
this.current_popup = this.popup_set[name];
- this.current_popup.show();
+ this.current_popup.show(options);
},
close_popup: function(){
if(this.current_popup){
this.current_popup = null;
}
},
- load_saved_screen: function(){
+ load_saved_screen: function(options){
+ options = options || {};
this.close_popup();
-
- var selectedOrder = this.pos.get('selectedOrder');
+ var selectedOrder = this.pos.get_order();
+ // FIXME : this changing screen behaviour is sometimes confusing ...
+ this.set_current_screen(selectedOrder.get_screen_data('screen') || options.default_screen || this.default_screen,null,'refresh');
+ //this.set_current_screen(this.default_screen,null,'refresh');
- if(this.current_mode === 'client'){
- this.set_current_screen(selectedOrder.get_screen_data('client_screen') || this.default_client_screen,null,'refresh');
- }else if(this.current_mode === 'cashier'){
- this.set_current_screen(selectedOrder.get_screen_data('cashier_screen') || this.default_cashier_screen,null,'refresh');
- }
- this.selected_order = selectedOrder;
},
set_user_mode: function(user_mode){
if(user_mode !== this.current_mode){
}
this.close_popup();
- var selectedOrder = this.pos.get('selectedOrder');
- if(this.current_mode === 'client'){
- selectedOrder.set_screen_data('client_screen',screen_name);
- if(params){
- selectedOrder.set_screen_data('client_screen_params',params);
- }
- }else{
- selectedOrder.set_screen_data('cashier_screen',screen_name);
+
+ var order = this.pos.get_order();
+ if (order) {
+ var old_screen_name = order.get_screen_data('screen');
+
+ order.set_screen_data('screen',screen_name);
+
if(params){
- selectedOrder.set_screen_data('cashier_screen_params',params);
+ order.set_screen_data('params',params);
+ }
+
+ if( screen_name !== old_screen_name ){
+ order.set_screen_data('previous-screen',old_screen_name);
}
}
- if(screen && (refresh || screen !== this.current_screen)){
+ if ( refresh || screen !== this.current_screen){
if(this.current_screen){
this.current_screen.close();
this.current_screen.hide();
this.current_screen.show();
}
},
- get_current_screen_param: function(param){
- var selected_order = this.pos.get('selectedOrder');
- if(this.current_mode === 'client'){
- var params = selected_order.get_screen_data('client_screen_params');
- }else{
- var params = selected_order.get_screen_data('cashier_screen_params');
- }
- if(params){
- return params[param];
- }else{
- return undefined;
+ get_current_screen: function(){
+ return this.pos.get_order().get_screen_data('screen') || this.default_screen;
+ },
+ back: function(){
+ var previous = this.pos.get_order().get_screen_data('previous-screen');
+ if(previous){
+ this.set_current_screen(previous);
}
},
+ get_current_screen_param: function(param){
+ var params = this.pos.get_order().get_screen_data('params');
+ return params ? params[param] : undefined;
+ },
set_default_screen: function(){
- this.set_current_screen(this.current_mode === 'client' ? this.default_client_screen : this.default_cashier_screen);
+ this.set_current_screen(this.default_screen);
+ },
+ change_default_screen: function(screen){
+ this.default_screen = screen;
},
});
},
barcode_product_screen: 'products', //if defined, this screen will be loaded when a product is scanned
- barcode_product_error_popup: 'error-product', //if defined, this popup will be loaded when there's an error in the popup
+
+ hotkeys_handlers: {},
// what happens when a product is scanned :
- // it will add the product to the order and go to barcode_product_screen. Or show barcode_product_error_popup if
- // there's an error.
+ // it will add the product to the order and go to barcode_product_screen.
barcode_product_action: function(code){
var self = this;
if(self.pos.scan_product(code)){
- self.pos.proxy.scan_item_success(code);
if(self.barcode_product_screen){
self.pos_widget.screen_selector.set_current_screen(self.barcode_product_screen);
}
}else{
- self.pos.proxy.scan_item_error_unrecognized(code);
- if(self.barcode_product_error_popup && self.pos_widget.screen_selector.get_user_mode() !== 'cashier'){
- self.pos_widget.screen_selector.show_popup(self.barcode_product_error_popup);
- }
+ self.pos_widget.screen_selector.show_popup('error-barcode',code.code);
}
},
// what happens when a cashier id barcode is scanned.
// the default behavior is the following :
- // - if there's a user with a matching ean, put it as the active 'cashier', go to cashier mode, and return true
+ // - if there's a user with a matching barcode, put it as the active 'cashier', go to cashier mode, and return true
// - else : do nothing and return false. You probably want to extend this to show and appropriate error popup...
barcode_cashier_action: function(code){
- var users = this.pos.get('user_list');
+ var users = this.pos.users;
for(var i = 0, len = users.length; i < len; i++){
- if(users[i].ean13 === code.code){
- this.pos.set('cashier',users[i]);
- this.pos_widget.username.refresh();
- this.pos.proxy.cashier_mode_activated();
- this.pos_widget.screen_selector.set_user_mode('cashier');
+ if(users[i].barcode === code.code){
+ this.pos.set_cashier(users[i]);
+ this.pos_widget.username.renderElement();
return true;
}
}
- this.pos.proxy.scan_item_error_unrecognized(code);
+ this.pos_widget.screen_selector.show_popup('error-barcode',code.code);
return false;
},
// what happens when a client id barcode is scanned.
// the default behavior is the following :
- // - if there's a user with a matching ean, put it as the active 'client' and return true
+ // - if there's a user with a matching barcode, put it as the active 'client' and return true
// - else : return false.
barcode_client_action: function(code){
- var partners = this.pos.get('partner_list');
- for(var i = 0, len = partners.length; i < len; i++){
- if(partners[i].ean13 === code.code){
- this.pos.get('selectedOrder').set_client(partners[i]);
- this.pos_widget.username.refresh();
- this.pos.proxy.scan_item_success(code);
- return true;
- }
+ var partner = this.pos.db.get_partner_by_barcode(code.code);
+ if(partner){
+ this.pos.get_order().set_client(partner);
+ return true;
}
- this.pos.proxy.scan_item_error_unrecognized(code);
+ this.pos_widget.screen_selector.show_popup('error-barcode',code.code);
return false;
- //TODO start the transaction
},
// what happens when a discount barcode is scanned : the default behavior
// is to set the discount on the last order.
barcode_discount_action: function(code){
- this.pos.proxy.scan_item_success(code);
- var last_orderline = this.pos.get('selectedOrder').getLastOrderline();
+ var last_orderline = this.pos.get_order().get_last_orderline();
if(last_orderline){
last_orderline.set_discount(code.value)
}
},
-
- // shows an action bar on the screen. The actionbar is automatically shown when you add a button
- // with add_action_button()
- show_action_bar: function(){
- this.pos_widget.action_bar.show();
- },
-
- // hides the action bar. The actionbar is automatically hidden when it is empty
- hide_action_bar: function(){
- this.pos_widget.action_bar.hide();
- },
-
- // adds a new button to the action bar. The button definition takes three parameters, all optional :
- // - label: the text below the button
- // - icon: a small icon that will be shown
- // - click: a callback that will be executed when the button is clicked.
- // the method returns a reference to the button widget, and automatically show the actionbar.
- add_action_button: function(button_def){
- this.show_action_bar();
- return this.pos_widget.action_bar.add_new_button(button_def);
+ // What happens when an invalid barcode is scanned : shows an error popup.
+ barcode_error_action: function(code){
+ this.pos_widget.screen_selector.show_popup('error-barcode',code.code);
},
// this method shows the screen and sets up all the widget related to this screen. Extend this method
this.$el.removeClass('oe_hidden');
}
- if(this.pos_widget.action_bar.get_button_count() > 0){
- this.show_action_bar();
- }else{
- this.hide_action_bar();
- }
-
- // we add the help button by default. we do this because the buttons are cleared on each refresh so that
- // the button stay local to each screen
- this.pos_widget.left_action_bar.add_new_button({
- label: _t('Help'),
- icon: '/point_of_sale/static/src/img/icons/png48/help.png',
- click: function(){ self.help_button_action(); },
- });
-
var self = this;
- this.cashier_mode = this.pos_widget.screen_selector.get_user_mode() === 'cashier';
- this.pos_widget.set_numpad_visible(this.show_numpad && this.cashier_mode);
+ this.pos_widget.set_numpad_visible(this.show_numpad);
this.pos_widget.set_leftpane_visible(this.show_leftpane);
- this.pos_widget.set_left_action_bar_visible(this.show_leftpane && !this.cashier_mode);
- this.pos_widget.set_cashier_controls_visible(this.cashier_mode);
- if(this.cashier_mode && this.pos.iface_self_checkout){
- this.pos_widget.client_button.show();
- }else{
- this.pos_widget.client_button.hide();
- }
- if(this.cashier_mode){
- this.pos_widget.close_button.show();
- }else{
- this.pos_widget.close_button.hide();
- }
-
this.pos_widget.username.set_user_mode(this.pos_widget.screen_selector.get_user_mode());
-
this.pos.barcode_reader.set_action_callback({
'cashier': self.barcode_cashier_action ? function(code){ self.barcode_cashier_action(code); } : undefined ,
'product': self.barcode_product_action ? function(code){ self.barcode_product_action(code); } : undefined ,
'client' : self.barcode_client_action ? function(code){ self.barcode_client_action(code); } : undefined ,
'discount': self.barcode_discount_action ? function(code){ self.barcode_discount_action(code); } : undefined,
+ 'error' : self.barcode_error_action ? function(code){ self.barcode_error_action(code); } : undefined,
});
},
if(this.pos.barcode_reader){
this.pos.barcode_reader.reset_action_callbacks();
}
- this.pos_widget.action_bar.destroy_buttons();
- this.pos_widget.left_action_bar.destroy_buttons();
},
// this methods hides the screen. It's not a good place to put your cleanup stuff as it is called on the
},
});
- module.HelpPopupWidget = module.PopUpWidget.extend({
- template:'HelpPopupWidget',
+ module.FullscreenPopup = module.PopUpWidget.extend({
+ template:'FullscreenPopupWidget',
show: function(){
+ var self = this;
this._super();
- this.pos.proxy.help_needed();
+ this.renderElement();
+ this.$('.button.fullscreen').off('click').click(function(){
+ window.document.body.webkitRequestFullscreen();
+ self.pos_widget.screen_selector.close_popup();
+ });
+ this.$('.button.cancel').off('click').click(function(){
+ self.pos_widget.screen_selector.close_popup();
+ });
+ },
+ ismobile: function(){
+ return typeof window.orientation !== 'undefined';
+ }
+ });
+
+
+ module.ErrorPopupWidget = module.PopUpWidget.extend({
+ template:'ErrorPopupWidget',
+ show: function(options){
+ options = options || {};
var self = this;
-
- this.$el.find('.button').off('click').click(function(){
+ this._super();
+
+ $('body').append('<audio src="/point_of_sale/static/src/sounds/error.wav" autoplay="true"></audio>');
+
+ this.message = options.message || _t('Error');
+ this.comment = options.comment || '';
+
+ this.renderElement();
+
+ this.pos.barcode_reader.save_callbacks();
+ this.pos.barcode_reader.reset_action_callbacks();
+
+ this.$('.footer .button').click(function(){
self.pos_widget.screen_selector.close_popup();
+ if ( options.confirm ) {
+ options.confirm.call(self);
+ }
});
},
close:function(){
- this.pos.proxy.help_canceled();
+ this._super();
+ this.pos.barcode_reader.restore_callbacks();
},
});
- module.ChooseReceiptPopupWidget = module.PopUpWidget.extend({
- template:'ChooseReceiptPopupWidget',
- show: function(){
+ module.ErrorTracebackPopupWidget = module.ErrorPopupWidget.extend({
+ template:'ErrorTracebackPopupWidget',
+ });
+
+ module.ErrorBarcodePopupWidget = module.ErrorPopupWidget.extend({
+ template:'ErrorBarcodePopupWidget',
+ show: function(barcode){
+ this.barcode = barcode;
this._super();
- this.renderElement();
+ },
+ });
+
+ module.ConfirmPopupWidget = module.PopUpWidget.extend({
+ template: 'ConfirmPopupWidget',
+ show: function(options){
+ options = options || {};
var self = this;
- var currentOrder = self.pos.get('selectedOrder');
+ this._super();
+
+ this.message = options.message || '';
+ this.comment = options.comment || '';
+ this.renderElement();
- this.$('.button.receipt').off('click').click(function(){
- currentOrder.set_receipt_type('receipt');
- self.pos_widget.screen_selector.set_current_screen('products');
+ this.$('.button.cancel').click(function(){
+ self.pos_widget.screen_selector.close_popup();
+ if( options.cancel ){
+ options.cancel.call(self);
+ }
});
- this.$('.button.invoice').off('click').click(function(){
- currentOrder.set_receipt_type('invoice');
- self.pos_widget.screen_selector.set_current_screen('products');
+ this.$('.button.confirm').click(function(){
+ self.pos_widget.screen_selector.close_popup();
+ if( options.confirm ){
+ options.confirm.call(self);
+ }
});
},
- get_client_name: function(){
- var client = this.pos.get('selectedOrder').get_client();
- if( client ){
- return client.name;
- }else{
- return '';
- }
- },
});
- module.ErrorPopupWidget = module.PopUpWidget.extend({
- template:'ErrorPopupWidget',
- show: function(){
+ /**
+ * A popup that allows the user to select one item from a list.
+ *
+ * show_popup('selection',{
+ * message: 'Pick an Option',
+ * message: "Popup Title",
+ * list: [
+ * { label: 'foobar', item: 45 },
+ * { label: 'bar foo', item: 'stuff' },
+ * ],
+ * confirm: function(item) {
+ * // get the item selected by the user.
+ * },
+ * cancel: function(){
+ * // user chose nothing
+ * }
+ * });
+ */
+
+ module.SelectionPopupWidget = module.PopUpWidget.extend({
+ template: 'SelectionPopupWidget',
+ show: function(options){
+ options = options || {};
var self = this;
this._super();
- this.pos.proxy.help_needed();
- this.pos.proxy.scan_item_error_unrecognized();
- this.pos.barcode_reader.save_callbacks();
- this.pos.barcode_reader.reset_action_callbacks();
- this.pos.barcode_reader.set_action_callback({
- 'cashier': function(code){
- clearInterval(this.intervalID);
- self.pos.proxy.cashier_mode_activated();
- self.pos_widget.screen_selector.set_user_mode('cashier');
- },
+ this.message = options.message || '';
+ this.list = options.list || [];
+ this.renderElement();
+
+ this.$('.button.cancel').click(function(){
+ self.pos_widget.screen_selector.close_popup();
+ if (options.cancel){
+ options.cancel.call(self);
+ }
});
- this.$('.footer .button').off('click').click(function(){
+
+ this.$('.selection-item').click(function(){
self.pos_widget.screen_selector.close_popup();
+ if (options.confirm) {
+ var item = self.list[parseInt($(this).data('item-index'))];
+ item = item ? item.item : item;
+ options.confirm.call(self,item);
+ }
});
},
- close:function(){
+ });
+
+ module.TextInputPopupWidget = module.PopUpWidget.extend({
+ template: 'TextInputPopupWidget',
+ show: function(options){
+ options = options || {};
+ var self = this;
this._super();
- this.pos.proxy.help_canceled();
- this.pos.barcode_reader.restore_callbacks();
+
+ this.message = options.message || '';
+ this.comment = options.comment || '';
+ this.value = options.value || '';
+ this.renderElement();
+ this.$('input,textarea').focus();
+
+ this.$('.button.cancel').click(function(){
+ self.pos_widget.screen_selector.close_popup();
+ if( options.cancel ){
+ options.cancel.call(self);
+ }
+ });
+
+ this.$('.button.confirm').click(function(){
+ self.pos_widget.screen_selector.close_popup();
+ var value = self.$('input,textarea').val();
+ if( options.confirm ){
+ options.confirm.call(self,value);
+ }
+ });
},
});
- module.ProductErrorPopupWidget = module.ErrorPopupWidget.extend({
- template:'ProductErrorPopupWidget',
+ module.TextAreaPopupWidget = module.TextInputPopupWidget.extend({
+ template: 'TextAreaPopupWidget',
});
- module.ErrorSessionPopupWidget = module.ErrorPopupWidget.extend({
- template:'ErrorSessionPopupWidget',
+ module.NumberPopupWidget = module.PopUpWidget.extend({
+ template: 'NumberPopupWidget',
+ click_numpad_button: function($el,event){
+ this.numpad_input($el.data('action'));
+ },
+ numpad_input: function(input) { //FIXME -> Deduplicate code
+ var oldbuf = this.inputbuffer.slice(0);
+
+ if (input === '.') {
+ if (this.firstinput) {
+ this.inputbuffer = "0.";
+ }else if (!this.inputbuffer.length || this.inputbuffer === '-') {
+ this.inputbuffer += "0.";
+ } else if (this.inputbuffer.indexOf('.') < 0){
+ this.inputbuffer = this.inputbuffer + '.';
+ }
+ } else if (input === 'CLEAR') {
+ this.inputbuffer = "";
+ } else if (input === 'BACKSPACE') {
+ this.inputbuffer = this.inputbuffer.substring(0,this.inputbuffer.length - 1);
+ } else if (input === '+') {
+ if ( this.inputbuffer[0] === '-' ) {
+ this.inputbuffer = this.inputbuffer.substring(1,this.inputbuffer.length);
+ }
+ } else if (input === '-') {
+ if ( this.inputbuffer[0] === '-' ) {
+ this.inputbuffer = this.inputbuffer.substring(1,this.inputbuffer.length);
+ } else {
+ this.inputbuffer = '-' + this.inputbuffer;
+ }
+ } else if (input[0] === '+' && !isNaN(parseFloat(input))) {
+ this.inputbuffer = '' + ((parseFloat(this.inputbuffer) || 0) + parseFloat(input));
+ } else if (!isNaN(parseInt(input))) {
+ if (this.firstinput) {
+ this.inputbuffer = '' + input;
+ } else {
+ this.inputbuffer += input;
+ }
+ }
+
+ this.firstinput = this.inputbuffer.length === 0;
+
+ if (this.inputbuffer !== oldbuf) {
+ this.$('.value').text(this.inputbuffer);
+ }
+ },
+ show: function(options){
+ options = options || {};
+ var self = this;
+ this._super();
+
+ this.message = options.message || '';
+ this.comment = options.comment || '';
+ this.inputbuffer = options.value || '';
+ this.renderElement();
+ this.firstinput = true;
+
+ this.$('.input-button,.mode-button').click(function(event){
+ self.click_numpad_button($(this),event);
+ });
+ this.$('.button.cancel').click(function(){
+ self.pos_widget.screen_selector.close_popup();
+ if( options.cancel ){
+ options.cancel.call(self);
+ }
+ });
+
+ this.$('.button.confirm').click(function(){
+ self.pos_widget.screen_selector.close_popup();
+ if( options.confirm ){
+ options.confirm.call(self,self.inputbuffer);
+ }
+ });
+ },
});
- module.ErrorNegativePricePopupWidget = module.ErrorPopupWidget.extend({
- template:'ErrorNegativePricePopupWidget',
+ module.PasswordPopupWidget = module.NumberPopupWidget.extend({
+ renderElement: function(){
+ this._super();
+ this.$('.popup').addClass('popup-password'); // HELLO HACK !
+ },
});
module.ErrorNoClientPopupWidget = module.ErrorPopupWidget.extend({
module.ErrorInvoiceTransferPopupWidget = module.ErrorPopupWidget.extend({
template: 'ErrorInvoiceTransferPopupWidget',
});
-
- module.ScaleInviteScreenWidget = module.ScreenWidget.extend({
- template:'ScaleInviteScreenWidget',
- next_screen:'scale',
- previous_screen:'products',
-
- show: function(){
- this._super();
- var self = this;
- var queue = this.pos.proxy_queue;
-
- queue.schedule(function(){
- return self.pos.proxy.weighting_start();
- },{ important: true });
-
- queue.schedule(function(){
- return self.pos.proxy.weighting_read_kg().then(function(weight){
- if(weight > 0.001){
- self.pos_widget.screen_selector.set_current_screen(self.next_screen);
- }
- });
- },{duration: 100, repeat: true});
+ module.UnsentOrdersPopupWidget = module.ConfirmPopupWidget.extend({
+ template: 'UnsentOrdersPopupWidget',
+ });
- this.add_action_button({
- label: _t('Back'),
- icon: '/point_of_sale/static/src/img/icons/png48/go-previous.png',
- click: function(){
- self.pos_widget.screen_selector.set_current_screen(self.previous_screen);
- }
- });
- },
- close: function(){
- this._super();
- var self = this;
- this.pos.proxy_queue.clear();
- this.pos.proxy_queue.schedule(function(){
- return self.pos.proxy.weighting_end();
- },{ important: true });
- },
+ module.UnpaidOrdersPopupWidget = module.ConfirmPopupWidget.extend({
+ template: 'UnpaidOrdersPopupWidget',
});
module.ScaleScreenWidget = module.ScreenWidget.extend({
next_screen: 'products',
previous_screen: 'products',
+ show_leftpane: false,
+
show: function(){
this._super();
var self = this;
$('body').on('keyup',this.hotkey_handler);
- this.add_action_button({
- label: _t('Back'),
- icon: '/point_of_sale/static/src/img/icons/png48/go-previous.png',
- click: function(){
- self.pos_widget.screen_selector.set_current_screen(self.previous_screen);
- }
- });
+ this.$('.back').click(function(){
+ self.pos_widget.screen_selector.set_current_screen(self.previous_screen);
+ });
+
+ this.$('.next,.buy-product').click(function(){
+ self.order_product();
+ self.pos_widget.screen_selector.set_current_screen(self.next_screen);
+ });
- this.validate_button = this.add_action_button({
- label: _t('Validate'),
- icon: '/point_of_sale/static/src/img/icons/png48/validate.png',
- click: function(){
- self.order_product();
- self.pos_widget.screen_selector.set_current_screen(self.next_screen);
- },
- });
-
- queue.schedule(function(){
- return self.pos.proxy.weighting_start()
- },{ important: true });
-
queue.schedule(function(){
- return self.pos.proxy.weighting_read_kg().then(function(weight){
- self.set_weight(weight);
+ return self.pos.proxy.scale_read().then(function(weight){
+ self.set_weight(weight.weight);
});
},{duration:50, repeat: true});
},
- renderElement: function(){
- var self = this;
- this._super();
- this.$('.product-picture').click(function(){
- self.order_product();
- self.pos_widget.screen_selector.set_current_screen(self.next_screen);
- });
- },
get_product: function(){
var ss = this.pos_widget.screen_selector;
if(ss){
}
},
order_product: function(){
- this.pos.get('selectedOrder').addProduct(this.get_product(),{ quantity: this.weight });
+ this.pos.get_order().add_product(this.get_product(),{ quantity: this.weight });
},
get_product_name: function(){
var product = this.get_product();
- return (product ? product.get('name') : undefined) || 'Unnamed Product';
+ return (product ? product.display_name : undefined) || 'Unnamed Product';
},
get_product_price: function(){
var product = this.get_product();
- return (product ? product.get('price') : 0) || 0;
+ return (product ? product.price : 0) || 0;
},
set_weight: function(weight){
this.weight = weight;
- this.$('.js-weight').text(this.get_product_weight_string());
+ this.$('.weight').text(this.get_product_weight_string());
+ this.$('.computed-price').text(this.get_computed_price_string());
},
get_product_weight_string: function(){
- return (this.weight || 0).toFixed(3) + ' Kg';
+ var product = this.get_product();
+ var defaultstr = (this.weight || 0).toFixed(3) + ' Kg';
+ if(!product || !this.pos){
+ return defaultstr;
+ }
+ var unit_id = product.uom_id;
+ if(!unit_id){
+ return defaultstr;
+ }
+ var unit = this.pos.units_by_id[unit_id[0]];
+ var weight = round_pr(this.weight || 0, unit.rounding);
+ var weightstr = weight.toFixed(Math.ceil(Math.log(1.0/unit.rounding) / Math.log(10) ));
+ weightstr += ' Kg';
+ return weightstr;
+ },
+ get_computed_price_string: function(){
+ return this.format_currency(this.get_product_price() * this.weight);
},
close: function(){
var self = this;
$('body').off('keyup',this.hotkey_handler);
this.pos.proxy_queue.clear();
- this.pos.proxy_queue.schedule(function(){
- self.pos.proxy.weighting_end();
- },{ important: true });
},
});
+ module.ProductScreenWidget = module.ScreenWidget.extend({
+ template:'ProductScreenWidget',
+
+ show_numpad: true,
+ show_leftpane: true,
- module.ClientPaymentScreenWidget = module.ScreenWidget.extend({
- template:'ClientPaymentScreenWidget',
+ start: function(){ //FIXME this should work as renderElement... but then the categories aren't properly set. explore why
+ var self = this;
- next_screen: 'welcome',
- previous_screen: 'products',
+ this.product_list_widget = new module.ProductListWidget(this,{
+ click_product_action: function(product){
+ if(product.to_weight && self.pos.config.iface_electronic_scale){
+ self.pos_widget.screen_selector.set_current_screen('scale',{product: product});
+ }else{
+ self.pos.get_order().add_product(product);
+ }
+ },
+ product_list: this.pos.db.get_product_by_category(0)
+ });
+ this.product_list_widget.replace(this.$('.placeholder-ProductListWidget'));
+
+ this.product_categories_widget = new module.ProductCategoriesWidget(this,{
+ product_list_widget: this.product_list_widget,
+ });
+ this.product_categories_widget.replace(this.$('.placeholder-ProductCategoriesWidget'));
+ },
show: function(){
this._super();
var self = this;
-
- this.queue = new module.JobQueue();
- this.canceled = false;
- this.paid = false;
-
- // initiates the connection to the payment terminal and starts the update requests
- this.start = function(){
- var def = new $.Deferred();
- self.pos.proxy.payment_request(self.pos.get('selectedOrder').getDueLeft())
- .done(function(ack){
- if(ack === 'ok'){
- self.queue.schedule(self.update);
- }else if(ack.indexOf('error') === 0){
- console.error('cannot make payment. TODO');
- }else{
- console.error('unknown payment request return value:',ack);
- }
- def.resolve();
- });
- return def;
- };
-
- // gets updated status from the payment terminal and performs the appropriate consequences
- this.update = function(){
- var def = new $.Deferred();
- if(self.canceled){
- return def.resolve();
- }
- self.pos.proxy.payment_status()
- .done(function(status){
- if(status.status === 'paid'){
-
- var currentOrder = self.pos.get('selectedOrder');
-
- //we get the first cashregister marked as self-checkout
- var selfCheckoutRegisters = [];
- for(var i = 0; i < self.pos.get('cashRegisters').models.length; i++){
- var cashregister = self.pos.get('cashRegisters').models[i];
- if(cashregister.self_checkout_payment_method){
- selfCheckoutRegisters.push(cashregister);
- }
- }
-
- var cashregister = selfCheckoutRegisters[0] || self.pos.get('cashRegisters').models[0];
- currentOrder.addPaymentLine(cashregister);
- self.pos.push_order(currentOrder)
- currentOrder.destroy();
- self.pos.proxy.transaction_end();
- self.pos_widget.screen_selector.set_current_screen(self.next_screen);
- self.paid = true;
- }else if(status.status.indexOf('error') === 0){
- console.error('error in payment request. TODO');
- }else if(status.status === 'waiting'){
- self.queue.schedule(self.update,200);
- }else{
- console.error('unknown status value:',status.status);
- }
- def.resolve();
- });
- return def;
- }
-
- // cancels a payment.
- this.cancel = function(){
- if(!self.paid && !self.canceled){
- self.canceled = true;
- self.pos.proxy.payment_cancel();
- self.pos_widget.screen_selector.set_current_screen(self.previous_screen);
- self.queue.clear();
- }
- return (new $.Deferred()).resolve();
- }
-
- if(this.pos.get('selectedOrder').getDueLeft() <= 0){
- this.pos_widget.screen_selector.show_popup('error-negative-price');
- }else{
- this.queue.schedule(this.start);
- }
- this.add_action_button({
- label: _t('Back'),
- icon: '/point_of_sale/static/src/img/icons/png48/go-previous.png',
- click: function(){
- self.queue.schedule(self.cancel);
- self.pos_widget.screen_selector.set_current_screen(self.previous_screen);
- }
- });
+ this.product_categories_widget.reset_category();
+
+ this.pos_widget.order_widget.set_editable(true);
},
+
close: function(){
- if(this.queue){
- this.queue.schedule(this.cancel);
- }
- //TODO CANCEL
this._super();
+
+ this.pos_widget.order_widget.set_editable(false);
+
+ if(this.pos.config.iface_vkeyboard && this.pos_widget.onscreen_keyboard){
+ this.pos_widget.onscreen_keyboard.hide();
+ }
},
});
- module.WelcomeScreenWidget = module.ScreenWidget.extend({
- template:'WelcomeScreenWidget',
+ module.ClientListScreenWidget = module.ScreenWidget.extend({
+ template: 'ClientListScreenWidget',
- next_screen: 'products',
+ init: function(parent, options){
+ this._super(parent, options);
+ this.partner_cache = new module.DomCache();
+ },
- show_numpad: false,
- show_leftpane: false,
- start: function(){
+ show_leftpane: false,
+
+ auto_back: true,
+
+ show: function(){
+ var self = this;
this._super();
- $('.goodbye-message').click(function(){
- $(this).addClass('oe_hidden');
+
+ this.renderElement();
+ this.details_visible = false;
+ this.old_client = this.pos.get_order().get_client()
+ this.new_client = this.old_client;
+
+ this.$('.back').click(function(){
+ self.pos_widget.screen_selector.back();
+ });
+
+ this.$('.next').click(function(){
+ self.save_changes();
+ self.pos_widget.screen_selector.back();
+ });
+
+ this.$('.new-customer').click(function(){
+ self.display_client_details('edit',{
+ 'country_id': self.pos.company.country_id,
+ });
+ });
+
+ var partners = this.pos.db.get_partners_sorted(1000);
+ this.render_list(partners);
+
+ this.reload_partners();
+
+ if( this.old_client ){
+ this.display_client_details('show',this.old_client,0);
+ }
+
+ this.$('.client-list-contents').delegate('.client-line','click',function(event){
+ self.line_select(event,$(this),parseInt($(this).data('id')));
+ });
+
+ var search_timeout = null;
+
+ if(this.pos.config.iface_vkeyboard && this.pos_widget.onscreen_keyboard){
+ this.pos_widget.onscreen_keyboard.connect(this.$('.searchbox input'));
+ }
+
+ this.$('.searchbox input').on('keyup',function(event){
+ clearTimeout(search_timeout);
+
+ var query = this.value;
+
+ search_timeout = setTimeout(function(){
+ self.perform_search(query,event.which === 13);
+ },70);
});
+
+ this.$('.searchbox .search-clear').click(function(){
+ self.clear_search();
+ });
+ },
+ barcode_client_action: function(code){
+ if (this.editing_client) {
+ this.$('.detail.barcode').val(code.code);
+ } else if (this.pos.db.get_partner_by_barcode(code.code)) {
+ this.display_client_details('show',this.pos.db.get_partner_by_barcode(code.code));
+ }
+ },
+ perform_search: function(query, associate_result){
+ if(query){
+ var customers = this.pos.db.search_partner(query);
+ this.display_client_details('hide');
+ if ( associate_result && customers.length === 1){
+ this.new_client = customers[0];
+ this.save_changes();
+ this.pos_widget.screen_selector.back();
+ }
+ this.render_list(customers);
+ }else{
+ var customers = this.pos.db.get_partners_sorted();
+ this.render_list(customers);
+ }
+ },
+ clear_search: function(){
+ var customers = this.pos.db.get_partners_sorted(1000);
+ this.render_list(customers);
+ this.$('.searchbox input')[0].value = '';
+ this.$('.searchbox input').focus();
+ },
+ render_list: function(partners){
+ var contents = this.$el[0].querySelector('.client-list-contents');
+ contents.innerHTML = "";
+ for(var i = 0, len = Math.min(partners.length,1000); i < len; i++){
+ var partner = partners[i];
+ var clientline = this.partner_cache.get_node(partner.id);
+ if(!clientline){
+ var clientline_html = QWeb.render('ClientLine',{widget: this, partner:partners[i]});
+ var clientline = document.createElement('tbody');
+ clientline.innerHTML = clientline_html;
+ clientline = clientline.childNodes[1];
+ this.partner_cache.cache_node(partner.id,clientline);
+ }
+ if( partners === this.new_client ){
+ clientline.classList.add('highlight');
+ }else{
+ clientline.classList.remove('highlight');
+ }
+ contents.appendChild(clientline);
+ }
+ },
+ save_changes: function(){
+ if( this.has_client_changed() ){
+ this.pos.get_order().set_client(this.new_client);
+ }
+ },
+ has_client_changed: function(){
+ if( this.old_client && this.new_client ){
+ return this.old_client.id !== this.new_client.id;
+ }else{
+ return !!this.old_client !== !!this.new_client;
+ }
+ },
+ toggle_save_button: function(){
+ var $button = this.$('.button.next');
+ if (this.editing_client) {
+ $button.addClass('oe_hidden');
+ return;
+ } else if( this.new_client ){
+ if( !this.old_client){
+ $button.text(_t('Set Customer'));
+ }else{
+ $button.text(_t('Change Customer'));
+ }
+ }else{
+ $button.text(_t('Deselect Customer'));
+ }
+ $button.toggleClass('oe_hidden',!this.has_client_changed());
+ },
+ line_select: function(event,$line,id){
+ var partner = this.pos.db.get_partner_by_id(id);
+ this.$('.client-list .lowlight').removeClass('lowlight');
+ if ( $line.hasClass('highlight') ){
+ $line.removeClass('highlight');
+ $line.addClass('lowlight');
+ this.display_client_details('hide',partner);
+ this.new_client = null;
+ this.toggle_save_button();
+ }else{
+ this.$('.client-list .highlight').removeClass('highlight');
+ $line.addClass('highlight');
+ var y = event.pageY - $line.parent().offset().top
+ this.display_client_details('show',partner,y);
+ this.new_client = partner;
+ this.toggle_save_button();
+ }
+ },
+ partner_icon_url: function(id){
+ return '/web/binary/image?model=res.partner&id='+id+'&field=image_small';
},
- barcode_product_action: function(code){
- this.pos.proxy.transaction_start();
- this._super(code);
+ // ui handle for the 'edit selected customer' action
+ edit_client_details: function(partner) {
+ this.display_client_details('edit',partner);
},
- barcode_client_action: function(code){
- this.pos.proxy.transaction_start();
- this._super(code);
- $('.goodbye-message').addClass('oe_hidden');
- this.pos_widget.screen_selector.show_popup('choose-receipt');
+ // ui handle for the 'cancel customer edit changes' action
+ undo_client_details: function(partner) {
+ if (!partner.id) {
+ this.display_client_details('hide');
+ } else {
+ this.display_client_details('show',partner);
+ }
},
-
- show: function(){
- this._super();
+
+ // what happens when we save the changes on the client edit form -> we fetch the fields, sanitize them,
+ // send them to the backend for update, and call saved_client_details() when the server tells us the
+ // save was successfull.
+ save_client_details: function(partner) {
var self = this;
+
+ var fields = {}
+ this.$('.client-details-contents .detail').each(function(idx,el){
+ fields[el.name] = el.value;
+ });
- this.add_action_button({
- label: _t('Help'),
- icon: '/point_of_sale/static/src/img/icons/png48/help.png',
- click: function(){
- $('.goodbye-message').css({opacity:1}).addClass('oe_hidden');
- self.help_button_action();
- },
+ if (!fields.name) {
+ this.pos_widget.screen_selector.show_popup('error',{
+ message: _t('A Customer Name Is Required'),
});
+ return;
+ }
+
+ if (this.uploaded_picture) {
+ fields.image = this.uploaded_picture;
+ }
- $('.goodbye-message').css({opacity:1}).removeClass('oe_hidden');
- setTimeout(function(){
- $('.goodbye-message').animate({opacity:0},500,'swing',function(){$('.goodbye-message').addClass('oe_hidden');});
- },5000);
+ fields.id = partner.id || false;
+ fields.country_id = fields.country_id || false;
+ fields.barcode = fields.barcode ? this.pos.barcode_reader.sanitize_ean(fields.barcode) : false;
+
+ new instance.web.Model('res.partner').call('create_from_ui',[fields]).then(function(partner_id){
+ self.saved_client_details(partner_id);
+ },function(err,event){
+ event.preventDefault();
+ self.pos_widget.screen_selector.show_popup('error',{
+ 'message':_t('Error: Could not Save Changes'),
+ 'comment':_t('Your Internet connection is probably down.'),
+ });
+ });
+ },
+
+ // what happens when we've just pushed modifications for a partner of id partner_id
+ saved_client_details: function(partner_id){
+ var self = this;
+ this.reload_partners().then(function(){
+ var partner = self.pos.db.get_partner_by_id(partner_id);
+ if (partner) {
+ self.new_client = partner;
+ self.toggle_save_button();
+ self.display_client_details('show',partner);
+ } else {
+ // should never happen, because create_from_ui must return the id of the partner it
+ // has created, and reload_partner() must have loaded the newly created partner.
+ self.display_client_details('hide');
+ }
+ });
},
- });
-
- module.ProductScreenWidget = module.ScreenWidget.extend({
- template:'ProductScreenWidget',
- scale_screen: 'scale',
- client_scale_screen : 'scale_invite',
- client_next_screen: 'client_payment',
+ // resizes an image, keeping the aspect ratio intact,
+ // the resize is useful to avoid sending 12Mpixels jpegs
+ // over a wireless connection.
+ resize_image_to_dataurl: function(img, maxwidth, maxheight, callback){
+ img.onload = function(){
+ var png = new Image();
+ var canvas = document.createElement('canvas');
+ var ctx = canvas.getContext('2d');
+ var ratio = 1;
- show_numpad: true,
- show_leftpane: true,
+ if (img.width > maxwidth) {
+ ratio = maxwidth / img.width;
+ }
+ if (img.height * ratio > maxheight) {
+ ratio = maxheight / img.height;
+ }
+ var width = Math.floor(img.width * ratio);
+ var height = Math.floor(img.height * ratio);
- start: function(){ //FIXME this should work as renderElement... but then the categories aren't properly set. explore why
- var self = this;
- this.product_categories_widget = new module.ProductCategoriesWidget(this,{});
- this.product_categories_widget.replace($('.placeholder-ProductCategoriesWidget'));
+ canvas.width = width;
+ canvas.height = height;
+ ctx.drawImage(img,0,0,width,height);
- this.product_list_widget = new module.ProductListWidget(this,{
- click_product_action: function(product){
- if(product.get('to_weight') && self.pos.iface_electronic_scale){
- self.pos_widget.screen_selector.set_current_screen( self.cashier_mode ? self.scale_screen : self.client_scale_screen, {product: product});
- }else{
- self.pos.get('selectedOrder').addProduct(product);
- }
- },
- });
- this.product_list_widget.replace($('.placeholder-ProductListWidget'));
+ var dataurl = canvas.toDataURL();
+ callback(dataurl);
+ }
},
- show: function(){
- this._super();
+ // Loads and resizes a File that contains an image.
+ // callback gets a dataurl in case of success.
+ load_image_file: function(file, callback){
var self = this;
+ if (!file.type.match(/image.*/)) {
+ this.pos_widget.screen_selector.show_popup('error',{
+ message:_t('Unsupported File Format'),
+ comment:_t('Only web-compatible Image formats such as .png or .jpeg are supported'),
+ });
+ return;
+ }
+
+ var reader = new FileReader();
+ reader.onload = function(event){
+ var dataurl = event.target.result;
+ var img = new Image();
+ img.src = dataurl;
+ self.resize_image_to_dataurl(img,800,600,callback);
+ }
+ reader.onerror = function(){
+ self.pos_widget.screen_selector.show_popup('error',{
+ message:_t('Could Not Read Image'),
+ comment:_t('The provided file could not be read due to an unknown error'),
+ });
+ };
+ reader.readAsDataURL(file);
+ },
- this.product_categories_widget.reset_category();
+ // This fetches partner changes on the server, and in case of changes,
+ // rerenders the affected views
+ reload_partners: function(){
+ var self = this;
+ return this.pos.load_new_partners().then(function(){
+ self.render_list(self.pos.db.get_partners_sorted(1000));
+
+ // update the currently assigned client if it has been changed in db.
+ var curr_client = self.pos.get_order().get_client();
+ if (curr_client) {
+ self.pos.get_order().set_client(self.pos.db.get_partner_by_id(curr_client.id));
+ }
+ });
+ },
- this.pos_widget.order_widget.set_numpad_state(this.pos_widget.numpad.state);
+ // Shows,hides or edit the customer details box :
+ // visibility: 'show', 'hide' or 'edit'
+ // partner: the partner object to show or edit
+ // clickpos: the height of the click on the list (in pixel), used
+ // to maintain consistent scroll.
+ display_client_details: function(visibility,partner,clickpos){
+ var self = this;
+ var contents = this.$('.client-details-contents');
+ var parent = this.$('.client-list').parent();
+ var scroll = parent.scrollTop();
+ var height = contents.height();
+
+ contents.off('click','.button.edit');
+ contents.off('click','.button.save');
+ contents.off('click','.button.undo');
+ contents.on('click','.button.edit',function(){ self.edit_client_details(partner); });
+ contents.on('click','.button.save',function(){ self.save_client_details(partner); });
+ contents.on('click','.button.undo',function(){ self.undo_client_details(partner); });
+ this.editing_client = false;
+ this.uploaded_picture = null;
+
+ if(visibility === 'show'){
+ contents.empty();
+ contents.append($(QWeb.render('ClientDetails',{widget:this,partner:partner})));
+
+ var new_height = contents.height();
+
+ if(!this.details_visible){
+ if(clickpos < scroll + new_height + 20 ){
+ parent.scrollTop( clickpos - 20 );
+ }else{
+ parent.scrollTop(parent.scrollTop() + new_height);
+ }
+ }else{
+ parent.scrollTop(parent.scrollTop() - height + new_height);
+ }
- if(this.pos_widget.screen_selector.current_mode === 'client'){
- this.add_action_button({
- label: _t('Pay'),
- icon: '/point_of_sale/static/src/img/icons/png48/go-next.png',
- click: function(){
- self.pos_widget.screen_selector.set_current_screen(self.client_next_screen);
+ this.details_visible = true;
+ this.toggle_save_button();
+ } else if (visibility === 'edit') {
+ this.editing_client = true;
+ contents.empty();
+ contents.append($(QWeb.render('ClientDetailsEdit',{widget:this,partner:partner})));
+ this.toggle_save_button();
+
+ contents.find('.image-uploader').on('change',function(){
+ self.load_image_file(event.target.files[0],function(res){
+ if (res) {
+ contents.find('.client-picture img, .client-picture .fa').remove();
+ contents.find('.client-picture').append("<img src='"+res+"'>");
+ contents.find('.detail.picture').remove();
+ self.uploaded_picture = res;
}
});
+ });
+ } else if (visibility === 'hide') {
+ contents.empty();
+ if( height > scroll ){
+ contents.css({height:height+'px'});
+ contents.animate({height:0},400,function(){
+ contents.css({height:''});
+ });
+ }else{
+ parent.scrollTop( parent.scrollTop() - height);
+ }
+ this.details_visible = false;
+ this.toggle_save_button();
}
},
-
close: function(){
this._super();
- this.pos_widget.order_widget.set_numpad_state(null);
- this.pos_widget.payment_screen.set_numpad_state(null);
- if(this.pos.iface_vkeyboard && this.pos_widget.onscreen_keyboard){
- this.pos_widget.onscreen_keyboard.hide();
- }
},
-
});
module.ReceiptScreenWidget = module.ScreenWidget.extend({
template: 'ReceiptScreenWidget',
+ show_numpad: false,
+ show_leftpane: false,
- show_numpad: true,
- show_leftpane: true,
-
- init: function(parent, options) {
- this._super(parent,options);
- this.model = options.model;
- this.user = this.pos.get('user');
- this.company = this.pos.get('company');
- this.shop_obj = this.pos.get('shop');
- },
- renderElement: function() {
- this._super();
- this.pos.bind('change:selectedOrder', this.change_selected_order, this);
- this.change_selected_order();
- },
show: function(){
this._super();
var self = this;
- var print_button = this.add_action_button({
- label: _t('Print'),
- icon: '/point_of_sale/static/src/img/icons/png48/printer.png',
- click: function(){ self.print(); },
- });
-
- var finish_button = this.add_action_button({
- label: _t('Next Order'),
- icon: '/point_of_sale/static/src/img/icons/png48/go-next.png',
- click: function() { self.finishOrder(); },
- });
+ this.refresh();
- this.print();
+ if (!this.pos.get_order()._printed && this.pos.config.iface_print_auto) {
+ this.print();
+ }
- // THIS IS THE HACK OF THE CENTURY
- //
// The problem is that in chrome the print() is asynchronous and doesn't
// execute until all rpc are finished. So it conflicts with the rpc used
// to send the orders to the backend, and the user is able to go to the next
// 2 seconds is the same as the default timeout for sending orders and so the dialog
// should have appeared before the timeout... so yeah that's not ultra reliable.
- finish_button.set_disabled(true);
+ this.lock_screen(true);
setTimeout(function(){
- finish_button.set_disabled(false);
+ self.lock_screen(false);
}, 2000);
},
+ lock_screen: function(locked) {
+ this._locked = locked;
+ if (locked) {
+ this.$('.next').removeClass('highlight');
+ } else {
+ this.$('.next').addClass('highlight');
+ }
+ },
print: function() {
+ this.pos.get_order()._printed = true;
window.print();
},
- finishOrder: function() {
- this.pos.get('selectedOrder').destroy();
- },
- change_selected_order: function() {
- if (this.currentOrderLines)
- this.currentOrderLines.unbind();
- this.currentOrderLines = (this.pos.get('selectedOrder')).get('orderLines');
- this.currentOrderLines.bind('add', this.refresh, this);
- this.currentOrderLines.bind('change', this.refresh, this);
- this.currentOrderLines.bind('remove', this.refresh, this);
- if (this.currentPaymentLines)
- this.currentPaymentLines.unbind();
- this.currentPaymentLines = (this.pos.get('selectedOrder')).get('paymentLines');
- this.currentPaymentLines.bind('all', this.refresh, this);
- this.refresh();
+ finish_order: function() {
+ if (!this._locked) {
+ this.pos.get_order().finalize();
+ }
+ },
+ renderElement: function() {
+ var self = this;
+ this._super();
+ this.$('.next').click(function(){
+ self.finish_order();
+ });
+ this.$('.button.print').click(function(){
+ self.print();
+ });
},
refresh: function() {
- this.currentOrder = this.pos.get('selectedOrder');
- $('.pos-receipt-container', this.$el).html(QWeb.render('PosTicket',{widget:this}));
+ var order = this.pos.get_order();
+ this.$('.pos-receipt-container').html(QWeb.render('PosTicket',{
+ widget:this,
+ order: order,
+ receipt: order.export_for_printing(),
+ orderlines: order.get_orderlines(),
+ paymentlines: order.get_paymentlines(),
+ }));
},
});
module.PaymentScreenWidget = module.ScreenWidget.extend({
- template: 'PaymentScreenWidget',
- back_screen: 'products',
- next_screen: 'receipt',
+ template: 'PaymentScreenWidget',
+ back_screen: 'product',
+ next_screen: 'receipt',
+ show_leftpane: false,
+ show_numpad: false,
init: function(parent, options) {
- this._super(parent,options);
- this.model = options.model;
- this.pos.bind('change:selectedOrder', this.change_selected_order, this);
- this.bindPaymentLineEvents();
- this.bind_orderline_events();
- this.paymentlinewidgets = [];
- this.focusedLine = null;
- },
- show: function(){
- this._super();
var self = this;
-
- this.hotkey_handler = function(event){
- if(event.which === 13){
- self.validateCurrentOrder();
- }else if(event.which === 27){
- self.back();
+ this._super(parent, options);
+
+ this.pos.bind('change:selectedOrder',function(){
+ this.renderElement();
+ this.watch_order_changes();
+ },this);
+ this.watch_order_changes();
+
+ this.inputbuffer = "";
+ this.firstinput = true;
+ this.keyboard_handler = function(event){
+ var key = '';
+ if ( event.keyCode === 13 ) { // Enter
+ self.validate_order();
+ } else if ( event.keyCode === 190 ) { // Dot
+ key = '.';
+ } else if ( event.keyCode === 46 ) { // Delete
+ key = 'CLEAR';
+ } else if ( event.keyCode === 8 ) { // Backspace
+ key = 'BACKSPACE';
+ event.preventDefault(); // Prevents history back nav
+ } else if ( event.keyCode >= 48 && event.keyCode <= 57 ){ // Numbers
+ key = '' + (event.keyCode - 48);
+ } else if ( event.keyCode >= 96 && event.keyCode <= 105 ){ // Numpad Numbers
+ key = '' + (event.keyCode - 96);
+ } else if ( event.keyCode === 189 || event.keyCode === 109 ) { // Minus
+ key = '-';
+ } else if ( event.keyCode === 107 ) { // Plus
+ key = '+';
}
- };
- $('body').on('keyup',this.hotkey_handler);
+ self.payment_input(key);
+
+ };
+ },
+ // resets the current input buffer
+ reset_input: function(){
+ var line = this.pos.get_order().selected_paymentline;
+ this.firstinput = true;
+ if (line) {
+ this.inputbuffer = this.format_currency_no_symbol(line.get_amount());
+ } else {
+ this.inputbuffer = "";
+ }
+ },
+ // handle both keyboard and numpad input. Accepts
+ // a string that represents the key pressed.
+ payment_input: function(input) {
+ var oldbuf = this.inputbuffer.slice(0);
+
+ if (input === '.') {
+ if (this.firstinput) {
+ this.inputbuffer = "0.";
+ }else if (!this.inputbuffer.length || this.inputbuffer === '-') {
+ this.inputbuffer += "0.";
+ } else if (this.inputbuffer.indexOf('.') < 0){
+ this.inputbuffer = this.inputbuffer + '.';
+ }
+ } else if (input === 'CLEAR') {
+ this.inputbuffer = "";
+ } else if (input === 'BACKSPACE') {
+ this.inputbuffer = this.inputbuffer.substring(0,this.inputbuffer.length - 1);
+ } else if (input === '+') {
+ if ( this.inputbuffer[0] === '-' ) {
+ this.inputbuffer = this.inputbuffer.substring(1,this.inputbuffer.length);
+ }
+ } else if (input === '-') {
+ if ( this.inputbuffer[0] === '-' ) {
+ this.inputbuffer = this.inputbuffer.substring(1,this.inputbuffer.length);
+ } else {
+ this.inputbuffer = '-' + this.inputbuffer;
+ }
+ } else if (input[0] === '+' && !isNaN(parseFloat(input))) {
+ this.inputbuffer = '' + ((parseFloat(this.inputbuffer) || 0) + parseFloat(input));
+ } else if (!isNaN(parseInt(input))) {
+ if (this.firstinput) {
+ this.inputbuffer = '' + input;
+ } else {
+ this.inputbuffer += input;
+ }
+ }
+ this.firstinput = this.inputbuffer.length === 0;
- if( this.pos.iface_cashdrawer && this.pos.get('selectedOrder').get('paymentLines').find( function(pl){ return pl.cashregister.get('journal').type === 'cash'; })){
- this.pos.proxy.open_cashbox();
+ if (this.inputbuffer !== oldbuf) {
+ var order = this.pos.get_order();
+ if (order.selected_paymentline) {
+ order.selected_paymentline.set_amount(parseFloat(this.inputbuffer));
+ this.order_changes();
+ this.render_paymentlines();
+ this.$('.paymentline.selected .edit').text(this.inputbuffer);
+ }
+ }
+ },
+ click_numpad: function(button) {
+ this.payment_input(button.data('action'));
+ },
+ render_numpad: function() {
+ var self = this;
+ var numpad = $(QWeb.render('PaymentScreen-Numpad', { widget:this }));
+ numpad.on('click','button',function(){
+ self.click_numpad($(this));
+ });
+ return numpad;
+ },
+ click_delete_paymentline: function(cid){
+ var lines = this.pos.get_order().get_paymentlines();
+ for ( var i = 0; i < lines.length; i++ ) {
+ if (lines[i].cid === cid) {
+ this.pos.get_order().remove_paymentline(lines[i]);
+ this.reset_input();
+ this.render_paymentlines();
+ return;
+ }
+ }
+ },
+ click_paymentline: function(cid){
+ var lines = this.pos.get_order().get_paymentlines();
+ for ( var i = 0; i < lines.length; i++ ) {
+ if (lines[i].cid === cid) {
+ this.pos.get_order().select_paymentline(lines[i]);
+ this.reset_input();
+ this.render_paymentlines();
+ return;
+ }
+ }
+ },
+ render_paymentlines: function() {
+ var self = this;
+ var order = this.pos.get_order();
+ if (!order) {
+ return;
}
- this.set_numpad_state(this.pos_widget.numpad.state);
-
- this.add_action_button({
- label: _t('Back'),
- icon: '/point_of_sale/static/src/img/icons/png48/go-previous.png',
- click: function(){
- self.back();
- },
- });
+ var lines = order.get_paymentlines();
+
+ this.$('.paymentlines-container').empty();
+ var lines = $(QWeb.render('PaymentScreen-Paymentlines', {
+ widget: this,
+ order: order,
+ paymentlines: lines,
+ }));
+
+ lines.on('click','.delete-button',function(){
+ self.click_delete_paymentline($(this).data('cid'));
+ });
- this.add_action_button({
- label: _t('Validate'),
- name: 'validation',
- icon: '/point_of_sale/static/src/img/icons/png48/validate.png',
- click: function(){
- self.validateCurrentOrder();
- },
+ lines.on('click','.paymentline',function(){
+ self.click_paymentline($(this).data('cid'));
+ });
+
+ lines.appendTo(this.$('.paymentlines-container'));
+ },
+ click_paymentmethods: function(id) {
+ var cashregister = null;
+ for ( var i = 0; i < this.pos.cashregisters.length; i++ ) {
+ if ( this.pos.cashregisters[i].journal_id[0] === id ){
+ cashregister = this.pos.cashregisters[i];
+ break;
+ }
+ }
+ this.pos.get_order().add_paymentline( cashregister );
+ this.reset_input();
+ this.render_paymentlines();
+ },
+ render_paymentmethods: function() {
+ var self = this;
+ var methods = $(QWeb.render('PaymentScreen-Paymentmethods', { widget:this }));
+ methods.on('click','.paymentmethod',function(){
+ self.click_paymentmethods($(this).data('id'));
});
-
- if(this.pos.iface_invoicing){
- this.add_action_button({
- label: 'Invoice',
- name: 'invoice',
- icon: '/point_of_sale/static/src/img/icons/png48/invoice.png',
- click: function(){
- self.validateCurrentOrder({invoice: true});
- },
- });
+ return methods;
+ },
+ click_invoice: function(){
+ var order = this.pos.get_order();
+ order.set_to_invoice(!order.is_to_invoice());
+ if (order.is_to_invoice()) {
+ this.$('.js_invoice').addClass('highlight');
+ } else {
+ this.$('.js_invoice').removeClass('highlight');
}
+ },
+ click_set_customer: function(){
+ this.pos_widget.screen_selector.set_current_screen('clientlist');
+ },
+ click_back: function(){
+ this.pos_widget.screen_selector.set_current_screen('products');
+ },
+ renderElement: function() {
+ var self = this;
+ this._super();
- if( this.pos.iface_cashdrawer ){
- this.add_action_button({
- label: _t('Cash'),
- name: 'cashbox',
- icon: '/point_of_sale/static/src/img/open-cashbox.png',
- click: function(){
- self.pos.proxy.open_cashbox();
- },
- });
- }
+ var numpad = this.render_numpad();
+ numpad.appendTo(this.$('.payment-numpad'));
+
+ var methods = this.render_paymentmethods();
+ methods.appendTo(this.$('.paymentmethods-container'));
+
+ this.render_paymentlines();
+
+ this.$('.back').click(function(){
+ self.click_back();
+ });
+
+ this.$('.next').click(function(){
+ self.validate_order();
+ });
+
+ this.$('.js_set_customer').click(function(){
+ self.click_set_customer();
+ });
+ this.$('.js_invoice').click(function(){
+ self.click_invoice();
+ });
- this.updatePaymentSummary();
- this.line_refocus();
},
- close: function(){
+ show: function(){
+ this.pos.get_order().clean_empty_paymentlines();
+ this.reset_input();
+ this.render_paymentlines();
+ this.order_changes();
+ window.document.body.addEventListener('keydown',this.keyboard_handler);
this._super();
- this.pos_widget.order_widget.set_numpad_state(null);
- this.pos_widget.payment_screen.set_numpad_state(null);
- $('body').off('keyup',this.hotkey_handler);
},
- back: function() {
- _.each(this.paymentlinewidgets, function(widget){
- if( widget.payment_line.get_amount() === 0 ){
- widget.payment_line.destroy();
- }
+ hide: function(){
+ window.document.body.removeEventListener('keydown',this.keyboard_handler);
+ this._super();
+ },
+ // sets up listeners to watch for order changes
+ watch_order_changes: function() {
+ var self = this;
+ var order = this.pos.get_order();
+ if (!order) {
+ return;
+ }
+ if(this.old_order){
+ this.old_order.unbind(null,null,this);
+ }
+ order.bind('all',function(){
+ self.order_changes();
});
- this.pos_widget.screen_selector.set_current_screen(this.back_screen);
+ this.old_order = order;
},
- validateCurrentOrder: function(options) {
+ // called when the order is changed, used to show if
+ // the order is paid or not
+ order_changes: function(){
+ var self = this;
+ var order = this.pos.get_order();
+ if (!order) {
+ return;
+ } else if (order.is_paid()) {
+ self.$('.next').addClass('highlight');
+ }else{
+ self.$('.next').removeClass('highlight');
+ }
+ },
+ print_escpos_receipt: function(){
+ var env = {
+ widget: this,
+ pos: this.pos,
+ order: this.pos.get_order(),
+ receipt: this.pos.get_order().export_for_printing(),
+ };
+
+ this.pos.proxy.print_receipt(QWeb.render('XmlReceipt',env));
+ },
+
+ // Check if the order is paid, then sends it to the backend,
+ // and complete the sale process
+ validate_order: function() {
var self = this;
- options = options || {};
- var currentOrder = this.pos.get('selectedOrder');
+ var order = this.pos.get_order();
- if(currentOrder.getPaidTotal() + 0.000001 < currentOrder.getTotalTaxIncluded()){
+ // FIXME: this check is there because the backend is unable to
+ // process empty orders. This is not the right place to fix it.
+ if (order.get_orderlines().length === 0) {
+ this.pos_widget.screen_selector.show_popup('error',{
+ 'message': _t('Empty Order'),
+ 'comment': _t('There must be at least one product in your order before it can be validated'),
+ });
return;
}
- if(options.invoice){
- // deactivate the validation button while we try to send the order
- this.pos_widget.action_bar.set_button_disabled('validation',true);
- this.pos_widget.action_bar.set_button_disabled('invoice',true);
+ if (!order.is_paid() || this.invoicing) {
+ return;
+ }
- var invoiced = this.pos.push_and_invoice_order(currentOrder);
+ // The exact amount must be paid if there is no cash payment method defined.
+ if (Math.abs(order.get_total_with_tax() - order.get_total_paid()) > 0.00001) {
+ var cash = false;
+ for (var i = 0; i < this.pos.cashregisters.length; i++) {
+ cash = cash || (this.pos.cashregisters[i].journal.type === 'cash');
+ }
+ if (!cash) {
+ this.pos_widget.screen_selector.show_popup('error',{
+ message: _t('Cannot return change without a cash payment method'),
+ comment: _t('There is no cash payment method available in this point of sale to handle the change.\n\n Please pay the exact amount or add a cash payment method in the point of sale configuration'),
+ });
+ return;
+ }
+ }
+
+ if (order.is_paid_with_cash() && this.pos.config.iface_cashdrawer) {
+
+ this.pos.proxy.open_cashbox();
+ }
+
+ if (order.is_to_invoice()) {
+ var invoiced = this.pos.push_and_invoice_order(order);
+ this.invoicing = true;
invoiced.fail(function(error){
- if(error === 'error-no-client'){
- self.pos_widget.screen_selector.show_popup('error-no-client');
- }else{
- self.pos_widget.screen_selector.show_popup('error-invoice-transfer');
+ self.invoicing = false;
+ if (error === 'error-no-client') {
+ self.pos_widget.screen_selector.show_popup('confirm',{
+ message: _t('Please select the Customer'),
+ comment: _t('You need to select the customer before you can invoice an order.'),
+ confirm: function(){
+ self.pos_widget.screen_selector.set_current_screen('clientlist');
+ },
+ });
+ } else {
+ self.pos_widget.screen_selector.show_popup('error',{
+ message: _t('The order could not be sent'),
+ comment: _t('Check your internet connection and try again.'),
+ });
}
- self.pos_widget.action_bar.set_button_disabled('validation',false);
- self.pos_widget.action_bar.set_button_disabled('invoice',false);
});
invoiced.done(function(){
- self.pos_widget.action_bar.set_button_disabled('validation',false);
- self.pos_widget.action_bar.set_button_disabled('invoice',false);
- self.pos.get('selectedOrder').destroy();
+ self.invoicing = false;
+ order.finalize();
});
-
- }else{
- this.pos.push_order(currentOrder)
- if(this.pos.iface_print_via_proxy){
- this.pos.proxy.print_receipt(currentOrder.export_for_printing());
- this.pos.get('selectedOrder').destroy(); //finish order and go back to scan screen
- }else{
+ } else {
+ this.pos.push_order(order)
+ if (this.pos.config.iface_print_via_proxy) {
+ this.print_escpos_receipt();
+ order.finalize(); //finish order and go back to scan screen
+ } else {
this.pos_widget.screen_selector.set_current_screen(this.next_screen);
}
}
},
- bindPaymentLineEvents: function() {
- this.currentPaymentLines = (this.pos.get('selectedOrder')).get('paymentLines');
- this.currentPaymentLines.bind('add', this.addPaymentLine, this);
- this.currentPaymentLines.bind('remove', this.renderElement, this);
- this.currentPaymentLines.bind('all', this.updatePaymentSummary, this);
- },
- bind_orderline_events: function() {
- this.currentOrderLines = (this.pos.get('selectedOrder')).get('orderLines');
- this.currentOrderLines.bind('all', this.updatePaymentSummary, this);
- },
- change_selected_order: function() {
- this.currentPaymentLines.unbind();
- this.bindPaymentLineEvents();
- this.currentOrderLines.unbind();
- this.bind_orderline_events();
- this.renderElement();
- },
- line_refocus: function(lineWidget){
- if(lineWidget){
- if(this.focusedLine !== lineWidget){
- this.focusedLine = lineWidget;
- }
- }
- if(this.focusedLine){
- this.focusedLine.focus();
- }
- },
- addPaymentLine: function(newPaymentLine) {
- var self = this;
- var l = new module.PaymentlineWidget(this, {
- payment_line: newPaymentLine,
- });
- l.on('delete_payment_line', self, function(r) {
- self.deleteLine(r);
- });
- l.appendTo(this.$('.paymentlines'));
- this.paymentlinewidgets.push(l);
- if(this.numpadState){
- this.numpadState.resetValue();
- }
- this.line_refocus(l);
- },
- renderElement: function() {
- this._super();
- this.$('.paymentlines').empty();
- for(var i = 0, len = this.paymentlinewidgets.length; i < len; i++){
- this.paymentlinewidgets[i].destroy();
- }
- this.paymentlinewidgets = [];
-
- this.currentPaymentLines.each(_.bind( function(paymentLine) {
- this.addPaymentLine(paymentLine);
- }, this));
- this.updatePaymentSummary();
- },
- deleteLine: function(lineWidget) {
- this.currentPaymentLines.remove([lineWidget.payment_line]);
- lineWidget.destroy();
- },
- updatePaymentSummary: function() {
- var currentOrder = this.pos.get('selectedOrder');
- var paidTotal = currentOrder.getPaidTotal();
- var dueTotal = currentOrder.getTotalTaxIncluded();
- var remaining = dueTotal > paidTotal ? dueTotal - paidTotal : 0;
- var change = paidTotal > dueTotal ? paidTotal - dueTotal : 0;
-
- this.$('.payment-due-total').html(this.format_currency(dueTotal));
- this.$('.payment-paid-total').html(this.format_currency(paidTotal));
- this.$('.payment-remaining').html(this.format_currency(remaining));
- this.$('.payment-change').html(this.format_currency(change));
- if(currentOrder.selected_orderline === undefined){
- remaining = 1; // What is this ?
- }
-
- if(this.pos_widget.action_bar){
- this.pos_widget.action_bar.set_button_disabled('validation', remaining > 0.000001);
- this.pos_widget.action_bar.set_button_disabled('invoice', remaining > 0.000001);
- }
- },
- set_numpad_state: function(numpadState) {
- if (this.numpadState) {
- this.numpadState.unbind('set_value', this.set_value);
- this.numpadState.unbind('change:mode', this.setNumpadMode);
- }
- this.numpadState = numpadState;
- if (this.numpadState) {
- this.numpadState.bind('set_value', this.set_value, this);
- this.numpadState.bind('change:mode', this.setNumpadMode, this);
- this.numpadState.reset();
- this.setNumpadMode();
- }
- },
- setNumpadMode: function() {
- this.numpadState.set({mode: 'payment'});
- },
- set_value: function(val) {
- this.currentPaymentLines.last().set_amount(val);
- },
});
+
}
+