From 6624bf9675a54e93be5983163722e183b8e3cefd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20van=20der=20Essen?= Date: Wed, 27 Mar 2013 15:14:26 +0100 Subject: [PATCH 01/11] [FIX] point_of_sale: add scrollbar when there are too much payment methods bzr revid: fva@openerp.com-20130327141426-ivgfxlsxlpazehar --- addons/point_of_sale/static/src/css/pos.css | 7 ++++++- addons/point_of_sale/static/src/xml/pos.xml | 1 - 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/addons/point_of_sale/static/src/css/pos.css b/addons/point_of_sale/static/src/css/pos.css index d9a4d6d00a9..16355240f25 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; diff --git a/addons/point_of_sale/static/src/xml/pos.xml b/addons/point_of_sale/static/src/xml/pos.xml index 3da76ec12d8..1dce368b9cb 100644 --- a/addons/point_of_sale/static/src/xml/pos.xml +++ b/addons/point_of_sale/static/src/xml/pos.xml @@ -538,7 +538,6 @@ -
From 28ed62fd7e22fe79d856926e6ce91aba5f0dd429 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20van=20der=20Essen?= Date: Wed, 27 Mar 2013 16:15:18 +0100 Subject: [PATCH 02/11] [IMP] point_of_sale: adding visual feedback when clicking on buttons bzr revid: fva@openerp.com-20130327151518-t4h67hqn02s80tai --- addons/point_of_sale/static/src/css/pos.css | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/addons/point_of_sale/static/src/css/pos.css b/addons/point_of_sale/static/src/css/pos.css index 16355240f25..e328054aa28 100644 --- a/addons/point_of_sale/static/src/css/pos.css +++ b/addons/point_of_sale/static/src/css/pos.css @@ -307,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; From 74b3538dd17b291c47fcb5697484506c66d6429d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20van=20der=20Essen?= Date: Wed, 27 Mar 2013 16:40:54 +0100 Subject: [PATCH 03/11] [IMP] point_of_sale: add an invoice button (does nothing yet) bzr revid: fva@openerp.com-20130327154054-nhfy6ak73v1gwj09 --- .../static/src/img/icons/png48/invoice.png | Bin 0 -> 2086 bytes addons/point_of_sale/static/src/js/screens.js | 18 ++++++++++++++++-- 2 files changed, 16 insertions(+), 2 deletions(-) create mode 100644 addons/point_of_sale/static/src/img/icons/png48/invoice.png 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 0000000000000000000000000000000000000000..e65d2ec5a67398a6aa245c89d66355ae3e5a0d46 GIT binary patch literal 2086 zcmV+>2-)|EP)3$g6o3^(HlyMaw}_JX0}^BWW6(b){xLDe#69s3KTTXhkf3a>RIF5r#a2ppT9Cp( zX**@6oo>(~2BX!1(q-Cdk)@Sx)0w_5vrp%E&h6>Dym>5nbq1ri`7+a)dGDR?oOADe zGj{;Mf13ZVKy!1=qbHi{iyE71{S7B=K-8b8(NNb|t)aHTs$#1rEGjj1$B9T?qxFid z!D_SCR;{&KtxFOn(AZ?l86Fv$hB%IJBn)2P6bug!z}Yst-&R*WQC(+FZf00aVl@C$t4^LxSP^NK*Gyu>6-PE8==Fflm};}Z%p2Fkbx#t@I<2G7_i zxRFth3&s==p2LUd4T2m0x1VqESJzhkQr1{@Ut9%h8>++jD1!F;IdB96Cn#`z@RN!V zP-X=bLJCm8qwW!KxrgyJHw1$Lah#*OJ!G+&`z)5S6>$^5z7+y8Xc@SG0%tG?L;;z7 zf`cb!gBQ#Q3=aPVSNbkN043-cXRl+rs=8dMtSoyeE&|oH$A!R@ZzkXi7cM}1dwbky zYiol}M>`A-4UmM8?ETXTW=pxZqT z(BWtk0yM-D>lzXgzx?)d;8>;U=$r-2mSZHKwntBoGwv0z+wE}r^l3PC>JKkgj2M_Q6U`_%RRYyrcW|$e%AhRwrKT4SwxCPkzoP7aT0Kp*p!P(OV zzuSL<=ATZ2#aac2O{HM2s({{$Jz_qXj+OQUz@3H#um@2f3JR^|h%*5dXl-qUGiS~~ zeSJNc&1NV)REh$JplDwK6c!c0#Y??VQ+s^kK3=5Q1m`FfwMk<Am zM`izO=KwT6}YBZfX0Y{FRQ0O3>J8y@Bhl&pv7C2gdC>$jL z4wNg9a5_6X#T*zK8Gyd4mua&@fs)IH1x#h7BtRLaYzjOQ{r?(V-1`Dq1^4V6kbA+1 zUx!xAh|Se$2E0M}j?a3#C~ z-2c=*ETAF;@{4ke3+(@~P$MAFXPV%BK<*6$?-zvJCloz=en!7<&_a?|xYxMAz5@lC z7V7G9+>TXrkF`$>Uu~a8hiF;Mp9A? z3gqPNApz=1UEunlwE@%pix&Dt<0jzyF~_LqqLG!o+qgh>ZWi4OsgsevktH05!$AV7 z6phRuGK>rC$;l)E%D5)$+}FBn%Z!R2z!)F+iNGxW8{m^apvC-%Mq_u@F5?1NwlD|* zu}WYCdTv_B`!s2K3NF*Ydo|Z(22q8WbBQhgFW9P0N#s$9Hy%PmM73k^hxgFn! zDFM`4Y9(l-?MyW;ke-po0=iX4_C8NG+4s8aQ+7G?(=Z*K7PrSV=#dbwb0uUIv;sT6 z{o1$ymW^9TSAm4n-QBGbNKO07m;jDD)6!E(;1(;8S6hW3!Yhxgf}96j4T-M*^%I%K z*WYZ5tY5!=@y!J|aAy9FZ>7Na+*cvGi>01s89gCQ;DmN5rIC`F99gqw%~FhO9>u2@ z;NbZKcOi>X)3yr%1qE)s1AA}27x3KB5gW7&T-}*zZ2KxHvU>IE2QjX@DY-167vSI* zBM*F&w%tn;i~?75w-Gnk(5@BH%CHc_fvN;JXc@S=JV)^_Ib~~j*|KHJF|K=&g;GK* zFpmxILzaKOX@jNp{JAI&_%}zlvkTUJ_Ng6XdJcI6S;C3d3$We&$WyDHUG?_bb)WpY zZF^GW%apCaU^ccu^0pb01e+o0%Ne#3$(vNR5J?+VHWORF5Nsm0e6F&Q*t|jT1%uC{ zAAS5`$I6u}*J4c1Bab3WZziySKkzW}4Dtf<3i2BA2J$BIF7h7pe%z4fe21^|I`S&= zG6(-8vJ6>t{TAZYhudfgSK=YIewhpD;Kpx|^d64^mF=u~XjDUV6 zVv&VxyqKi8I`<@)rOXn(?jnw30Vh}uUJB?WSVS^kf^vyvz7~#w 0); + this.pos_widget.action_bar.set_button_disabled('invoice', remaining > 0); } }, set_numpad_state: function(numpadState) { From 0fdf9066ddb12b5058b055433b9b98e62189c83a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20van=20der=20Essen?= Date: Thu, 28 Mar 2013 16:07:18 +0100 Subject: [PATCH 04/11] [FIX] point_of_sale: fixing potential timeout issues when sending orders over bad connection bzr revid: fva@openerp.com-20130328150718-zj91ljrlkvv59tkj --- addons/point_of_sale/static/src/js/db.js | 16 ++- addons/point_of_sale/static/src/js/models.js | 133 +++++++++++++------ 2 files changed, 109 insertions(+), 40 deletions(-) diff --git a/addons/point_of_sale/static/src/js/db.js b/addons/point_of_sale/static/src/js/db.js index a79b111b5c5..c28c08950db 100644 --- a/addons/point_of_sale/static/src/js/db.js +++ b/addons/point_of_sale/static/src/js/db.js @@ -267,11 +267,12 @@ function openerp_pos_db(instance, module){ return results; }, add_order: function(order){ - var last_id = this.load('last_order_id',0); + var order_id = this.load('last_order_id',0) + 1; var orders = this.load('orders',[]); - orders.push({id: last_id + 1, data: order}); - this.save('last_order_id',last_id+1); + orders.push({id: order_id, data: order}); + this.save('last_order_id', order_id); this.save('orders',orders); + return order_id; }, remove_order: function(order_id){ var orders = this.load('orders',[]); @@ -283,5 +284,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 660434ba53e..ac86ee852e0 100644 --- a/addons/point_of_sale/static/src/js/models.js +++ b/addons/point_of_sale/static/src/js/models.js @@ -120,7 +120,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); @@ -256,12 +255,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}); @@ -269,45 +262,111 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal this.set('selectedOrder', order); }, + // saves the order locally and try to send it to the backend. 'record' is a bizzarely defined JSON version of the Order + // it returns a deferred that succeeds or fail when the pushed order is successfully posted on the server, previously failed orders + // will try to be re-sent but don't make this method return false if they fail. ( So that in the unlikely case of an order making + // the server crash, the other orders can still be sent ) + // payment process ) + push_order: function(record) { + var self = this; + var order_id = this.db.add_order(record); + var pushed = new $.Deferred(); + + this.set('nbr_pending_operations',self.db.get_orders().length); + + this.flush_mutex.exec(function(){ + + //first we try to push all orders (the one we added and the one that were not sent) + var tried_all = self._flush_all_orders(); + var done = new $.Deferred(); + + tried_all.always(function(){ + // then we verify that the one we just added has been sent successfuly. + self._flush_order(order_id) + .done( function(){ pushed.resolve();}) + .fail( function(){ pushed.reject(); }) + .always( function(){ done.resolve(); }); + }); + + return done; + }); + + return pushed; + }, + + // 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 a long time to decide if it is successful or not (60s) + // it is therefore important to only call this method from inside a mutex + // this method returns a deferred indicating wether the sending was successful or not + _flush_order: function(order_id){ + var self = this; + var order = this.db.get_order(order_id); + + if(!order){ + // flushing a non existing order always succeeds + return (new $.Deferred()).resolve(); + } + + //we try to send the order. shadow prevents a spinner if it takes too long. + var rpc = (new instance.web.Model('pos.order')).call('create_from_ui',[[order]],undefined,{shadow: true, timeout: 2000}); + + rpc.fail(function(unused,event){ + //prevent an error popup creation by the rpc failure + 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){ From 96a74ae6ad1872a519cbe70d07fe2fbc00b40cf4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20van=20der=20Essen?= Date: Thu, 28 Mar 2013 16:35:37 +0100 Subject: [PATCH 05/11] [IMP] point_of_sale: ... bzr revid: fva@openerp.com-20130328153537-ohdc3243lcu3dgbj --- addons/point_of_sale/static/src/js/models.js | 1 - 1 file changed, 1 deletion(-) diff --git a/addons/point_of_sale/static/src/js/models.js b/addons/point_of_sale/static/src/js/models.js index ac86ee852e0..6a9d54ce608 100644 --- a/addons/point_of_sale/static/src/js/models.js +++ b/addons/point_of_sale/static/src/js/models.js @@ -294,7 +294,6 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal return pushed; }, - // 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. From cae96ed4543abc9f05dbc4398a1832a1468639c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20van=20der=20Essen?= Date: Fri, 29 Mar 2013 12:00:48 +0100 Subject: [PATCH 06/11] [FIX] point_of_sale: correctly set the customer on the order bzr revid: fva@openerp.com-20130329110048-1cx2sr4hp98cue0f --- addons/point_of_sale/point_of_sale.py | 3 ++- addons/point_of_sale/static/src/js/models.js | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/addons/point_of_sale/point_of_sale.py b/addons/point_of_sale/point_of_sale.py index 44139f7a499..9ee5b944718 100644 --- a/addons/point_of_sale/point_of_sale.py +++ b/addons/point_of_sale/point_of_sale.py @@ -489,7 +489,8 @@ class pos_order(osv.osv): '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']: diff --git a/addons/point_of_sale/static/src/js/models.js b/addons/point_of_sale/static/src/js/models.js index 6a9d54ce608..426bea9a9b7 100644 --- a/addons/point_of_sale/static/src/js/models.js +++ b/addons/point_of_sale/static/src/js/models.js @@ -330,7 +330,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal //we try to send the order. shadow prevents a spinner if it takes too long. var rpc = (new instance.web.Model('pos.order')).call('create_from_ui',[[order]],undefined,{shadow: true, timeout: 2000}); - + rpc.fail(function(unused,event){ //prevent an error popup creation by the rpc failure event.preventDefault(); @@ -863,7 +863,7 @@ 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, }; }, From 3aa6f4add577b153df2518f8681d0fbe2a84e8a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20van=20der=20Essen?= Date: Thu, 11 Apr 2013 14:07:19 +0200 Subject: [PATCH 07/11] [IMP] point_of_sale: the invoicing should now work reliably and validate the orders bzr revid: fva@openerp.com-20130411120719-colzjq4l8eaiotdf --- addons/point_of_sale/point_of_sale.py | 11 ++ addons/point_of_sale/static/src/js/db.js | 13 ++- addons/point_of_sale/static/src/js/models.js | 105 +++++++++++++----- addons/point_of_sale/static/src/js/screens.js | 74 ++++++++++-- addons/point_of_sale/static/src/js/widgets.js | 9 ++ addons/point_of_sale/static/src/xml/pos.xml | 37 +++++- 6 files changed, 206 insertions(+), 43 deletions(-) diff --git a/addons/point_of_sale/point_of_sale.py b/addons/point_of_sale/point_of_sale.py index 9ee5b944718..9363afe01ea 100644 --- a/addons/point_of_sale/point_of_sale.py +++ b/addons/point_of_sale/point_of_sale.py @@ -483,7 +483,10 @@ class pos_order(osv.osv): #_logger.info("orders: %r", orders) order_ids = [] for tmp_order in orders: + to_invoice = tmp_order['to_invoice'] order = tmp_order['data'] + + order_id = self.create(cr, uid, { 'name': order['name'], 'user_id': order['user_id'] or False, @@ -522,6 +525,13 @@ class pos_order(osv.osv): order_ids.append(order_id) wf_service = netsvc.LocalService("workflow") wf_service.trg_validate(uid, 'pos.order', order_id, 'paid', cr) + + if to_invoice: + self.action_invoice(cr, uid, [order_id], context) + order_obj = self.browse(cr, uid, order_id, context) + wf_service = netsvc.LocalService('workflow') + wf_service.trg_validate(uid,'account.invoice', order_obj.invoice_id.id,'invoice_open',cr) + return order_ids def unlink(self, cr, uid, ids, context=None): @@ -859,6 +869,7 @@ class pos_order(osv.osv): inv_line_ref.create(cr, uid, inv_line, context=context) inv_ref.button_reset_taxes(cr, uid, [inv_id], context=context) wf_service.trg_validate(uid, 'pos.order', order.id, 'invoice', cr) + wf_service.trg_validate(uid, 'account.invoice', inv_id, 'validate', cr) if not inv_ids: return {} diff --git a/addons/point_of_sale/static/src/js/db.js b/addons/point_of_sale/static/src/js/db.js index c28c08950db..aad6ed8ca69 100644 --- a/addons/point_of_sale/static/src/js/db.js +++ b/addons/point_of_sale/static/src/js/db.js @@ -267,10 +267,19 @@ function openerp_pos_db(instance, module){ return results; }, add_order: function(order){ - var order_id = this.load('last_order_id',0) + 1; + var order_id = order.uid; var orders = this.load('orders',[]); + + // if the order was already stored, we overwrite its data + for(var i = 0, len = orders.length; i < len; i++){ + if(orders[i].id === order_id){ + orders[i].data = order; + this.save('orders',orders); + return order_id; + } + } + orders.push({id: order_id, data: order}); - this.save('last_order_id', order_id); this.save('orders',orders); return order_id; }, diff --git a/addons/point_of_sale/static/src/js/models.js b/addons/point_of_sale/static/src/js/models.js index 426bea9a9b7..ec4924c6068 100644 --- a/addons/point_of_sale/static/src/js/models.js +++ b/addons/point_of_sale/static/src/js/models.js @@ -262,36 +262,80 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal this.set('selectedOrder', order); }, - // saves the order locally and try to send it to the backend. 'record' is a bizzarely defined JSON version of the Order - // it returns a deferred that succeeds or fail when the pushed order is successfully posted on the server, previously failed orders - // will try to be re-sent but don't make this method return false if they fail. ( So that in the unlikely case of an order making - // the server crash, the other orders can still be sent ) - // payment process ) - push_order: function(record) { + // saves the order locally and try to send it to the backend. + // it returns a deferred that succeeds after having tried to send the order and all the other pending orders. + push_order: function(order) { var self = this; - var order_id = this.db.add_order(record); + var order_id = this.db.add_order(order.export_as_JSON()); var pushed = new $.Deferred(); this.set('nbr_pending_operations',self.db.get_orders().length); this.flush_mutex.exec(function(){ + var flushed = self._flush_all_orders(); - //first we try to push all orders (the one we added and the one that were not sent) - var tried_all = self._flush_all_orders(); - var done = new $.Deferred(); + flushed.always(function(){ + pushed.resolve(); + }); - tried_all.always(function(){ - // then we verify that the one we just added has been sent successfuly. - self._flush_order(order_id) - .done( function(){ pushed.resolve();}) - .fail( function(){ pushed.reject(); }) - .always( function(){ done.resolve(); }); + return flushed; + }); + return pushed; + }, + + // saves the order locally and try to send it to the backend and make an invoice + // returns a deferred that succeeds when the order has been posted and successfully generated + // an invoice. This method can fail in various ways: + // error-no-client: the order must have an associated partner_id. You can retry to make an invoice once + // this error is solved + // error-transfer: there was a connection error during the transfer. You can retry to make the invoice once + // the network connection is up + + push_and_invoice_order: function(order){ + var self = this; + var invoiced = new $.Deferred(); + + if(!order.get_client()){ + invoiced.reject('error-no-client'); + return invoiced; + } + + var order_id = this.db.add_order(order.export_as_JSON()); + + this.set('nbr_pending_operations',self.db.get_orders().length); + + this.flush_mutex.exec(function(){ + var done = new $.Deferred(); // holds the mutex + + // send the order to the server + // we have a 30 seconds timeout on this push. + // FIXME: if the server takes more than 30 seconds to accept the order, + // the client will believe it wasn't successfully sent, and very bad + // things will happen as a duplicate will be sent next time + // so we must make sure the server detects and ignores duplicated orders + + var transfer = self._flush_order(order_id, {timeout:30000, to_invoice:true}); + + transfer.fail(function(){ + invoiced.reject('error-transfer'); + done.reject(); + }); + + // on success, get the order id generated by the server + transfer.pipe(function(order_server_id){ + // generate the pdf and download it + self.pos_widget.do_action('point_of_sale.pos_invoice_report',{additional_context:{ + active_ids:order_server_id, + }}); + invoiced.resolve(); + done.resolve(); }); return done; + }); - return pushed; + return invoiced; }, // attemps to send all pending orders ( stored in the pos_db ) to the server, @@ -316,23 +360,30 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal }, // attempts to send the locally stored order of id 'order_id' - // the sending is asynchronous and can take a long time to decide if it is successful or not (60s) + // the sending is asynchronous and can take some time to decide if it is successful or not // it is therefore important to only call this method from inside a mutex // this method returns a deferred indicating wether the sending was successful or not - _flush_order: function(order_id){ + // there is a timeout parameter which is set to 2 seconds by default. + _flush_order: function(order_id, options){ var self = this; + options = options || {}; + timeout = typeof options.timeout === 'number' ? options.timeout : 5000; + var order = this.db.get_order(order_id); + order.to_invoice = options.to_invoice || false; if(!order){ - // flushing a non existing order always succeeds - return (new $.Deferred()).resolve(); + // flushing a non existing order always fails + return (new $.Deferred()).reject(); } - //we try to send the order. shadow prevents a spinner if it takes too long. - var rpc = (new instance.web.Model('pos.order')).call('create_from_ui',[[order]],undefined,{shadow: true, timeout: 2000}); + // we try to send the order. shadow prevents a spinner if it takes too long. (unless we are sending an invoice, + // then we want to notify the user that we are waiting on something ) + var rpc = (new instance.web.Model('pos.order')).call('create_from_ui',[[order]],undefined,{shadow: !options.to_invoice, timeout:timeout}); rpc.fail(function(unused,event){ - //prevent an error popup creation by the rpc failure + // prevent an error popup creation by the rpc failure + // we want the failure to be silent as we send the orders in the background event.preventDefault(); console.error('Failed to send order:',order); }); @@ -664,11 +715,12 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal module.Order = Backbone.Model.extend({ initialize: function(attributes){ Backbone.Model.prototype.initialize.apply(this, arguments); + this.uid = this.generateUniqueId(); this.set({ creationDate: new Date(), orderLines: new module.OrderlineCollection(), paymentLines: new module.PaymentlineCollection(), - name: "Order " + this.generateUniqueId(), + name: "Order " + this.uid, client: null, }); this.pos = attributes.pos; @@ -844,7 +896,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal currency: this.pos.get('currency'), }; }, - exportAsJSON: function() { + export_as_JSON: function() { var orderLines, paymentLines; orderLines = []; (this.get('orderLines')).each(_.bind( function(item) { @@ -865,6 +917,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal pos_session_id: this.pos.get('pos_session').id, partner_id: this.get_client() ? this.get_client().id : false, user_id: this.pos.get('cashier') ? this.pos.get('cashier').id : this.pos.get('user').id, + uid: this.uid, }; }, getSelectedLine: function(){ diff --git a/addons/point_of_sale/static/src/js/screens.js b/addons/point_of_sale/static/src/js/screens.js index a99de66988d..6c96d51748b 100644 --- a/addons/point_of_sale/static/src/js/screens.js +++ b/addons/point_of_sale/static/src/js/screens.js @@ -433,6 +433,14 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa template:'ErrorNegativePricePopupWidget', }); + module.ErrorNoClientPopupWidget = module.ErrorPopupWidget.extend({ + template: 'ErrorNoClientPopupWidget', + }); + + module.ErrorInvoiceTransferPopupWidget = module.ErrorPopupWidget.extend({ + template: 'ErrorInvoiceTransferPopupWidget', + }); + module.ScaleInviteScreenWidget = module.ScreenWidget.extend({ template:'ScaleInviteScreenWidget', @@ -639,7 +647,7 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa var cashregister = selfCheckoutRegisters[0] || self.pos.get('cashRegisters').models[0]; currentOrder.addPaymentLine(cashregister); - self.pos.push_order(currentOrder.exportAsJSON()) + self.pos.push_order(currentOrder) currentOrder.destroy(); self.pos.proxy.transaction_end(); self.pos_widget.screen_selector.set_current_screen(self.next_screen); @@ -807,19 +815,40 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa this._super(); var self = this; - this.add_action_button({ + var print_button = this.add_action_button({ label: 'Print', icon: '/point_of_sale/static/src/img/icons/png48/printer.png', click: function(){ self.print(); }, }); - this.add_action_button({ + var finish_button = this.add_action_button({ label: 'Next Order', icon: '/point_of_sale/static/src/img/icons/png48/go-next.png', click: function() { self.finishOrder(); }, }); window.print(); + + // THIS IS THE HACK OF THE CENTURY + // + // The problem is that in chrome the print() is asynchronous and doesn't + // execute until all rpc are finished. So it conflicts with the rpc used + // to send the orders to the backend, and the user is able to go to the next + // screen before the printing dialog is opened. The problem is that what's + // printed is whatever is in the page when the dialog is opened and not when it's called, + // and so you end up printing the product list instead of the receipt... + // + // Fixing this would need a re-architecturing + // of the code to postpone sending of orders after printing. + // + // But since the print dialog also blocks the other asynchronous calls, the + // button enabling in the setTimeout() is blocked until the printing dialog is + // closed. But this is not reliable ... if the timeout is too slow it doesn't work + + finish_button.set_disabled(true); + setTimeout(function(){ + finish_button.set_disabled(false); + }, 2000); }, print: function() { window.print(); @@ -896,7 +925,7 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa }); this.updatePaymentSummary(); - this.line_refocus(); + this.line_refocus();this }, close: function(){ this._super(); @@ -907,18 +936,43 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa this.pos_widget.screen_selector.set_current_screen(self.back_screen); }, validateCurrentOrder: function(options) { + var self = this; options = options || {}; var currentOrder = this.pos.get('selectedOrder'); - this.pos.push_order(currentOrder.exportAsJSON()) + if(options.invoice){ - console.log('send invoice'); - }else if(this.pos.iface_print_via_proxy){ - this.pos.proxy.print_receipt(currentOrder.export_for_printing()); - this.pos.get('selectedOrder').destroy(); //finish order and go back to scan screen + // deactivate the validation button while we try to send the order + this.pos_widget.action_bar.set_button_disabled('validation',true); + this.pos_widget.action_bar.set_button_disabled('invoice',true); + + var invoiced = this.pos.push_and_invoice_order(currentOrder); + + invoiced.fail(function(error){ + if(error === 'error-no-client'){ + self.pos_widget.screen_selector.show_popup('error-no-client'); + }else{ + self.pos_widget.screen_selector.show_popup('error-invoice-transfer'); + } + self.pos_widget.action_bar.set_button_disabled('validation',false); + self.pos_widget.action_bar.set_button_disabled('invoice',false); + }); + + invoiced.done(function(){ + self.pos_widget.action_bar.set_button_disabled('validation',false); + self.pos_widget.action_bar.set_button_disabled('invoice',false); + self.pos.get('selectedOrder').destroy(); + }); + }else{ - this.pos_widget.screen_selector.set_current_screen(this.next_screen); + this.pos.push_order(currentOrder) + if(this.pos.iface_print_via_proxy){ + this.pos.proxy.print_receipt(currentOrder.export_for_printing()); + this.pos.get('selectedOrder').destroy(); //finish order and go back to scan screen + }else{ + this.pos_widget.screen_selector.set_current_screen(this.next_screen); + } } }, bindPaymentLineEvents: function() { diff --git a/addons/point_of_sale/static/src/js/widgets.js b/addons/point_of_sale/static/src/js/widgets.js index cec48dbe72f..6a606652a8e 100644 --- a/addons/point_of_sale/static/src/js/widgets.js +++ b/addons/point_of_sale/static/src/js/widgets.js @@ -828,6 +828,7 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa instance.web.blockUI(); this.pos = new module.PosModel(this.session); + this.pos.pos_widget = this; this.pos_widget = this; //So that pos_widget's childs have pos_widget set automatically this.numpad_visible = true; @@ -942,6 +943,12 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa this.error_negative_price_popup = new module.ErrorNegativePricePopupWidget(this, {}); this.error_negative_price_popup.appendTo($('.point-of-sale')); + this.error_no_client_popup = new module.ErrorNoClientPopupWidget(this, {}); + this.error_no_client_popup.appendTo($('.point-of-sale')); + + this.error_invoice_transfer_popup = new module.ErrorInvoiceTransferPopupWidget(this, {}); + this.error_invoice_transfer_popup.appendTo($('.point-of-sale')); + // -------- Misc --------- this.notification = new module.SynchNotificationWidget(this,{}); @@ -1003,6 +1010,8 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa 'error-session': this.error_session_popup, 'error-negative-price': this.error_negative_price_popup, 'choose-receipt': this.choose_receipt_popup, + 'error-no-client': this.error_no_client_popup, + 'error-invoice-transfer': this.error_invoice_transfer_popup, }, default_client_screen: 'welcome', default_cashier_screen: 'products', diff --git a/addons/point_of_sale/static/src/xml/pos.xml b/addons/point_of_sale/static/src/xml/pos.xml index 1dce368b9cb..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 @@ - @@ -361,6 +361,33 @@ + + + + + + + +