[MERGE] point_of_sale: various fixes and improvements

bzr revid: chs@openerp.com-20130910125223-5iu0tohmourxcz64
bzr revid: fva@openerp.com-20130911155921-xyqskhjx9r9pgw9c
This commit is contained in:
Frédéric van der Essen 2013-09-11 17:59:21 +02:00
commit a590fbaf43
10 changed files with 261 additions and 163 deletions

View File

@ -3,12 +3,17 @@ import logging
import simplejson import simplejson
import os import os
import openerp import openerp
import time
import random
from openerp.addons.web import http from openerp.addons.web import http
from openerp.addons.web.http import request from openerp.addons.web.http import request
from openerp.addons.web.controllers.main import manifest_list, module_boot, html_template from openerp.addons.web.controllers.main import manifest_list, module_boot, html_template
class PointOfSaleController(http.Controller): class PointOfSaleController(http.Controller):
def __init__(self):
self.scale = 'closed'
self.scale_weight = 0.0
@http.route('/pos/app', type='http', auth='admin') @http.route('/pos/app', type='http', auth='admin')
def app(self): def app(self):
@ -64,6 +69,10 @@ class PointOfSaleController(http.Controller):
return m return m
@http.route('/pos/test_connection', type='json', auth='admin')
def test_connection(self):
return
@http.route('/pos/scan_item_success', type='json', auth='admin') @http.route('/pos/scan_item_success', type='json', auth='admin')
def scan_item_success(self, ean): def scan_item_success(self, ean):
""" """
@ -98,18 +107,38 @@ class PointOfSaleController(http.Controller):
@http.route('/pos/weighting_start', type='json', auth='admin') @http.route('/pos/weighting_start', type='json', auth='admin')
def weighting_start(self): def weighting_start(self):
print "weighting_start" if self.scale == 'closed':
print "Opening (Fake) Connection to Scale..."
self.scale = 'open'
self.scale_weight = 0.0
time.sleep(0.1)
print "... Scale Open."
else:
print "WARNING: Scale already Connected !!!"
return return
@http.route('/pos/weighting_read_kg', type='json', auth='admin') @http.route('/pos/weighting_read_kg', type='json', auth='admin')
def weighting_read_kg(self): def weighting_read_kg(self):
print "weighting_read_kg" if self.scale == 'open':
return 3.14 print "Reading Scale..."
time.sleep(0.025)
self.scale_weight += 0.01
print "... Done."
return self.scale_weight
else:
print "WARNING: Reading closed scale !!!"
return 0.0
@http.route('/pos/weighting_end', type='json', auth='admin') @http.route('/pos/weighting_end', type='json', auth='admin')
def weighting_end(self): def weighting_end(self):
print "weighting_end" if self.scale == 'open':
return print "Closing Connection to Scale ..."
self.scale = 'closed'
self.scale_weight = 0.0
time.sleep(0.1)
print "... Scale Closed."
else:
print "WARNING: Scale already Closed !!!"
@http.route('/pos/payment_request', type='json', auth='admin') @http.route('/pos/payment_request', type='json', auth='admin')
def payment_request(self, price): def payment_request(self, price):

View File

@ -175,10 +175,9 @@
background: linear-gradient(#b2b3d7, #7f82ac); background: linear-gradient(#b2b3d7, #7f82ac);
} }
.point-of-sale #rightheader button.neworder-button { .point-of-sale #rightheader button.square{
width: 32px; width: 32px;
margin-left:4px; margin-left:4px;
margin-right:4px;
} }
.point-of-sale div#order-selector { .point-of-sale div#order-selector {
@ -186,12 +185,20 @@
} }
.point-of-sale ol#orders { .point-of-sale ol#orders {
display: inline; display: inline;
margin-left: 8px;
} }
.point-of-sale li.order-selector-button { .point-of-sale li.order-selector-button {
display: inline; display: inline;
} }
.point-of-sale li.selected-order button { .point-of-sale li.selected-order button {
font-weight: 900; font-weight: 900;
background: #7174A8 !important;
color: rgb(236, 237, 255) !important;
text-shadow: 0px 1px rgba(0, 0, 0, 0.31);
-webkit-box-shadow: 0px 1px 2px rgb(63, 66, 139) inset;
-moz-box-shadow: 0px 1px 2px rgb(63, 66, 139) inset;
-ms-box-shadow: 0px 1px 2px rgb(63, 66, 139) inset;
box-shadow: 0px 1px 2px rgb(63, 66, 139) inset;
} }
/* c) The session buttons */ /* c) The session buttons */
@ -215,7 +222,7 @@
.point-of-sale #rightheader .header-button:last-child{ .point-of-sale #rightheader .header-button:last-child{
border-left: 1px solid #3a3a3a; border-left: 1px solid #3a3a3a;
} }
.point-of-sale #rightheader .header-button:hover{ .point-of-sale #rightheader .header-button:active{
background: rgba(0,0,0,0.2); background: rgba(0,0,0,0.2);
text-shadow: #000 0px 0px 3px; text-shadow: #000 0px 0px 3px;
color:#EEE; color:#EEE;
@ -307,18 +314,10 @@
border-top: 1px solid #efefef; border-top: 1px solid #efefef;
font-size: 14px; font-size: 14px;
} }
.point-of-sale #paypad button, .point-of-sale #numpad button, .point-of-sale .popup button{ .point-of-sale #paypad button:active,
position: relative; .point-of-sale #numpad button:active,
top: 0; .point-of-sale #numpad .selected-mode,
-webkit-transition: top 150ms linear; .point-of-sale .popup button:active{
-moz-transition: top 150ms linear;
-ms-transition: top 150ms linear;
transition: top 150ms linear;
}
.point-of-sale #paypad button:active, .point-of-sale #numpad button:active, .point-of-sale .popup button:active{
top:3px;
}
.point-of-sale #paypad button:hover, .point-of-sale #numpad button:hover, .point-of-sale #numpad .selected-mode, .point-of-sale .popup button:hover {
border: none; border: none;
color: white; color: white;
background: #7f82ac; background: #7f82ac;
@ -518,7 +517,7 @@
-moz-box-shadow: 0px 2px 2px rgba(0,0,0, 0.1); -moz-box-shadow: 0px 2px 2px rgba(0,0,0, 0.1);
box-shadow: 0px 2px 2px rgba(0,0,0, 0.1); box-shadow: 0px 2px 2px rgba(0,0,0, 0.1);
} }
.point-of-sale .category-simple-button:hover { .point-of-sale .category-simple-button:active{
color: white; color: white;
background: #7f82ac; background: #7f82ac;
border: 1px solid #7f82ac; border: 1px solid #7f82ac;
@ -1049,13 +1048,13 @@
-moz-transition: background 250ms ease-in-out; -moz-transition: background 250ms ease-in-out;
transition: background 250ms ease-in-out; transition: background 250ms ease-in-out;
} }
.point-of-sale .order .orderline:hover{ .point-of-sale .order .orderline:active{
background: rgba(140,143,183,0.05); background: rgba(140,143,183,0.05);
-webkit-transition: background 50ms ease-in-out; -webkit-transition: background 50ms ease-in-out;
-moz-transition: background 50ms ease-in-out; -moz-transition: background 50ms ease-in-out;
transition: background 50ms ease-in-out; transition: background 50ms ease-in-out;
} }
.point-of-sale .order .orderline.empty:hover{ .point-of-sale .order .orderline.empty:active{
background: transparent; background: transparent;
cursor: default; cursor: default;
} }
@ -1156,7 +1155,7 @@
.point-of-sale .pos-actionbar .button .icon{ .point-of-sale .pos-actionbar .button .icon{
margin-top: 10px; margin-top: 10px;
} }
.point-of-sale .pos-actionbar .button:hover { .point-of-sale .pos-actionbar .button:active{
color: white; color: white;
background: #7f82ac; background: #7f82ac;
border: 1px solid #7f82ac; border: 1px solid #7f82ac;
@ -1173,7 +1172,7 @@
.point-of-sale .pos-actionbar .button.disabled *{ .point-of-sale .pos-actionbar .button.disabled *{
opacity: 0.5; opacity: 0.5;
} }
.point-of-sale .pos-actionbar .button.disabled:hover{ .point-of-sale .pos-actionbar .button.disabled:active{
border: 1px solid #cacaca; border: 1px solid #cacaca;
border-radius: 4px; border-radius: 4px;
color: #555; color: #555;
@ -1242,7 +1241,7 @@
display: block; display: block;
cursor:pointer; cursor:pointer;
} }
.point-of-sale .debug-widget .button:hover{ .point-of-sale .debug-widget .button:active{
background: rgba(96,21,177,0.45); background: rgba(96,21,177,0.45);
} }
.point-of-sale .debug-widget input{ .point-of-sale .debug-widget input{
@ -1330,7 +1329,7 @@
-moz-box-shadow: 0px 2px 2px rgba(0,0,0, 0.3); -moz-box-shadow: 0px 2px 2px rgba(0,0,0, 0.3);
box-shadow: 0px 2px 2px rgba(0,0,0, 0.3); box-shadow: 0px 2px 2px rgba(0,0,0, 0.3);
} }
.point-of-sale .popup .button:hover { .point-of-sale .popup .button:active{
color: white; color: white;
background: #7f82ac; background: #7f82ac;
border: 1px solid #7f82ac; border: 1px solid #7f82ac;
@ -1398,7 +1397,7 @@
-moz-transition: all 250ms ease-in-out; -moz-transition: all 250ms ease-in-out;
transition: all 250ms ease-in-out; transition: all 250ms ease-in-out;
} }
.point-of-sale .scrollbar .button:hover{ .point-of-sale .scrollbar .button:active{
text-shadow: rgba(255,255,255,0.8) 0px 0px 15px; text-shadow: rgba(255,255,255,0.8) 0px 0px 15px;
} }
.point-of-sale .scrollbar .button.disabled{ .point-of-sale .scrollbar .button.disabled{

View File

@ -1,6 +1,81 @@
function openerp_pos_devices(instance,module){ //module is instance.point_of_sale function openerp_pos_devices(instance,module){ //module is instance.point_of_sale
// the JobQueue schedules a sequence of 'jobs'. each job is
// a function returning a deferred. the queue waits for each job to finish
// before launching the next. Each job can also be scheduled with a delay.
// the is used to prevent parallel requests to the proxy.
module.JobQueue = function(){
var queue = [];
var running = false;
var scheduled_end_time = 0;
var end_of_queue = (new $.Deferred()).resolve();
var stoprepeat = false;
var run = function(){
if(end_of_queue.state() === 'resolved'){
end_of_queue = new $.Deferred();
}
if(queue.length > 0){
running = true;
var job = queue[0];
if(!job.opts.repeat || stoprepeat){
queue.shift();
stoprepeat = false;
}
// the time scheduled for this job
scheduled_end_time = (new Date()).getTime() + (job.opts.duration || 0);
// we run the job and put in def when it finishes
var def = job.fun() || (new $.Deferred()).resolve();
// we don't care if a job fails ...
def.always(function(){
// we run the next job after the scheduled_end_time, even if it finishes before
setTimeout(function(){
run();
}, Math.max(0, scheduled_end_time - (new Date()).getTime()) );
});
}else{
running = false;
end_of_queue.resolve();
}
};
// adds a job to the schedule.
// opts : {
// duration : the job is guaranteed to finish no quicker than this (milisec)
// repeat : if true, the job will be endlessly repeated
// important : if true, the scheduled job cannot be canceled by a queue.clear()
// }
this.schedule = function(fun, opts){
queue.push({fun:fun, opts:opts || {}});
if(!running){
run();
}
}
// remove all jobs from the schedule (except the ones marked as important)
this.clear = function(){
queue = _.filter(queue,function(job){job.opts.important === true});
};
// end the repetition of the current job
this.stoprepeat = function(){
stoprepeat = true;
};
// returns a deferred that resolves when all scheduled
// jobs have been run.
// ( jobs added after the call to this method are considered as well )
this.finished = function(){
return end_of_queue;
}
};
// this object interfaces with the local proxy to communicate to the various hardware devices // this object interfaces with the local proxy to communicate to the various hardware devices
// connected to the Point of Sale. As the communication only goes from the POS to the proxy, // connected to the Point of Sale. As the communication only goes from the POS to the proxy,
// methods are used both to signal an event, and to fetch information. // methods are used both to signal an event, and to fetch information.
@ -10,7 +85,6 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal
options = options || {}; options = options || {};
url = options.url || 'http://localhost:8069'; url = options.url || 'http://localhost:8069';
this.weight = 0;
this.weighting = false; this.weighting = false;
this.debug_weight = 0; this.debug_weight = 0;
this.use_debug_weight = false; this.use_debug_weight = false;
@ -35,18 +109,11 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal
this.connection.destroy(); this.connection.destroy();
}, },
message : function(name,params){ message : function(name,params){
var ret = new $.Deferred();
var callbacks = this.notifications[name] || []; var callbacks = this.notifications[name] || [];
for(var i = 0; i < callbacks.length; i++){ for(var i = 0; i < callbacks.length; i++){
callbacks[i](params); callbacks[i](params);
} }
return this.connection.rpc('/pos/' + name, params || {});
this.connection.rpc('/pos/' + name, params || {}).done(function(result) {
ret.resolve(result);
}).fail(function(error) {
ret.reject(error);
});
return ret;
}, },
// this allows the client to be notified when a proxy call is made. The notification // this allows the client to be notified when a proxy call is made. The notification
@ -82,13 +149,32 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal
//the client is starting to weight //the client is starting to weight
weighting_start: function(){ weighting_start: function(){
var ret = new $.Deferred();
if(!this.weighting){ if(!this.weighting){
this.weighting = true; this.weighting = true;
if(!this.bypass_proxy){ this.message('weighting_start').always(function(){
this.weight = 0; ret.resolve();
return this.message('weighting_start'); });
} }else{
console.error('Weighting already started!!!');
ret.resolve();
} }
return ret;
},
// the client has finished weighting products
weighting_end: function(){
var ret = new $.Deferred();
if(this.weighting){
this.weighting = false;
this.message('weighting_end').always(function(){
ret.resolve();
});
}else{
console.error('Weighting already ended !!!');
ret.resolve();
}
return ret;
}, },
//returns the weight on the scale. //returns the weight on the scale.
@ -96,17 +182,14 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal
// and a weighting_end() // and a weighting_end()
weighting_read_kg: function(){ weighting_read_kg: function(){
var self = this; var self = this;
var ret = new $.Deferred();
this.message('weighting_read_kg',{}) this.message('weighting_read_kg',{})
.done(function(weight){ .then(function(weight){
if(self.weighting){ ret.resolve(self.use_debug_weight ? self.debug_weight : weight);
if(self.use_debug_weight){ }, function(){ //failed to read weight
self.weight = self.debug_weight; ret.resolve(self.use_debug_weight ? self.debug_weight : 0.0);
}else{
self.weight = weight;
}
}
}); });
return this.weight; return ret;
}, },
// sets a custom weight, ignoring the proxy returned value. // sets a custom weight, ignoring the proxy returned value.
@ -121,12 +204,6 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal
this.debug_weight = 0; this.debug_weight = 0;
}, },
// the client has finished weighting products
weighting_end: function(){
this.weight = 0;
this.weighting = false;
this.message('weighting_end');
},
// the pos asks the client to pay 'price' units // the pos asks the client to pay 'price' units
payment_request: function(price){ payment_request: function(price){

View File

@ -24,6 +24,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
this.barcode_reader = new module.BarcodeReader({'pos': this}); // used to read barcodes this.barcode_reader = new module.BarcodeReader({'pos': this}); // used to read barcodes
this.proxy = new module.ProxyDevice(); // used to communicate to the hardware devices via a local proxy this.proxy = new module.ProxyDevice(); // used to communicate to the hardware devices via a local proxy
this.proxy_queue = new module.JobQueue(); // used to prevent parallels communications to the proxy
this.db = new module.PosLS(); // a database used to store the products and categories this.db = new module.PosLS(); // a database used to store the products and categories
this.db.clear('products','categories'); this.db.clear('products','categories');
this.debug = jQuery.deparam(jQuery.param.querystring()).debug !== undefined; //debug mode this.debug = jQuery.deparam(jQuery.param.querystring()).debug !== undefined; //debug mode
@ -55,7 +56,9 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
'selectedOrder': null, 'selectedOrder': null,
}); });
this.get('orders').bind('remove', function(){ self.on_removed_order(); }); this.get('orders').bind('remove', function(order,_unused_,options){
self.on_removed_order(order,options.index,options.reason);
});
// We fetch the backend data on the server asynchronously. this is done only when the pos user interface is launched, // We fetch the backend data on the server asynchronously. this is done only when the pos user interface is launched,
// Any change on this data made on the server is thus not reflected on the point of sale until it is relaunched. // Any change on this data made on the server is thus not reflected on the point of sale until it is relaunched.
@ -239,11 +242,14 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
// this is called when an order is removed from the order collection. It ensures that there is always an existing // this is called when an order is removed from the order collection. It ensures that there is always an existing
// order and a valid selected order // order and a valid selected order
on_removed_order: function(removed_order){ on_removed_order: function(removed_order,index,reason){
if( this.get('orders').isEmpty()){ if(reason === 'abandon' && this.get('orders').size() > 0){
this.add_new_order(); // when we intentionally remove an unfinished order, and there is another existing one
this.set({'selectedOrder' : this.get('orders').at(index) || this.get('orders').last()});
}else{ }else{
this.set({ selectedOrder: this.get('orders').last() }); // when the order was automatically removed after completion,
// or when we intentionally delete the only concurrent order
this.add_new_order();
} }
}, },
@ -254,6 +260,12 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
this.set('selectedOrder', order); this.set('selectedOrder', order);
}, },
//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. // 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. // it returns a deferred that succeeds after having tried to send the order and all the other pending orders.
push_order: function(order) { push_order: function(order) {

View File

@ -271,19 +271,19 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
}); });
var self = this; var self = this;
var cashier_mode = this.pos_widget.screen_selector.get_user_mode() === 'cashier'; this.cashier_mode = this.pos_widget.screen_selector.get_user_mode() === 'cashier';
this.pos_widget.set_numpad_visible(this.show_numpad && cashier_mode); this.pos_widget.set_numpad_visible(this.show_numpad && this.cashier_mode);
this.pos_widget.set_leftpane_visible(this.show_leftpane); this.pos_widget.set_leftpane_visible(this.show_leftpane);
this.pos_widget.set_left_action_bar_visible(this.show_leftpane && !cashier_mode); this.pos_widget.set_left_action_bar_visible(this.show_leftpane && !this.cashier_mode);
this.pos_widget.set_cashier_controls_visible(cashier_mode); this.pos_widget.set_cashier_controls_visible(this.cashier_mode);
if(cashier_mode && this.pos.iface_self_checkout){ if(this.cashier_mode && this.pos.iface_self_checkout){
this.pos_widget.client_button.show(); this.pos_widget.client_button.show();
}else{ }else{
this.pos_widget.client_button.hide(); this.pos_widget.client_button.hide();
} }
if(cashier_mode){ if(this.cashier_mode){
this.pos_widget.close_button.show(); this.pos_widget.close_button.show();
}else{ }else{
this.pos_widget.close_button.hide(); this.pos_widget.close_button.hide();
@ -441,7 +441,7 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
module.ErrorInvoiceTransferPopupWidget = module.ErrorPopupWidget.extend({ module.ErrorInvoiceTransferPopupWidget = module.ErrorPopupWidget.extend({
template: 'ErrorInvoiceTransferPopupWidget', template: 'ErrorInvoiceTransferPopupWidget',
}); });
module.ScaleInviteScreenWidget = module.ScreenWidget.extend({ module.ScaleInviteScreenWidget = module.ScreenWidget.extend({
template:'ScaleInviteScreenWidget', template:'ScaleInviteScreenWidget',
@ -451,30 +451,35 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
show: function(){ show: function(){
this._super(); this._super();
var self = this; var self = this;
var queue = this.pos.proxy_queue;
self.pos.proxy.weighting_start(); queue.schedule(function(){
return self.pos.proxy.weighting_start();
this.intervalID = setInterval(function(){ },{ unclearable: true });
var weight = self.pos.proxy.weighting_read_kg();
if(weight > 0.001){ queue.schedule(function(){
clearInterval(this.intervalID); return self.pos.proxy.weighting_read_kg().then(function(weight){
self.pos_widget.screen_selector.set_current_screen(self.next_screen); if(weight > 0.001){
} self.pos_widget.screen_selector.set_current_screen(self.next_screen);
},100); }
});
},{duration: 100, repeat: true});
this.add_action_button({ this.add_action_button({
label: _t('Back'), label: _t('Back'),
icon: '/point_of_sale/static/src/img/icons/png48/go-previous.png', icon: '/point_of_sale/static/src/img/icons/png48/go-previous.png',
click: function(){ click: function(){
clearInterval(this.intervalID);
self.pos_widget.screen_selector.set_current_screen(self.previous_screen); self.pos_widget.screen_selector.set_current_screen(self.previous_screen);
} }
}); });
}, },
close: function(){ close: function(){
this._super(); this._super();
clearInterval(this.intervalID); var self = this;
this.pos.proxy.weighting_end(); this.pos.proxy_queue.clear();
this.pos.proxy_queue.schedule(function(){
return self.pos.proxy.weighting_end();
},{ unclearable: true });
}, },
}); });
@ -486,9 +491,11 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
show: function(){ show: function(){
this._super(); this._super();
this.renderElement();
var self = this; var self = this;
var queue = this.pos.proxy_queue;
this.set_weight(0);
this.renderElement();
this.add_action_button({ this.add_action_button({
label: _t('Back'), label: _t('Back'),
@ -507,14 +514,16 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
}, },
}); });
this.pos.proxy.weighting_start(); queue.schedule(function(){
this.intervalID = setInterval(function(){ return self.pos.proxy.weighting_start()
var weight = self.pos.proxy.weighting_read_kg(); },{ unclearable: true });
if(weight != self.weight){
self.weight = weight; queue.schedule(function(){
self.renderElement(); return self.pos.proxy.weighting_read_kg().then(function(weight){
} self.set_weight(weight);
},100); });
},{duration:50, repeat: true});
}, },
renderElement: function(){ renderElement: function(){
var self = this; var self = this;
@ -544,54 +553,24 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
var product = this.get_product(); var product = this.get_product();
return (product ? product.get('price') : 0) || 0; return (product ? product.get('price') : 0) || 0;
}, },
get_product_weight: function(){ set_weight: function(weight){
return this.weight || 0; this.weight = weight;
this.$('.js-weight').text(this.get_product_weight_string());
},
get_product_weight_string: function(){
return (this.weight || 0).toFixed(3) + ' Kg';
}, },
close: function(){ close: function(){
var self = this;
this._super(); this._super();
clearInterval(this.intervalID);
this.pos.proxy.weighting_end(); this.pos.proxy_queue.clear();
this.pos.proxy_queue.schedule(function(){
self.pos.proxy.weighting_end();
},{ unclearable: true });
}, },
}); });
// the JobQueue schedules a sequence of 'jobs'. each job is
// a function returning a deferred. the queue waits for each job to finish
// before launching the next. Each job can also be scheduled with a delay.
// the queue jobqueue is used to prevent parallel requests to the payment terminal.
module.JobQueue = function(){
var queue = [];
var running = false;
var run = function(){
if(queue.length > 0){
running = true;
var job = queue.shift();
setTimeout(function(){
var def = job.fun();
if(def){
def.done(run);
}else{
run();
}
},job.delay || 0);
}else{
running = false;
}
};
// adds a job to the schedule.
this.schedule = function(fun, delay){
queue.push({fun:fun, delay:delay});
if(!running){
run();
}
}
// remove all jobs from the schedule
this.clear = function(){
queue = [];
};
};
module.ClientPaymentScreenWidget = module.ScreenWidget.extend({ module.ClientPaymentScreenWidget = module.ScreenWidget.extend({
template:'ClientPaymentScreenWidget', template:'ClientPaymentScreenWidget',
@ -740,7 +719,8 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
module.ProductScreenWidget = module.ScreenWidget.extend({ module.ProductScreenWidget = module.ScreenWidget.extend({
template:'ProductScreenWidget', template:'ProductScreenWidget',
scale_screen: 'scale_invite', scale_screen: 'scale',
client_scale_screen : 'scale_invite',
client_next_screen: 'client_payment', client_next_screen: 'client_payment',
show_numpad: true, show_numpad: true,
@ -754,7 +734,7 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
this.product_list_widget = new module.ProductListWidget(this,{ this.product_list_widget = new module.ProductListWidget(this,{
click_product_action: function(product){ click_product_action: function(product){
if(product.get('to_weight') && self.pos.iface_electronic_scale){ if(product.get('to_weight') && self.pos.iface_electronic_scale){
self.pos_widget.screen_selector.set_current_screen(self.scale_screen, {product: product}); self.pos_widget.screen_selector.set_current_screen( self.cashier_mode ? self.scale_screen : self.client_scale_screen, {product: product});
}else{ }else{
self.pos.get('selectedOrder').addProduct(product); self.pos.get('selectedOrder').addProduct(product);
} }

View File

@ -62,10 +62,10 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
start: function() { start: function() {
this.state.bind('change:mode', this.changedMode, this); this.state.bind('change:mode', this.changedMode, this);
this.changedMode(); this.changedMode();
this.$el.find('button#numpad-backspace').click(_.bind(this.clickDeleteLastChar, this)); this.$el.find('.numpad-backspace').click(_.bind(this.clickDeleteLastChar, this));
this.$el.find('button#numpad-minus').click(_.bind(this.clickSwitchSign, this)); this.$el.find('.numpad-minus').click(_.bind(this.clickSwitchSign, this));
this.$el.find('button.number-char').click(_.bind(this.clickAppendNewChar, this)); this.$el.find('.number-char').click(_.bind(this.clickAppendNewChar, this));
this.$el.find('button.mode-button').click(_.bind(this.clickChangeMode, this)); this.$el.find('.mode-button').click(_.bind(this.clickChangeMode, this));
}, },
clickDeleteLastChar: function() { clickDeleteLastChar: function() {
return this.state.deleteLastChar(); return this.state.deleteLastChar();
@ -189,8 +189,6 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
}else if( mode === 'price'){ }else if( mode === 'price'){
order.getSelectedLine().set_unit_price(val); order.getSelectedLine().set_unit_price(val);
} }
} else {
this.pos.get('selectedOrder').destroy();
} }
}, },
change_selected_order: function() { change_selected_order: function() {
@ -362,28 +360,23 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
this.order = options.order; this.order = options.order;
this.order.bind('destroy',function(){ self.destroy(); }); this.order.bind('destroy',function(){ self.destroy(); });
this.order.bind('change', function(){ self.renderElement(); }); this.order.bind('change', function(){ self.renderElement(); });
this.pos.bind('change:selectedOrder', _.bind( function(pos) { this.pos.bind('change:selectedOrder', function() {
var selectedOrder; self.renderElement();
selectedOrder = pos.get('selectedOrder'); }, this);
if (this.order === selectedOrder) {
this.setButtonSelected();
}
}, this));
}, },
renderElement:function(){ renderElement:function(){
this._super(); this._super();
this.$('button.select-order').off('click').click(_.bind(this.selectOrder, this)); this.$('button.select-order').off('click').click(_.bind(this.selectOrder, this));
this.$('button.close-order').off('click').click(_.bind(this.closeOrder, this)); this.$('button.close-order').off('click').click(_.bind(this.closeOrder, this));
if( this.order === this.pos.get('selectedOrder') ){
this.$el.addClass('selected-order');
}
}, },
selectOrder: function(event) { selectOrder: function(event) {
this.pos.set({ this.pos.set({
selectedOrder: this.order selectedOrder: this.order
}); });
}, },
setButtonSelected: function() {
$('.selected-order').removeClass('selected-order');
this.$el.addClass('selected-order');
},
closeOrder: function(event) { closeOrder: function(event) {
this.order.destroy(); this.order.destroy();
}, },
@ -848,17 +841,24 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
this.leftpane_width = '440px'; this.leftpane_width = '440px';
this.cashier_controls_visible = true; this.cashier_controls_visible = true;
this.image_cache = new module.ImageCache(); // for faster products image display this.image_cache = new module.ImageCache(); // for faster products image display
}, },
start: function() { start: function() {
var self = this; var self = this;
return self.pos.ready.done(function() { return self.pos.ready.done(function() {
$('.oe_tooltip').remove(); // remove tooltip from the start session button
self.build_currency_template(); self.build_currency_template();
self.renderElement(); self.renderElement();
self.$('.neworder-button').click(function(){ self.$('.neworder-button').click(function(){
self.pos.add_new_order(); self.pos.add_new_order();
}); });
self.$('.deleteorder-button').click(function(){
self.pos.delete_current_order();
});
//when a new order is created, add an order button widget //when a new order is created, add an order button widget
self.pos.get('orders').bind('add', function(new_order){ self.pos.get('orders').bind('add', function(new_order){

View File

@ -12,7 +12,8 @@
</div> </div>
<div id="rightheader"> <div id="rightheader">
<div id="order-selector"> <div id="order-selector">
<button class="neworder-button">+</button> <button class="neworder-button square"></button>
<button class="deleteorder-button square"></button>
<ol id="orders"></ol> <ol id="orders"></ol>
</div> </div>
<!-- here goes header buttons --> <!-- here goes header buttons -->
@ -93,10 +94,10 @@
<button class="input-button number-char">9</button> <button class="input-button number-char">9</button>
<button class="mode-button" data-mode='price'>Price</button> <button class="mode-button" data-mode='price'>Price</button>
<br /> <br />
<button class="input-button" id="numpad-minus" >+/-</button> <button class="input-button numpad-minus" >+/-</button>
<button class="input-button number-char">0</button> <button class="input-button number-char">0</button>
<button class="input-button number-char">.</button> <button class="input-button number-char">.</button>
<button class="input-button" id="numpad-backspace"> <button class="input-button numpad-backspace">
<img src="/point_of_sale/static/src/img/backspace.png" width="24" height="21" /> <img src="/point_of_sale/static/src/img/backspace.png" width="24" height="21" />
</button> </button>
<br /> <br />
@ -191,8 +192,9 @@
<div class="display"> <div class="display">
<span class="weight"> <span class="weight">
<p> <p>
<t t-esc="widget.get_product_weight().toFixed(3)" /> <span class='js-weight'>
Kg <t t-esc="widget.get_product_weight_string()" />
</span>
</p> </p>
</span> </span>
<span class="product-name"> <span class="product-name">

View File

@ -451,7 +451,7 @@ instance.web.DataSet = instance.web.Class.extend(instance.web.PropertiesMixin,
// TODO: reorder results to match ids list // TODO: reorder results to match ids list
return this._model.call('read', return this._model.call('read',
[ids, fields || false], [ids, fields || false],
{context: this._model.context(options.context)}); {context: this.get_context(options.context)});
}, },
/** /**
* Read a slice of the records represented by this DataSet, based on its * Read a slice of the records represented by this DataSet, based on its

View File

@ -166,13 +166,13 @@ instance.web.TreeView = instance.web.View.extend(/** @lends instance.web.TreeVie
var is_loaded = 0, var is_loaded = 0,
$this = $(this), $this = $(this),
record_id = $this.data('id'), record_id = $this.data('id'),
parent_id = $this.data('parent-id'), row_parent_id = $this.data('row-parent-id'),
record = self.records[record_id], record = self.records[record_id],
children_ids = record[self.children_field]; children_ids = record[self.children_field];
_(children_ids).each(function(childid) { _(children_ids).each(function(childid) {
if (self.$el.find('[id=treerow_' + childid + '][data-parent-id='+ record_id +']').length ) { if (self.$el.find('[id=treerow_' + childid + '][data-row-parent-id='+ record_id +']').length ) {
if (self.$el.find('[id=treerow_' + childid + '][data-parent-id='+ record_id +']').is(':hidden')) { if (self.$el.find('[id=treerow_' + childid + '][data-row-parent-id='+ record_id +']').is(':hidden')) {
is_loaded = -1; is_loaded = -1;
} else { } else {
is_loaded++; is_loaded++;
@ -196,7 +196,6 @@ instance.web.TreeView = instance.web.View.extend(/** @lends instance.web.TreeVie
_(records).each(function (record) { _(records).each(function (record) {
self.records[record.id] = record; self.records[record.id] = record;
}); });
var $curr_node = self.$el.find('#treerow_' + id); var $curr_node = self.$el.find('#treerow_' + id);
var children_rows = QWeb.render('TreeView.rows', { var children_rows = QWeb.render('TreeView.rows', {
'records': records, 'records': records,
@ -206,7 +205,7 @@ instance.web.TreeView = instance.web.View.extend(/** @lends instance.web.TreeVie
'level': $curr_node.data('level') || 0, 'level': $curr_node.data('level') || 0,
'render': instance.web.format_value, 'render': instance.web.format_value,
'color_for': self.color_for, 'color_for': self.color_for,
'parent_id': id 'row_parent_id': id
}); });
if ($curr_node.length) { if ($curr_node.length) {
$curr_node.addClass('oe_open'); $curr_node.addClass('oe_open');
@ -250,7 +249,7 @@ instance.web.TreeView = instance.web.View.extend(/** @lends instance.web.TreeVie
showcontent: function (curnode,record_id, show) { showcontent: function (curnode,record_id, show) {
curnode.parent('tr').toggleClass('oe_open', show); curnode.parent('tr').toggleClass('oe_open', show);
_(this.records[record_id][this.children_field]).each(function (child_id) { _(this.records[record_id][this.children_field]).each(function (child_id) {
var $child_row = this.$el.find('[id=treerow_' + child_id + '][data-parent-id='+ curnode.data('id') +']'); var $child_row = this.$el.find('[id=treerow_' + child_id + '][data-row-parent-id='+ curnode.data('id') +']');
if ($child_row.hasClass('oe_open')) { if ($child_row.hasClass('oe_open')) {
$child_row.toggleClass('oe_open',show); $child_row.toggleClass('oe_open',show);
this.showcontent($child_row, child_id, false); this.showcontent($child_row, child_id, false);

View File

@ -685,7 +685,7 @@
t-foreach="records" t-as="record" t-foreach="records" t-as="record"
t-att-id="'treerow_' + record.id" t-att-id="'treerow_' + record.id"
t-att-data-id="record.id" t-att-data-level="level + 1" t-att-data-id="record.id" t-att-data-level="level + 1"
t-att-data-parent-id="parent_id"> t-att-data-row-parent-id="row_parent_id">
<t t-set="children" t-value="record[children_field]"/> <t t-set="children" t-value="record[children_field]"/>
<t t-set="class" t-value="children and children.length ? 'treeview-tr' : 'treeview-td'"/> <t t-set="class" t-value="children and children.length ? 'treeview-tr' : 'treeview-td'"/>
<t t-set="rank" t-value="'oe-treeview-first'"/> <t t-set="rank" t-value="'oe-treeview-first'"/>