[MERGE] point_of_sale: various fixes and improvements

bzr revid: fva@openerp.com-20130917162146-40l4zmby8lmo2dtn
This commit is contained in:
Frédéric van der Essen 2013-09-17 18:21:46 +02:00
commit 6bb396eb68
12 changed files with 210 additions and 122 deletions

View File

@ -98,6 +98,7 @@ Main Features
'static/src/js/widgets.js',
'static/src/js/devices.js',
'static/src/js/screens.js',
'static/src/js/tests.js',
'static/src/js/main.js',
],
'css': [

View File

@ -10,6 +10,8 @@ from openerp.addons.web import http
from openerp.addons.web.http import request
from openerp.addons.web.controllers.main import manifest_list, module_boot, html_template
_logger = logging.getLogger(__name__)
class PointOfSaleController(http.Controller):
def __init__(self):
self.scale = 'closed'
@ -71,7 +73,7 @@ class PointOfSaleController(http.Controller):
@http.route('/pos/test_connection', type='json', auth='admin')
def test_connection(self):
return
_logger.info('Received Connection Test from the Point of Sale');
@http.route('/pos/scan_item_success', type='json', auth='admin')
def scan_item_success(self, ean):
@ -79,7 +81,6 @@ class PointOfSaleController(http.Controller):
A product has been scanned with success
"""
print 'scan_item_success: ' + str(ean)
return
@http.route('/pos/scan_item_error_unrecognized')
def scan_item_error_unrecognized(self, ean):
@ -87,7 +88,6 @@ class PointOfSaleController(http.Controller):
A product has been scanned without success
"""
print 'scan_item_error_unrecognized: ' + str(ean)
return
@http.route('/pos/help_needed', type='json', auth='admin')
def help_needed(self):
@ -95,7 +95,6 @@ class PointOfSaleController(http.Controller):
The user wants an help (ex: light is on)
"""
print "help_needed"
return
@http.route('/pos/help_canceled', type='json', auth='admin')
def help_canceled(self):
@ -103,7 +102,6 @@ class PointOfSaleController(http.Controller):
The user stops the help request
"""
print "help_canceled"
return
@http.route('/pos/weighting_start', type='json', auth='admin')
def weighting_start(self):
@ -115,7 +113,6 @@ class PointOfSaleController(http.Controller):
print "... Scale Open."
else:
print "WARNING: Scale already Connected !!!"
return
@http.route('/pos/weighting_read_kg', type='json', auth='admin')
def weighting_read_kg(self):
@ -156,41 +153,37 @@ class PointOfSaleController(http.Controller):
@http.route('/pos/payment_cancel', type='json', auth='admin')
def payment_cancel(self):
print "payment_cancel"
return
@http.route('/pos/transaction_start', type='json', auth='admin')
def transaction_start(self):
print 'transaction_start'
return
@http.route('/pos/transaction_end', type='json', auth='admin')
def transaction_end(self):
print 'transaction_end'
return
@http.route('/pos/cashier_mode_activated', type='json', auth='admin')
def cashier_mode_activated(self):
print 'cashier_mode_activated'
return
@http.route('/pos/cashier_mode_deactivated', type='json', auth='admin')
def cashier_mode_deactivated(self):
print 'cashier_mode_deactivated'
return
@http.route('/pos/open_cashbox', type='json', auth='admin')
def open_cashbox(self):
print 'open_cashbox'
return
@http.route('/pos/print_receipt', type='json', auth='admin')
def print_receipt(self, receipt):
print 'print_receipt' + str(receipt)
return
@http.route('/pos/log', type='json', auth='admin')
def log(self, arguments):
_logger.info(' '.join(str(v) for v in arguments))
@http.route('/pos/print_pdf_invoice', type='json', auth='admin')
def print_pdf_invoice(self, pdfinvoice):
print 'print_pdf_invoice' + str(pdfinvoice)
return

View File

@ -21,6 +21,10 @@
user-select: none;
}
.point-of-sale .oe_hidden{
display: none !important;
}
.point-of-sale ul, .point-of-sale li {
margin: 0;
padding: 0;

View File

@ -40,6 +40,7 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal
});
}else{
running = false;
scheduled_end_time = 0;
end_of_queue.resolve();
}
};
@ -82,6 +83,7 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal
module.ProxyDevice = instance.web.Class.extend({
init: function(options){
var self = this;
options = options || {};
url = options.url || 'http://localhost:8069';
@ -99,10 +101,13 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal
};
this.custom_payment_status = this.default_payment_status;
this.notifications = {};
this.bypass_proxy = false;
this.connection = new instance.web.Session(undefined,url);
this.connection.session_id = _.uniqueId('posproxy');
this.bypass_proxy = false;
this.notifications = {};
this.test_connection();
window.proxy = this;
},
close: function(){
@ -113,7 +118,19 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal
for(var i = 0; i < callbacks.length; i++){
callbacks[i](params);
}
return this.connection.rpc('/pos/' + name, params || {});
if(this.connected){
return this.connection.rpc('/pos/' + name, params || {});
}else{
return (new $.Deferred()).reject();
}
},
test_connection: function(){
var self = this;
this.connected = true;
return this.message('test_connection').fail(function(){
self.connected = false;
console.error('Could not connect to the Proxy');
});
},
// this allows the client to be notified when a proxy call is made. The notification
@ -124,6 +141,8 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal
}
this.notifications[name].push(callback);
},
//a product has been scanned and recognized with success
// ean is a parsed ean object
@ -316,6 +335,11 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal
return this.message('print_receipt',{receipt: receipt});
},
// asks the proxy to log some information, as with the debug.log you can provide several arguments.
log: function(){
return this.message('log',{'arguments': _.toArray(arguments)});
},
// asks the proxy to print an invoice in pdf form ( used to print invoices generated by the server )
print_pdf_invoice: function(pdfinvoice){
return this.message('print_pdf_invoice',{pdfinvoice: pdfinvoice});

View File

@ -16,10 +16,12 @@ openerp.point_of_sale = function(instance) {
openerp_pos_scrollbar(instance,module); // import pos_scrollbar_widget.js
openerp_pos_screens(instance,module); // import pos_screens.js
openerp_pos_devices(instance,module); // import pos_devices.js
openerp_pos_widgets(instance,module); // import pos_widgets.js
openerp_pos_devices(instance,module); // import pos_devices.js
openerp_pos_tests(instance,module); // import pos_tests.js
instance.web.client_actions.add('pos.ui', 'instance.point_of_sale.PosWidget');
};

View File

@ -263,13 +263,13 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
//removes the current order
delete_current_order: function(){
this.get('selectedOrder').destroy({'reason':'abandon'});
console.log('coucou!');
},
// saves the order locally and try to send it to the backend.
// it returns a deferred that succeeds after having tried to send the order and all the other pending orders.
push_order: function(order) {
var self = this;
this.proxy.log('push_order',order.export_as_JSON());
var order_id = this.db.add_order(order.export_as_JSON());
var pushed = new $.Deferred();

View File

@ -253,7 +253,7 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
this.hidden = false;
if(this.$el){
this.$el.show();
this.$el.removeClass('oe_hidden');
}
if(this.pos_widget.action_bar.get_button_count() > 0){
@ -314,7 +314,7 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
hide: function(){
this.hidden = true;
if(this.$el){
this.$el.hide();
this.$el.addClass('oe_hidden');
}
},
@ -326,7 +326,7 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
this._super();
if(this.hidden){
if(this.$el){
this.$el.hide();
this.$el.addClass('oe_hidden');
}
}
},
@ -335,7 +335,7 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
module.PopUpWidget = module.PosBaseWidget.extend({
show: function(){
if(this.$el){
this.$el.show();
this.$el.removeClass('oe_hidden');
}
},
/* called before hide, when a popup is closed */
@ -345,7 +345,7 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
* pos instantiation, so you don't want to do anything fancy in here */
hide: function(){
if(this.$el){
this.$el.hide();
this.$el.addClass('oe_hidden');
}
},
});
@ -455,7 +455,7 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
queue.schedule(function(){
return self.pos.proxy.weighting_start();
},{ unclearable: true });
},{ important: true });
queue.schedule(function(){
return self.pos.proxy.weighting_read_kg().then(function(weight){
@ -479,7 +479,7 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
this.pos.proxy_queue.clear();
this.pos.proxy_queue.schedule(function(){
return self.pos.proxy.weighting_end();
},{ unclearable: true });
},{ important: true });
},
});
@ -516,7 +516,7 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
queue.schedule(function(){
return self.pos.proxy.weighting_start()
},{ unclearable: true });
},{ important: true });
queue.schedule(function(){
return self.pos.proxy.weighting_read_kg().then(function(weight){
@ -542,8 +542,7 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
}
},
order_product: function(){
var weight = this.pos.proxy.weighting_read_kg();
this.pos.get('selectedOrder').addProduct(this.get_product(),{ quantity:weight });
this.pos.get('selectedOrder').addProduct(this.get_product(),{ quantity: this.weight });
},
get_product_name: function(){
var product = this.get_product();
@ -567,7 +566,7 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
this.pos.proxy_queue.clear();
this.pos.proxy_queue.schedule(function(){
self.pos.proxy.weighting_end();
},{ unclearable: true });
},{ important: true });
},
});
@ -692,7 +691,7 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
barcode_client_action: function(ean){
this.pos.proxy.transaction_start();
this._super(ean);
$('.goodbye-message').hide();
$('.goodbye-message').addClass('oe_hidden');
this.pos_widget.screen_selector.show_popup('choose-receipt');
},
@ -704,14 +703,14 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
label: _t('Help'),
icon: '/point_of_sale/static/src/img/icons/png48/help.png',
click: function(){
$('.goodbye-message').css({opacity:1}).hide();
$('.goodbye-message').css({opacity:1}).addClass('oe_hidden');
self.help_button_action();
},
});
$('.goodbye-message').css({opacity:1}).show();
$('.goodbye-message').css({opacity:1}).removeClass('oe_hidden');
setTimeout(function(){
$('.goodbye-message').animate({opacity:0},500,'swing',function(){$('.goodbye-message').hide();});
$('.goodbye-message').animate({opacity:0},500,'swing',function(){$('.goodbye-message').addClass('oe_hidden');});
},5000);
},
});

View File

@ -0,0 +1,92 @@
function openerp_pos_tests(instance, module){ //module is instance.point_of_sale
// Various UI Tests to measure performance and memory leaks.
module.UiTester = function(){
var running = false;
var queue = new module.JobQueue();
// stop the currently running test
this.stop = function(){
queue.clear();
};
// randomly switch product categories
this.category_switch = function(interval){
queue.schedule(function(){
var breadcrumbs = $('.breadcrumb a');
var categories = $('li.category-button');
if(categories.length > 0){
var rnd = Math.floor(Math.random()*categories.length);
categories.eq(rnd).click();
}else{
var rnd = Math.floor(Math.random()*breadcrumbs.length);
breadcrumbs.eq(rnd).click();
}
},{repeat:true, duration:interval});
};
// randomly order products then resets the order
this.order_products = function(interval){
queue.schedule(function(){
var def = new $.Deferred();
var order_queue = new module.JobQueue();
var order_size = 1 + Math.floor(Math.random()*10);
while(order_size--){
order_queue.schedule(function(){
var products = $('.product a');
if(products.length > 0){
var rnd = Math.floor(Math.random()*products.length);
products.eq(rnd).click();
}
},{duration:250});
}
order_queue.finished().then(function(){
$('.deleteorder-button').click();
def.resolve();
});
return def;
},{repeat:true, duration: interval});
};
// makes a complete product order cycle ( print via proxy must be activated, and scale deactivated )
this.full_order_cycle = function(interval){
queue.schedule(function(){
var def = new $.Deferred();
var order_queue = new module.JobQueue();
var order_size = 1 + Math.floor(Math.random()*50);
while(order_size--){
order_queue.schedule(function(){
var products = $('.product a');
if(products.length > 0){
var rnd = Math.floor(Math.random()*products.length);
products.eq(rnd).click();
}
},{duration:250});
}
order_queue.schedule(function(){
$('.paypad-button:first').click();
},{duration:250});
order_queue.schedule(function(){
$('.paymentline-amount input:first').val(10000);
$('.paymentline-amount input:first').keyup();
},{duration:250});
order_queue.schedule(function(){
$('.pos-actionbar-button-list .button:eq(2)').click();
},{duration:250});
order_queue.schedule(function(){
def.resolve();
});
return def;
},{repeat: true, duration: interval});
};
};
if(jQuery.deparam(jQuery.param.querystring()).debug !== undefined){
window.pos_test_ui = new module.UiTester();
}
}

View File

@ -41,10 +41,10 @@ function openerp_pos_basewidget(instance, module){ //module is instance.point_of
},
show: function(){
this.$el.show();
this.$el.removeClass('oe_hidden');
},
hide: function(){
this.$el.hide();
this.$el.addClass('oe_hidden');
},
});

View File

@ -81,7 +81,7 @@ function openerp_pos_scrollbar(instance, module){ //module is instance.point_of_
$(window).unbind('resize',this.resize_handler);
$(window).bind('resize',this.resize_handler);
this.target().unbind('mousewheel',this.target_mousweheel_handler);
this.target().unbind('mousewheel',this.target_mousewheel_handler);
this.target().bind('mousewheel',this.target_mousewheel_handler);
// because the rendering is asynchronous we must wait for the next javascript update
@ -93,22 +93,18 @@ function openerp_pos_scrollbar(instance, module){ //module is instance.point_of_
},0);
},
// binds the window resize and the target scrolling events.
// it is good advice not to bind these multiple_times
bind_events:function(){
$(window).resize(function(){
});
this.target().bind('mousewheel',function(event,delta){
self.scroll(delta*self.wheel_step);
});
destroy: function(){
$(window).unbind('resize',this.resize_handler);
this.target().unbind('mousewheel',this.target_mousewheel_handler);
this._super();
},
// shows the scrollbar. if animated is true, it will do it in an animated fashion
show: function(animated){ //FIXME: animated show and hide don't work ... ?
if(animated){
this.$el.show().animate({'width':'48px'}, 500, 'swing');
this.$el.removeClass('oe_hidden').animate({'width':'48px'}, 500, 'swing');
}else{
this.$el.show().css('width','48px');
this.$el.removeClass('oe_hidden').css('width','48px');
}
this.on_show(this);
},
@ -117,9 +113,9 @@ function openerp_pos_scrollbar(instance, module){ //module is instance.point_of_
hide: function(animated){
var self = this;
if(animated){
this.$el.animate({'width':'0px'}, 500, 'swing', function(){ self.$el.hide();});
this.$el.animate({'width':'0px'}, 500, 'swing', function(){ self.$el.addClass('oe_hidden');});
}else{
this.$el.hide().css('width','0px');
this.$el.addClass('oe_hidden').css('width','0px');
}
this.on_hide(this);
},

View File

@ -137,17 +137,15 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
this.model = options.model;
this.order = options.order;
this.model.bind('change', _.bind( function() {
this.refresh();
}, this));
},
click_handler: function() {
this.order.selectLine(this.model);
this.trigger('order_line_selected');
this.model.bind('change', this.refresh, this);
},
renderElement: function() {
var self = this;
this._super();
this.$el.click(_.bind(this.click_handler, this));
this.$el.click(function(){
self.order.selectLine(this.model);
self.trigger('order_line_selected');
});
if(this.model.is_selected()){
this.$el.addClass('selected');
}
@ -156,6 +154,10 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
this.renderElement();
this.trigger('order_line_refreshed');
},
destroy: function(){
this.model.unbind('change',this.refresh,this);
this._super();
},
});
module.OrderWidget = module.PosBaseWidget.extend({
@ -281,27 +283,6 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
},
});
module.ProductWidget = module.PosBaseWidget.extend({
template: 'ProductWidget',
init: function(parent, options) {
this._super(parent,options);
this.model = options.model;
this.model.attributes.weight = options.weight;
this.next_screen = options.next_screen; //when a product is clicked, this screen is set
this.click_product_action = options.click_product_action;
},
// returns the url of the product thumbnail
renderElement: function() {
this._super();
this.$('img').replaceWith(this.pos_widget.image_cache.get_image(this.model.get_image_url()));
var self = this;
$("a", this.$el).click(function(e){
if(self.click_product_action){
self.click_product_action(self.model);
}
});
},
});
module.PaymentlineWidget = module.PosBaseWidget.extend({
template: 'PaymentlineWidget',
@ -358,16 +339,16 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
var self = this;
this.order = options.order;
this.order.bind('destroy',function(){ self.destroy(); });
this.order.bind('change', function(){ self.renderElement(); });
this.pos.bind('change:selectedOrder', function() {
self.renderElement();
}, this);
this.order.bind('destroy',this.destroy, this );
this.order.bind('change', this.renderElement, this );
this.pos.bind('change:selectedOrder', this.renderElement,this );
},
renderElement:function(){
this._super();
this.$('button.select-order').off('click').click(_.bind(this.selectOrder, this));
this.$('button.close-order').off('click').click(_.bind(this.closeOrder, this));
var self = this;
this.$el.click(function(){
self.selectOrder();
});
if( this.order === this.pos.get('selectedOrder') ){
this.$el.addClass('selected-order');
}
@ -377,8 +358,11 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
selectedOrder: this.order
});
},
closeOrder: function(event) {
this.order.destroy();
destroy: function(){
this.order.unbind('destroy', this.destroy, this);
this.order.unbind('change', this.renderElement, this);
this.pos.unbind('change:selectedOrder', this.renderElement, this);
this._super();
},
});
@ -422,9 +406,9 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
if(visible != this.visibility[element]){
this.visibility[element] = !!visible;
if(visible){
this.$('.'+element).show();
this.$('.'+element).removeClass('oe_hidden');
}else{
this.$('.'+element).hide();
this.$('.'+element).addClass('oe_hidden');
}
}
if(visible && action){
@ -459,10 +443,10 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
return button;
},
show:function(){
this.$el.show();
this.$el.removeClass('oe_hidden');
},
hide:function(){
this.$el.hide();
this.$el.addClass('oe_hidden');
},
});
@ -614,25 +598,19 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
renderElement: function() {
var self = this;
this._super();
// free subwidgets memory from previous renders
for(var i = 0, len = this.productwidgets.length; i < len; i++){
this.productwidgets[i].destroy();
}
this.productwidgets = [];
if(this.scrollbar){
this.scrollbar.destroy();
}
var products = this.pos.get('products').models || [];
for(var i = 0, len = products.length; i < len; i++){
var product = new module.ProductWidget(self, {
model: products[i],
click_product_action: this.click_product_action,
});
this.productwidgets.push(product);
product.appendTo(this.$('.product-list'));
}
_.each(products,function(product,i){
var $product = $(QWeb.render('Product',{ widget:self, product: products[i] }));
$product.find('img').replaceWith(self.pos_widget.image_cache.get_image(products[i].get_image_url()));
$product.find('a').click(function(){ self.click_product_action(product); });
$product.appendTo(self.$('.product-list'));
});
this.scrollbar = new module.ScrollbarWidget(this,{
target_widget: this,
target_selector: '.product-list-scroller',
@ -693,8 +671,8 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
this.$el.click(function(){ self.action(); });
}
},
show: function(){ this.$el.show(); },
hide: function(){ this.$el.hide(); },
show: function(){ this.$el.removeClass('oe_hidden'); },
hide: function(){ this.$el.addClass('oe_hidden'); },
});
// The debug widget lets the user control and monitor the hardware and software status
@ -873,13 +851,12 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
new_order_button.selectOrder();
}, self);
self.pos.get('orders').add(new module.Order({ pos: self.pos }));
self.pos.add_new_order();
self.build_widgets();
self.screen_selector.set_default_screen();
window.screen_selector = self.screen_selector;
self.pos.barcode_reader.connect();
@ -892,7 +869,7 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
}
instance.web.unblockUI();
self.$('.loader').animate({opacity:0},1500,'swing',function(){self.$('.loader').hide();});
self.$('.loader').animate({opacity:0},1500,'swing',function(){self.$('.loader').addClass('oe_hidden');});
self.pos.flush();
@ -1081,11 +1058,11 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
if(visible !== this.leftpane_visible){
this.leftpane_visible = visible;
if(visible){
$('#leftpane').show().animate({'width':this.leftpane_width},500,'swing');
$('#leftpane').removeClass('oe_hidden').animate({'width':this.leftpane_width},500,'swing');
$('#rightpane').animate({'left':this.leftpane_width},500,'swing');
}else{
var leftpane = $('#leftpane');
$('#leftpane').animate({'width':'0px'},500,'swing', function(){ leftpane.hide(); });
$('#leftpane').animate({'width':'0px'},500,'swing', function(){ leftpane.addClass('oe_hidden'); });
$('#rightpane').animate({'left':'0px'},500,'swing');
}
}
@ -1095,11 +1072,11 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
if(visible !== this.cashier_controls_visible){
this.cashier_controls_visible = visible;
if(visible){
$('#loggedas').show();
$('#rightheader').show();
$('#loggedas').removeClass('oe_hidden');
$('#rightheader').removeClass('oe_hidden');
}else{
$('#loggedas').hide();
$('#rightheader').hide();
$('#loggedas').addClass('oe_hidden');
$('#rightheader').addClass('oe_hidden');
}
}
},

View File

@ -399,24 +399,24 @@
</div>
</t>
<t t-name="ProductWidget">
<t t-name="Product">
<li class='product'>
<a href="#">
<div class="product-img">
<img src='' /> <!-- the product thumbnail -->
<t t-if="!widget.model.get('to_weight')">
<t t-if="!product.get('to_weight')">
<span class="price-tag">
<t t-esc="widget.format_currency(widget.model.get('price'))"/>
<t t-esc="widget.format_currency(product.get('price'))"/>
</span>
</t>
<t t-if="widget.model.get('to_weight')">
<t t-if="product.get('to_weight')">
<span class="price-tag">
<t t-esc="widget.format_currency(widget.model.get('price'))+'/Kg'"/>
<t t-esc="widget.format_currency(product.get('price'))+'/Kg'"/>
</span>
</t>
</div>
<div class="product-name">
<t t-esc="widget.model.get('name')"/>
<t t-esc="product.get('name')"/>
</div>
</a>
</li>