[MERGE] point_of_sale: various fixes and improvements: invoicing support, snappier scale, and a more robust order sending

bzr revid: fva@openerp.com-20130712125419-t588gpoysustm19t
This commit is contained in:
Frédéric van der Essen 2013-07-12 14:54:19 +02:00
commit 4c96a05dbb
10 changed files with 390 additions and 123 deletions

View File

@ -4,27 +4,31 @@ import simplejson
import os
import openerp
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
class PointOfSaleController(openerp.addons.web.http.Controller):
_cp_path = '/pos'
class PointOfSaleController(http.Controller):
@openerp.addons.web.http.httprequest
def app(self, req, s_action=None, **kw):
js = "\n ".join('<script type="text/javascript" src="%s"></script>' % i for i in manifest_list(req, None, 'js'))
css = "\n ".join('<link rel="stylesheet" href="%s">' % i for i in manifest_list(req, None, 'css'))
@http.route('/pos/app', type='http', auth='admin')
def app(self):
js = "\n ".join('<script type="text/javascript" src="%s"></script>' % i for i in manifest_list('js',db=request.db))
css = "\n ".join('<link rel="stylesheet" href="%s">' % i for i in manifest_list('css',db=request.db))
cookie = request.httprequest.cookies.get("instance0|session_id")
session_id = cookie.replace("%22","")
template = html_template.replace('<html','<html manifest="/pos/manifest?session_id=%s"' % request.session_id)
template = html_template.replace('<html','<html manifest="/pos/manifest?session_id=%s"' % req.session_id)
r = template % {
'js': js,
'css': css,
'modules': simplejson.dumps(module_boot(req)),
'modules': simplejson.dumps(module_boot(request)),
'init': 'var wc = new s.web.WebClient();wc.appendTo($(document.body));'
}
return r
@openerp.addons.web.http.httprequest
def manifest(self, req, **kwargs):
@http.route('/pos/manifest',type='http', auth='admin')
def manifest(self):
""" This generates a HTML5 cache manifest files that preloads the categories and products thumbnails
and other ressources necessary for the point of sale to work offline """
@ -43,16 +47,16 @@ class PointOfSaleController(openerp.addons.web.http.Controller):
imgdir = openerp.modules.get_module_resource('point_of_sale','static/src/img');
load_css_img(imgdir,'/point_of_sale/static/src/img')
products = req.session.model('product.product')
for p in products.search_read([('pos_categ_id','!=',False)], ['name']):
products = request.registry.get('product.product')
for p in products.search_read(request.cr, request.uid, [('pos_categ_id','!=',False)], ['name']):
product_id = p['id']
url = "/web/binary/image?session_id=%s&model=product.product&field=image&id=%s" % (req.session_id, product_id)
url = "/web/binary/image?session_id=%s&model=product.product&field=image&id=%s" % (request.session_id, product_id)
ml.append(url)
categories = req.session.model('pos.category')
for c in categories.search_read([],['name']):
categories = request.registry.get('pos.category')
for c in categories.search_read(request.cr, request.uid, [], ['name']):
category_id = c['id']
url = "/web/binary/image?session_id=%s&model=pos.category&field=image&id=%s" % (req.session_id, category_id)
url = "/web/binary/image?session_id=%s&model=pos.category&field=image&id=%s" % (request.session_id, category_id)
ml.append(url)
ml += ["NETWORK:","*"]
@ -60,108 +64,103 @@ class PointOfSaleController(openerp.addons.web.http.Controller):
return m
@openerp.addons.web.http.jsonrequest
def dispatch(self, request, iface, **kwargs):
method = 'iface_%s' % iface
return getattr(self, method)(request, **kwargs)
@openerp.addons.web.http.jsonrequest
def scan_item_success(self, request, ean):
@http.route('/pos/scan_item_success', type='json', auth='admin')
def scan_item_success(self, ean):
"""
A product has been scanned with success
"""
print 'scan_item_success: ' + str(ean)
return
@openerp.addons.web.http.jsonrequest
def scan_item_error_unrecognized(self, request, ean):
@http.route('/pos/scan_item_error_unrecognized')
def scan_item_error_unrecognized(self, ean):
"""
A product has been scanned without success
"""
print 'scan_item_error_unrecognized: ' + str(ean)
return
@openerp.addons.web.http.jsonrequest
def help_needed(self, request):
@http.route('/pos/help_needed', type='json', auth='admin')
def help_needed(self):
"""
The user wants an help (ex: light is on)
"""
print "help_needed"
return
@openerp.addons.web.http.jsonrequest
def help_canceled(self, request):
@http.route('/pos/help_canceled', type='json', auth='admin')
def help_canceled(self):
"""
The user stops the help request
"""
print "help_canceled"
return
@openerp.addons.web.http.jsonrequest
def weighting_start(self, request):
@http.route('/pos/weighting_start', type='json', auth='admin')
def weighting_start(self):
print "weighting_start"
return
@openerp.addons.web.http.jsonrequest
def weighting_read_kg(self, request):
@http.route('/pos/weighting_read_kg', type='json', auth='admin')
def weighting_read_kg(self):
print "weighting_read_kg"
return 0.0
return 3.14
@openerp.addons.web.http.jsonrequest
def weighting_end(self, request):
@http.route('/pos/weighting_end', type='json', auth='admin')
def weighting_end(self):
print "weighting_end"
return
@openerp.addons.web.http.jsonrequest
def payment_request(self, request, price):
@http.route('/pos/payment_request', type='json', auth='admin')
def payment_request(self, price):
"""
The PoS will activate the method payment
"""
print "payment_request: price:"+str(price)
return 'ok'
@openerp.addons.web.http.jsonrequest
def payment_status(self, request):
@http.route('/pos/payment_status', type='json', auth='admin')
def payment_status(self):
print "payment_status"
return { 'status':'waiting' }
@openerp.addons.web.http.jsonrequest
def payment_cancel(self, request):
@http.route('/pos/payment_cancel', type='json', auth='admin')
def payment_cancel(self):
print "payment_cancel"
return
@openerp.addons.web.http.jsonrequest
def transaction_start(self, request):
@http.route('/pos/transaction_start', type='json', auth='admin')
def transaction_start(self):
print 'transaction_start'
return
@openerp.addons.web.http.jsonrequest
def transaction_end(self, request):
@http.route('/pos/transaction_end', type='json', auth='admin')
def transaction_end(self):
print 'transaction_end'
return
@openerp.addons.web.http.jsonrequest
def cashier_mode_activated(self, request):
@http.route('/pos/cashier_mode_activated', type='json', auth='admin')
def cashier_mode_activated(self):
print 'cashier_mode_activated'
return
@openerp.addons.web.http.jsonrequest
def cashier_mode_deactivated(self, request):
@http.route('/pos/cashier_mode_deactivated', type='json', auth='admin')
def cashier_mode_deactivated(self):
print 'cashier_mode_deactivated'
return
@openerp.addons.web.http.jsonrequest
def open_cashbox(self, request):
@http.route('/pos/open_cashbox', type='json', auth='admin')
def open_cashbox(self):
print 'open_cashbox'
return
@openerp.addons.web.http.jsonrequest
def print_receipt(self, request, receipt):
@http.route('/pos/print_receipt', type='json', auth='admin')
def print_receipt(self, receipt):
print 'print_receipt' + str(receipt)
return
@openerp.addons.web.http.jsonrequest
def print_pdf_invoice(self, request, pdfinvoice):
@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

@ -63,6 +63,7 @@ class pos_config(osv.osv):
'iface_electronic_scale' : fields.boolean('Electronic Scale Interface'),
'iface_vkeyboard' : fields.boolean('Virtual KeyBoard Interface'),
'iface_print_via_proxy' : fields.boolean('Print via Proxy'),
'iface_invoicing': fields.boolean('Invoicing',help='Enables invoice generation from the Point of Sale'),
'state' : fields.selection(POS_CONFIG_STATE, 'Status', required=True, readonly=True),
'sequence_id' : fields.many2one('ir.sequence', 'Order IDs Sequence', readonly=True,
@ -127,7 +128,8 @@ class pos_config(osv.osv):
'warehouse_id': _default_warehouse,
'journal_id': _default_sale_journal,
'group_by' : True,
'pricelist_id': _default_pricelist
'pricelist_id': _default_pricelist,
'iface_invoicing': True,
}
def set_active(self, cr, uid, ids, context=None):
@ -492,13 +494,17 @@ class pos_order(osv.osv):
#_logger.info("orders: %r", orders)
order_ids = []
for tmp_order in orders:
to_invoice = tmp_order['to_invoice']
order = tmp_order['data']
order_id = self.create(cr, uid, {
'name': order['name'],
'user_id': order['user_id'] or False,
'session_id': order['pos_session_id'],
'lines': order['lines'],
'pos_reference':order['name']
'pos_reference':order['name'],
'partner_id': order['partner_id'] or False
}, context)
for payments in order['statement_ids']:
@ -529,6 +535,12 @@ class pos_order(osv.osv):
}, context=context)
order_ids.append(order_id)
self.signal_paid(cr, uid, [order_id])
if to_invoice:
self.action_invoice(cr, uid, [order_id], context)
order_obj = self.browse(cr, uid, order_id, context)
self.pool['account.invoice'].signal_invoice_open(cr, uid, [order_obj.invoice_id.id])
return order_ids
def write(self, cr, uid, ids, vals, context=None):
@ -881,6 +893,7 @@ class pos_order(osv.osv):
inv_line_ref.create(cr, uid, inv_line, context=context)
inv_ref.button_reset_taxes(cr, uid, [inv_id], context=context)
self.signal_invoice(cr, uid, [order.id])
inv_ref.signal_validate(cr, uid, [inv_id])
if not inv_ids: return {}

View File

@ -794,6 +794,7 @@
<field name="iface_self_checkout" />
<field name="iface_cashdrawer" />
<field name="iface_payment_terminal" />
<field name="iface_invoicing" />
</group>
<group>
<field name="iface_electronic_scale" />

View File

@ -291,10 +291,15 @@
display: inline-block;
text-align: center;
vertical-align: top;
width: 205px;
max-height: 232px;
overflow-y: auto;
overflow-x: hidden;
}
.point-of-sale #paypad button {
height: 50px;
width: 208px;
display: block;
width: 100%;
margin: 0px -6px 4px -2px;
font-weight: bold;
vertical-align: middle;
@ -302,6 +307,17 @@
border-top: 1px solid #efefef;
font-size: 14px;
}
.point-of-sale #paypad button, .point-of-sale #numpad button, .point-of-sale .popup button{
position: relative;
top: 0;
-webkit-transition: top 150ms linear;
-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;
color: white;

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -267,11 +267,21 @@ function openerp_pos_db(instance, module){
return results;
},
add_order: function(order){
var last_id = this.load('last_order_id',0);
var order_id = order.uid;
var orders = this.load('orders',[]);
orders.push({id: last_id + 1, data: order});
this.save('last_order_id',last_id+1);
// if the order was already stored, we overwrite its data
for(var i = 0, len = orders.length; i < len; i++){
if(orders[i].id === order_id){
orders[i].data = order;
this.save('orders',orders);
return order_id;
}
}
orders.push({id: order_id, data: order});
this.save('orders',orders);
return order_id;
},
remove_order: function(order_id){
var orders = this.load('orders',[]);
@ -283,5 +293,14 @@ function openerp_pos_db(instance, module){
get_orders: function(){
return this.load('orders',[]);
},
get_order: function(order_id){
var orders = this.get_orders();
for(var i = 0, len = orders.length; i < len; i++){
if(orders[i].id === order_id){
return orders[i];
}
}
return undefined;
},
});
}

View File

@ -104,7 +104,6 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
return self.fetch('res.currency',['symbol','position','rounding','accuracy'],[['id','=',self.get('company').currency_id[0]]]);
}).then(function(currencies){
console.log('Currency:',currencies[0]);
self.set('currency',currencies[0]);
return self.fetch('product.uom', null, null);
@ -145,7 +144,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
['name','journal_ids','warehouse_id','journal_id','pricelist_id',
'iface_self_checkout', 'iface_led', 'iface_cashdrawer',
'iface_payment_terminal', 'iface_electronic_scale', 'iface_barscan', 'iface_vkeyboard',
'iface_print_via_proxy','iface_cashdrawer','state','sequence_id','session_ids'],
'iface_print_via_proxy','iface_cashdrawer','iface_invoicing','state','sequence_id','session_ids'],
[['id','=', self.get('pos_session').config_id[0]]]
);
}).then(function(configs){
@ -156,6 +155,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
self.iface_vkeyboard = !!pos_config.iface_vkeyboard;
self.iface_self_checkout = !!pos_config.iface_self_checkout;
self.iface_cashdrawer = !!pos_config.iface_cashdrawer;
self.iface_invoicing = !!pos_config.iface_invoicing;
return self.fetch('stock.warehouse',[],[['id','=',pos_config.warehouse_id[0]]]);
}).then(function(shops){
@ -240,12 +240,6 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
}
},
// saves the order locally and try to send it to the backend. 'record' is a bizzarely defined JSON version of the Order
push_order: function(record) {
this.db.add_order(record);
this.flush();
},
//creates a new empty order and sets it as the current order
add_new_order: function(){
var order = new module.Order({pos:this});
@ -253,45 +247,161 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
this.set('selectedOrder', order);
},
// 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;
var order_id = this.db.add_order(order.export_as_JSON());
var pushed = new $.Deferred();
this.set('nbr_pending_operations',self.db.get_orders().length);
this.flush_mutex.exec(function(){
var flushed = self._flush_all_orders();
flushed.always(function(){
pushed.resolve();
});
return flushed;
});
return pushed;
},
// saves the order locally and try to send it to the backend and make an invoice
// returns a deferred that succeeds when the order has been posted and successfully generated
// an invoice. This method can fail in various ways:
// error-no-client: the order must have an associated partner_id. You can retry to make an invoice once
// this error is solved
// error-transfer: there was a connection error during the transfer. You can retry to make the invoice once
// the network connection is up
push_and_invoice_order: function(order){
var self = this;
var invoiced = new $.Deferred();
if(!order.get_client()){
invoiced.reject('error-no-client');
return invoiced;
}
var order_id = this.db.add_order(order.export_as_JSON());
this.set('nbr_pending_operations',self.db.get_orders().length);
this.flush_mutex.exec(function(){
var done = new $.Deferred(); // holds the mutex
// send the order to the server
// we have a 30 seconds timeout on this push.
// FIXME: if the server takes more than 30 seconds to accept the order,
// the client will believe it wasn't successfully sent, and very bad
// things will happen as a duplicate will be sent next time
// so we must make sure the server detects and ignores duplicated orders
var transfer = self._flush_order(order_id, {timeout:30000, to_invoice:true});
transfer.fail(function(){
invoiced.reject('error-transfer');
done.reject();
});
// on success, get the order id generated by the server
transfer.pipe(function(order_server_id){
// generate the pdf and download it
self.pos_widget.do_action('point_of_sale.pos_invoice_report',{additional_context:{
active_ids:order_server_id,
}});
invoiced.resolve();
done.resolve();
});
return done;
});
return invoiced;
},
// attemps to send all pending orders ( stored in the pos_db ) to the server,
// and remove the successfully sent ones from the db once
// it has been confirmed that they have been sent correctly.
flush: function() {
//TODO make the mutex work
//this makes sure only one _int_flush is called at the same time
/*
return this.flush_mutex.exec(_.bind(function() {
return this._flush(0);
}, this));
*/
this._flush(0);
var self = this;
var flushed = new $.Deferred();
this.flush_mutex.exec(function(){
var done = new $.Deferred();
self._flush_all_orders()
.done( function(){ flushed.resolve();})
.fail( function(){ flushed.reject(); })
.always(function(){ done.resolve(); });
return done;
});
return flushed;
},
// attempts to send an order of index 'index' in the list of order to send. The index
// is used to skip orders that failed. do not call this method outside the mutex provided
// by flush()
_flush: function(index){
// attempts to send the locally stored order of id 'order_id'
// the sending is asynchronous and can take some time to decide if it is successful or not
// it is therefore important to only call this method from inside a mutex
// this method returns a deferred indicating wether the sending was successful or not
// there is a timeout parameter which is set to 2 seconds by default.
_flush_order: function(order_id, options){
var self = this;
options = options || {};
timeout = typeof options.timeout === 'number' ? options.timeout : 5000;
var order = this.db.get_order(order_id);
order.to_invoice = options.to_invoice || false;
if(!order){
// flushing a non existing order always fails
return (new $.Deferred()).reject();
}
// we try to send the order. shadow prevents a spinner if it takes too long. (unless we are sending an invoice,
// then we want to notify the user that we are waiting on something )
var rpc = (new instance.web.Model('pos.order')).call('create_from_ui',[[order]],undefined,{shadow: !options.to_invoice, timeout:timeout});
rpc.fail(function(unused,event){
// prevent an error popup creation by the rpc failure
// we want the failure to be silent as we send the orders in the background
event.preventDefault();
console.error('Failed to send order:',order);
});
rpc.done(function(){
self.db.remove_order(order_id);
self.set('nbr_pending_operations',self.db.get_orders().length);
});
return rpc;
},
// attempts to send all the locally stored orders. As with _flush_order, it should only be
// called from within a mutex.
// this method returns a deferred that always succeeds when all orders have been tried to be sent,
// even if none of them could actually be sent.
_flush_all_orders: function(){
var self = this;
var orders = this.db.get_orders();
self.set('nbr_pending_operations',orders.length);
var tried_all = new $.Deferred();
var order = orders[index];
if(!order){
return;
function rec_flush(index){
if(index < orders.length){
self._flush_order(orders[index].id).always(function(){
rec_flush(index+1);
})
}else{
tried_all.resolve();
}
}
//try to push an order to the server
// shadow : true is to prevent a spinner to appear in case of timeout
(new instance.web.Model('pos.order')).call('create_from_ui',[[order]],undefined,{ shadow:true })
.fail(function(unused, event){
//don't show error popup if it fails
event.preventDefault();
console.error('Failed to send order:',order);
self._flush(index+1);
})
.done(function(){
//remove from db if success
self.db.remove_order(order.id);
self._flush(index);
});
rec_flush(0);
return tried_all;
},
scan_product: function(parsed_ean){
@ -590,11 +700,12 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
module.Order = Backbone.Model.extend({
initialize: function(attributes){
Backbone.Model.prototype.initialize.apply(this, arguments);
this.uid = this.generateUniqueId();
this.set({
creationDate: new Date(),
orderLines: new module.OrderlineCollection(),
paymentLines: new module.PaymentlineCollection(),
name: "Order " + this.generateUniqueId(),
name: "Order " + this.uid,
client: null,
});
this.pos = attributes.pos;
@ -770,7 +881,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
currency: this.pos.get('currency'),
};
},
exportAsJSON: function() {
export_as_JSON: function() {
var orderLines, paymentLines;
orderLines = [];
(this.get('orderLines')).each(_.bind( function(item) {
@ -789,8 +900,9 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
lines: orderLines,
statement_ids: paymentLines,
pos_session_id: this.pos.get('pos_session').id,
partner_id: this.pos.get('client') ? this.pos.get('client').id : undefined,
partner_id: this.get_client() ? this.get_client().id : false,
user_id: this.pos.get('cashier') ? this.pos.get('cashier').id : this.pos.get('user').id,
uid: this.uid,
};
},
getSelectedLine: function(){

View File

@ -434,6 +434,14 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
template:'ErrorNegativePricePopupWidget',
});
module.ErrorNoClientPopupWidget = module.ErrorPopupWidget.extend({
template: 'ErrorNoClientPopupWidget',
});
module.ErrorInvoiceTransferPopupWidget = module.ErrorPopupWidget.extend({
template: 'ErrorInvoiceTransferPopupWidget',
});
module.ScaleInviteScreenWidget = module.ScreenWidget.extend({
template:'ScaleInviteScreenWidget',
@ -452,7 +460,7 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
clearInterval(this.intervalID);
self.pos_widget.screen_selector.set_current_screen(self.next_screen);
}
},500);
},100);
this.add_action_button({
label: _t('Back'),
@ -507,7 +515,7 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
self.weight = weight;
self.renderElement();
}
},200);
},100);
},
renderElement: function(){
var self = this;
@ -640,7 +648,7 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
var cashregister = selfCheckoutRegisters[0] || self.pos.get('cashRegisters').models[0];
currentOrder.addPaymentLine(cashregister);
self.pos.push_order(currentOrder.exportAsJSON())
self.pos.push_order(currentOrder)
currentOrder.destroy();
self.pos.proxy.transaction_end();
self.pos_widget.screen_selector.set_current_screen(self.next_screen);
@ -808,19 +816,42 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
this._super();
var self = this;
this.add_action_button({
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(); },
});
this.add_action_button({
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(); },
});
window.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
// screen before the printing dialog is opened. The problem is that what's
// printed is whatever is in the page when the dialog is opened and not when it's called,
// and so you end up printing the product list instead of the receipt...
//
// Fixing this would need a re-architecturing
// of the code to postpone sending of orders after printing.
//
// But since the print dialog also blocks the other asynchronous calls, the
// button enabling in the setTimeout() is blocked until the printing dialog is
// closed. But the timeout has to be big enough or else it doesn't work
// 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);
setTimeout(function(){
finish_button.set_disabled(false);
}, 2000);
},
print: function() {
window.print();
@ -870,15 +901,15 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
this.set_numpad_state(this.pos_widget.numpad.state);
this.back_button = this.add_action_button({
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.back_screen);
},
});
this.validate_button = this.add_action_button({
this.add_action_button({
label: _t('Validate'),
name: 'validation',
icon: '/point_of_sale/static/src/img/icons/png48/validate.png',
@ -886,6 +917,17 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
self.validateCurrentOrder();
},
});
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});
},
});
}
this.updatePaymentSummary();
this.line_refocus();
@ -898,15 +940,44 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
back: function() {
this.pos_widget.screen_selector.set_current_screen(self.back_screen);
},
validateCurrentOrder: function() {
validateCurrentOrder: function(options) {
var self = this;
options = options || {};
var currentOrder = this.pos.get('selectedOrder');
this.pos.push_order(currentOrder.exportAsJSON())
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
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);
var invoiced = this.pos.push_and_invoice_order(currentOrder);
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.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();
});
}else{
this.pos_widget.screen_selector.set_current_screen(this.next_screen);
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{
this.pos_widget.screen_selector.set_current_screen(this.next_screen);
}
}
},
bindPaymentLineEvents: function() {
@ -985,6 +1056,7 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
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) {

View File

@ -838,6 +838,7 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
instance.web.blockUI();
this.pos = new module.PosModel(this.session);
this.pos.pos_widget = this;
this.pos_widget = this; //So that pos_widget's childs have pos_widget set automatically
this.numpad_visible = true;
@ -952,6 +953,12 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
this.error_negative_price_popup = new module.ErrorNegativePricePopupWidget(this, {});
this.error_negative_price_popup.appendTo($('.point-of-sale'));
this.error_no_client_popup = new module.ErrorNoClientPopupWidget(this, {});
this.error_no_client_popup.appendTo($('.point-of-sale'));
this.error_invoice_transfer_popup = new module.ErrorInvoiceTransferPopupWidget(this, {});
this.error_invoice_transfer_popup.appendTo($('.point-of-sale'));
// -------- Misc ---------
this.notification = new module.SynchNotificationWidget(this,{});
@ -1013,6 +1020,8 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
'error-session': this.error_session_popup,
'error-negative-price': this.error_negative_price_popup,
'choose-receipt': this.choose_receipt_popup,
'error-no-client': this.error_no_client_popup,
'error-invoice-transfer': this.error_invoice_transfer_popup,
},
default_client_screen: 'welcome',
default_cashier_screen: 'products',

View File

@ -336,11 +336,11 @@
<div class="modal-dialog">
<div class="popup popup-help">
<p class="message">The scanned product was not recognized<br /> Please wait, a cashier is on the way</p>
</div>
</div>
<div class="footer">
<div class="button">
Ok
<div class="footer">
<div class="button">
Ok
</div>
</div>
</div>
</div>
</t>
@ -361,6 +361,33 @@
</div>
</t>
<t t-name="ErrorNoClientPopupWidget">
<div class="modal-dialog">
<div class="popup popup-help">
<p class="message">An anonymous order cannot be invoiced</p>
<div class="footer">
<div class="button">
Ok
</div>
</div>
</div>
</div>
</t>
<t t-name="ErrorInvoiceTransferPopupWidget">
<div class="modal-dialog">
<div class="popup popup-help">
<p class="message">The Order could not be sent to the server for invoicing. Invoices cannot be generated
in offline mode. Please check your internet connection and try again.</p>
<div class="footer">
<div class="button">
Ok
</div>
</div>
</div>
</div>
</t>
<t t-name="ErrorPopupWidget">
<div class="modal-dialog">
<div class="popup popup-help">
@ -538,7 +565,6 @@
<button class="paypad-button" t-att-cash-register-id="widget.cashRegister.get('id')">
<t t-esc="widget.cashRegister.get('journal').name"/>
</button>
<br />
</t>
<t t-name="OrderButtonWidget">