diff options
| author | Frédéric van der Essen <fva@openerp.com> | 2013-07-12 14:54:19 +0200 |
|---|---|---|
| committer | Frédéric van der Essen <fva@openerp.com> | 2013-07-12 14:54:19 +0200 |
| commit | 4c96a05dbbffd80b07401fd0b7fc6950cd83ab3d (patch) | |
| tree | 4f0a4dcc664c268f0167f2b58d481ee6a192790d /addons | |
| parent | 050b6b0ec523c1952d645a7db94580a5eec3536a (diff) | |
| parent | 3c4455981105e4af72cedd8bca9a6abb76bb2ce1 (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.py | 111 | ||||
| -rw-r--r-- | addons/point_of_sale/point_of_sale.py | 17 | ||||
| -rw-r--r-- | addons/point_of_sale/point_of_sale_view.xml | 1 | ||||
| -rw-r--r-- | addons/point_of_sale/static/src/css/pos.css | 18 | ||||
| -rw-r--r-- | addons/point_of_sale/static/src/img/icons/png48/invoice.png | bin | 0 -> 2086 bytes | |||
| -rw-r--r-- | addons/point_of_sale/static/src/js/db.js | 25 | ||||
| -rw-r--r-- | addons/point_of_sale/static/src/js/models.js | 194 | ||||
| -rw-r--r-- | addons/point_of_sale/static/src/js/screens.js | 100 | ||||
| -rw-r--r-- | addons/point_of_sale/static/src/js/widgets.js | 9 | ||||
| -rw-r--r-- | addons/point_of_sale/static/src/xml/pos.xml | 38 |
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 Binary files differnew file mode 100644 index 00000000000..e65d2ec5a67 --- /dev/null +++ b/addons/point_of_sale/static/src/img/icons/png48/invoice.png 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"> |
