[IMP] point_of_sale: the invoicing should now work reliably and validate the orders

bzr revid: fva@openerp.com-20130411120719-colzjq4l8eaiotdf
This commit is contained in:
Frédéric van der Essen 2013-04-11 14:07:19 +02:00
parent cae96ed454
commit 3aa6f4add5
6 changed files with 206 additions and 43 deletions

View File

@ -483,7 +483,10 @@ 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,
@ -522,6 +525,13 @@ class pos_order(osv.osv):
order_ids.append(order_id)
wf_service = netsvc.LocalService("workflow")
wf_service.trg_validate(uid, 'pos.order', order_id, 'paid', cr)
if to_invoice:
self.action_invoice(cr, uid, [order_id], context)
order_obj = self.browse(cr, uid, order_id, context)
wf_service = netsvc.LocalService('workflow')
wf_service.trg_validate(uid,'account.invoice', order_obj.invoice_id.id,'invoice_open',cr)
return order_ids
def unlink(self, cr, uid, ids, context=None):
@ -859,6 +869,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)
wf_service.trg_validate(uid, 'pos.order', order.id, 'invoice', cr)
wf_service.trg_validate(uid, 'account.invoice', inv_id, 'validate', cr)
if not inv_ids: return {}

View File

@ -267,10 +267,19 @@ function openerp_pos_db(instance, module){
return results;
},
add_order: function(order){
var order_id = this.load('last_order_id',0) + 1;
var order_id = order.uid;
var orders = this.load('orders',[]);
// 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('last_order_id', order_id);
this.save('orders',orders);
return order_id;
},

View File

@ -262,36 +262,80 @@ 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. 'record' is a bizzarely defined JSON version of the Order
// it returns a deferred that succeeds or fail when the pushed order is successfully posted on the server, previously failed orders
// will try to be re-sent but don't make this method return false if they fail. ( So that in the unlikely case of an order making
// the server crash, the other orders can still be sent )
// payment process )
push_order: function(record) {
// 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(record);
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();
//first we try to push all orders (the one we added and the one that were not sent)
var tried_all = self._flush_all_orders();
var done = new $.Deferred();
flushed.always(function(){
pushed.resolve();
});
tried_all.always(function(){
// then we verify that the one we just added has been sent successfuly.
self._flush_order(order_id)
.done( function(){ pushed.resolve();})
.fail( function(){ pushed.reject(); })
.always( function(){ done.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 pushed;
return invoiced;
},
// attemps to send all pending orders ( stored in the pos_db ) to the server,
@ -316,23 +360,30 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
},
// attempts to send the locally stored order of id 'order_id'
// the sending is asynchronous and can take a long time to decide if it is successful or not (60s)
// 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
_flush_order: function(order_id){
// 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 succeeds
return (new $.Deferred()).resolve();
// 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.
var rpc = (new instance.web.Model('pos.order')).call('create_from_ui',[[order]],undefined,{shadow: true, timeout: 2000});
// 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
// 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);
});
@ -664,11 +715,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;
@ -844,7 +896,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) {
@ -865,6 +917,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
pos_session_id: this.pos.get('pos_session').id,
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

@ -433,6 +433,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',
@ -639,7 +647,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);
@ -807,19 +815,40 @@ 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: '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: '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 this is not reliable ... if the timeout is too slow it doesn't work
finish_button.set_disabled(true);
setTimeout(function(){
finish_button.set_disabled(false);
}, 2000);
},
print: function() {
window.print();
@ -896,7 +925,7 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
});
this.updatePaymentSummary();
this.line_refocus();
this.line_refocus();this
},
close: function(){
this._super();
@ -907,18 +936,43 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
this.pos_widget.screen_selector.set_current_screen(self.back_screen);
},
validateCurrentOrder: function(options) {
var self = this;
options = options || {};
var currentOrder = this.pos.get('selectedOrder');
this.pos.push_order(currentOrder.exportAsJSON())
if(options.invoice){
console.log('send invoice');
}else 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
// 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() {

View File

@ -828,6 +828,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;
@ -942,6 +943,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,{});
@ -1003,6 +1010,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">