diff --git a/addons/account/static/description/index.html b/addons/account/static/description/index.html
index 09a58fb3246..d9b26f11d16 100644
--- a/addons/account/static/description/index.html
+++ b/addons/account/static/description/index.html
@@ -17,7 +17,7 @@ By far the most beautiful and full featured accounting software. OpenERP Account
Activate features on demand, from integrated analytic accounting to budget, assets and multiple companies consolidation.
diff --git a/addons/account_voucher/static/description/index.html b/addons/account_voucher/static/description/index.html
index d59c110dde5..a363291f12e 100644
--- a/addons/account_voucher/static/description/index.html
+++ b/addons/account_voucher/static/description/index.html
@@ -17,7 +17,7 @@
automatically based on your activities.
diff --git a/addons/crm/static/description/index.html b/addons/crm/static/description/index.html
index 5ae542f5c91..70f52d0ca87 100644
--- a/addons/crm/static/description/index.html
+++ b/addons/crm/static/description/index.html
@@ -17,7 +17,7 @@
Manage your sales funnel with no effort. Attract leads, follow-up on phone calls and meetings. Analyse the quality of your leads to make informed decisions and save time by integrating emails directly into the application.
diff --git a/addons/hr/static/description/index.html b/addons/hr/static/description/index.html
index 0bc3db084a4..f97cb462fe3 100644
--- a/addons/hr/static/description/index.html
+++ b/addons/hr/static/description/index.html
@@ -17,7 +17,7 @@ Get all your HR operations managed easily: knowledge sharing, recruitments, appr
Each need is provided by a specific app that you activate on demand.
diff --git a/addons/mrp/static/description/index.html b/addons/mrp/static/description/index.html
index 5777eb5f147..d44da3f37b1 100644
--- a/addons/mrp/static/description/index.html
+++ b/addons/mrp/static/description/index.html
@@ -20,7 +20,7 @@ planning with the smart kanban and gantt views. Use the advanced analytics
features to detect bottleneck in resources capacities and inventory locations.
diff --git a/addons/note/static/description/index.html b/addons/note/static/description/index.html
index 7ef37c646e9..dc688859334 100644
--- a/addons/note/static/description/index.html
+++ b/addons/note/static/description/index.html
@@ -15,7 +15,7 @@
Organize yourself with efficient todo lists and notes. From personnal tasks to collaborative meeting minutes, increase your user's productivity by giving them the tools to prioritize their work, share their ideas and collaborate on documents.
diff --git a/addons/point_of_sale/static/src/css/pos.css b/addons/point_of_sale/static/src/css/pos.css
index d9a4d6d00a9..e328054aa28 100644
--- a/addons/point_of_sale/static/src/css/pos.css
+++ b/addons/point_of_sale/static/src/css/pos.css
@@ -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;
diff --git a/addons/point_of_sale/static/src/img/icons/png48/invoice.png b/addons/point_of_sale/static/src/img/icons/png48/invoice.png
new file mode 100644
index 00000000000..e65d2ec5a67
Binary files /dev/null and b/addons/point_of_sale/static/src/img/icons/png48/invoice.png differ
diff --git a/addons/point_of_sale/static/src/js/db.js b/addons/point_of_sale/static/src/js/db.js
index e47ce66f648..d16ba5a53dd 100644
--- a/addons/point_of_sale/static/src/js/db.js
+++ b/addons/point_of_sale/static/src/js/db.js
@@ -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;
+ },
});
}
diff --git a/addons/point_of_sale/static/src/js/models.js b/addons/point_of_sale/static/src/js/models.js
index 8cb88303dd1..8375377b31c 100644
--- a/addons/point_of_sale/static/src/js/models.js
+++ b/addons/point_of_sale/static/src/js/models.js
@@ -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(){
diff --git a/addons/point_of_sale/static/src/js/screens.js b/addons/point_of_sale/static/src/js/screens.js
index 7a01732c353..e0a8ba4b71c 100644
--- a/addons/point_of_sale/static/src/js/screens.js
+++ b/addons/point_of_sale/static/src/js/screens.js
@@ -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) {
diff --git a/addons/point_of_sale/static/src/js/widgets.js b/addons/point_of_sale/static/src/js/widgets.js
index 5d56943de73..911d8cd321b 100644
--- a/addons/point_of_sale/static/src/js/widgets.js
+++ b/addons/point_of_sale/static/src/js/widgets.js
@@ -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',
diff --git a/addons/point_of_sale/static/src/xml/pos.xml b/addons/point_of_sale/static/src/xml/pos.xml
index 3da76ec12d8..e28ea1c06b0 100644
--- a/addons/point_of_sale/static/src/xml/pos.xml
+++ b/addons/point_of_sale/static/src/xml/pos.xml
@@ -336,11 +336,11 @@
The scanned product was not recognized Please wait, a cashier is on the way
-
-
-
@@ -361,6 +361,33 @@
+
+
+
+
An anonymous order cannot be invoiced
+
+
+
+
+
+
+
+
+
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.
diff --git a/addons/sale/static/description/index.html b/addons/sale/static/description/index.html
index 1eb6215db2f..52c84b82f12 100644
--- a/addons/sale/static/description/index.html
+++ b/addons/sale/static/description/index.html
@@ -17,7 +17,7 @@ you need, easily accessible. Keep track of long term contracts, automate invoici
and notify sales when they have things to do.
diff --git a/addons/stock/static/description/index.html b/addons/stock/static/description/index.html
index aac71e529ae..dd7fbe568f7 100644
--- a/addons/stock/static/description/index.html
+++ b/addons/stock/static/description/index.html
@@ -17,7 +17,7 @@
Decrease your process times, automate transactions, reduce your stock levels and get complete traceability on all operations with the OpenERP double entry inventory system.