[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:
parent
cae96ed454
commit
3aa6f4add5
|
@ -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 {}
|
||||
|
||||
|
|
|
@ -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;
|
||||
},
|
||||
|
|
|
@ -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(){
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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">
|
||||
|
|
Loading…
Reference in New Issue