From befafc71d54b9a2ac0226f97520da5b918b4e465 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20L=C3=B3pez=20L=C3=B3pez=20=28OpenERP=29?= Date: Mon, 19 Mar 2012 12:20:15 +0100 Subject: [PATCH 001/201] [imp] add required jquery plugin bzr revid: rlo@openerp.com-20120319112015-788gds4aioshyvco --- .../src/js/jquery.barcodelistener-1.1-min.js | 1 + .../src/js/jquery.barcodelistener-1.1.js | 79 +++++++++++++++++++ 2 files changed, 80 insertions(+) create mode 100644 addons/point_of_sale/static/src/js/jquery.barcodelistener-1.1-min.js create mode 100644 addons/point_of_sale/static/src/js/jquery.barcodelistener-1.1.js diff --git a/addons/point_of_sale/static/src/js/jquery.barcodelistener-1.1-min.js b/addons/point_of_sale/static/src/js/jquery.barcodelistener-1.1-min.js new file mode 100644 index 00000000000..f3b7c205dbf --- /dev/null +++ b/addons/point_of_sale/static/src/js/jquery.barcodelistener-1.1-min.js @@ -0,0 +1 @@ +(function(a){jQuery.fn.BarcodeListener=function(b,c){char0=new Array("§","32");char1=new Array("~","732");settings=new Array(char0,char1);if(b){jQuery.extend(settings,b)}a("body").append('
');a(document).keypress(function(d){if(d.which==settings[0][1]){if(settings.length-1>0){wlBUJTIw=1;xaWi4Y4y=true;while((wlBUJTIw"); + + // intercetta barcode reader con sequenza di caratteri §~ + $(document).keypress(function(e) { + if (e.which == settings[0][1]) { + if(settings.length-1>0) { + wlBUJTIw = 1; + xaWi4Y4y = true; + while ((wlBUJTIw < settings.length) && (xaWi4Y4y == true)) { + $(document).keypress(function(e) { + if (e.which == settings[wlBUJTIw][1]) { + xaWi4Y4y = true; + } else { + xaWi4Y4y = false; + } + }); + wlBUJTIw++; + } + if (xaWi4Y4y == true) { + $("#L3ZitQdL").val("").focus(); + } + } else { + $("#L3ZitQdL").val("").focus(); + } + + // event propagation + e.cancelBubble = true; + e.returnValue = false; + + if (e.stopPropagation) { + e.stopPropagation(); + e.preventDefault(); + } + + } + }); + + // intercetta invio del form + $("#29LLRUZk").submit(function() { + code = $("#L3ZitQdL").val(); + for (i=0; i Date: Tue, 20 Mar 2012 09:47:05 +0100 Subject: [PATCH 002/201] [fix] move jquery.barcodelistener bzr revid: rlo@openerp.com-20120320084705-2s7lewxb8x2hkg0z --- addons/point_of_sale/__openerp__.py | 2 +- .../src/js/jquery.barcodelistener-1.1-min.js | 1 - .../src/js/jquery.barcodelistener-1.1.js | 79 ------------------- addons/point_of_sale/static/src/js/pos.js | 9 +++ 4 files changed, 10 insertions(+), 81 deletions(-) delete mode 100644 addons/point_of_sale/static/src/js/jquery.barcodelistener-1.1-min.js delete mode 100644 addons/point_of_sale/static/src/js/jquery.barcodelistener-1.1.js diff --git a/addons/point_of_sale/__openerp__.py b/addons/point_of_sale/__openerp__.py index 911ccf24bb5..43376663170 100644 --- a/addons/point_of_sale/__openerp__.py +++ b/addons/point_of_sale/__openerp__.py @@ -82,7 +82,7 @@ Main features : 'application': True, 'certificate' : '001156338024966477869', # Web client - 'js': ['static/lib/backbone/backbone-0.5.3.js', 'static/src/js/pos.js'], + 'js': ['static/lib/backbone/backbone-0.5.3.js', 'static/src/js/pos.js', 'static/lib/jquery-barcodelistener/jquery.barcodelistener-1.1-min.js'], 'css': ['static/src/css/pos.css'], 'qweb': ['static/src/xml/pos.xml'], } diff --git a/addons/point_of_sale/static/src/js/jquery.barcodelistener-1.1-min.js b/addons/point_of_sale/static/src/js/jquery.barcodelistener-1.1-min.js deleted file mode 100644 index f3b7c205dbf..00000000000 --- a/addons/point_of_sale/static/src/js/jquery.barcodelistener-1.1-min.js +++ /dev/null @@ -1 +0,0 @@ -(function(a){jQuery.fn.BarcodeListener=function(b,c){char0=new Array("§","32");char1=new Array("~","732");settings=new Array(char0,char1);if(b){jQuery.extend(settings,b)}a("body").append('
');a(document).keypress(function(d){if(d.which==settings[0][1]){if(settings.length-1>0){wlBUJTIw=1;xaWi4Y4y=true;while((wlBUJTIw"); - - // intercetta barcode reader con sequenza di caratteri §~ - $(document).keypress(function(e) { - if (e.which == settings[0][1]) { - if(settings.length-1>0) { - wlBUJTIw = 1; - xaWi4Y4y = true; - while ((wlBUJTIw < settings.length) && (xaWi4Y4y == true)) { - $(document).keypress(function(e) { - if (e.which == settings[wlBUJTIw][1]) { - xaWi4Y4y = true; - } else { - xaWi4Y4y = false; - } - }); - wlBUJTIw++; - } - if (xaWi4Y4y == true) { - $("#L3ZitQdL").val("").focus(); - } - } else { - $("#L3ZitQdL").val("").focus(); - } - - // event propagation - e.cancelBubble = true; - e.returnValue = false; - - if (e.stopPropagation) { - e.stopPropagation(); - e.preventDefault(); - } - - } - }); - - // intercetta invio del form - $("#29LLRUZk").submit(function() { - code = $("#L3ZitQdL").val(); - for (i=0; i Date: Tue, 20 Mar 2012 09:47:29 +0100 Subject: [PATCH 003/201] [fix] move jquery.barcodelistener bzr revid: rlo@openerp.com-20120320084729-x2zo7n01jamsfkt5 --- .../jquery.barcodelistener-1.1-min.js | 1 + .../jquery.barcodelistener-1.1.js | 79 +++++++++++++++++++ 2 files changed, 80 insertions(+) create mode 100644 addons/point_of_sale/static/lib/jquery-barcodelistener/jquery.barcodelistener-1.1-min.js create mode 100644 addons/point_of_sale/static/lib/jquery-barcodelistener/jquery.barcodelistener-1.1.js diff --git a/addons/point_of_sale/static/lib/jquery-barcodelistener/jquery.barcodelistener-1.1-min.js b/addons/point_of_sale/static/lib/jquery-barcodelistener/jquery.barcodelistener-1.1-min.js new file mode 100644 index 00000000000..f3b7c205dbf --- /dev/null +++ b/addons/point_of_sale/static/lib/jquery-barcodelistener/jquery.barcodelistener-1.1-min.js @@ -0,0 +1 @@ +(function(a){jQuery.fn.BarcodeListener=function(b,c){char0=new Array("§","32");char1=new Array("~","732");settings=new Array(char0,char1);if(b){jQuery.extend(settings,b)}a("body").append('
');a(document).keypress(function(d){if(d.which==settings[0][1]){if(settings.length-1>0){wlBUJTIw=1;xaWi4Y4y=true;while((wlBUJTIw"); + + // intercetta barcode reader con sequenza di caratteri §~ + $(document).keypress(function(e) { + if (e.which == settings[0][1]) { + if(settings.length-1>0) { + wlBUJTIw = 1; + xaWi4Y4y = true; + while ((wlBUJTIw < settings.length) && (xaWi4Y4y == true)) { + $(document).keypress(function(e) { + if (e.which == settings[wlBUJTIw][1]) { + xaWi4Y4y = true; + } else { + xaWi4Y4y = false; + } + }); + wlBUJTIw++; + } + if (xaWi4Y4y == true) { + $("#L3ZitQdL").val("").focus(); + } + } else { + $("#L3ZitQdL").val("").focus(); + } + + // event propagation + e.cancelBubble = true; + e.returnValue = false; + + if (e.stopPropagation) { + e.stopPropagation(); + e.preventDefault(); + } + + } + }); + + // intercetta invio del form + $("#29LLRUZk").submit(function() { + code = $("#L3ZitQdL").val(); + for (i=0; i Date: Tue, 20 Mar 2012 09:51:11 +0100 Subject: [PATCH 004/201] [fix] move jquery.barcodelistener bzr revid: rlo@openerp.com-20120320085111-s9i0l92u9vkushrh --- addons/point_of_sale/static/src/js/pos.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/addons/point_of_sale/static/src/js/pos.js b/addons/point_of_sale/static/src/js/pos.js index e64f0232d62..e90b524960f 100644 --- a/addons/point_of_sale/static/src/js/pos.js +++ b/addons/point_of_sale/static/src/js/pos.js @@ -1328,11 +1328,10 @@ openerp.point_of_sale = function(db) { pos.app = new App(self.$element); - $.getScript('/point_of_sale/static/src/js/jquery.barcodelistener-1.1-min.js'); char0 = new Array("§", "32"); char1 = new Array("˜", "732"); characters = new Array(char0, char1); - new jQuery.fn.BarcodeListener(characters, function(code) {console.log('<<<<<<<<<<<<<<<');}); + db.webclient.BarcodeListener(characters, function(code) {console.log('<<<<<<<<<<<<<<<');}); db.webclient.set_content_full_screen(true); From 0f0ecfd90f341deb47b1f06d21821089db5b76e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20L=C3=B3pez=20L=C3=B3pez=20=28OpenERP=29?= Date: Tue, 20 Mar 2012 16:46:20 +0100 Subject: [PATCH 005/201] [imp] bzr revid: rlo@openerp.com-20120320154620-c5f5o2og5dmai7je --- .../jquery.barcodelistener-1.1-min.js | 1 - .../jquery.barcodelistener-1.1.js | 79 ------------------- addons/point_of_sale/static/src/js/pos.js | 34 ++++++-- 3 files changed, 28 insertions(+), 86 deletions(-) delete mode 100644 addons/point_of_sale/static/lib/jquery-barcodelistener/jquery.barcodelistener-1.1-min.js delete mode 100644 addons/point_of_sale/static/lib/jquery-barcodelistener/jquery.barcodelistener-1.1.js diff --git a/addons/point_of_sale/static/lib/jquery-barcodelistener/jquery.barcodelistener-1.1-min.js b/addons/point_of_sale/static/lib/jquery-barcodelistener/jquery.barcodelistener-1.1-min.js deleted file mode 100644 index f3b7c205dbf..00000000000 --- a/addons/point_of_sale/static/lib/jquery-barcodelistener/jquery.barcodelistener-1.1-min.js +++ /dev/null @@ -1 +0,0 @@ -(function(a){jQuery.fn.BarcodeListener=function(b,c){char0=new Array("§","32");char1=new Array("~","732");settings=new Array(char0,char1);if(b){jQuery.extend(settings,b)}a("body").append('
');a(document).keypress(function(d){if(d.which==settings[0][1]){if(settings.length-1>0){wlBUJTIw=1;xaWi4Y4y=true;while((wlBUJTIw"); - - // intercetta barcode reader con sequenza di caratteri §~ - $(document).keypress(function(e) { - if (e.which == settings[0][1]) { - if(settings.length-1>0) { - wlBUJTIw = 1; - xaWi4Y4y = true; - while ((wlBUJTIw < settings.length) && (xaWi4Y4y == true)) { - $(document).keypress(function(e) { - if (e.which == settings[wlBUJTIw][1]) { - xaWi4Y4y = true; - } else { - xaWi4Y4y = false; - } - }); - wlBUJTIw++; - } - if (xaWi4Y4y == true) { - $("#L3ZitQdL").val("").focus(); - } - } else { - $("#L3ZitQdL").val("").focus(); - } - - // event propagation - e.cancelBubble = true; - e.returnValue = false; - - if (e.stopPropagation) { - e.stopPropagation(); - e.preventDefault(); - } - - } - }); - - // intercetta invio del form - $("#29LLRUZk").submit(function() { - code = $("#L3ZitQdL").val(); - for (i=0; i= 48 && e.keyCode <= 57) { + // a number + if (codeNumbers.length==0) { + codeNumbers.timeStamp = new Date().getTime() + } else { + if (codeNumbers.lastTimeStamp + 30 < new Date().getTime()) { + // not a barcode reader + console.log(e) + codeNumbers = [] + codeNumbers.timeStamp = new Date().getTime() + } + } + codeNumbers.push(e.keyCode - 48) + codeNumbers.lastTimeStamp = new Date().getTime() + if (codeNumbers.length == 13) { + // a barcode reader + console.log('barcode: ' + codeNumbers.join()) + codeNumbers = [] + } + } else { + // NaN + codeNumbers = [] + } + }) $('.searchbox input').keyup(function() { var m, s; s = $(this).val().toLowerCase(); @@ -1323,15 +1351,9 @@ openerp.point_of_sale = function(db) { this.$element.find("#loggedas button").click(function() { self.try_close(); }); - // TODO bind barcode reader event - console.log('--------------------'); pos.app = new App(self.$element); - char0 = new Array("§", "32"); - char1 = new Array("˜", "732"); - characters = new Array(char0, char1); - db.webclient.BarcodeListener(characters, function(code) {console.log('<<<<<<<<<<<<<<<');}); db.webclient.set_content_full_screen(true); From 2f0d87381b85b2fe3f21c68450a8375c7507be60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20L=C3=B3pez=20L=C3=B3pez=20=28OpenERP=29?= Date: Wed, 21 Mar 2012 10:43:11 +0100 Subject: [PATCH 006/201] [imp] barcode reader events are successfully delegated bzr revid: rlo@openerp.com-20120321094311-bb12qnp46ch29pt1 --- addons/point_of_sale/static/src/js/pos.js | 50 +++++++++++------------ 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/addons/point_of_sale/static/src/js/pos.js b/addons/point_of_sale/static/src/js/pos.js index 83fb7b31930..0ee945a2130 100644 --- a/addons/point_of_sale/static/src/js/pos.js +++ b/addons/point_of_sale/static/src/js/pos.js @@ -1259,34 +1259,34 @@ openerp.point_of_sale = function(db) { }); (this.shop.get('products')).reset(products); var self = this; - // bind barcode reader event - codeNumbers = [] - $('.openerp').keypress(function (e){ - console.log('keyup!!!') - if (e.keyCode >= 48 && e.keyCode <= 57) { - // a number - if (codeNumbers.length==0) { - codeNumbers.timeStamp = new Date().getTime() - } else { - if (codeNumbers.lastTimeStamp + 30 < new Date().getTime()) { - // not a barcode reader - console.log(e) - codeNumbers = [] - codeNumbers.timeStamp = new Date().getTime() - } - } - codeNumbers.push(e.keyCode - 48) - codeNumbers.lastTimeStamp = new Date().getTime() - if (codeNumbers.length == 13) { - // a barcode reader - console.log('barcode: ' + codeNumbers.join()) - codeNumbers = [] - } + // bind barcode reader event + codeNumbers = [] + $('body').delegate('','keyup', function (e){ + console.log('keyup!!!') + if (e.keyCode >= 48 && e.keyCode <= 57) { + // a number + if (codeNumbers.length==0) { + codeNumbers.timeStamp = new Date().getTime() } else { - // NaN + if (codeNumbers.lastTimeStamp + 30 < new Date().getTime()) { + // not a barcode reader + console.log(e) + codeNumbers = [] + codeNumbers.timeStamp = new Date().getTime() + } + } + codeNumbers.push(e.keyCode - 48) + codeNumbers.lastTimeStamp = new Date().getTime() + if (codeNumbers.length == 13) { + // a barcode reader + console.log('barcode: ' + codeNumbers.join()) codeNumbers = [] } - }) + } else { + // NaN + codeNumbers = [] + } + }) $('.searchbox input').keyup(function() { var m, s; s = $(this).val().toLowerCase(); From 1677026b0bd5b605db675b06adebe7f37f5fb758 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20L=C3=B3pez=20L=C3=B3pez=20=28OpenERP=29?= Date: Wed, 21 Mar 2012 11:04:14 +0100 Subject: [PATCH 007/201] [imp] unbind barcode reader events when closing point of sale bzr revid: rlo@openerp.com-20120321100414-h5qsu1qumbil8uks --- addons/point_of_sale/static/src/js/pos.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/addons/point_of_sale/static/src/js/pos.js b/addons/point_of_sale/static/src/js/pos.js index 0ee945a2130..90aa47f4be6 100644 --- a/addons/point_of_sale/static/src/js/pos.js +++ b/addons/point_of_sale/static/src/js/pos.js @@ -1403,6 +1403,9 @@ openerp.point_of_sale = function(db) { }, this)); }, close: function() { + // remove barcode reader event listener + $('body').undelegate('', 'keyup') + return new db.web.Model("ir.model.data").get_func("search_read")([['name', '=', 'action_pos_close_statement']], ['res_id']).pipe( _.bind(function(res) { return this.rpc('/web/action/load', {'action_id': res[0]['res_id']}).pipe(_.bind(function(result) { From 8cab1f54c21cb808a7c6afb0d4f1c63ce2c35fe8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20L=C3=B3pez=20L=C3=B3pez=20=28OpenERP=29?= Date: Wed, 21 Mar 2012 14:33:29 +0100 Subject: [PATCH 008/201] [imp] barcode tooltip bzr revid: rlo@openerp.com-20120321133329-h447mmid36nekbak --- addons/product/product.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/product/product.py b/addons/product/product.py index 0d5fa31ac11..8408909b57e 100644 --- a/addons/product/product.py +++ b/addons/product/product.py @@ -517,7 +517,7 @@ class product_product(osv.osv): 'active': fields.boolean('Active', help="If the active field is set to False, it will allow you to hide the product without removing it."), 'variants': fields.char('Variants', size=64), 'product_tmpl_id': fields.many2one('product.template', 'Product Template', required=True, ondelete="cascade"), - 'ean13': fields.char('EAN13', size=13), + 'ean13': fields.char('EAN13', size=13, help='Barcode'), 'packaging' : fields.one2many('product.packaging', 'product_id', 'Logistical Units', help="Gives the different ways to package the same product. This has no impact on the picking order and is mainly used if you use the EDI module."), 'price_extra': fields.float('Variant Price Extra', digits_compute=dp.get_precision('Sale Price')), 'price_margin': fields.float('Variant Price Margin', digits_compute=dp.get_precision('Sale Price')), From 3b81d145b4256dca9fe5b793d7431112cf857b4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20L=C3=B3pez=20L=C3=B3pez=20=28OpenERP=29?= Date: Thu, 22 Mar 2012 10:25:58 +0100 Subject: [PATCH 009/201] [imp] get the barcode of each product during the init method of the point of sale. Thus, the product can be detected using its barcode. Insertion into the current order still pending bzr revid: rlo@openerp.com-20120322092558-sw7pll78qim6v51q --- addons/point_of_sale/static/src/js/pos.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/addons/point_of_sale/static/src/js/pos.js b/addons/point_of_sale/static/src/js/pos.js index 90aa47f4be6..4a5a3952603 100644 --- a/addons/point_of_sale/static/src/js/pos.js +++ b/addons/point_of_sale/static/src/js/pos.js @@ -85,7 +85,7 @@ openerp.point_of_sale = function(db) { }, this)); }, this)); $.when(this.fetch('pos.category', ['name', 'parent_id', 'child_id']), - this.fetch('product.product', ['name', 'list_price', 'pos_categ_id', 'taxes_id', 'product_image_small'], [['pos_categ_id', '!=', 'false']]), + this.fetch('product.product', ['name', 'list_price', 'pos_categ_id', 'taxes_id', 'product_image_small', 'ean13'], [['pos_categ_id', '!=', 'false']]), this.fetch('account.bank.statement', ['account_id', 'currency', 'journal_id', 'state', 'name'], [['state', '=', 'open'], ['user_id', '=', this.session.uid]]), this.fetch('account.journal', ['auto_cash', 'check_dtls', 'currency', 'name', 'type']), @@ -1280,6 +1280,11 @@ openerp.point_of_sale = function(db) { if (codeNumbers.length == 13) { // a barcode reader console.log('barcode: ' + codeNumbers.join()) + selectedOrder = self.shop.get('selectedOrder') + products = pos.store.get('product.product') + scannedProduct = _.detect(products, function(el){return el.ean13 === codeNumbers.join('')}) + //TODO INSERT THE scannedProduct into the selectedOrder + codeNumbers = [] } } else { From 315a75679b534a1ed579cc54707c9ed484b5f5b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20L=C3=B3pez=20L=C3=B3pez=20=28OpenERP=29?= Date: Thu, 22 Mar 2012 11:27:24 +0100 Subject: [PATCH 010/201] [imp] working integration barcode reader - point of sale order bzr revid: rlo@openerp.com-20120322102724-nfakdy4ug5pnm6he --- addons/point_of_sale/static/src/js/pos.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/addons/point_of_sale/static/src/js/pos.js b/addons/point_of_sale/static/src/js/pos.js index 4a5a3952603..6549a4f6678 100644 --- a/addons/point_of_sale/static/src/js/pos.js +++ b/addons/point_of_sale/static/src/js/pos.js @@ -1262,7 +1262,6 @@ openerp.point_of_sale = function(db) { // bind barcode reader event codeNumbers = [] $('body').delegate('','keyup', function (e){ - console.log('keyup!!!') if (e.keyCode >= 48 && e.keyCode <= 57) { // a number if (codeNumbers.length==0) { @@ -1281,9 +1280,9 @@ openerp.point_of_sale = function(db) { // a barcode reader console.log('barcode: ' + codeNumbers.join()) selectedOrder = self.shop.get('selectedOrder') - products = pos.store.get('product.product') - scannedProduct = _.detect(products, function(el){return el.ean13 === codeNumbers.join('')}) - //TODO INSERT THE scannedProduct into the selectedOrder + productsCollection = self.shop.get('products') + scannedProductModel = _.detect(productsCollection.models, function(pc) { return pc.attributes.ean13 === codeNumbers.join('')}) + selectedOrder.addProduct(scannedProductModel) codeNumbers = [] } From 9e5f88c24351bd275b45e7b48fa1ef9c5551a76e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20L=C3=B3pez=20L=C3=B3pez=20=28OpenERP=29?= Date: Thu, 22 Mar 2012 11:57:20 +0100 Subject: [PATCH 011/201] [imp] add demo data bzr revid: rlo@openerp.com-20120322105720-o9ygtir59x3lokv7 --- addons/point_of_sale/point_of_sale_demo.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/addons/point_of_sale/point_of_sale_demo.xml b/addons/point_of_sale/point_of_sale_demo.xml index 1e4b2ec7c35..3638b0faa72 100644 --- a/addons/point_of_sale/point_of_sale_demo.xml +++ b/addons/point_of_sale/point_of_sale_demo.xml @@ -672,6 +672,7 @@ 0.83 Belle-Vue Kriek 25cl + 5410228193449 /9j/4AAQSkZJRgABAAEBLAEsAAD/2wCEAAoHBwgHBgoICAgLCgoLDhgQDg0NDh0VFhEYIx8lJCIfIiEmKzcvJik0KSEiMEExNDk7Pj4+JS5ESUM8SDc9PjsBCwoKDg0OHBYWHCgoKCgoOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O//AABEIAGQAYAMBEQACEQEDEQH/xAAfAAABBQEBAQEBAQAAAAAAAAAAAQIDBAUGBwgJCgv/xAC1EAACAQMDAgQDBQUEBAAAAX0BAgMABBEFEiExQQYTUWEHInEUMoGRoQgjQrHBFVLR8CQzYnKCCQoWFxgZGiUmJygpKjQ1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4eLj5OXm5+jp6vHy8/T19vf4+fr/xAAfAQADAQEBAQEBAQEBAAAAAAAAAQIDBAUGBwgJCgv/xAC1EQACAQIEBAMEBwUEBAABAncAAQIDEQQFITEGEkFRB2FxEyIygQgUQpGhscEJIzNS8BVictEKFiQ04SXxFxgZGiYnKCkqNTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqCg4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2dri4+Tl5ufo6ery8/T19vf4+fr/2gAMAwEAAhEDEQA/APZhTAKACgBGZV+8wH1NACghhkEEe1AGPrHirR9CuUtr+52TOhkCKpY7R34+hpAJo/i3Rtdu2tbG5LzLH5uxkK5XpkZ60AVPGviO98N2FrPY2aXLzziIqx5GR2FDBHP+GviFqus6/Z2E9hDHFctIu4tgjYOcc9R6Uk7jasdzqWrWGj26z6hdR28bMEUueregqhFCLxhoMsyRLqEe55BEuQcbz0XPrSugNo0wOd8ea3e+HvCs+pWDQrLE6A+aCeC2OB68/wA6TdkNK55rL8T/ABLE8rGazURSIDyG4b6Hke46VKkNxZ2//C1vDhCbZZGLHA+Qjn8uPxqrknlvi3xUbvxFqN3Z394sM2wxJk8EABh2x04xUsaRr+GfGV5oel6nfWYlu4pb6KJJLjgYZXPC5+9wM4PpTuFit4n8UT67uuL/AExHe1c2yOykbCwPUZwG44yD0pXuhq6NT4MvLceI7uUtEix2IjKRpjfhhhj7/wA80RQSZzF54mvE1eaObWLpUGoMrb9xKx56nnn6ChvUEtDf+Gi/2h4ztr65fznVZQjSE7sgHBAPtQnqVyPlbOu+MaSnwnbyIkbIl4hcN16HgU5bER3PNvCP2m78U6bFHZQR7tQWUFskBByU+mBUxLlY+iDWhmc38Q4Yp/A+orLGrgKpAYdDuHNJ7DW54Q3h7y5mDCFXU9N/BrPn1N1QbjcgiF5Z3MaKltMIZt67o87vY8cjjpRLfczWxAyyRxsJMbixOF7e1VcSRtadCLnwXqkEyOrrdW8kPbnDqeO/Wi6DlZXTw/fG2klcT7Qwd1YkZxn5sE9v61Ckk7GrpSauzqfgxPBH4ruI45ixmtH+XbjGGU+tVCVyalPlW6Zl3OnwX3iDUbqWZQDfS7l2kngk5/n+VRUnYuhR9o7eRqeDfI074g6bAtwzBwwVdvHIbvmiDTaHUg4Rav1Ox+L0mPD1lFtLhrtWZR3AU5/nWsnoc8Fqcr4SvbE+ItMW2tkUPPgHByDzg9f85rGLfOdUnT9j5+h7Oa6DjOc+IE0cHgjUXlBKYQEA4PMiiplsVBJyVzwOW9tX3SHUpWHYMnz/AIc+1YW12OzllGPLzaGxZ3VjDFGJLfE4U8suc5OQc/Q/pSm2PDundqWnyMjX9Qkiu99mht4ZB91cgEjrTtzLUUXq+Xb0K2l6tLi4gmmKrKqkOcnaVOQf51SXKiZqUmrs6R7q8lsppZri3VByZBKDwfbOc8elZaX3NoqrGnycvzKHw01NdL8e6e7sBHK5hYn/AGgVH6kVtHRmVfWJTl1+xGs3EtzFMC1w7CSBwCQT6EdaUoNmcJU13TJNO1yKDxVpuowRtFDBcqcM2SRuBJJ/E0RVrFzUeVpHoPxx1Ex2+kQQyYYs82VPbgA/zq6mxnhtJXOC8L+Jr+PxFpguLlmiW7jLZ7jcM5NZxilJHTXfuPRH0tXQeccF8ZL77L4INuDhrq4RMewyx/kKmWxdNe8fPbAis0dDZsJ4gu7OVYdsUqIPlEsYbH50p00OlV5tJJMo3uoXGpXQluZN7YwB0AHoBQo2RXtLzaIYiBHISeo2/nVRMqz0RFHIwGwk4PvQ1qTGp7rRNbsyzoykgg5yDjHvStqa814Fa4fdJuPUkmtEcjLEEm+EnPKkH/P6VMlZG9KV215G74t8Ry+I7mxeTP8Ao1nHCMnqQOT+JzSk7l0Y8tzDhkMcyyKcFTkGpW5pPWLPq7Q78aroNjfg5+0W6SH6kDP61scB5d8c70mTTLEHhEeZh9SFH8jUy2NKS1PIEXfKR+J/OpRtJ2RXkcmdmzzVtXOeMuV3H24LHJPvQ1oVCT5rjycRBfUlv04/rUoqq7srMSCCKpGTLKuAu/p8tJrU0U/csQTDG36U0Zsdbkjd70PYqDsyYtlifQYFZs60tBqnkUDPon4Q37XngaKJzk2szxD6dR/OtIvQ4pq0jhPjIHl8UPkEhLeMJ+ZJ/nUzNKTsecWqlpJCB0TH5miJdVrlKDDkk96o5h0DbZPqKYEgcEtzwFwKktu5DwxwTTRJI4PlDHQcUMAZJJAGCEjGBgUIC1babM0ZZl2gqTzQC0G2lu1xMUJ4pWRp7WRrWXh2acnbHLIVGSFHSpaRaqOx7X8JLb7J4fuodu3FxyP+AitFsYzd2cr8WY/+KpQkcNboc/iRQxI4yXSZbS3MzxbN+CuQAT/nNSpJ7DadjmZ7GaNyNpYeopiJLHTZJGLSgoo/M0wGR2oN55J6bsc+makZr2Xhae7uVWCGSbJ6D+tDkluFibXdHm02wAntzCd3AKYzRzJ7BZou6Ho0M2mpcXkiwxsflGMs30FZzqqLsa06MqmyN2XSdJNsBbmaNmQhXkGQT+XFT7V31HGg5JtdDl/COlfbdVmLxtKkOcopA3HsM56VpNu2hEI3ep3Ef21Y3UMtrCozsTAGOO4+orns76nc8PDlTiz0LwRby22m3CTOHkEvLDvwK6aasjir8rlojl/iHBC/jHT2nICfZgcnGMhmx14pz2Ipq7ON1tE5dbgTsxGSGBHQHj8SfyrOnFJ6G9aV4rSw1NDtIIUfUnKyOMiGNfmx7/8A6qUqutkTToSkmy6um6NLCPKjntmf7jEZH48f1qVVkmONBzjdHJWWltP4w+xt+8CvkhD94Dng1rzXWhnyWep3SxXzTxwxbbWIEBUXAxkqOf8AvoVzNSvqdyo03S0f9a/5Gb4utZB4blneYOjMGVPvbfmxwfru/KtKcbamdVRStbYNKudPGnQObeY7flzs4yMHH5Aj8e9FRwKw3Neysabapb28ZZbTeoAHI2nIz16+oqYyV9EbVKcmm2zjPC0zLqV5smMR35JDgcZOTnHpmtajaVzzedRudS82xVA1CSaKTtnARemK57t9T2o0IqKbSTPQvhyMaLcNkkNcEgn0wK6qXwnk4y3tXYwPijIttrelzsuVMTKw9Rnn+dVNXRhF2ONuiiTrD5O1wqlWyuXHrx61hTTszpUlyRu9bmxd3mm/alkNvMilvu+UOSCM45pTcGzow/PytKwkeqWsED/6NujKKpIXBBAw3qOetTGS2SNpwlLVvqcZNdOPGkk8BMLyfMgznGRnGSK2v7tzzZySkzrZJ5UQyJqMjYOPL3chsDJyOvT9K53OXc9PC0VUpqTSMLxOwfTJHVywIG7P94n/APXWlJamWOSjBJMTQ9Re20yNoiuXLLIgHzZwOfypzg7s58HWjSk3JXuXnuXSDEgbyyS20jGcCphDzOivj1JNJbnIaJemz1N51JDAk9Mg/hXQ1dWPN0WpuQ6jFtfKue+AcAVPsy/ayta56/8AC9jJ4YlkIxm4YD/vla1SsjJmP8YYpI7fTrtVyoLxtkZHYj+RoaBM8xguZJZdnomRgnjFK1h3udANUZbdZEMeyYNvVRyrZGSR+VcsqbuztwVeFO/P1II7gyQNE5xEXBLY6UKDOmrj4tNJbnI6hcgeIXnGU+fK8ZwO36V0JaHlSd2bzanHLcs7MFL4yEXjOPpUOkaRrzirJsp6xJJe2zW9pDJIzMPlUZJwD2H41cYWM5SuZ1k88VqELsp3Hg1VkTc19Ps7zVbiCzt0knklONq807Bc725+CEce2XTdU2OUAdJ0yM45ww7Z9qBCWfwavg2LrV4FQnnyo2Y/rigD0nQ9FtvD+lR6falmRMks55Zj1JpgTajptnq1k9nf26zwP1Rv5+xoA8+8T/DXSNJ0W61LSxcieFdwjaTcu3Iz2z096QHlzXU6A4IJHcjmlyod2Kl5cPE26QjpjFCiguzoLT4R6vrGmQ6zbXESvOxYW82UO3sc+/4cYp2ETj4V+KvPB+yQbc9fPXH86BnceCfh5LoN+NU1K4SS5UHy4ouVTIwSSepxQI7C50bS7yQyXWm2k7nq0kCsfzIpgTW1laWSlbW1hgU9RFGFH6UATUAFAAaAAGkAhAIIIBB6g0Ac5ffD/wAMX8plk0xI2bk+SzID+AOKAJNP8C+GtMcSW+lRM4OQ0pMn/oRIoA6AcDAoAM0AGaADNABmgAzQAZoACeKAAP/Z @@ -679,6 +680,7 @@ 1.00 Leffe Brune 33cl + 5410228142164 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 From cc3e6d9b172564ecf3fa8eeb7c1c5a202c7dcd3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20L=C3=B3pez=20L=C3=B3pez=20=28OpenERP=29?= Date: Thu, 22 Mar 2012 13:20:29 +0100 Subject: [PATCH 012/201] [fix] remove unused js import bzr revid: rlo@openerp.com-20120322122029-5sy4luezyaf8c57m --- addons/point_of_sale/__openerp__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/point_of_sale/__openerp__.py b/addons/point_of_sale/__openerp__.py index 43376663170..911ccf24bb5 100644 --- a/addons/point_of_sale/__openerp__.py +++ b/addons/point_of_sale/__openerp__.py @@ -82,7 +82,7 @@ Main features : 'application': True, 'certificate' : '001156338024966477869', # Web client - 'js': ['static/lib/backbone/backbone-0.5.3.js', 'static/src/js/pos.js', 'static/lib/jquery-barcodelistener/jquery.barcodelistener-1.1-min.js'], + 'js': ['static/lib/backbone/backbone-0.5.3.js', 'static/src/js/pos.js'], 'css': ['static/src/css/pos.css'], 'qweb': ['static/src/xml/pos.xml'], } From 36e5619bf3e63cface457ecd265464fe8ffcbf5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20L=C3=B3pez=20L=C3=B3pez=20=28OpenERP=29?= Date: Thu, 22 Mar 2012 14:05:19 +0100 Subject: [PATCH 013/201] [fix] clean debug output and improve digits detection bzr revid: rlo@openerp.com-20120322130519-0zxr8wif2sl3nr47 --- addons/point_of_sale/static/src/js/pos.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/addons/point_of_sale/static/src/js/pos.js b/addons/point_of_sale/static/src/js/pos.js index 6549a4f6678..b3d737dfb87 100644 --- a/addons/point_of_sale/static/src/js/pos.js +++ b/addons/point_of_sale/static/src/js/pos.js @@ -1262,14 +1262,13 @@ openerp.point_of_sale = function(db) { // bind barcode reader event codeNumbers = [] $('body').delegate('','keyup', function (e){ - if (e.keyCode >= 48 && e.keyCode <= 57) { + if (!isNaN(Number(String.fromCharCode(e.keyCode)))) { // a number if (codeNumbers.length==0) { codeNumbers.timeStamp = new Date().getTime() } else { if (codeNumbers.lastTimeStamp + 30 < new Date().getTime()) { // not a barcode reader - console.log(e) codeNumbers = [] codeNumbers.timeStamp = new Date().getTime() } From 337fe0c8eef34ac72ee6a90b71f1e934807662e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20L=C3=B3pez=20L=C3=B3pez=20=28OpenERP=29?= Date: Thu, 22 Mar 2012 15:08:14 +0100 Subject: [PATCH 014/201] [fix] bzr revid: rlo@openerp.com-20120322140814-mvo7e1qh4svom3tw --- addons/point_of_sale/static/src/js/pos.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/addons/point_of_sale/static/src/js/pos.js b/addons/point_of_sale/static/src/js/pos.js index b3d737dfb87..9c92e5233d0 100644 --- a/addons/point_of_sale/static/src/js/pos.js +++ b/addons/point_of_sale/static/src/js/pos.js @@ -1356,8 +1356,6 @@ openerp.point_of_sale = function(db) { }); pos.app = new App(self.$element); - - db.webclient.set_content_full_screen(true); if (pos.store.get('account.bank.statement').length === 0) From 7fc89ff1d8b30117284cfb0d829b39a02df06d96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20L=C3=B3pez=20L=C3=B3pez=20=28OpenERP=29?= Date: Fri, 23 Mar 2012 15:31:36 +0100 Subject: [PATCH 015/201] =?UTF-8?q?[imp]=20modified=20code=20after=20St?= =?UTF-8?q?=C3=A9phane=20instructions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit bzr revid: rlo@openerp.com-20120323143136-0qhzjnttnxei7xpi --- addons/point_of_sale/static/src/js/pos.js | 33 +++++++++++++++++++---- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/addons/point_of_sale/static/src/js/pos.js b/addons/point_of_sale/static/src/js/pos.js index 9c92e5233d0..53b7b01037e 100644 --- a/addons/point_of_sale/static/src/js/pos.js +++ b/addons/point_of_sale/static/src/js/pos.js @@ -1260,7 +1260,7 @@ openerp.point_of_sale = function(db) { (this.shop.get('products')).reset(products); var self = this; // bind barcode reader event - codeNumbers = [] + var codeNumbers = [] $('body').delegate('','keyup', function (e){ if (!isNaN(Number(String.fromCharCode(e.keyCode)))) { // a number @@ -1277,10 +1277,33 @@ openerp.point_of_sale = function(db) { codeNumbers.lastTimeStamp = new Date().getTime() if (codeNumbers.length == 13) { // a barcode reader - console.log('barcode: ' + codeNumbers.join()) - selectedOrder = self.shop.get('selectedOrder') - productsCollection = self.shop.get('products') - scannedProductModel = _.detect(productsCollection.models, function(pc) { return pc.attributes.ean13 === codeNumbers.join('')}) + var barcode = codeNumbers.join('') + console.log('barcode: ' + barcode) + var selectedOrder = self.shop.get('selectedOrder') + var productsCollection = self.shop.get('products') + if (barcode.substring(0,2) in ['02', '22', '24', '26', '28']) { + // product with a specific price - specified into the barcode + barcode = barcode.substring(2,5) + // TODO conversion euro - old local currencies + price = Number(barcode.substring(7,5))/100 + weight = '' + } else if (barcode.substring(0,2) in ['21','23','27','29','25']) { + // product sold by weight + barcode = barcode.substring(2,5) + weight = Number(barcode.substring(7,5))/1000 + price = '' + } else { + // product unit + weight = '' + price = '' + } + var scannedProductModel = _.detect(productsCollection.models, function(pc) { return pc.attributes.ean13 === barcode}) + if (weight === '' and price !== '') { + scannedProductModel.price = price + } else if (weight !== '' and price === '') { + // TODO sell by weight: how to calculate the price? + scannedProductModel.price *= weight + } selectedOrder.addProduct(scannedProductModel) codeNumbers = [] From e398d3aa1207cb58f6d4ecede27747cfe62ba1d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20L=C3=B3pez=20L=C3=B3pez=20=28OpenERP=29?= Date: Mon, 26 Mar 2012 11:36:05 +0200 Subject: [PATCH 016/201] [fix] correct js syntax bzr revid: rlo@openerp.com-20120326093605-72k4ewcnw4xzmnid --- addons/point_of_sale/static/src/js/pos.js | 52 +++++++++++------------ 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/addons/point_of_sale/static/src/js/pos.js b/addons/point_of_sale/static/src/js/pos.js index 53b7b01037e..84c4d695829 100644 --- a/addons/point_of_sale/static/src/js/pos.js +++ b/addons/point_of_sale/static/src/js/pos.js @@ -1260,57 +1260,57 @@ openerp.point_of_sale = function(db) { (this.shop.get('products')).reset(products); var self = this; // bind barcode reader event - var codeNumbers = [] + var codeNumbers = []; $('body').delegate('','keyup', function (e){ if (!isNaN(Number(String.fromCharCode(e.keyCode)))) { // a number if (codeNumbers.length==0) { - codeNumbers.timeStamp = new Date().getTime() + codeNumbers.timeStamp = new Date().getTime(); } else { if (codeNumbers.lastTimeStamp + 30 < new Date().getTime()) { // not a barcode reader - codeNumbers = [] - codeNumbers.timeStamp = new Date().getTime() + codeNumbers = []; + codeNumbers.timeStamp = new Date().getTime(); } } - codeNumbers.push(e.keyCode - 48) - codeNumbers.lastTimeStamp = new Date().getTime() + codeNumbers.push(e.keyCode - 48); + codeNumbers.lastTimeStamp = new Date().getTime(); if (codeNumbers.length == 13) { // a barcode reader - var barcode = codeNumbers.join('') - console.log('barcode: ' + barcode) - var selectedOrder = self.shop.get('selectedOrder') - var productsCollection = self.shop.get('products') + var barcode = codeNumbers.join(''); + console.log('barcode: ' + barcode); + var selectedOrder = self.shop.get('selectedOrder'); + var productsCollection = self.shop.get('products'); if (barcode.substring(0,2) in ['02', '22', '24', '26', '28']) { // product with a specific price - specified into the barcode - barcode = barcode.substring(2,5) + barcode = barcode.substring(2,5); // TODO conversion euro - old local currencies - price = Number(barcode.substring(7,5))/100 - weight = '' + price = Number(barcode.substring(7,5))/100; + weight = ''; } else if (barcode.substring(0,2) in ['21','23','27','29','25']) { // product sold by weight - barcode = barcode.substring(2,5) - weight = Number(barcode.substring(7,5))/1000 - price = '' + barcode = barcode.substring(2,5); + weight = Number(barcode.substring(7,5))/1000; + price = ''; } else { // product unit - weight = '' - price = '' + weight = ''; + price = ''; } - var scannedProductModel = _.detect(productsCollection.models, function(pc) { return pc.attributes.ean13 === barcode}) - if (weight === '' and price !== '') { - scannedProductModel.price = price - } else if (weight !== '' and price === '') { + var scannedProductModel = _.detect(productsCollection.models, function(pc) { return pc.attributes.ean13 === barcode;}); + if (weight === '' && price !== '') { + scannedProductModel.price = price; + } else if (weight !== '' && price === '') { // TODO sell by weight: how to calculate the price? - scannedProductModel.price *= weight + scannedProductModel.price *= weight; } - selectedOrder.addProduct(scannedProductModel) + selectedOrder.addProduct(scannedProductModel); - codeNumbers = [] + codeNumbers = []; } } else { // NaN - codeNumbers = [] + codeNumbers = []; } }) $('.searchbox input').keyup(function() { From 08e30b83bf5759d7c75848ab8585e22f78f6fdf6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20L=C3=B3pez=20L=C3=B3pez=20=28OpenERP=29?= Date: Mon, 26 Mar 2012 14:27:43 +0200 Subject: [PATCH 017/201] [imp] warning product not recognized bzr revid: rlo@openerp.com-20120326122743-3m6c24j0dnb9c1yq --- addons/point_of_sale/static/src/js/pos.js | 18 +++++++++++++++++- addons/point_of_sale/static/src/xml/pos.xml | 3 +++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/addons/point_of_sale/static/src/js/pos.js b/addons/point_of_sale/static/src/js/pos.js index 84c4d695829..70de88dd93e 100644 --- a/addons/point_of_sale/static/src/js/pos.js +++ b/addons/point_of_sale/static/src/js/pos.js @@ -1298,10 +1298,26 @@ openerp.point_of_sale = function(db) { price = ''; } var scannedProductModel = _.detect(productsCollection.models, function(pc) { return pc.attributes.ean13 === barcode;}); + if (scannedProductModel == undefined) { + // product not recognized, raise warning + $(QWeb.render('pos-scan-warning')).dialog({ + resizable: false, + height:160, + modal: true, + title: "Warning", + buttons: { + "OK": function() { + $( this ).dialog( "close" ); + }, + } + }); + } if (weight === '' && price !== '') { + // product sold by price scannedProductModel.price = price; } else if (weight !== '' && price === '') { - // TODO sell by weight: how to calculate the price? + // product sold by weight + // TODO check how to calculate the price scannedProductModel.price *= weight; } selectedOrder.addProduct(scannedProductModel); diff --git a/addons/point_of_sale/static/src/xml/pos.xml b/addons/point_of_sale/static/src/xml/pos.xml index d24d16713d8..16192f208c8 100644 --- a/addons/point_of_sale/static/src/xml/pos.xml +++ b/addons/point_of_sale/static/src/xml/pos.xml @@ -141,6 +141,9 @@ + +
The product could not be recognized. Please contact an employee.
+
There are pending operations that could not be saved into the database, are you sure you want to exit?
From d849defd2a2cb0aa2827970e88e413fedc98ed01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20L=C3=B3pez=20L=C3=B3pez=20=28OpenERP=29?= Date: Mon, 26 Mar 2012 16:39:00 +0200 Subject: [PATCH 018/201] [imp] dialog height bzr revid: rlo@openerp.com-20120326143900-qb6g01z9wsdzuyac --- addons/point_of_sale/static/src/js/pos.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/point_of_sale/static/src/js/pos.js b/addons/point_of_sale/static/src/js/pos.js index 70de88dd93e..18c6511629f 100644 --- a/addons/point_of_sale/static/src/js/pos.js +++ b/addons/point_of_sale/static/src/js/pos.js @@ -1302,7 +1302,7 @@ openerp.point_of_sale = function(db) { // product not recognized, raise warning $(QWeb.render('pos-scan-warning')).dialog({ resizable: false, - height:160, + height:220, modal: true, title: "Warning", buttons: { From e70087c87451593d897d2aa3cf98a65ce538c596 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20L=C3=B3pez=20L=C3=B3pez=20=28OpenERP=29?= Date: Tue, 27 Mar 2012 08:53:22 +0200 Subject: [PATCH 019/201] [imp] add control digit bzr revid: rlo@openerp.com-20120327065322-a7gegbmtrtjexn41 --- addons/point_of_sale/static/src/js/pos.js | 1 + 1 file changed, 1 insertion(+) diff --git a/addons/point_of_sale/static/src/js/pos.js b/addons/point_of_sale/static/src/js/pos.js index 18c6511629f..369c7fbc032 100644 --- a/addons/point_of_sale/static/src/js/pos.js +++ b/addons/point_of_sale/static/src/js/pos.js @@ -1281,6 +1281,7 @@ openerp.point_of_sale = function(db) { console.log('barcode: ' + barcode); var selectedOrder = self.shop.get('selectedOrder'); var productsCollection = self.shop.get('products'); + var controlDigit = Number(barcode.charAt(12)); if (barcode.substring(0,2) in ['02', '22', '24', '26', '28']) { // product with a specific price - specified into the barcode barcode = barcode.substring(2,5); From b6fa154cbc153aef1db14cc44c5ecc15443a7b04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20L=C3=B3pez=20L=C3=B3pez=20=28OpenERP=29?= Date: Tue, 27 Mar 2012 10:25:45 +0200 Subject: [PATCH 020/201] [imp] add control digit bzr revid: rlo@openerp.com-20120327082545-y3fzc2rysefx0k2q --- addons/point_of_sale/static/src/js/pos.js | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/addons/point_of_sale/static/src/js/pos.js b/addons/point_of_sale/static/src/js/pos.js index 369c7fbc032..ecaecdf91d5 100644 --- a/addons/point_of_sale/static/src/js/pos.js +++ b/addons/point_of_sale/static/src/js/pos.js @@ -1281,7 +1281,28 @@ openerp.point_of_sale = function(db) { console.log('barcode: ' + barcode); var selectedOrder = self.shop.get('selectedOrder'); var productsCollection = self.shop.get('products'); - var controlDigit = Number(barcode.charAt(12)); + // EAN digit control calculation, based on http://es.wikipedia.org/wiki/EAN#Javascript , licensed under Creative Commons Attribution-Share-Alike License 3.0 + var checksum = 0; + ean13Reversed = codeNumbers.reverse(); + for (var pos in ean13Reversed) { + checksum += ean13Reversed[pos] * (3 - 2 * (pos % 2)); + } + checksum = (10 - (checksum % 10)) % 10; + // End of EAN digit control calculation + if (Number(barcode.charAt(12)) !== checksum) { + // barcode read error, raise warning + $(QWeb.render('pos-scan-warning')).dialog({ + resizable: false, + height:220, + modal: true, + title: "Warning", + buttons: { + "OK": function() { + $( this ).dialog( "close" ); + }, + } + }); + } if (barcode.substring(0,2) in ['02', '22', '24', '26', '28']) { // product with a specific price - specified into the barcode barcode = barcode.substring(2,5); From e5150890236f42e48e4055dcba8aef39e093c8a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20L=C3=B3pez=20L=C3=B3pez=20=28OpenERP=29?= Date: Tue, 27 Mar 2012 16:14:22 +0200 Subject: [PATCH 021/201] [imp] currency conversion - yet incomplete bzr revid: rlo@openerp.com-20120327141422-9grv8dw70q89ayx5 --- addons/point_of_sale/__openerp__.py | 2 +- addons/point_of_sale/static/src/js/pos.js | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/addons/point_of_sale/__openerp__.py b/addons/point_of_sale/__openerp__.py index 911ccf24bb5..f39c05c5e7d 100644 --- a/addons/point_of_sale/__openerp__.py +++ b/addons/point_of_sale/__openerp__.py @@ -40,7 +40,7 @@ Main features : """, 'author': 'OpenERP SA', 'images': ['images/cash_registers.jpeg', 'images/pos_analysis.jpeg','images/register_analysis.jpeg','images/sale_order_pos.jpeg','images/product_pos.jpeg'], - 'depends': ['sale'], + 'depends': ['sale','account'], 'init_xml': [], 'update_xml': [ diff --git a/addons/point_of_sale/static/src/js/pos.js b/addons/point_of_sale/static/src/js/pos.js index ecaecdf91d5..77b38d9b872 100644 --- a/addons/point_of_sale/static/src/js/pos.js +++ b/addons/point_of_sale/static/src/js/pos.js @@ -1249,6 +1249,7 @@ openerp.point_of_sale = function(db) { id = 0; } c = pos.categories[id]; + currency = pos.get('currency'); this.categoryView.ancestors = c.ancestors; this.categoryView.children = c.children; this.categoryView.renderElement(); @@ -1307,6 +1308,7 @@ openerp.point_of_sale = function(db) { // product with a specific price - specified into the barcode barcode = barcode.substring(2,5); // TODO conversion euro - old local currencies + new db.web.Model('res.currency').get_func('_get_conversion_rate')(['FRF', 'EUR']).pipe(function(result){}); price = Number(barcode.substring(7,5))/100; weight = ''; } else if (barcode.substring(0,2) in ['21','23','27','29','25']) { From ce55e113ed3b01506664b9b59b0d9c9999a24ee3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20L=C3=B3pez=20L=C3=B3pez=20=28OpenERP=29?= Date: Wed, 28 Mar 2012 12:03:37 +0200 Subject: [PATCH 022/201] [imp] control digit check + more demo data bzr revid: rlo@openerp.com-20120328100337-aypdlk8o9revqpm6 --- addons/point_of_sale/point_of_sale_demo.xml | 1 + addons/point_of_sale/static/src/js/pos.js | 40 ++++++++++++++------- 2 files changed, 29 insertions(+), 12 deletions(-) diff --git a/addons/point_of_sale/point_of_sale_demo.xml b/addons/point_of_sale/point_of_sale_demo.xml index 3638b0faa72..95c0192f6c7 100644 --- a/addons/point_of_sale/point_of_sale_demo.xml +++ b/addons/point_of_sale/point_of_sale_demo.xml @@ -687,6 +687,7 @@ 1.00 Leffe Blonde 33cl + 5410228142218 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 diff --git a/addons/point_of_sale/static/src/js/pos.js b/addons/point_of_sale/static/src/js/pos.js index 77b38d9b872..238366163d9 100644 --- a/addons/point_of_sale/static/src/js/pos.js +++ b/addons/point_of_sale/static/src/js/pos.js @@ -1266,31 +1266,46 @@ openerp.point_of_sale = function(db) { if (!isNaN(Number(String.fromCharCode(e.keyCode)))) { // a number if (codeNumbers.length==0) { - codeNumbers.timeStamp = new Date().getTime(); + timeStamp = new Date().getTime(); } else { - if (codeNumbers.lastTimeStamp + 30 < new Date().getTime()) { + if (lastTimeStamp + 30 < new Date().getTime()) { // not a barcode reader codeNumbers = []; - codeNumbers.timeStamp = new Date().getTime(); + timeStamp = new Date().getTime(); } } codeNumbers.push(e.keyCode - 48); - codeNumbers.lastTimeStamp = new Date().getTime(); + lastTimeStamp = new Date().getTime(); if (codeNumbers.length == 13) { // a barcode reader var barcode = codeNumbers.join(''); console.log('barcode: ' + barcode); var selectedOrder = self.shop.get('selectedOrder'); var productsCollection = self.shop.get('products'); - // EAN digit control calculation, based on http://es.wikipedia.org/wiki/EAN#Javascript , licensed under Creative Commons Attribution-Share-Alike License 3.0 - var checksum = 0; - ean13Reversed = codeNumbers.reverse(); - for (var pos in ean13Reversed) { - checksum += ean13Reversed[pos] * (3 - 2 * (pos % 2)); - } - checksum = (10 - (checksum % 10)) % 10; + // EAN digit control calculation + var st1 = codeNumbers; + var st2 = st1.slice(0,12).reverse(); + var countSt3 = 1; + var st3 = 0; + $.each(st2, function() { + if (countSt3%2 === 1) { + st3 += this; + } + countSt3 ++; + }); + st3 *= 3; + var st4 = 0; + var countSt4 = 1; + $.each(st2, function() { + if (countSt4%2 === 0) { + st4 += this; + } + countSt4 ++; + }); + var st5 = st3 + st4; + var dc = (10 - (st5%10)) % 10; // End of EAN digit control calculation - if (Number(barcode.charAt(12)) !== checksum) { + if (Number(barcode.charAt(12)) !== dc) { // barcode read error, raise warning $(QWeb.render('pos-scan-warning')).dialog({ resizable: false, @@ -1332,6 +1347,7 @@ openerp.point_of_sale = function(db) { buttons: { "OK": function() { $( this ).dialog( "close" ); + return; }, } }); From 6178afe4be0a386022e02d22ee099517f3b88f26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20L=C3=B3pez=20L=C3=B3pez=20=28OpenERP=29?= Date: Wed, 28 Mar 2012 15:46:56 +0200 Subject: [PATCH 023/201] [imp] unit barcode bzr revid: rlo@openerp.com-20120328134656-zxxdpa861q2qk3r5 --- addons/point_of_sale/static/src/js/pos.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/addons/point_of_sale/static/src/js/pos.js b/addons/point_of_sale/static/src/js/pos.js index 238366163d9..ec6726a42ef 100644 --- a/addons/point_of_sale/static/src/js/pos.js +++ b/addons/point_of_sale/static/src/js/pos.js @@ -1254,6 +1254,7 @@ openerp.point_of_sale = function(db) { this.categoryView.children = c.children; this.categoryView.renderElement(); this.categoryView.start(); + allProducts = pos.store.get('product.product'); products = pos.store.get('product.product').filter( function(p) { var _ref; return _ref = p.pos_categ_id[0], _.indexOf(c.subtree, _ref) >= 0; @@ -1279,9 +1280,7 @@ openerp.point_of_sale = function(db) { if (codeNumbers.length == 13) { // a barcode reader var barcode = codeNumbers.join(''); - console.log('barcode: ' + barcode); var selectedOrder = self.shop.get('selectedOrder'); - var productsCollection = self.shop.get('products'); // EAN digit control calculation var st1 = codeNumbers; var st2 = st1.slice(0,12).reverse(); @@ -1315,6 +1314,7 @@ openerp.point_of_sale = function(db) { buttons: { "OK": function() { $( this ).dialog( "close" ); + return; }, } }); @@ -1336,7 +1336,7 @@ openerp.point_of_sale = function(db) { weight = ''; price = ''; } - var scannedProductModel = _.detect(productsCollection.models, function(pc) { return pc.attributes.ean13 === barcode;}); + var scannedProductModel = _.detect(allProducts, function(pc) { return pc.ean13 === barcode;}); if (scannedProductModel == undefined) { // product not recognized, raise warning $(QWeb.render('pos-scan-warning')).dialog({ @@ -1360,7 +1360,7 @@ openerp.point_of_sale = function(db) { // TODO check how to calculate the price scannedProductModel.price *= weight; } - selectedOrder.addProduct(scannedProductModel); + selectedOrder.addProduct(new Product(scannedProductModel)); codeNumbers = []; } @@ -1368,7 +1368,7 @@ openerp.point_of_sale = function(db) { // NaN codeNumbers = []; } - }) + }); $('.searchbox input').keyup(function() { var m, s; s = $(this).val().toLowerCase(); From c67aea4ed7e61eed9fd02835cf0ade4d883e1f93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20L=C3=B3pez=20L=C3=B3pez=20=28OpenERP=29?= Date: Thu, 29 Mar 2012 12:04:27 +0200 Subject: [PATCH 024/201] [imp] improved dc check bzr revid: rlo@openerp.com-20120329100427-r5pk59e5dnne99ou --- addons/point_of_sale/static/src/js/pos.js | 57 +++++++++++++---------- 1 file changed, 33 insertions(+), 24 deletions(-) diff --git a/addons/point_of_sale/static/src/js/pos.js b/addons/point_of_sale/static/src/js/pos.js index ec6726a42ef..81ac6cff516 100644 --- a/addons/point_of_sale/static/src/js/pos.js +++ b/addons/point_of_sale/static/src/js/pos.js @@ -1260,6 +1260,38 @@ openerp.point_of_sale = function(db) { return _ref = p.pos_categ_id[0], _.indexOf(c.subtree, _ref) >= 0; }); (this.shop.get('products')).reset(products); + var checkEan = function(code) { + // EAN control digit calculation + var st1 = code.slice(); + var st2 = st1.slice(0,st1.length-1).reverse(); + // some EAN13 barcodes have a length of 12, as they start by 0 + while (st2.length < 12) { + st2.push(0); + } + console.log('code: '+code.join(',')); + console.log('st1: '+st1.join(',')); + console.log('st2: '+st2.join(',')); + var countSt3 = 1; + var st3 = 0; + $.each(st2, function() { + if (countSt3%2 === 1) { + st3 += this; + } + countSt3 ++; + }); + st3 *= 3; + var st4 = 0; + var countSt4 = 1; + $.each(st2, function() { + if (countSt4%2 === 0) { + st4 += this; + } + countSt4 ++; + }); + var st5 = st3 + st4; + var cd = (10 - (st5%10)) % 10; + return code[code.length-1] === cd; + } var self = this; // bind barcode reader event var codeNumbers = []; @@ -1281,30 +1313,7 @@ openerp.point_of_sale = function(db) { // a barcode reader var barcode = codeNumbers.join(''); var selectedOrder = self.shop.get('selectedOrder'); - // EAN digit control calculation - var st1 = codeNumbers; - var st2 = st1.slice(0,12).reverse(); - var countSt3 = 1; - var st3 = 0; - $.each(st2, function() { - if (countSt3%2 === 1) { - st3 += this; - } - countSt3 ++; - }); - st3 *= 3; - var st4 = 0; - var countSt4 = 1; - $.each(st2, function() { - if (countSt4%2 === 0) { - st4 += this; - } - countSt4 ++; - }); - var st5 = st3 + st4; - var dc = (10 - (st5%10)) % 10; - // End of EAN digit control calculation - if (Number(barcode.charAt(12)) !== dc) { + if (!checkEan(codeNumbers)) { // barcode read error, raise warning $(QWeb.render('pos-scan-warning')).dialog({ resizable: false, From d9c673d586e58ee56bc29f2d6df8e4101383ba9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20L=C3=B3pez=20L=C3=B3pez=20=28OpenERP=29?= Date: Thu, 29 Mar 2012 12:24:46 +0200 Subject: [PATCH 025/201] [imp] add method to get exchange rates from the client bzr revid: rlo@openerp.com-20120329102446-wzg853rgzd11f4yg --- addons/account/res_currency.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/addons/account/res_currency.py b/addons/account/res_currency.py index 8c9c6a8b103..6381ca258de 100644 --- a/addons/account/res_currency.py +++ b/addons/account/res_currency.py @@ -25,6 +25,11 @@ from osv import osv class res_currency_account(osv.osv): _inherit = "res.currency" + def get_conversion_rate(self, cr, uid, from_currency, to_currency, context=None): + if context is None: + context = {} + return self._get_conversion_rate(cr, uid, from_currency, to_currency, context=context) + def _get_conversion_rate(self, cr, uid, from_currency, to_currency, context=None): if context is None: context = {} @@ -44,4 +49,4 @@ class res_currency_account(osv.osv): res_currency_account() -# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: \ No newline at end of file +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: From 646e0334c83bd636868ea7d772352ee546859a3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20L=C3=B3pez=20L=C3=B3pez=20=28OpenERP=29?= Date: Thu, 29 Mar 2012 12:25:48 +0200 Subject: [PATCH 026/201] [fix] fix 'in' expression when reading a price or weight barcode bzr revid: rlo@openerp.com-20120329102548-gnffz8am3m755agq --- addons/point_of_sale/static/src/js/pos.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/addons/point_of_sale/static/src/js/pos.js b/addons/point_of_sale/static/src/js/pos.js index 81ac6cff516..17c9da5e92c 100644 --- a/addons/point_of_sale/static/src/js/pos.js +++ b/addons/point_of_sale/static/src/js/pos.js @@ -1328,14 +1328,14 @@ openerp.point_of_sale = function(db) { } }); } - if (barcode.substring(0,2) in ['02', '22', '24', '26', '28']) { + if (barcode.substring(0,2) in {'02':'', '22':'', '24':'', '26':'', '28':''}) { // product with a specific price - specified into the barcode barcode = barcode.substring(2,5); // TODO conversion euro - old local currencies new db.web.Model('res.currency').get_func('_get_conversion_rate')(['FRF', 'EUR']).pipe(function(result){}); price = Number(barcode.substring(7,5))/100; weight = ''; - } else if (barcode.substring(0,2) in ['21','23','27','29','25']) { + } else if (barcode.substring(0,2) in {'21':'','23':'','27':'','29':'','25':''}) { // product sold by weight barcode = barcode.substring(2,5); weight = Number(barcode.substring(7,5))/1000; From 0a77d53dea33d0b66d631d21e62a095bc0e22d78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20L=C3=B3pez=20L=C3=B3pez=20=28OpenERP=29?= Date: Thu, 29 Mar 2012 14:12:23 +0200 Subject: [PATCH 027/201] [imp] currency conversion (partial) bzr revid: rlo@openerp.com-20120329121223-gb0d9sbpdjijaf0t --- addons/point_of_sale/static/src/js/pos.js | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/addons/point_of_sale/static/src/js/pos.js b/addons/point_of_sale/static/src/js/pos.js index 17c9da5e92c..95c8bfa1d32 100644 --- a/addons/point_of_sale/static/src/js/pos.js +++ b/addons/point_of_sale/static/src/js/pos.js @@ -1268,9 +1268,6 @@ openerp.point_of_sale = function(db) { while (st2.length < 12) { st2.push(0); } - console.log('code: '+code.join(',')); - console.log('st1: '+st1.join(',')); - console.log('st2: '+st2.join(',')); var countSt3 = 1; var st3 = 0; $.each(st2, function() { @@ -1330,15 +1327,15 @@ openerp.point_of_sale = function(db) { } if (barcode.substring(0,2) in {'02':'', '22':'', '24':'', '26':'', '28':''}) { // product with a specific price - specified into the barcode - barcode = barcode.substring(2,5); + price = Number(barcode.substring(7,12))/100; + barcode = barcode.substring(2,7); // TODO conversion euro - old local currencies - new db.web.Model('res.currency').get_func('_get_conversion_rate')(['FRF', 'EUR']).pipe(function(result){}); - price = Number(barcode.substring(7,5))/100; + new db.web.Model('res.currency').get_func('get_conversion_rate')([pos.session.uid,'FRF', pos.get('currency'), {}]).pipe(function(result){}); weight = ''; } else if (barcode.substring(0,2) in {'21':'','23':'','27':'','29':'','25':''}) { // product sold by weight - barcode = barcode.substring(2,5); - weight = Number(barcode.substring(7,5))/1000; + weight = Number(barcode.substring(7,12))/1000; + barcode = barcode.substring(2,7); price = ''; } else { // product unit From 09506d254af19e1a626413d0eab3f4a043cc0d1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20L=C3=B3pez=20L=C3=B3pez=20=28OpenERP=29?= Date: Fri, 30 Mar 2012 12:02:21 +0200 Subject: [PATCH 028/201] [imp] partially working price/weight barcodes bzr revid: rlo@openerp.com-20120330100221-tjzbpqfp1wzv9n6w --- addons/point_of_sale/static/src/js/pos.js | 54 ++++++++++++----------- 1 file changed, 29 insertions(+), 25 deletions(-) diff --git a/addons/point_of_sale/static/src/js/pos.js b/addons/point_of_sale/static/src/js/pos.js index 95c8bfa1d32..6c86194201c 100644 --- a/addons/point_of_sale/static/src/js/pos.js +++ b/addons/point_of_sale/static/src/js/pos.js @@ -85,7 +85,8 @@ openerp.point_of_sale = function(db) { }, this)); }, this)); $.when(this.fetch('pos.category', ['name', 'parent_id', 'child_id']), - this.fetch('product.product', ['name', 'list_price', 'pos_categ_id', 'taxes_id', 'product_image_small', 'ean13'], [['pos_categ_id', '!=', 'false']]), + this.fetch('product.product', ['name', 'list_price', 'pos_categ_id', 'taxes_id', 'product_image_small', 'ean13', 'id'], [['pos_categ_id', '!=', 'false']]), + this.fetch('product.packaging', ['product_id', 'ean']), this.fetch('account.bank.statement', ['account_id', 'currency', 'journal_id', 'state', 'name'], [['state', '=', 'open'], ['user_id', '=', this.session.uid]]), this.fetch('account.journal', ['auto_cash', 'check_dtls', 'currency', 'name', 'type']), @@ -1255,6 +1256,7 @@ openerp.point_of_sale = function(db) { this.categoryView.renderElement(); this.categoryView.start(); allProducts = pos.store.get('product.product'); + allPackages = pos.store.get('product.packaging'); products = pos.store.get('product.product').filter( function(p) { var _ref; return _ref = p.pos_categ_id[0], _.indexOf(c.subtree, _ref) >= 0; @@ -1308,8 +1310,6 @@ openerp.point_of_sale = function(db) { lastTimeStamp = new Date().getTime(); if (codeNumbers.length == 13) { // a barcode reader - var barcode = codeNumbers.join(''); - var selectedOrder = self.shop.get('selectedOrder'); if (!checkEan(codeNumbers)) { // barcode read error, raise warning $(QWeb.render('pos-scan-warning')).dialog({ @@ -1325,25 +1325,36 @@ openerp.point_of_sale = function(db) { } }); } + var barcode = codeNumbers.join(''); + var selectedOrder = self.shop.get('selectedOrder'); if (barcode.substring(0,2) in {'02':'', '22':'', '24':'', '26':'', '28':''}) { - // product with a specific price - specified into the barcode + // PRICE barcode price = Number(barcode.substring(7,12))/100; - barcode = barcode.substring(2,7); - // TODO conversion euro - old local currencies - new db.web.Model('res.currency').get_func('get_conversion_rate')([pos.session.uid,'FRF', pos.get('currency'), {}]).pipe(function(result){}); - weight = ''; + barcode = barcode.substring(0,7); + var scannedPackaging = _.detect(allPackages, function(pack) { return pack.ean !== undefined && pack.ean.substring(0,7) === barcode;}); + if (scannedPackaging !== undefined) { + var scannedProductModel = _.detect(allProducts, function(pc) { return pc.id === scannedPackaging.product_id[0];}); + console.log(scannedProductModel.price); + scannedProductModel.price = price; + console.log('price: '+price); + } } else if (barcode.substring(0,2) in {'21':'','23':'','27':'','29':'','25':''}) { - // product sold by weight + // WEIGHT barcode weight = Number(barcode.substring(7,12))/1000; - barcode = barcode.substring(2,7); - price = ''; + console.log(weight); + barcode = barcode.substring(0,7); + var scannedPackaging = _.detect(allPackages, function(pack) { return pack.ean !== undefined && pack.ean.substring(0,7) === barcode;}); + if (scannedPackaging !== undefined) { + var scannedProductModel = _.detect(allProducts, function(pc) { return pc.ean13 === scannedPackaging.product_id[0];}); + console.log(scannedProductModel.price); + scannedProductModel.price *= weight; + console.log(weight); + } } else { - // product unit - weight = ''; - price = ''; + // UNIT barcode + var scannedProductModel = _.detect(allProducts, function(pc) { return pc.ean13 === barcode;}); } - var scannedProductModel = _.detect(allProducts, function(pc) { return pc.ean13 === barcode;}); - if (scannedProductModel == undefined) { + if (scannedProductModel === undefined) { // product not recognized, raise warning $(QWeb.render('pos-scan-warning')).dialog({ resizable: false, @@ -1357,16 +1368,9 @@ openerp.point_of_sale = function(db) { }, } }); + } else { + selectedOrder.addProduct(new Product(scannedProductModel)); } - if (weight === '' && price !== '') { - // product sold by price - scannedProductModel.price = price; - } else if (weight !== '' && price === '') { - // product sold by weight - // TODO check how to calculate the price - scannedProductModel.price *= weight; - } - selectedOrder.addProduct(new Product(scannedProductModel)); codeNumbers = []; } From de6a9009725afe731ce682b5b65a6a62215d3578 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20L=C3=B3pez=20L=C3=B3pez=20=28OpenERP=29?= Date: Fri, 30 Mar 2012 13:16:17 +0200 Subject: [PATCH 029/201] [imp] working price barcodes bzr revid: rlo@openerp.com-20120330111617-fnc85bpk26eevicc --- addons/point_of_sale/static/src/js/pos.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/addons/point_of_sale/static/src/js/pos.js b/addons/point_of_sale/static/src/js/pos.js index 6c86194201c..98930e284cd 100644 --- a/addons/point_of_sale/static/src/js/pos.js +++ b/addons/point_of_sale/static/src/js/pos.js @@ -1334,9 +1334,7 @@ openerp.point_of_sale = function(db) { var scannedPackaging = _.detect(allPackages, function(pack) { return pack.ean !== undefined && pack.ean.substring(0,7) === barcode;}); if (scannedPackaging !== undefined) { var scannedProductModel = _.detect(allProducts, function(pc) { return pc.id === scannedPackaging.product_id[0];}); - console.log(scannedProductModel.price); - scannedProductModel.price = price; - console.log('price: '+price); + scannedProductModel.list_price = price; } } else if (barcode.substring(0,2) in {'21':'','23':'','27':'','29':'','25':''}) { // WEIGHT barcode From caba6de06e850006689ea4d318807057e97bae85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20L=C3=B3pez=20L=C3=B3pez=20=28OpenERP=29?= Date: Fri, 30 Mar 2012 13:42:25 +0200 Subject: [PATCH 030/201] [imp] working weight barcodes bzr revid: rlo@openerp.com-20120330114225-iouhxmyv3hb3xbcd --- addons/point_of_sale/static/src/js/pos.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/addons/point_of_sale/static/src/js/pos.js b/addons/point_of_sale/static/src/js/pos.js index 98930e284cd..4b63740c9f9 100644 --- a/addons/point_of_sale/static/src/js/pos.js +++ b/addons/point_of_sale/static/src/js/pos.js @@ -1339,14 +1339,12 @@ openerp.point_of_sale = function(db) { } else if (barcode.substring(0,2) in {'21':'','23':'','27':'','29':'','25':''}) { // WEIGHT barcode weight = Number(barcode.substring(7,12))/1000; - console.log(weight); barcode = barcode.substring(0,7); var scannedPackaging = _.detect(allPackages, function(pack) { return pack.ean !== undefined && pack.ean.substring(0,7) === barcode;}); if (scannedPackaging !== undefined) { - var scannedProductModel = _.detect(allProducts, function(pc) { return pc.ean13 === scannedPackaging.product_id[0];}); - console.log(scannedProductModel.price); - scannedProductModel.price *= weight; - console.log(weight); + var scannedProductModel = _.detect(allProducts, function(pc) { return pc.id === scannedPackaging.product_id[0];}); + scannedProductModel.list_price *= weight; + scannedProductModel.name += ' - ' + weight + ' Kg.' } } else { // UNIT barcode From e3a06211124d62b1b0f8e5b62a767960c0cd0737 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20L=C3=B3pez=20L=C3=B3pez=20=28OpenERP=29?= Date: Fri, 30 Mar 2012 14:42:07 +0200 Subject: [PATCH 031/201] [fix] fix search field 'clear' button bzr revid: rlo@openerp.com-20120330124207-5c4lb61al2c1q8s2 --- addons/point_of_sale/static/src/js/pos.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/point_of_sale/static/src/js/pos.js b/addons/point_of_sale/static/src/js/pos.js index 4b63740c9f9..d845fe06679 100644 --- a/addons/point_of_sale/static/src/js/pos.js +++ b/addons/point_of_sale/static/src/js/pos.js @@ -1390,7 +1390,7 @@ openerp.point_of_sale = function(db) { return (self.shop.get('products')).reset(m); }); return $('.search-clear').click( function() { - (this.shop.get('products')).reset(products); + (self.shop.get('products')).reset(products); $('.searchbox input').val('').focus(); return $('.search-clear').fadeOut(); }); From 2d672a4089587934baf092b9b367d46df0dfc6e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20L=C3=B3pez=20L=C3=B3pez=20=28OpenERP=29?= Date: Fri, 30 Mar 2012 15:23:04 +0200 Subject: [PATCH 032/201] [fix] remove some useless changes after review bzr revid: rlo@openerp.com-20120330132304-6kyfsir5efcp9kig --- addons/account/res_currency.py | 5 ----- addons/point_of_sale/__openerp__.py | 2 +- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/addons/account/res_currency.py b/addons/account/res_currency.py index 6381ca258de..ce781a1e11a 100644 --- a/addons/account/res_currency.py +++ b/addons/account/res_currency.py @@ -25,11 +25,6 @@ from osv import osv class res_currency_account(osv.osv): _inherit = "res.currency" - def get_conversion_rate(self, cr, uid, from_currency, to_currency, context=None): - if context is None: - context = {} - return self._get_conversion_rate(cr, uid, from_currency, to_currency, context=context) - def _get_conversion_rate(self, cr, uid, from_currency, to_currency, context=None): if context is None: context = {} diff --git a/addons/point_of_sale/__openerp__.py b/addons/point_of_sale/__openerp__.py index f39c05c5e7d..911ccf24bb5 100644 --- a/addons/point_of_sale/__openerp__.py +++ b/addons/point_of_sale/__openerp__.py @@ -40,7 +40,7 @@ Main features : """, 'author': 'OpenERP SA', 'images': ['images/cash_registers.jpeg', 'images/pos_analysis.jpeg','images/register_analysis.jpeg','images/sale_order_pos.jpeg','images/product_pos.jpeg'], - 'depends': ['sale','account'], + 'depends': ['sale'], 'init_xml': [], 'update_xml': [ From c82330ccb63d598792b17415308316659d3df079 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20L=C3=B3pez=20L=C3=B3pez=20=28OpenERP=29?= Date: Fri, 30 Mar 2012 15:30:01 +0200 Subject: [PATCH 033/201] [fix] remove some useless changes after review bzr revid: rlo@openerp.com-20120330133001-8l5w5d7nsy2vpyh9 --- addons/account/res_currency.py | 1 + 1 file changed, 1 insertion(+) diff --git a/addons/account/res_currency.py b/addons/account/res_currency.py index ce781a1e11a..8e02836274a 100644 --- a/addons/account/res_currency.py +++ b/addons/account/res_currency.py @@ -45,3 +45,4 @@ class res_currency_account(osv.osv): res_currency_account() # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: + From c053aab30edb3bff9e741653352165bc730bfa4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20L=C3=B3pez=20L=C3=B3pez=20=28OpenERP=29?= Date: Mon, 2 Apr 2012 11:13:43 +0200 Subject: [PATCH 034/201] [fix] remove unused code bzr revid: rlo@openerp.com-20120402091343-8dltnmyld7yk2yo2 --- addons/point_of_sale/static/src/js/pos.js | 1 - 1 file changed, 1 deletion(-) diff --git a/addons/point_of_sale/static/src/js/pos.js b/addons/point_of_sale/static/src/js/pos.js index d845fe06679..3253fe85bd9 100644 --- a/addons/point_of_sale/static/src/js/pos.js +++ b/addons/point_of_sale/static/src/js/pos.js @@ -1250,7 +1250,6 @@ openerp.point_of_sale = function(db) { id = 0; } c = pos.categories[id]; - currency = pos.get('currency'); this.categoryView.ancestors = c.ancestors; this.categoryView.children = c.children; this.categoryView.renderElement(); From c407affde98d46158e8f8a85745e25863ed8749f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20van=20der=20Essen?= Date: Mon, 2 Apr 2012 16:25:13 +0200 Subject: [PATCH 035/201] [IMP]Cleaned syntax of the Backbone models bzr revid: fva@openerp.com-20120402142513-funzsxkit810c3q2 --- addons/point_of_sale/static/src/js/pos.js | 232 ++++++++-------------- 1 file changed, 88 insertions(+), 144 deletions(-) diff --git a/addons/point_of_sale/static/src/js/pos.js b/addons/point_of_sale/static/src/js/pos.js index 3253fe85bd9..85aaacc8621 100644 --- a/addons/point_of_sale/static/src/js/pos.js +++ b/addons/point_of_sale/static/src/js/pos.js @@ -224,57 +224,29 @@ openerp.point_of_sale = function(db) { Models --- */ - var CashRegister = (function() { - __extends(CashRegister, Backbone.Model); - function CashRegister() { - CashRegister.__super__.constructor.apply(this, arguments); - } + console.log("Fva Tools Reloaded"); - return CashRegister; - })(); - var CashRegisterCollection = (function() { - __extends(CashRegisterCollection, Backbone.Collection); - function CashRegisterCollection() { - CashRegisterCollection.__super__.constructor.apply(this, arguments); - } + var CashRegister = Backbone.Model.extend({ + }); - CashRegisterCollection.prototype.model = CashRegister; - return CashRegisterCollection; - })(); - var Product = (function() { - __extends(Product, Backbone.Model); - function Product() { - Product.__super__.constructor.apply(this, arguments); - } + var CashRegisterCollection = Backbone.Collection.extend({ + model: CashRegister, + }); - return Product; - })(); - var ProductCollection = (function() { - __extends(ProductCollection, Backbone.Collection); - function ProductCollection() { - ProductCollection.__super__.constructor.apply(this, arguments); - } + var Product = Backbone.Model.extend({ + }); - ProductCollection.prototype.model = Product; - return ProductCollection; - })(); - var Category = (function() { - __extends(Category, Backbone.Model); - function Category() { - Category.__super__.constructor.apply(this, arguments); - } + var ProductCollection = Backbone.Collection.extend({ + model: Product, + }); - return Category; - })(); - var CategoryCollection = (function() { - __extends(CategoryCollection, Backbone.Collection); - function CategoryCollection() { - CategoryCollection.__super__.constructor.apply(this, arguments); - } + var Category = Backbone.Model.extend({ + }); + + var CategoryCollection = Backbone.Collection.extend({ + model: Category, + }); - CategoryCollection.prototype.model = Category; - return CategoryCollection; - })(); /* Each Order contains zero or more Orderlines (i.e. the content of the "shopping cart".) There should only ever be one Orderline per distinct product in an Order. @@ -352,91 +324,73 @@ openerp.point_of_sale = function(db) { }; }, exportAsJSON: function() { - var result; - result = { + return { qty: this.get('quantity'), price_unit: this.get('list_price'), discount: this.get('discount'), product_id: this.get('id') }; - return result; }, }); + var OrderlineCollection = Backbone.Collection.extend({ model: Orderline, }); - /* - Every PaymentLine has all the attributes of the corresponding CashRegister. - */ - var Paymentline = (function() { - __extends(Paymentline, Backbone.Model); - function Paymentline() { - Paymentline.__super__.constructor.apply(this, arguments); - } - Paymentline.prototype.defaults = { - amount: 0 - }; - Paymentline.prototype.getAmount = function() { + // Every PaymentLine has all the attributes of the corresponding CashRegister. + var Paymentline = Backbone.Model.extend({ + defaults: { + amount: 0, + }, + initialize: function(attributes) { + Backbone.Model.prototype.initialize.apply(this, arguments); + }, + getAmount: function(){ return this.get('amount'); - }; - Paymentline.prototype.exportAsJSON = function() { - var result; - result = { + }, + exportAsJSON: function(){ + return { name: db.web.datetime_to_str(new Date()), statement_id: this.get('id'), account_id: (this.get('account_id'))[0], journal_id: (this.get('journal_id'))[0], amount: this.getAmount() }; - return result; - }; - return Paymentline; - })(); - var PaymentlineCollection = (function() { - __extends(PaymentlineCollection, Backbone.Collection); - function PaymentlineCollection() { - PaymentlineCollection.__super__.constructor.apply(this, arguments); - } + }, + }); - PaymentlineCollection.prototype.model = Paymentline; - return PaymentlineCollection; - })(); - var Order = (function() { - __extends(Order, Backbone.Model); - function Order() { - Order.__super__.constructor.apply(this, arguments); - } - - Order.prototype.defaults = { + var PaymentlineCollection = Backbone.Collection.extend({ + model: Paymentline, + }); + + var Order = Backbone.Model.extend({ + defaults:{ validated: false, step: 'products', - }; - Order.prototype.initialize = function() { - this.set({creationDate: new Date}); + }, + initialize: function(attributes){ + Backbone.Model.prototype.initialize.apply(this, arguments); this.set({ - orderLines: new OrderlineCollection - }); - this.set({ - paymentLines: new PaymentlineCollection + creationDate: new Date, + orderLines: new OrderlineCollection, + paymentLines: new PaymentlineCollection, + name: "Order " + this.generateUniqueId(), }); this.bind('change:validated', this.validatedChanged); - return this.set({ - name: "Order " + this.generateUniqueId() - }); - }; - Order.prototype.events = { + return this; + }, + events: { 'change:validated': 'validatedChanged' - }; - Order.prototype.validatedChanged = function() { + }, + validatedChanged: function() { if (this.get("validated") && !this.previous("validated")) { this.set({'step': 'receipt'}); } - } - Order.prototype.generateUniqueId = function() { + }, + generateUniqueId: function() { return new Date().getTime(); - }; - Order.prototype.addProduct = function(product) { + }, + addProduct: function(product) { var existing; existing = (this.get('orderLines')).get(product.id); if (existing != null) { @@ -448,8 +402,8 @@ openerp.point_of_sale = function(db) { this.get('orderLines').remove(line); }, this); } - }; - Order.prototype.addPaymentLine = function(cashRegister) { + }, + addPaymentLine: function(cashRegister) { var newPaymentline; newPaymentline = new Paymentline(cashRegister); /* TODO: Should be 0 for cash-like accounts */ @@ -457,38 +411,38 @@ openerp.point_of_sale = function(db) { amount: this.getDueLeft() }); return (this.get('paymentLines')).add(newPaymentline); - }; - Order.prototype.getName = function() { + }, + getName: function() { return this.get('name'); - }; - Order.prototype.getTotal = function() { + }, + getTotal: function() { return (this.get('orderLines')).reduce((function(sum, orderLine) { return sum + orderLine.getPriceWithTax(); }), 0); - }; - Order.prototype.getTotalTaxExcluded = function() { + }, + getTotalTaxExcluded: function() { return (this.get('orderLines')).reduce((function(sum, orderLine) { return sum + orderLine.getPriceWithoutTax(); }), 0); - }; - Order.prototype.getTax = function() { + }, + getTax: function() { return (this.get('orderLines')).reduce((function(sum, orderLine) { return sum + orderLine.getTax(); }), 0); - }; - Order.prototype.getPaidTotal = function() { + }, + getPaidTotal: function() { return (this.get('paymentLines')).reduce((function(sum, paymentLine) { return sum + paymentLine.getAmount(); }), 0); - }; - Order.prototype.getChange = function() { + }, + getChange: function() { return this.getPaidTotal() - this.getTotal(); - }; - Order.prototype.getDueLeft = function() { + }, + getDueLeft: function() { return this.getTotal() - this.getPaidTotal(); - }; - Order.prototype.exportAsJSON = function() { - var orderLines, paymentLines, result; + }, + exportAsJSON: function() { + var orderLines, paymentLines; orderLines = []; (this.get('orderLines')).each(_.bind( function(item) { return orderLines.push([0, 0, item.exportAsJSON()]); @@ -497,7 +451,7 @@ openerp.point_of_sale = function(db) { (this.get('paymentLines')).each(_.bind( function(item) { return paymentLines.push([0, 0, item.exportAsJSON()]); }, this)); - result = { + return { name: this.getName(), amount_paid: this.getPaidTotal(), amount_total: this.getTotal(), @@ -506,26 +460,15 @@ openerp.point_of_sale = function(db) { lines: orderLines, statement_ids: paymentLines }; - return result; - }; - return Order; - })(); - var OrderCollection = (function() { - __extends(OrderCollection, Backbone.Collection); - function OrderCollection() { - OrderCollection.__super__.constructor.apply(this, arguments); - } + }, + }); - OrderCollection.prototype.model = Order; - return OrderCollection; - })(); - var Shop = (function() { - __extends(Shop, Backbone.Model); - function Shop() { - Shop.__super__.constructor.apply(this, arguments); - } + var OrderCollection = Backbone.Collection.extend({ + model: Order, + }); - Shop.prototype.initialize = function() { + var Shop = Backbone.Model.extend({ + initialize: function() { this.set({ orders: new OrderCollection(), products: new ProductCollection() @@ -543,15 +486,15 @@ openerp.point_of_sale = function(db) { }); } }, this)); - }; - Shop.prototype.addAndSelectOrder = function(newOrder) { + }, + addAndSelectOrder: function(newOrder) { (this.get('orders')).add(newOrder); return this.set({ selectedOrder: newOrder }); - }; - return Shop; - })(); + }, + }); + /* The numpad handles both the choice of the property currently being modified (quantity, price or discount) and the edition of the corresponding numeric value. @@ -618,6 +561,7 @@ openerp.point_of_sale = function(db) { } }, }); + /* --- Views From d7d7500588993348edf3c97b70f2aae7236e32fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20van=20der=20Essen?= Date: Mon, 2 Apr 2012 17:50:05 +0200 Subject: [PATCH 036/201] [IMP]Cleaned more syntax, added comments, removed __extends() bzr revid: fva@openerp.com-20120402155005-6rpin43aho26h1yz --- addons/point_of_sale/static/src/js/pos.js | 53 ++++++++++++----------- 1 file changed, 28 insertions(+), 25 deletions(-) diff --git a/addons/point_of_sale/static/src/js/pos.js b/addons/point_of_sale/static/src/js/pos.js index 85aaacc8621..e6868519c55 100644 --- a/addons/point_of_sale/static/src/js/pos.js +++ b/addons/point_of_sale/static/src/js/pos.js @@ -2,22 +2,6 @@ openerp.point_of_sale = function(db) { db.point_of_sale = {}; - var __extends = function(child, parent) { - var __hasProp = Object.prototype.hasOwnProperty; - for (var key in parent) { - if (__hasProp.call(parent, key)) - child[key] = parent[key]; - } - function ctor() { - this.constructor = child; - } - - ctor.prototype = parent.prototype; - child.prototype = new ctor; - child.__super__ = parent.prototype; - return child; - }; - var QWeb = db.web.qweb; var qweb_template = function(template) { return function(ctx) { @@ -719,6 +703,7 @@ openerp.point_of_sale = function(db) { }, on_selected: function() {}, }); + var OrderWidget = db.web.OldWidget.extend({ init: function(parent, options) { this._super(parent); @@ -800,6 +785,7 @@ openerp.point_of_sale = function(db) { $('#total').html(total.toFixed(2)).hide().fadeIn(); }, }); + /* "Products" step. */ @@ -838,6 +824,7 @@ openerp.point_of_sale = function(db) { }, on_change_category: function(id) {}, }); + var ProductWidget = db.web.OldWidget.extend({ tagName:'li', template_fct: qweb_template('pos-product-template'), @@ -860,6 +847,7 @@ openerp.point_of_sale = function(db) { return this; }, }); + var ProductListWidget = db.web.OldWidget.extend({ init: function(parent, options) { this._super(parent); @@ -1011,6 +999,7 @@ openerp.point_of_sale = function(db) { this.currentPaymentLines.last().set({amount: val}); }, }); + var ReceiptWidget = db.web.OldWidget.extend({ init: function(parent, options) { this._super(parent); @@ -1053,6 +1042,7 @@ openerp.point_of_sale = function(db) { $('.pos-receipt-container', this.$element).html(qweb_template('pos-ticket')({widget:this})); }, }); + var OrderButtonWidget = db.web.OldWidget.extend({ tagName: 'li', template_fct: qweb_template('pos-order-selector-button-template'), @@ -1092,6 +1082,7 @@ openerp.point_of_sale = function(db) { this.$element.addClass('order-selector-button'); } }); + var ShopWidget = db.web.OldWidget.extend({ init: function(parent, options) { this._super(parent); @@ -1172,7 +1163,9 @@ openerp.point_of_sale = function(db) { } }, }); + var App = (function() { + function App($element) { this.initialize($element); } @@ -1188,16 +1181,18 @@ openerp.point_of_sale = function(db) { this.categoryView.on_change_category.add_last(_.bind(this.category, this)); this.category(); }; + App.prototype.category = function(id) { - var c, products; - if (id == null) { - id = 0; - } + var c, products, self = this; + + id = !id ? 0 : id; + c = pos.categories[id]; this.categoryView.ancestors = c.ancestors; this.categoryView.children = c.children; this.categoryView.renderElement(); this.categoryView.start(); + allProducts = pos.store.get('product.product'); allPackages = pos.store.get('product.packaging'); products = pos.store.get('product.product').filter( function(p) { @@ -1205,8 +1200,9 @@ openerp.point_of_sale = function(db) { return _ref = p.pos_categ_id[0], _.indexOf(c.subtree, _ref) >= 0; }); (this.shop.get('products')).reset(products); + + //returns true if the code is a valid EAN codebar number by checking the control digit. var checkEan = function(code) { - // EAN control digit calculation var st1 = code.slice(); var st2 = st1.slice(0,st1.length-1).reverse(); // some EAN13 barcodes have a length of 12, as they start by 0 @@ -1234,12 +1230,18 @@ openerp.point_of_sale = function(db) { var cd = (10 - (st5%10)) % 10; return code[code.length-1] === cd; } - var self = this; - // bind barcode reader event + + // The barcode readers acts as a keyboard, we catch all keyup events and try to find a + // barcode sequence in the typed keys, then act accordingly. + var codeNumbers = []; $('body').delegate('','keyup', function (e){ + + //We only care about numbers if (!isNaN(Number(String.fromCharCode(e.keyCode)))) { - // a number + + // The barcode reader sends keystrokes with a specific interval. + // We look if the typed keys fit in the interval. if (codeNumbers.length==0) { timeStamp = new Date().getTime(); } else { @@ -1291,7 +1293,7 @@ openerp.point_of_sale = function(db) { } } else { // UNIT barcode - var scannedProductModel = _.detect(allProducts, function(pc) { return pc.ean13 === barcode;}); + var scannedProductModel = _.detect(allProducts, function(pc) { return pc.ean13 === barcode;}); //TODO DOES NOT SCALE } if (scannedProductModel === undefined) { // product not recognized, raise warning @@ -1318,6 +1320,7 @@ openerp.point_of_sale = function(db) { codeNumbers = []; } }); + $('.searchbox input').keyup(function() { var m, s; s = $(this).val().toLowerCase(); From a7bdd4e172657f2b5962e13c3eadafa759c12d1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20van=20der=20Essen?= Date: Mon, 2 Apr 2012 18:17:54 +0200 Subject: [PATCH 037/201] [FIX] Remove debug console.log bzr revid: fva@openerp.com-20120402161754-dmhty19ag58aow2q --- addons/point_of_sale/static/src/js/pos.js | 1 - 1 file changed, 1 deletion(-) diff --git a/addons/point_of_sale/static/src/js/pos.js b/addons/point_of_sale/static/src/js/pos.js index e6868519c55..cf8c066c249 100644 --- a/addons/point_of_sale/static/src/js/pos.js +++ b/addons/point_of_sale/static/src/js/pos.js @@ -208,7 +208,6 @@ openerp.point_of_sale = function(db) { Models --- */ - console.log("Fva Tools Reloaded"); var CashRegister = Backbone.Model.extend({ }); From 34610c1e61c5de6759b82e51fcafcb8f26e88697 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20L=C3=B3pez=20L=C3=B3pez=20=28OpenERP=29?= Date: Tue, 3 Apr 2012 10:28:03 +0200 Subject: [PATCH 038/201] [fix] modify according to review: split functionalities into different functions bzr revid: rlo@openerp.com-20120403082803-hvvd65cl64qmzvms --- addons/point_of_sale/static/src/js/pos.js | 54 +++++++++++++---------- 1 file changed, 30 insertions(+), 24 deletions(-) diff --git a/addons/point_of_sale/static/src/js/pos.js b/addons/point_of_sale/static/src/js/pos.js index cf8c066c249..2ec692618f0 100644 --- a/addons/point_of_sale/static/src/js/pos.js +++ b/addons/point_of_sale/static/src/js/pos.js @@ -1234,6 +1234,35 @@ openerp.point_of_sale = function(db) { // barcode sequence in the typed keys, then act accordingly. var codeNumbers = []; + + var getProductByEAN = function(ean) { + var prefix = ean.substring(0,2); + var scannedProductModel = undefined; + if (prefix in {'02':'', '22':'', '24':'', '26':'', '28':''}) { + // PRICE barcode + var itemCode = ean.substring(0,7); + var scannedPackaging = _.detect(allPackages, function(pack) { return pack.ean !== undefined && pack.ean.substring(0,7) === itemCode;}); + if (scannedPackaging !== undefined) { + scannedProductModel = _.detect(allProducts, function(pc) { return pc.id === scannedPackaging.product_id[0];}); + scannedProductModel.list_price = Number(ean.substring(7,12))/100; + } + } else if (prefix in {'21':'','23':'','27':'','29':'','25':''}) { + // WEIGHT barcode + var weight = Number(barcode.substring(7,12))/1000; + var itemCode = ean.substring(0,7); + var scannedPackaging = _.detect(allPackages, function(pack) { return pack.ean !== undefined && pack.ean.substring(0,7) === itemCode;}); + if (scannedPackaging !== undefined) { + scannedProductModel = _.detect(allProducts, function(pc) { return pc.id === scannedPackaging.product_id[0];}); + scannedProductModel.list_price *= weight; + scannedProductModel.name += ' - ' + weight + ' Kg.'; + } + } else { + // UNIT barcode + scannedProductModel = _.detect(allProducts, function(pc) { return pc.ean13 === ean;}); //TODO DOES NOT SCALE + } + return scannedProductModel; + } + $('body').delegate('','keyup', function (e){ //We only care about numbers @@ -1269,31 +1298,8 @@ openerp.point_of_sale = function(db) { } }); } - var barcode = codeNumbers.join(''); var selectedOrder = self.shop.get('selectedOrder'); - if (barcode.substring(0,2) in {'02':'', '22':'', '24':'', '26':'', '28':''}) { - // PRICE barcode - price = Number(barcode.substring(7,12))/100; - barcode = barcode.substring(0,7); - var scannedPackaging = _.detect(allPackages, function(pack) { return pack.ean !== undefined && pack.ean.substring(0,7) === barcode;}); - if (scannedPackaging !== undefined) { - var scannedProductModel = _.detect(allProducts, function(pc) { return pc.id === scannedPackaging.product_id[0];}); - scannedProductModel.list_price = price; - } - } else if (barcode.substring(0,2) in {'21':'','23':'','27':'','29':'','25':''}) { - // WEIGHT barcode - weight = Number(barcode.substring(7,12))/1000; - barcode = barcode.substring(0,7); - var scannedPackaging = _.detect(allPackages, function(pack) { return pack.ean !== undefined && pack.ean.substring(0,7) === barcode;}); - if (scannedPackaging !== undefined) { - var scannedProductModel = _.detect(allProducts, function(pc) { return pc.id === scannedPackaging.product_id[0];}); - scannedProductModel.list_price *= weight; - scannedProductModel.name += ' - ' + weight + ' Kg.' - } - } else { - // UNIT barcode - var scannedProductModel = _.detect(allProducts, function(pc) { return pc.ean13 === barcode;}); //TODO DOES NOT SCALE - } + var scannedProductModel = getProductByEAN(codeNumbers.join('')); if (scannedProductModel === undefined) { // product not recognized, raise warning $(QWeb.render('pos-scan-warning')).dialog({ From ef10d28de963cabf8eb5e636b6b726159f4bbf11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20van=20der=20Essen?= Date: Tue, 3 Apr 2012 10:46:58 +0200 Subject: [PATCH 039/201] [FIX] Comment in the wrong place bzr revid: fva@openerp.com-20120403084658-j19s285vjd307ag7 --- addons/point_of_sale/static/src/js/pos.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/addons/point_of_sale/static/src/js/pos.js b/addons/point_of_sale/static/src/js/pos.js index 2ec692618f0..a03e34a3e53 100644 --- a/addons/point_of_sale/static/src/js/pos.js +++ b/addons/point_of_sale/static/src/js/pos.js @@ -1230,11 +1230,10 @@ openerp.point_of_sale = function(db) { return code[code.length-1] === cd; } - // The barcode readers acts as a keyboard, we catch all keyup events and try to find a - // barcode sequence in the typed keys, then act accordingly. - var codeNumbers = []; + // returns a product that has a packaging with an EAN matching to provided ean string. + // returns undefined if no such product is found. var getProductByEAN = function(ean) { var prefix = ean.substring(0,2); var scannedProductModel = undefined; @@ -1263,6 +1262,8 @@ openerp.point_of_sale = function(db) { return scannedProductModel; } + // The barcode readers acts as a keyboard, we catch all keyup events and try to find a + // barcode sequence in the typed keys, then act accordingly. $('body').delegate('','keyup', function (e){ //We only care about numbers From bc05498416ae2fce7625d7f5d842ed0c38fb60a9 Mon Sep 17 00:00:00 2001 From: Stephane Wirtel Date: Wed, 4 Apr 2012 14:31:14 +0200 Subject: [PATCH 040/201] [IMP] point_of_sale: Add the POS Config object bzr revid: stw@openerp.com-20120404123114-e557auyszvy4uy3a --- addons/point_of_sale/point_of_sale.py | 57 ++++++++++++++++ addons/point_of_sale/point_of_sale_view.xml | 76 +++++++++++++++++++++ 2 files changed, 133 insertions(+) diff --git a/addons/point_of_sale/point_of_sale.py b/addons/point_of_sale/point_of_sale.py index 5af78b131b2..012de387776 100644 --- a/addons/point_of_sale/point_of_sale.py +++ b/addons/point_of_sale/point_of_sale.py @@ -33,6 +33,63 @@ import decimal_precision as dp _logger = logging.getLogger(__name__) +class pos_config(osv.osv): + _name = 'pos.config' + + POS_CONFIG_STATE = [('draft', 'Draft'),('active', 'Active'),('inactive', 'Inactive'),('deprecated', 'Deprecated')] + + _columns = { + 'name' : fields.char('Name', size=32, select=1, required=True), + 'journal_ids' : fields.many2many('account.journal', 'pos_config_journal_rel', 'pos_config_id', 'journal_id', 'Payment Methods'), + 'shop_id' : fields.many2one('sale.shop', 'Shop', required=True, select=1), + 'journal_id' : fields.many2one('account.journal', 'Journal', required=True, select=1), + 'profit_account_id' : fields.many2one('account.account', 'Profit Account', required=True, select=1), + 'loss_account_id' : fields.many2one('account.account', 'Loss Account', required=True, select=1), + + 'authorized_cashbox_diff' : fields.integer('Authorized Cashbox Difference (%)'), + 'authorized_cashbox_diff_fixed' : fields.integer('Authorized Cashbox Difference (Fixed Amount)'), + + + 'iface_self_checkout' : fields.boolean('Self Checkout Mode'), + 'iface_websql' : fields.boolean('WebSQL (to store data)'), + 'iface_led' : fields.boolean('LED Interface'), + 'iface_cashdrawer' : fields.boolean('Cashdrawer Interface'), + 'iface_payment_terminal' : fields.boolean('Payment Terminal Interface'), + 'iface_electronic_scale' : fields.boolean('Electronic Scale Interface'), + 'iface_barscan' : fields.boolean('BarScan Interface'), + 'iface_vkeyboard' : fields.boolean('Virtual KeyBoard Interface'), + + 'state' : fields.selection(POS_CONFIG_STATE, 'State', required=True, readonly=True), + + } + + _defaults = { + 'state' : 'draft', + } + + def set_draft(self, cr, uid, ids, context=None): + return self.write(cr, uid, ids, {'state' : 'draft'}, context=context) + + def set_active(self, cr, uid, ids, context=None): + return self.write(cr, uid, ids, {'state' : 'active'}, context=context) + + def set_inactive(self, cr, uid, ids, context=None): + return self.write(cr, uid, ids, {'state' : 'inactive'}, context=context) + + def set_deprecate(self, cr, uid, ids, context=None): + return self.write(cr, uid, ids, {'state' : 'deprecated'}, context=context) + +pos_config() + +class pos_session(osv.osv): + _name = 'pos.session' + + _columns = { + 'config_id' : fields.many2one('pos.config', 'Configuration', required=True, select=1), + } + +pos_session() + class pos_config_journal(osv.osv): """ Point of Sale journal configuration""" _name = 'pos.config.journal' diff --git a/addons/point_of_sale/point_of_sale_view.xml b/addons/point_of_sale/point_of_sale_view.xml index b397bd826cc..4e1fcc7c6db 100644 --- a/addons/point_of_sale/point_of_sale_view.xml +++ b/addons/point_of_sale/point_of_sale_view.xml @@ -790,5 +790,81 @@ + + pos.config.form.view + pos.config + form + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+

Receipt

+
+
+
+
+ + +
+
+
+
@@ -229,20 +265,6 @@ - - -
+ + + + +
+

WELCUM

+
+
+ + +
+
+
+ + +
+
+
+ + +
+
+
+
From 05535cf4e961080a74726c000963c9eca5d7e9f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20van=20der=20Essen?= Date: Thu, 3 May 2012 18:27:46 +0200 Subject: [PATCH 076/201] Added a few more screens bzr revid: fva@openerp.com-20120503162746-lo7m3qno48jcanyz --- addons/point_of_sale/__openerp__.py | 1 + .../static/src/js/pos_devices.js | 31 +- .../point_of_sale/static/src/js/pos_main.js | 2 + .../static/src/js/pos_widgets.js | 369 +++--------------- addons/point_of_sale/static/src/xml/pos.xml | 24 +- 5 files changed, 102 insertions(+), 325 deletions(-) diff --git a/addons/point_of_sale/__openerp__.py b/addons/point_of_sale/__openerp__.py index c159193e299..2c356ab4f31 100644 --- a/addons/point_of_sale/__openerp__.py +++ b/addons/point_of_sale/__openerp__.py @@ -87,6 +87,7 @@ Main features : 'static/src/js/pos_models.js', 'static/src/js/pos_widgets.js', 'static/src/js/pos_devices.js', + 'static/src/js/pos_screens.js', 'static/src/js/pos_main.js' ], 'css': ['static/src/css/pos.css'], diff --git a/addons/point_of_sale/static/src/js/pos_devices.js b/addons/point_of_sale/static/src/js/pos_devices.js index f87754c92c7..86c5bcda635 100644 --- a/addons/point_of_sale/static/src/js/pos_devices.js +++ b/addons/point_of_sale/static/src/js/pos_devices.js @@ -1,72 +1,97 @@ function openerp_pos_devices(module, instance){ //module is instance.point_of_sale - // this module interfaces with the local proxy to communicate to the various hardware devices + window.debug_devices = new (instance.web.Class.extend({ + payment_status: 'waiting_for_payment', + weight: 0, + accept_payment: function(){ this.payment_status = 'payment_accepted'; }, + reject_payment: function(){ this.payment_status = 'payment_rejected'; }, + delay_payment: function(){ this.payment_status = 'waiting_for_payment'; }, + }))(); + + // this object interfaces with the local proxy to communicate to the various hardware devices // connected to the Point of Sale. As the communication only goes from the POS to the proxy, // methods are used both to signal an event, and to fetch information. + module.ProxyDevice = instance.web.Class.extend({ //a product has been scanned and recognized with success scan_item_succes: function(){ + console.log('PROXY: scan item success'); }, //a product has been scanned but not recognized scan_item_error_unrecognized: function(){ + console.log('PROXY: scan item error'); }, //the client is asking for help help_needed: function(){ + console.log('PROXY: help needed'); }, //the client does not need help anymore help_canceled: function(){ + console.log('PROXY: help canceled'); }, //the client is starting to weight weighting_start: function(){ + console.log('PROXY: weighting start'); }, //returns the weight on the scale. // is called at regular interval (up to 10x/sec) between a weighting_start() // and a weighting_end() weighting_read_kg: function(){ - return Math.random() + 0.1; + console.log('PROXY: weighting read'); + //return Math.random() + 0.1; + return window.debug_devices.weight; }, // the client has finished weighting products weighting_end: function(){ + console.log('PROXY: weighting end'); }, // the pos asks the client to pay 'price' units // method: 'mastercard' | 'cash' | ... ? TBD // info: 'extra information to display on the payment terminal' ... ? TBD payment_request: function(price, method, info){ + console.log('PROXY: payment request:',price,method,info); }, // is called at regular interval after a payment request to see if the client // has paid the required money // returns 'waiting_for_payment' | 'payment_accepted' | 'payment_rejected' is_payment_accepted: function(){ - return 'waiting_for_payment'; // 'payment_accepted' | 'payment_rejected' + console.log('PROXY: is payment accepted ?'); + //return 'waiting_for_payment'; // 'payment_accepted' | 'payment_rejected' + return window.debug_devices.payment_status; }, // the client cancels his payment payment_canceled: function(){ + console.log('PROXY: payment canceled by client'); }, // called when the client logs in or starts to scan product transation_start: function(){ + console.log('PROXY: transaction start'); }, // called when the clients has finished his interaction with the machine transaction_end: function(){ + console.log('PROXY: transaction end'); }, // called when the POS turns to cashier mode cashier_mode_activated: function(){ + console.log('PROXY:'); }, // called when the POS turns to client mode cashier_mode_deactivated: function(){ + console.log('PROXY:'); }, }); diff --git a/addons/point_of_sale/static/src/js/pos_main.js b/addons/point_of_sale/static/src/js/pos_main.js index 09e51dd41e1..a358639e2e3 100644 --- a/addons/point_of_sale/static/src/js/pos_main.js +++ b/addons/point_of_sale/static/src/js/pos_main.js @@ -6,6 +6,8 @@ openerp.point_of_sale = function(instance) { var module = instance.point_of_sale; openerp_pos_models(module,instance); // import pos_models.js + + openerp_pos_screens(module,instance); // import pos_screens.js openerp_pos_widgets(module,instance); // import pos_widgets.js diff --git a/addons/point_of_sale/static/src/js/pos_widgets.js b/addons/point_of_sale/static/src/js/pos_widgets.js index ae17c6dbbc4..09f56573bf6 100644 --- a/addons/point_of_sale/static/src/js/pos_widgets.js +++ b/addons/point_of_sale/static/src/js/pos_widgets.js @@ -357,278 +357,27 @@ function openerp_pos_widgets(module, instance){ //module is instance.point_of_sa template:'pos-actionbar', init: function(parent, options){ this._super(parent,options); - this.left_button_list = []; - this.right_button_list = []; + this.button_list = []; }, - start: function(){ - window.actionbarwidget = this; - }, - destroyButtons:function(position){ - var button_list; - if(position === 'left'){ - button_list = this.left_button_list; - this.left_button_list = []; - }else if (position === 'right'){ - button_list = this.right_button_list; - this.right_button_list = []; - }else{ - return this; - } - for(var i = 0; i < button_list.length; i++){ - button_list[i].destroy(); + destroy_buttons:function(){ + for(var i = 0; i < this.button_list.length; i++){ + this.button_list[i].destroy(); } + this.button_list = []; return this; }, - addNewButton: function(position,button_options){ - if(arguments.length == 2){ - var button_list; - var $button_list; - if(position === 'left'){ - button_list = this.left_button_list; - $button_list = $('.pos-actionbar-left-region'); - }else if(position === 'right'){ - button_list = this.right_button_list; - $button_list = $('.pos-actionbar-right-region'); - } + add_new_button: function(button_options){ + if(arguments.length == 1){ var button = new module.ActionButtonWidget(this,button_options); - button_list.push(button); - button.appendTo($button_list); + this.button_list.push(button); + button.appendTo($('.pos-actionbar-right-region')); }else{ - for(var i = 1; i < arguments.length; i++){ - this.addNewButton(position,arguments[i]); + for(var i = 0; i < arguments.length; i++){ + this.add_new_button(arguments[i]); } } return this; } - /* - renderElement: function() { - //this.$element.html(this.template_fct()); - },*/ - }); - -// ---------- Screens Widgets ---------- - - module.ScreenWidget = instance.web.Widget.extend({ - init: function(parent, options){ - this._super(parent, options); - }, - show: function(){ - if(this.$element){ - this.$element.show(); - } - }, - hide: function(){ - if(this.$element){ - this.$element.hide(); - } - }, - }); - - module.ScreenSelector = instance.web.Class.extend({ - init: function(options){ - this.pos = options.pos; - this.screen_set = options.screen_set || {}; - this.current_screen = options.current_screen ? this.screen_set[options.current_screen] : undefined; - this.default_screen = options.default_screen; - - var current = null; - for(screen_name in this.screen_set){ - var screen = this.screen_set[screen_name]; - if(screen === this.current_screen){ - current = screen; - }else{ - screen.hide(); - } - } - if(current){ - current.show(); - } - - this.selected_order = this.pos.get('selectedOrder'); - this.pos.bind('change:selectedOrder', this.load_saved_screen, this); - }, - add_screen: function(screen_name, screen){ - screen.hide(); - this.screen_set[screen_name] = screen; - return this; - }, - load_saved_screen: function(){ - if(this.selected_order != this.pos.get('selectedOrder')){ - var screen = this.pos.get('selectedOrder').get('screen') || this.default_screen; - this.selected_order = this.pos.get('selectedOrder'); - this.set_current_screen(screen); - } - }, - set_current_screen: function(screen_name){ - var screen = this.screen_set[screen_name]; - - this.pos.get('selectedOrder').set({'screen':screen_name}); - - console.log('Set Current Screen: '+screen_name+' :',screen,'old:',this.current_screen); - if(screen && screen !== this.current_screen){ - if(this.current_screen){ - this.current_screen.hide(); - } - this.current_screen = screen; - this.current_screen.show(); - } - }, - }); - - module.PaymentScreenWidget = module.ScreenWidget.extend({ - template_fct: qweb_template('PaymentScreenWidget'), - init: function(parent, options) { - this._super(parent); - this.model = options.model; - this.pos = options.pos; - this.pos_widget = options.pos_widget; - this.pos.bind('change:selectedOrder', this.changeSelectedOrder, this); - this.bindPaymentLineEvents(); - this.bindOrderLineEvents(); - }, - show: function(){ - this._super(); - this.setNumpadState(this.pos_widget.numpadView.state); - }, - hide: function(){ - this._super(); - this.pos_widget.orderView.setNumpadState(null); - this.pos_widget.payment_screen.setNumpadState(null); - }, - paymentLineList: function() { - return this.$element.find('#paymentlines'); - }, - back: function() { - console.log('back'); - this.pos.screen_selector.set_current_screen('products'); - }, - validateCurrentOrder: function() { - var callback, currentOrder; - currentOrder = this.pos.get('selectedOrder'); - $('button#validate-order', this.$element).attr('disabled', 'disabled'); - this.pos.push_order(currentOrder.exportAsJSON()).then(_.bind(function() { - $('button#validate-order', this.$element).removeAttr('disabled'); - return currentOrder.set({ - validated: true - }); - }, this)); - }, - bindPaymentLineEvents: function() { - this.currentPaymentLines = (this.pos.get('selectedOrder')).get('paymentLines'); - this.currentPaymentLines.bind('add', this.addPaymentLine, this); - this.currentPaymentLines.bind('remove', this.renderElement, this); - this.currentPaymentLines.bind('all', this.updatePaymentSummary, this); - }, - bindOrderLineEvents: function() { - this.currentOrderLines = (this.pos.get('selectedOrder')).get('orderLines'); - this.currentOrderLines.bind('all', this.updatePaymentSummary, this); - }, - changeSelectedOrder: function() { - this.currentPaymentLines.unbind(); - this.bindPaymentLineEvents(); - this.currentOrderLines.unbind(); - this.bindOrderLineEvents(); - this.renderElement(); - }, - addPaymentLine: function(newPaymentLine) { - var x = new module.PaymentlineWidget(null, { - model: newPaymentLine - }); - x.on_delete.add(_.bind(this.deleteLine, this, x)); - x.appendTo(this.paymentLineList()); - }, - renderElement: function() { - this._super(); - this.$element.html(this.template_fct()); - this.paymentLineList().empty(); - this.currentPaymentLines.each(_.bind( function(paymentLine) { - this.addPaymentLine(paymentLine); - }, this)); - this.updatePaymentSummary(); - $('button#validate-order', this.$element).click(_.bind(this.validateCurrentOrder, this)); - $('.oe-back-to-products', this.$element).click(_.bind(this.back, this)); - }, - deleteLine: function(lineWidget) { - this.currentPaymentLines.remove([lineWidget.model]); - }, - updatePaymentSummary: function() { - var currentOrder, dueTotal, paidTotal, remaining, remainingAmount; - currentOrder = this.pos.get('selectedOrder'); - paidTotal = currentOrder.getPaidTotal(); - dueTotal = currentOrder.getTotal(); - this.$element.find('#payment-due-total').html(dueTotal.toFixed(2)); - this.$element.find('#payment-paid-total').html(paidTotal.toFixed(2)); - remainingAmount = dueTotal - paidTotal; - remaining = remainingAmount > 0 ? 0 : (-remainingAmount).toFixed(2); - $('#payment-remaining').html(remaining); - }, - setNumpadState: function(numpadState) { - if (this.numpadState) { - this.numpadState.unbind('setValue', this.setValue); - this.numpadState.unbind('change:mode', this.setNumpadMode); - } - this.numpadState = numpadState; - if (this.numpadState) { - this.numpadState.bind('setValue', this.setValue, this); - this.numpadState.bind('change:mode', this.setNumpadMode, this); - this.numpadState.reset(); - this.setNumpadMode(); - } - }, - setNumpadMode: function() { - this.numpadState.set({mode: 'payment'}); - }, - setValue: function(val) { - this.currentPaymentLines.last().set({amount: val}); - }, - }); - - module.ReceiptScreenWidget = module.ScreenWidget.extend({ - template: 'ReceiptScreenWidget', - init: function(parent, options) { - this._super(parent); - this.model = options.model; - this.pos = options.pos; - this.pos = options.pos; - this.user = this.pos.get('user'); - this.company = this.pos.get('company'); - this.shop_obj = this.pos.get('shop'); - }, - start: function() { - this.pos.bind('change:selectedOrder', this.changeSelectedOrder, this); - this.changeSelectedOrder(); - $('button#pos-finish-order', this.$element).click(_.bind(this.finishOrder, this)); - $('button#print-the-ticket', this.$element).click(_.bind(this.print, this)); - }, - print: function() { - window.print(); - }, - finishOrder: function() { - this.pos.get('selectedOrder').destroy(); - }, - changeSelectedOrder: function() { - if (this.currentOrderLines) - this.currentOrderLines.unbind(); - this.currentOrderLines = (this.pos.get('selectedOrder')).get('orderLines'); - this.currentOrderLines.bind('add', this.refresh, this); - this.currentOrderLines.bind('change', this.refresh, this); - this.currentOrderLines.bind('remove', this.refresh, this); - if (this.currentPaymentLines) - this.currentPaymentLines.unbind(); - this.currentPaymentLines = (this.pos.get('selectedOrder')).get('paymentLines'); - this.currentPaymentLines.bind('all', this.refresh, this); - this.refresh(); - }, - refresh: function() { - this.currentOrder = this.pos.get('selectedOrder'); - $('.pos-receipt-container', this.$element).html(qweb_template('pos-ticket')({widget:this})); - }, - }); - - module.WelcomeScreenWidget = module.ScreenWidget.extend({ - }); - - module.ScanProductScreenWidget = module.ScreenWidget.extend({ }); module.ProductCategoriesWidget = instance.web.Widget.extend({ @@ -743,45 +492,6 @@ function openerp_pos_widgets(module, instance){ //module is instance.point_of_sa }, }); - module.SearchProductScreenWidget = module.ScreenWidget.extend({ - template:'SearchProductScreenWidget', - init: function(parent, options){ - this._super(parent,options); - options = options || {}; - this.pos = options.pos; - this.pos_widget = options.pos_widget; - }, - start: function(){ - this.product_categories_widget = new module.ProductCategoriesWidget(null,{ - pos:this.pos, - }); - this.product_categories_widget.replace($('.placeholder-ProductCategoriesWidget')); - - this.product_list_widget = new module.ProductListWidget(null,{ - pos:this.pos, - }); - this.product_list_widget.replace($('.placeholder-ProductListWidget')); - }, - show: function(){ - this._super(); - this.pos_widget.orderView.setNumpadState(this.pos_widget.numpadView.state); - }, - hide: function(){ - this._super(); - this.pos_widget.orderView.setNumpadState(null); - this.pos_widget.payment_screen.setNumpadState(null); - }, - - }); - - module.ScaleInviteScreenWidget = module.ScreenWidget.extend({ - }); - - module.ScaleProductSelectionScreenWidget = module.ScreenWidget.extend({ - }); - - module.AskForMoneyScreenWidget = module.ScreenWidget.extend({ - }); // ---------- PopUp Widgets ---------- @@ -799,6 +509,8 @@ function openerp_pos_widgets(module, instance){ //module is instance.point_of_sa module.TicketOrInvoicePopUp = module.PopUp.extend({ }); +// ---------- OnScreen Keyboard Widget ---------- + // A Widget that displays an onscreen keyboard. // There are two options when creating the widget : // @@ -994,6 +706,8 @@ function openerp_pos_widgets(module, instance){ //module is instance.point_of_sa }, }); +// ---------- Main Point of Sale Widget ---------- + module.SynchNotification = instance.web.OldWidget.extend({ template: "pos-synch-notification", init: function() { @@ -1061,6 +775,11 @@ function openerp_pos_widgets(module, instance){ //module is instance.point_of_sa }); this.search_product_screen.appendTo($('#rightpane')); + this.scan_product_screen = new module.ScanProductScreenWidget(this,{ + pos: this.pos, + pos_widget: this, + }); + this.scan_product_screen.appendTo($('#rightpane')); this.receipt_screen = new module.ReceiptScreenWidget(this, { pos: this.pos, @@ -1068,13 +787,30 @@ function openerp_pos_widgets(module, instance){ //module is instance.point_of_sa }); this.receipt_screen.appendTo($('#rightpane')); - this.payment_screen = new module.PaymentScreenWidget(this, { pos: this.pos, pos_widget: this, }); this.payment_screen.appendTo($('#rightpane')); + this.welcome_screen = new module.WelcomeScreenWidget(this,{ + pos:this.pos, + pos_widget: this, + }); + this.welcome_screen.appendTo($('#rightpane')); + + this.client_payment_screen = new module.ClientPaymentScreenWidget(this, { + pos: this.pos, + pos_widget: this, + }); + this.client_payment_screen.appendTo($('#rightpane')); + + this.scale_invite_screen = new module.ScaleInviteScreenWidget(this, { + pos: this.pos, + pos_widget: this, + }); + this.scale_invite_screen.appendTo($('#rightpane')); + this.paypadView = new module.PaypadWidget(null, { pos: this.pos }); @@ -1089,29 +825,28 @@ function openerp_pos_widgets(module, instance){ //module is instance.point_of_sa }); this.orderView.$element = $('#current-order-content'); this.orderView.start(); + + this.action_bar = new module.ActionbarWidget(null); + this.action_bar.appendTo($(".point-of-sale #content")); + + this.onscreen_keyboard = new module.OnscreenKeyboardWidget(null, { + 'keyboard_model': 'simple' + }); + this.onscreen_keyboard.appendTo($(".point-of-sale #content")); this.pos.screen_selector = new module.ScreenSelector({ pos: this.pos, screen_set:{ 'products': this.search_product_screen, + 'scan': this.scan_product_screen, 'payment' : this.payment_screen, + 'client_payment' : this.client_payment_screen, + 'scale_invite' : this.scale_invite_screen, 'receipt' : this.receipt_screen, + 'welcome' : this.welcome_screen, }, - current_screen: 'products', - default_screen: 'products', - }); - - this.onscreenKeyboard = new module.OnscreenKeyboardWidget(null, { - 'keyboard_model': 'simple' - }); - this.onscreenKeyboard.appendTo($(".point-of-sale #content")); - - this.action_bar = new module.ActionbarWidget(null); - this.action_bar.appendTo($(".point-of-sale #content")); - this.action_bar.addNewButton('left',{ - label : 'Hello World', - icon : '/point_of_sale/static/src/img/icons/png48/face-monkey.png', - click : function(){ console.log("Hello World!"); } + current_screen: 'welcome', + default_screen: 'welcome', }); this.pos.barcode_reader.connect(); diff --git a/addons/point_of_sale/static/src/xml/pos.xml b/addons/point_of_sale/static/src/xml/pos.xml index 7abd08f1974..e8ca9202a08 100644 --- a/addons/point_of_sale/static/src/xml/pos.xml +++ b/addons/point_of_sale/static/src/xml/pos.xml @@ -218,26 +218,40 @@
- -
+

WELCUM

+

plz scan ur card or thingie

- -
+ + +
+

SCAN YOUR PRODUCT

+

please scan another product ! fun !

+
+
+ + +
+

GIMME UR MONEY!!!11

+

It's a rubbery

-
+
+

Put ur loot on the scale

+

Is it worth a ton ?

+ +
From 850ed87082a8cf3756d22f0f07ab8c7271e93763 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20van=20der=20Essen?= Date: Thu, 3 May 2012 18:40:32 +0200 Subject: [PATCH 077/201] Draft of the product scale screen bzr revid: fva@openerp.com-20120503164032-zoe5rini8lj7kw1i --- addons/point_of_sale/static/src/js/pos_widgets.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/addons/point_of_sale/static/src/js/pos_widgets.js b/addons/point_of_sale/static/src/js/pos_widgets.js index 09f56573bf6..a4c12e8ccb2 100644 --- a/addons/point_of_sale/static/src/js/pos_widgets.js +++ b/addons/point_of_sale/static/src/js/pos_widgets.js @@ -811,6 +811,12 @@ function openerp_pos_widgets(module, instance){ //module is instance.point_of_sa }); this.scale_invite_screen.appendTo($('#rightpane')); + this.scale_product_screen = new module.ScaleProductScreenWidget(this, { + pos: this.pos, + pos_widget: this, + }); + this.scale_product_screen.appendTo($('#rightpane')); + this.paypadView = new module.PaypadWidget(null, { pos: this.pos }); @@ -842,6 +848,7 @@ function openerp_pos_widgets(module, instance){ //module is instance.point_of_sa 'payment' : this.payment_screen, 'client_payment' : this.client_payment_screen, 'scale_invite' : this.scale_invite_screen, + 'scale_product' : this.scale_product_screen, 'receipt' : this.receipt_screen, 'welcome' : this.welcome_screen, }, From fa4b14487d374c390bdb1c8ced70b43a360445e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20van=20der=20Essen?= Date: Thu, 3 May 2012 19:15:23 +0200 Subject: [PATCH 078/201] Forgot to add pos_screens.js bzr revid: fva@openerp.com-20120503171523-rmew4wbblrw3hpyk --- .../static/src/js/pos_screens.js | 543 ++++++++++++++++++ 1 file changed, 543 insertions(+) create mode 100644 addons/point_of_sale/static/src/js/pos_screens.js diff --git a/addons/point_of_sale/static/src/js/pos_screens.js b/addons/point_of_sale/static/src/js/pos_screens.js new file mode 100644 index 00000000000..29c92d85792 --- /dev/null +++ b/addons/point_of_sale/static/src/js/pos_screens.js @@ -0,0 +1,543 @@ + +// this file contains the screens definitions. Screens are the +// content of the right pane of the pos, containing the main functionalities. +// screens are contained in the PosWidget, in pos_widget.js +// all screens are present in the dom at all time, but only one is shown at the +// same time. +// +// transition between screens is made possible by the use of the screen_selector, +// which is responsible of hiding and showing the screens, as well as maintaining +// the state of the screens between different orders. +// +// all screens inherit from ScreenWidget. the only addition from the base widgets +// are show() and hide() which shows and hides the screen but are also used to +// bind and unbind actions on widgets and devices. The screen_selector guarantees +// that only one screen is shown at the same time and that show() is called after all +// hide()s + +function openerp_pos_screens(module, instance){ //module is instance.point_of_sale + var QWeb = instance.web.qweb; + + var qweb_template = function(template,pos){ + return function(ctx){ + if(!pos){ //this is a huge hack that needs to be removed ... TODO + var HackPosModel = Backbone.Model.extend({ + initialize:function(){ + this.set({ + 'currency': {symbol: '$', position: 'after'}, + }); + }, + }); + pos = new HackPosModel(); + } + return QWeb.render(template, _.extend({}, ctx,{ + 'currency': pos.get('currency'), + 'format_amount': function(amount) { + if (pos.get('currency').position == 'after') { + return amount + ' ' + pos.get('currency').symbol; + } else { + return pos.get('currency').symbol + ' ' + amount; + } + }, + })); + }; + }; + + module.ScreenSelector = instance.web.Class.extend({ + init: function(options){ + this.pos = options.pos; + this.screen_set = options.screen_set || {}; + this.current_screen = options.current_screen ? this.screen_set[options.current_screen] : undefined; + this.default_screen = options.default_screen; + + var current = null; + for(screen_name in this.screen_set){ + var screen = this.screen_set[screen_name]; + if(screen === this.current_screen){ + current = screen; + }else{ + screen.hide(); + } + } + if(current){ + current.show(); + } + + this.selected_order = this.pos.get('selectedOrder'); + this.pos.bind('change:selectedOrder', this.load_saved_screen, this); + }, + add_screen: function(screen_name, screen){ + screen.hide(); + this.screen_set[screen_name] = screen; + return this; + }, + load_saved_screen: function(){ + if(this.selected_order != this.pos.get('selectedOrder')){ + var screen = this.pos.get('selectedOrder').get('screen') || this.default_screen; + this.selected_order = this.pos.get('selectedOrder'); + this.set_current_screen(screen); + } + }, + set_current_screen: function(screen_name){ + var screen = this.screen_set[screen_name]; + + this.pos.get('selectedOrder').set({'screen':screen_name}); + + console.log('Set Current Screen: '+screen_name+' :',screen,'old:',this.current_screen); + if(screen && screen !== this.current_screen){ + if(this.current_screen){ + this.current_screen.hide(); + } + this.current_screen = screen; + this.current_screen.show(); + } + }, + }); + + module.ScreenWidget = instance.web.Widget.extend({ + init: function(parent, options){ + this._super(parent, options); + options = options || {}; + this.pos = options.pos; + this.pos_widget = options.pos_widget; + }, + show: function(){ + if(this.$element){ + this.$element.show(); + } + }, + hide: function(){ + if(this.$element){ + this.$element.hide(); + } + if(this.pos.barcode_reader){ + this.pos.barcode_reader.reset_action_callbacks(); + } + if(this.pos_widget.action_bar){ + this.pos_widget.action_bar.destroy_buttons(); + } + }, + }); + + module.ScaleInviteScreenWidget = module.ScreenWidget.extend({ + template:'ScaleInviteScreenWidget', + show: function(){ + this._super(); + var self = this; + + self.pos.proxy.weighting_start(); + + var intervalID = setInterval(function(){ + var weight = self.pos.proxy.weighting_read_kg(); + if(weight > 0.001){ + clearInterval(intervalID); + self.pos.screen_selector.set_current_screen('scale_product'); + } + },500); + + this.pos_widget.action_bar.add_new_button( + { + label: 'help', + click: function(){ //TODO Show help popup + } + },{ + label: 'back', + click: function(){ //TODO Go to ask for weighting screen + clearInterval(intervalID); + self.pos.proxy.weighting_end(); + self.pos.screen_selector.set_current_screen('scan'); + } + } + ); + }, + }); + + module.ScaleProductScreenWidget = module.ScreenWidget.extend({ + template:'ScaleProductSelectionScreenWidget', + start: function(){ + this.product_categories_widget = new module.ProductCategoriesWidget(null,{ + pos:this.pos, + }); + this.product_categories_widget.replace($('.placeholder-ProductCategoriesWidget')); + + this.product_list_widget = new module.ProductListWidget(null,{ + pos:this.pos, + }); + this.product_list_widget.replace($('.placeholder-ProductListWidget')); + }, + show: function(){ + this._super(); + var self = this; + this.pos_widget.orderView.setNumpadState(this.pos_widget.numpadView.state); + this.pos_widget.action_bar.add_new_button( + { + label: 'help', + click: function(){ //TODO Show help popup + } + },{ + label: 'back', + click: function(){ + self.pos.screen_selector.set_current_screen('scan'); + } + } + ); + this.pos.barcode_reader.set_action_callbacks({ + 'cashier': function(ean){ + //TODO 'switch to cashier mode' + self.proxy.cashier_mode_activated(); + }, + }); + }, + hide: function(){ + this._super(); + this.pos_widget.orderView.setNumpadState(null); + this.pos_widget.payment_screen.setNumpadState(null); + }, + + }); + + module.ClientPaymentScreenWidget = module.ScreenWidget.extend({ + template:'ClientPaymentScreenWidget', + show: function(){ + this._super(); + var self = this; + + this.pos.proxy.payment_request(0,'card','info'); //TODO TOTAL + + var intervalID = setInterval(function(){ + var payment = self.pos.proxy.is_payment_accepted(); + if(payment === 'payment_accepted'){ + clearInterval(intervalID); + //TODO process the payment stuff + self.pos.proxy.transaction_end(); + self.pos.screen_selector.set_current_screen('welcome'); + }else if(payment === 'payment_rejected'){ + clearInterval(intervalID); + //TODO show a tryagain thingie ? + } + },500); + + this.pos_widget.action_bar.add_new_button( + { + label: 'help', + click: function(){ //TODO Show help popup + } + },{ + label: 'back', + click: function(){ //TODO Go to ask for weighting screen + clearInterval(intervalID); + self.pos.proxy.payment_canceled(); + self.pos.screen_selector.set_current_screen('scan'); + } + } + ); + + this.pos.barcode_reader.set_action_callbacks({ + 'cashier': function(ean){ + //TODO 'switch to cashier mode' + clearInterval(intervalID); + self.proxy.cashier_mode_activated(); + self.pos.screen_selector.set_current_screen('products'); + }, + }); + }, + }); + + module.WelcomeScreenWidget = module.ScreenWidget.extend({ + template:'WelcomeScreenWidget', + show: function(){ + this._super(); + var self = this; + this.pos_widget.action_bar.add_new_button( + { + label:'scan', + click: function(){ + self.pos.screen_selector.set_current_screen('scan'); + } + },{ + label: 'help', + click: function(){ //TODO Show help popup + } + },{ + label: 'peser', + click: function(){ //TODO Go to ask for weighting screen + self.pos.screen_selector.set_current_screen('scale_invite'); + } + } + ); + this.pos.barcode_reader.set_action_callbacks({ + 'product': function(ean){ + self.proxy.transaction_start(); + self.pos.barcode_reader.scan_product_callback(ean); + self.pos.screen_selector.set_current_screen('products'); + }, + 'cashier': function(ean){ + //TODO 'switch to cashier mode' + self.proxy.cashier_mode_activated(); + self.pos.screen_selector.set_current_screen('products'); + }, + 'client': function(ean){ + self.proxy.transaction_start(); + //TODO 'log the client' + self.pos.screen_selector.set_current_screen('products'); + }, + 'discount': function(ean){ + // TODO : what to do in this case ???? + }, + }); + }, + hide: function(){ + this._super(); + this.pos.barcode_reader.reset_action_callbacks(); + this.pos_widget.action_bar.destroy_buttons(); + }, + }); + + module.ScanProductScreenWidget = module.ScreenWidget.extend({ + template:'ScanProductScreenWidget', + show: function(){ + this._super(); + var self = this; + this.pos_widget.action_bar.add_new_button( + { + label: 'help', + click: function(){ //TODO Show help popup + } + },{ + label: 'weight', + click: function(){ //TODO Go to ask for weighting screen + self.pos.screen_selector.set_current_screen('scale_invite'); + } + },{ + label: 'pay', + click: function(){ + self.pos.screen_selector.set_current_screen('client_payment'); //TODO what stuff ? + } + } + ); + this.pos.barcode_reader.set_action_callbacks({ + 'product': function(ean){ + var success = self.pos.barcode_reader.scan_product_callback(ean); + if(success){ + self.proxy.scan_item_success(); + }else{ + self.proxy.scan_item_error_unrecognized(); + } + }, + 'cashier': function(ean){ + //TODO 'switch to cashier mode' + self.proxy.cashier_mode_activated(); + }, + 'discount': function(ean){ + // TODO : handle the discount + }, + }); + }, + }); + + module.SearchProductScreenWidget = module.ScreenWidget.extend({ + template:'SearchProductScreenWidget', + start: function(){ + this.product_categories_widget = new module.ProductCategoriesWidget(null,{ + pos:this.pos, + }); + this.product_categories_widget.replace($('.placeholder-ProductCategoriesWidget')); + + this.product_list_widget = new module.ProductListWidget(null,{ + pos:this.pos, + }); + this.product_list_widget.replace($('.placeholder-ProductListWidget')); + }, + show: function(){ + this._super(); + var self = this; + this.pos_widget.orderView.setNumpadState(this.pos_widget.numpadView.state); + this.pos_widget.action_bar.add_new_button( + { + label: 'help', + click: function(){ //TODO Show help popup + } + },{ + label: 'weight', + click: function(){ //TODO Go to ask for weighting screen + } + },{ + label: 'pay', + click: function(){ + self.pos.screen_selector.set_current_screen('payment'); //TODO what stuff ? + } + } + ); + this.pos.barcode_reader.set_action_callbacks({ + 'product': function(ean){ + var success = self.pos.barcode_reader.scan_product_callback(ean); + if(success){ + self.proxy.scan_item_success(); + }else{ + self.proxy.scan_item_error_unrecognized(); + } + }, + 'cashier': function(ean){ + //TODO 'switch to cashier mode' + self.proxy.cashier_mode_activated(); + }, + 'discount': function(ean){ + // TODO : handle the discount + }, + }); + }, + hide: function(){ + this._super(); + this.pos_widget.orderView.setNumpadState(null); + this.pos_widget.payment_screen.setNumpadState(null); + }, + + }); + + module.ReceiptScreenWidget = module.ScreenWidget.extend({ + template: 'ReceiptScreenWidget', + init: function(parent, options) { + this._super(parent,options); + this.model = options.model; + this.user = this.pos.get('user'); + this.company = this.pos.get('company'); + this.shop_obj = this.pos.get('shop'); + }, + start: function() { + this.pos.bind('change:selectedOrder', this.changeSelectedOrder, this); + this.changeSelectedOrder(); + $('button#pos-finish-order', this.$element).click(_.bind(this.finishOrder, this)); + $('button#print-the-ticket', this.$element).click(_.bind(this.print, this)); + }, + print: function() { + window.print(); + }, + finishOrder: function() { + this.pos.get('selectedOrder').destroy(); + }, + changeSelectedOrder: function() { + if (this.currentOrderLines) + this.currentOrderLines.unbind(); + this.currentOrderLines = (this.pos.get('selectedOrder')).get('orderLines'); + this.currentOrderLines.bind('add', this.refresh, this); + this.currentOrderLines.bind('change', this.refresh, this); + this.currentOrderLines.bind('remove', this.refresh, this); + if (this.currentPaymentLines) + this.currentPaymentLines.unbind(); + this.currentPaymentLines = (this.pos.get('selectedOrder')).get('paymentLines'); + this.currentPaymentLines.bind('all', this.refresh, this); + this.refresh(); + }, + refresh: function() { + this.currentOrder = this.pos.get('selectedOrder'); + $('.pos-receipt-container', this.$element).html(qweb_template('pos-ticket')({widget:this})); + }, + }); + + module.PaymentScreenWidget = module.ScreenWidget.extend({ + template_fct: qweb_template('PaymentScreenWidget'), + init: function(parent, options) { + this._super(parent,options); + this.model = options.model; + this.pos.bind('change:selectedOrder', this.changeSelectedOrder, this); + this.bindPaymentLineEvents(); + this.bindOrderLineEvents(); + }, + show: function(){ + this._super(); + this.setNumpadState(this.pos_widget.numpadView.state); + }, + hide: function(){ + this._super(); + this.pos_widget.orderView.setNumpadState(null); + this.pos_widget.payment_screen.setNumpadState(null); + }, + paymentLineList: function() { + return this.$element.find('#paymentlines'); + }, + back: function() { + console.log('back'); + this.pos.screen_selector.set_current_screen('products'); + }, + validateCurrentOrder: function() { + var callback, currentOrder; + currentOrder = this.pos.get('selectedOrder'); + $('button#validate-order', this.$element).attr('disabled', 'disabled'); + this.pos.push_order(currentOrder.exportAsJSON()).then(_.bind(function() { + $('button#validate-order', this.$element).removeAttr('disabled'); + return currentOrder.set({ + validated: true + }); + }, this)); + }, + bindPaymentLineEvents: function() { + this.currentPaymentLines = (this.pos.get('selectedOrder')).get('paymentLines'); + this.currentPaymentLines.bind('add', this.addPaymentLine, this); + this.currentPaymentLines.bind('remove', this.renderElement, this); + this.currentPaymentLines.bind('all', this.updatePaymentSummary, this); + }, + bindOrderLineEvents: function() { + this.currentOrderLines = (this.pos.get('selectedOrder')).get('orderLines'); + this.currentOrderLines.bind('all', this.updatePaymentSummary, this); + }, + changeSelectedOrder: function() { + this.currentPaymentLines.unbind(); + this.bindPaymentLineEvents(); + this.currentOrderLines.unbind(); + this.bindOrderLineEvents(); + this.renderElement(); + }, + addPaymentLine: function(newPaymentLine) { + var x = new module.PaymentlineWidget(null, { + model: newPaymentLine + }); + x.on_delete.add(_.bind(this.deleteLine, this, x)); + x.appendTo(this.paymentLineList()); + }, + renderElement: function() { + this._super(); + this.$element.html(this.template_fct()); + this.paymentLineList().empty(); + this.currentPaymentLines.each(_.bind( function(paymentLine) { + this.addPaymentLine(paymentLine); + }, this)); + this.updatePaymentSummary(); + $('button#validate-order', this.$element).click(_.bind(this.validateCurrentOrder, this)); + $('.oe-back-to-products', this.$element).click(_.bind(this.back, this)); + }, + deleteLine: function(lineWidget) { + this.currentPaymentLines.remove([lineWidget.model]); + }, + updatePaymentSummary: function() { + var currentOrder, dueTotal, paidTotal, remaining, remainingAmount; + currentOrder = this.pos.get('selectedOrder'); + paidTotal = currentOrder.getPaidTotal(); + dueTotal = currentOrder.getTotal(); + this.$element.find('#payment-due-total').html(dueTotal.toFixed(2)); + this.$element.find('#payment-paid-total').html(paidTotal.toFixed(2)); + remainingAmount = dueTotal - paidTotal; + remaining = remainingAmount > 0 ? 0 : (-remainingAmount).toFixed(2); + $('#payment-remaining').html(remaining); + }, + setNumpadState: function(numpadState) { + if (this.numpadState) { + this.numpadState.unbind('setValue', this.setValue); + this.numpadState.unbind('change:mode', this.setNumpadMode); + } + this.numpadState = numpadState; + if (this.numpadState) { + this.numpadState.bind('setValue', this.setValue, this); + this.numpadState.bind('change:mode', this.setNumpadMode, this); + this.numpadState.reset(); + this.setNumpadMode(); + } + }, + setNumpadMode: function() { + this.numpadState.set({mode: 'payment'}); + }, + setValue: function(val) { + this.currentPaymentLines.last().set({amount: val}); + }, + }); + +} From 22c0ae2d8b9bc5cd94d628469287c52a7a3a18d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20van=20der=20Essen?= Date: Fri, 4 May 2012 11:48:25 +0200 Subject: [PATCH 079/201] Client and cashier mode bzr revid: fva@openerp.com-20120504094825-fb0ku12w3kq1ubcf --- .../static/src/js/pos_screens.js | 81 ++++++++++++++++--- .../static/src/js/pos_widgets.js | 6 +- 2 files changed, 74 insertions(+), 13 deletions(-) diff --git a/addons/point_of_sale/static/src/js/pos_screens.js b/addons/point_of_sale/static/src/js/pos_screens.js index 29c92d85792..0f8cdaadbb4 100644 --- a/addons/point_of_sale/static/src/js/pos_screens.js +++ b/addons/point_of_sale/static/src/js/pos_screens.js @@ -46,9 +46,22 @@ function openerp_pos_screens(module, instance){ //module is instance.point_of_sa module.ScreenSelector = instance.web.Class.extend({ init: function(options){ this.pos = options.pos; + this.screen_set = options.screen_set || {}; - this.current_screen = options.current_screen ? this.screen_set[options.current_screen] : undefined; - this.default_screen = options.default_screen; + + this.default_client_screen = options.default_client_screen; + this.default_cashier_screen = options.default_cashier_screen; + + this.current_client_screen = this.screen_set[this.default_client_screen]; + + this.current_cashier_screen = this.screen_set[this.default_client_screen]; + + this.default_mode = options.default_mode || 'client'; + this.current_mode = this.default_mode; + + this.current_screen = this.current_mode === 'client' ? + this.current_client_screen: + this.current_cashier_screen; var current = null; for(screen_name in this.screen_set){ @@ -64,6 +77,12 @@ function openerp_pos_screens(module, instance){ //module is instance.point_of_sa } this.selected_order = this.pos.get('selectedOrder'); + this.selected_order.set({ + user_mode : this.current_mode, + client_screen: this.default_client_screen, + cashier_screen: this.default_cashier_screen, + }); + this.pos.bind('change:selectedOrder', this.load_saved_screen, this); }, add_screen: function(screen_name, screen){ @@ -72,18 +91,54 @@ function openerp_pos_screens(module, instance){ //module is instance.point_of_sa return this; }, load_saved_screen: function(){ - if(this.selected_order != this.pos.get('selectedOrder')){ - var screen = this.pos.get('selectedOrder').get('screen') || this.default_screen; - this.selected_order = this.pos.get('selectedOrder'); - this.set_current_screen(screen); + console.log('load_saved_screen'); + if(true || this.selected_order != this.pos.get('selectedOrder')){ + var selectedOrder = this.pos.get('selectedOrder'); + + var user_mode = selectedOrder.get('user_mode'); + console.log('user mode:',user_mode); + + if(user_mode === 'client'){ + this.current_mode = 'client'; + this.set_current_screen(selectedOrder.get('client_screen') || this.default_client_screen); + }else if(user_mode === 'cashier'){ + this.current_mode = 'cashier'; + console.log('default_cashier_screen:',this.default_cashier_screen); + this.set_current_screen(selectedOrder.get('cashier_screen') || this.default_cashier_screen); + }else{ + this.current_mode = this.default_mode; + selectedOrder.set({ user_mode: this.current_mode }); + if(this.current_mode === 'client'){ + this.set_current_screen(this.default_client_screen); + }else{ + this.set_current_screen(this.default_cashier_screen); + } + } + this.selected_order = selectedOrder; + // var screen = this.pos.get('selectedOrder').get('screen') || this.default_screen; + // this.selected_order = this.pos.get('selectedOrder'); + // this.set_current_screen(screen); + } + }, + set_user_mode: function(user_mode){ + console.log('set user mode:',user_mode); + if(user_mode !== this.current_mode){ + this.current_mode = user_mode; + this.pos.get('selectedOrder').set({ user_mode : this.current_mode }); + this.load_saved_screen(); } }, set_current_screen: function(screen_name){ var screen = this.screen_set[screen_name]; - - this.pos.get('selectedOrder').set({'screen':screen_name}); - console.log('Set Current Screen: '+screen_name+' :',screen,'old:',this.current_screen); + var selectedOrder = this.pos.get('selectedOrder'); + if(this.current_mode === 'client'){ + selectedOrder.set({'client_screen': screen_name}); + }else{ + selectedOrder.set({'cashier_screen': screen_name}); + } + + console.log('Set Current Screen: '+screen_name+' :',screen,'old:',this.current_screen, 'mode:',this.current_mode); if(screen && screen !== this.current_screen){ if(this.current_screen){ this.current_screen.hide(); @@ -127,7 +182,7 @@ function openerp_pos_screens(module, instance){ //module is instance.point_of_sa self.pos.proxy.weighting_start(); - var intervalID = setInterval(function(){ + this.intervalID = setInterval(function(){ var weight = self.pos.proxy.weighting_read_kg(); if(weight > 0.001){ clearInterval(intervalID); @@ -143,13 +198,17 @@ function openerp_pos_screens(module, instance){ //module is instance.point_of_sa },{ label: 'back', click: function(){ //TODO Go to ask for weighting screen - clearInterval(intervalID); + clearInterval(this.intervalID); self.pos.proxy.weighting_end(); self.pos.screen_selector.set_current_screen('scan'); } } ); }, + hide: function(){ + this._super(); + clearInterval(this.intervalID); + }, }); module.ScaleProductScreenWidget = module.ScreenWidget.extend({ diff --git a/addons/point_of_sale/static/src/js/pos_widgets.js b/addons/point_of_sale/static/src/js/pos_widgets.js index a4c12e8ccb2..91e4e7b9d40 100644 --- a/addons/point_of_sale/static/src/js/pos_widgets.js +++ b/addons/point_of_sale/static/src/js/pos_widgets.js @@ -852,9 +852,11 @@ function openerp_pos_widgets(module, instance){ //module is instance.point_of_sa 'receipt' : this.receipt_screen, 'welcome' : this.welcome_screen, }, - current_screen: 'welcome', - default_screen: 'welcome', + default_client_screen: 'welcome', + default_cashier_screen: 'products', + default_mode: 'client', }); + window.screen_selector = this.pos.screen_selector; this.pos.barcode_reader.connect(); From bf9fc9b13c856b874f4ae6719cf4aab116885a3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20van=20der=20Essen?= Date: Fri, 4 May 2012 12:27:37 +0200 Subject: [PATCH 080/201] Showing and hiding widgets in different screens and modes bzr revid: fva@openerp.com-20120504102737-gfi4kggapocpnxrk --- .../static/src/js/pos_screens.js | 40 ++++++++++++++++++ .../static/src/js/pos_widgets.js | 41 +++++++++++++++++++ 2 files changed, 81 insertions(+) diff --git a/addons/point_of_sale/static/src/js/pos_screens.js b/addons/point_of_sale/static/src/js/pos_screens.js index 0f8cdaadbb4..4c11770151d 100644 --- a/addons/point_of_sale/static/src/js/pos_screens.js +++ b/addons/point_of_sale/static/src/js/pos_screens.js @@ -180,6 +180,10 @@ function openerp_pos_screens(module, instance){ //module is instance.point_of_sa this._super(); var self = this; + this.pos_widget.set_numpad_visible(false); + this.pos_widget.set_leftpane_visible(true); + this.pos_widget.set_cashier_controls_visible(false); + self.pos.proxy.weighting_start(); this.intervalID = setInterval(function(){ @@ -227,6 +231,11 @@ function openerp_pos_screens(module, instance){ //module is instance.point_of_sa show: function(){ this._super(); var self = this; + + this.pos_widget.set_numpad_visible(false); + this.pos_widget.set_leftpane_visible(true); + this.pos_widget.set_cashier_controls_visible(false); + this.pos_widget.orderView.setNumpadState(this.pos_widget.numpadView.state); this.pos_widget.action_bar.add_new_button( { @@ -261,6 +270,10 @@ function openerp_pos_screens(module, instance){ //module is instance.point_of_sa this._super(); var self = this; + this.pos_widget.set_numpad_visible(false); + this.pos_widget.set_leftpane_visible(true); + this.pos_widget.set_cashier_controls_visible(false); + this.pos.proxy.payment_request(0,'card','info'); //TODO TOTAL var intervalID = setInterval(function(){ @@ -307,6 +320,11 @@ function openerp_pos_screens(module, instance){ //module is instance.point_of_sa show: function(){ this._super(); var self = this; + + this.pos_widget.set_numpad_visible(false); + this.pos_widget.set_leftpane_visible(false); + this.pos_widget.set_cashier_controls_visible(false); + this.pos_widget.action_bar.add_new_button( { label:'scan', @@ -357,6 +375,11 @@ function openerp_pos_screens(module, instance){ //module is instance.point_of_sa show: function(){ this._super(); var self = this; + + this.pos_widget.set_numpad_visible(false); + this.pos_widget.set_leftpane_visible(true); + this.pos_widget.set_cashier_controls_visible(false); + this.pos_widget.action_bar.add_new_button( { label: 'help', @@ -410,6 +433,11 @@ function openerp_pos_screens(module, instance){ //module is instance.point_of_sa show: function(){ this._super(); var self = this; + + this.pos_widget.set_numpad_visible(true); + this.pos_widget.set_leftpane_visible(true); + this.pos_widget.set_cashier_controls_visible(true); + this.pos_widget.orderView.setNumpadState(this.pos_widget.numpadView.state); this.pos_widget.action_bar.add_new_button( { @@ -468,6 +496,13 @@ function openerp_pos_screens(module, instance){ //module is instance.point_of_sa $('button#pos-finish-order', this.$element).click(_.bind(this.finishOrder, this)); $('button#print-the-ticket', this.$element).click(_.bind(this.print, this)); }, + show: function(){ + this._super(); + + this.pos_widget.set_numpad_visible(true); + this.pos_widget.set_leftpane_visible(true); + this.pos_widget.set_cashier_controls_visible(true); + }, print: function() { window.print(); }, @@ -504,6 +539,11 @@ function openerp_pos_screens(module, instance){ //module is instance.point_of_sa }, show: function(){ this._super(); + + this.pos_widget.set_numpad_visible(true); + this.pos_widget.set_leftpane_visible(true); + this.pos_widget.set_cashier_controls_visible(true); + this.setNumpadState(this.pos_widget.numpadView.state); }, hide: function(){ diff --git a/addons/point_of_sale/static/src/js/pos_widgets.js b/addons/point_of_sale/static/src/js/pos_widgets.js index 91e4e7b9d40..2d0fad64748 100644 --- a/addons/point_of_sale/static/src/js/pos_widgets.js +++ b/addons/point_of_sale/static/src/js/pos_widgets.js @@ -729,6 +729,10 @@ function openerp_pos_widgets(module, instance){ //module is instance.point_of_sa init: function() { this._super.apply(this, arguments); this.pos = new module.PosModel(this.session); + this.numpad_visible = true; + this.leftpane_visible = true; + this.leftpane_width = '440px'; + this.cashier_controls_visible = true; }, start: function() { var self = this; @@ -882,6 +886,43 @@ function openerp_pos_widgets(module, instance){ //module is instance.point_of_sa var self = this; this.synch_notification.on_change_nbr_pending(self.pos.get('nbr_pending_operations').length); }, + set_numpad_visible: function(visible){ + if(visible != this.numpad_visible){ + this.numpad_visible = visible; + if(visible){ + $('#numpad').show(); + $('#paypad').show(); + }else{ + $('#numpad').hide(); + $('#paypad').hide(); + } + } + }, + set_leftpane_visible: function(visible){ + if(visible != this.leftpane_visible){ + this.leftpane_visible = visible; + if(visible){ + $('#leftpane').show().animate({'width':this.leftpane_width},500,'swing'); + $('#rightpane').animate({'left':this.leftpane_width},500,'swing'); + }else{ + var leftpane = $('#leftpane'); + $('#leftpane').animate({'width':'0px'},500,'swing', function(){ leftpane.hide(); }); + $('#rightpane').animate({'left':'0px'},500,'swing'); + } + } + }, + set_cashier_controls_visible: function(visible){ + if(visible != this.cashier_controls_visible){ + this.cashier_controls_visible = visible; + if(visible){ + $('#loggedas').show(); + $('#rightheader').show(); + }else{ + $('#loggedas').hide(); + $('#rightheader').hide(); + } + } + }, try_close: function() { var self = this; self.pos.flush().then(_.bind(function() { From 5fafeb4c89e8b35ccf04af216797167eef4fafe4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20van=20der=20Essen?= Date: Fri, 4 May 2012 14:24:48 +0200 Subject: [PATCH 081/201] New action bar draft bzr revid: fva@openerp.com-20120504122448-bhs06j1lgoauss58 --- addons/point_of_sale/static/src/css/pos.css | 6 ++---- .../point_of_sale/static/src/js/pos_widgets.js | 8 ++++---- addons/point_of_sale/static/src/xml/pos.xml | 18 ++++++++++-------- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/addons/point_of_sale/static/src/css/pos.css b/addons/point_of_sale/static/src/css/pos.css index 3769417995b..067f1368d71 100644 --- a/addons/point_of_sale/static/src/css/pos.css +++ b/addons/point_of_sale/static/src/css/pos.css @@ -510,7 +510,7 @@ body{ list-style: none; } -.point-of-sale .pos-actionbar-left-region{ +.point-of-sale .pos-actionbar-left-pane{ height: 100%; width: 434px; margin: 0px; @@ -520,7 +520,7 @@ body{ float: left; } -.point-of-sale .pos-actionbar-right-region{ +.point-of-sale .pos-actionbar-button-list{ height: 100%; margin: 0px; padding-left:3px; @@ -534,8 +534,6 @@ body{ text-align:center; margin:3px; margin-top:6px; - //line-height: 90px; - //vertical-align:center; float:left; font-size: 14px; diff --git a/addons/point_of_sale/static/src/js/pos_widgets.js b/addons/point_of_sale/static/src/js/pos_widgets.js index 2d0fad64748..899221a009a 100644 --- a/addons/point_of_sale/static/src/js/pos_widgets.js +++ b/addons/point_of_sale/static/src/js/pos_widgets.js @@ -353,8 +353,8 @@ function openerp_pos_widgets(module, instance){ //module is instance.point_of_sa }, }); - module.ActionbarWidget = instance.web.Widget.extend({ - template:'pos-actionbar', + module.ActionBarWidget = instance.web.Widget.extend({ + template:'ActionBarWidget', init: function(parent, options){ this._super(parent,options); this.button_list = []; @@ -370,7 +370,7 @@ function openerp_pos_widgets(module, instance){ //module is instance.point_of_sa if(arguments.length == 1){ var button = new module.ActionButtonWidget(this,button_options); this.button_list.push(button); - button.appendTo($('.pos-actionbar-right-region')); + button.appendTo($('.pos-actionbar-button-list')); }else{ for(var i = 0; i < arguments.length; i++){ this.add_new_button(arguments[i]); @@ -836,7 +836,7 @@ function openerp_pos_widgets(module, instance){ //module is instance.point_of_sa this.orderView.$element = $('#current-order-content'); this.orderView.start(); - this.action_bar = new module.ActionbarWidget(null); + this.action_bar = new module.ActionBarWidget(null); this.action_bar.appendTo($(".point-of-sale #content")); this.onscreen_keyboard = new module.OnscreenKeyboardWidget(null, { diff --git a/addons/point_of_sale/static/src/xml/pos.xml b/addons/point_of_sale/static/src/xml/pos.xml index e8ca9202a08..dd5412dcaf9 100644 --- a/addons/point_of_sale/static/src/xml/pos.xml +++ b/addons/point_of_sale/static/src/xml/pos.xml @@ -354,15 +354,17 @@
- +
-
    - -
- -
    +
    +
    + Help +
    +

    + Total: 1234 +

    +
    +
      From e56de26bcc14532ef8d38d29d88f9046c649bc60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20van=20der=20Essen?= Date: Fri, 4 May 2012 14:30:33 +0200 Subject: [PATCH 082/201] removed custom help buttons bzr revid: fva@openerp.com-20120504123033-mffgt1x9swahq5oe --- .../static/src/js/pos_screens.js | 24 ------------------- 1 file changed, 24 deletions(-) diff --git a/addons/point_of_sale/static/src/js/pos_screens.js b/addons/point_of_sale/static/src/js/pos_screens.js index 4c11770151d..083f415669e 100644 --- a/addons/point_of_sale/static/src/js/pos_screens.js +++ b/addons/point_of_sale/static/src/js/pos_screens.js @@ -196,10 +196,6 @@ function openerp_pos_screens(module, instance){ //module is instance.point_of_sa this.pos_widget.action_bar.add_new_button( { - label: 'help', - click: function(){ //TODO Show help popup - } - },{ label: 'back', click: function(){ //TODO Go to ask for weighting screen clearInterval(this.intervalID); @@ -239,10 +235,6 @@ function openerp_pos_screens(module, instance){ //module is instance.point_of_sa this.pos_widget.orderView.setNumpadState(this.pos_widget.numpadView.state); this.pos_widget.action_bar.add_new_button( { - label: 'help', - click: function(){ //TODO Show help popup - } - },{ label: 'back', click: function(){ self.pos.screen_selector.set_current_screen('scan'); @@ -291,10 +283,6 @@ function openerp_pos_screens(module, instance){ //module is instance.point_of_sa this.pos_widget.action_bar.add_new_button( { - label: 'help', - click: function(){ //TODO Show help popup - } - },{ label: 'back', click: function(){ //TODO Go to ask for weighting screen clearInterval(intervalID); @@ -331,10 +319,6 @@ function openerp_pos_screens(module, instance){ //module is instance.point_of_sa click: function(){ self.pos.screen_selector.set_current_screen('scan'); } - },{ - label: 'help', - click: function(){ //TODO Show help popup - } },{ label: 'peser', click: function(){ //TODO Go to ask for weighting screen @@ -382,10 +366,6 @@ function openerp_pos_screens(module, instance){ //module is instance.point_of_sa this.pos_widget.action_bar.add_new_button( { - label: 'help', - click: function(){ //TODO Show help popup - } - },{ label: 'weight', click: function(){ //TODO Go to ask for weighting screen self.pos.screen_selector.set_current_screen('scale_invite'); @@ -441,10 +421,6 @@ function openerp_pos_screens(module, instance){ //module is instance.point_of_sa this.pos_widget.orderView.setNumpadState(this.pos_widget.numpadView.state); this.pos_widget.action_bar.add_new_button( { - label: 'help', - click: function(){ //TODO Show help popup - } - },{ label: 'weight', click: function(){ //TODO Go to ask for weighting screen } From ec308a08e73dc07a71216a7127ffd74160e80c55 Mon Sep 17 00:00:00 2001 From: Stephane Wirtel Date: Fri, 4 May 2012 14:51:58 +0200 Subject: [PATCH 083/201] [IMP] account: Add new wizard for the cash register "Take Money Out", "Put Money In" [FIX] point_of_sale: Fix some bugs bzr revid: stw@openerp.com-20120504125158-p45qfzo8mif6x9xe --- addons/account/__openerp__.py | 1 + addons/account/account.py | 4 + addons/account/account_cash_statement.py | 41 +++++++++- addons/account/account_view.xml | 18 +++-- addons/account/wizard/__init__.py | 2 + addons/account/wizard/pos_box.py | 80 +++++++++++++++++++ addons/account/wizard/pos_box.xml | 62 ++++++++++++++ addons/point_of_sale/__openerp__.py | 3 +- .../point_of_sale/account_bank_statement.py | 5 +- .../point_of_sale/account_statement_view.xml | 1 - addons/point_of_sale/point_of_sale.py | 20 ++--- addons/point_of_sale/point_of_sale_view.xml | 35 ++++---- addons/point_of_sale/wizard/__init__.py | 4 +- addons/point_of_sale/wizard/pos_box.py | 26 ++++++ addons/point_of_sale/wizard/pos_box.xml | 22 +++++ .../point_of_sale/wizard/pos_box_entries.py | 22 ++++- addons/point_of_sale/wizard/pos_payment.py | 22 ++++- addons/point_of_sale/wizard/pos_payment.xml | 3 +- 18 files changed, 327 insertions(+), 44 deletions(-) create mode 100644 addons/account/wizard/pos_box.py create mode 100644 addons/account/wizard/pos_box.xml create mode 100644 addons/point_of_sale/wizard/pos_box.py create mode 100644 addons/point_of_sale/wizard/pos_box.xml diff --git a/addons/account/__openerp__.py b/addons/account/__openerp__.py index 221a58e35d1..e6f356e0a44 100644 --- a/addons/account/__openerp__.py +++ b/addons/account/__openerp__.py @@ -100,6 +100,7 @@ module named account_voucher. 'wizard/account_reconcile_partner_process_view.xml', 'wizard/account_automatic_reconcile_view.xml', 'wizard/account_financial_report_view.xml', + 'wizard/pos_box.xml', 'project/wizard/project_account_analytic_line_view.xml', 'account_end_fy.xml', 'account_invoice_view.xml', diff --git a/addons/account/account.py b/addons/account/account.py index 75bfc891980..df446abc3bc 100644 --- a/addons/account/account.py +++ b/addons/account/account.py @@ -739,6 +739,10 @@ class account_journal(osv.osv): 'entry_posted': fields.boolean('Skip \'Draft\' State for Manual Entries', help='Check this box if you don\'t want new journal entries to pass through the \'draft\' state and instead goes directly to the \'posted state\' without any manual validation. \nNote that journal entries that are automatically created by the system are always skipping that state.'), 'company_id': fields.many2one('res.company', 'Company', required=True, select=1, help="Company related to this journal"), 'allow_date':fields.boolean('Check Date in Period', help= 'If set to True then do not accept the entry if the entry date is not into the period dates'), + + 'profit_account_id' : fields.many2one('account.account', 'Profit Account'), + 'loss_account_id' : fields.many2one('account.account', 'Loss Account'), + 'internal_account_id' : fields.many2one('account.account', 'Internal Transfers Account', select=1), } _defaults = { diff --git a/addons/account/account_cash_statement.py b/addons/account/account_cash_statement.py index f21bd7e9a36..9cec8dfc723 100644 --- a/addons/account/account_cash_statement.py +++ b/addons/account/account_cash_statement.py @@ -188,6 +188,14 @@ class account_cash_statement(osv.osv): result[line.statement_id.id] = True return result.keys() + def _compute_difference(self, cr, uid, ids, fieldnames, args, context=None): + result = dict.fromkeys(ids, 0.0) + + for obj in self.browse(cr, uid, ids, context=context): + result[obj.id] = obj.balance_end - obj.balance_end_cash + + return result + _columns = { 'total_entry_encoding': fields.function(_get_sum_entry_encoding, string="Cash Transaction", help="Total cash transactions", store = { @@ -198,6 +206,7 @@ class account_cash_statement(osv.osv): 'balance_end_cash': fields.function(_balance_end_cash, store=False, string='Closing Balance', help="Closing balance based on cashBox"), 'details_ids' : fields.one2many('account.cashbox.line', 'bank_statement_id', string='CashBox Lines'), 'user_id': fields.many2one('res.users', 'Responsible', required=False), + 'difference' : fields.function(_compute_difference, method=True, string="Difference", type="float"), } _defaults = { @@ -234,7 +243,8 @@ class account_cash_statement(osv.osv): for line in vals.get('details_ids',[]): print "line: %r" % (line,) if line and len(line)==3 and line[2]: - amount_total+= line[2]['pieces'] * line[2]['number_opening'] + # FIXME: If there is no piece # does not work with GTK + amount_total+= line[2].get('pieces', 0) * line[2]['number_opening'] vals.update(balance_start= amount_total) vals.update(balance_end_real=self._compute_balance_end_real(cr, uid, vals['journal_id'], context=context)) @@ -287,7 +297,7 @@ class account_cash_statement(osv.osv): balance_start = 0.0 if journal_id: count = self.search_count(cr, uid, [('journal_id', '=', journal_id),('state', '=', 'open')], context=None) - if count: + if 0: # count: journal = self.pool.get('account.journal').browse(cr, uid, journal_id, context=context) raise osv.except_osv(_('Error !'), (_('The Account Journal %s is opened by an other Cash Register !') % (journal.name,))) else: @@ -380,6 +390,33 @@ class account_cash_statement(osv.osv): def button_confirm_cash(self, cr, uid, ids, context=None): super(account_cash_statement, self).button_confirm_bank(cr, uid, ids, context=context) + absl_proxy = self.pool.get('account.bank.statement.line') + + TABLES = (('Profit', 'profit_account_id'), ('Loss', 'loss_account_id'),) + + for obj in self.browse(cr, uid, ids, context=context): + if obj.difference == 0.0: + continue + + for item_label, item_account in TALBES: + if getattr(obj.journal_id, item_account): + raise osv.except_osv(_('Error !'), + _('There is no %s Account on the Journal %s') % (item_label, obj.journal_id.name,)) + + is_profit = obj.difference < 0.0 + + account = getattr(obj.journal_id, TABLES[is_profit][1]) + + values = { + 'statement_id' : obj.id, + 'journal_id' : obj.journal_id.id, + 'account_id' : account.id, + 'amount' : obj.difference, + 'name' : 'Exceptional %s' % TABLES[is_profit][0], + } + + absl_proxy.create(cr, uid, values, context=context) + return self.write(cr, uid, ids, {'closing_date': time.strftime("%Y-%m-%d %H:%M:%S")}, context=context) def button_cancel(self, cr, uid, ids, context=None): diff --git a/addons/account/account_view.xml b/addons/account/account_view.xml index 5258b876ad2..273035fbc2a 100644 --- a/addons/account/account_view.xml +++ b/addons/account/account_view.xml @@ -505,11 +505,17 @@ - + + + + + + + @@ -2659,12 +2665,13 @@ action = pool.get('res.config').next(cr, uid, [], context) - + + - + - + @@ -2677,7 +2684,7 @@ action = pool.get('res.config').next(cr, uid, [], context) - + @@ -2690,6 +2697,7 @@ action = pool.get('res.config').next(cr, uid, [], context) + diff --git a/addons/account/wizard/__init__.py b/addons/account/wizard/__init__.py index a78cf12050b..87a053d558c 100644 --- a/addons/account/wizard/__init__.py +++ b/addons/account/wizard/__init__.py @@ -64,6 +64,8 @@ import account_report_account_balance import account_change_currency +import pos_box; + # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/account/wizard/pos_box.py b/addons/account/wizard/pos_box.py new file mode 100644 index 00000000000..a9e752397e0 --- /dev/null +++ b/addons/account/wizard/pos_box.py @@ -0,0 +1,80 @@ +#!/usr/bin/env python +from osv import osv, fields +import decimal_precision as dp + +class CashBox(osv.osv_memory): + _register = False + _columns = { + 'name' : fields.char('Reason', size=64, required=True), + # Attention, we don't set a domain, because there is a journal_type key + # in the context of the action + 'amount' : fields.float('Amount', + digits_compute = dp.get_precision('Account'), + required=True), + } + + def run(self, cr, uid, ids, context=None): + if not context: + context = dict() + + active_model = context.get('active_model', False) or False + active_ids = context.get('active_ids', []) or [] + + records = self.pool.get(active_model).browse(cr, uid, active_ids, context=context) + + return self._run(cr, uid, ids, records, context=None) + + def _run(self, cr, uid, ids, records, context=None): + for box in self.browse(cr, uid, ids, context=context): + for record in records: + if not record.journal_id.internal_account_id: + raise osv.except_osv(_('Error !'), + _('Please check that internal account is set to %s') % (record.journal_id.name,)) + + self._create_bank_statement_line(cr, uid, box, record, context=context) + + return {} + + +class CashBoxIn(CashBox): + _name = 'cash.box.in' + + _columns = CashBox._columns.copy() + _columns.update({ + 'ref' : fields.char('Reference', size=32), + }) + + def _create_bank_statement_line(self, cr, uid, box, record, context=None): + absl_proxy = self.pool.get('account.bank.statement.line') + + values = { + 'statement_id' : record.id, + 'journal_id' : record.journal_id.id, + 'account_id' : record.journal_id.internal_account_id.id, + 'amount' : box.amount or 0.0, + 'ref' : "%s" % (box.ref or ''), + 'name' : box.name, + } + + return absl_proxy.create(cr, uid, values, context=context) + +CashBoxIn() + +class CashBoxOut(CashBox): + _name = 'cash.box.out' + + def _create_bank_statement_line(self, cr, uid, box, record, context=None): + absl_proxy = self.pool.get('account.bank.statement.line') + + amount = box.amount or 0.0 + values = { + 'statement_id' : record.id, + 'journal_id' : record.journal_id.id, + 'account_id' : record.journal_id.internal_account_id.id, + 'amount' : -amount if amount > 0.0 else amount, + 'name' : box.name, + } + + return absl_proxy.create(cr, uid, values, context=context) + +CashBoxOut() diff --git a/addons/account/wizard/pos_box.xml b/addons/account/wizard/pos_box.xml new file mode 100644 index 00000000000..6136cca06ad --- /dev/null +++ b/addons/account/wizard/pos_box.xml @@ -0,0 +1,62 @@ + + + + + cash_box_in + cash.box.in + form + +
      + + + + + + + +
+ + + + @@ -407,7 +423,7 @@

- Total: 1234 € + Total: 0 €

    From 6cefb2c802c9a803711a38bef35d6da87afbbcae Mon Sep 17 00:00:00 2001 From: Stephane Wirtel Date: Mon, 7 May 2012 15:09:46 +0200 Subject: [PATCH 087/201] [FIX] point_of_sale: Fix some bugs bzr revid: stw@openerp.com-20120507130946-13i3yq82oi69zjjt --- .../point_of_sale/account_bank_statement.py | 15 ++++++ addons/point_of_sale/point_of_sale.py | 47 +++++++++++++++---- addons/point_of_sale/point_of_sale_view.xml | 9 ++-- addons/point_of_sale/wizard/pos_confirm.py | 2 +- addons/point_of_sale/wizard/pos_payment.py | 22 +++++++-- addons/point_of_sale/wizard/pos_payment.xml | 3 +- 6 files changed, 77 insertions(+), 21 deletions(-) diff --git a/addons/point_of_sale/account_bank_statement.py b/addons/point_of_sale/account_bank_statement.py index ab03b61ba43..786d4629cd4 100644 --- a/addons/point_of_sale/account_bank_statement.py +++ b/addons/point_of_sale/account_bank_statement.py @@ -74,6 +74,21 @@ class account_cash_statement(osv.osv): res.sort() return res + def search(self, cr, uid, domain, offset=0, limit=None, order=None, context=None, count=False): + if not context: + context = {} + import pdb + pdb.set_trace() + pos_session_id = context.pop('pos_session_id', False) or False + if pos_session_id and isinstance(pos_session_id, (int, long)): + session = self.pool.get('pos.session').browse(cr, uid, pos_session_id, context=context) + return [ + statement.id + for order in session.order_ids + for statement in order.statement_ids + ] + return super(account_cash_statement, self).search(cr, uid, domain, offset=offset, limit=limit, order=order, context=context, count=count) + account_cash_statement() # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/point_of_sale/point_of_sale.py b/addons/point_of_sale/point_of_sale.py index 877da092468..cbc83b951cf 100644 --- a/addons/point_of_sale/point_of_sale.py +++ b/addons/point_of_sale/point_of_sale.py @@ -151,7 +151,15 @@ pos_config() class pos_session(osv.osv): _name = 'pos.session' + #"status [BUTTON TEXT]" -> utiliser + # "opening control (open) -> opened (cashbox control) -> closing control (close) -> closed & posted" POS_SESSION_STATE = [('new', 'New'),('opened', 'Opened'),('closed', 'Closed'),('posted', 'Posted')] + #POS_SESSION_STATE = [ + # ('new', 'Opening Control'), + # ('opened', 'Opened'), + # ('closed', 'Closing Control'), + # ('posted', 'Closed & Posted'), + #] _columns = { 'config_id' : fields.many2one('pos.config', 'PoS', required=True, select=1, domain="[('state', '=', 'active')]"), @@ -168,6 +176,7 @@ class pos_session(osv.osv): 'details_ids' : fields.related('cash_register_id', 'details_ids', type='one2many', relation='account.cashbox.line', string='CashBox Lines'), + 'order_ids' : fields.one2many('pos.order', 'session_id', 'Orders'), } _defaults = { @@ -180,7 +189,7 @@ class pos_session(osv.osv): ('uniq_name', 'unique(name)', "The name of this POS Session must be unique !"), ] - def _create_cash_register(self, cr, uid, pos_config, name, context=None): + def _create_cash_register(self, cr, uid, pos_config, context=None): if not pos_config: return False @@ -196,7 +205,6 @@ class pos_session(osv.osv): return False values = { - 'name' : name, 'journal_id' : journal_id, } cash_register_id = proxy.create(cr, uid, values, context=context) @@ -211,7 +219,7 @@ class pos_session(osv.osv): name = pos_config.sequence_id._next() values.update( name=name, - cash_register_id=self._create_cash_register(cr, uid, pos_config, name, context=context), + cash_register_id=self._create_cash_register(cr, uid, pos_config, context=context), ) else: raise osv.except_osv(_('Error!'), _('There is no POS Config attached to this POS Session')) @@ -228,14 +236,33 @@ class pos_session(osv.osv): record.write(values, context=context) + record.cash_register_id.button_open(context=context) + return True def wkf_action_close(self, cr, uid, ids, context=None): + # Close CashBox + record.cash_register_id.button_confirm_cash(context=context) return self.write(cr, uid, ids, {'state' : 'close', 'stop_at' : time.strftime('%Y-%m-%d %H:%M:%S')}, context=context) def wkf_action_post(self, cr, uid, ids, context=None): + self._confirm_orders(cr, uid, ids, context=context) return self.write(cr, uid, ids, {'state' : 'post'}, context=context) + def _confirm_orders(self, cr, uid, ids, context=None): + wf_service = netsvc.LocalService("workflow") + + for session in self.browse(cr, uid, ids, context=context): + for order in session.order_ids: + if order.state != 'paid': + raise osv.except_osv( + _('Error !'), + _("You can not confirm all orders of this session, because they have not the 'paid' status")) + else: + wf_service.trg_validate(uid, 'pos.order', order.id, 'done', cr) + + return True + def get_current_session(self, cr, uid, context=None): current_user = self.pool.get('res.users').browse(cr, uid, uid, context=context) domain = [ @@ -295,6 +322,7 @@ class pos_order(osv.osv): session_id = self.pool.get('pos.session').get_current_session() for order in orders: # order :: {'name': 'Order 1329148448062', 'amount_paid': 9.42, 'lines': [[0, 0, {'discount': 0, 'price_unit': 1.46, 'product_id': 124, 'qty': 5}], [0, 0, {'discount': 0, 'price_unit': 0.53, 'product_id': 62, 'qty': 4}]], 'statement_ids': [[0, 0, {'journal_id': 7, 'amount': 9.42, 'name': '2012-02-13 15:54:12', 'account_id': 12, 'statement_id': 21}]], 'amount_tax': 0, 'amount_return': 0, 'amount_total': 9.42} + order['session_id'] = session_id order_obj = self.pool.get('pos.order') # get statements out of order because they will be generated with add_payment to ensure # the module behavior is the same when using the front-end or the back-end @@ -547,11 +575,14 @@ class pos_order(osv.osv): msg = _('There is no receivable account defined to make payment for the partner: "%s" (id:%d)') % (order.partner_id.name, order.partner_id.id,) raise osv.except_osv(_('Configuration Error !'), msg) - statement_id = statement_obj.search(cr,uid, [ - ('journal_id', '=', int(data['journal'])), - ('company_id', '=', curr_company), - ('user_id', '=', uid), - ('state', '=', 'open')], context=context) + context.pop('pos_session_id', False) + domain = [ + ('journal_id', '=', int(data['journal'])), + ('company_id', '=', curr_company), + ('user_id', '=', uid), + ('state', '=', 'open') + ] + statement_id = statement_obj.search(cr,uid, domain, context=context) if len(statement_id) == 0: raise osv.except_osv(_('Error !'), _('You have to open at least one cashbox')) if statement_id: diff --git a/addons/point_of_sale/point_of_sale_view.xml b/addons/point_of_sale/point_of_sale_view.xml index c8a666f4a52..96c753fb0e6 100644 --- a/addons/point_of_sale/point_of_sale_view.xml +++ b/addons/point_of_sale/point_of_sale_view.xml @@ -923,7 +923,7 @@ - Point of Sales Session + PoS Sessions ir.actions.act_window pos.session form @@ -943,15 +943,14 @@ src_model="pos.session" res_model="pos.order" domain="[('session_id', '=', active_id)]" /> - + domain="[]" + context="{'pos_session_id' : active_id}" /> pos.order.list.select diff --git a/addons/point_of_sale/wizard/pos_confirm.py b/addons/point_of_sale/wizard/pos_confirm.py index 4f77774171a..fc244c1cb00 100644 --- a/addons/point_of_sale/wizard/pos_confirm.py +++ b/addons/point_of_sale/wizard/pos_confirm.py @@ -34,7 +34,7 @@ class pos_confirm(osv.osv_memory): for order in order_obj.browse(cr, uid, ids, context=context): todo = True for line in order.statement_ids: - if line.statement_id.state <> 'confirm': + if line.statement_id.state != 'confirm': todo = False break if todo: diff --git a/addons/point_of_sale/wizard/pos_payment.py b/addons/point_of_sale/wizard/pos_payment.py index e72c5bf649a..b23318f9762 100644 --- a/addons/point_of_sale/wizard/pos_payment.py +++ b/addons/point_of_sale/wizard/pos_payment.py @@ -32,6 +32,8 @@ class account_journal(osv.osv): def search(self, cr, uid, args, offset=0, limit=None, order=None, context=None, count=False): if not context: context = {} + import pdb + pdb.set_trace() session_id = context.get('pos_session_id', False) or False @@ -44,6 +46,8 @@ class account_journal(osv.osv): return super(account_journal, self).search(cr, uid, args, offset=offset, limit=limit, order=order, context=context, count=count) +account_journal() + class pos_make_payment(osv.osv_memory): _name = 'pos.make.payment' _description = 'Point of Sale Payment' @@ -61,7 +65,7 @@ class pos_make_payment(osv.osv_memory): amount = order.amount_total - order.amount_paid data = self.read(cr, uid, ids, context=context)[0] # this is probably a problem of osv_memory as it's not compatible with normal OSV's - #data['journal'] = data['journal'][0] + data['journal'] = data['journal_id'][0] if amount != 0.0: order_obj.add_payment(cr, uid, active_id, data, context=context) @@ -95,8 +99,17 @@ class pos_make_payment(osv.osv_memory): } def _default_journal(self, cr, uid, context=None): - res = pos_box_entries.get_journal(self, cr, uid, context=context) - return len(res)>1 and res[1][0] or False + if not context: + context = {} + pos_session_id = context.get('pos_session_id', False) or False + + if isinstance(pos_session_id, (long, int)): + session = self.pool.get('pos.session').browse(cr, uid, pos_session_id, context=context) + for journal in session.config_id.journal_ids: + if journal.type == 'cash': + return journal.id + + return False def _default_amount(self, cr, uid, context=None): order_obj = self.pool.get('pos.order') @@ -107,16 +120,15 @@ class pos_make_payment(osv.osv_memory): return False _columns = { - #'journal': fields.selection(pos_box_entries.get_journal, "Payment Mode", required=True), 'journal_id' : fields.many2one('account.journal', 'Payment Mode', required=True), 'amount': fields.float('Amount', digits=(16,2), required= True), 'payment_name': fields.char('Payment Reference', size=32), 'payment_date': fields.date('Payment Date', required=True), } _defaults = { + 'journal_id' : _default_journal, 'payment_date': time.strftime('%Y-%m-%d %H:%M:%S'), 'amount': _default_amount, - #'journal': _default_journal } pos_make_payment() diff --git a/addons/point_of_sale/wizard/pos_payment.xml b/addons/point_of_sale/wizard/pos_payment.xml index 84c0cdfb5df..6d450f93043 100644 --- a/addons/point_of_sale/wizard/pos_payment.xml +++ b/addons/point_of_sale/wizard/pos_payment.xml @@ -10,8 +10,7 @@ - - + From 551678423323c1aede92fb11e4817858fa6ac7fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20van=20der=20Essen?= Date: Mon, 7 May 2012 15:14:14 +0200 Subject: [PATCH 088/201] barcode reader simulation bzr revid: fva@openerp.com-20120507131414-svwk1fbvc25rztza --- .../static/src/js/pos_devices.js | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/addons/point_of_sale/static/src/js/pos_devices.js b/addons/point_of_sale/static/src/js/pos_devices.js index 6fb2337485e..ea101697d9f 100644 --- a/addons/point_of_sale/static/src/js/pos_devices.js +++ b/addons/point_of_sale/static/src/js/pos_devices.js @@ -271,6 +271,29 @@ function openerp_pos_devices(module, instance){ //module is instance.point_of_sa } }, + simulate : function(type){ + + var parse_result = { + ean: '01238534932', + type: type, + prefix: '012', + id: '392', + value: 42, + unit: 'Kg', + }; + + if(parse_result.type in {'unit':'', 'weight':'', 'price':''}){ //ean is associated to a product + if(this.action_callback['product']){ + this.action_callback['product'](parse_result); + } + }else{ + if(this.action_callback[parse_result.type]){ + this.action_callback[parse_result.type](parse_result); + } + } + + }, + // starts catching keyboard events and tries to interpret codebar // calling the callbacks when needed. connect: function(){ From 9fe2a57c11a94846df1491e23237cb11ea496d41 Mon Sep 17 00:00:00 2001 From: Stephane Wirtel Date: Mon, 7 May 2012 16:14:09 +0200 Subject: [PATCH 089/201] [REF] Remove the pdb call bzr revid: stw@openerp.com-20120507141409-kfhugzui4z1d9pox --- addons/point_of_sale/account_bank_statement.py | 2 -- addons/point_of_sale/point_of_sale.py | 3 +++ addons/point_of_sale/wizard/pos_payment.py | 3 --- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/addons/point_of_sale/account_bank_statement.py b/addons/point_of_sale/account_bank_statement.py index 786d4629cd4..c68b98384df 100644 --- a/addons/point_of_sale/account_bank_statement.py +++ b/addons/point_of_sale/account_bank_statement.py @@ -77,8 +77,6 @@ class account_cash_statement(osv.osv): def search(self, cr, uid, domain, offset=0, limit=None, order=None, context=None, count=False): if not context: context = {} - import pdb - pdb.set_trace() pos_session_id = context.pop('pos_session_id', False) or False if pos_session_id and isinstance(pos_session_id, (int, long)): session = self.pool.get('pos.session').browse(cr, uid, pos_session_id, context=context) diff --git a/addons/point_of_sale/point_of_sale.py b/addons/point_of_sale/point_of_sale.py index cbc83b951cf..64e56e89f5d 100644 --- a/addons/point_of_sale/point_of_sale.py +++ b/addons/point_of_sale/point_of_sale.py @@ -67,6 +67,9 @@ class pos_config(osv.osv): } + #def default_get(self, cr, uid, fieldnames, context=None): + # return dict() + _defaults = { 'state' : 'draft', 'user_id' : lambda obj, cr, uid, context: uid, diff --git a/addons/point_of_sale/wizard/pos_payment.py b/addons/point_of_sale/wizard/pos_payment.py index b23318f9762..194428db118 100644 --- a/addons/point_of_sale/wizard/pos_payment.py +++ b/addons/point_of_sale/wizard/pos_payment.py @@ -32,9 +32,6 @@ class account_journal(osv.osv): def search(self, cr, uid, args, offset=0, limit=None, order=None, context=None, count=False): if not context: context = {} - import pdb - pdb.set_trace() - session_id = context.get('pos_session_id', False) or False if session_id: From 2227cfc68dfdf8f32c81b794fd89a0e296e43966 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20van=20der=20Essen?= Date: Mon, 7 May 2012 16:18:57 +0200 Subject: [PATCH 090/201] debug logs bzr revid: fva@openerp.com-20120507141857-1t3fv90kw9v4b7bn --- addons/point_of_sale/static/src/js/pos_models.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/addons/point_of_sale/static/src/js/pos_models.js b/addons/point_of_sale/static/src/js/pos_models.js index b572172347b..1ea09b5d858 100644 --- a/addons/point_of_sale/static/src/js/pos_models.js +++ b/addons/point_of_sale/static/src/js/pos_models.js @@ -81,6 +81,7 @@ function openerp_pos_models(module, instance){ //module is instance.point_of_sal ['name', 'list_price', 'pos_categ_id', 'taxes_id','product_image_small'], [['pos_categ_id','!=', false]] ).then(function(result){ + console.log('product_list:',result); return self.set({'product_list': result}); }); @@ -89,11 +90,13 @@ function openerp_pos_models(module, instance){ //module is instance.point_of_sal ['account_id', 'currency', 'journal_id', 'state', 'name'], [['state','=','open'], ['user_id', '=', this.session.uid]] ).then(function(result){ + console.log('bank_statements:',result); return self.set({'bank_statements': result}); }); var tax_def = fetch('account.tax', ['amount','price_include','type']) .then(function(result){ + console.log('taxes:',result); return self.set({'taxes': result}); }); From d4cbce0ce75f997dda11fe3f51b49faff8c95103 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20van=20der=20Essen?= Date: Tue, 8 May 2012 14:37:01 +0200 Subject: [PATCH 091/201] barcode scanning fixes bzr revid: fva@openerp.com-20120508123701-pb641zlrwd183pf1 --- addons/point_of_sale/static/src/css/pos.css | 3 + .../static/src/js/pos_devices.js | 63 +++++++-- .../point_of_sale/static/src/js/pos_models.js | 1 - .../static/src/js/pos_screens.js | 126 +++++++++--------- .../static/src/js/pos_widgets.js | 27 +++- addons/point_of_sale/static/src/xml/pos.xml | 10 +- 6 files changed, 145 insertions(+), 85 deletions(-) diff --git a/addons/point_of_sale/static/src/css/pos.css b/addons/point_of_sale/static/src/css/pos.css index e566482b8b7..4ba5520752f 100644 --- a/addons/point_of_sale/static/src/css/pos.css +++ b/addons/point_of_sale/static/src/css/pos.css @@ -418,6 +418,9 @@ body{ margin-top: 0px; padding-top: 7px; } +.point-of-sale .step-screen p{ + font-size: 18px; +} .point-of-sale .pos-step-container { display: inline-block; diff --git a/addons/point_of_sale/static/src/js/pos_devices.js b/addons/point_of_sale/static/src/js/pos_devices.js index ea101697d9f..e9c6941b953 100644 --- a/addons/point_of_sale/static/src/js/pos_devices.js +++ b/addons/point_of_sale/static/src/js/pos_devices.js @@ -1,6 +1,8 @@ function openerp_pos_devices(module, instance){ //module is instance.point_of_sale + var QWeb = instance.web.qweb; + window.debug_devices = new (instance.web.Class.extend({ payment_status: 'waiting_for_payment', weight: 0, @@ -75,7 +77,7 @@ function openerp_pos_devices(module, instance){ //module is instance.point_of_sa }, // called when the client logs in or starts to scan product - transation_start: function(){ + transaction_start: function(){ console.log('PROXY: transaction start'); }, @@ -103,12 +105,17 @@ function openerp_pos_devices(module, instance){ //module is instance.point_of_sa init: function(attributes){ this.pos = attributes.pos; - this.action_callbacks = { + this.action_callback = { 'product': undefined, 'cashier': undefined, 'client': undefined, 'discount': undefined, }; + this.price_prefix_set = attributes.price_prefix_set || {'02':'', '22':'', '24':'', '26':'', '28':''}; + this.weight_prefix_set = attributes.weight_prefix_set || {'21':'','23':'','27':'','29':'','25':''}; + this.client_prefix_set = attributes.weight_prefix_set || {'42':''}; + this.cashier_prefix_set = attributes.weight_prefix_set || {'43':''}; + this.discount_prefix_set = attributes.weight_prefix_set || {'44':''}; }, // when an ean is scanned and parsed, the callback corresponding @@ -124,14 +131,14 @@ function openerp_pos_devices(module, instance){ //module is instance.point_of_sa set_action_callbacks: function(callbacks){ for(action in callbacks){ - this.action_callbacks[action] = callbacks[action]; + this.action_callback[action] = callbacks[action]; } }, //remove all action callbacks reset_action_callbacks: function(){ - for(action in this.action_callbacks){ - this.action_callbacks[action] = undefined; + for(action in this.action_callback){ + this.action_callback[action] = undefined; } }, @@ -140,6 +147,9 @@ function openerp_pos_devices(module, instance){ //module is instance.point_of_sa // ean must be a string check_ean: function(ean){ var code = ean.split(''); + for(var i = 0; i < code.length; i++){ + code[i] = Number(code[i]); + } var st1 = code.slice(); var st2 = st1.slice(0,st1.length-1).reverse(); // some EAN13 barcodes have a length of 12, as they start by 0 @@ -196,18 +206,32 @@ function openerp_pos_devices(module, instance){ //module is instance.point_of_sa if(!this.check_ean(ean)){ parse_result.type = 'error'; - }else if (prefix2 in {'02':'', '22':'', '24':'', '26':'', '28':''}){ + }else if (prefix2 in this.price_prefix_set){ parse_result.type = 'price'; parse_result.prefix = prefix2; parse_result.id = ean.substring(0,7); parse_result.value = Number(ean.substring(7,12))/100.0; parse_result.unit = 'euro'; - } else if (prefix2 in {'21':'','23':'','27':'','29':'','25':''}) { + } else if (prefix2 in this.price_prefix_set){ parse_result.type = 'weight'; parse_result.prefix = prefix2; - prase_result.id = ean.substring(0,7); + parse_result.id = ean.substring(0,7); parse_result.value = Number(ean.substring(7,12))/1000.0; parse_result.unit = 'Kg'; + }else if (prefix2 in this.client_prefix_set){ + parse_result.type = 'client'; + parse_result.prefix = prefix2; + parse_result.id = ean.substring(0,7); + }else if (prefix2 in this.cashier_prefix_set){ + parse_result.type = 'cashier'; + parse_result.prefix = prefix2; + parse_result.id = ean.substring(0,7); + }else if (prefix2 in this.discount_prefix_set){ + parse_result.type = 'discount'; + parse_result.prefix = prefix2; + parse_result.id = ean.substring(0,7); + parse_result.value = Number(ean.substring(7,12))/100.0; + parse_result.unit = '%'; }else{ parse_result.type = 'unit'; parse_result.prefix = ''; @@ -224,6 +248,8 @@ function openerp_pos_devices(module, instance){ //module is instance.point_of_sa var scannedProductModel = undefined; var parse_result = this.parse_ean(ean); + console.log('getting products:',ean,parse_result,allProducts); + if (parse_result.type === 'price') { var itemCode = parse_result.id; var scannedPackaging = _.detect(allPackages, function(pack) { return pack.ean !== undefined && pack.ean.substring(0,7) === itemCode;}); @@ -249,6 +275,7 @@ function openerp_pos_devices(module, instance){ //module is instance.point_of_sa // a default callback for the 'product' action. It will select the product // corresponding to the ean and add it to the current order. scan_product_callback: function(parse_result){ + var self = this; var selectedOrder = self.pos.get('selectedOrder'); var scannedProductModel = self.get_product_by_ean(parse_result.ean); if (scannedProductModel === undefined) { @@ -283,15 +310,18 @@ function openerp_pos_devices(module, instance){ //module is instance.point_of_sa }; if(parse_result.type in {'unit':'', 'weight':'', 'price':''}){ //ean is associated to a product + console.log('calling product callback'); if(this.action_callback['product']){ + console.log('found product callback'); this.action_callback['product'](parse_result); } }else{ + console.log('calling callback:',parse_result.type); if(this.action_callback[parse_result.type]){ + console.log('found product callback'); this.action_callback[parse_result.type](parse_result); } } - }, // starts catching keyboard events and tries to interpret codebar @@ -324,7 +354,8 @@ function openerp_pos_devices(module, instance){ //module is instance.point_of_sa lastTimeStamp = new Date().getTime(); if (codeNumbers.length == 13) { //We have found what seems to be a valid codebar - var parse_result = this.parse_ean(codeNumbers.join('')); + var parse_result = self.parse_ean(codeNumbers.join('')); + console.log('BARCODE:',parse_result); if (parse_result.type === 'error') { //most likely a checksum error, raise warning $(QWeb.render('pos-scan-warning')).dialog({ @@ -334,13 +365,17 @@ function openerp_pos_devices(module, instance){ //module is instance.point_of_sa title: "Warning", }); }else if(parse_result.type in {'unit':'', 'weight':'', 'price':''}){ //ean is associated to a product - if(this.action_callback['product']){ - this.action_callback['product'](parse_result); + console.log('calling product callback'); + if(self.action_callback['product']){ + console.log('found product callback'); + self.action_callback['product'](parse_result); } //this.trigger("codebar",parse_result ); }else{ - if(this.action_callback[parse_result.type]){ - this.action_callback[parse_result.type](parse_result); + console.log('calling callback:',parse_result.type); + if(self.action_callback[parse_result.type]){ + console.log('found callback'); + self.action_callback[parse_result.type](parse_result); } } diff --git a/addons/point_of_sale/static/src/js/pos_models.js b/addons/point_of_sale/static/src/js/pos_models.js index 1ea09b5d858..8a43677ab5f 100644 --- a/addons/point_of_sale/static/src/js/pos_models.js +++ b/addons/point_of_sale/static/src/js/pos_models.js @@ -431,7 +431,6 @@ function openerp_pos_models(module, instance){ //module is instance.point_of_sal existing = (this.get('orderLines')).get(product.id); if (existing != null) { if(existing.get('weighted')){ - console.log('TODO VERIFY THIS'); existing.incrementWeight(product.attributes.weight); }else{ existing.incrementQuantity(); diff --git a/addons/point_of_sale/static/src/js/pos_screens.js b/addons/point_of_sale/static/src/js/pos_screens.js index 1f478a80c6a..a9c49bf6c6c 100644 --- a/addons/point_of_sale/static/src/js/pos_screens.js +++ b/addons/point_of_sale/static/src/js/pos_screens.js @@ -60,8 +60,7 @@ function openerp_pos_screens(module, instance){ //module is instance.point_of_sa this.current_popup = null; - this.default_mode = options.default_mode || 'client'; - this.current_mode = this.default_mode; + this.current_mode = options.default_mode || 'client'; this.current_screen = this.current_mode === 'client' ? this.current_client_screen: @@ -113,45 +112,28 @@ function openerp_pos_screens(module, instance){ //module is instance.point_of_sa } }, load_saved_screen: function(){ - console.log('load_saved_screen'); this.close_popup(); - if(true || this.selected_order != this.pos.get('selectedOrder')){ - var selectedOrder = this.pos.get('selectedOrder'); - - var user_mode = this.current_mode; //selectedOrder.get('user_mode'); - console.log('user mode:',user_mode); - if(user_mode === 'client'){ - this.current_mode = 'client'; - this.set_current_screen(selectedOrder.get('client_screen') || this.default_client_screen); - }else if(user_mode === 'cashier'){ - this.current_mode = 'cashier'; - console.log('default_cashier_screen:',this.default_cashier_screen); - this.set_current_screen(selectedOrder.get('cashier_screen') || this.default_cashier_screen); - }else{ - this.current_mode = this.default_mode; - selectedOrder.set({ user_mode: this.current_mode }); - if(this.current_mode === 'client'){ - this.set_current_screen(this.default_client_screen); - }else{ - this.set_current_screen(this.default_cashier_screen); - } - } - this.selected_order = selectedOrder; - // var screen = this.pos.get('selectedOrder').get('screen') || this.default_screen; - // this.selected_order = this.pos.get('selectedOrder'); - // this.set_current_screen(screen); + var selectedOrder = this.pos.get('selectedOrder'); + + if(this.current_mode === 'client'){ + this.set_current_screen(selectedOrder.get('client_screen') || this.default_client_screen); + }else if(this.current_mode === 'cashier'){ + console.log('default_cashier_screen:',this.default_cashier_screen); + this.set_current_screen(selectedOrder.get('cashier_screen') || this.default_cashier_screen); } + this.selected_order = selectedOrder; }, set_user_mode: function(user_mode){ - console.log('set user mode:',user_mode); if(user_mode !== this.current_mode){ this.close_popup(); this.current_mode = user_mode; - this.pos.get('selectedOrder').set({ user_mode : this.current_mode }); this.load_saved_screen(); } }, + get_user_mode: function(){ + return this.current_mode; + }, set_current_screen: function(screen_name){ var screen = this.screen_set[screen_name]; @@ -163,7 +145,6 @@ function openerp_pos_screens(module, instance){ //module is instance.point_of_sa selectedOrder.set({'cashier_screen': screen_name}); } - // console.log('Set Current Screen: '+screen_name+' :',screen,'old:',this.current_screen, 'mode:',this.current_mode); if(screen && screen !== this.current_screen){ if(this.current_screen){ this.current_screen.hide(); @@ -282,7 +263,7 @@ function openerp_pos_screens(module, instance){ //module is instance.point_of_sa { label: 'back', icon: '/point_of_sale/static/src/img/icons/png48/go-previous.png', - click: function(){ //TODO Go to ask for weighting screen + click: function(){ clearInterval(this.intervalID); self.pos.proxy.weighting_end(); self.pos.screen_selector.set_current_screen('scan'); @@ -313,37 +294,56 @@ function openerp_pos_screens(module, instance){ //module is instance.point_of_sa show: function(){ this._super(); var self = this; + if(this.pos.screen_selector.get_user_mode() === 'client'){ + this.pos_widget.set_numpad_visible(false); + this.pos_widget.set_leftpane_visible(true); + this.pos_widget.set_cashier_controls_visible(false); + this.pos_widget.action_bar.set_total_visible(true); + this.pos_widget.action_bar.set_help_visible(true,function(){self.pos.screen_selector.show_popup('help');}); + this.pos_widget.action_bar.set_logout_visible(false); - this.pos_widget.set_numpad_visible(false); - this.pos_widget.set_leftpane_visible(true); - this.pos_widget.set_cashier_controls_visible(false); - this.pos_widget.action_bar.set_total_visible(true); - this.pos_widget.action_bar.set_help_visible(true,function(){self.pos.screen_selector.show_popup('help');}); - this.pos_widget.action_bar.set_logout_visible(false); - - this.pos_widget.orderView.setNumpadState(this.pos_widget.numpadView.state); - this.pos_widget.action_bar.add_new_button( - { - label: 'back', - icon: '/point_of_sale/static/src/img/icons/png48/go-previous.png', - click: function(){ - self.pos.screen_selector.set_current_screen('scan'); + this.pos_widget.orderView.setNumpadState(this.pos_widget.numpadView.state); + this.pos_widget.action_bar.add_new_button( + { + label: 'back', + icon: '/point_of_sale/static/src/img/icons/png48/go-previous.png', + click: function(){ + self.pos.screen_selector.set_current_screen('scan'); + } } - } - ); - this.pos.barcode_reader.set_action_callbacks({ - 'cashier': function(ean){ - //TODO 'switch to cashier mode' - self.proxy.cashier_mode_activated(); - }, - }); + ); + this.pos.barcode_reader.set_action_callbacks({ + 'cashier': function(ean){ + self.proxy.cashier_mode_activated(); + }, + }); + this.product_list_widget.set_next_screen('scan'); + }else{ // user_mode === 'cashier' + this.pos_widget.set_numpad_visible(true); + this.pos_widget.set_leftpane_visible(true); + this.pos_widget.set_cashier_controls_visible(true); + this.pos_widget.action_bar.set_total_visible(true); + this.pos_widget.action_bar.set_help_visible(false); + + this.pos_widget.orderView.setNumpadState(this.pos_widget.numpadView.state); + this.pos_widget.action_bar.add_new_button( + { + label: 'back', + icon: '/point_of_sale/static/src/img/icons/png48/go-previous.png', + click: function(){ + self.pos.screen_selector.set_current_screen('products'); + } + } + ); + this.product_list_widget.set_next_screen('undefined'); + } this.pos.proxy.weighting_start(); this.last_weight = this.product_list_widget.weight; this.intervalID = setInterval(function(){ var weight = self.pos.proxy.weighting_read_kg(); if(weight != self.last_weight){ - self.product_list_widget.setWeight(weight); + self.product_list_widget.set_weight(weight); self.last_weight = weight; } },500); @@ -355,7 +355,6 @@ function openerp_pos_screens(module, instance){ //module is instance.point_of_sa clearInterval(this.intervalID); this.pos.proxy.weighting_end(); }, - }); module.ClientPaymentScreenWidget = module.ScreenWidget.extend({ @@ -407,6 +406,10 @@ function openerp_pos_screens(module, instance){ //module is instance.point_of_sa }, }); }, + hide: function(){ + this._super(); + clearInterval(this.intervalID); + }, }); module.WelcomeScreenWidget = module.ScreenWidget.extend({ @@ -438,17 +441,18 @@ function openerp_pos_screens(module, instance){ //module is instance.point_of_sa ); this.pos.barcode_reader.set_action_callbacks({ 'product': function(ean){ - self.proxy.transaction_start(); + console.log('product!'); + self.pos.proxy.transaction_start(); self.pos.barcode_reader.scan_product_callback(ean); self.pos.screen_selector.set_current_screen('products'); }, 'cashier': function(ean){ //TODO 'switch to cashier mode' - self.proxy.cashier_mode_activated(); + self.pos.proxy.cashier_mode_activated(); self.pos.screen_selector.set_current_screen('products'); }, 'client': function(ean){ - self.proxy.transaction_start(); + self.pos.proxy.transaction_start(); //TODO 'log the client' self.pos.screen_selector.show_popup('receipt'); }, @@ -543,7 +547,8 @@ function openerp_pos_screens(module, instance){ //module is instance.point_of_sa { label: 'weight', icon: '/point_of_sale/static/src/img/icons/png48/scale.png', - click: function(){ //TODO Go to ask for weighting screen + click: function(){ + self.pos.screen_selector.set_current_screen('scale_product'); } } ); @@ -557,7 +562,6 @@ function openerp_pos_screens(module, instance){ //module is instance.point_of_sa } }, 'cashier': function(ean){ - //TODO 'switch to cashier mode' self.proxy.cashier_mode_activated(); }, 'discount': function(ean){ diff --git a/addons/point_of_sale/static/src/js/pos_widgets.js b/addons/point_of_sale/static/src/js/pos_widgets.js index 7fae2674a33..834ec0ce973 100644 --- a/addons/point_of_sale/static/src/js/pos_widgets.js +++ b/addons/point_of_sale/static/src/js/pos_widgets.js @@ -244,21 +244,30 @@ function openerp_pos_widgets(module, instance){ //module is instance.point_of_sa this.model = options.model; this.pos = options.pos; this.model.attributes.weight = options.weight || undefined; + this.next_screen = options.next_screen || undefined; }, addToOrder: function(event) { /* Preserve the category URL */ event.preventDefault(); return (this.pos.get('selectedOrder')).addProduct(this.model); }, - setWeight: function(weight){ + set_weight: function(weight){ this.model.attributes.weight = weight; this.renderElement(); }, + set_next_screen: function(screen){ + this.next_screen = screen; + }, renderElement: function() { + var self = this; this.$element.addClass("product"); this.$element.html(this.template_fct(this.model.toJSON())); - $("a", this.$element).click(_.bind(this.addToOrder, this)); - return this; + $("a", this.$element).click(function(e){ + self.addToOrder(e); + if(self.next_screen){ + self.pos.screen_selector.set_current_screen(self.next_screen); //FIXME There ought to be a better way to do this ... + } + }); }, }); @@ -527,10 +536,16 @@ function openerp_pos_widgets(module, instance){ //module is instance.point_of_sa this.pos.get('products').bind('reset', this.renderElement, this); this.product_list = []; this.weight = options.weight; + this.next_screen = options.next_screen || false; }, - setWeight: function(weight){ + set_weight: function(weight){ for(var i = 0; i < this.product_list.length; i++){ - this.product_list[i].setWeight(weight); + this.product_list[i].set_weight(weight); + } + }, + set_next_screen: function(screen){ + for(var i = 0; i < this.product_list.length; i++){ + this.product_list[i].set_next_screen(screen); } }, renderElement: function() { @@ -974,9 +989,11 @@ function openerp_pos_widgets(module, instance){ //module is instance.point_of_sa if(visible){ $('#numpad').show(); $('#paypad').show(); + $('#current-order').css({'bottom':'271px'}); }else{ $('#numpad').hide(); $('#paypad').hide(); + $('#current-order').css({'bottom':'0px'}); } } }, diff --git a/addons/point_of_sale/static/src/xml/pos.xml b/addons/point_of_sale/static/src/xml/pos.xml index eca8f0e932f..34d6bda0518 100644 --- a/addons/point_of_sale/static/src/xml/pos.xml +++ b/addons/point_of_sale/static/src/xml/pos.xml @@ -222,21 +222,23 @@

    Welcome

    -

    please scan an item or your member card

    + +

    Please scan an item or your member card

    -

    Scan your item

    -

    Please scan an item

    +

    Please scan an item

    +

    Payment

    +

    Please insert your card in the reader and follow the instructions to complete your purchase

    @@ -245,7 +247,7 @@

    Please put your product on the scale

    -

    +
    From 72c89a2028fb8e25c76301629277cd0ae1885ffc Mon Sep 17 00:00:00 2001 From: Stephane Wirtel Date: Tue, 8 May 2012 17:28:05 +0200 Subject: [PATCH 092/201] [FIX] point_of_sale: Begin the connection between the frontend and the backend bzr revid: stw@openerp.com-20120508152805-874rbq1jv8mpvwo6 --- addons/point_of_sale/point_of_sale.py | 40 ++++++++++++++----- addons/point_of_sale/point_of_sale_view.xml | 3 +- .../security/ir.model.access.csv | 3 ++ .../point_of_sale/static/src/js/pos_models.js | 39 ++++++++++-------- .../static/src/js/pos_widgets.js | 36 +++++------------ addons/point_of_sale/static/src/xml/pos.xml | 2 +- 6 files changed, 69 insertions(+), 54 deletions(-) diff --git a/addons/point_of_sale/point_of_sale.py b/addons/point_of_sale/point_of_sale.py index 64e56e89f5d..1921edfe872 100644 --- a/addons/point_of_sale/point_of_sale.py +++ b/addons/point_of_sale/point_of_sale.py @@ -174,11 +174,12 @@ class pos_session(osv.osv): 'state' : fields.selection(POS_SESSION_STATE, 'State', required=True, readonly=True, select=1), - 'cash_register_id' : fields.many2one('account.bank.statement', 'Bank Account Statement'), + 'cash_register_id' : fields.many2one('account.bank.statement', 'Bank Account Statement', ondelete='cascade'), 'details_ids' : fields.related('cash_register_id', 'details_ids', type='one2many', relation='account.cashbox.line', string='CashBox Lines'), + 'journal_ids' : fields.related('config_id', 'journal_ids', type='many2many', relation='account.journal', string='Journals'), 'order_ids' : fields.one2many('pos.order', 'session_id', 'Orders'), } @@ -192,7 +193,7 @@ class pos_session(osv.osv): ('uniq_name', 'unique(name)', "The name of this POS Session must be unique !"), ] - def _create_cash_register(self, cr, uid, pos_config, context=None): + def _create_cash_register(self, cr, uid, pos_config, user_id, context=None): if not pos_config: return False @@ -209,6 +210,7 @@ class pos_session(osv.osv): values = { 'journal_id' : journal_id, + 'user_id' : pos_config.user_id and pos_config.user_id.id or uid, } cash_register_id = proxy.create(cr, uid, values, context=context) @@ -220,15 +222,31 @@ class pos_session(osv.osv): if config_id: pos_config = self.pool.get('pos.config').browse(cr, uid, config_id, context=context) name = pos_config.sequence_id._next() + user_id = values.get('user_id', uid) or uid values.update( name=name, - cash_register_id=self._create_cash_register(cr, uid, pos_config, context=context), + cash_register_id=self._create_cash_register(cr, uid, pos_config, user_id=user_id, context=context), ) else: raise osv.except_osv(_('Error!'), _('There is no POS Config attached to this POS Session')) return super(pos_session, self).create(cr, uid, values, context=context) + def unlink(self, cr, uid, ids, context=None): + for obj in self.browse(cr, uid, ids, context=context): + if obj.cash_register_id: + obj.cash_register_id.unlink(context=context) + return True + + def on_change_config(self, cr, uid, ids, config_id, context=None): + result = dict(value=dict()) + if not config_id: + result['value']['user_id'] = uid + else: + result['value']['user_id'] = self.pool.get('pos.config').browse(cr, uid, config_id, context=context).user_id.id + + return result + def wkf_action_open(self, cr, uid, ids, context=None): # si pas de date start_at, je balance une date, sinon on utilise celle de l'utilisateur for record in self.browse(cr, uid, ids, context=context): @@ -271,7 +289,7 @@ class pos_session(osv.osv): domain = [ ('state', '=', 'open'), ('start_at', '>=', time.strftime('%Y-%m-%d 00:00:00')), - ('config_id.user_id', '=', uid), + ('user_id', '=', uid), ] session_ids = self.search(cr, uid, domain, context=context, limit=1, order='start_at desc') session_id = session_ids[0] if session_ids else False @@ -287,7 +305,7 @@ class pos_session(osv.osv): config = pos_config_proxy.browse(cr, uid, pos_config_ids[0], context=context) values = { - 'state' : 'draft', + 'state' : 'new', 'start_at' : time.strftime('%Y-%m-%d %H:%M:%S'), 'config_id' : config.id, 'journal_id' : config.journal_id.id, @@ -322,22 +340,24 @@ class pos_order(osv.osv): def create_from_ui(self, cr, uid, orders, context=None): #_logger.info("orders: %r", orders) list = [] - session_id = self.pool.get('pos.session').get_current_session() + session_id = self.pool.get('pos.session').get_current_session(cr, uid, context=context) for order in orders: # order :: {'name': 'Order 1329148448062', 'amount_paid': 9.42, 'lines': [[0, 0, {'discount': 0, 'price_unit': 1.46, 'product_id': 124, 'qty': 5}], [0, 0, {'discount': 0, 'price_unit': 0.53, 'product_id': 62, 'qty': 4}]], 'statement_ids': [[0, 0, {'journal_id': 7, 'amount': 9.42, 'name': '2012-02-13 15:54:12', 'account_id': 12, 'statement_id': 21}]], 'amount_tax': 0, 'amount_return': 0, 'amount_total': 9.42} order['session_id'] = session_id order_obj = self.pool.get('pos.order') # get statements out of order because they will be generated with add_payment to ensure # the module behavior is the same when using the front-end or the back-end - statement_ids = order.pop('statement_ids') + if not order['data']['statement_ids']: + continue + statement_ids = order['data'].pop('statement_ids') order_id = self.create(cr, uid, order, context) list.append(order_id) # call add_payment; refer to wizard/pos_payment for data structure # add_payment launches the 'paid' signal to advance the workflow to the 'paid' state data = { 'journal': statement_ids[0][2]['journal_id'], - 'amount': order['amount_paid'], - 'payment_name': order['name'], + 'amount': order['data']['amount_paid'], + 'payment_name': order['data']['name'], 'payment_date': statement_ids[0][2]['name'], } order_obj.add_payment(cr, uid, order_id, data, context=context) @@ -550,6 +570,8 @@ class pos_order(osv.osv): def add_payment(self, cr, uid, order_id, data, context=None): """Create a new payment for the order""" + if not context: + context = {} statement_obj = self.pool.get('account.bank.statement') statement_line_obj = self.pool.get('account.bank.statement.line') prod_obj = self.pool.get('product.product') diff --git a/addons/point_of_sale/point_of_sale_view.xml b/addons/point_of_sale/point_of_sale_view.xml index 96c753fb0e6..188dd603708 100644 --- a/addons/point_of_sale/point_of_sale_view.xml +++ b/addons/point_of_sale/point_of_sale_view.xml @@ -852,7 +852,7 @@ - + @@ -870,6 +870,7 @@ + diff --git a/addons/point_of_sale/security/ir.model.access.csv b/addons/point_of_sale/security/ir.model.access.csv index 9eb77e81244..c0219c40cd5 100644 --- a/addons/point_of_sale/security/ir.model.access.csv +++ b/addons/point_of_sale/security/ir.model.access.csv @@ -32,6 +32,7 @@ access_account_period_pos_manager,account.period pos manager,account.model_accou access_account_fiscalyear_pos_user,account.fiscalyear user,account.model_account_fiscalyear,group_pos_user,1,1,1,0 access_account_fiscalyear_pos_manager,account.fiscalyear manager,account.model_account_fiscalyear,group_pos_manager,1,0,0,0 access_account_cashbox_line,account.cashbox.line,account.model_account_cashbox_line,group_pos_user,1,1,1,0 +access_account_journal_cashbox_line,account.cashbox.journal.line,account.model_account_journal_cashbox_line,group_pos_user,1,1,1,0 access_account_cashbox_line_manager,account.cashbox.line manager,account.model_account_cashbox_line,group_pos_manager,1,1,1,1 access_product_product,product.product,product.model_product_product,group_pos_user,1,0,0,0 access_product_template_pos_user,product.template pos user,product.model_product_template,group_pos_user,1,0,0,0 @@ -64,3 +65,5 @@ access_product_category_manager,product.category manager,product.model_product_c access_product_pricelist_manager,product.pricelist manager,product.model_product_pricelist,group_pos_manager,1,0,0,0 access_product_category_pos_manager,pos.category manager,model_pos_category,group_pos_manager,1,1,1,"1""" access_product_category_pos_user,pos.category user,model_pos_category,group_pos_user,1,0,0,"0""" +access_pos_session_user,pos.session user,model_pos_session,group_pos_user,1,1,1,0 +access_pos_config_user,pos.config user,model_pos_config,group_pos_user,1,1,1,0 \ No newline at end of file diff --git a/addons/point_of_sale/static/src/js/pos_models.js b/addons/point_of_sale/static/src/js/pos_models.js index 8a43677ab5f..ec3d65b9dab 100644 --- a/addons/point_of_sale/static/src/js/pos_models.js +++ b/addons/point_of_sale/static/src/js/pos_models.js @@ -67,7 +67,6 @@ function openerp_pos_models(module, instance){ //module is instance.point_of_sal 'user': {}, 'orders': new module.OrderCollection(), 'products': new module.ProductCollection(), - //'cashRegisters': [], // new module.CashRegisterCollection(this.pos.get('bank_statements')), 'selectedOrder': undefined, }); @@ -85,13 +84,19 @@ function openerp_pos_models(module, instance){ //module is instance.point_of_sal return self.set({'product_list': result}); }); - var bank_def = fetch( - 'account.bank.statement', - ['account_id', 'currency', 'journal_id', 'state', 'name'], - [['state','=','open'], ['user_id', '=', this.session.uid]] - ).then(function(result){ - console.log('bank_statements:',result); - return self.set({'bank_statements': result}); + var session_def = fetch( + 'pos.session', + ['id', 'journal_ids'], + [['state', '=', 'opened'], ['user_id', '=', this.session.uid]] + ).then(function(result) { + console.log('pos_session:', result); + var journal_def = fetch( + 'account.journal', + ['name'], + [['id', 'in', result[0]['journal_ids']]]).then(function(inner_result) { + self.set({'account_journals' : inner_result}); + }); + return self; }); var tax_def = fetch('account.tax', ['amount','price_include','type']) @@ -100,10 +105,10 @@ function openerp_pos_models(module, instance){ //module is instance.point_of_sal return self.set({'taxes': result}); }); - $.when(cat_def,prod_def,bank_def,tax_def,this.get_app_data(), this.flush()) + $.when(cat_def, prod_def, session_def, tax_def, this.get_app_data(), this.flush()) .pipe(_.bind(this.build_tree, this)) .pipe(function(){ - self.set({'cashRegisters': new module.CashRegisterCollection(self.get('bank_statements')) }); + self.set({'accountJournals' : new module.AccountJournalCollection(self.get('account_journals'))}); self.ready.resolve(); }); @@ -248,11 +253,11 @@ function openerp_pos_models(module, instance){ //module is instance.point_of_sal } }); - module.CashRegister = Backbone.Model.extend({ + module.AccountJournal = Backbone.Model.extend({ }); - module.CashRegisterCollection = Backbone.Collection.extend({ - model: module.CashRegister, + module.AccountJournalCollection = Backbone.Collection.extend({ + model: module.AccountJournal, }); module.Product = Backbone.Model.extend({ @@ -380,9 +385,11 @@ function openerp_pos_models(module, instance){ //module is instance.point_of_sal Backbone.Model.prototype.initialize.apply(this, arguments); }, getAmount: function(){ - return this.get('amount'); + // FIXME + return 0.0; //this.get('amount'); }, exportAsJSON: function(){ + // FIXME return { name: instance.web.datetime_to_str(new Date()), statement_id: this.get('id'), @@ -445,9 +452,9 @@ function openerp_pos_models(module, instance){ //module is instance.point_of_sal }, this); } }, - addPaymentLine: function(cashRegister) { + addPaymentLine: function(accountJournal) { var newPaymentline; - newPaymentline = new module.Paymentline(cashRegister); + newPaymentline = new module.Paymentline(accountJournal); /* TODO: Should be 0 for cash-like accounts */ newPaymentline.set({ amount: this.getDueLeft() diff --git a/addons/point_of_sale/static/src/js/pos_widgets.js b/addons/point_of_sale/static/src/js/pos_widgets.js index 834ec0ce973..9a6345e6f9d 100644 --- a/addons/point_of_sale/static/src/js/pos_widgets.js +++ b/addons/point_of_sale/static/src/js/pos_widgets.js @@ -74,22 +74,22 @@ function openerp_pos_widgets(module, instance){ //module is instance.point_of_sa performPayment: function(event) { if (this.pos.get('selectedOrder').get('screen') === 'receipt') return; - var cashRegister, cashRegisterCollection, cashRegisterId; + var accountJournal, accountJournalCollection, accountJournalId; /* set correct view */ this.pos.screen_selector.set_current_screen('payment'); - cashRegisterId = event.currentTarget.attributes['cash-register-id'].nodeValue; - cashRegisterCollection = this.pos.get('cashRegisters'); - cashRegister = cashRegisterCollection.find(_.bind( function(item) { - return (item.get('id')) === parseInt(cashRegisterId, 10); + accountJournalId = event.currentTarget.attributes['account-journal-id'].nodeValue; + accountRegisterCollection = this.pos.get('accountJournals'); + accountJournal = accountRegisterCollection.find(_.bind(function(item) { + return (item.get('id')) === parseInt(accountJournalId, 10); }, this)); - return (this.pos.get('selectedOrder')).addPaymentLine(cashRegister); + return (this.pos.get('selectedOrder')).addPaymentLine(accountJournal); }, renderElement: function() { this.$element.empty(); - return (this.pos.get('cashRegisters')).each(_.bind( function(cashRegister) { + return (this.pos.get('accountJournals')).each(_.bind(function(accountJournal) { var button = new module.PaymentButtonWidget(); - button.model = cashRegister; + button.model = accountJournal; button.appendTo(this.$element); }, this)); } @@ -100,7 +100,7 @@ function openerp_pos_widgets(module, instance){ //module is instance.point_of_sa renderElement: function() { this.$element.html(this.template_fct({ id: this.model.get('id'), - name: (this.model.get('journal_id'))[1] + name: this.model.get('name') })); return this; } @@ -825,15 +825,6 @@ function openerp_pos_widgets(module, instance){ //module is instance.point_of_sa this.buildWidgets(); instance.webclient.set_content_full_screen(true); - - if (self.pos.get('bank_statements').length === 0) - return new instance.web.Model("ir.model.data").get_func("search_read")([['name', '=', 'action_pos_open_statement']], ['res_id']).pipe( - _.bind(function(res) { - return this.rpc('/web/action/load', {'action_id': res[0]['res_id']}).pipe(_.bind(function(result) { - var action = result.result; - this.do_action(action); - }, this)); - }, this)); }, this)); }, render: function() { @@ -1054,15 +1045,6 @@ function openerp_pos_widgets(module, instance){ //module is instance.point_of_sa }, close: function() { this.pos.barcode_reader.disconnect(); - - return new instance.web.Model("ir.model.data").get_func("search_read")([['name', '=', 'action_pos_close_statement']], ['res_id']).pipe( - _.bind(function(res) { - return this.rpc('/web/action/load', {'action_id': res[0]['res_id']}).pipe(_.bind(function(result) { - var action = result.result; - action.context = _.extend(action.context || {}, {'cancel_action': {type: 'ir.actions.client', tag: 'default_home'}}); - this.do_action(action); - }, this)); - }, this)); }, destroy: function() { instance.webclient.set_content_full_screen(false); diff --git a/addons/point_of_sale/static/src/xml/pos.xml b/addons/point_of_sale/static/src/xml/pos.xml index 34d6bda0518..07b86ed39eb 100644 --- a/addons/point_of_sale/static/src/xml/pos.xml +++ b/addons/point_of_sale/static/src/xml/pos.xml @@ -350,7 +350,7 @@
    -
    From 7e38590ac33525a39775164ac28e1b2768a51dee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20van=20der=20Essen?= Date: Tue, 8 May 2012 17:40:30 +0200 Subject: [PATCH 093/201] Forgot to add pictures, handling of codebar scanning in popups, client mode payment fixes bzr revid: fva@openerp.com-20120508154030-f62eahx7c801lxnz --- addons/point_of_sale/static/src/css/pos.css | 3 - .../static/src/img/bancontact.png | Bin 0 -> 13380 bytes addons/point_of_sale/static/src/img/scale.png | Bin 0 -> 22887 bytes addons/point_of_sale/static/src/img/scan.png | Bin 0 -> 14404 bytes .../static/src/js/pos_devices.js | 80 ++++----------- .../static/src/js/pos_screens.js | 92 ++++++++++++------ .../static/src/js/pos_widgets.js | 41 ++++++++ 7 files changed, 124 insertions(+), 92 deletions(-) create mode 100644 addons/point_of_sale/static/src/img/bancontact.png create mode 100644 addons/point_of_sale/static/src/img/scale.png create mode 100644 addons/point_of_sale/static/src/img/scan.png diff --git a/addons/point_of_sale/static/src/css/pos.css b/addons/point_of_sale/static/src/css/pos.css index 4ba5520752f..e1d01ee9b52 100644 --- a/addons/point_of_sale/static/src/css/pos.css +++ b/addons/point_of_sale/static/src/css/pos.css @@ -1,6 +1,3 @@ -body{ - overflow: hidden; -} .point-of-sale { padding: 0; margin: 0; diff --git a/addons/point_of_sale/static/src/img/bancontact.png b/addons/point_of_sale/static/src/img/bancontact.png new file mode 100644 index 0000000000000000000000000000000000000000..5c34fefb0f9ec38d71decfebaf14848954c57c34 GIT binary patch literal 13380 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
    Ory?^BHW^`JHX{6!IHgY};45*b6&r)Clo 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 Cu|kIc literal 0 HcmV?d00001 diff --git a/addons/point_of_sale/static/src/img/scan.png b/addons/point_of_sale/static/src/img/scan.png new file mode 100644 index 0000000000000000000000000000000000000000..e5eafbe821d11811a227370edcc379a3ba47ef3b GIT binary patch literal 14404 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 qDG(DDwDL3H|2O~NtdqV+%rhba*X`b4QK Date: Tue, 8 May 2012 17:49:42 +0200 Subject: [PATCH 094/201] [FIX] point_of_sale: Add the ean13 field in the frontend bzr revid: stw@openerp.com-20120508154942-sou5eycvhxd8st5w --- addons/point_of_sale/static/src/js/pos_models.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/point_of_sale/static/src/js/pos_models.js b/addons/point_of_sale/static/src/js/pos_models.js index ec3d65b9dab..9833de9e679 100644 --- a/addons/point_of_sale/static/src/js/pos_models.js +++ b/addons/point_of_sale/static/src/js/pos_models.js @@ -77,7 +77,7 @@ function openerp_pos_models(module, instance){ //module is instance.point_of_sal var prod_def = fetch( 'product.product', - ['name', 'list_price', 'pos_categ_id', 'taxes_id','product_image_small'], + ['name', 'list_price', 'pos_categ_id', 'taxes_id','product_image_small', 'ean13'], [['pos_categ_id','!=', false]] ).then(function(result){ console.log('product_list:',result); From 8f4c7e161d7c8771e220489c0b8e3f5445f24341 Mon Sep 17 00:00:00 2001 From: Stephane Wirtel Date: Tue, 8 May 2012 18:42:26 +0200 Subject: [PATCH 095/201] [FIX] point_of_sale: Check if there is at least one record bzr revid: stw@openerp.com-20120508164226-1vn2ub22ioo5gtly --- addons/point_of_sale/static/src/js/pos_models.js | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/addons/point_of_sale/static/src/js/pos_models.js b/addons/point_of_sale/static/src/js/pos_models.js index 9833de9e679..54c2cd189c3 100644 --- a/addons/point_of_sale/static/src/js/pos_models.js +++ b/addons/point_of_sale/static/src/js/pos_models.js @@ -89,13 +89,15 @@ function openerp_pos_models(module, instance){ //module is instance.point_of_sal ['id', 'journal_ids'], [['state', '=', 'opened'], ['user_id', '=', this.session.uid]] ).then(function(result) { - console.log('pos_session:', result); - var journal_def = fetch( - 'account.journal', - ['name'], - [['id', 'in', result[0]['journal_ids']]]).then(function(inner_result) { - self.set({'account_journals' : inner_result}); - }); + if( result.length !== 0 ) { + console.log('pos_session:', result); + var journal_def = fetch( + 'account.journal', + ['name'], + [['id', 'in', result[0]['journal_ids']]]).then(function(inner_result) { + self.set({'account_journals' : inner_result}); + }); + } return self; }); From 2def801d804d08fde4fef031314370d27d4864ef Mon Sep 17 00:00:00 2001 From: Stephane Wirtel Date: Wed, 9 May 2012 10:19:42 +0200 Subject: [PATCH 096/201] [IMP] point_of_sale: Add some fields in readonly mode bzr revid: stw@openerp.com-20120509081942-oo65eepvzi0whz03 --- addons/point_of_sale/point_of_sale.py | 110 +++++++++++++++----- addons/point_of_sale/point_of_sale_view.xml | 44 +++++--- 2 files changed, 115 insertions(+), 39 deletions(-) diff --git a/addons/point_of_sale/point_of_sale.py b/addons/point_of_sale/point_of_sale.py index 1921edfe872..b47723d9d56 100644 --- a/addons/point_of_sale/point_of_sale.py +++ b/addons/point_of_sale/point_of_sale.py @@ -36,20 +36,42 @@ _logger = logging.getLogger(__name__) class pos_config(osv.osv): _name = 'pos.config' - POS_CONFIG_STATE = [('draft', 'Draft'),('active', 'Active'),('inactive', 'Inactive'),('deprecated', 'Deprecated')] + POS_CONFIG_STATE = [ + ('draft', 'Draft'), + ('active', 'Active'), + ('inactive', 'Inactive'), + ('deprecated', 'Deprecated') + ] _columns = { - 'name' : fields.char('Name', size=32, select=1, required=True), - 'journal_ids' : fields.many2many('account.journal', 'pos_config_journal_rel', 'pos_config_id', 'journal_id', 'Payment Methods', domain="[('journal_user', '=', True )]"), - 'shop_id' : fields.many2one('sale.shop', 'Shop', required=True, select=1), - 'journal_id' : fields.many2one('account.journal', 'Journal', required=True, select=1, domain=[('type', '=', 'sale')]), - 'profit_account_id' : fields.many2one('account.account', 'Profit Account', required=True, select=1), - 'loss_account_id' : fields.many2one('account.account', 'Loss Account', required=True, select=1), - - 'authorized_cashbox_diff' : fields.integer('Authorized Cashbox Difference (%)'), - 'authorized_cashbox_diff_fixed' : fields.integer('Authorized Cashbox Difference (Fixed Amount)'), - - + 'name' : fields.char('Name', size=32, + select=1, + required=True, + readonly=True, + states={'draft' : [('readonly', False)]} + ), + 'journal_ids' : fields.many2many('account.journal', + 'pos_config_journal_rel', + 'pos_config_id', + 'journal_id', + 'Payment Methods', + domain="[('journal_user', '=', True )]", + readonly=True, + states={'draft' : [('readonly', False)]} + ), + 'shop_id' : fields.many2one('sale.shop', 'Shop', + required=True, + select=1, + readonly=True, + states={'draft' : [('readonly', False)]} + ), + 'journal_id' : fields.many2one('account.journal', 'Journal', + required=True, + select=1, + domain=[('type', '=', 'sale')], + readonly=True, + states={'draft' : [('readonly', False)]} + ), 'iface_self_checkout' : fields.boolean('Self Checkout Mode'), 'iface_websql' : fields.boolean('WebSQL (to store data)'), 'iface_led' : fields.boolean('LED Interface'), @@ -59,17 +81,19 @@ class pos_config(osv.osv): 'iface_barscan' : fields.boolean('BarScan Interface'), 'iface_vkeyboard' : fields.boolean('Virtual KeyBoard Interface'), - 'state' : fields.selection(POS_CONFIG_STATE, 'State', required=True, readonly=True), + 'state' : fields.selection(POS_CONFIG_STATE, 'State', + required=True, + readonly=True), - 'sequence_id' : fields.many2one('ir.sequence', 'Sequence', readonly=True), - # Add a sequence when we create a new pos.config object - 'user_id' : fields.many2one('res.users', 'User'), + 'sequence_id' : fields.many2one('ir.sequence', 'Sequence', + readonly=True), + 'user_id' : fields.many2one('res.users', 'User', + readonly=True, + states={'draft' : [('readonly', False)]} + ), } - #def default_get(self, cr, uid, fieldnames, context=None): - # return dict() - _defaults = { 'state' : 'draft', 'user_id' : lambda obj, cr, uid, context: uid, @@ -165,21 +189,45 @@ class pos_session(osv.osv): #] _columns = { - 'config_id' : fields.many2one('pos.config', 'PoS', required=True, select=1, domain="[('state', '=', 'active')]"), + 'config_id' : fields.many2one('pos.config', 'PoS', + required=True, + select=1, + domain="[('state', '=', 'active')]", + readonly=True, + states={'draft' : [('readonly', False)]} + ), - 'name' : fields.char('Session Sequence', size=32, required=True, select=1, readonly=1), - 'user_id' : fields.many2one('res.users', 'User', required=True, select=1), - 'start_at' : fields.datetime('Opening Date'), #, readonly=True), + 'name' : fields.char('Session Sequence', size=32, + required=True, + select=1, + readonly=True, + states={'draft' : [('readonly', False)]} + ), + 'user_id' : fields.many2one('res.users', 'User', + required=True, + select=1, + readonly=True, + states={'draft' : [('readonly', False)]} + ), + 'start_at' : fields.datetime('Opening Date'), 'stop_at' : fields.datetime('Closing Date'), - 'state' : fields.selection(POS_SESSION_STATE, 'State', required=True, readonly=True, select=1), + 'state' : fields.selection(POS_SESSION_STATE, 'State', + required=True, + readonly=True, + select=1), - 'cash_register_id' : fields.many2one('account.bank.statement', 'Bank Account Statement', ondelete='cascade'), + 'cash_register_id' : fields.many2one('account.bank.statement', 'Bank Account Statement', + ondelete='cascade'), 'details_ids' : fields.related('cash_register_id', 'details_ids', type='one2many', relation='account.cashbox.line', string='CashBox Lines'), - 'journal_ids' : fields.related('config_id', 'journal_ids', type='many2many', relation='account.journal', string='Journals'), + 'journal_ids' : fields.related('config_id', 'journal_ids', + type='many2many', + readonly=True, + relation='account.journal', + string='Journals'), 'order_ids' : fields.one2many('pos.order', 'session_id', 'Orders'), } @@ -296,7 +344,14 @@ class pos_session(osv.osv): if not session_id: pos_config_proxy = self.pool.get('pos.config') - pos_config_ids = pos_config_proxy.search(cr, uid, [('user_id', '=', uid),('state', '=', 'active')], limit=1, order='create_date desc') + domain = [ + ('user_id', '=', uid), + ('state', '=', 'active'), + ] + pos_config_ids = pos_config_proxy.search(cr, uid, domain, + limit=1, + order='create_date desc', + context=context) if not pos_config_ids: raise osv.except_osv(_('Error !'), @@ -311,6 +366,7 @@ class pos_session(osv.osv): 'journal_id' : config.journal_id.id, 'user_id': current_user.id, } + session_id = self.create(cr, uid, values, context=context) wkf_service = netsvc.LocalService('workflow') wkf_service.trg_validate(uid, 'pos.session', session_id, 'open', cr) diff --git a/addons/point_of_sale/point_of_sale_view.xml b/addons/point_of_sale/point_of_sale_view.xml index 188dd603708..f148fd96c74 100644 --- a/addons/point_of_sale/point_of_sale_view.xml +++ b/addons/point_of_sale/point_of_sale_view.xml @@ -774,13 +774,6 @@ - - - - - - - @@ -792,12 +785,10 @@ - + - - @@ -816,13 +807,41 @@ pos.config tree - + + + + + + pos.config.search.view + pos.config + search + + + + + + + + + + + + + + + + + + + + + - Point of Sales + PoS Config ir.actions.act_window pos.config form tree,form + Date: Wed, 9 May 2012 10:39:40 +0200 Subject: [PATCH 097/201] [IMP] point_of_sale: Add the 'to_weight' field on the category for the weight bzr revid: stw@openerp.com-20120509083940-akzpgtwkre87rczr --- addons/point_of_sale/__init__.py | 1 + addons/point_of_sale/__openerp__.py | 1 + addons/point_of_sale/product.py | 13 +++++++++++++ addons/point_of_sale/product_view.xml | 16 ++++++++++++++++ 4 files changed, 31 insertions(+) create mode 100644 addons/point_of_sale/product.py create mode 100644 addons/point_of_sale/product_view.xml diff --git a/addons/point_of_sale/__init__.py b/addons/point_of_sale/__init__.py index 4d35174ce6b..323eb4ccd7f 100644 --- a/addons/point_of_sale/__init__.py +++ b/addons/point_of_sale/__init__.py @@ -21,6 +21,7 @@ import point_of_sale import account_bank_statement +import product import wizard import report diff --git a/addons/point_of_sale/__openerp__.py b/addons/point_of_sale/__openerp__.py index c97ffedf046..91f5202536b 100644 --- a/addons/point_of_sale/__openerp__.py +++ b/addons/point_of_sale/__openerp__.py @@ -66,6 +66,7 @@ Main features : 'point_of_sale_workflow.xml', 'account_statement_view.xml', 'account_statement_report.xml', + 'product_view.xml', ], 'demo_xml': [ 'point_of_sale_demo.xml', diff --git a/addons/point_of_sale/product.py b/addons/point_of_sale/product.py new file mode 100644 index 00000000000..0b1b8d19d33 --- /dev/null +++ b/addons/point_of_sale/product.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python +from osv import osv, fields + +class product_category(osv.osv): + _inherit = 'product.category' + + _columns = { + 'to_weight' : fields.boolean('To Weight'), + } + + _defaults = { + 'to_weight' : False, + } diff --git a/addons/point_of_sale/product_view.xml b/addons/point_of_sale/product_view.xml new file mode 100644 index 00000000000..c693f37aa23 --- /dev/null +++ b/addons/point_of_sale/product_view.xml @@ -0,0 +1,16 @@ + + + + + product.category.form + product.category + form + + + + + + + + + \ No newline at end of file From ca2db3740e8b8306c0a5567c12b535b3c27396ee Mon Sep 17 00:00:00 2001 From: Stephane Wirtel Date: Wed, 9 May 2012 11:35:17 +0200 Subject: [PATCH 098/201] [IMP] point_of_sale: Add the EAN13 field on the user with validation bzr revid: stw@openerp.com-20120509093517-24lw0pbiqum0wpw1 --- addons/point_of_sale/__init__.py | 1 + addons/point_of_sale/__openerp__.py | 1 + addons/point_of_sale/res_users.py | 54 +++++++++++++++++++++++++ addons/point_of_sale/res_users_view.xml | 18 +++++++++ 4 files changed, 74 insertions(+) create mode 100644 addons/point_of_sale/res_users.py create mode 100644 addons/point_of_sale/res_users_view.xml diff --git a/addons/point_of_sale/__init__.py b/addons/point_of_sale/__init__.py index 323eb4ccd7f..ec5403212f4 100644 --- a/addons/point_of_sale/__init__.py +++ b/addons/point_of_sale/__init__.py @@ -22,6 +22,7 @@ import point_of_sale import account_bank_statement import product +import res_users import wizard import report diff --git a/addons/point_of_sale/__openerp__.py b/addons/point_of_sale/__openerp__.py index 91f5202536b..088e3ceeb3a 100644 --- a/addons/point_of_sale/__openerp__.py +++ b/addons/point_of_sale/__openerp__.py @@ -67,6 +67,7 @@ Main features : 'account_statement_view.xml', 'account_statement_report.xml', 'product_view.xml', + 'res_users_view.xml', ], 'demo_xml': [ 'point_of_sale_demo.xml', diff --git a/addons/point_of_sale/res_users.py b/addons/point_of_sale/res_users.py new file mode 100644 index 00000000000..e048ad01dac --- /dev/null +++ b/addons/point_of_sale/res_users.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python +from osv import osv, fields +import math + +def is_pair(x): + return not x%2 +# This code is a duplicate of product#check_ean function +def check_ean(eancode): + if not eancode: + return True + if len(eancode) <> 13: + return False + try: + int(eancode) + except: + return False + oddsum=0 + evensum=0 + total=0 + eanvalue=eancode + reversevalue = eanvalue[::-1] + finalean=reversevalue[1:] + + for i in range(len(finalean)): + if is_pair(i): + oddsum += int(finalean[i]) + else: + evensum += int(finalean[i]) + total=(oddsum * 3) + evensum + + check = int(10 - math.ceil(total % 10.0)) %10 + + if check != int(eancode[-1]): + return False + return True + +class res_users(osv.osv): + _inherit = 'res.users' + + _columns = { + 'ean13' : fields.char('EAN13', size=13, help="BarCode"), + } + + def _check_ean(self, cr, uid, ids, context=None): + return all( + check_ean(user.ean13) == True + for user in self.browse(cr, uid, ids, context=context) + ) + + _constraints = [ + (_check_ean, "Error: Invalid ean code", ['ean13'],), + ] + + diff --git a/addons/point_of_sale/res_users_view.xml b/addons/point_of_sale/res_users_view.xml new file mode 100644 index 00000000000..c34b9566406 --- /dev/null +++ b/addons/point_of_sale/res_users_view.xml @@ -0,0 +1,18 @@ + + + + + res.users.form.view + res.users + form + + + + + + + + + + + \ No newline at end of file From 8a09bcf83ece18505a343ebbc5c6d6b3e98c7a4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20van=20der=20Essen?= Date: Wed, 9 May 2012 11:44:38 +0200 Subject: [PATCH 099/201] removed debug logs, fixed product barcode scanning bzr revid: fva@openerp.com-20120509094438-0vibc269r3ayb6ta --- addons/point_of_sale/static/src/js/pos_devices.js | 6 +----- addons/point_of_sale/static/src/js/pos_models.js | 1 - addons/point_of_sale/static/src/js/pos_screens.js | 3 --- addons/point_of_sale/static/src/js/pos_widgets.js | 2 +- 4 files changed, 2 insertions(+), 10 deletions(-) diff --git a/addons/point_of_sale/static/src/js/pos_devices.js b/addons/point_of_sale/static/src/js/pos_devices.js index 0a3fbe9b9ac..8311b256cfe 100644 --- a/addons/point_of_sale/static/src/js/pos_devices.js +++ b/addons/point_of_sale/static/src/js/pos_devices.js @@ -17,7 +17,7 @@ function openerp_pos_devices(module, instance){ //module is instance.point_of_sa module.ProxyDevice = instance.web.Class.extend({ //a product has been scanned and recognized with success - scan_item_succes: function(){ + scan_item_success: function(){ console.log('PROXY: scan item success'); }, @@ -323,16 +323,12 @@ function openerp_pos_devices(module, instance){ //module is instance.point_of_sa title: "Warning", }); }else if(parse_result.type in {'unit':'', 'weight':'', 'price':''}){ //ean is associated to a product - console.log('calling product callback'); if(self.action_callback['product']){ - console.log('found product callback'); self.action_callback['product'](parse_result); } //this.trigger("codebar",parse_result ); }else{ - console.log('calling callback:',parse_result.type); if(self.action_callback[parse_result.type]){ - console.log('found callback'); self.action_callback[parse_result.type](parse_result); } } diff --git a/addons/point_of_sale/static/src/js/pos_models.js b/addons/point_of_sale/static/src/js/pos_models.js index 54c2cd189c3..c8aa194f92e 100644 --- a/addons/point_of_sale/static/src/js/pos_models.js +++ b/addons/point_of_sale/static/src/js/pos_models.js @@ -284,7 +284,6 @@ function openerp_pos_models(module, instance){ //module is instance.point_of_sal }, initialize: function(attributes) { this.pos = attributes.pos; - console.log(attributes); Backbone.Model.prototype.initialize.apply(this, arguments); if(attributes.weight){ diff --git a/addons/point_of_sale/static/src/js/pos_screens.js b/addons/point_of_sale/static/src/js/pos_screens.js index e693a6beada..f3a576a306d 100644 --- a/addons/point_of_sale/static/src/js/pos_screens.js +++ b/addons/point_of_sale/static/src/js/pos_screens.js @@ -119,7 +119,6 @@ function openerp_pos_screens(module, instance){ //module is instance.point_of_sa if(this.current_mode === 'client'){ this.set_current_screen(selectedOrder.get('client_screen') || this.default_client_screen); }else if(this.current_mode === 'cashier'){ - console.log('default_cashier_screen:',this.default_cashier_screen); this.set_current_screen(selectedOrder.get('cashier_screen') || this.default_cashier_screen); } this.selected_order = selectedOrder; @@ -228,7 +227,6 @@ function openerp_pos_screens(module, instance){ //module is instance.point_of_sa this.pos.barcode_reader.save_callbacks(); this.pos.barcode_reader.reset_action_callbacks(); - console.log('actionz callbacks zwave been resetses'); this.pos.barcode_reader.set_action_callbacks({ 'cashier': function(ean){ clearInterval(this.intervalID); @@ -699,7 +697,6 @@ function openerp_pos_screens(module, instance){ //module is instance.point_of_sa return this.$element.find('#paymentlines'); }, back: function() { - console.log('back'); this.pos.screen_selector.set_current_screen('products'); }, validateCurrentOrder: function() { diff --git a/addons/point_of_sale/static/src/js/pos_widgets.js b/addons/point_of_sale/static/src/js/pos_widgets.js index 4bb6ba19f0a..afd755b03de 100644 --- a/addons/point_of_sale/static/src/js/pos_widgets.js +++ b/addons/point_of_sale/static/src/js/pos_widgets.js @@ -957,7 +957,7 @@ function openerp_pos_widgets(module, instance){ //module is instance.point_of_sa //FIXME this method is probably not at the right place ... scan_product: function(parsed_ean){ var selectedOrder = this.pos.get('selectedOrder'); - var scannedProductModel = this.get_product_by_ean(parsed_ean.ean); + var scannedProductModel = this.get_product_by_ean(parsed_ean); if (!scannedProductModel){ return false; } else { From 1050ef628c041f149ef9afa1a292109abefe8634 Mon Sep 17 00:00:00 2001 From: Stephane Wirtel Date: Wed, 9 May 2012 11:55:55 +0200 Subject: [PATCH 100/201] [IMP] point_of_sale: Show a popup when there is no pos session bzr revid: stw@openerp.com-20120509095555-sw6h3acxl9pn1537 --- addons/point_of_sale/static/src/js/pos_widgets.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/addons/point_of_sale/static/src/js/pos_widgets.js b/addons/point_of_sale/static/src/js/pos_widgets.js index 4bb6ba19f0a..a2f0db5f336 100644 --- a/addons/point_of_sale/static/src/js/pos_widgets.js +++ b/addons/point_of_sale/static/src/js/pos_widgets.js @@ -825,6 +825,10 @@ function openerp_pos_widgets(module, instance){ //module is instance.point_of_sa this.buildWidgets(); instance.webclient.set_content_full_screen(true); + if (self.pos.get('account_journals').length === 0) { + // TODO: Create a popup to inform there is no PoSSession for this user + self.pos.screen_selector.show_popup('error'); + } }, this)); }, render: function() { From cd171effd5d6348b10a39efabb33a182f560d5eb Mon Sep 17 00:00:00 2001 From: Stephane Wirtel Date: Wed, 9 May 2012 12:39:59 +0200 Subject: [PATCH 101/201] [FIX] point_of_sale: Use the right values for the state and fix the closing button bzr revid: stw@openerp.com-20120509103959-dgb2qzd7v1afh2hp --- addons/point_of_sale/point_of_sale.py | 7 ++++--- addons/point_of_sale/point_of_sale_view.xml | 1 - 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/addons/point_of_sale/point_of_sale.py b/addons/point_of_sale/point_of_sale.py index b47723d9d56..f40ff5a1bd2 100644 --- a/addons/point_of_sale/point_of_sale.py +++ b/addons/point_of_sale/point_of_sale.py @@ -311,12 +311,13 @@ class pos_session(osv.osv): def wkf_action_close(self, cr, uid, ids, context=None): # Close CashBox - record.cash_register_id.button_confirm_cash(context=context) - return self.write(cr, uid, ids, {'state' : 'close', 'stop_at' : time.strftime('%Y-%m-%d %H:%M:%S')}, context=context) + for record in self.browse(cr, uid, ids, context=context): + record.cash_register_id.button_confirm_cash(context=context) + return self.write(cr, uid, ids, {'state' : 'closed', 'stop_at' : time.strftime('%Y-%m-%d %H:%M:%S')}, context=context) def wkf_action_post(self, cr, uid, ids, context=None): self._confirm_orders(cr, uid, ids, context=context) - return self.write(cr, uid, ids, {'state' : 'post'}, context=context) + return self.write(cr, uid, ids, {'state' : 'posted'}, context=context) def _confirm_orders(self, cr, uid, ids, context=None): wf_service = netsvc.LocalService("workflow") diff --git a/addons/point_of_sale/point_of_sale_view.xml b/addons/point_of_sale/point_of_sale_view.xml index f148fd96c74..062efd14fdc 100644 --- a/addons/point_of_sale/point_of_sale_view.xml +++ b/addons/point_of_sale/point_of_sale_view.xml @@ -890,7 +890,6 @@ - From a4dd08a34045db540967ba49c5b82873e4bccc5b Mon Sep 17 00:00:00 2001 From: Stephane Wirtel Date: Wed, 9 May 2012 15:30:18 +0200 Subject: [PATCH 102/201] [IMP] point_of_sale: review the session workflow [REF] point_of_sale: move the 'to_weight' field into the pos.category bzr revid: stw@openerp.com-20120509133018-mu06479yu2yahvu4 --- addons/point_of_sale/__init__.py | 1 - addons/point_of_sale/__openerp__.py | 1 - .../point_of_sale/account_statement_view.xml | 2 + addons/point_of_sale/point_of_sale.py | 63 ++++++++++--------- addons/point_of_sale/point_of_sale_view.xml | 12 ++-- .../point_of_sale/point_of_sale_workflow.xml | 48 +++++++------- addons/point_of_sale/product.py | 13 ---- addons/point_of_sale/product_view.xml | 16 ----- addons/point_of_sale/wizard/pos_confirm.xml | 3 +- 9 files changed, 71 insertions(+), 88 deletions(-) delete mode 100644 addons/point_of_sale/product.py delete mode 100644 addons/point_of_sale/product_view.xml diff --git a/addons/point_of_sale/__init__.py b/addons/point_of_sale/__init__.py index ec5403212f4..bf0b86a0e9d 100644 --- a/addons/point_of_sale/__init__.py +++ b/addons/point_of_sale/__init__.py @@ -21,7 +21,6 @@ import point_of_sale import account_bank_statement -import product import res_users import wizard import report diff --git a/addons/point_of_sale/__openerp__.py b/addons/point_of_sale/__openerp__.py index 088e3ceeb3a..a1cc41b7411 100644 --- a/addons/point_of_sale/__openerp__.py +++ b/addons/point_of_sale/__openerp__.py @@ -66,7 +66,6 @@ Main features : 'point_of_sale_workflow.xml', 'account_statement_view.xml', 'account_statement_report.xml', - 'product_view.xml', 'res_users_view.xml', ], 'demo_xml': [ diff --git a/addons/point_of_sale/account_statement_view.xml b/addons/point_of_sale/account_statement_view.xml index 9bc039a1852..e309879100e 100644 --- a/addons/point_of_sale/account_statement_view.xml +++ b/addons/point_of_sale/account_statement_view.xml @@ -129,6 +129,7 @@ + diff --git a/addons/point_of_sale/point_of_sale.py b/addons/point_of_sale/point_of_sale.py index f40ff5a1bd2..db293bcc814 100644 --- a/addons/point_of_sale/point_of_sale.py +++ b/addons/point_of_sale/point_of_sale.py @@ -47,8 +47,8 @@ class pos_config(osv.osv): 'name' : fields.char('Name', size=32, select=1, required=True, - readonly=True, - states={'draft' : [('readonly', False)]} +# readonly=True, +# states={'draft' : [('readonly', False)]} ), 'journal_ids' : fields.many2many('account.journal', 'pos_config_journal_rel', @@ -56,21 +56,21 @@ class pos_config(osv.osv): 'journal_id', 'Payment Methods', domain="[('journal_user', '=', True )]", - readonly=True, - states={'draft' : [('readonly', False)]} +# readonly=True, +# states={'draft' : [('readonly', False)]} ), 'shop_id' : fields.many2one('sale.shop', 'Shop', required=True, select=1, - readonly=True, - states={'draft' : [('readonly', False)]} +# readonly=True, +# states={'draft' : [('readonly', False)]} ), 'journal_id' : fields.many2one('account.journal', 'Journal', required=True, select=1, domain=[('type', '=', 'sale')], - readonly=True, - states={'draft' : [('readonly', False)]} +# readonly=True, +# states={'draft' : [('readonly', False)]} ), 'iface_self_checkout' : fields.boolean('Self Checkout Mode'), 'iface_websql' : fields.boolean('WebSQL (to store data)'), @@ -88,8 +88,8 @@ class pos_config(osv.osv): 'sequence_id' : fields.many2one('ir.sequence', 'Sequence', readonly=True), 'user_id' : fields.many2one('res.users', 'User', - readonly=True, - states={'draft' : [('readonly', False)]} +# readonly=True, +# states={'draft' : [('readonly', False)]} ), } @@ -180,34 +180,34 @@ class pos_session(osv.osv): #"status [BUTTON TEXT]" -> utiliser # "opening control (open) -> opened (cashbox control) -> closing control (close) -> closed & posted" - POS_SESSION_STATE = [('new', 'New'),('opened', 'Opened'),('closed', 'Closed'),('posted', 'Posted')] - #POS_SESSION_STATE = [ - # ('new', 'Opening Control'), - # ('opened', 'Opened'), - # ('closed', 'Closing Control'), - # ('posted', 'Closed & Posted'), - #] + #POS_SESSION_STATE = [('new', 'New'),('opened', 'Opened'),('closed', 'Closed'),('posted', 'Posted')] + POS_SESSION_STATE = [ + ('opening_control', 'Opening Control'), # Signal open + ('opened', 'Opened'), # Signal closing + ('closing_control', 'Closing Control'), # Signal close + ('closed', 'Closed'), + ] _columns = { 'config_id' : fields.many2one('pos.config', 'PoS', required=True, select=1, domain="[('state', '=', 'active')]", - readonly=True, - states={'draft' : [('readonly', False)]} +# readonly=True, +# states={'draft' : [('readonly', False)]} ), 'name' : fields.char('Session Sequence', size=32, required=True, select=1, - readonly=True, - states={'draft' : [('readonly', False)]} +# readonly=True, +# states={'draft' : [('readonly', False)]} ), 'user_id' : fields.many2one('res.users', 'User', required=True, select=1, - readonly=True, - states={'draft' : [('readonly', False)]} +# readonly=True, +# states={'draft' : [('readonly', False)]} ), 'start_at' : fields.datetime('Opening Date'), 'stop_at' : fields.datetime('Closing Date'), @@ -234,7 +234,7 @@ class pos_session(osv.osv): _defaults = { 'name' : '/', 'user_id' : lambda obj, cr, uid, context: uid, - 'state' : 'new', + 'state' : 'opening_control', } _sql_constraints = [ @@ -309,15 +309,15 @@ class pos_session(osv.osv): return True - def wkf_action_close(self, cr, uid, ids, context=None): + def wkf_action_closing_control(self, cr, uid, ids, context=None): # Close CashBox for record in self.browse(cr, uid, ids, context=context): record.cash_register_id.button_confirm_cash(context=context) - return self.write(cr, uid, ids, {'state' : 'closed', 'stop_at' : time.strftime('%Y-%m-%d %H:%M:%S')}, context=context) + return self.write(cr, uid, ids, {'state' : 'closing_control', 'stop_at' : time.strftime('%Y-%m-%d %H:%M:%S')}, context=context) - def wkf_action_post(self, cr, uid, ids, context=None): + def wkf_action_close(self, cr, uid, ids, context=None): self._confirm_orders(cr, uid, ids, context=context) - return self.write(cr, uid, ids, {'state' : 'posted'}, context=context) + return self.write(cr, uid, ids, {'state' : 'closed'}, context=context) def _confirm_orders(self, cr, uid, ids, context=None): wf_service = netsvc.LocalService("workflow") @@ -370,7 +370,7 @@ class pos_session(osv.osv): session_id = self.create(cr, uid, values, context=context) wkf_service = netsvc.LocalService('workflow') - wkf_service.trg_validate(uid, 'pos.session', session_id, 'open', cr) + wkf_service.trg_validate(uid, 'pos.session', session_id, 'opening_control', cr) return session_id @@ -1112,6 +1112,11 @@ class pos_category(osv.osv): 'parent_id': fields.many2one('pos.category','Parent Category', select=True), 'child_id': fields.one2many('pos.category', 'parent_id', string='Children Categories'), 'sequence': fields.integer('Sequence', help="Gives the sequence order when displaying a list of product categories."), + 'to_weight' : fields.boolean('To Weight'), + } + + _defaults = { + 'to_weight' : False, } pos_category() diff --git a/addons/point_of_sale/point_of_sale_view.xml b/addons/point_of_sale/point_of_sale_view.xml index 062efd14fdc..f45cc786846 100644 --- a/addons/point_of_sale/point_of_sale_view.xml +++ b/addons/point_of_sale/point_of_sale_view.xml @@ -641,6 +641,7 @@ + @@ -671,6 +672,7 @@ + POS Sales Lines @@ -894,10 +897,11 @@ - - - - - -
    - - - - -
    - - - - -
    - - - - -
    - + +
    +
    +
    - - -
    + +
    + + + + + + + + + + + +
    ProductPriceDisc (%)QtyTotal
    +
    +
    @@ -102,6 +87,36 @@
    There are pending operations that could not be saved into the database, are you sure you want to exit?
    + +
    +
    +
    + +
    + + + + +
    + + + + +
    + + + + +
    + + + + +
    +
    +
    @@ -273,7 +288,7 @@
    + + + -
    @@ -74,17 +74,14 @@
    - + pending orders - -
    The product could not be recognized. Please contact an employee.
    -
    - +
    There are pending operations that could not be saved into the database, are you sure you want to exit?
    @@ -177,9 +174,9 @@ Total: - + - + @@ -189,17 +186,17 @@ Paid: - + - + Change: - + - + @@ -338,7 +335,7 @@ - + @@ -363,14 +360,16 @@ - - - - - - - - + + + + + + + + + + @@ -380,10 +379,12 @@
    - - + +
  • + +
  • - +
    @@ -402,17 +403,17 @@ - +
    Tax: - +
    Total: - +

    @@ -422,14 +423,14 @@ - +
    Change: - +
    @@ -461,14 +462,14 @@
    - +
  • - +
  • @@ -478,7 +479,7 @@ - +
    • `~
    • @@ -539,7 +540,7 @@

      close

    - +
    • q1
    • From 3a52dbb43ac0e8fd58ec8fa09449c592131692eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20van=20der=20Essen?= Date: Mon, 14 May 2012 16:31:21 +0200 Subject: [PATCH 121/201] [IMP] point_of_sale: Added a Session Error popup bzr revid: fva@openerp.com-20120514143121-m5tmqhwanf8tube9 --- .../static/src/js/pos_screens.js | 8 + .../static/src/js/pos_widgets.js | 25 +++- addons/point_of_sale/static/src/xml/pos.xml | 139 +++++++++++------- 3 files changed, 110 insertions(+), 62 deletions(-) diff --git a/addons/point_of_sale/static/src/js/pos_screens.js b/addons/point_of_sale/static/src/js/pos_screens.js index a9a935b4750..3b321602145 100644 --- a/addons/point_of_sale/static/src/js/pos_screens.js +++ b/addons/point_of_sale/static/src/js/pos_screens.js @@ -216,6 +216,14 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa }, }); + module.ErrorProductNotRecognizedPopupWidget = module.ErrorPopupWidget.extend({ + template:'ErrorProductNotRecognizedPopupWidget', + }); + + module.ErrorNoSessionPopupWidget = module.ErrorPopupWidget.extend({ + template:'ErrorNoSessionPopupWidget', + }); + module.ScaleInviteScreenWidget = module.ScreenWidget.extend({ template:'ScaleInviteScreenWidget', show: function(){ diff --git a/addons/point_of_sale/static/src/js/pos_widgets.js b/addons/point_of_sale/static/src/js/pos_widgets.js index 590bd646b59..5a4b413f9cc 100644 --- a/addons/point_of_sale/static/src/js/pos_widgets.js +++ b/addons/point_of_sale/static/src/js/pos_widgets.js @@ -793,15 +793,17 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa this.build_widgets(); instance.webclient.set_content_full_screen(true); - if (self.pos.get('account_journals').length === 0) { + if (!self.pos.get('account_journals') ||self.pos.get('account_journals').length === 0) { // TODO: Create a popup to inform there is no PoSSession for this user - self.pos.screen_selector.show_popup('error'); + self.screen_selector.show_popup('error-session'); } }, this)); }, build_widgets: function() { + // -------- Screens --------- + this.search_product_screen = new module.SearchProductScreenWidget(this,{ pos: this.pos, pos_widget: this, @@ -850,6 +852,8 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa }); this.scale_product_screen.appendTo($('#rightpane')); + // -------- Popups --------- + this.help_popup = new module.HelpPopupWidget(this, { pos: this.pos, pos_widget: this, @@ -868,6 +872,18 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa }); this.error_popup.appendTo($('.point-of-sale')); + this.error_product_popup = new module.ErrorProductNotRecognizedPopupWidget(this, { + pos: this.pos, + pos_widget: this, + }); + this.error_product_popup.appendTo($('.point-of-sale')); + + this.error_session_popup = new module.ErrorNoSessionPopupWidget(this, { + pos: this.pos, + pos_widget: this, + }); + this.error_session_popup.appendTo($('.point-of-sale')); + this.action_bar = new module.ActionBarWidget(this); this.action_bar.appendTo($(".point-of-sale #content")); @@ -886,9 +902,6 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa }); this.order_widget.replace($('#placeholder-OrderWidget')); - //this.orderView.$element = $('#current-order-content'); - //this.orderView.start(); - this.onscreen_keyboard = new module.OnscreenKeyboardWidget(this, { 'keyboard_model': 'simple' }); @@ -909,6 +922,8 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa popup_set:{ 'help': this.help_popup, 'error': this.error_popup, + 'error-product': this.error_product_popup, + 'error-session': this.error_session_popup, 'receipt': this.receipt_popup, }, default_client_screen: 'welcome', diff --git a/addons/point_of_sale/static/src/xml/pos.xml b/addons/point_of_sale/static/src/xml/pos.xml index 18b939e4a50..7aba6a12036 100644 --- a/addons/point_of_sale/static/src/xml/pos.xml +++ b/addons/point_of_sale/static/src/xml/pos.xml @@ -2,62 +2,64 @@ + -
      -
      -
      - +
      +
      +
      + +
      +
      + + +
      +
      +
      + +
        +
        +
        -
        - - -
        -
        -
        - -
          +
          + +
          +
          +
          + +
            +
          • + Subtotal: + + 0 + +
          • + +
          • + Tax: + + 0 + +
          • + +
          • + Total: + + 0 + +
          • +
          + +
          +
          + +
          +
          +
          -
          - -
          -
          -
          - -
            -
          • - Subtotal: - - 0 - -
          • - -
          • - Tax: - - 0 - -
          • - -
          • - Total: - - 0 - -
          • -
          - -
          -
          - -
          -
          -
          -
          -
          -
          +
          @@ -74,6 +76,7 @@
          + @@ -81,13 +84,16 @@ +
          There are pending operations that could not be saved into the database, are you sure you want to exit?
          +
          +
          @@ -115,8 +121,6 @@
          - -
          +
          +
          @@ -162,8 +168,6 @@
          - -

          Payment

          @@ -213,8 +217,6 @@
          - -

          Receipt

          @@ -229,7 +231,6 @@
          -
          @@ -282,6 +283,7 @@
          + From f5679b7b8d49970633ade676524cf3ec916514f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20van=20der=20Essen?= Date: Thu, 24 May 2012 16:14:08 +0200 Subject: [PATCH 138/201] [IMP] point_of_sale: put the receipt screen buttons in the action bar bzr revid: fva@openerp.com-20120524141408-10ru52eqcjbew2rw --- .../static/src/img/icons/png48/printer.png | Bin 0 -> 1681 bytes .../static/src/img/icons/printer.svg | 502 ++++++++++++++++++ .../static/src/js/pos_screens.js | 14 +- addons/point_of_sale/static/src/xml/pos.xml | 5 - 4 files changed, 514 insertions(+), 7 deletions(-) create mode 100644 addons/point_of_sale/static/src/img/icons/png48/printer.png create mode 100644 addons/point_of_sale/static/src/img/icons/printer.svg diff --git a/addons/point_of_sale/static/src/img/icons/png48/printer.png b/addons/point_of_sale/static/src/img/icons/png48/printer.png new file mode 100644 index 0000000000000000000000000000000000000000..9747f37ddcca05adcc3024ae4ff827f21e1aa9ac GIT binary patch literal 1681 zcmV;C25$L@P)o%Ze?=j`}Z#Z001F$MObuGZ*_8GWdLY&bZ|N^FKTIRZDC_BZFO^LV`yP) zY%XJZX=dYI0000b7gZcaCKsAX=7w>ZDDC{FImI>$^ZZbQ%OWYRA_Hw2^g&e#J1M<(3Xa_&_f|u%^WN9P{K@Xls55I^4ebw}&b;@{d%xfN z-uJyXvy6!FGDF#G0z1Y30`?k-sD@XrT=|-azL!!q?Dm&N;82T?CZN<>rZriOT?9tNAu>O$q=Qh7pHSxPZaI!M6>=_{p-Y_r0{) z+1UxxG_kg}2F6(IV|6X*%KJly4*A!fnDgh)6X3*zs;YkoAx_=6aU*a5W3`#!^y$;> z9UUEyMn^~64;(n)*I&JQ6-SRA^#|DLtZ!DIOeQfsJ&n=PQ2@^AcX9D=x_0gQqm`AF z*Y4c8Q?9fRcp#ZfUO#c-M6!~+hfF3DNPRmY8`kC}iF`f}+qS_NLwEP9?AWnmos*N3 z-vRh`Qvo9*BOmwn_I}ad->*t3s|HA=QYaJ(n-YkF?SeX$O8GlJ02GTw96x@Xr)Ous z8X6k<_2$i+Gob=>UH|6n*|VW}?97=n{xuQ##k;Spuf2{diiluYmM?;cAcVk|=gt|I zE?xQoz$c*sEX(?+rKJUmqC|f4Y9ur*0U^W(m337DY};}TU#4A*I?UrAg%%n-4n2~vI5gI@#M)<+`e_YYUgbvGTE%ZDnuL) zB$KW9YkC@|PM*a0`1mf<49A1HIjpX(Lf4&TALfC803jR!eSLk%WtVVqd>o3RhICKc zUG4}(1j8`!*1!Omn?xcB@Z6-HL(K%111QsCLMG9Jt= zmLERMPTae9Zx#S^i@y)p00aP$PN#p?b>91rng5Vz-~0q+m&>I;XJ%%8Lj_l$;-#K3 z)(oK4Erwe-oF%L60cpS?>-{eQ*Z{EZmI4uxzY`PDhWlD40h9q0;dn>^5Ox@91O%K3 z6fUQd%WIw7nGbwcVT`E&bU3rFVs8aVw+H|hfM-NxRT=~Y)PgalVDEm8B_i7LYwxxK b_B;LsbzY-L<5yxk00000NkvXXu0mjf{wM7; literal 0 HcmV?d00001 diff --git a/addons/point_of_sale/static/src/img/icons/printer.svg b/addons/point_of_sale/static/src/img/icons/printer.svg new file mode 100644 index 00000000000..4c702dc48e2 --- /dev/null +++ b/addons/point_of_sale/static/src/img/icons/printer.svg @@ -0,0 +1,502 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + Printer + + + Jakub Steiner + + + + http://jimmac.musichall.cz + + + printer + local + laser + bubblejet + inkjet + print + output + cups + lpd + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/addons/point_of_sale/static/src/js/pos_screens.js b/addons/point_of_sale/static/src/js/pos_screens.js index 875524dfaa1..4281125dd27 100644 --- a/addons/point_of_sale/static/src/js/pos_screens.js +++ b/addons/point_of_sale/static/src/js/pos_screens.js @@ -642,8 +642,6 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa start: function() { this.pos.bind('change:selectedOrder', this.change_selected_order, this); this.change_selected_order(); - $('button#pos-finish-order', this.$element).click(_.bind(this.finishOrder, this)); - $('button#print-the-ticket', this.$element).click(_.bind(this.print, this)); }, show: function(){ this._super(); @@ -657,6 +655,18 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa this.pos_widget.action_bar.set_logout_visible(true, function(){ self.pos_widget.screen_selector.set_user_mode('client'); }); + + this.pos_widget.action_bar.add_new_button({ + label: 'Print', + icon: '/point_of_sale/static/src/img/icons/png48/printer.png', + click: function(){ self.print(); }, + }); + + this.pos_widget.action_bar.add_new_button({ + label: 'Next Order', + icon: '/point_of_sale/static/src/img/icons/png48/go-next.png', + click: function() { self.finishOrder(); }, + }); }, print: function() { window.print(); diff --git a/addons/point_of_sale/static/src/xml/pos.xml b/addons/point_of_sale/static/src/xml/pos.xml index 930dbd3c18f..8ac4f0f49c8 100644 --- a/addons/point_of_sale/static/src/xml/pos.xml +++ b/addons/point_of_sale/static/src/xml/pos.xml @@ -218,11 +218,6 @@
          -
          - - -
          From d82f9f6c9deaed9fcdb47247ce82e2a54b2510b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20van=20der=20Essen?= Date: Thu, 24 May 2012 17:33:15 +0200 Subject: [PATCH 139/201] [FIX] point_of_sale: only show weightable products in scale screens bzr revid: fva@openerp.com-20120524153315-wa67qcyldrui8nrf --- .../point_of_sale/static/src/js/pos_models.js | 22 ++++++++++++++-- .../static/src/js/pos_screens.js | 1 + .../static/src/js/pos_widgets.js | 25 +++++++++++-------- 3 files changed, 36 insertions(+), 12 deletions(-) diff --git a/addons/point_of_sale/static/src/js/pos_models.js b/addons/point_of_sale/static/src/js/pos_models.js index 9527c69f651..6b892c10863 100644 --- a/addons/point_of_sale/static/src/js/pos_models.js +++ b/addons/point_of_sale/static/src/js/pos_models.js @@ -94,7 +94,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal // We fetch the backend data on the server asynchronously - var cat_def = fetch('pos.category', ['name', 'parent_id', 'child_id']) + var cat_def = fetch('pos.category', ['id','name', 'parent_id', 'child_id', 'to_weight']) .pipe(function(result){ return self.set({'categories': result}); }); @@ -107,6 +107,24 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal self.set({'product_list': result}); }); + // associate the products with their categories + var prod_process_def = $.when(cat_def, prod_def) + .pipe(function(){ + var product_list = self.get('product_list'); + var categories = self.get('categories'); + var cat_by_id = {}; + for(var i = 0; i < categories.length; i++){ + cat_by_id[categories[i].id] = categories[i]; + } + //set the parent in the category + for(var i = 0; i < categories.length; i++){ + categories[i].parent_category = cat_by_id[categories[i].parent_id[0]]; + } + for(var i = 0; i < product_list.length; i++){ + product_list[i].pos_category = cat_by_id[product_list[i].pos_categ_id[0]]; + } + }); + var tax_def = fetch('account.tax', ['amount','price_include','type']) .then(function(result){ @@ -180,7 +198,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal }); // when all the data has loaded, we compute some stuff, and declare the Pos ready to be used. - $.when(cat_def, prod_def, session_def, tax_def, this.get_app_data(), this.flush()) + $.when(cat_def, prod_def, session_def, tax_def, prod_process_def, this.get_app_data(), this.flush()) .then(function(){ self.build_tree(); self.set({'cashRegisters' : new module.CashRegisterCollection(self.get('bank_statements'))}); diff --git a/addons/point_of_sale/static/src/js/pos_screens.js b/addons/point_of_sale/static/src/js/pos_screens.js index 4281125dd27..9a4afe58289 100644 --- a/addons/point_of_sale/static/src/js/pos_screens.js +++ b/addons/point_of_sale/static/src/js/pos_screens.js @@ -309,6 +309,7 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa this.product_list_widget = new module.ProductListWidget(null,{ pos:this.pos, pos_widget:this.pos_widget, + only_weightable: true, weight: this.pos.proxy.weighting_read_kg(), }); this.product_list_widget.replace($('.placeholder-ProductListWidget')); diff --git a/addons/point_of_sale/static/src/js/pos_widgets.js b/addons/point_of_sale/static/src/js/pos_widgets.js index cd910fea45c..84690aaa7bb 100644 --- a/addons/point_of_sale/static/src/js/pos_widgets.js +++ b/addons/point_of_sale/static/src/js/pos_widgets.js @@ -496,6 +496,7 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa this.pos.get('products').bind('reset', this.renderElement, this); this.product_list = []; this.weight = options.weight; + this.only_weightable = options.only_weightable || false; this.next_screen = options.next_screen || false; }, set_weight: function(weight){ @@ -512,17 +513,21 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa var self = this; this._super(); this.product_list = []; - this.pos.get('products').chain().map(function(product) { - var product = new module.ProductWidget(this, { - model: product, - pos: self.pos, - weight: self.weight, - pos_widget: self.pos_widget, //FIXME ARGH + this.pos.get('products') + .chain() + .filter(function(product){ + // if only weightable, only keeps those with a to_weight category, keep all otherwise + return !self.only_weightable || (product.get('pos_category') && product.get('pos_category').to_weight); }) - self.product_list.push(product); - return product; - }).invoke('appendTo', this.$element); - return this; + .map(function(product) { + var product = new module.ProductWidget(this, { + model: product, + weight: self.weight, + }) + self.product_list.push(product); + return product; + }) + .invoke('appendTo', this.$element); }, }); From 00b7fefcc4312dbb29425dd10d931f6d77abe211 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20van=20der=20Essen?= Date: Fri, 25 May 2012 14:15:39 +0200 Subject: [PATCH 140/201] [IMP] point_of_sale: adding a session opening popup if no session bzr revid: fva@openerp.com-20120525121539-qaywet30fcst1lvv --- .../point_of_sale/static/src/js/pos_models.js | 9 ++++ .../static/src/js/pos_widgets.js | 42 ++++++++++++------- 2 files changed, 35 insertions(+), 16 deletions(-) diff --git a/addons/point_of_sale/static/src/js/pos_models.js b/addons/point_of_sale/static/src/js/pos_models.js index 6b892c10863..59cbfd81956 100644 --- a/addons/point_of_sale/static/src/js/pos_models.js +++ b/addons/point_of_sale/static/src/js/pos_models.js @@ -205,6 +205,10 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal console.log('cashRegisters:',self.get('cashRegisters')); self.ready.resolve(); self.log_loaded_data(); + },function(){ + //we failed to load some backend data, or the backend was badly configured. + //the error messages will be displayed in PosWidget + self.ready.reject(); }); }, @@ -318,6 +322,11 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal }); }); }, + /* + build_category_tree : function() { + var categories = this.get('categories'); + for(var i = 0; i < + }*/ // I guess this builds a tree of categories ? TODO ask Niv for more info. build_tree: function() { var c, id, _i, _len, _ref, _ref2; diff --git a/addons/point_of_sale/static/src/js/pos_widgets.js b/addons/point_of_sale/static/src/js/pos_widgets.js index 84690aaa7bb..a73d18b6067 100644 --- a/addons/point_of_sale/static/src/js/pos_widgets.js +++ b/addons/point_of_sale/static/src/js/pos_widgets.js @@ -760,35 +760,35 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa start: function() { var self = this; - return self.pos.ready.then(_.bind(function() { - this.build_currency_template(); - this.renderElement(); - this.synch_notification = new module.SynchNotificationWidget(this); - this.synch_notification.replace($('.placeholder-SynchNotificationWidget', this.$element)); - this.synch_notification.on_synch.add(_.bind(self.pos.flush, self.pos)); + return self.pos.ready.then(function() { + self.build_currency_template(); + self.renderElement(); + self.synch_notification = new module.SynchNotificationWidget(this); + self.synch_notification.replace($('.placeholder-SynchNotificationWidget', self.$element)); + self.synch_notification.on_synch.add(_.bind(self.pos.flush, self.pos)); - self.pos.bind('change:nbr_pending_operations', this.changed_pending_operations, this); - this.changed_pending_operations(); + self.pos.bind('change:nbr_pending_operations', self.changed_pending_operations, self); + self.changed_pending_operations(); - this.$element.find("#loggedas button").click(function() { + self.$element.find("#loggedas button").click(function() { self.try_close(); }); - $('button#neworder-button', this.$element).click(_.bind(this.create_new_order, this)); + self.$('button#neworder-button').click(_.bind(self.create_new_order, self)); //when a new order is created, add an order button widget - this.pos.get('orders').bind('add', function(new_order){ + self.pos.get('orders').bind('add', function(new_order){ var new_order_button = new module.OrderButtonWidget(null, { order: new_order, - pos: this.pos + pos: self.pos }); new_order_button.appendTo($('#orders')); new_order_button.selectOrder(); - }, this); + }, self); - this.pos.get('orders').add(new module.Order({ pos: this.pos })); + self.pos.get('orders').add(new module.Order({ pos: self.pos })); - this.build_widgets(); + self.build_widgets(); instance.webclient.set_content_full_screen(true); @@ -806,7 +806,17 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa $('.loader').animate({opacity:0},3000,'swing',function(){$('.loader').hide();}); $('.loader img').hide(); - }, this)); + },function(){ // error when loading models data from the backend + $('.loader img').hide(); + return new instance.web.Model("ir.model.data").get_func("search_read")([['name', '=', 'action_pos_session_opening']], ['res_id']) + .pipe( _.bind(function(res){ + return instance.connection.rpc('/web/action/load', {'action_id': res[0]['res_id']}) + .pipe(_.bind(function(result){ + var action = result.result; + this.do_action(action); + }, this)); + }, self)); + }); }, build_widgets: function() { From 9160712352572da2c91c49f8c65b19f026637b5f Mon Sep 17 00:00:00 2001 From: Stephane Wirtel Date: Thu, 24 May 2012 10:04:43 +0200 Subject: [PATCH 141/201] [FIX] account: add a last closing balance on the cash register object bzr revid: stw@openerp.com-20120524080443-jv4rjvkmjcgs6db5 --- addons/account/account_cash_statement.py | 42 ++++++++++++++++++++++++ addons/account/account_view.xml | 2 +- 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/addons/account/account_cash_statement.py b/addons/account/account_cash_statement.py index cf88fff2041..3c0cd4a3f6c 100644 --- a/addons/account/account_cash_statement.py +++ b/addons/account/account_cash_statement.py @@ -127,6 +127,47 @@ class account_cash_statement(osv.osv): return result + def _compute_last_closing_balance(self, cr, uid, ids, fieldnames, args, context=None): + result = dict.fromkeys(ids, 0.0) + + for obj in self.browse(cr, uid, ids, context=context): + if obj.state == 'draft': + self.search(cr, uid, + [('journal_id', '=', journal_id),('state', '=', 'closed')], + order='create_date desc', + limit=1, + context=context + ) + + if not statement_ids: + return result + + st = self.browse(cr, uid, statement_ids[0], context=context) + result[obj.id] = st.balance_end_real + + return result + + def onchange_journal_id(self, cr, uid, ids, journal_id, context=None): + result = super(account_cash_statement, self).on_change_journal_id(cr, uid, ids, journal_id) + + if not journal_id: + return result + + statement_ids = self.search(cr, uid, + [('journal_id', '=', journal_id),('state', '=', 'closed')], + order='create_date desc', + limit=1, + context=context + ) + + if not statement_ids: + return result + + st = self.browse(cr, uid, statement_ids[0], context=context) + result.setdefault('value', {}).update({'last_closing_balance' : st.balance_end_real}) + + return result + _columns = { 'total_entry_encoding': fields.function(_get_sum_entry_encoding, string="Total Cash Transactions", store = { @@ -139,6 +180,7 @@ class account_cash_statement(osv.osv): 'closing_details_ids' : fields.one2many('account.cashbox.line', 'bank_statement_id', string='Closing Cashbox Lines'), 'user_id': fields.many2one('res.users', 'Responsible', required=False), 'difference' : fields.function(_compute_difference, method=True, string="Difference", type="float"), + 'last_closing_balance' : fields.function(_compute_last_closing_balance, method=True, string='Last Closing Balance', type='float', store=True), } _defaults = { 'state': 'draft', diff --git a/addons/account/account_view.xml b/addons/account/account_view.xml index 5c5fd28b0f9..64eb718bd47 100644 --- a/addons/account/account_view.xml +++ b/addons/account/account_view.xml @@ -2732,12 +2732,12 @@ action = pool.get('res.config').next(cr, uid, [], context) + - From c08b3fe9e4f65573e762529fa43cf07dd20477e5 Mon Sep 17 00:00:00 2001 From: Stephane Wirtel Date: Fri, 25 May 2012 14:23:28 +0200 Subject: [PATCH 142/201] [FIX] account: Fix the last_closing_balance field bzr revid: stw@openerp.com-20120525122328-c17167sm5yun6q0o --- addons/account/account_cash_statement.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/addons/account/account_cash_statement.py b/addons/account/account_cash_statement.py index 3c0cd4a3f6c..d0474887594 100644 --- a/addons/account/account_cash_statement.py +++ b/addons/account/account_cash_statement.py @@ -133,7 +133,7 @@ class account_cash_statement(osv.osv): for obj in self.browse(cr, uid, ids, context=context): if obj.state == 'draft': self.search(cr, uid, - [('journal_id', '=', journal_id),('state', '=', 'closed')], + [('journal_id', '=', journal_id),('state', '=', 'confirm')], order='create_date desc', limit=1, context=context @@ -148,13 +148,13 @@ class account_cash_statement(osv.osv): return result def onchange_journal_id(self, cr, uid, ids, journal_id, context=None): - result = super(account_cash_statement, self).on_change_journal_id(cr, uid, ids, journal_id) + result = super(account_cash_statement, self).onchange_journal_id(cr, uid, ids, journal_id) if not journal_id: return result statement_ids = self.search(cr, uid, - [('journal_id', '=', journal_id),('state', '=', 'closed')], + [('journal_id', '=', journal_id),('state', '=', 'confirm')], order='create_date desc', limit=1, context=context @@ -180,7 +180,7 @@ class account_cash_statement(osv.osv): 'closing_details_ids' : fields.one2many('account.cashbox.line', 'bank_statement_id', string='Closing Cashbox Lines'), 'user_id': fields.many2one('res.users', 'Responsible', required=False), 'difference' : fields.function(_compute_difference, method=True, string="Difference", type="float"), - 'last_closing_balance' : fields.function(_compute_last_closing_balance, method=True, string='Last Closing Balance', type='float', store=True), + 'last_closing_balance' : fields.function(_compute_last_closing_balance, method=True, string='Last Closing Balance', type='float'), } _defaults = { 'state': 'draft', From cf46c8653c9e7f7ab74335a184036f728d2ba190 Mon Sep 17 00:00:00 2001 From: Stephane Wirtel Date: Fri, 25 May 2012 14:24:15 +0200 Subject: [PATCH 143/201] [FIX] point_of_sale: Fix some bugs bzr revid: stw@openerp.com-20120525122415-30r3e2phsfdqgz8l --- addons/point_of_sale/point_of_sale.py | 73 ++++++++++----------- addons/point_of_sale/point_of_sale_view.xml | 14 ++-- 2 files changed, 43 insertions(+), 44 deletions(-) diff --git a/addons/point_of_sale/point_of_sale.py b/addons/point_of_sale/point_of_sale.py index fc362f5a820..af932caf884 100644 --- a/addons/point_of_sale/point_of_sale.py +++ b/addons/point_of_sale/point_of_sale.py @@ -376,45 +376,34 @@ class pos_session(osv.osv): return True - def get_current_session(self, cr, uid, context=None): - current_user = self.pool.get('res.users').browse(cr, uid, uid, context=context) - domain = [ - ('state', '=', 'open'), - ('start_at', '>=', time.strftime('%Y-%m-%d 00:00:00')), - ('user_id', '=', uid), - ] - session_ids = self.search(cr, uid, domain, context=context, limit=1, order='start_at desc') - session_id = session_ids[0] if session_ids else False + # def get_current_session(self, cr, uid, context=None): + # # TODO: Remove this code and use a wizard in the front-end + # current_user = self.pool.get('res.users').browse(cr, uid, uid, context=context) + # domain = [ + # ('state', '=', 'open'), + # ('start_at', '>=', time.strftime('%Y-%m-%d 00:00:00')), + # ('user_id', '=', uid), + # ] + # session_ids = self.search(cr, uid, domain, context=context, limit=1, order='start_at desc') + # session_id = session_ids[0] if session_ids else False - if not session_id: - pos_config_proxy = self.pool.get('pos.config') - domain = [ - ('user_id', '=', uid), - ('state', '=', 'active'), - ] - pos_config_ids = pos_config_proxy.search(cr, uid, domain, - limit=1, - order='create_date desc', - context=context) + # if not session_id: + # if not current_user.pos_config: + # raise osv.except_osv(_('Error !'), + # _('There is no active Point of Sale Config for this User %s') % current_user.name) - if not pos_config_ids: - raise osv.except_osv(_('Error !'), - _('There is no active Point of Sale Config for this User %s') % current_user.name) + # values = { + # 'state' : 'opening_control', + # 'start_at' : time.strftime('%Y-%m-%d %H:%M:%S'), + # 'config_id' : current_user.pos_config.id, + # 'journal_id' : current_user.pos_config.journal_id.id, + # 'user_id': current_user.id, + # } - config = pos_config_proxy.browse(cr, uid, pos_config_ids[0], context=context) - - values = { - 'state' : 'new', - 'start_at' : time.strftime('%Y-%m-%d %H:%M:%S'), - 'config_id' : config.id, - 'journal_id' : config.journal_id.id, - 'user_id': current_user.id, - } - - session_id = self.create(cr, uid, values, context=context) - wkf_service = netsvc.LocalService('workflow') - wkf_service.trg_validate(uid, 'pos.session', session_id, 'opening_control', cr) - return session_id + # session_id = self.create(cr, uid, values, context=context) + # wkf_service = netsvc.LocalService('workflow') + # wkf_service.trg_validate(uid, 'pos.session', session_id, 'opening_control', cr) + # return session_id pos_session() @@ -847,13 +836,16 @@ class pos_order(osv.osv): tax_amount = 0 taxes = [t for t in line.product_id.taxes_id] computed = account_tax_obj.compute_all(cr, uid, taxes, line.price_unit * (100.0-line.discount) / 100.0, line.qty) + print "#### TAX: %r" % (taxes,) + print "#### TAX2: %r" % (computed,) computed_taxes = computed['taxes'] for tax in computed_taxes: tax_amount += round(tax['amount'], 2) group_key = (tax['tax_code_id'], tax['base_code_id'], - tax['account_collected_id']) + tax['account_collected_id'], + tax['id']) if group_key in group_tax: group_tax[group_key] += round(tax['amount'], 2) @@ -917,7 +909,7 @@ class pos_order(osv.osv): continue account_move_line_obj.create(cr, uid, { - 'name': _("Tax") + line.name + " (%s)" % (tax.name), + 'name': _('Tax'), 'date': order.date_order[:10], 'ref': order.name, 'product_id':line.product_id.id, @@ -935,10 +927,11 @@ class pos_order(osv.osv): # Create a move for each tax group - (tax_code_pos, base_code_pos, account_pos)= (0, 1, 2) + (tax_code_pos, base_code_pos, account_pos, tax_id)= (0, 1, 2, 3) for key, amount in group_tax.items(): + tax = self.pool.get('account.tax').browse(cr, uid, key[tax_id], context=context) account_move_line_obj.create(cr, uid, { - 'name': 'Tax', + 'name': _('Tax') + ' ' + tax.name, 'date': order.date_order[:10], 'ref': order.name, 'move_id': move_id, diff --git a/addons/point_of_sale/point_of_sale_view.xml b/addons/point_of_sale/point_of_sale_view.xml index 8d1b9ad6a3a..f16673e019e 100644 --- a/addons/point_of_sale/point_of_sale_view.xml +++ b/addons/point_of_sale/point_of_sale_view.xml @@ -871,10 +871,9 @@ - +
        + +
        + + +
        +
        +
          + +
        1. + + + +
        2. +
          +
        +
        +
        +
        From a81a9177f1556d9d512c3103fa65ab96d73ce9c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20van=20der=20Essen?= Date: Tue, 29 May 2012 18:39:44 +0200 Subject: [PATCH 150/201] [IMP] point_of_sale: new category system bzr revid: fva@openerp.com-20120529163944-crk8dblg7t6b2ove --- .../static/src/js/pos_devices.js | 6 +- .../point_of_sale/static/src/js/pos_models.js | 92 +------- .../static/src/js/pos_screens.js | 13 +- .../static/src/js/pos_widgets.js | 203 ++++++++---------- addons/point_of_sale/static/src/xml/pos.xml | 35 --- 5 files changed, 111 insertions(+), 238 deletions(-) diff --git a/addons/point_of_sale/static/src/js/pos_devices.js b/addons/point_of_sale/static/src/js/pos_devices.js index de036e8a9bc..53b72dfb765 100644 --- a/addons/point_of_sale/static/src/js/pos_devices.js +++ b/addons/point_of_sale/static/src/js/pos_devices.js @@ -31,9 +31,9 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal }, message : function(name,params,callback){ - var success_callback = function(result){ console.log('PROXY SUCCESS:'+name+': ',result); } - var error_callback = function(result){ console.log('PROXY ERROR:'+name+': ',result); } - console.log('PROXY: '+name); + var success_callback = function(result){}; //console.log('PROXY SUCCESS:'+name+': ',result); } + var error_callback = function(result){}; // console.log('PROXY ERROR:'+name+': ',result); } + //console.log('PROXY: '+name); this.connection.rpc('/pos/'+name, params || {}, callback || success_callback, error_callback); }, diff --git a/addons/point_of_sale/static/src/js/pos_models.js b/addons/point_of_sale/static/src/js/pos_models.js index 8e8af120e56..8d11a20967a 100644 --- a/addons/point_of_sale/static/src/js/pos_models.js +++ b/addons/point_of_sale/static/src/js/pos_models.js @@ -61,7 +61,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal this.dao = new module.LocalStorageDAO(); // used to store the order's data on the Hard Drive this.ready = $.Deferred(); // used to notify the GUI that the PosModel has loaded all resources this.flush_mutex = new $.Mutex(); // used to make sure the orders are sent to the server once at time - this.build_tree = _.bind(this.build_tree, this); // ??? + //this.build_tree = _.bind(this.build_tree, this); // ??? this.session = session; this.categories = {}; this.root_category = null; @@ -212,7 +212,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal // when all the data has loaded, we compute some stuff, and declare the Pos ready to be used. $.when(cat_def, prod_def, session_def, tax_def, prod_process_def, this.get_app_data(), this.flush()) .then(function(){ - self.build_tree(); + //self.build_tree(); self.build_categories(); self.set({'cashRegisters' : new module.CashRegisterCollection(self.get('bank_statements'))}); console.log('cashRegisters:',self.get('cashRegisters')); @@ -335,6 +335,10 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal }); }); }, + + // this adds several properties to the categories in order to make it easier to diplay them + // fields added include the list of product relevant to each category, list of child categories, + // list of ancestors, etc. build_categories : function(){ var categories = this.get('categories'); var products = this.get('product_list'); @@ -356,6 +360,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal for(var i = 0; i < categories.length; i++){ categories_by_id[categories[i].id] = categories[i]; } + this.categories_by_id = categories_by_id; var root_category = { name : 'Root', @@ -390,9 +395,6 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal cat.product_set = {}; // [product.id] === true if product is in category cat.weightable_product_list = []; cat.weightable_product_set = {}; - cat.regular_product_list = []; //not weightable - cat.regular_product_set = {}; - cat.progeny = []; } this.root_category = root_category; @@ -409,9 +411,6 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal if(product.weightable){ cat.weightable_product_list.push(product); cat.weightable_product_set[product.id] = true; - }else{ - cat.regular_product_list.push(product); - cat.regular_product_set[product.id] = true; } } } @@ -426,15 +425,6 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal } } - // add progeny field to categories, contains all subcategories of a category - function make_progeny(cat){ - for(var i = 0; i < cat.childrens.length; i++){ - make_progeny(cat.childrens[i]); - cat.progeny.push(cat.childrens[i]); - append(cat.progeny,cat.childrens[i].progeny); - } - } - //add the products of the subcategories to the parent categories function make_products(cat){ for(var i = 0; i < cat.childrens.length; i++){ @@ -442,83 +432,15 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal append(cat.product_list, cat.childrens[i].product_list); append(cat.weightable_product_list, cat.childrens[i].weightable_product_list); - append(cat.regular_product_list, cat.childrens[i].regular_product_list); appendSet(cat.product_set, cat.childrens[i].product_set); appendSet(cat.weightable_product_set, cat.childrens[i].weightable_product_set); - appendSet(cat.regular_product_set, cat.childrens[i].regular_product_set); } } make_ancestors(root_category,[]); - make_progeny(root_category); make_products(root_category); }, - - build_tree: function() { - var c, id, _i, _len, _ref, _ref2; - _ref = this.get('categories'); - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - c = _ref[_i]; - this.categories[c.id] = { - id: c.id, - name: c.name, - children: c.child_id, - parent: c.parent_id[0], - ancestors: [c.id], - subtree: [c.id] - }; - } - _ref2 = this.categories; - for (id in _ref2) { - c = _ref2[id]; - this.current_category = c; - this.build_ancestors(c.parent); - this.build_subtree(c); - } - this.categories[0] = { - ancestors: [], - children: (function() { - var _j, _len2, _ref3, _results; - _ref3 = this.get('categories'); - _results = []; - for (_j = 0, _len2 = _ref3.length; _j < _len2; _j++) { - c = _ref3[_j]; - if (!(c.parent_id[0] != null)) { - _results.push(c.id); - } - } - return _results; - }).call(this), - subtree: (function() { - var _j, _len2, _ref3, _results; - _ref3 = this.get('categories'); - _results = []; - for (_j = 0, _len2 = _ref3.length; _j < _len2; _j++) { - c = _ref3[_j]; - _results.push(c.id); - } - return _results; - }).call(this) - }; - }, - build_ancestors: function(parent) { - if (parent != null) { - this.current_category.ancestors.unshift(parent); - return this.build_ancestors(this.categories[parent].parent); - } - }, - build_subtree: function(category) { - var c, _i, _len, _ref, _results; - _ref = category.children; - _results = []; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - c = _ref[_i]; - this.current_category.subtree.push(c); - _results.push(this.build_subtree(this.categories[c])); - } - return _results; - } }); module.CashRegister = Backbone.Model.extend({ diff --git a/addons/point_of_sale/static/src/js/pos_screens.js b/addons/point_of_sale/static/src/js/pos_screens.js index 58b6ddab99e..e4b36f79a8f 100644 --- a/addons/point_of_sale/static/src/js/pos_screens.js +++ b/addons/point_of_sale/static/src/js/pos_screens.js @@ -307,8 +307,7 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa this.product_categories_widget.replace($('.placeholder-ProductCategoriesWidget')); this.product_list_widget = new module.ProductListWidget(this,{ - only_weightable: true, - weight: this.pos.proxy.weighting_read_kg(), + show_scale: true, }); this.product_list_widget.replace($('.placeholder-ProductListWidget')); }, @@ -322,7 +321,7 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa this.pos_widget.action_bar.set_total_visible(true); this.pos_widget.action_bar.set_help_visible(true,function(){self.pos_widget.screen_selector.show_popup('help');}); this.pos_widget.action_bar.set_logout_visible(false); - this.pos_widget.onscreen_keyboard.connect(); + //this.pos_widget.onscreen_keyboard.connect(); this.product_categories_widget.reset_category(); @@ -357,7 +356,9 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa this.pos_widget.set_cashier_controls_visible(true); this.pos_widget.action_bar.set_total_visible(true); this.pos_widget.action_bar.set_help_visible(false); - this.pos_widget.onscreen_keyboard.connect(); + //this.pos_widget.onscreen_keyboard.connect(); + + this.product_categories_widget.reset_category(); this.pos_widget.order_widget.set_numpad_state(this.pos_widget.numpad.state); this.pos_widget.action_bar.add_new_button( @@ -564,7 +565,7 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa module.SearchProductScreenWidget = module.ScreenWidget.extend({ template:'SearchProductScreenWidget', start: function(){ - this.product_categories_widget = new module.ProductCategoriesWidget2(this,{}); + this.product_categories_widget = new module.ProductCategoriesWidget(this,{}); this.product_categories_widget.replace($('.placeholder-ProductCategoriesWidget')); this.product_list_widget = new module.ProductListWidget(this,{}); @@ -583,7 +584,7 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa self.pos_widget.screen_selector.set_user_mode('client'); }); this.product_categories_widget.reset_category(); - this.pos_widget.onscreen_keyboard.connect(); + //this.pos_widget.onscreen_keyboard.connect(); this.pos_widget.order_widget.set_numpad_state(this.pos_widget.numpad.state); this.pos_widget.action_bar.add_new_button( diff --git a/addons/point_of_sale/static/src/js/pos_widgets.js b/addons/point_of_sale/static/src/js/pos_widgets.js index ef6a901ffb1..b7db970891d 100644 --- a/addons/point_of_sale/static/src/js/pos_widgets.js +++ b/addons/point_of_sale/static/src/js/pos_widgets.js @@ -396,143 +396,131 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa }); module.ProductCategoriesWidget = module.PosBaseWidget.extend({ + template: 'ProductCategoriesWidget', init: function(parent, options){ - this._super(parent,options); - this.on_change_category.add_last(_.bind(this.search_and_categories, this)); - this.search_and_categories(); - }, - start: function() { - this.search_and_categories(); - }, - template:'ProductCategoriesWidget', - renderElement: function() { var self = this; - var c; - this.$element.empty(); - this.$element.html(QWeb.render(this.template, { - breadcrumb: (function() { - var _i, _len, _results; - _results = []; - for (_i = 0, _len = self.ancestors.length; _i < _len; _i++) { - c = self.ancestors[_i]; - _results.push(self.pos.categories[c]); - } - return _results; - })(), - categories: (function() { - var _i, _len, _results; - _results = []; - for (_i = 0, _len = self.children.length; _i < _len; _i++) { - c = self.children[_i]; - _results.push(self.pos.categories[c]); - } - return _results; - })() - })); - this.$element.find(".oe-pos-categories-list a").click(_.bind(this.change_category, this)); - }, - reset_category: function(){ - this.on_change_category(0); - }, - change_category: function(a) { - var id = $(a.target).data("category-id"); - this.on_change_category(id); - }, - search_and_categories: function(id){ - var self = this, - c, - product_list, - allProducts, - allPackages; - - id = id || 0; - - c = this.pos.categories[id]; - this.ancestors = c.ancestors; - this.children = c.children; - this.renderElement(); - - allProducts = this.pos.get('product_list'); - - allPackages = this.pos.get('product.packaging'); - - product_list = this.pos.get('product_list').filter( function(p){ - var _ref = p.pos_categ_id[0]; - return _.indexOf(c.subtree, _ref) >= 0; - }); - - this.pos.get('products').reset(product_list); - - this.$element.find('.searchbox input').keyup(function(){ - var results, search_str; - search_str = $(this).val().toLowerCase(); - if(search_str){ - results = product_list.filter( function(p){ - return p.name.toLowerCase().indexOf(search_str) != -1; - }); - self.$element.find('.search-clear').fadeIn(); - }else{ - results = product_list; - self.$element.find('.search-clear').fadeOut(); - } - self.pos.get('products').reset(results); - }); - - this.$element.find('.search-clear').click(function(){ - self.pos.get('products').reset(product_list); - self.$element.find('.searchbox input').val('').focus(); - self.$element.find('.search-clear').fadeOut(); - }); - }, - on_change_category: function(id) {}, - }); - - module.ProductCategoriesWidget2 = module.PosBaseWidget.extend({ - template: 'ProductCategoriesWidget2', - init: function(parent, options){ this._super(parent,options); - this.onlyWeightable = false; + this.onlyWeightable = options.onlyWeightable || false; this.category = this.pos.root_category; this.breadcrumb = []; this.subcategories = []; this.set_category(); }, + + start: function(){ + this.search_and_categories(); + }, + + // changes the category. if undefined, sets to root category set_category : function(category){ if(!category){ this.category = this.pos.root_category; }else{ this.category = category; } + this.breadcrumb = []; for(var i = 1; i < this.category.ancestors.length; i++){ - this.breadcrumb.push(this.category.ancestor[i]); + this.breadcrumb.push(this.category.ancestors[i]); + } + if(this.category !== this.pos.root_category){ + this.breadcrumb.push(this.category); + } + if(this.onlyWeightable){ + this.subcategories = []; + for(var i = 0; i < this.category.childrens.length; i++){ + if(this.category.childrens[i].weightable_product_list.length > 0){ + this.subcategories.push( this.category.childrens[i]); + } + } + }else{ + this.subcategories = this.category.childrens || []; } - this.subcategories = this.category.childrens || []; }, + renderElement: function(){ var self = this; this._super(); - this.$element.empty(); + this.$element.find(".oe-pos-categories-list a").click(function(event){ + var id = $(event.target).data("category-id"); + var category = self.pos.categories_by_id[id]; + self.set_category(category); + self.renderElement(); + self.search_and_categories(category); + }); }, + + // resets the current category to the root category reset_category: function(){ this.set_category(); + this.renderElement(); + this.search_and_categories(); }, - on_change_category: function(id){}, - search_and_categories: function(id){}, - change_category: function(a){ + + // filters the products, and sets up the search callbacks + search_and_categories: function(category){ + var self = this; + + var all_products = this.pos.get('product_list'); + var all_packages = this.pos.get('product.packaging'); + + // find all products belonging to the current category + var products = []; + if(this.onlyWeightable){ + products = all_products.filter( function(product){ + return self.category.weightable_product_set[product.id]; + }); + }else{ + products = all_products.filter( function(product){ + return self.category.product_set[product.id]; + }); + } + + // product lists watch for reset events on 'products' to re-render. + // FIXME that means all productlist widget re-render... even the hidden ones ! + this.pos.get('products').reset(products); + + // find all the products whose name match the query in the searchbox + this.$('.searchbox input').keyup(function(){ + var results, search_str; + search_str = $(this).val().toLowerCase(); + if(search_str){ + results = products.filter( function(p){ + return p.name.toLowerCase().indexOf(search_str) != -1 || + (p.ean13 && p.ean13.indexOf(search_str) != -1); + }); + self.$element.find('.search-clear').fadeIn(); + }else{ + results = products; + self.$element.find('.search-clear').fadeOut(); + } + self.pos.get('products').reset(results); + }); + this.$('.searchbox input').click(function(){ + }); + + //reset the search when clicking on reset + this.$('.search-clear').click(function(){ + self.pos.get('products').reset(products); + self.$('.searchbox input').val('').focus(); + self.$('.search-clear').fadeOut(); + }); }, }); module.ProductListWidget = module.ScreenWidget.extend({ template:'ProductListWidget', init: function(parent, options) { + var self = this; this._super(parent,options); - console.log('ProductListWidget:',this); this.model = options.model; - this.pos.get('products').bind('reset', this.renderElement, this); this.product_list = []; - this.weight = options.weight; - this.only_weightable = options.only_weightable || false; + this.weight = options.weight || 0; + this.show_scale = options.show_scale || false; this.next_screen = options.next_screen || false; + + this.pos.get('products').bind('reset', function(){ + self.renderElement(); + }); }, set_weight: function(weight){ for(var i = 0; i < this.product_list.length; i++){ @@ -550,10 +538,6 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa this.product_list = []; this.pos.get('products') .chain() - .filter(function(product){ - // if only weightable, only keeps those with a to_weight category, keep all otherwise - return !self.only_weightable || (product.get('pos_category') && product.get('pos_category').to_weight); - }) .map(function(product) { var product = new module.ProductWidget(self, { model: product, @@ -604,6 +588,7 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa }, connect : function(){ + var self = this; $(this.input_selector).focus(function(){self.show();}); }, @@ -913,10 +898,10 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa this.order_widget = new module.OrderWidget(this, {}); this.order_widget.replace($('#placeholder-OrderWidget')); - this.onscreen_keyboard = new module.OnscreenKeyboardWidget(this, { + /*this.onscreen_keyboard = new module.OnscreenKeyboardWidget(this, { 'keyboard_model': 'simple' }); - this.onscreen_keyboard.appendTo($(".point-of-sale #content")); + this.onscreen_keyboard.appendTo($(".point-of-sale #content")); */ // -------- Screen Selector --------- diff --git a/addons/point_of_sale/static/src/xml/pos.xml b/addons/point_of_sale/static/src/xml/pos.xml index 02ef0097f7b..4a016d1b4f2 100644 --- a/addons/point_of_sale/static/src/xml/pos.xml +++ b/addons/point_of_sale/static/src/xml/pos.xml @@ -125,41 +125,6 @@
        -
        - - -
        -
        -
          - -
        1. - - - -
        2. -
          -
        -
        -
        - -
        +
        +
        + +
        Close
        +
        +
        -
        Exit
        +
        Client Mode

        From 668b82d0a977024f76419114aa251272da78a555 Mon Sep 17 00:00:00 2001 From: Stephane Wirtel Date: Tue, 5 Jun 2012 13:38:39 +0200 Subject: [PATCH 170/201] [FIX] point_of_sale: remove the call of the report in the pos.payment wizard bzr revid: stw@openerp.com-20120605113839-c7pb1czizw38epau --- addons/point_of_sale/wizard/pos_payment.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/addons/point_of_sale/wizard/pos_payment.py b/addons/point_of_sale/wizard/pos_payment.py index 194428db118..183ccfe32b3 100644 --- a/addons/point_of_sale/wizard/pos_payment.py +++ b/addons/point_of_sale/wizard/pos_payment.py @@ -70,7 +70,8 @@ class pos_make_payment(osv.osv_memory): if order_obj.test_paid(cr, uid, [active_id]): wf_service = netsvc.LocalService("workflow") wf_service.trg_validate(uid, 'pos.order', active_id, 'paid', cr) - return self.print_report(cr, uid, ids, context=context) + return {'type' : 'ir.actions.act_window_close' } + ##self.print_report(cr, uid, ids, context=context) return self.launch_payment(cr, uid, ids, context=context) From 85aec52251a67992c7792313e16cc1433320bba8 Mon Sep 17 00:00:00 2001 From: Stephane Wirtel Date: Tue, 5 Jun 2012 14:02:29 +0200 Subject: [PATCH 171/201] [FIX] account: check if the journal exist on the account.bank.statement bzr revid: stw@openerp.com-20120605120229-u37kbjcdp7nbpp6e --- addons/account/wizard/pos_box.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/addons/account/wizard/pos_box.py b/addons/account/wizard/pos_box.py index 00a3c404a65..12a606833a1 100644 --- a/addons/account/wizard/pos_box.py +++ b/addons/account/wizard/pos_box.py @@ -28,6 +28,10 @@ class CashBox(osv.osv_memory): def _run(self, cr, uid, ids, records, context=None): for box in self.browse(cr, uid, ids, context=context): for record in records: + if not record.journal_id: + raise osv.except_osv(_('Error !'), + _("Please check that the field 'Journal' is set on the Bank Statement")) + if not record.journal_id.internal_account_id: raise osv.except_osv(_('Error !'), _("Please check that the field 'Internal Transfers Account' is set on the payment method '%s'.") % (record.journal_id.name,)) From d251e07d88d8373c8a3c0108f773df172efbaffa Mon Sep 17 00:00:00 2001 From: Stephane Wirtel Date: Tue, 5 Jun 2012 14:23:14 +0200 Subject: [PATCH 172/201] [IMP] point_of_sale: add a guardian for the account.bank.statement bzr revid: stw@openerp.com-20120605122314-byw2uq6hjlas0052 --- addons/point_of_sale/wizard/pos_box.py | 11 ++++- .../point_of_sale/wizard/pos_receipt_view.xml | 42 +++++++++---------- 2 files changed, 29 insertions(+), 24 deletions(-) diff --git a/addons/point_of_sale/wizard/pos_box.py b/addons/point_of_sale/wizard/pos_box.py index b4a87cb0933..a79ab0fa779 100644 --- a/addons/point_of_sale/wizard/pos_box.py +++ b/addons/point_of_sale/wizard/pos_box.py @@ -1,4 +1,6 @@ #!/usr/bin/env python + +from tools.translate import _ from osv import osv, fields from account.wizard.pos_box import CashBox @@ -14,7 +16,14 @@ class PosBox(CashBox): if active_model == 'pos.session': records = self.pool.get(active_model).browse(cr, uid, context.get('active_ids', []) or [], context=context) - return self._run(cr, uid, ids, [record.cash_register_id for record in records], context=context) + + bank_statements = [record.cash_register_id for record in records if record.cash_register_id] + + if not bank_statements: + raise osv.except_osv(_('Error !'), + _("There is no cash register for this PoS Session")) + + return self._run(cr, uid, ids, bank_statements, context=context) else: return super(PosBox, self).run(cr, uid, ids, context=context) diff --git a/addons/point_of_sale/wizard/pos_receipt_view.xml b/addons/point_of_sale/wizard/pos_receipt_view.xml index 0b8acfd8197..5ec8ee9d1ed 100644 --- a/addons/point_of_sale/wizard/pos_receipt_view.xml +++ b/addons/point_of_sale/wizard/pos_receipt_view.xml @@ -1,32 +1,28 @@ - + + + + Receipt + pos.receipt + form + +

        + +
      1. + +
        +
        + &atilde; +
        + +
        +
        + +
        + &auml; +
        +
        +
        @@ -366,6 +396,25 @@ + +
      2. +

        + Pizza +

        + + 3.14$ + +
          +
        • + 5 pizza at $0.51/pizza +
        • +
        • + 25 discount +
        • +
        +
      3. +
        + From 40405d11ebda80c48a2402153ea8d9ef6a4f416d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20van=20der=20Essen?= Date: Fri, 22 Jun 2012 18:53:55 +0200 Subject: [PATCH 179/201] [IMP] point_of_sale: new order widget bzr revid: fva@openerp.com-20120622165355-jmex9f0vupidambm --- addons/point_of_sale/static/src/css/pos.css | 153 ++++++++++-------- .../point_of_sale/static/src/js/pos_models.js | 1 + .../static/src/js/pos_widgets.js | 60 +++---- addons/point_of_sale/static/src/xml/pos.xml | 107 ++++-------- 4 files changed, 149 insertions(+), 172 deletions(-) diff --git a/addons/point_of_sale/static/src/css/pos.css b/addons/point_of_sale/static/src/css/pos.css index 3f21a49c57e..3ce06d12b0b 100644 --- a/addons/point_of_sale/static/src/css/pos.css +++ b/addons/point_of_sale/static/src/css/pos.css @@ -10,6 +10,7 @@ top: 0; width: 100%; height: 100%; + -webkit-user-select: none; } .point-of-sale .loader{ background-color: #222; @@ -141,7 +142,7 @@ /*height: 87%; width: 440px; position: relative;*/ - border-right: solid 1px #afafb6; + border-right: solid 1px #CECBCB; background-color: white; } .point-of-sale #leftpane footer { @@ -149,69 +150,9 @@ bottom: 0; left: 0; width: 100%; - background-color: #e0e0e0; - background-image: url(../img/headerbackground.jpg); + background: #F0EEEE; white-space: nowrap; } -.point-of-sale #current-order { - width: 100%; - position: absolute; - top: 0; - bottom: 271px; - overflow: auto; -} -.point-of-sale #current-order thead { - background-color: #cccccc; - background-image: url(../img/headerbackground.jpg); - border: 0px; - font-size: 12px; - width: 440px; -} -.point-of-sale #current-order thead td { - text-align: center; - padding: 8px 0px; - min-width: 40px; - font-size: 12px; -} -.point-of-sale #current-order td { - padding: 6px 4px; - font-size: 11px; - text-align: right; - min-width: 40px; - white-space: nowrap; -} -.point-of-sale #current-order td:first-child { - width: 320px; - padding: 6px; - text-align: left; - text-overflow: ellipsis; -} -.point-of-sale #current-order td:last-child { - border-right: none; -} -.point-of-sale #current-order tr.selected { - background-color: #e9eaf2; -} -.point-of-sale #current-order tr.selected td { - border-top: 2px solid #d5d6e0; - border-bottom: 1px solid #d5d6e0; - padding-top: 5px; - color: #555555; -} -.point-of-sale #amounts { - background: white; - border-bottom: solid 1px #d2d2d2; - border-top: solid 1px #e9eaec; - font-weight: bold; - text-align: right; - -webkit-margin-before: 0; - -webkit-margin-after: 0; -} -.point-of-sale #amounts li { - display: inline-block; - padding: 8px; - width: 29%; -} .point-of-sale #paypad { padding: 8px 4px 8px 8px; display: inline-block; @@ -275,6 +216,7 @@ left: 440px; right: 0; vertical-align: top; + /*border-left: solid 1px #FFF;*/ } .point-of-sale #rightpane header { padding: 0; @@ -364,10 +306,11 @@ font-size: 12px; } .point-of-sale .product { + position:relative; vertical-align: top; display: inline-block; font-size: 11px; - margin: 10px; + margin: 5px; max-width: 120px; background:#fff; border: 1px solid #fff; @@ -379,7 +322,7 @@ -box-shadow: 0px 1px 8px rgba(0,0,0,0.9); -webkit-filter: blur(3px); } -.point-of-sale .product-img { +.point-of-sale .product .product-img { position: relative; width: 120px; height: 100px; @@ -387,7 +330,7 @@ text-align: center; -webkit-filter: blur(3px); } -.point-of-sale .price-tag { +.point-of-sale .product .price-tag { position: absolute; top: 2px; right: 2px; @@ -399,7 +342,7 @@ -webkit-border-radius: 3px; border-radius: 3px; } -.point-of-sale .price-subtag { +.point-of-sale .product .price-subtag { position: absolute; top: 24px; right: 2px; @@ -411,8 +354,15 @@ -webkit-border-radius: 3px; border-radius: 3px; } -.point-of-sale .product-name { +.point-of-sale .product .product-name { + position: absolute; + -webkit-box-sizing: border-box; + bottom:0; + top:auto; + width:100%; + background: -webkit-linear-gradient(-90deg,rgba(255,255,255,0),rgba(255,255,255,1), rgba(255,255,255,1)); padding: 3px; + padding-top:15px; } .point-of-sale #login-form label, .point-of-sale #login-form input { display: block; @@ -457,11 +407,13 @@ } .point-of-sale .screen .header-row { + border:none; width:100%; height:0px; } .point-of-sale .screen .header-cell{ + border:none; width:100%; height:0px; } @@ -562,6 +514,70 @@ } } +.point-of-sale .order-container{ + position: absolute; + top: 0px; + bottom: 229px; + width:100%; + background: #F0EEEE; + overflow:hidden; +} +.point-of-sale .order{ + background: #FFF; + background: -webkit-linear-gradient(0deg,rgba(245,245,245,1),rgba(255,255,255,1), rgba(245,245,245,1)); + padding-bottom:15px; + padding-top:15px; + margin-left:16px; + margin-right:16px; + margin-top:16px; + -webkit-box-shadow: 0px 5px 16px rgba(0,0,0, 0.3); + +} +.point-of-sale .orderline{ + width:100%; + margin:0px; + padding-top:3px; + padding-bottom:10px; + padding-left:15px; + padding-right:15px; + cursor: pointer; + -webkit-box-sizing: border-box; + -webkit-transition: background 250ms ease-in-out; +} +.point-of-sale .orderline:hover{ + background: rgba(140,143,183,0.05); + -webkit-transition: background 50ms ease-in-out; +} + +.point-of-sale .orderline.selected{ + background: rgba(140,143,183,0.2); + -webkit-transition: background 250ms ease-in-out; + cursor: default; +} +.point-of-sale .orderline .product-name{ + padding:0; + display:inline-block; + font-size:18px; + font-weight: bold; + width:80%; + overflow:hidden; +} +.point-of-sale .orderline .price{ + padding:0; + font-size: 18px; + font-weight: bold; + float:right; +} +.point-of-sale .orderline .info-list{ + color: #888; + margin-left:10px; +} +.point-of-sale .orderline .info-list em{ + color: #777; + font-weight: bold; + font-style:normal; +} + /* ----------------------- ACTION BAR ---------------------- */ .point-of-sale .pos-actionbar{ @@ -771,6 +787,9 @@ text-shadow: rgba(255,255,255,0.5) 0px 0px 10px; -webkit-transition: all 250ms ease-in-out; } +.point-of-sale .scrollbar .button:hover{ + text-shadow: rgba(255,255,255,0.8) 0px 0px 15px; +} .point-of-sale .scrollbar .button.disabled{ background: rgba(0,0,0,0.3); color:rgba(255,255,255,0.5); diff --git a/addons/point_of_sale/static/src/js/pos_models.js b/addons/point_of_sale/static/src/js/pos_models.js index 0c796a8a5e5..b183c22d8ec 100644 --- a/addons/point_of_sale/static/src/js/pos_models.js +++ b/addons/point_of_sale/static/src/js/pos_models.js @@ -493,6 +493,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal discount: 0, weighted: false, product_type: 'unit', + unit: 'Unit', }, initialize: function(attributes) { this.pos = attributes.pos; diff --git a/addons/point_of_sale/static/src/js/pos_widgets.js b/addons/point_of_sale/static/src/js/pos_widgets.js index c4ce370a4c3..714094f1cbc 100644 --- a/addons/point_of_sale/static/src/js/pos_widgets.js +++ b/addons/point_of_sale/static/src/js/pos_widgets.js @@ -92,30 +92,42 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa this.$element.remove(); }, this)); this.order = options.order; - }, - start: function() { - this.$element.click(_.bind(this.clickHandler, this)); - this.refresh(); + + if(options.selected){ + this.select(); + }else{ + this.selected = false; + } }, clickHandler: function() { this.select(); }, renderElement: function() { this._super(); - this.select(); + this.$element.click(_.bind(this.clickHandler, this)); + if(this.selected){ + this.$element.addClass('selected'); + } }, - refresh: function() { + refresh: function(){ this.renderElement(); - var heights = _.map(this.$element.prevAll(), function(el) {return $(el).outerHeight();}); - heights.push($('#current-order thead').outerHeight()); - var position = _.reduce(heights, function(memo, num){ return memo + num; }, 0); - $('#current-order').scrollTop(position); }, select: function() { - $('tr.selected').removeClass('selected'); - this.$element.addClass('selected'); + console.log('SELECT:',this); + if(this.order.selected_widget){ + this.order.selected_widget.deselect(); + } + this.selected = true; + this.order.selected_widget = this; this.order.selected = this.model; this.on_selected(); + this.$element.addClass('selected'); + }, + deselect: function(){ + this.selected = false; + this.order.selected_widget = null; + this.order.selected = null; + this.$element.removeClass('selected'); }, on_selected: function() {}, }); @@ -163,12 +175,12 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa var line = new module.OrderlineWidget(null, { model: newLine, pos: this.pos, - order: this.pos.get('selectedOrder') + order: this.pos.get('selectedOrder'), + selected:true, }); line.on_selected.add(_.bind(this.selected_line, this)); this.selected_line(); - line.appendTo(this.$element.find('#current-order-content')); - this.update_summary(); + line.appendTo(this.$('.order')); }, selected_line: function() { var reset = false; @@ -178,32 +190,20 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa this.currentSelected = this.pos.get('selectedOrder').selected; if (reset && this.numpadState) this.numpadState.reset(); - this.update_summary(); }, renderElement: function() { this._super(); - var $content = this.$element.find('#current-order-content'); + var $content = this.$('.order'); $content.empty(); this.currentOrderLines.each(_.bind( function(orderLine) { var line = new module.OrderlineWidget(null, { model: orderLine, - order: this.pos.get('selectedOrder') + order: this.pos.get('selectedOrder'), + selected:true, }); line.on_selected.add(_.bind(this.selected_line, this)); line.appendTo($content); }, this)); - this.update_summary(); - }, - update_summary: function() { - var currentOrder, tax, total, totalTaxExcluded; - currentOrder = this.pos.get('selectedOrder'); - total = currentOrder.getTotal(); - totalTaxExcluded = currentOrder.getTotalTaxExcluded(); - tax = currentOrder.getTax(); - this.pos_widget.action_bar.set_total_value(Math.round(total*100)/100); - $('#subtotal').html(totalTaxExcluded.toFixed(2)).hide().fadeIn(); - $('#tax').html(tax.toFixed(2)).hide().fadeIn(); - $('#total').html(total.toFixed(2)).hide().fadeIn(); }, }); diff --git a/addons/point_of_sale/static/src/xml/pos.xml b/addons/point_of_sale/static/src/xml/pos.xml index 564b0da09ea..7cf4744a5ce 100644 --- a/addons/point_of_sale/static/src/xml/pos.xml +++ b/addons/point_of_sale/static/src/xml/pos.xml @@ -25,33 +25,8 @@
        - -
          -
        • - Subtotal: - - 0 - -
        • - -
        • - Tax: - - 0 - -
        • - -
        • - Total: - - 0 - -
        • -
        -
        -
        @@ -63,22 +38,6 @@
        - -
        - - - - - - - - - - - -
        ProductPriceDisc (%)QtyTotal
        -
        -
        @@ -371,46 +330,44 @@ - - - - - - - - - - - - - - - - - - - - - - - + + +
        +
          +
        +
        - +
      4. -

        - Pizza -

        + + + - 3.14$ +
          -
        • - 5 pizza at $0.51/pizza -
        • -
        • - 25 discount -
        • + +
        • + + + + + at + + / + +
        • +
          + +
        • + With a + + % + + discount +
        • +
      5. From 1f0d6e4cb0d80e764090739c74e81ac05058429d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20van=20der=20Essen?= Date: Mon, 25 Jun 2012 12:01:55 +0200 Subject: [PATCH 180/201] [IMP] point_of_sale: merged start() with renderElement() where possible bzr revid: fva@openerp.com-20120625100155-3ec0rk2bq61oy0lo --- .../point_of_sale/static/src/js/pos_screens.js | 9 ++++++--- .../point_of_sale/static/src/js/pos_widgets.js | 17 +++++++---------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/addons/point_of_sale/static/src/js/pos_screens.js b/addons/point_of_sale/static/src/js/pos_screens.js index 1503ab54784..e055c74584e 100644 --- a/addons/point_of_sale/static/src/js/pos_screens.js +++ b/addons/point_of_sale/static/src/js/pos_screens.js @@ -345,7 +345,8 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa module.ScaleProductScreenWidget = module.BaseScreenWidget.extend({ template:'ScaleProductSelectionScreenWidget', - start: function(){ + renderElement: function(){ + this._super(); this.product_categories_widget = new module.ProductCategoriesWidget(this,{ pos:this.pos, product_type: 'weightable', @@ -520,7 +521,8 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa show_leftpane: true, show_total: true, - start: function(){ + renderElement: function(){ + this._super(); this.product_categories_widget = new module.ProductCategoriesWidget(this,{}); this.product_categories_widget.replace($('.placeholder-ProductCategoriesWidget')); @@ -569,7 +571,8 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa this.company = this.pos.get('company'); this.shop_obj = this.pos.get('shop'); }, - start: function() { + renderElement: function() { + this._super(); this.pos.bind('change:selectedOrder', this.change_selected_order, this); this.change_selected_order(); }, diff --git a/addons/point_of_sale/static/src/js/pos_widgets.js b/addons/point_of_sale/static/src/js/pos_widgets.js index 714094f1cbc..9573429c074 100644 --- a/addons/point_of_sale/static/src/js/pos_widgets.js +++ b/addons/point_of_sale/static/src/js/pos_widgets.js @@ -288,9 +288,10 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa } }, this)); }, - start: function() { - $('button.select-order', this.$element).click(_.bind(this.selectOrder, this)); - $('button.close-order', this.$element).click(_.bind(this.closeOrder, this)); + renderElement:function(){ + this._super(); + this.$('button.select-order').click(_.bind(this.selectOrder, this)); + this.$('button.close-order').click(_.bind(this.closeOrder, this)); }, selectOrder: function(event) { this.pos.set({ @@ -318,7 +319,8 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa this.template = 'ActionButtonWidgetWithIcon'; } }, - start: function(){ + renderElement: function(){ + this._super(); if(this.click_action){ this.$element.click(_.bind(this.click_action, this)); } @@ -388,10 +390,6 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa this.set_category(); }, - start: function(){ - this.search_and_categories(); - }, - // changes the category. if undefined, sets to root category set_category : function(category){ if(!category){ @@ -428,6 +426,7 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa self.renderElement(); self.search_and_categories(category); }); + this.search_and_categories(); }, set_product_type: function(type){ // 'all' | 'weightable' @@ -508,8 +507,6 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa self.renderElement(); }); }, - start: function(){ - }, set_weight: function(weight){ for(var i = 0; i < this.product_list.length; i++){ this.product_list[i].set_weight(weight); From f69643a4a1221f291c300c0ce0db49fbe576b68b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20van=20der=20Essen?= Date: Thu, 28 Jun 2012 14:38:25 +0200 Subject: [PATCH 181/201] [IMP] point_of_sale: order widget is now complete bzr revid: fva@openerp.com-20120628123825-hrat3tlhtyz8yv1i --- addons/point_of_sale/point_of_sale_demo.xml | 46 +++++ addons/point_of_sale/static/src/css/pos.css | 124 +++++++++----- .../point_of_sale/static/src/js/pos_models.js | 151 +++++++++++------ .../static/src/js/pos_screens.js | 12 +- .../static/src/js/pos_scrollbar_widget.js | 158 ++++++++++++------ .../static/src/js/pos_widgets.js | 130 +++++++------- addons/point_of_sale/static/src/xml/pos.xml | 34 ++-- 7 files changed, 422 insertions(+), 233 deletions(-) diff --git a/addons/point_of_sale/point_of_sale_demo.xml b/addons/point_of_sale/point_of_sale_demo.xml index 270d79e01d1..1de0d863141 100644 --- a/addons/point_of_sale/point_of_sale_demo.xml +++ b/addons/point_of_sale/point_of_sale_demo.xml @@ -148,6 +148,8 @@ Boni Oranges True + + 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aVK5ZUlhJ7RvgfrWEsHSl0OiONqx6lV/BNr/AMsrydf97B/oKxeW031ZqsxqdUirL4Lus/uL6PH+2p/pWbyxdGWsy7xIH8Gavj93fWmf9pWpf2b5lf2jH+URfBOrFvnv7Xb7I1NZb5ieYr+UtweCXwPP1A8do4/8auOXQW7ZnLMJPZF2Pwbp6ffnupPYuAP0FaxwVJdzOWNqvsatnpdjYDFtbJGf7wHP510RhGOyOeVSUt2WqsgKACgAoAKACgAoAKACgAoAKACgAFCAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAFCAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAFCAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAFCAKACgAoAKACgAoAKACgApAFMAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAQGkAZoAM0AGaADNABmgAzQAZoAM0AGaADNABmgAzQAZoAM0ALRcAzRcBM0AGaLgANAC5oAKYBQAUgA0AGaADNABmgAFABTAKACgAoAKACgAoAKACgAzSAM0AGaAAc0AFABmgAzQAZoQBmgAzQAZoAM0AFMAoADSAM0AJmgYZoELmi4wouAUXEFABQAUAApgAAD/2Q== @@ -155,6 +157,8 @@ Papillon Oranges True + + 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 @@ -163,6 +167,8 @@ Citron True + + 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 @@ -170,6 +176,8 @@ Limons True + + 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 @@ -177,6 +185,8 @@ Pamplemousse Rouge True + + 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 @@ -185,6 +195,8 @@ Pommes Granny Smith True + + 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 @@ -192,6 +204,8 @@ Pommes Jonagold True + + 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 @@ -199,6 +213,8 @@ Pommes Golden Perlim True + + /9j/4AAQSkZJRgABAAEBLAEsAAD/2wCEAAoHBwgHBgoICAgLCgoLDhgQDg0NDh0VFhEYIx8lJCIfIiEmKzcvJik0KSEiMEExNDk7Pj4+JS5ESUM8SDc9PjsBCwoKDg0OHBYWHCgoKCgoOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O//AABEIAEsAZAMBEQACEQEDEQH/xAAfAAABBQEBAQEBAQAAAAAAAAAAAQIDBAUGBwgJCgv/xAC1EAACAQMDAgQDBQUEBAAAAX0BAgMABBEFEiExQQYTUWEHInEUMoGRoQgjQrHBFVLR8CQzYnKCCQoWFxgZGiUmJygpKjQ1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4eLj5OXm5+jp6vHy8/T19vf4+fr/xAAfAQADAQEBAQEBAQEBAAAAAAAAAQIDBAUGBwgJCgv/xAC1EQACAQIEBAMEBwUEBAABAncAAQIDEQQFITEGEkFRB2FxEyIygQgUQpGhscEJIzNS8BVictEKFiQ04SXxFxgZGiYnKCkqNTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqCg4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2dri4+Tl5ufo6ery8/T19vf4+fr/2gAMAwEAAhEDEQA/APZhTAKACgCA3sIvxZMWWVo/MXK8MM4OD3I7j3pXV7DtoT0xBQAUAFABQAUAFABQAGkwQCmAUAQXl9b2MXmTyBQeg7monOMFdjSbOf1TWvtkKi1hVZY2DxSueVYew7dQeehNcVXHw6FqFhRrOpu6nfGqnqFT/HNYvMJD5ETpqt4j/M4PsVGKUcfO4ciNeyvUvI8jhx1WvRoV41VoZyjYsVsIzpde02K6ktmuAZIlDNtBIGc8ZHfisp14Q3Y+VkaeJdLkztnPBxyprN4ykuo+SRct9RtLriGZWJ7dDWkK9OezE00Wa1EBpMEApgUr7VILI7Dl5P7o7fWsateNMaVziPEGtl77fPHuG07FDcDHavLrVnVlsbQjoN0zUxdLlbc7QeuDisOTXYctDZDAjIXHHalOm1qiUywskckA4+YVKacRjLK5+y38ZJwCwU++a2wlVwqoJq6H67rLtO1pbkiNeHYfxH0rrxWKfM4xM4xOdslExuS8W4ySt164GF/pXJKctFYtjZtGIwYmliycnPNRKd1qiuYmhWe2IV3LAdCBipU4rYT1Or0PV/tY+zynMij5WP8AEK9fCYn2is9zKSsbBrrZKKeq3406wefq3RB6ms61T2cLjSucQ13PNIZCxJJyx9a8SpWk2aWJ0hadd0iBvTcKz552BFmGHc+zApJykwJpbdoVBDc1bi49QKbXXlyjtntWUnd3KRqWumrOiyzrnoVX0rqo0OrBslv9LS4t3RcqxHDDqDW0qbErGT4V0yZRPNdqfMEhUA9Pc04U7scmdK0Ksm1lBHpWrgmiTG1GBLcYK8N901w16agCM23nNreRzLwUbNLDzcJpobWh3eQwBHQivfMTnPF0h228Q6fM1cOPeiRUDmkO3H1ryCzSW6BjC46elVKegIEmIfIrNSsxkzXDOvPNW5tgYayG48XWlmD8jZLD2AzRSjzSsNHoCKAMCvWirEikcVVhEVsCu8Y6tmlDQbJTTaEZ+tRb7B27pyK5sVG8Bo5lMtIO9cdNajZ38YKxID1CgV9AtjI5vxYD59uexQj9a4MetionN5rymiiaM4xSaBEqtz1qWtRj9xHfjFOwGPpkgPj21Y+jD9K2w/xD6HpINeoiBJGCoT6CnJ6Ainay7pZApPytg/ln+tZRlqU9i6DxWrZJT1M/8S+bP9w1jWfuMZgaHateX0a4+VTuf6VlhKXNMUnY7c167M0YXiqAvYxTgZ8p8H6H/wDUK5cbC8LlROSIrxmixysOlJoCRWOaloB0j7Uz7U7aAV/Cunm91fUb/bn7NENn++Tn+Sn867cJR5oSfkJvU7uNw8YYcgjIrpi7oY5lyKbWgitawGMsW6scmphGw2yyeOKtiRR1VJZrNoYELySEKAKznFyVkO5c0fS10y0CHDSvy7D+VdlCiqcbGbdy+a1YkRXNul3ayQSfdkXB9qUoqUbMDgr20lsblredcEdD2YeorxK1F05WZomQDjmsWhjlbmlygR3Mo8sgZLHoBTUbiO08MaT/AGTo6RuuJ5j5kvrk9vwGBXuYal7OmkQ3ckRPs07254UfNH7r6fh0/KueUfZza+4tO6J88VSAapGTSQATSbAltEyTIfoK3oR6kyZZroJA0mCAdKYEF5Y21/F5VzEHHY9x9D2qJwjNWaBOxjN4PtgcxXcyjPRgGx/KuaWBg+5XMKPCVvk5uZMdvlGaX1CF92HMWtP8OWFhMJwHnmH3XlIO36ADFa08LTpu6E3c1a6BDJoUmXDjkdCOoqJwU1ZjTsU2tbvdtVk2/wB4n+lcrw9W9k0VzIZHYXaEkyxn86Fhai6oOYmitJD/AK5lA9FOauOHlf3mJyLgAUAAYArpSsSFMANJggoAKACgAoAKACgAoAKACgAoAKACgAoYIP/Z @@ -207,6 +223,8 @@ Poires Conference True + + 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 @@ -215,6 +233,8 @@ Nectarine True + + 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 @@ -222,6 +242,8 @@ Peche True + + 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 @@ -230,6 +252,8 @@ Raisins Noirs True + + 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 @@ -238,6 +262,8 @@ Pommes De Terre True + + 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 @@ -246,6 +272,8 @@ Chicons Flandria Extra True + + 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 @@ -253,6 +281,8 @@ Carottes True + + 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 @@ -260,6 +290,8 @@ Fenouil True + + /9j/4AAQSkZJRgABAAEBLAEsAAD/2wCEAAoHBwgHBgoICAgLCgoLDhgQDg0NDh0VFhEYIx8lJCIfIiEmKzcvJik0KSEiMEExNDk7Pj4+JS5ESUM8SDc9PjsBCwoKDg0OHBYWHCgoKCgoOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O//AABEIAGQAYQMBEQACEQEDEQH/xAAfAAABBQEBAQEBAQAAAAAAAAAAAQIDBAUGBwgJCgv/xAC1EAACAQMDAgQDBQUEBAAAAX0BAgMABBEFEiExQQYTUWEHInEUMoGRoQgjQrHBFVLR8CQzYnKCCQoWFxgZGiUmJygpKjQ1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4eLj5OXm5+jp6vHy8/T19vf4+fr/xAAfAQADAQEBAQEBAQEBAAAAAAAAAQIDBAUGBwgJCgv/xAC1EQACAQIEBAMEBwUEBAABAncAAQIDEQQFITEGEkFRB2FxEyIygQgUQpGhscEJIzNS8BVictEKFiQ04SXxFxgZGiYnKCkqNTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqCg4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2dri4+Tl5ufo6ery8/T19vf4+fr/2gAMAwEAAhEDEQA/APZhTAKACgBGYKpZiAB1JpANimjmUNGwZSAcj0NCaYAk0UufLdWI6gHpQmmBCuoWzIrF9u44ww5ByQRj1yDU86As1YDGlVVZm4C9aVwHKSVBIwT29KAFpgFAETdTSBEopgB4oAq6jfJp1m1zICVXHSpnJRVwOL1fxdLcbjAxig7DqW+vtXJUxD6DOat/ElxYyO0S5UjaYz3Hoa5ISkpN9wuW7DxLMqJuYRCMksQPU9/WrhVcWkBasNS/tDWI4yQkbMT97GB1Iz6Zq4ScpiOouvETWVpcBAWES4jc9yen+NdU6vJFgO0/WoYbS0jv7sfaGZWcvxkEf04/KlGoopKT1A6NWDKGUgg9CK6QFoAKAIm6mkCJRTAxrvxAls8gjXz/ACztKgEEEdetctTFxg7bjsZ2oapDqeleZEuyVWzJHnqB16/nUzq+0pXQI4m7QGeRFGY2JIOORXFze6BjtatAjuWBkbJX0A9frTb7CKyW8pxwAQP71IZu+HPKV2EpMXy4Vs1rRaT1EbGoXski2tmUVWDedJgDBJ6ZHsB+taVJttL5gZl0j3VzL5bhVUF33H14A471j8V2B0mh6jd6XCCN0lqpOUIOCT6GnSrSpehVjsRqVsqQmWVY2mQMFJ5r0XWgrXdrkloEMAQQQe4q7gRN1NAIivrma3VFghMskhIUZxg46n2pNtAcjfCQ3DPM6mSUbjtGB6V5mKg1K/cZUtGjhlZRapOH6oz7Rn6kHHpSw01ez6gW7fSLaWPM5CyDnAbOPoaHTjFtNjRzWqWgDCMRfvYiysQT8y54/nUVJKysIzjaTeX56KdoHU1F+4MLcSRXcYMRUSOuHA+VSen5kGtI33QjUuhJHM5IJcphQev+eKTm22MjgiGBI/UPkgkfMe3Hp/jScrKwjpNO1DOZbpwYLcbj/tN/Co/H+VaUL3u9kNsoTajNeXRkAJZjhQO3oKynKVSYI7rRrGWxsQszlpH5Zc8L7CvVoUnThZg2WjnJz61cVJb/ANdvwEFxCZYHQOyburKcHH1qmroDKfQYWSVEjVWK4EmPmPoSetZToqUWgOYaKS2nyVIIbBHpXjyThIZouN6Qzp1YFX/3gf6it6/vpTQ0UdQ06Vry2uok3DcElX+8Cf8A69ZRV1YDYt9DjNkYZIlAbOVxwMknit+TTUditHoD2SLFHMrwkYlDqCzAZKgenJp004J26isUL22W4lkx1U5wPXp1/Coajv2BmQ9rIshUMzEnt3NYp8zEJdSGRIraJFVYfvEHlnPU/wBK1m7KyEdN4R0tWb7bMBlTiNSO/rXThKP2mM7Cu8RE3U0gRKOlNAFAHP65YgXAmUYWXr9a83G0rPmXUaKFrA6ybChbPQ+nIyfyrnpt/D3Gkb8VrGFAZd2OxrojTSGThcjBqrAVbyPCZz07VEkM5tgfNeRUZkc8EchfrWU02tBDGRbY+b/GF+T1ye/8/wBKmPuK4mY0Fk9xqCKMqJGxkdVqqUeZ2EdfaWUqw7UEkBXlCrHI9jz0NejGNkI37bzBAolOWxya2WwAeSfrUxdwJRVgFAFa/t/tFsw7ryBWVeHPBoaKtqgRQcDOOuK4qcbFFpa1sA8DHepsBXuozLGVBwTSktBoz1tlt0MY5BOST3rMLFK/tNzBlJIAwBWVSN2JovaFpkXkSSyxq2/5VyO3f9f5V24SmuW7JZsmBCytjBXjPqPSuuwiQDAxTAhBzn6n+dY0ZKUXa27/ADf9Mb3Jq2EFAB1oAoRccehIrjsWTxjJppASGk0IicA81mxopzrUtDIHUbMmjkA1rOMRWkaDsua76ceWKRmyarAKAIsYzj1NZUoqKsu7/Nj6korUQUAFAFHH71x/tGuZrVlomU4wanYB4O4Um7iGnGMUrDKsw9qmwypIcgKOpbFOwG4BgAeldxmFABQBEep+tRG9tRko6VYgoAKAKMgKTMAMc5rmnpIpbDixJwO9Zt3GOB2ihKwDSx9aLDIJX9KGA2CLzLiMEcBs/lVwV5ITZq11EBQAUARN1NICUUwCgAoAr3KYIk/A1lVj1GmQoOc1ikUKzYFDAZnPX8aAGNycn8KAJ7KP5jIenQVrSj1Ey5WxIUAFAETdTSBEopgFABQAEZGDQBC9v/zzOPaspU+w0yrcJNGu5beSXB6IVz+pFQ6cirobCLiUfNZyxenmMv8AQmhU5BdE62bvjzCFHcKc1SpdxXLQUKoVRgDoK1tYkWmAUAFAEPJznrk9KypObT5rXu/uu7fha/mPQk3GtRBuNAChjSACxoATcaAFLGgBNxoANxoANxpgG40AG40AG40AMPU1KAD/2Q== @@ -268,6 +300,8 @@ Tomates En Grappe True + + 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 @@ -276,6 +310,8 @@ Oignons True + + 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 @@ -284,6 +320,8 @@ Poivrons Rouges True + + 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 @@ -291,6 +329,8 @@ Poivrons Verts True + + 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 @@ -298,6 +338,8 @@ Poivrons Jaunes True + + /9j/4AAQSkZJRgABAAEBLAEsAAD/2wCEAAoHBwgHBgoICAgLCgoLDhgQDg0NDh0VFhEYIx8lJCIfIiEmKzcvJik0KSEiMEExNDk7Pj4+JS5ESUM8SDc9PjsBCwoKDg0OHBYWHCgoKCgoOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O//AABEIAFUAZAMBEQACEQEDEQH/xAAfAAABBQEBAQEBAQAAAAAAAAAAAQIDBAUGBwgJCgv/xAC1EAACAQMDAgQDBQUEBAAAAX0BAgMABBEFEiExQQYTUWEHInEUMoGRoQgjQrHBFVLR8CQzYnKCCQoWFxgZGiUmJygpKjQ1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4eLj5OXm5+jp6vHy8/T19vf4+fr/xAAfAQADAQEBAQEBAQEBAAAAAAAAAQIDBAUGBwgJCgv/xAC1EQACAQIEBAMEBwUEBAABAncAAQIDEQQFITEGEkFRB2FxEyIygQgUQpGhscEJIzNS8BVictEKFiQ04SXxFxgZGiYnKCkqNTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqCg4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2dri4+Tl5ufo6ery8/T19vf4+fr/2gAMAwEAAhEDEQA/APZhTAKACgAoAKACgCtealZaeF+13CRbzhd3eolOMd2BYR0kRXRgysMhgcgiqWoC0wDpQBFFd207lIriKRh1VXBIqVJPZgS1QAaTBAKYBQBl33iTTNOuvs1xOQ4+9tGdv1rKVaEZWbCxpRyJNGskbBkcZVh0IrRO4DqYBQB5r4ma41bxDJb26mZ9/logP90H/A15VeUp1WlqUrHe6NYtp2k29o7bmjX5vYnnH616NGDhBJie5drQRxvirXnmZ9OshI4ztcQglpG7qMdq4MRXcpckRpGJ4YsVuvFEP+i+QYcyFirK4x2PPc1GGblO2ppJWiem16RkBpMEApgFAHmXjKxez12WXDFJ/nUn14z/ADrzsVC0rjOw0eddL8JW0zyeb+73DnqWOQPwzj8K2VVUMPzM0jD2k7IwrnxLqF5fCzgm2EnHy8c/WvExOPxNSXLF2PTpYSnGPM0SvJq9p8zXM2P7wckVw1a+Nou7nL7ylChPojOEM9pf22owABw5Yk8g5BB/nWlHMakI80t+jMZ4SDlp8zZm8UXzkiONEA9Bk1tPPa8n7qSBYGC3YweLJ0s7gygMwjbYw4w2OP1rpweb1KjcZoyxGEjCN0VvAcSz39zct87RIACexYn/AA/Wu/AxvNs4nsdzXpEhQAGkwQdqYGBqHi21s5HjijaUqcFui5ryq2b0YTcVqzsp4KcldnLeKNa/tfT4X8pUaOTlgexGP8K5I5m68+RxsOthPZwumZVtql19njtjcSeUrcJu4Hfp9c1y4ypVTtzOxvglFw21L8lirXMckcgjlwCxH+eteaqsr+8ztjKyNC4nuTbCN52dVHAIxmpqYl1VytuxEIxTvYrQXbvGEDZUnABpL3VYuUFckmUrKwYAupwSDkU5VZRbT3Ekmhk9kZrUNJCPLfoyDHP4VaqVaaU7ClGE04thpT3OhvJJZykh9oZGHBxn/Gumlm9WElbT9TnjgYapssT6nqdwzSS+cD1GCQFHsK1rZlWm7u6NYYSlFaNFnQ/EdwmoR288jyQyts+c5Kk9CDXRl+Y1FVUZu6ffoZYvBx5G0tUdqa+kZ5CMXxFqjWUKwxPsaQZZh1A9q8zNMW6MOWLs2deDoKo7vocfLtdOVYBuhNfKN3l1PXUTKl0q6kV0ju0ETEEqYye/rmuqGJpQabTbRjWozmrXSK99o94YFFq2ZVbgqwH867FjMPP4jkjha9P4Td0u1niiSJiZZ2+8x5JNeVVqc9S0Ed0Y8sPeZPcyyRxOjHjPI96xje9ioxT1MrTpw3mR91kNdFeFrPyNZI0gefrXMzNkqsyrtDHae1JydrEPcjuMtGQKIaMpEserzC28hgG4I3Gu14qfJyh7GLdzOiVvtMe3r5gx+dKh/Ej6lVpe6/Q9Rr7lnziOT8Wxn7VGx6FB/Wvns9i7xZ6eXy0aMR55JbeOF8FY/unHOK8KVWUkk+h6Cik211JIruWO2MBSNkPTK8j8aftnyOOliXBOVxisAwYqDz0NYrQvoXI7zy1YwxKjH+LrgVUarhflVmZShfczNQmCW7Mx+tFGN5GsTntFuS93Kc8M2a9HFU7QRTeh0qnPNeYyGShsDiosZsa5ytNaMaKzDBrVGqehLpA+0axbR9jKM/TNduBp3xEF5nNiHaEn5HpRr7NnhowvFVqZbFJx/wAszg/Q15ubUeejfsdeDnyzt3OQXpXyDPXTFVsj3pNDuODUrASBjtqWhMxtdZmttgPXrXbhF71x7IwtCk/0phXfi17pR2ER5xXjSJexL90ZqNzNsa5wvFUtxoqSMxPpWsUijS8MwbtctyB0JP5A16OVLmxUfI5sW7UWegmvrWeMiK5gW6tZIH+7IpFTUgpxafUqMnGVzzieN7a5eJxgoxBr4jE0XSqOL6HvU5KUUxOhyK5yhQaAJV+5UPcRnXNq19e29on3ppBH9MnrXoYGHPNLuxVZcsGzVu/hvDpFtNfWV7PM8SbvKkUcjvyPbNe/jMAvZNp7HFSx7lJJooI/yq3pXyzWp3rUuKwZM9c1ko6mUtBvBSl1KiyBo8kE1opFpHReErYteST44jTH4n/Jr3MhpXqSn2Rw5hO0UjrTX0jPLQDpTA5nxRoctwfttmhd8fvEHU+4rxc1y91f3kFr1R34PEqHuyOSjuB5nkyDa/QA96+dnSfRHp36kxBBrELkgOFqWhM3PDGhF7tdXuMbVBECY5J6bv54r6bJcE1FVZfL/M8/G4i/uL5nW9a94844HX9I/su9JjX/AEaYkp6Ke618nmmCdCpzLZ/1Y9jCV/aRs90ULd+TEe/IryprqdMlcm27VrO9xJEeNxFWkXex3Hh+yNnpqlhh5fnP07V9llWG9hh1fd6ni4urz1PQ0zXezmQUwCgBjQQuWLRISwwSVHIqXGL6DUmZD+FbB5i4eZFP8CsMD8xmvOlk+HlK+p0rG1EraD08Maajhikj4/hZ+DTjk+Fi78txPGVWtzXVQqhVAAAwAO1eikkrI5m7hTAjnt4bqFoZ41kjbqrCoqU41I8sldDjJxd0ZK+E9JSUyCOTODgGQ4X6f/Xrh/sjC66b+Z0/XavczLzw3eQkmACdO2Dhh+Brw8RkVem/c95fiddPGwlvoT6V4bk85Zr1QqryI85J+tdWX5NOMlKr06GeIxqtaJ01fRnnAaTBBTAKAA8UAGaQBmgAzQAZoAM0AGaADNABmgAzQAHmhggA/9k= @@ -305,6 +347,8 @@ Poireaux True + + /9j/4AAQSkZJRgABAAEBLAEsAAD/2wCEAAoHBwgHBgoICAgLCgoLDhgQDg0NDh0VFhEYIx8lJCIfIiEmKzcvJik0KSEiMEExNDk7Pj4+JS5ESUM8SDc9PjsBCwoKDg0OHBYWHCgoKCgoOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O//AABEIACgAZAMBEQACEQEDEQH/xAAfAAABBQEBAQEBAQAAAAAAAAAAAQIDBAUGBwgJCgv/xAC1EAACAQMDAgQDBQUEBAAAAX0BAgMABBEFEiExQQYTUWEHInEUMoGRoQgjQrHBFVLR8CQzYnKCCQoWFxgZGiUmJygpKjQ1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4eLj5OXm5+jp6vHy8/T19vf4+fr/xAAfAQADAQEBAQEBAQEBAAAAAAAAAQIDBAUGBwgJCgv/xAC1EQACAQIEBAMEBwUEBAABAncAAQIDEQQFITEGEkFRB2FxEyIygQgUQpGhscEJIzNS8BVictEKFiQ04SXxFxgZGiYnKCkqNTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqCg4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2dri4+Tl5ufo6ery8/T19vf4+fr/2gAMAwEAAhEDEQA/APZhTAKACgAoAKACgCjrOpjStNkudoeQcRof4m7Um7AY+peM7KxsmAZWvAg3IvKo+Ome+KTkkBwNz431ggouozZJyAnWs+diKdp4o16zLtBcyReYwaSR23s5H1z/AEo5mgOw0Xx7DFAZtav3llnYBYIoB+6A43E+/XFWpBc7wOrorqQysMgjuKoY9e9ACimAUAFABQA15EjUs7qqjuxwKTaW4GLqvi7S9MiZvM85h2QjH5mueeLprRageca74nuNbnErFpkXmONeIkHr/tH3NQ6spb6AYG2W4YyXMhRc/wCrTgn8e1NNCHFbdFAUYAOQiA/r607tgQvLPITsjEYH8Ttz+VNIC5Z3mmWcGbm0F3ck8vKxCJ7BVIz9SfwrRWA9U8JeJrXXrIJbW7QiBQmzqoAHY+n+FWmNHSr3qgAsFXJ6ClcDMvtftbNgvMh7hOcVlOsolxptk1rrFpd24mWQID1Vzgj8KaqxaBwaJTd7xmJHk/3Rx+Z4pOo3sHLbcytX0+a+i3T3MdpGvvuP+A/WuWtSc9Ww0OE1e10iw+dYrjUJuuX+7+uB+VcyT2TsJmRJc3l0Vht7a2t0Izhn3HH6Vpypb3JIbmOWIcyISP8AZJH861g0xFBnuGzmfbznCIB/Ot0guUZVmkYmQzP/AL8oH8qpAPjsUcAs9sn++7N/IGmB1/gnUtK8P3j3F5qMQLLt/cLK2R6EbQKqNkNHrFhf2upWwubKdJ4ScB0ORmrQC3yPJaSRxg7mHGDipmrocdzgryC9to5ZNjLLHggHoTmuJxaOpSTLtvJ5d2pUornBO4Eg9Oc/j0pLcHsdNbXUUgwbozt/diXgflXTHUwZYZC5ytqM/wB5yP8A65pum30JuY2peFpNVmaSW6SAHtGm4/mcVksGm7tibMqX4ZQOeNSbHo0Of61qqFtmIjX4XRfxas+PQQ4/9mqvZASL8LrDHz6hO30UCnyCsSL8LdF/5aXN4/8AwNR/SnyIZMnwx8Ooclbl8f3pf8BRyIC5B4D8NwdNNWQ+sjs38zT5UBuWlpbWUAgtYEhiXokagAU0BNQBXurKG7QpIowamUFIak0U/wDhH7MkbgSFAAGeKz9hEv2rNCGCOBdsagD6VqopEN3JKoQUAFABQAUAFABQAlIBRQgAAP/Z @@ -312,6 +356,8 @@ Courgettes True + + /9j/4AAQSkZJRgABAAEBLAEsAAD/2wCEAAoHBwgHBgoICAgLCgoLDhgQDg0NDh0VFhEYIx8lJCIfIiEmKzcvJik0KSEiMEExNDk7Pj4+JS5ESUM8SDc9PjsBCwoKDg0OHBYWHCgoKCgoOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O//AABEIACgAZAMBEQACEQEDEQH/xAAfAAABBQEBAQEBAQAAAAAAAAAAAQIDBAUGBwgJCgv/xAC1EAACAQMDAgQDBQUEBAAAAX0BAgMABBEFEiExQQYTUWEHInEUMoGRoQgjQrHBFVLR8CQzYnKCCQoWFxgZGiUmJygpKjQ1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4eLj5OXm5+jp6vHy8/T19vf4+fr/xAAfAQADAQEBAQEBAQEBAAAAAAAAAQIDBAUGBwgJCgv/xAC1EQACAQIEBAMEBwUEBAABAncAAQIDEQQFITEGEkFRB2FxEyIygQgUQpGhscEJIzNS8BVictEKFiQ04SXxFxgZGiYnKCkqNTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqCg4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2dri4+Tl5ufo6ery8/T19vf4+fr/2gAMAwEAAhEDEQA/APZqYBQAUAFABQAjMqKWZgoHUk4oAoz65pVsD5t/CMddrbv5VLml1Ayrjx5okAJDyy4/uJ/iRUOtEDOn+JdigPk2bue258fyBpe3QGXc/E2+YFbeziQ+pBP9al1wM8/EPxEzDaYxnoojB/pU+3YGjYfEm+t50Gq2yPA33mQbWX39K0jVvuB6JbzxXVvHcQuHilUMjDuD0rYCSgAoAoajrul6UD9svYo2H8G7LfkOaTkkBy2ofE6yhJWxtJJz2aQ7R+VZuqkBzl58Q9euCRE8cCntGo/meaydZgYs+taneEtdXkj+zN/jWTqNhcrFwxBZmbd6tWbkK44eXnGBn060rgTyAKnEZJ9hUqVwIW3HjGPfNVcCEOyPgNiqTAlbdKFUknPFPnaA9Z8BNKfC8KStuEbsif7vp+tdtJ3iM6CeeK2t5J53CRRKWdj0AHU1oB5R4j+Il/fyPDpxa1tegKn53HqT2+n86wnUYHINLJM5eV2JPUnk1k3cCWFgy4C8+prOTESOcgAde/FRcBdrkgqPrQMkSNB8zRnPqBmokriJAp3ApwPpR0AmYM59qm1gGtACMFzTTYDRaxg9TxVXYGzonh651aULGvlw5+eRun4eta06TkNHqWm2kVhZR2kK4SJcD1PqTXdFJKwHFfEPxHvtpNDs2GHGLmQfw8/dB/Dn8qyqVbOwHlyp5c5jBd06g+tYOTYi0ls8hzwg9KlysBat7UA4RTIR78fn0oUJz2QF+OzbqWjB9ACxrRYZ9WA82F0R+6SVz/swH/Gj6t5jI20nxA3/AB76PK/1G3+tH1fzCxJF4d8YSHjRQPdpgKPq3mFi3F4J8YzHm3soPd5CxH5U1hkFi5B8NfEUhzcarbRA9fLiJ/mapYeIGrZfC9I3D3mrXE5H8IAUVapRXQDr7DRrbT4gkWcAY5NaJAXgoXpVAeZ+IPD9ymoyvb6fPcGZy6si7gckk+w5PeuGdGp7R22ZTtYyh8PfEuozxyM0Nqg6+YckZ9hW0KCSJsdBY/C5V2tf6k8pHVY12r/jVxpRQWOktPB2k2igCHfj+9zV2A1IdMsoBiO2QfhTsBYWNF+6ij6CgB34UAFABQAUAFABTAKAAP/Z diff --git a/addons/point_of_sale/static/src/css/pos.css b/addons/point_of_sale/static/src/css/pos.css index 3ce06d12b0b..27135512d1c 100644 --- a/addons/point_of_sale/static/src/css/pos.css +++ b/addons/point_of_sale/static/src/css/pos.css @@ -76,8 +76,13 @@ height: 100%; } .point-of-sale #topheader { + position:absolute; + left:0; + top:0; width: 100%; - height: 54px; + height: 33px; + margin:0; + padding:0; color: gray; border-top: solid 1px #d3d3d3; border-bottom: solid 1px black; @@ -87,40 +92,47 @@ } .point-of-sale #topheader button { color: black; + height:29px; + margin:2px; + margin-right:0px; border: 1px solid black; background: #7f82ac; background: -moz-linear-gradient(#b2b3d7, #7f82ac); background: -webkit-gradient(linear, left top, left bottom, from(#b2b3d7), to(#7f82ac)); } -.point-of-sale #branding, .point-of-sale #rightheader { - float: left; - overflow: hidden; - height: 35px; - padding: 10px; -} -.point-of-sale #rightheader { - float: none; - margin-left: 440px; -} -.point-of-sale #branding { +.point-of-sale #branding{ + position: absolute; + display: table-cell; + left:0; + top:0; + width:439px; + height:100%; + margin:0; + padding:0; border-right: 1px solid #373737; - text-align: left; - width: 419px; + text-align:left; + line-height:100%; + vertical-align: middle; } .point-of-sale #branding img { height: 32px; width: 116px; + margin-left:5px; + vertical-align:middle; } +.point-of-sale #rightheader { + position: absolute; + left:440px; + right:0; + top:0; + height:100%; +} .point-of-sale #neworder-button { width: 32px; - padding: 4px 10px; + margin-left:5px; } .point-of-sale #loggedas { float: right; - padding: 5px 9px; - text-align: center; - color: white; - border-left: 1px solid #373737; } .point-of-sale #loggedas p { margin: 0 0 3px 0; @@ -128,7 +140,7 @@ .point-of-sale #content { width: 100%; position: absolute; - top: 56px; + top: 35px; bottom: 0; background: #F0EEEE; //#E6E4E4; //yellow; //#F0EEEE; } @@ -228,8 +240,11 @@ } .point-of-sale .product-list { padding:10px; - overflow: hidden; +} +.point-of-sale .product-list-scroller{ + width:100%; height:100%; + overflow: hidden; } .point-of-sale .product-list-container { position:absolute; @@ -311,7 +326,8 @@ display: inline-block; font-size: 11px; margin: 5px; - max-width: 120px; + width: 120px; + height:120px; background:#fff; border: 1px solid #fff; -moz-border-radius: 2px; @@ -320,7 +336,6 @@ -moz-box-shadow: 0px 1px 8px #777777; -webkit-box-shadow: 0px 1px 8px rgba(0,0,0,0.2); -box-shadow: 0px 1px 8px rgba(0,0,0,0.9); - -webkit-filter: blur(3px); } .point-of-sale .product .product-img { position: relative; @@ -361,6 +376,7 @@ top:auto; width:100%; background: -webkit-linear-gradient(-90deg,rgba(255,255,255,0),rgba(255,255,255,1), rgba(255,255,255,1)); + /*background:#FFF;*/ padding: 3px; padding-top:15px; } @@ -517,9 +533,13 @@ .point-of-sale .order-container{ position: absolute; top: 0px; - bottom: 229px; + bottom: 232px; width:100%; background: #F0EEEE; +} +.point-of-sale .order-scroller{ + width:100%; + height:100%; overflow:hidden; } .point-of-sale .order{ @@ -530,10 +550,12 @@ margin-left:16px; margin-right:16px; margin-top:16px; + margin-bottom:16px; + font-size:16px; -webkit-box-shadow: 0px 5px 16px rgba(0,0,0, 0.3); } -.point-of-sale .orderline{ +.point-of-sale .order .orderline{ width:100%; margin:0px; padding-top:3px; @@ -544,39 +566,62 @@ -webkit-box-sizing: border-box; -webkit-transition: background 250ms ease-in-out; } -.point-of-sale .orderline:hover{ +.point-of-sale .order .empty{ + text-align:center; + margin-top: 15px; + margin-bottom: 5px; + color:#999; + font-weight: normal; + +} +.point-of-sale .order .orderline:hover{ background: rgba(140,143,183,0.05); -webkit-transition: background 50ms ease-in-out; } -.point-of-sale .orderline.selected{ +.point-of-sale .order .orderline.selected{ background: rgba(140,143,183,0.2); -webkit-transition: background 250ms ease-in-out; cursor: default; } -.point-of-sale .orderline .product-name{ +.point-of-sale .order .orderline .product-name{ padding:0; display:inline-block; - font-size:18px; font-weight: bold; width:80%; overflow:hidden; } -.point-of-sale .orderline .price{ +.point-of-sale .order .orderline .price{ padding:0; - font-size: 18px; font-weight: bold; float:right; } -.point-of-sale .orderline .info-list{ +.point-of-sale .order .orderline .info-list{ color: #888; margin-left:10px; } -.point-of-sale .orderline .info-list em{ +.point-of-sale .order .orderline .info-list em{ color: #777; font-weight: bold; font-style:normal; } +.point-of-sale .order .summary{ + width:100%; + text-align:right; + font-weight: bold; + margin-top:20px; + margin-bottom:10px; +} +.point-of-sale .order .summary .line{ + margin-right:15px; + padding-top:5px; + border-top: solid 2px; + border-color:#777; +} +.point-of-sale .order .summary .line.empty{ + border-color:#BBB; + color:#999; +} /* ----------------------- ACTION BAR ---------------------- */ @@ -637,13 +682,6 @@ background: -webkit-gradient(linear, left top, left bottom, from(#f0f0f0), to(#e2e2e2)); -webkit-box-shadow: 0px 2px 2px rgba(0,0,0, 0.3); } -.point-of-sale .total{ - line-height: 105px; - font-size: 26px; - margin: 0; - margin-left: 180px; - font-weight: bold; -} .point-of-sale .pos-actionbar .button .label{ margin-top: 37px; } @@ -762,10 +800,10 @@ .point-of-sale .scrollbar{ position:absolute; - top:0px; - right:0px; + top:4px; + right:7px; width:48px; - height:100%; + bottom:4px; background: rgba(0,0,0,0.1); } diff --git a/addons/point_of_sale/static/src/js/pos_models.js b/addons/point_of_sale/static/src/js/pos_models.js index b183c22d8ec..e810ca56518 100644 --- a/addons/point_of_sale/static/src/js/pos_models.js +++ b/addons/point_of_sale/static/src/js/pos_models.js @@ -92,13 +92,16 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal 'shop': null, 'company': null, 'user': null, + 'user_list': null, + 'cashier': null, + 'customer': null, 'orders': new module.OrderCollection(), //this is the product list as seen by the product list widgets, it will change based on the category filters 'products': new module.ProductCollection(), 'cashRegisters': null, - 'product_list': null, // the list of all products. + 'product_list': null, // the list of all products, does not change. 'bank_statements': null, 'taxes': null, 'pos_session': null, @@ -119,12 +122,34 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal var prod_def = fetch( 'product.product', - ['name', 'list_price', 'pos_categ_id', 'taxes_id','product_image_small', 'ean13', 'to_weight'], + ['name', 'list_price', 'pos_categ_id', 'taxes_id','product_image_small', 'ean13', 'to_weight', 'uom_id', 'uos_id', 'uos_coeff', 'mes_type'], [['pos_categ_id','!=', false]] ).then(function(result){ self.set({'product_list': result}); }); + var uom_def = fetch( //unit of measure + 'product.uom', + null, + null + ).then(function(result){ + self.set({'units': result}); + var units_by_id = {}; + for(var i = 0, len = result.length; i < len; i++){ + units_by_id[result[i].id] = result[i]; + } + self.set({'units_by_id':units_by_id}); + }); + + var user_def = fetch( + 'res.users', + ['name','ean13'] + [['ean13', '!=', false]] + ).then(function(result){ + self.set({'user_list':result}); + }); + + // associate the products with their categories var prod_process_def = $.when(cat_def, prod_def) .pipe(function(){ @@ -221,7 +246,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal }); // when all the data has loaded, we compute some stuff, and declare the Pos ready to be used. - $.when(cat_def, prod_def, session_def, tax_def, prod_process_def, this.get_app_data(), this.flush()) + $.when(cat_def, prod_def, user_def, uom_def, session_def, tax_def, prod_process_def, this.get_app_data(), this.flush()) .then(function(){ //self.build_tree(); self.build_categories(); @@ -241,6 +266,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal console.log('PosModel data has been loaded:'); console.log('PosModel: categories:',this.get('categories')); console.log('PosModel: product_list:',this.get('product_list')); + console.log('PosModel: units:',this.get('units')); console.log('PosModel: bank_statements:',this.get('bank_statements')); console.log('PosModel: journals:',this.get('journals')); console.log('PosModel: taxes:',this.get('taxes')); @@ -250,6 +276,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal console.log('PosModel: shop:',this.get('shop')); console.log('PosModel: company:',this.get('company')); console.log('PosModel: currency:',this.get('currency')); + console.log('PosModel: user_list:',this.get('user_list')); console.log('PosModel.session:',this.session); console.log('PosModel.categories:',this.categories); console.log('PosModel end of data log.'); @@ -493,7 +520,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal discount: 0, weighted: false, product_type: 'unit', - unit: 'Unit', + selected: false, }, initialize: function(attributes) { this.pos = attributes.pos; @@ -504,29 +531,44 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal this.set({weighted: true}); this.set({product_type: 'weight'}); } - - this.bind('change:quantity', function(unused, qty) { - if (qty == 0) - this.trigger('killme'); - }, this); }, // we override the attributes set to prevent some out of range values + // we also round the quantity according to the unit of measure rounding methods set: function(attributes, options){ + var attributes = _.clone(attributes); //so we don't modify the argument if(attributes.discount > 100){ attributes.discount = 100; }else if(attributes.discount < 0){ attributes.discount = 0; } - if(attributes.quantity < 0){ - attributes.quantity = 0; - } - if(attributes.list_price < 0){ - attributes.list_price = 0; + if(_.isNaN(attributes.quantity)){ + console.log(this.get('order')); + this.get('order').removeOrderline(this); + return this; + }else if(attributes.quantity !== undefined){ + attributes.quantity = Math.max(0,attributes.quantity); + var unit = this.get_unit(); + if(unit && attributes.quantity){ + attributes.quantity = Math.max(unit.rounding, Math.round( attributes.quantity / unit.rounding) * unit.rounding); + } } Backbone.Model.prototype.set.call(this,attributes,options); return this; }, + // returns the unit of measure associated with the product if there is one, undefined otherwise + get_unit: function(){ + var unit_id = (this.get('uos_id') || this.get('uom_id')); + if(!unit_id){ + return undefined; + } + unit_id = unit_id[0]; + if(!this.pos){ + return undefined; + } + return this.pos.get('units_by_id')[unit_id]; + }, + // when we add an new orderline we want to merge it with the last line to see reduce the number of items // in the orderline. This returns true if it makes sense to merge the two @@ -669,6 +711,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal // there is always an active ('selected') order in the Pos, a new one is created // automaticaly once an order is completed and sent to the server. + module.Order = Backbone.Model.extend({ initialize: function(attributes){ Backbone.Model.prototype.initialize.apply(this, arguments); @@ -680,7 +723,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal }); this.pos = attributes.pos; //TODO put that in set and remember to use 'get' to read it ... this.pos_widget = attributes.pos_widget; //FIXME we shouldn't depend on pos_widget in the models - this.last_orderline = undefined; + this.selected_orderline = undefined; return this; }, generateUniqueId: function() { @@ -689,41 +732,24 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal addProduct: function(product){ var attr = product.toJSON(); attr.pos = this.pos; + attr.order = this; var line = new module.Orderline(attr); var self = this; - if( this.last_orderline && this.last_orderline.can_be_merged_with(line) ){ - this.last_orderline.merge(line); + var last_orderline = this.getLastOrderline(); + if( last_orderline && last_orderline.can_be_merged_with(line) ){ + last_orderline.merge(line); }else{ this.get('orderLines').add(line); - line.bind('killme', function() { - this.get('orderLines').remove(line); - }, this); - this.last_orderline = line; } + this.selectLine(this.getLastOrderline()); }, - addProductOld: function(product) { - var existing; - - existing = (this.get('orderLines')).get(product.id); - if (existing != null) { - this.last_orderline = existing; - if(existing.get('weighted')){ - existing.incrementWeight(product.attributes.weight); - }else{ - existing.incrementQuantity(); - } - } else { - var attr = product.toJSON(); - attr.pos = this.pos; - var line = new module.Orderline(attr); - console.log('new Orderline:',line,attr); - this.last_orderline = line; - this.get('orderLines').add(line); - line.bind('killme', function() { - this.get('orderLines').remove(line); - }, this); - } + removeOrderline: function( line ){ + this.get('orderLines').remove(line); + this.selectLine(this.getLastOrderline()); + }, + getLastOrderline: function(){ + return this.get('orderLines').at(this.get('orderLines').length -1); }, addPaymentLine: function(cashRegister) { var newPaymentline; @@ -787,6 +813,23 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal pos_session_id: this.pos.get('pos_session').id, }; }, + getSelectedLine: function(){ + return this.selected_orderline; + }, + selectLine: function(line){ + if(line){ + if(line !== this.selected_orderline){ + if(this.selected_orderline){ + this.selected_orderline.set({'selected':false}); + } + this.selected_orderline = line; + this.selected_orderline.set({'selected':true}); + } + }else{ + this.selected_orderline = undefined; + } + }, + }); module.OrderCollection = Backbone.Collection.extend({ @@ -821,15 +864,18 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal this.updateTarget(); }, deleteLastChar: function() { - var tempNewBuffer; - tempNewBuffer = (this.get('buffer')).slice(0, -1) || "0"; - if (isNaN(tempNewBuffer)) { - tempNewBuffer = "0"; + var tempNewBuffer = this.get('buffer').slice(0, -1); + + if(!tempNewBuffer){ + this.set({ buffer: "0" }); + this.killTarget(); + }else{ + if (isNaN(tempNewBuffer)) { + tempNewBuffer = "0"; + } + this.set({ buffer: tempNewBuffer }); + this.updateTarget(); } - this.set({ - buffer: tempNewBuffer - }); - this.updateTarget(); }, switchSign: function() { var oldBuffer; @@ -858,5 +904,8 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal this.trigger('set_value', parseFloat(bufferContent)); } }, + killTarget: function(){ + this.trigger('set_value',Number.NaN); + }, }); } diff --git a/addons/point_of_sale/static/src/js/pos_screens.js b/addons/point_of_sale/static/src/js/pos_screens.js index e055c74584e..6ef88a0808b 100644 --- a/addons/point_of_sale/static/src/js/pos_screens.js +++ b/addons/point_of_sale/static/src/js/pos_screens.js @@ -238,7 +238,6 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa show_numpad: true, show_leftpane: true, - show_total: true, help_button_action: function(){ this.pos_widget.screen_selector.show_popup('help'); @@ -290,7 +289,6 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa this.pos_widget.set_numpad_visible(this.show_numpad && cashier_mode); this.pos_widget.set_leftpane_visible(this.show_leftpane); this.pos_widget.set_cashier_controls_visible(cashier_mode); - this.pos_widget.action_bar.set_element_visible('total', this.show_total); this.pos_widget.action_bar.set_element_visible('help-button', !cashier_mode, function(){ self.help_button_action(); }); this.pos_widget.action_bar.set_element_visible('logout-button', cashier_mode, function(){ self.logout_button_action(); }); this.pos_widget.action_bar.set_element_visible('close-button', cashier_mode); @@ -345,8 +343,7 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa module.ScaleProductScreenWidget = module.BaseScreenWidget.extend({ template:'ScaleProductSelectionScreenWidget', - renderElement: function(){ - this._super(); + start: function(){ this.product_categories_widget = new module.ProductCategoriesWidget(this,{ pos:this.pos, product_type: 'weightable', @@ -465,7 +462,6 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa show_numpad: false, show_leftpane: false, - show_total: false, show: function(){ this._super(); @@ -488,7 +484,6 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa show_numpad: false, show_leftpane: true, - show_total: true, show: function(){ this._super(); @@ -519,10 +514,8 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa show_numpad: true, show_leftpane: true, - show_total: true, - renderElement: function(){ - this._super(); + start: function(){ //FIXME this should work as renderElement... but then the categories aren't properly set. explore why this.product_categories_widget = new module.ProductCategoriesWidget(this,{}); this.product_categories_widget.replace($('.placeholder-ProductCategoriesWidget')); @@ -562,7 +555,6 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa show_numpad: true, show_leftpane: true, - show_total: true, init: function(parent, options) { this._super(parent,options); diff --git a/addons/point_of_sale/static/src/js/pos_scrollbar_widget.js b/addons/point_of_sale/static/src/js/pos_scrollbar_widget.js index 38793df13aa..d197d388998 100644 --- a/addons/point_of_sale/static/src/js/pos_scrollbar_widget.js +++ b/addons/point_of_sale/static/src/js/pos_scrollbar_widget.js @@ -8,8 +8,11 @@ * jquery selector string that will match on the widget's dom element. If there is no widget provided, * it will match on the document * step: on each click, the target will be scrolled by it's deplayed size multiplied by this value. - * delay: this is the duration of the scrolling animation + * duration: this is the duration of the scrolling animation * wheel_step: the target will be scrolled by wheel_step pixels on each mouse scroll. + * track_bottom: the target will be kept on bottom when it's on the bottom and the size has changed + * on_show: this function will be called with the scrollbar as sole argument when the scrollbar is shown + * on_hide: this function will be called with the scrollbar as sole argument when the scrollbar is hidden */ function openerp_pos_scrollbar(instance, module){ //module is instance.point_of_sale @@ -17,17 +20,39 @@ function openerp_pos_scrollbar(instance, module){ //module is instance.point_of_ template:'ScrollbarWidget', init: function(parent,options){ + var self = this; options = options || {}; this._super(parent,options); this.target_widget = options.target_widget; this.target_selector = options.target_selector; this.scroll_target = this.target().scrollTop(); this.scroll_step = options.step || 0.8; - this.scroll_delay = options.delay || 250; + this.scroll_duration = options.duration || 250; this.wheel_step = options.wheel_step || 80; + this.name = options.name || 'unnamed'; + this.bottom = false; // true if the scroller cannot be scrolled further + this.track_bottom = options.track_bottom || false; + this.on_show = options.on_show || function(){}; + this.on_hide = options.on_hide || function(){}; + + // these handlers are declared once for the object's lifetime so that we can bind and unbind them. + this.resize_handler = function(){ + setTimeout(function(){ + if(self.bottom && self.track_bottom){ + self.set_position(Number.MAX_VALUE); + } + self.update_scroller_dimensions(); + self.update_button_status(); + self.auto_hide(false); + },0); + }; + this.target_mousewheel_handler = function(event,delta){ + self.scroll(delta*self.wheel_step); + } }, - start: function(){ + renderElement: function(){ + this._super(); var self = this; this.$('.up-button').off('click').click(function(){ self.page_up(); @@ -35,17 +60,9 @@ function openerp_pos_scrollbar(instance, module){ //module is instance.point_of_ this.$('.down-button').off('click').click(function(){ self.page_down(); }); - this.update_scroller_dimensions(); + this.update_scroller_dimensions(false); this.update_button_status(); this.auto_hide(false); - $(window).resize(function(){ //FIXME REMOVE HANDLER ... - self.update_scroller_dimensions(); - self.update_button_status(); - self.auto_hide(false); - }); - this.target().bind('mousewheel',function(event,delta){ - self.scroll(delta*self.wheel_step); - }); this.$element.bind('mousewheel',function(event,delta){ self.scroll(delta*self.wheel_step); }); @@ -59,7 +76,29 @@ function openerp_pos_scrollbar(instance, module){ //module is instance.point_of_ self.page_down(); } }); + $(window).unbind('resize',this.resize_handler); + $(window).bind('resize',this.resize_handler); + this.target().unbind('mousewheel',this.target_mousweheel_handler); + this.target().bind('mousewheel',this.target_mousewheel_handler); + + // because the rendering is asynchronous we must wait for the next javascript update + // for good dimensions values + setTimeout(function(){ + self.update_scroller_dimensions(false); + self.update_button_status(); + self.auto_hide(false); + },0); + }, + + // binds the window resize and the target scrolling events. + // it is good advice not to bind these multiple_times + bind_events:function(){ + $(window).resize(function(){ + }); + this.target().bind('mousewheel',function(event,delta){ + self.scroll(delta*self.wheel_step); + }); }, // shows the scrollbar. if animated is true, it will do it in an animated fashion @@ -69,6 +108,7 @@ function openerp_pos_scrollbar(instance, module){ //module is instance.point_of_ }else{ this.$element.show().css('width','48px'); } + this.on_show(this); }, // hides the scrollbar. if animated is true, it will do it in a animated fashion @@ -79,6 +119,7 @@ function openerp_pos_scrollbar(instance, module){ //module is instance.point_of_ }else{ this.$element.hide().css('width','0px'); } + this.on_hide(this); }, // returns the scroller position and other information as a dictionnary with the following fields: @@ -88,12 +129,12 @@ function openerp_pos_scrollbar(instance, module){ //module is instance.point_of_ // bar_height: the height of the scrollbar's inner region scroller_dimensions: function(){ var target = this.target()[0]; - var scroller_height = target.clientHeight / target.scrollHeight; - var scroller_pos = this.scroll_target / target.scrollHeight; - var button_up_height = this.$('.up-button')[0].offsetHeight; - var button_down_height = this.$('.down-button')[0].offsetHeight; + var scroller_height = target.clientHeight / target.scrollHeight || 0; + var scroller_pos = this.scroll_target / target.scrollHeight || 0; + var button_up_height = this.$('.up-button')[0].offsetHeight || 48; + var button_down_height = this.$('.down-button')[0].offsetHeight || 48; - var bar_height = this.$element[0].offsetHeight; + var bar_height = this.$element[0].offsetHeight || 96; var scrollbar_height = bar_height - button_up_height - button_down_height; scroller_pos = scroller_pos * scrollbar_height + button_up_height; @@ -105,15 +146,6 @@ function openerp_pos_scrollbar(instance, module){ //module is instance.point_of_ bar_height: scrollbar_height }; }, - //scrolls up or down by pixels - scroll: function(pixels){ - var target = this.target()[0]; - this.scroll_target = this.scroll_target - pixels; - this.scroll_target = Math.max(0,Math.min(target.scrollHeight-target.clientHeight, this.scroll_target)); - this.target().scrollTop(this.scroll_target); - this.update_scroller_dimensions(); - this.update_button_status(); - }, //checks if it should show or hide the scrollbar based on the target content and then show or hide it // if animated is true, then the scrollbar will be shown or hidden with an animation @@ -139,11 +171,17 @@ function openerp_pos_scrollbar(instance, module){ //module is instance.point_of_ //if animated is true, the scroller will move smoothly to its destination update_scroller_dimensions: function(animated){ var dim = this.scroller_dimensions(); + var target = this.target()[0]; if(animated){ - this.$('.scroller').animate({'top':dim.pos+'px', 'height': dim.height+'px'},this.scroll_delay); + this.$('.scroller').animate({'top':dim.pos+'px', 'height': dim.height+'px'},this.scroll_duration); }else{ this.$('.scroller').css({'top':dim.pos+'px', 'height': dim.height+'px'}); } + if(this.scroll_target + target.clientHeight >= target.scrollHeight){ + this.bottom = true; + }else{ + this.bottom = false; + } }, //disable or enable the up/down buttons according to the scrolled position @@ -176,32 +214,54 @@ function openerp_pos_scrollbar(instance, module){ //module is instance.point_of_ //scroll one page up page_up: function(){ - var target = this.target()[0] - if(this.scroll_target <= 0){ - return; - } - this.scroll_target = this.scroll_target - this.get_scroll_step(); - this.scroll_target = Math.max(0,this.scroll_target); - this.target().animate({'scrollTop':this.scroll_target},this.scroll_delay); - this.update_scroller_dimensions(true); - this.update_button_status(); + return this.set_position(this.scroll_target - this.get_scroll_step(), true); }, //scroll one page down page_down: function(){ - var target = this.target()[0]; - var max_scroll = target.scrollHeight - target.clientHeight; - - if(this.scroll_target >= max_scroll){ - this.scroll_target = max_scroll; - this.target().scrollTop(max_scroll); - return; - } - this.scroll_target = this.scroll_target + this.get_scroll_step(); - this.scroll_target = Math.min(this.scroll_target, max_scroll); - this.target().animate({'scrollTop':this.scroll_target},this.scroll_delay); - this.update_scroller_dimensions(true); - this.update_button_status(); + return this.set_position(this.scroll_target + this.get_scroll_step(), true); }, + + //scrolls up or down by pixels + scroll: function(pixels){ + return this.set_position(this.scroll_target - pixels, false); + }, + + //scroll to a specific position (in pixels). + //if animated is true, it will do this in an animated fashion with a duration equal to scroll_duration + set_position: function(position,animated){ + var self = this; + var target = this.target()[0]; + var bottom = target.scrollHeight-target.clientHeight; + this.scroll_target = Math.max(0,Math.min(bottom,position)); + if(this.scroll_target === 0){ + this.position = 'top'; + }else if(this.scroll_target === 'bottom'){ + this.position = 'bottom'; + }else{ + this.position = 'center'; + } + if(animated){ + this.target().animate({'scrollTop':this.scroll_target},this.scroll_duration); + this.update_button_status(); + this.update_scroller_dimensions(true); + }else{ + this.target().scrollTop(this.scroll_target); + this.update_scroller_dimensions(false); + this.update_button_status(); + } + return this.scroll_target; + }, + + //returns the current position of the scrollbar + get_position: function(){ + return this.scroll_target; + }, + + //returns true if it cannot be scrolled further down + is_at_bottom: function(){ + return this.bottom; + } + }); } diff --git a/addons/point_of_sale/static/src/js/pos_widgets.js b/addons/point_of_sale/static/src/js/pos_widgets.js index 9573429c074..98044b1766d 100644 --- a/addons/point_of_sale/static/src/js/pos_widgets.js +++ b/addons/point_of_sale/static/src/js/pos_widgets.js @@ -78,65 +78,41 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa }, }); -// ---------- "Shopping Carts" ---------- - module.OrderlineWidget = module.PosBaseWidget.extend({ template: 'OrderlineWidget', init: function(parent, options) { this._super(parent,options); + this.model = options.model; + this.order = options.order; + this.model.bind('change', _.bind( function() { this.refresh(); }, this)); - this.model.bind('remove', _.bind( function() { - this.$element.remove(); - }, this)); - this.order = options.order; - - if(options.selected){ - this.select(); - }else{ - this.selected = false; - } }, - clickHandler: function() { - this.select(); + click_handler: function() { + this.order.selectLine(this.model); + this.on_selected(); }, renderElement: function() { this._super(); - this.$element.click(_.bind(this.clickHandler, this)); - if(this.selected){ + this.$element.click(_.bind(this.click_handler, this)); + if(this.model.get('selected')){ this.$element.addClass('selected'); } }, refresh: function(){ this.renderElement(); - }, - select: function() { - console.log('SELECT:',this); - if(this.order.selected_widget){ - this.order.selected_widget.deselect(); - } - this.selected = true; - this.order.selected_widget = this; - this.order.selected = this.model; - this.on_selected(); - this.$element.addClass('selected'); - }, - deselect: function(){ - this.selected = false; - this.order.selected_widget = null; - this.order.selected = null; - this.$element.removeClass('selected'); + this.on_refresh(); }, on_selected: function() {}, + on_refresh: function(){}, }); - + module.OrderWidget = module.PosBaseWidget.extend({ template:'OrderWidget', init: function(parent, options) { this._super(parent,options); - console.log('OrderWidget init:',options) this.set_numpad_state(options.numpadState); this.pos.bind('change:selectedOrder', this.change_selected_order, this); this.bind_orderline_events(); @@ -152,11 +128,11 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa } }, set_value: function(val) { - var param = {}; - param[this.numpadState.get('mode')] = val; var order = this.pos.get('selectedOrder'); if (order.get('orderLines').length !== 0) { - order.selected.set(param); + var param = {}; + param[this.numpadState.get('mode')] = val; + order.getSelectedLine().set(param); } else { this.pos.get('selectedOrder').destroy(); } @@ -168,42 +144,61 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa }, bind_orderline_events: function() { this.currentOrderLines = (this.pos.get('selectedOrder')).get('orderLines'); - this.currentOrderLines.bind('add', this.add_line, this); + this.currentOrderLines.bind('add', this.renderElement, this); this.currentOrderLines.bind('remove', this.renderElement, this); }, - add_line: function(newLine) { - var line = new module.OrderlineWidget(null, { - model: newLine, - pos: this.pos, - order: this.pos.get('selectedOrder'), - selected:true, - }); - line.on_selected.add(_.bind(this.selected_line, this)); - this.selected_line(); - line.appendTo(this.$('.order')); - }, - selected_line: function() { + update_numpad: function() { var reset = false; - if (this.currentSelected !== this.pos.get('selectedOrder').selected) { + if (this.selected_line !== this.pos.get('selectedOrder').getSelectedLine()) { reset = true; } - this.currentSelected = this.pos.get('selectedOrder').selected; + this.selected_line = this.pos.get('selectedOrder').getSelectedLine(); if (reset && this.numpadState) this.numpadState.reset(); }, renderElement: function() { + var self = this; this._super(); - var $content = this.$('.order'); - $content.empty(); + var $content = this.$('.orderlines'); this.currentOrderLines.each(_.bind( function(orderLine) { - var line = new module.OrderlineWidget(null, { + var line = new module.OrderlineWidget(this, { model: orderLine, order: this.pos.get('selectedOrder'), - selected:true, }); - line.on_selected.add(_.bind(this.selected_line, this)); + line.on_selected.add(_.bind(this.update_numpad, this)); + line.on_refresh.add(_.bind(this.update_summary, this)); line.appendTo($content); }, this)); + this.update_numpad(); + this.update_summary(); + + var position = this.scrollbar ? this.scrollbar.get_position() : 0; + var at_bottom = this.scrollbar ? this.scrollbar.is_at_bottom() : false; + + this.scrollbar = new module.ScrollbarWidget(this,{ + target_widget: this, + target_selector: '.order-scroller', + name: 'order', + track_bottom: true, + on_show: function(){ + self.$('.order-scroller').css({'width':'89%'},100); + }, + on_hide: function(){ + self.$('.order-scroller').css({'width':'100%'},100); + }, + }); + + this.scrollbar.replace(this.$('.placeholder-ScrollbarWidget')); + this.scrollbar.set_position(position); + + if( at_bottom ){ + this.scrollbar.set_position(Number.MAX_VALUE, false); + } + }, + update_summary: function(){ + var order = this.pos.get('selectedOrder'); + var total = order ? order.getTotal() : 0; + this.$('.summary .value.total').html(this.format_currency(total)); }, }); @@ -218,7 +213,7 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa this.model.attributes.weight = options.weight || undefined; this.next_screen = options.next_screen || undefined; }, - addToOrder: function(event) { + add_to_order: function(event) { /* Preserve the category URL */ event.preventDefault(); return (this.pos.get('selectedOrder')).addProduct(this.model); @@ -234,7 +229,7 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa this._super(); var self = this; $("a", this.$element).click(function(e){ - self.addToOrder(e); + self.add_to_order(e); if(self.next_screen){ self.pos_widget.screen_selector.set_current_screen(self.next_screen); //FIXME There ought to be a better way to do this ... } @@ -334,10 +329,6 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa this.button_list = []; this.fake_buttons = {}; this.visibility = {}; - this.total_visibility = true; - this.help_visibility = true; - this.logout_visibility = true; - this.close_visibility = true; }, set_element_visible: function(element, visible, action){ if(visible != this.visibility[element]){ @@ -352,9 +343,6 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa this.$('.'+element).off('click').click(action); } }, - set_total_value: function(value){ - this.$('.value').html(value); - }, destroy_buttons:function(){ for(var i = 0; i < this.button_list.length; i++){ this.button_list[i].destroy(); @@ -397,6 +385,8 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa }else{ this.category = category; } + console.log('setting categories:',this.category); + this.breadcrumb = []; for(var i = 1; i < this.category.ancestors.length; i++){ this.breadcrumb.push(this.category.ancestors[i]); @@ -419,7 +409,7 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa renderElement: function(){ var self = this; this._super(); - this.$element.find(".oe-pos-categories-list a").click(function(event){ + this.$(".oe-pos-categories-list a").click(function(event){ var id = $(event.target).data("category-id"); var category = self.pos.categories_by_id[id]; self.set_category(category); @@ -535,7 +525,8 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa this.scrollbar = new module.ScrollbarWidget(this,{ target_widget: this, - target_selector: '.product-list', + target_selector: '.product-list-scroller', + name: 'product-list', }); this.scrollbar.replace(this.$('.placeholder-ScrollbarWidget')); @@ -745,7 +736,6 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa window.screen_selector = this.screen_selector; //DEBUG this.pos.barcode_reader.connect(); - }, //FIXME this method is probably not at the right place ... diff --git a/addons/point_of_sale/static/src/xml/pos.xml b/addons/point_of_sale/static/src/xml/pos.xml index 7cf4744a5ce..026473f8f04 100644 --- a/addons/point_of_sale/static/src/xml/pos.xml +++ b/addons/point_of_sale/static/src/xml/pos.xml @@ -11,7 +11,6 @@
        -
        @@ -120,8 +119,10 @@
        -
          -
        +
        +
          +
        +
        @@ -333,8 +334,24 @@
        -
          -
        +
        +
        +
          + +
        • + Your shopping cart is empty +
        • +
          + +
        +
        + + Total: 0.00 € + +
        +
        +
        +
        @@ -352,11 +369,11 @@ - + at / - + @@ -470,9 +487,6 @@
        Client Mode
        -

        - Total: 0 € -