summaryrefslogtreecommitdiff
path: root/addons
diff options
context:
space:
mode:
authorFrédéric van der Essen <fva@openerp.com>2013-07-12 14:54:19 +0200
committerFrédéric van der Essen <fva@openerp.com>2013-07-12 14:54:19 +0200
commit4c96a05dbbffd80b07401fd0b7fc6950cd83ab3d (patch)
tree4f0a4dcc664c268f0167f2b58d481ee6a192790d /addons
parent050b6b0ec523c1952d645a7db94580a5eec3536a (diff)
parent3c4455981105e4af72cedd8bca9a6abb76bb2ce1 (diff)
[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
Diffstat (limited to 'addons')
-rw-r--r--addons/point_of_sale/controllers/main.py111
-rw-r--r--addons/point_of_sale/point_of_sale.py17
-rw-r--r--addons/point_of_sale/point_of_sale_view.xml1
-rw-r--r--addons/point_of_sale/static/src/css/pos.css18
-rw-r--r--addons/point_of_sale/static/src/img/icons/png48/invoice.pngbin0 -> 2086 bytes
-rw-r--r--addons/point_of_sale/static/src/js/db.js25
-rw-r--r--addons/point_of_sale/static/src/js/models.js194
-rw-r--r--addons/point_of_sale/static/src/js/screens.js100
-rw-r--r--addons/point_of_sale/static/src/js/widgets.js9
-rw-r--r--addons/point_of_sale/static/src/xml/pos.xml38
10 files changed, 390 insertions, 123 deletions
diff --git a/addons/point_of_sale/controllers/main.py b/addons/point_of_sale/controllers/main.py
index a4a06e94eaa..db5d5a25018 100644
--- a/addons/point_of_sale/controllers/main.py
+++ b/addons/point_of_sale/controllers/main.py
@@ -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
diff --git a/addons/point_of_sale/point_of_sale.py b/addons/point_of_sale/point_of_sale.py
index 126b4b40a07..efe8cd98689 100644
--- a/addons/point_of_sale/point_of_sale.py
+++ b/addons/point_of_sale/point_of_sale.py
@@ -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 {}
diff --git a/addons/point_of_sale/point_of_sale_view.xml b/addons/point_of_sale/point_of_sale_view.xml
index dbd5e731db1..f87fc9603a8 100644
--- a/addons/point_of_sale/point_of_sale_view.xml
+++ b/addons/point_of_sale/point_of_sale_view.xml
@@ -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" />
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
--- /dev/null
+++ b/addons/point_of_sale/static/src/img/icons/png48/invoice.png
Binary files 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 @@
<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">