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 01/54] [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 02/54] [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 03/54] [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 04/54] [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 05/54] [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 06/54] [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 07/54] [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 08/54] [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 09/54] [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 10/54] [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 11/54] [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 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 @@ -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 12/54] [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 13/54] [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 14/54] [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 15/54] =?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 16/54] [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 17/54] [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 18/54] [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 19/54] [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 20/54] [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 21/54] [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 22/54] [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 23/54] [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 24/54] [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 25/54] [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 26/54] [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 27/54] [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 28/54] [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 29/54] [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 30/54] [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 31/54] [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 32/54] [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 33/54] [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 34/54] [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 35/54] [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 36/54] [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 37/54] [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 38/54] [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 39/54] [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 7a54f94ac065f90ccbf23659566821fac62e37b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20van=20der=20Essen?= Date: Thu, 5 Apr 2012 14:58:43 +0200 Subject: [PATCH 40/54] [IMP] First working draft of the OnScreen Keyboard bzr revid: fva@openerp.com-20120405125843-axg3m7h10g5sg6mx --- addons/point_of_sale/static/src/css/pos.css | 146 +++++++++++++++ addons/point_of_sale/static/src/js/pos.js | 195 ++++++++++++++++++++ addons/point_of_sale/static/src/xml/pos.xml | 104 +++++++++++ 3 files changed, 445 insertions(+) diff --git a/addons/point_of_sale/static/src/css/pos.css b/addons/point_of_sale/static/src/css/pos.css index 09fbe3818f2..47ea23f202b 100644 --- a/addons/point_of_sale/static/src/css/pos.css +++ b/addons/point_of_sale/static/src/css/pos.css @@ -487,3 +487,149 @@ } } +/* Onscreen Keyboard http://net.tutsplus.com/tutorials/javascript-ajax/creating-a-keyboard-with-css-and-jquery/ */ + +.point-of-sale .keyboard_frame{ + display: none; + position:absolute; + left: 0; + bottom: 0px; + margin: 0; + padding: 0; + padding-top: 15px; + width: 100%; + height: 0px; /* 235px, animated via jquery */ + background-color: #BBB; + overflow:hidden; + -webkit-box-shadow: 0px 0px 10px rgba(0,0,0, 0.3); + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + +} +.point-of-sale .keyboard_frame .close_button{ + height:40px; + width:60px; + text-align:center; + background-color: #DDD; + font-size: 12px; + line-height:40px; + border: 1px solid #CCC; + -webkit-border-radius: 5px; + -webkit-box-shadow: 0px 2px 5px rgba(0,0,0, 0.2); + position:absolute; + top:0; + right:15px; + cursor: pointer; +} +.point-of-sale .keyboard li { + float: left; + text-align: center; + background-color: #fff; + border: 1px solid #f0f0f0; + top:0; + -moz-border-radius: 5px; + -webkit-border-radius: 5px; + -webkit-box-shadow: 0px 2px 5px rgba(0,0,0, 0.2); + -webkit-transition-property: top, background-color; + -webkit-transition-duration: 0.2s; + -webkit-transition-timing-function: linear; +} +.point-of-sale .keyboard li:hover { + position: relative; + top: 2px; + left: 0px; + border-color: #ddd; + background-color:#e5e5e5; + cursor: pointer; + -webkit-transition-property: top, background-color; + -webkit-transition-duration: 0.1s; + -webkit-transition-timing-function: ease-out; +} +.point-of-sale .uppercase { + text-transform: uppercase; +} +.point-of-sale .on { + display: none; +} +.point-of-sale .firstitem{ + clear: left; +} +.point-of-sale .keyboard .lastitem { + margin-right: 0; +} + +/* ---- full sized keyboard ---- */ + +.point-of-sale .full_keyboard { + list-style: none; + font-size: 14px; + width: 680px; + height: 100%; + margin-left: auto; + margin-right: auto; +} +.point-of-sale .full_keyboard li{ + margin: 0 5px 5px 0; + width: 40px; + height: 40px; + line-height: 40px; +} +.point-of-sale .full_keyboard .tab, .point-of-sale .full_keyboard .delete { + width: 70px; +} +.point-of-sale .full_keyboard .capslock { + width: 80px; +} +.point-of-sale .full_keyboard .return { + width: 77px; +} +.point-of-sale .full_keyboard .left-shift { + width: 95px; +} +.point-of-sale .full_keyboard .right-shift { + width: 109px; +} +.point-of-sale .full_keyboard .space { + clear: left; + width: 673px; +} + +/* ---- simplified keyboard ---- */ + +.point-of-sale .simple_keyboard { + list-style: none; + font-size: 16px; + width: 545px; + height: 220px; + margin-left: auto; + margin-right: auto; +} +.point-of-sale .simple_keyboard li{ + margin: 0 5px 5px 0; + width: 49px; + height: 49px; + line-height: 49px; +} +.point-of-sale .simple_keyboard .firstitem.row_asdf{ + margin-left:25px; +} +.point-of-sale .simple_keyboard .firstitem.row_zxcv{ + margin-left:55px; +} +.point-of-sale .simple_keyboard .delete{ + width: 103px; +} +.point-of-sale .simple_keyboard .return{ + width: 103px; +} +.point-of-sale .simple_keyboard .space{ + width:268px; +} +.point-of-sale .simple_keyboard .numlock{ + width:103px; +} + diff --git a/addons/point_of_sale/static/src/js/pos.js b/addons/point_of_sale/static/src/js/pos.js index a03e34a3e53..7d37ae5dd8c 100644 --- a/addons/point_of_sale/static/src/js/pos.js +++ b/addons/point_of_sale/static/src/js/pos.js @@ -1082,6 +1082,198 @@ openerp.point_of_sale = function(db) { } }); + // A Widget that displays an onscreen keyboard. + // There are two options when creating the widget : + // + // * 'keyboard_model' : 'simple' | 'full' (default) + // The 'full' emulates a PC keyboard, while 'simple' emulates an 'android' one. + // + // * 'input_selector : (default: '.searchbox input') + // defines the dom element that the keyboard will write to. + // + // The widget is initally hidden. It can be shown with this.show(), and is + // automatically shown when the input_selector gets focused. + var OnscreenKeyboardWidget = db.web.Widget.extend({ + tagName: 'div', + + init: function(parent, options){ + var self = this; + + function get_option(opt,default_value){ + if(options){ + return options[opt] || default_value; + }else{ + return default_value; + } + } + + this.keyboard_model = get_option('keyboard_model','full'); + this.template_simple = qweb_template('pos-onscreen-keyboard-simple-template'); + this.template_full = qweb_template('pos-onscreen-keyboard-full-template'); + + this.template_fct = function(){ + if( this.keyboard_model == 'full' ){ + return this.template_full.apply(this,arguments); + }else{ + return this.template_simple.apply(this,arguments); + } + }; + + this.input_selector = get_option('input_selector','.searchbox input'); + + //show the keyboard when the input zone is clicked. + $(this.input_selector).focus(function(){self.show();}); + + //Keyboard state + this.capslock = false; + this.shift = false; + this.numlock = false; + }, + + // Write a character to the input zone + writeCharacter: function(character){ + var $input = $(this.input_selector); + $input[0].value += character; + $input.keydown(); + $input.keyup(); + }, + + // Sends a 'return' character to the input zone. TODO + sendReturn: function(){ + }, + + // Removes the last character from the input zone. + deleteCharacter: function(){ + var $input = $(this.input_selector); + var input_value = $input[0].value; + $input[0].value = input_value.substr(0, input_value.length - 1); + $input.keydown(); + $input.keyup(); + }, + + // Clears the content of the input zone. + deleteAllCharacters: function(){ + var $input = $(this.input_selector); + $input[0].value = ""; + $input.keydown(); + $input.keyup(); + }, + renderElement: function(){ + this.$element.html(this.template_fct()); + }, + + // Makes the keyboard show and slide from the bottom of the screen. + show: function(){ + $('.keyboard_frame').show().animate({'height':'235px'}, 500, 'swing'); + }, + + // Makes the keyboard hide by sliding to the bottom of the screen. + hide: function(){ + var self = this; + var frame = $('.keyboard_frame'); + frame.animate({'height':'0'}, 500, 'swing', function(){ frame.hide(); self.reset(); }); + }, + + //What happens when the shift key is pressed : toggle case, remove capslock + toggleShift: function(){ + $('.letter').toggleClass('uppercase'); + $('.symbol span').toggle(); + + self.shift = (self.shift === true) ? false : true; + self.capslock = false; + }, + + //what happens when capslock is pressed : toggle case, set capslock + toggleCapsLock: function(){ + $('.letter').toggleClass('uppercase'); + self.capslock = true; + }, + + //What happens when numlock is pressed : toggle symbols and numlock label + toggleNumLock: function(){ + $('.symbol span').toggle(); + $('.numlock span').toggle(); + self.numlock = (self.numlock === true ) ? false : true; + }, + + //After a key is pressed, shift is disabled. + removeShift: function(){ + if (self.shift === true) { + $('.symbol span').toggle(); + if (this.capslock === false) $('.letter').toggleClass('uppercase'); + + self.shift = false; + } + }, + + // Resets the keyboard to its original state; capslock: false, shift: false, numlock: false + reset: function(){ + if(this.shift){ + this.toggleShift(); + } + if(this.capslock){ + this.toggleCapsLock(); + } + if(this.numlock){ + this.toggleNumLock(); + } + }, + + //called after the keyboard is in the DOM, sets up the key bindings. + start: function(){ + var self = this; + + //this.show(); + + + $('.close_button').click(function(){ + self.deleteAllCharacters(); + self.hide(); + }); + + // Keyboard key click handling + $('.keyboard li').click(function(){ + + var $this = $(this), + character = $this.html(); // If it's a lowercase letter, nothing happens to this variable + + if ($this.hasClass('left-shift') || $this.hasClass('right-shift')) { + self.toggleShift(); + return false; + } + + if ($this.hasClass('capslock')) { + self.toggleCapsLock(); + return false; + } + + if ($this.hasClass('delete')) { + self.deleteCharacter(); + return false; + } + + if ($this.hasClass('numlock')){ + self.toggleNumLock(); + return false; + } + + // Special characters + if ($this.hasClass('symbol')) character = $('span:visible', $this).html(); + if ($this.hasClass('space')) character = ' '; + if ($this.hasClass('tab')) character = "\t"; + if ($this.hasClass('return')) character = "\n"; + + // Uppercase letter + if ($this.hasClass('uppercase')) character = character.toUpperCase(); + + // Remove shift once a key is clicked. + self.removeShift(); + + self.writeCharacter(character); + }); + }, + }); + var ShopWidget = db.web.OldWidget.extend({ init: function(parent, options) { this._super(parent); @@ -1179,6 +1371,9 @@ openerp.point_of_sale = function(db) { this.categoryView = new CategoryWidget(null, 'products-screen-categories'); this.categoryView.on_change_category.add_last(_.bind(this.category, this)); this.category(); + + this.onscreenKeyboard = new OnscreenKeyboardWidget(null,{keyboard_model:'simple'}); + this.onscreenKeyboard.appendTo($(".point-of-sale #content")); }; App.prototype.category = function(id) { diff --git a/addons/point_of_sale/static/src/xml/pos.xml b/addons/point_of_sale/static/src/xml/pos.xml index 16192f208c8..b772b9ca2e5 100644 --- a/addons/point_of_sale/static/src/xml/pos.xml +++ b/addons/point_of_sale/static/src/xml/pos.xml @@ -294,4 +294,108 @@ + + +
+
    +
  • `~
  • +
  • 1!
  • +
  • 2@
  • +
  • 3#
  • +
  • 4$
  • +
  • 5%
  • +
  • 6^
  • +
  • 7&
  • +
  • 8*
  • +
  • 9(
  • +
  • 0)
  • +
  • -_
  • +
  • =+
  • +
  • delete
  • +
  • tab
  • +
  • q
  • +
  • w
  • +
  • e
  • +
  • r
  • +
  • t
  • +
  • y
  • +
  • u
  • +
  • i
  • +
  • o
  • +
  • p
  • +
  • [{
  • +
  • ]}
  • +
  • \|
  • +
  • caps lock
  • +
  • a
  • +
  • s
  • +
  • d
  • +
  • f
  • +
  • g
  • +
  • h
  • +
  • j
  • +
  • k
  • +
  • l
  • +
  • ;:
  • +
  • '"
  • +
  • return
  • +
  • shift
  • +
  • z
  • +
  • x
  • +
  • c
  • +
  • v
  • +
  • b
  • +
  • n
  • +
  • m
  • +
  • ,<
  • +
  • .>
  • +
  • /?
  • +
  • shift
  • +
  • &nbsp;
  • +
+

close

+
+
+ +
+
    +
  • q1
  • +
  • w2
  • +
  • e3
  • +
  • r4
  • +
  • t5
  • +
  • y6
  • +
  • u7
  • +
  • i8
  • +
  • o9
  • +
  • p0
  • + +
  • a@
  • +
  • s#
  • +
  • d%
  • +
  • f*
  • +
  • g/
  • +
  • h-
  • +
  • j+
  • +
  • k(
  • +
  • l)
  • + +
  • z?
  • +
  • x!
  • +
  • c"
  • +
  • v'
  • +
  • b:
  • +
  • n;
  • +
  • m,
  • +
  • delete
  • + +
  • 123ABC
  • +
  • &nbsp;
  • +
  • ..
  • +
  • return
  • +
+

close

+
+
From a6526301f96fb7b908dd3058e35653d28178f6c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20van=20der=20Essen?= Date: Tue, 10 Apr 2012 12:42:47 +0200 Subject: [PATCH 41/54] [IMP] some refactoring of the pos bzr revid: fva@openerp.com-20120410104247-bfk6031bn861091v --- addons/point_of_sale/static/src/js/pos.js | 153 ++++++++++++---------- 1 file changed, 82 insertions(+), 71 deletions(-) diff --git a/addons/point_of_sale/static/src/js/pos.js b/addons/point_of_sale/static/src/js/pos.js index 7d37ae5dd8c..f2b8e37ddf7 100644 --- a/addons/point_of_sale/static/src/js/pos.js +++ b/addons/point_of_sale/static/src/js/pos.js @@ -380,6 +380,7 @@ openerp.point_of_sale = function(db) { existing.incrementQuantity(); } else { var line = new Orderline(product.toJSON()); + console.log(line); this.get('orderLines').add(line); line.bind('killme', function() { this.get('orderLines').remove(line); @@ -903,6 +904,7 @@ openerp.point_of_sale = function(db) { $('.delete-payment-line', this.$element).click(this.on_delete); }, }); + var PaymentWidget = db.web.OldWidget.extend({ init: function(parent, options) { this._super(parent); @@ -1082,22 +1084,31 @@ openerp.point_of_sale = function(db) { } }); - // A Widget that displays an onscreen keyboard. - // There are two options when creating the widget : - // - // * 'keyboard_model' : 'simple' | 'full' (default) - // The 'full' emulates a PC keyboard, while 'simple' emulates an 'android' one. - // - // * 'input_selector : (default: '.searchbox input') - // defines the dom element that the keyboard will write to. - // - // The widget is initally hidden. It can be shown with this.show(), and is - // automatically shown when the input_selector gets focused. + var ToolbarWidget = db.web.Widget.extend({ + tagName: 'div', + init: function(parent, options){ + this._super(parent,options); + }, + }); + + // A Widget that displays an onscreen keyboard. + // There are two options when creating the widget : + // + // * 'keyboard_model' : 'simple' | 'full' (default) + // The 'full' emulates a PC keyboard, while 'simple' emulates an 'android' one. + // + // * 'input_selector : (default: '.searchbox input') + // defines the dom element that the keyboard will write to. + // + // The widget is initally hidden. It can be shown with this.show(), and is + // automatically shown when the input_selector gets focused. var OnscreenKeyboardWidget = db.web.Widget.extend({ tagName: 'div', init: function(parent, options){ var self = this; + + this._super(parent,options); function get_option(opt,default_value){ if(options){ @@ -1376,6 +1387,66 @@ openerp.point_of_sale = function(db) { this.onscreenKeyboard.appendTo($(".point-of-sale #content")); }; + //returns true if the code is a valid EAN codebar number by checking the control digit. + App.checkEan = function(code){ + 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); + } + 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; + }; + + // returns a product that has a packaging with an EAN matching to provided ean string. + // returns undefined if no such product is found. + App.getProductByEAN = function(ean, allPackages, allProducts) { + 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; + }; + App.prototype.category = function(id) { var c, products, self = this; @@ -1394,69 +1465,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) { - 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); - } - 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 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; - 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; - } - // 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){ From e90e598dff084bf36976d3ff0f300df531d34833 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20van=20der=20Essen?= Date: Mon, 16 Apr 2012 12:41:44 +0200 Subject: [PATCH 42/54] Renamed db to session, draft of the SqlStore bzr revid: fva@openerp.com-20120416104144-ijzqhul3m7r0b62g --- addons/point_of_sale/static/src/js/pos.js | 133 ++++++++++++++-------- 1 file changed, 85 insertions(+), 48 deletions(-) diff --git a/addons/point_of_sale/static/src/js/pos.js b/addons/point_of_sale/static/src/js/pos.js index f2b8e37ddf7..7fd1309cc46 100644 --- a/addons/point_of_sale/static/src/js/pos.js +++ b/addons/point_of_sale/static/src/js/pos.js @@ -1,8 +1,9 @@ -openerp.point_of_sale = function(db) { - - db.point_of_sale = {}; - var QWeb = db.web.qweb; +openerp.point_of_sale = function(session) { + + session.point_of_sale = {}; + + var QWeb = session.web.qweb; var qweb_template = function(template) { return function(ctx) { return QWeb.render(template, _.extend({}, ctx,{ @@ -17,13 +18,13 @@ openerp.point_of_sale = function(db) { })); }; }; - var _t = db.web._t; + var _t = session.web._t; /* Local store access. Read once from localStorage upon construction and persist on every change. There should only be one store active at any given time to ensure data consistency. */ - var Store = db.web.Class.extend({ + var Store = session.web.Class.extend({ init: function() { this.data = {}; }, @@ -33,15 +34,52 @@ openerp.point_of_sale = function(db) { if (stored) this.data[key] = JSON.parse(stored); else + console.log('Store get: ',key, 'default: ',_default); return _default; } + console.log('Store get: ',key, 'value: ',this.data[key]); return this.data[key]; }, set: function(key, value) { + console.log('Store set: ',key,' value: ',value); this.data[key] = value; localStorage['oe_pos_' + key] = JSON.stringify(value); }, }); + + var SqlStore = session.web.Class.extend({ + init: function(){ + this.data = {}; + }, + get: function(key, _default){ + if (this.data[key] === undefined) { + var stored = localStorage['oe_pos_' + key]; + if (stored){ + this.data[key] = JSON.parse(stored); + }else{ + console.log('Store get: ',key, 'default: ',_default); + return _default; + } + } + console.log('Store get: ',key, 'value: ', this.data[key]); + return this.data[key]; + }, + set: function(key, value){ + console.log('Store set: ',key,' value: ',value); + this.data[key] = value; + localStorage['oe_pos_' + key] = JSON.stringify(value); + }, + collection_new: function(collection, elem_name_field, elem_name_func){ + }, + collection_get: function(collection, elem_name_field, elem_name){ + }, + collection_add: function(collection, elem){ + }, + collection_find: function(collection, elem_name_field, elem_name_pattern){ + }, + }); + + /* Gets all the necessary data from the OpenERP web client (session, shop data etc.) */ @@ -81,26 +119,26 @@ openerp.point_of_sale = function(db) { fetch: function(osvModel, fields, domain) { var dataSetSearch; var self = this; - dataSetSearch = new db.web.DataSetSearch(this, osvModel, {}, domain); + dataSetSearch = new session.web.DataSetSearch(this, osvModel, {}, domain); return dataSetSearch.read_slice(fields, 0).then(function(result) { return self.store.set(osvModel, result); }); }, get_app_data: function() { var self = this; - return $.when(new db.web.Model("sale.shop").get_func("search_read")([]).pipe(function(result) { + return $.when(new session.web.Model("sale.shop").get_func("search_read")([]).pipe(function(result) { self.set({'shop': result[0]}); var company_id = result[0]['company_id'][0]; - return new db.web.Model("res.company").get_func("read")(company_id, ['currency_id', 'name', 'phone']).pipe(function(result) { + return new session.web.Model("res.company").get_func("read")(company_id, ['currency_id', 'name', 'phone']).pipe(function(result) { self.set({'company': result}); var currency_id = result['currency_id'][0] - return new db.web.Model("res.currency").get_func("read")([currency_id], + return new session.web.Model("res.currency").get_func("read")([currency_id], ['symbol', 'position']).pipe(function(result) { self.set({'currency': result[0]}); }); }); - }), new db.web.Model("res.users").get_func("read")(this.session.uid, ['name']).pipe(function(result) { + }), new session.web.Model("res.users").get_func("read")(this.session.uid, ['name']).pipe(function(result) { self.set({'user': result}); })); }, @@ -123,7 +161,7 @@ openerp.point_of_sale = function(db) { /* we prevent the default error handler and assume errors * are a normal use case, except we stop the current iteration */ - return new db.web.Model("pos.order").get_func("create_from_ui")([op]).fail(function(unused, event) { + return new session.web.Model("pos.order").get_func("create_from_ui")([op]).fail(function(unused, event) { event.preventDefault(); }).pipe(_.bind(function() { console.debug('saved 1 record'); @@ -333,7 +371,7 @@ openerp.point_of_sale = function(db) { }, exportAsJSON: function(){ return { - name: db.web.datetime_to_str(new Date()), + name: session.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], @@ -380,7 +418,7 @@ openerp.point_of_sale = function(db) { existing.incrementQuantity(); } else { var line = new Orderline(product.toJSON()); - console.log(line); + console.log('orderline:',line,product.toJSON()); this.get('orderLines').add(line); line.bind('killme', function() { this.get('orderLines').remove(line); @@ -551,7 +589,7 @@ openerp.point_of_sale = function(db) { Views --- */ - var NumpadWidget = db.web.OldWidget.extend({ + var NumpadWidget = session.web.OldWidget.extend({ init: function(parent, options) { this._super(parent); this.state = new NumpadState(); @@ -588,7 +626,7 @@ openerp.point_of_sale = function(db) { /* Gives access to the payment methods (aka. 'cash registers') */ - var PaypadWidget = db.web.OldWidget.extend({ + var PaypadWidget = session.web.OldWidget.extend({ init: function(parent, options) { this._super(parent); this.shop = options.shop; @@ -619,7 +657,7 @@ openerp.point_of_sale = function(db) { }, this)); } }); - var PaymentButtonWidget = db.web.OldWidget.extend({ + var PaymentButtonWidget = session.web.OldWidget.extend({ template_fct: qweb_template('pos-payment-button-template'), renderElement: function() { this.$element.html(this.template_fct({ @@ -637,7 +675,7 @@ openerp.point_of_sale = function(db) { It should be possible to go back to any step as long as step 3 hasn't been completed. Modifying an order after validation shouldn't be allowed. */ - var StepSwitcher = db.web.OldWidget.extend({ + var StepSwitcher = session.web.OldWidget.extend({ init: function(parent, options) { this._super(parent); this.shop = options.shop; @@ -663,7 +701,7 @@ openerp.point_of_sale = function(db) { /* Shopping carts. */ - var OrderlineWidget = db.web.OldWidget.extend({ + var OrderlineWidget = session.web.OldWidget.extend({ tagName: 'tr', template_fct: qweb_template('pos-orderline-template'), init: function(parent, options) { @@ -704,7 +742,7 @@ openerp.point_of_sale = function(db) { on_selected: function() {}, }); - var OrderWidget = db.web.OldWidget.extend({ + var OrderWidget = session.web.OldWidget.extend({ init: function(parent, options) { this._super(parent); this.shop = options.shop; @@ -789,7 +827,7 @@ openerp.point_of_sale = function(db) { /* "Products" step. */ - var CategoryWidget = db.web.OldWidget.extend({ + var CategoryWidget = session.web.OldWidget.extend({ start: function() { this.$element.find(".oe-pos-categories-list a").click(_.bind(this.changeCategory, this)); }, @@ -825,7 +863,7 @@ openerp.point_of_sale = function(db) { on_change_category: function(id) {}, }); - var ProductWidget = db.web.OldWidget.extend({ + var ProductWidget = session.web.OldWidget.extend({ tagName:'li', template_fct: qweb_template('pos-product-template'), init: function(parent, options) { @@ -848,7 +886,7 @@ openerp.point_of_sale = function(db) { }, }); - var ProductListWidget = db.web.OldWidget.extend({ + var ProductListWidget = session.web.OldWidget.extend({ init: function(parent, options) { this._super(parent); this.model = options.model; @@ -870,7 +908,7 @@ openerp.point_of_sale = function(db) { /* "Payment" step. */ - var PaymentlineWidget = db.web.OldWidget.extend({ + var PaymentlineWidget = session.web.OldWidget.extend({ tagName: 'tr', template_fct: qweb_template('pos-paymentline-template'), init: function(parent, options) { @@ -905,7 +943,7 @@ openerp.point_of_sale = function(db) { }, }); - var PaymentWidget = db.web.OldWidget.extend({ + var PaymentWidget = session.web.OldWidget.extend({ init: function(parent, options) { this._super(parent); this.model = options.model; @@ -1001,7 +1039,7 @@ openerp.point_of_sale = function(db) { }, }); - var ReceiptWidget = db.web.OldWidget.extend({ + var ReceiptWidget = session.web.OldWidget.extend({ init: function(parent, options) { this._super(parent); this.model = options.model; @@ -1044,7 +1082,7 @@ openerp.point_of_sale = function(db) { }, }); - var OrderButtonWidget = db.web.OldWidget.extend({ + var OrderButtonWidget = session.web.OldWidget.extend({ tagName: 'li', template_fct: qweb_template('pos-order-selector-button-template'), init: function(parent, options) { @@ -1084,7 +1122,7 @@ openerp.point_of_sale = function(db) { } }); - var ToolbarWidget = db.web.Widget.extend({ + var ToolbarWidget = session.web.Widget.extend({ tagName: 'div', init: function(parent, options){ this._super(parent,options); @@ -1100,9 +1138,9 @@ openerp.point_of_sale = function(db) { // * 'input_selector : (default: '.searchbox input') // defines the dom element that the keyboard will write to. // - // The widget is initally hidden. It can be shown with this.show(), and is + // The widget is initially hidden. It can be shown with this.show(), and is // automatically shown when the input_selector gets focused. - var OnscreenKeyboardWidget = db.web.Widget.extend({ + var OnscreenKeyboardWidget = session.web.Widget.extend({ tagName: 'div', init: function(parent, options){ @@ -1285,7 +1323,7 @@ openerp.point_of_sale = function(db) { }, }); - var ShopWidget = db.web.OldWidget.extend({ + var ShopWidget = session.web.OldWidget.extend({ init: function(parent, options) { this._super(parent); this.shop = options.shop; @@ -1422,7 +1460,9 @@ openerp.point_of_sale = function(db) { App.getProductByEAN = function(ean, allPackages, allProducts) { 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;}); @@ -1489,24 +1529,20 @@ openerp.point_of_sale = function(db) { codeNumbers.push(e.keyCode - 48); lastTimeStamp = new Date().getTime(); if (codeNumbers.length == 13) { + console.log('found code:', codeNumbers.join('')); + // a barcode reader - if (!checkEan(codeNumbers)) { + if (!App.checkEan(codeNumbers)) { // 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" ); - return; - }, - } }); } var selectedOrder = self.shop.get('selectedOrder'); - var scannedProductModel = getProductByEAN(codeNumbers.join('')); + var scannedProductModel = App.getProductByEAN(codeNumbers.join(''),allPackages,allProducts); if (scannedProductModel === undefined) { // product not recognized, raise warning $(QWeb.render('pos-scan-warning')).dialog({ @@ -1514,12 +1550,13 @@ openerp.point_of_sale = function(db) { height:220, modal: true, title: "Warning", + /* buttons: { "OK": function() { $( this ).dialog( "close" ); return; }, - } + }*/ }); } else { selectedOrder.addProduct(new Product(scannedProductModel)); @@ -1556,7 +1593,7 @@ openerp.point_of_sale = function(db) { return App; })(); - db.point_of_sale.SynchNotification = db.web.OldWidget.extend({ + session.point_of_sale.SynchNotification = session.web.OldWidget.extend({ template: "pos-synch-notification", init: function() { this._super.apply(this, arguments); @@ -1573,8 +1610,8 @@ openerp.point_of_sale = function(db) { on_synch: function() {} }); - db.web.client_actions.add('pos.ui', 'db.point_of_sale.PointOfSale'); - db.point_of_sale.PointOfSale = db.web.OldWidget.extend({ + session.web.client_actions.add('pos.ui', 'session.point_of_sale.PointOfSale'); + session.point_of_sale.PointOfSale = session.web.OldWidget.extend({ init: function() { this._super.apply(this, arguments); @@ -1587,7 +1624,7 @@ openerp.point_of_sale = function(db) { var self = this; return pos.ready.then(_.bind(function() { this.renderElement(); - this.synch_notification = new db.point_of_sale.SynchNotification(this); + this.synch_notification = new session.point_of_sale.SynchNotification(this); this.synch_notification.replace($('.oe_pos_synch-notification', this.$element)); this.synch_notification.on_synch.add(_.bind(pos.flush, pos)); @@ -1599,10 +1636,10 @@ openerp.point_of_sale = function(db) { }); pos.app = new App(self.$element); - db.webclient.set_content_full_screen(true); + session.webclient.set_content_full_screen(true); if (pos.store.get('account.bank.statement').length === 0) - return new db.web.Model("ir.model.data").get_func("search_read")([['name', '=', 'action_pos_open_statement']], ['res_id']).pipe( + return new session.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; @@ -1650,7 +1687,7 @@ openerp.point_of_sale = function(db) { // 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( + return new session.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; @@ -1660,7 +1697,7 @@ openerp.point_of_sale = function(db) { }, this)); }, destroy: function() { - db.webclient.set_content_full_screen(false); + session.webclient.set_content_full_screen(false); pos = undefined; this._super(); } From e81d9fd69ea855078a49623c799d3e358f3d516d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20van=20der=20Essen?= Date: Mon, 16 Apr 2012 18:12:03 +0200 Subject: [PATCH 43/54] Dummy asynchronous storage as a preparation for a proper db bzr revid: fva@openerp.com-20120416161203-3dd0oc9ywe5pvard --- addons/point_of_sale/static/src/js/pos.js | 151 ++++++++++++++++------ 1 file changed, 111 insertions(+), 40 deletions(-) diff --git a/addons/point_of_sale/static/src/js/pos.js b/addons/point_of_sale/static/src/js/pos.js index bee61369665..9e8c962d62a 100644 --- a/addons/point_of_sale/static/src/js/pos.js +++ b/addons/point_of_sale/static/src/js/pos.js @@ -1,26 +1,30 @@ - (function(){ - window.asyncLocalStorageDelay = 0; - window.asyncLocalStorage = { + window.asyncStorageDelay = 0; + window.asyncStorage = { length: function(){ var d = $.Deferred(); setTimeout(function(){ d.resolve(localStorage.length); - },asyncLocalStorageDelay); + },asyncStorageDelay); return d.promise(); }, key: function(index){ var d = $.Deferred(); setTimeout(function(){ d.resolve(localStorage.key(index)); - },asyncLocalStorageDelay); + },asyncStorageDelay); return d.promise(); }, getItem: function(key){ var d = $.Deferred(); setTimeout(function(){ - d.resolve(localStorage.getItem(key)); - },asyncLocalStorageDelay); + var val = localStorage.getItem(key); + if(val){ + d.resolve(val); + }else{ + d.reject(); + } + },asyncStorageDelay); return d.promise(); }, setItem: function(key,data){ @@ -28,7 +32,7 @@ setTimeout(function(){ localStorage.setItem(key,data); d.resolve(); - },asyncLocalStorageDelay); + },asyncStorageDelay); return d.promise(); }, removeItem: function(key){ @@ -36,7 +40,7 @@ setTimeout(function(){ localStorage.removeItem(key); d.resolve(); - },asyncLocalStorageDelay); + },asyncStorageDelay); return d.promise(); }, clear: function(){ @@ -44,9 +48,97 @@ setTimeout(function(){ localStorage.clear(); d.resolve(); - },asyncLocalStorageDelay); + },asyncStorageDelay); return d.promise(); - } + }, + + // A collection holds a list of objects that can be stored and searched on the disc asynchronously. + // collection : the name of the collection. + // searchable_fields : a list of the name of the object's field that can be searched and matched + collection_new: function(collection, searchable_fields){ + var col_obj = { + 'name': collection, + 'searchable_fields': searchable_fields || [], + 'data': [], + }; + return asyncStorage.setItem('collection_'+collection, JSON.stringify(col_obj)); + }, + + // returns a defered that fetches one element of the collection that has a field of value equal to name + // the defered fails if there is no such element + collection_get: function(collection, field, name){ + var d = $.Deferred(); + + setTimeout(function(){ + var col_str = localStorage.getItem('collection_'+collection) + var col_obj = JSON.parse(col_str); + var data = col_obj.data; + for(var i = 0; i < data.length; i++){ + var elem = data[i]; + if (elem[field] && elem[field] === name){ + d.resolve(elem); + return d.promise(); + } + } + d.reject(); + },asyncStorageDelay); + + return d.promise(); + }, + + // asynchronously add an element to the collection + collection_add: function(collection, elem){ + var d = $.Deferred(); + + var col_str = localStorage.getItem('collection_'+collection); + var col_obj = JSON.parse(col_str); + + col_obj.data.push(elem); + col_str = JSON.stringify(col_obj); + + localStorage.setItem('collection_'+collection, col_str) + + setTimeout(function(){ + d.resolve(); + },asyncStorageDelay); + + return d.promise(); + }, + + // returns a deffered that provides a list of element in the collection + // matching the patterns * + // patterns is a dictionary of {'field':'pattern'}. + // an object is said to match the patterns if one of his field has a related field in the + // dictionary with a value that contains the 'pattern'. the list may be empty + collection_find: function(collection, patterns){ + var d = $.Deferred(); + + var col_str = localStorage.getItem('collection_'+collection) + var col_obj = JSON.parse(col_str); + var data = col_obj.data; + var results = []; + var lowerpatterns = {}; + for (field in patterns){ + lowerpatterns[field] = patterns[field].toLowerCase(); + } + + + for(var i = 0; i < data.length; i++){ + var elem = data[i]; + for(field in lowerpatterns){ + if(elem[field] && (''+elem[field]).toLowerCase().indexOf(lowerpatterns[field]) >= 0){ + results.push(elem); + break; + } + } + } + + setTimeout(function(){ + d.resolve(results); + },asyncStorageDelay); + + return d.promise(); + }, }; })(); @@ -96,40 +188,19 @@ openerp.point_of_sale = function(session) { this.data[key] = value; localStorage['oe_pos_' + key] = JSON.stringify(value); }, - }); - - var SqlStore = session.web.Class.extend({ - init: function(){ - this.data = {}; + collection_new: function(collection, searchable_fields){ + return asyncStorage.collection_new(collection,searchable_fields); }, - get: function(key, _default){ - if (this.data[key] === undefined) { - var stored = localStorage['oe_pos_' + key]; - if (stored){ - this.data[key] = JSON.parse(stored); - }else{ - console.log('Store get: ',key, 'default: ',_default); - return _default; - } - } - console.log('Store get: ',key, 'value: ', this.data[key]); - return this.data[key]; - }, - set: function(key, value){ - console.log('Store set: ',key,' value: ',value); - this.data[key] = value; - localStorage['oe_pos_' + key] = JSON.stringify(value); - }, - collection_new: function(collection, elem_name_field, elem_name_func){ - }, - collection_get: function(collection, elem_name_field, elem_name){ + collection_get: function(collection, field, name){ + return asyncStorage.collection_get(collection,field,name); }, collection_add: function(collection, elem){ + return asyncStorage.collection_add(collection,elem); }, - collection_find: function(collection, elem_name_field, elem_name_pattern){ - }, + collection_find: function(collection, patterns){ + return asyncStorage.collection_find(collection,patterns); + } }); - /* Gets all the necessary data from the OpenERP web client (session, shop data etc.) From 3a10debd1fb4ef7b3f1d17d045f615e714b9aff3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20van=20der=20Essen?= Date: Wed, 18 Apr 2012 16:11:44 +0200 Subject: [PATCH 44/54] [IMP] action bar bzr revid: fva@openerp.com-20120418141144-g2653zv5vn33xfm5 --- addons/point_of_sale/static/src/css/pos.css | 93 +++++++++++++++++++-- addons/point_of_sale/static/src/js/pos.js | 78 +++++++++++++++-- addons/point_of_sale/static/src/xml/pos.xml | 20 +++++ 3 files changed, 179 insertions(+), 12 deletions(-) diff --git a/addons/point_of_sale/static/src/css/pos.css b/addons/point_of_sale/static/src/css/pos.css index 47ea23f202b..b3aaded127b 100644 --- a/addons/point_of_sale/static/src/css/pos.css +++ b/addons/point_of_sale/static/src/css/pos.css @@ -1,4 +1,6 @@ - +body{ + overflow: hidden; +} .point-of-sale { padding: 0; margin: 0; @@ -116,9 +118,15 @@ bottom: 0; } .point-of-sale #leftpane { - height: 100%; + -webkit-box-sizing:border-box; + position:absolute; + left:0; + width:440px; + top:0px; + bottom:105px; + /*height: 87%; width: 440px; - position: relative; + position: relative;*/ border-right: solid 1px #afafb6; background-color: white; } @@ -249,10 +257,10 @@ .point-of-sale #rightpane { position: absolute; top: 0; - bottom: 0; - left: 441px; + bottom: 105px; + left: 440px; + //height:100%; right: 0; - height: 100%; vertical-align: top; } .point-of-sale #rightpane header { @@ -487,6 +495,79 @@ } } +.point-of-sale .pos-actionbar{ + position:absolute; + left: 0; + bottom: 0px; + height: 105px; + width: 100%; + margin: 0; + background: #f5f5f5; /*#ebebeb;*/ + border-top: solid 1px #afafb6; +} + +.point-of-sale .pos-actionbar ul{ + list-style: none; +} + +.point-of-sale .pos-actionbar-left-region{ + height: 100%; + width: 434px; + margin: 0px; + padding-left:3px; + padding-right:3px; + border-right: solid 1px #dfdfdf; + float: left; +} + +.point-of-sale .pos-actionbar-right-region{ + height: 100%; + margin: 0px; + padding-left:3px; + padding-right:3px; + overflow:hidden; +} + +.point-of-sale .pos-actionbar .button{ + width: 90px; + height: 90px; + text-align:center; + margin:3px; + margin-top:6px; + line-height: 90px; + float:left; + + font-size: 14px; + font-weight: bold; + + cursor: pointer; + + border: 1px solid #cacaca; + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + border-radius: 4px; + + background: #e2e2e2; + background: -moz-linear-gradient(#f0f0f0, #e2e2e2); + 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 .pos-actionbar .button:hover { + color: white; + background: #7f82ac; + border: 1px solid #7f82ac; + background: -moz-linear-gradient(#9d9fc5, #7f82ac); + background: -webkit-gradient(linear, left top, left bottom, from(#9d9fc5), to(#7f82ac)); + + -webkit-transition-property: background, border; + -webkit-transition-duration: 0.2s; + -webkit-transition-timing-function: ease-out; +} + +.point-of-sale .pos-actionbar .button.rightalign{ + float:right; +} + /* Onscreen Keyboard http://net.tutsplus.com/tutorials/javascript-ajax/creating-a-keyboard-with-css-and-jquery/ */ .point-of-sale .keyboard_frame{ diff --git a/addons/point_of_sale/static/src/js/pos.js b/addons/point_of_sale/static/src/js/pos.js index 9e8c962d62a..6fde28e0941 100644 --- a/addons/point_of_sale/static/src/js/pos.js +++ b/addons/point_of_sale/static/src/js/pos.js @@ -177,14 +177,14 @@ openerp.point_of_sale = function(session) { if (stored) this.data[key] = JSON.parse(stored); else - console.log('Store get: ',key, 'default: ',_default); + //console.log('Store get: ',key, 'default: ',_default); return _default; } - console.log('Store get: ',key, 'value: ',this.data[key]); + //console.log('Store get: ',key, 'value: ',this.data[key]); return this.data[key]; }, set: function(key, value) { - console.log('Store set: ',key,' value: ',value); + //console.log('Store set: ',key,' value: ',value); this.data[key] = value; localStorage['oe_pos_' + key] = JSON.stringify(value); }, @@ -540,7 +540,7 @@ openerp.point_of_sale = function(session) { existing.incrementQuantity(); } else { var line = new Orderline(product.toJSON()); - console.log('orderline:',line,product.toJSON()); + //console.log('orderline:',line,product.toJSON()); this.get('orderLines').add(line); line.bind('killme', function() { this.get('orderLines').remove(line); @@ -1244,11 +1244,67 @@ openerp.point_of_sale = function(session) { } }); - var ToolbarWidget = session.web.Widget.extend({ - tagName: 'div', + var ActionButtonWidget = session.web.Widget.extend({ + template:'pos-action-button', + init: function(parent, options){ + this._super(parent, options); + this.label = options.label || 'button'; + this.rightalign = options.rightalign || false; + }, + }); + + var ActionbarWidget = session.web.Widget.extend({ + template:'pos-actionbar', init: function(parent, options){ this._super(parent,options); + this.left_button_list = []; + this.right_button_list = []; }, + start: function(){ + console.log('hello world!'); + 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(); + } + 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'); + } + var button = new ActionButtonWidget(this,button_options); + button_list.push(button); + button.appendTo($button_list); + }else{ + for(var i = 1; i < arguments.length; i++){ + this.addNewButton(position,arguments[i]); + } + } + return this; + } + /* + renderElement: function() { + //this.$element.html(this.template_fct()); + },*/ }); // A Widget that displays an onscreen keyboard. @@ -1545,6 +1601,16 @@ openerp.point_of_sale = function(session) { this.onscreenKeyboard = new OnscreenKeyboardWidget(null,{keyboard_model:'simple'}); this.onscreenKeyboard.appendTo($(".point-of-sale #content")); + + this.actionBar = new ActionbarWidget(null); + this.actionBar.appendTo($(".point-of-sale #content")); + + this.actionBar.addNewButton('left',{'label':'foobar'}); + this.actionBar.addNewButton('left',{'label':'test'}); + this.actionBar.addNewButton('left',{'label':'kikoo', rightalign:true}); + + this.actionBar.addNewButton('right',{'label':'boo'}); + this.actionBar.addNewButton('right',{'label':'bah', rightalign:true}); }; //returns true if the code is a valid EAN codebar number by checking the control digit. diff --git a/addons/point_of_sale/static/src/xml/pos.xml b/addons/point_of_sale/static/src/xml/pos.xml index b772b9ca2e5..bd0ecb5b65a 100644 --- a/addons/point_of_sale/static/src/xml/pos.xml +++ b/addons/point_of_sale/static/src/xml/pos.xml @@ -294,6 +294,26 @@ + +
+
    + +
+ +
    + +
+
+
+ +
  • + +
  • +
    From dd42fc4a033f23910114ff584a832b90a1295fd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20van=20der=20Essen?= Date: Wed, 18 Apr 2012 17:53:14 +0200 Subject: [PATCH 45/54] [IMP] Images for the action bar bzr revid: fva@openerp.com-20120418155314-s9i15jzid5xuv9j1 --- addons/point_of_sale/static/src/css/pos.css | 9 +++++++- .../src/img/icons/png48/face-monkey.png | Bin 0 -> 3993 bytes .../static/src/img/icons/png48/go-next.png | Bin 0 -> 2323 bytes .../src/img/icons/png48/help-browser.png | Bin 0 -> 3757 bytes addons/point_of_sale/static/src/js/pos.js | 20 ++++++++++++++++-- addons/point_of_sale/static/src/xml/pos.xml | 12 ++++++++++- 6 files changed, 37 insertions(+), 4 deletions(-) create mode 100644 addons/point_of_sale/static/src/img/icons/png48/face-monkey.png create mode 100644 addons/point_of_sale/static/src/img/icons/png48/go-next.png create mode 100644 addons/point_of_sale/static/src/img/icons/png48/help-browser.png diff --git a/addons/point_of_sale/static/src/css/pos.css b/addons/point_of_sale/static/src/css/pos.css index b3aaded127b..3769417995b 100644 --- a/addons/point_of_sale/static/src/css/pos.css +++ b/addons/point_of_sale/static/src/css/pos.css @@ -534,7 +534,8 @@ body{ text-align:center; margin:3px; margin-top:6px; - line-height: 90px; + //line-height: 90px; + //vertical-align:center; float:left; font-size: 14px; @@ -552,6 +553,12 @@ body{ 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 .pos-actionbar .button .label{ + margin-top: 37px; +} +.point-of-sale .pos-actionbar .button .icon{ + margin-top: 10px; +} .point-of-sale .pos-actionbar .button:hover { color: white; background: #7f82ac; diff --git a/addons/point_of_sale/static/src/img/icons/png48/face-monkey.png b/addons/point_of_sale/static/src/img/icons/png48/face-monkey.png new file mode 100644 index 0000000000000000000000000000000000000000..1a57c9e06962401c3aa0d3940a9cb359822e59f9 GIT binary patch literal 3993 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 literal 0 HcmV?d00001 diff --git a/addons/point_of_sale/static/src/img/icons/png48/go-next.png b/addons/point_of_sale/static/src/img/icons/png48/go-next.png new file mode 100644 index 0000000000000000000000000000000000000000..bcd343dde9432a6fb396a888e6ac4ae300579a9e GIT binary patch literal 2323 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 XbyEqQZ*wax00000NkvXXu0mjfk-@L< literal 0 HcmV?d00001 diff --git a/addons/point_of_sale/static/src/js/pos.js b/addons/point_of_sale/static/src/js/pos.js index 6fde28e0941..0c1fa6da98c 100644 --- a/addons/point_of_sale/static/src/js/pos.js +++ b/addons/point_of_sale/static/src/js/pos.js @@ -1250,6 +1250,10 @@ openerp.point_of_sale = function(session) { this._super(parent, options); this.label = options.label || 'button'; this.rightalign = options.rightalign || false; + if(options.icon){ + this.icon = options.icon; + this.template = 'pos-action-button-with-icon'; + } }, }); @@ -1605,12 +1609,24 @@ openerp.point_of_sale = function(session) { this.actionBar = new ActionbarWidget(null); this.actionBar.appendTo($(".point-of-sale #content")); - this.actionBar.addNewButton('left',{'label':'foobar'}); + this.actionBar.addNewButton('left',{ + label : 'Aide', + icon : '/point_of_sale/static/src/img/icons/png48/help-browser.png', + }); this.actionBar.addNewButton('left',{'label':'test'}); this.actionBar.addNewButton('left',{'label':'kikoo', rightalign:true}); this.actionBar.addNewButton('right',{'label':'boo'}); - this.actionBar.addNewButton('right',{'label':'bah', rightalign:true}); + this.actionBar.addNewButton('right',{ + label : 'Payer', + rightalign : true, + icon : '/point_of_sale/static/src/img/icons/png48/go-next.png', + }); + this.actionBar.addNewButton('right',{ + label : 'Ook Ook', + rightalign : true, + icon : '/point_of_sale/static/src/img/icons/png48/face-monkey.png', + }); }; //returns true if the code is a valid EAN codebar number by checking the control digit. diff --git a/addons/point_of_sale/static/src/xml/pos.xml b/addons/point_of_sale/static/src/xml/pos.xml index bd0ecb5b65a..fc8df431bef 100644 --- a/addons/point_of_sale/static/src/xml/pos.xml +++ b/addons/point_of_sale/static/src/xml/pos.xml @@ -311,7 +311,17 @@
  • - +
    + +
    +
  • +
    + +
  • +
    + +
    +
  • - +
    From 8a9ce7b98e9a5889ee11aedf459a912a13d0eddb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20van=20der=20Essen?= Date: Tue, 24 Apr 2012 16:02:57 +0200 Subject: [PATCH 47/54] [PoS] Removed pos global variable bzr revid: fva@openerp.com-20120424140257-jn0fadimt6o62qhc --- addons/point_of_sale/static/src/js/pos.js | 26 ++++++++++++++++------- 1 file changed, 18 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 fa3deb32562..cbcfbd69eba 100644 --- a/addons/point_of_sale/static/src/js/pos.js +++ b/addons/point_of_sale/static/src/js/pos.js @@ -4,12 +4,20 @@ openerp.point_of_sale = function(instance) { var namespace = instance.point_of_sale; - var posmodel; //the global point of sale instance - var QWeb = instance.web.qweb; - var qweb_template = function(template) { - return function(ctx) { + var qweb_template = function(template,posmodel){ + return function(ctx){ + if(!posmodel){ //this is a huge hack that needs to be removed ... TODO + var HackPosModel = Backbone.Model.extend({ + initialize:function(){ + this.set({ + 'currency': {symbol: '$', position: 'after'}, + }); + }, + }); + posmodel = new HackPosModel(); + } return QWeb.render(template, _.extend({}, ctx,{ 'currency': posmodel.get('currency'), 'format_amount': function(amount) { @@ -22,6 +30,7 @@ openerp.point_of_sale = function(instance) { })); }; }; + var _t = instance.web._t; var LocalStorageDAO = instance.web.Class.extend({ @@ -1055,9 +1064,10 @@ openerp.point_of_sale = function(instance) { this._super(parent); this.model = options.model; this.shop = options.shop; - this.user = posmodel.get('user'); - this.company = posmodel.get('company'); - this.shop_obj = posmodel.get('shop'); + this.posmodel = options.posmodel; + this.user = this.posmodel.get('user'); + this.company = this.posmodel.get('company'); + this.shop_obj = this.posmodel.get('shop'); }, start: function() { this.shop.bind('change:selectedOrder', this.changeSelectedOrder, this); @@ -1434,6 +1444,7 @@ openerp.point_of_sale = function(instance) { this.paymentView.start(); this.receiptView = new ReceiptWidget(null, { shop: this.shop, + posmodel: this.posmodel, }); this.receiptView.replace($('#receipt-screen')); this.stepSwitcher = new StepSwitcher(this, {shop: this.shop}); @@ -1717,7 +1728,6 @@ openerp.point_of_sale = function(instance) { this.posmodel = new PosModel(this.session); - posmodel = this.posmodel; }, start: function() { var self = this; From 4efe726fdf3004f44c40f434fca9ca7d40a3ce6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20van=20der=20Essen?= Date: Tue, 24 Apr 2012 18:25:46 +0200 Subject: [PATCH 48/54] [POS] Split the pos code into files bzr revid: fva@openerp.com-20120424162546-3w604gxmfb0ezt47 --- addons/point_of_sale/__openerp__.py | 7 +- addons/point_of_sale/static/src/js/pos.js | 160 ++- .../point_of_sale/static/src/js/pos_main.js | 15 + .../point_of_sale/static/src/js/pos_models.js | 771 +++++++++++++ .../static/src/js/pos_widgets.js | 1023 +++++++++++++++++ 5 files changed, 1891 insertions(+), 85 deletions(-) create mode 100644 addons/point_of_sale/static/src/js/pos_main.js create mode 100644 addons/point_of_sale/static/src/js/pos_models.js create mode 100644 addons/point_of_sale/static/src/js/pos_widgets.js diff --git a/addons/point_of_sale/__openerp__.py b/addons/point_of_sale/__openerp__.py index 911ccf24bb5..f64141aa9fc 100644 --- a/addons/point_of_sale/__openerp__.py +++ b/addons/point_of_sale/__openerp__.py @@ -82,7 +82,12 @@ 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_models.js', + 'static/src/js/pos_widgets.js', + 'static/src/js/pos_main.js' + ], 'css': ['static/src/css/pos.css'], 'qweb': ['static/src/xml/pos.xml'], } diff --git a/addons/point_of_sale/static/src/js/pos.js b/addons/point_of_sale/static/src/js/pos.js index cbcfbd69eba..e7566256648 100644 --- a/addons/point_of_sale/static/src/js/pos.js +++ b/addons/point_of_sale/static/src/js/pos.js @@ -30,10 +30,8 @@ openerp.point_of_sale = function(instance) { })); }; }; - - var _t = instance.web._t; - var LocalStorageDAO = instance.web.Class.extend({ + namespace.LocalStorageDAO = instance.web.Class.extend({ add_operation: function(operation) { var self = this; return $.async_when().pipe(function() { @@ -79,11 +77,11 @@ openerp.point_of_sale = function(instance) { /* Gets all the necessary data from the OpenERP web client (instance, shop data etc.) */ - var PosModel = Backbone.Model.extend({ + namespace.PosModel = Backbone.Model.extend({ initialize: function(session, attributes) { Backbone.Model.prototype.initialize.call(this, attributes); var self = this; - this.dao = new LocalStorageDAO(); + this.dao = new namespace.LocalStorageDAO(); this.ready = $.Deferred(); this.flush_mutex = new $.Mutex(); this.build_tree = _.bind(this.build_tree, this); @@ -257,25 +255,18 @@ openerp.point_of_sale = function(instance) { --- */ - var CashRegister = Backbone.Model.extend({ + namespace.CashRegister = Backbone.Model.extend({ }); - var CashRegisterCollection = Backbone.Collection.extend({ - model: CashRegister, + namespace.CashRegisterCollection = Backbone.Collection.extend({ + model: namespace.CashRegister, }); - var Product = Backbone.Model.extend({ + namespace.Product = Backbone.Model.extend({ }); - var ProductCollection = Backbone.Collection.extend({ - model: Product, - }); - - var Category = Backbone.Model.extend({ - }); - - var CategoryCollection = Backbone.Collection.extend({ - model: Category, + namespace.ProductCollection = Backbone.Collection.extend({ + model: namespace.Product, }); /* @@ -284,7 +275,7 @@ openerp.point_of_sale = function(instance) { To add more of the same product, just update the quantity accordingly. The Order also contains payment information. */ - var Orderline = Backbone.Model.extend({ + namespace.Orderline = Backbone.Model.extend({ defaults: { quantity: 1, list_price: 0, @@ -365,12 +356,12 @@ openerp.point_of_sale = function(instance) { }, }); - var OrderlineCollection = Backbone.Collection.extend({ - model: Orderline, + namespace.OrderlineCollection = Backbone.Collection.extend({ + model: namespace.Orderline, }); // Every PaymentLine has all the attributes of the corresponding CashRegister. - var Paymentline = Backbone.Model.extend({ + namespace.Paymentline = Backbone.Model.extend({ defaults: { amount: 0, }, @@ -391,11 +382,11 @@ openerp.point_of_sale = function(instance) { }, }); - var PaymentlineCollection = Backbone.Collection.extend({ - model: Paymentline, + namespace.PaymentlineCollection = Backbone.Collection.extend({ + model: namespace.Paymentline, }); - var Order = Backbone.Model.extend({ + namespace.Order = Backbone.Model.extend({ defaults:{ validated: false, step: 'products', @@ -403,9 +394,9 @@ openerp.point_of_sale = function(instance) { initialize: function(attributes){ Backbone.Model.prototype.initialize.apply(this, arguments); this.set({ - creationDate: new Date, - orderLines: new OrderlineCollection, - paymentLines: new PaymentlineCollection, + creationDate: new Date(), + orderLines: new namespace.OrderlineCollection(), + paymentLines: new namespace.PaymentlineCollection(), name: "Order " + this.generateUniqueId(), }); this.posmodel = attributes.posmodel; //TODO put that in set and remember to use 'get' to read it ... @@ -431,7 +422,7 @@ openerp.point_of_sale = function(instance) { } else { var attr = product.toJSON(); attr.posmodel = this.posmodel; - var line = new Orderline(attr); + var line = new namespace.Orderline(attr); this.get('orderLines').add(line); line.bind('killme', function() { this.get('orderLines').remove(line); @@ -440,7 +431,7 @@ openerp.point_of_sale = function(instance) { }, addPaymentLine: function(cashRegister) { var newPaymentline; - newPaymentline = new Paymentline(cashRegister); + newPaymentline = new namespace.Paymentline(cashRegister); /* TODO: Should be 0 for cash-like accounts */ newPaymentline.set({ amount: this.getDueLeft() @@ -498,24 +489,24 @@ openerp.point_of_sale = function(instance) { }, }); - var OrderCollection = Backbone.Collection.extend({ - model: Order, + namespace.OrderCollection = Backbone.Collection.extend({ + model: namespace.Order, }); - var Shop = Backbone.Model.extend({ + namespace.Shop = Backbone.Model.extend({ initialize: function(attributes) { var self = this; this.set({ - orders: new OrderCollection(), - products: new ProductCollection(), + orders: new namespace.OrderCollection(), + products: new namespace.ProductCollection(), }); this.posmodel = attributes.posmodel; this.set({ - cashRegisters: new CashRegisterCollection(this.posmodel.get('bank_statements')), + cashRegisters: new namespace.CashRegisterCollection(this.posmodel.get('bank_statements')), }); return (this.get('orders')).bind('remove', _.bind( function(removedOrder) { if ((this.get('orders')).isEmpty()) { - this.addAndSelectOrder(new Order({posmodel: self.posmodel})); + this.addAndSelectOrder(new namespace.Order({posmodel: self.posmodel})); } if ((this.get('selectedOrder')) === removedOrder) { return this.set({ @@ -536,7 +527,7 @@ openerp.point_of_sale = function(instance) { The numpad handles both the choice of the property currently being modified (quantity, price or discount) and the edition of the corresponding numeric value. */ - var NumpadState = Backbone.Model.extend({ + namespace.NumpadState = Backbone.Model.extend({ defaults: { buffer: "0", mode: "quantity" @@ -604,10 +595,10 @@ openerp.point_of_sale = function(instance) { Views --- */ - var NumpadWidget = instance.web.OldWidget.extend({ + namespace.NumpadWidget = instance.web.OldWidget.extend({ init: function(parent, options) { this._super(parent); - this.state = new NumpadState(); + this.state = new namespace.NumpadState(); }, start: function() { this.state.bind('change:mode', this.changedMode, this); @@ -641,7 +632,7 @@ openerp.point_of_sale = function(instance) { /* Gives access to the payment methods (aka. 'cash registers') */ - var PaypadWidget = instance.web.OldWidget.extend({ + namespace.PaypadWidget = instance.web.OldWidget.extend({ init: function(parent, options) { this._super(parent); this.shop = options.shop; @@ -666,13 +657,14 @@ openerp.point_of_sale = function(instance) { renderElement: function() { this.$element.empty(); return (this.shop.get('cashRegisters')).each(_.bind( function(cashRegister) { - var button = new PaymentButtonWidget(); + var button = new namespace.PaymentButtonWidget(); button.model = cashRegister; button.appendTo(this.$element); }, this)); } }); - var PaymentButtonWidget = instance.web.OldWidget.extend({ + + namespace.PaymentButtonWidget = instance.web.OldWidget.extend({ template_fct: qweb_template('pos-payment-button-template'), renderElement: function() { this.$element.html(this.template_fct({ @@ -690,7 +682,7 @@ openerp.point_of_sale = function(instance) { It should be possible to go back to any step as long as step 3 hasn't been completed. Modifying an order after validation shouldn't be allowed. */ - var StepSwitcher = instance.web.OldWidget.extend({ + namespace.StepSwitcher = instance.web.OldWidget.extend({ init: function(parent, options) { this._super(parent); this.shop = options.shop; @@ -716,7 +708,7 @@ openerp.point_of_sale = function(instance) { /* Shopping carts. */ - var OrderlineWidget = instance.web.OldWidget.extend({ + namespace.OrderlineWidget = instance.web.OldWidget.extend({ tagName: 'tr', template_fct: qweb_template('pos-orderline-template'), init: function(parent, options) { @@ -757,7 +749,7 @@ openerp.point_of_sale = function(instance) { on_selected: function() {}, }); - var OrderWidget = instance.web.OldWidget.extend({ + namespace.OrderWidget = instance.web.OldWidget.extend({ init: function(parent, options) { this._super(parent); this.shop = options.shop; @@ -796,7 +788,7 @@ openerp.point_of_sale = function(instance) { this.currentOrderLines.bind('remove', this.renderElement, this); }, addLine: function(newLine) { - var line = new OrderlineWidget(null, { + var line = new namespace.OrderlineWidget(null, { model: newLine, order: this.shop.get('selectedOrder') }); @@ -818,7 +810,7 @@ openerp.point_of_sale = function(instance) { renderElement: function() { this.$element.empty(); this.currentOrderLines.each(_.bind( function(orderLine) { - var line = new OrderlineWidget(null, { + var line = new namespace.OrderlineWidget(null, { model: orderLine, order: this.shop.get('selectedOrder') }); @@ -842,7 +834,7 @@ openerp.point_of_sale = function(instance) { /* "Products" step. */ - var CategoryWidget = instance.web.OldWidget.extend({ + namespace.CategoryWidget = instance.web.OldWidget.extend({ init: function(parent, options){ this._super(parent,options.element_id); this.posmodel = options.posmodel; @@ -882,7 +874,7 @@ openerp.point_of_sale = function(instance) { on_change_category: function(id) {}, }); - var ProductWidget = instance.web.OldWidget.extend({ + namespace.ProductWidget = instance.web.OldWidget.extend({ tagName:'li', template_fct: qweb_template('pos-product-template'), init: function(parent, options) { @@ -905,7 +897,7 @@ openerp.point_of_sale = function(instance) { }, }); - var ProductListWidget = instance.web.OldWidget.extend({ + namespace.ProductListWidget = instance.web.OldWidget.extend({ init: function(parent, options) { this._super(parent); this.model = options.model; @@ -915,7 +907,7 @@ openerp.point_of_sale = function(instance) { renderElement: function() { this.$element.empty(); (this.shop.get('products')).each(_.bind( function(product) { - var p = new ProductWidget(null, { + var p = new namespace.ProductWidget(null, { model: product, shop: this.shop }); @@ -927,7 +919,7 @@ openerp.point_of_sale = function(instance) { /* "Payment" step. */ - var PaymentlineWidget = instance.web.OldWidget.extend({ + namespace.PaymentlineWidget = instance.web.OldWidget.extend({ tagName: 'tr', template_fct: qweb_template('pos-paymentline-template'), init: function(parent, options) { @@ -962,7 +954,7 @@ openerp.point_of_sale = function(instance) { }, }); - var PaymentWidget = instance.web.OldWidget.extend({ + namespace.PaymentWidget = instance.web.OldWidget.extend({ init: function(parent, options) { this._super(parent); this.model = options.model; @@ -1011,7 +1003,7 @@ openerp.point_of_sale = function(instance) { this.renderElement(); }, addPaymentLine: function(newPaymentLine) { - var x = new PaymentlineWidget(null, { + var x = new namespace.PaymentlineWidget(null, { model: newPaymentLine }); x.on_delete.add(_.bind(this.deleteLine, this, x)); @@ -1059,7 +1051,7 @@ openerp.point_of_sale = function(instance) { }, }); - var ReceiptWidget = instance.web.OldWidget.extend({ + namespace.ReceiptWidget = instance.web.OldWidget.extend({ init: function(parent, options) { this._super(parent); this.model = options.model; @@ -1103,7 +1095,7 @@ openerp.point_of_sale = function(instance) { }, }); - var OrderButtonWidget = instance.web.OldWidget.extend({ + namespace.OrderButtonWidget = instance.web.OldWidget.extend({ tagName: 'li', template_fct: qweb_template('pos-order-selector-button-template'), init: function(parent, options) { @@ -1143,7 +1135,7 @@ openerp.point_of_sale = function(instance) { } }); - var ActionButtonWidget = instance.web.Widget.extend({ + namespace.ActionButtonWidget = instance.web.Widget.extend({ template:'pos-action-button', init: function(parent, options){ this._super(parent, options); @@ -1156,7 +1148,7 @@ openerp.point_of_sale = function(instance) { }, }); - var ActionbarWidget = instance.web.Widget.extend({ + namespace.ActionbarWidget = instance.web.Widget.extend({ template:'pos-actionbar', init: function(parent, options){ this._super(parent,options); @@ -1194,7 +1186,7 @@ openerp.point_of_sale = function(instance) { button_list = this.right_button_list; $button_list = $('.pos-actionbar-right-region'); } - var button = new ActionButtonWidget(this,button_options); + var button = new namespace.ActionButtonWidget(this,button_options); button_list.push(button); button.appendTo($button_list); }else{ @@ -1221,7 +1213,7 @@ openerp.point_of_sale = function(instance) { // // The widget is initially hidden. It can be shown with this.show(), and is // automatically shown when the input_selector gets focused. - var OnscreenKeyboardWidget = instance.web.Widget.extend({ + namespace.OnscreenKeyboardWidget = instance.web.Widget.extend({ tagName: 'div', init: function(parent, options){ @@ -1404,7 +1396,7 @@ openerp.point_of_sale = function(instance) { }, }); - var ShopWidget = instance.web.OldWidget.extend({ + namespace.ShopWidget = instance.web.OldWidget.extend({ init: function(parent, options) { this._super(parent); this.shop = options.shop; @@ -1414,46 +1406,46 @@ openerp.point_of_sale = function(instance) { $('button#neworder-button', this.$element).click(_.bind(this.createNewOrder, this)); (this.shop.get('orders')).bind('add', this.orderAdded, this); - (this.shop.get('orders')).add(new Order({'posmodel':this.posmodel})); - this.productListView = new ProductListWidget(null, { + (this.shop.get('orders')).add(new namespace.Order({'posmodel':this.posmodel})); + this.productListView = new namespace.ProductListWidget(null, { shop: this.shop }); this.productListView.$element = $("#products-screen-ol"); this.productListView.renderElement(); this.productListView.start(); - this.paypadView = new PaypadWidget(null, { + this.paypadView = new namespace.PaypadWidget(null, { shop: this.shop }); this.paypadView.$element = $('#paypad'); this.paypadView.renderElement(); this.paypadView.start(); - this.numpadView = new NumpadWidget(null); + this.numpadView = new namespace.NumpadWidget(null); this.numpadView.$element = $('#numpad'); this.numpadView.start(); - this.orderView = new OrderWidget(null, { + this.orderView = new namespace.OrderWidget(null, { shop: this.shop, }); this.orderView.$element = $('#current-order-content'); this.orderView.start(); - this.paymentView = new PaymentWidget(null, { + this.paymentView = new namespace.PaymentWidget(null, { shop: this.shop, posmodel: this.posmodel, }); this.paymentView.$element = $('#payment-screen'); this.paymentView.renderElement(); this.paymentView.start(); - this.receiptView = new ReceiptWidget(null, { + this.receiptView = new namespace.ReceiptWidget(null, { shop: this.shop, posmodel: this.posmodel, }); this.receiptView.replace($('#receipt-screen')); - this.stepSwitcher = new StepSwitcher(this, {shop: this.shop}); + this.stepSwitcher = new namespace.StepSwitcher(this, {shop: this.shop}); this.shop.bind('change:selectedOrder', this.changedSelectedOrder, this); this.changedSelectedOrder(); }, createNewOrder: function() { var newOrder; - newOrder = new Order({'posmodel': this.posmodel}); + newOrder = new namespace.Order({'posmodel': this.posmodel}); (this.shop.get('orders')).add(newOrder); this.shop.set({ selectedOrder: newOrder @@ -1461,7 +1453,7 @@ openerp.point_of_sale = function(instance) { }, orderAdded: function(newOrder) { var newOrderButton; - newOrderButton = new OrderButtonWidget(null, { + newOrderButton = new namespace.OrderButtonWidget(null, { order: newOrder, shop: this.shop }); @@ -1488,7 +1480,7 @@ openerp.point_of_sale = function(instance) { }, }); - var App = (function() { + namespace.App = (function() { function App($element, posmodel) { this.initialize($element, posmodel); @@ -1496,21 +1488,21 @@ openerp.point_of_sale = function(instance) { App.prototype.initialize = function($element, posmodel) { this.posmodel = posmodel; - this.shop = new Shop({'posmodel': posmodel}); - this.shopView = new ShopWidget(null, { + this.shop = new namespace.Shop({'posmodel': posmodel}); + this.shopView = new namespace.ShopWidget(null, { shop: this.shop, 'posmodel': posmodel, }); this.shopView.$element = $element; this.shopView.start(); - this.categoryView = new CategoryWidget(null, {element_id: 'products-screen-categories', posmodel: posmodel} ); + this.categoryView = new namespace.CategoryWidget(null, {element_id: 'products-screen-categories', posmodel: posmodel} ); this.categoryView.on_change_category.add_last(_.bind(this.category, this)); this.category(); - this.onscreenKeyboard = new OnscreenKeyboardWidget(null,{keyboard_model:'simple'}); + this.onscreenKeyboard = new namespace.OnscreenKeyboardWidget(null,{keyboard_model:'simple'}); this.onscreenKeyboard.appendTo($(".point-of-sale #content")); - this.actionBar = new ActionbarWidget(null); + this.actionBar = new namespace.ActionbarWidget(null); this.actionBar.appendTo($(".point-of-sale #content")); this.actionBar.addNewButton('left',{ @@ -1669,7 +1661,7 @@ openerp.point_of_sale = function(instance) { }*/ }); } else { - selectedOrder.addProduct(new Product(scannedProductModel)); + selectedOrder.addProduct(new namespace.Product(scannedProductModel)); } codeNumbers = []; @@ -1703,7 +1695,7 @@ openerp.point_of_sale = function(instance) { return App; })(); - instance.point_of_sale.SynchNotification = instance.web.OldWidget.extend({ + namespace.SynchNotification = instance.web.OldWidget.extend({ template: "pos-synch-notification", init: function() { this._super.apply(this, arguments); @@ -1722,18 +1714,18 @@ openerp.point_of_sale = function(instance) { instance.web.client_actions.add('pos.ui', 'instance.point_of_sale.POSWidget'); - instance.point_of_sale.POSWidget = instance.web.OldWidget.extend({ + namespace.POSWidget = instance.web.OldWidget.extend({ init: function() { this._super.apply(this, arguments); - this.posmodel = new PosModel(this.session); + this.posmodel = new namespace.PosModel(this.session); }, start: function() { var self = this; return self.posmodel.ready.then(_.bind(function() { this.renderElement(); - this.synch_notification = new instance.point_of_sale.SynchNotification(this); + this.synch_notification = new namespace.SynchNotification(this); this.synch_notification.replace($('.oe_pos_synch-notification', this.$element)); this.synch_notification.on_synch.add(_.bind(self.posmodel.flush, self.posmodel)); @@ -1744,7 +1736,7 @@ openerp.point_of_sale = function(instance) { self.try_close(); }); - self.posmodel.app = new App(self.$element, self.posmodel); + self.posmodel.app = new namespace.App(self.$element, self.posmodel); instance.webclient.set_content_full_screen(true); if (self.posmodel.get('bank_statements').length === 0) diff --git a/addons/point_of_sale/static/src/js/pos_main.js b/addons/point_of_sale/static/src/js/pos_main.js new file mode 100644 index 00000000000..38503e5cfb9 --- /dev/null +++ b/addons/point_of_sale/static/src/js/pos_main.js @@ -0,0 +1,15 @@ + +openerp.point_of_sale = function(instance) { + + instance.point_of_sale = {}; + + var module = instance.point_of_sale; + + pos_models(module,instance); // import pos_models.js + + pos_widgets(module,instance); // import pos_widgets.js + + instance.web.client_actions.add('pos.ui', 'instance.point_of_sale.POSWidget'); +}; + + diff --git a/addons/point_of_sale/static/src/js/pos_models.js b/addons/point_of_sale/static/src/js/pos_models.js new file mode 100644 index 00000000000..a3e60f676df --- /dev/null +++ b/addons/point_of_sale/static/src/js/pos_models.js @@ -0,0 +1,771 @@ +function pos_models(module, instance){ + var QWeb = instance.web.qweb; + + module.LocalStorageDAO = instance.web.Class.extend({ + add_operation: function(operation) { + var self = this; + return $.async_when().pipe(function() { + var tmp = self._get('oe_pos_operations', []); + var last_id = self._get('oe_pos_operations_sequence', 1); + tmp.push({'id': last_id, 'data': operation}); + self._set('oe_pos_operations', tmp); + self._set('oe_pos_operations_sequence', last_id + 1); + }); + }, + remove_operation: function(id) { + var self = this; + return $.async_when().pipe(function() { + var tmp = self._get('oe_pos_operations', []); + tmp = _.filter(tmp, function(el) { + return el.id !== id; + }); + self._set('oe_pos_operations', tmp); + }); + }, + get_operations: function() { + var self = this; + return $.async_when().pipe(function() { + return self._get('oe_pos_operations', []); + }); + }, + _get: function(key, default_) { + var txt = localStorage[key]; + if (! txt) + return default_; + return JSON.parse(txt); + }, + _set: function(key, value) { + localStorage[key] = JSON.stringify(value); + }, + }); + + var fetch = function(osvModel, fields, domain){ + var dataSetSearch = new instance.web.DataSetSearch(null, osvModel, {}, domain); + return dataSetSearch.read_slice(fields, 0); + }; + + /* + Gets all the necessary data from the OpenERP web client (instance, shop data etc.) + */ + module.PosModel = Backbone.Model.extend({ + initialize: function(session, attributes) { + Backbone.Model.prototype.initialize.call(this, attributes); + var self = this; + this.dao = new module.LocalStorageDAO(); + this.ready = $.Deferred(); + this.flush_mutex = new $.Mutex(); + this.build_tree = _.bind(this.build_tree, this); + this.session = session; + this.categories = {}; + this.set({ + 'nbr_pending_operations': 0, + 'currency': {symbol: '$', position: 'after'}, + 'shop': {}, + 'company': {}, + 'user': {} + }); + + var cat_def = fetch('pos.category', ['name', 'parent_id', 'child_id']) + .pipe(function(result){ + return self.set({'categories': result}); + }); + + var prod_def = fetch( + 'product.product', + ['name', 'list_price', 'pos_categ_id', 'taxes_id','product_image_small'], + [['pos_categ_id','!=', 'false']] + ).then(function(result){ + 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){ + return self.set({'bank_statements': result}); + }); + + var tax_def = fetch('account.tax', ['amount','price_include','type']) + .then(function(result){ + return self.set({'taxes': result}); + }); + + $.when(cat_def,prod_def,bank_def,tax_def,this.get_app_data(), this.flush()) + .pipe(_.bind(this.build_tree, this)); + }, + + get_app_data: function() { + var self = this; + return $.when(new instance.web.Model("sale.shop").get_func("search_read")([]).pipe(function(result) { + self.set({'shop': result[0]}); + var company_id = result[0]['company_id'][0]; + return new instance.web.Model("res.company").get_func("read")(company_id, ['currency_id', 'name', 'phone']).pipe(function(result) { + self.set({'company': result}); + var currency_id = result['currency_id'][0] + return new instance.web.Model("res.currency").get_func("read")([currency_id], + ['symbol', 'position']).pipe(function(result) { + self.set({'currency': result[0]}); + + }); + }); + }), new instance.web.Model("res.users").get_func("read")(this.session.uid, ['name']).pipe(function(result) { + self.set({'user': result}); + })); + }, + push_order: function(record) { + var self = this; + return this.dao.add_operation(record).pipe(function(){ + return self.flush(); + }); + }, + flush: function() { + return this.flush_mutex.exec(_.bind(function() { + return this._int_flush(); + }, this)); + }, + _int_flush : function() { + var self = this; + + this.dao.get_operations().pipe(function(operations) { + self.set( {'nbr_pending_operations':operations.length} ); + if(operations.length === 0){ + return $.when(); + } + var op = operations[0].data; + + // we prevent the default error handler and assume errors + // are a normal use case, except we stop the current iteration + + return new instance.web.Model('pos.order').get_func('create_from_ui')([op]) + .fail(function(unused, event){ + event.preventDefault(); + }) + .pipe(function(){ + //console.debug('saved 1 record'); TODO Debug this + self.dao.remove_operation(op.id).pipe(function(){ + return self._int_flush(); + }); + }, function(){ + return $.when(); + }); + }); + }, + 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) + }; + return this.ready.resolve(); + }, + 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({ + }); + + module.CashRegisterCollection = Backbone.Collection.extend({ + model: module.CashRegister, + }); + + module.Product = Backbone.Model.extend({ + }); + + module.ProductCollection = Backbone.Collection.extend({ + model: module.Product, + }); + + /* + 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. + To add more of the same product, just update the quantity accordingly. + The Order also contains payment information. + */ + module.Orderline = Backbone.Model.extend({ + defaults: { + quantity: 1, + list_price: 0, + discount: 0 + }, + initialize: function(attributes) { + this.posmodel = attributes.posmodel; + Backbone.Model.prototype.initialize.apply(this, arguments); + this.bind('change:quantity', function(unused, qty) { + if (qty == 0) + this.trigger('killme'); + }, this); + }, + incrementQuantity: function() { + return this.set({ + quantity: (this.get('quantity')) + 1 + }); + }, + getPriceWithoutTax: function() { + return this.getAllPrices().priceWithoutTax; + }, + getPriceWithTax: function() { + return this.getAllPrices().priceWithTax; + }, + getTax: function() { + return this.getAllPrices().tax; + }, + getAllPrices: function() { + var self = this; + var base = (this.get('quantity')) * (this.get('list_price')) * (1 - (this.get('discount')) / 100); + var totalTax = base; + var totalNoTax = base; + + var product_list = self.posmodel.get('product_list'); + var product = _.detect(product_list, function(el) {return el.id === self.get('id');}); + var taxes_ids = product.taxes_id; + var taxes = self.posmodel.get('taxes'); + var taxtotal = 0; + _.each(taxes_ids, function(el) { + var tax = _.detect(taxes, function(t) {return t.id === el;}); + if (tax.price_include) { + var tmp; + if (tax.type === "percent") { + tmp = base - (base / (1 + tax.amount)); + } else if (tax.type === "fixed") { + tmp = tax.amount * self.get('quantity'); + } else { + throw "This type of tax is not supported by the point of sale: " + tax.type; + } + taxtotal += tmp; + totalNoTax -= tmp; + } else { + var tmp; + if (tax.type === "percent") { + tmp = tax.amount * base; + } else if (tax.type === "fixed") { + tmp = tax.amount * self.get('quantity'); + } else { + throw "This type of tax is not supported by the point of sale: " + tax.type; + } + taxtotal += tmp; + totalTax += tmp; + } + }); + return { + "priceWithTax": totalTax, + "priceWithoutTax": totalNoTax, + "tax": taxtotal, + }; + }, + exportAsJSON: function() { + return { + qty: this.get('quantity'), + price_unit: this.get('list_price'), + discount: this.get('discount'), + product_id: this.get('id') + }; + }, + }); + + module.OrderlineCollection = Backbone.Collection.extend({ + model: module.Orderline, + }); + + // Every PaymentLine has all the attributes of the corresponding CashRegister. + module.Paymentline = Backbone.Model.extend({ + defaults: { + amount: 0, + }, + initialize: function(attributes) { + Backbone.Model.prototype.initialize.apply(this, arguments); + }, + getAmount: function(){ + return this.get('amount'); + }, + exportAsJSON: function(){ + return { + name: instance.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() + }; + }, + }); + + module.PaymentlineCollection = Backbone.Collection.extend({ + model: module.Paymentline, + }); + + module.Order = Backbone.Model.extend({ + defaults:{ + validated: false, + step: 'products', + }, + initialize: function(attributes){ + Backbone.Model.prototype.initialize.apply(this, arguments); + this.set({ + creationDate: new Date(), + orderLines: new module.OrderlineCollection(), + paymentLines: new module.PaymentlineCollection(), + name: "Order " + this.generateUniqueId(), + }); + this.posmodel = attributes.posmodel; //TODO put that in set and remember to use 'get' to read it ... + this.bind('change:validated', this.validatedChanged); + return this; + }, + events: { + 'change:validated': 'validatedChanged' + }, + validatedChanged: function() { + if (this.get("validated") && !this.previous("validated")) { + this.set({'step': 'receipt'}); + } + }, + generateUniqueId: function() { + return new Date().getTime(); + }, + addProduct: function(product) { + var existing; + existing = (this.get('orderLines')).get(product.id); + if (existing != null) { + existing.incrementQuantity(); + } else { + var attr = product.toJSON(); + attr.posmodel = this.posmodel; + var line = new module.Orderline(attr); + this.get('orderLines').add(line); + line.bind('killme', function() { + this.get('orderLines').remove(line); + }, this); + } + }, + addPaymentLine: function(cashRegister) { + var newPaymentline; + newPaymentline = new module.Paymentline(cashRegister); + /* TODO: Should be 0 for cash-like accounts */ + newPaymentline.set({ + amount: this.getDueLeft() + }); + return (this.get('paymentLines')).add(newPaymentline); + }, + getName: function() { + return this.get('name'); + }, + getTotal: function() { + return (this.get('orderLines')).reduce((function(sum, orderLine) { + return sum + orderLine.getPriceWithTax(); + }), 0); + }, + getTotalTaxExcluded: function() { + return (this.get('orderLines')).reduce((function(sum, orderLine) { + return sum + orderLine.getPriceWithoutTax(); + }), 0); + }, + getTax: function() { + return (this.get('orderLines')).reduce((function(sum, orderLine) { + return sum + orderLine.getTax(); + }), 0); + }, + getPaidTotal: function() { + return (this.get('paymentLines')).reduce((function(sum, paymentLine) { + return sum + paymentLine.getAmount(); + }), 0); + }, + getChange: function() { + return this.getPaidTotal() - this.getTotal(); + }, + getDueLeft: function() { + return this.getTotal() - this.getPaidTotal(); + }, + exportAsJSON: function() { + var orderLines, paymentLines; + orderLines = []; + (this.get('orderLines')).each(_.bind( function(item) { + return orderLines.push([0, 0, item.exportAsJSON()]); + }, this)); + paymentLines = []; + (this.get('paymentLines')).each(_.bind( function(item) { + return paymentLines.push([0, 0, item.exportAsJSON()]); + }, this)); + return { + name: this.getName(), + amount_paid: this.getPaidTotal(), + amount_total: this.getTotal(), + amount_tax: this.getTax(), + amount_return: this.getChange(), + lines: orderLines, + statement_ids: paymentLines + }; + }, + }); + + module.OrderCollection = Backbone.Collection.extend({ + model: module.Order, + }); + + module.Shop = Backbone.Model.extend({ + initialize: function(attributes) { + var self = this; + this.set({ + orders: new module.OrderCollection(), + products: new module.ProductCollection(), + }); + this.posmodel = attributes.posmodel; + this.set({ + cashRegisters: new module.CashRegisterCollection(this.posmodel.get('bank_statements')), + }); + return (this.get('orders')).bind('remove', _.bind( function(removedOrder) { + if ((this.get('orders')).isEmpty()) { + this.addAndSelectOrder(new module.Order({posmodel: self.posmodel})); + } + if ((this.get('selectedOrder')) === removedOrder) { + return this.set({ + selectedOrder: (this.get('orders')).last() + }); + } + }, this)); + }, + addAndSelectOrder: function(newOrder) { + (this.get('orders')).add(newOrder); + return this.set({ + selectedOrder: newOrder + }); + }, + }); + + /* + The numpad handles both the choice of the property currently being modified + (quantity, price or discount) and the edition of the corresponding numeric value. + */ + module.NumpadState = Backbone.Model.extend({ + defaults: { + buffer: "0", + mode: "quantity" + }, + appendNewChar: function(newChar) { + var oldBuffer; + oldBuffer = this.get('buffer'); + if (oldBuffer === '0') { + this.set({ + buffer: newChar + }); + } else if (oldBuffer === '-0') { + this.set({ + buffer: "-" + newChar + }); + } else { + this.set({ + buffer: (this.get('buffer')) + newChar + }); + } + this.updateTarget(); + }, + deleteLastChar: function() { + var tempNewBuffer; + tempNewBuffer = (this.get('buffer')).slice(0, -1) || "0"; + if (isNaN(tempNewBuffer)) { + tempNewBuffer = "0"; + } + this.set({ + buffer: tempNewBuffer + }); + this.updateTarget(); + }, + switchSign: function() { + var oldBuffer; + oldBuffer = this.get('buffer'); + this.set({ + buffer: oldBuffer[0] === '-' ? oldBuffer.substr(1) : "-" + oldBuffer + }); + this.updateTarget(); + }, + changeMode: function(newMode) { + this.set({ + buffer: "0", + mode: newMode + }); + }, + reset: function() { + this.set({ + buffer: "0", + mode: "quantity" + }); + }, + updateTarget: function() { + var bufferContent, params; + bufferContent = this.get('buffer'); + if (bufferContent && !isNaN(bufferContent)) { + this.trigger('setValue', parseFloat(bufferContent)); + } + }, + }); + + namespace.App = (function() { + + function App($element, posmodel) { + this.initialize($element, posmodel); + } + + App.prototype.initialize = function($element, posmodel) { + this.posmodel = posmodel; + this.shop = new namespace.Shop({'posmodel': posmodel}); + this.shopView = new namespace.ShopWidget(null, { + shop: this.shop, + 'posmodel': posmodel, + }); + this.shopView.$element = $element; + this.shopView.start(); + this.categoryView = new namespace.CategoryWidget(null, {element_id: 'products-screen-categories', posmodel: posmodel} ); + this.categoryView.on_change_category.add_last(_.bind(this.category, this)); + this.category(); + + this.onscreenKeyboard = new namespace.OnscreenKeyboardWidget(null,{keyboard_model:'simple'}); + this.onscreenKeyboard.appendTo($(".point-of-sale #content")); + + this.actionBar = new namespace.ActionbarWidget(null); + this.actionBar.appendTo($(".point-of-sale #content")); + + this.actionBar.addNewButton('left',{ + label : 'Aide', + icon : '/point_of_sale/static/src/img/icons/png48/help-browser.png', + }); + this.actionBar.addNewButton('left',{'label':'test'}); + this.actionBar.addNewButton('left',{'label':'kikoo', rightalign:true}); + + this.actionBar.addNewButton('right',{'label':'boo'}); + this.actionBar.addNewButton('right',{ + label : 'Payer', + rightalign : true, + icon : '/point_of_sale/static/src/img/icons/png48/go-next.png', + }); + this.actionBar.addNewButton('right',{ + label : 'Ook Ook', + rightalign : true, + icon : '/point_of_sale/static/src/img/icons/png48/face-monkey.png', + }); + }; + + //returns true if the code is a valid EAN codebar number by checking the control digit. + App.checkEan = function(code){ + 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); + } + 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; + }; + + // returns a product that has a packaging with an EAN matching to provided ean string. + // returns undefined if no such product is found. + App.getProductByEAN = function(ean, allPackages, allProducts) { + 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; + }; + + App.prototype.category = function(id) { + var c, product_list, self = this; + + id = !id ? 0 : id; + + c = this.posmodel.categories[id]; + this.categoryView.ancestors = c.ancestors; + this.categoryView.children = c.children; + this.categoryView.renderElement(); + this.categoryView.start(); + + allProducts = this.posmodel.get('product_list'); + + allPackages = this.posmodel.get('product.packaging'); + + product_list = this.posmodel.get('product_list').filter( function(p) { + var _ref; + return _ref = p.pos_categ_id[0], _.indexOf(c.subtree, _ref) >= 0; + }); + (this.shop.get('products')).reset(product_list); + + var codeNumbers = []; + + // 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 + if (!isNaN(Number(String.fromCharCode(e.keyCode)))) { + + // 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 { + if (lastTimeStamp + 30 < new Date().getTime()) { + // not a barcode reader + codeNumbers = []; + timeStamp = new Date().getTime(); + } + } + codeNumbers.push(e.keyCode - 48); + lastTimeStamp = new Date().getTime(); + if (codeNumbers.length == 13) { + console.log('found code:', codeNumbers.join('')); + + // a barcode reader + if (!App.checkEan(codeNumbers)) { + // barcode read error, raise warning + $(QWeb.render('pos-scan-warning')).dialog({ + resizable: false, + height:220, + modal: true, + title: "Warning", + }); + } + var selectedOrder = self.shop.get('selectedOrder'); + var scannedProductModel = App.getProductByEAN(codeNumbers.join(''),allPackages,allProducts); + if (scannedProductModel === undefined) { + // product not recognized, raise warning + $(QWeb.render('pos-scan-warning')).dialog({ + resizable: false, + height:220, + modal: true, + title: "Warning", + /* + buttons: { + "OK": function() { + $( this ).dialog( "close" ); + return; + }, + }*/ + }); + } else { + selectedOrder.addProduct(new namespace.Product(scannedProductModel)); + } + + codeNumbers = []; + } + } else { + // NaN + codeNumbers = []; + } + }); + + $('.searchbox input').keyup(function() { + var m, s; + s = $(this).val().toLowerCase(); + if (s) { + m = product_list.filter( function(p) { + return p.name.toLowerCase().indexOf(s) != -1; + }); + $('.search-clear').fadeIn(); + } else { + m = product_list; + $('.search-clear').fadeOut(); + } + return (self.shop.get('products')).reset(m); + }); + return $('.search-clear').click( function() { + (self.shop.get('products')).reset(product_list); + $('.searchbox input').val('').focus(); + return $('.search-clear').fadeOut(); + }); + }; + return App; + })(); + +} diff --git a/addons/point_of_sale/static/src/js/pos_widgets.js b/addons/point_of_sale/static/src/js/pos_widgets.js new file mode 100644 index 00000000000..5afe52cddf4 --- /dev/null +++ b/addons/point_of_sale/static/src/js/pos_widgets.js @@ -0,0 +1,1023 @@ +function pos_widgets(module, instance){ + var QWeb = instance.web.qweb; + + var qweb_template = function(template,posmodel){ + return function(ctx){ + if(!posmodel){ //this is a huge hack that needs to be removed ... TODO + var HackPosModel = Backbone.Model.extend({ + initialize:function(){ + this.set({ + 'currency': {symbol: '$', position: 'after'}, + }); + }, + }); + posmodel = new HackPosModel(); + } + return QWeb.render(template, _.extend({}, ctx,{ + 'currency': posmodel.get('currency'), + 'format_amount': function(amount) { + if (posmodel.get('currency').position == 'after') { + return amount + ' ' + posmodel.get('currency').symbol; + } else { + return posmodel.get('currency').symbol + ' ' + amount; + } + }, + })); + }; + }; + + module.NumpadWidget = instance.web.OldWidget.extend({ + init: function(parent, options) { + this._super(parent); + this.state = new module.NumpadState(); + }, + start: function() { + this.state.bind('change:mode', this.changedMode, this); + this.changedMode(); + this.$element.find('button#numpad-backspace').click(_.bind(this.clickDeleteLastChar, this)); + this.$element.find('button#numpad-minus').click(_.bind(this.clickSwitchSign, this)); + this.$element.find('button.number-char').click(_.bind(this.clickAppendNewChar, this)); + this.$element.find('button.mode-button').click(_.bind(this.clickChangeMode, this)); + }, + clickDeleteLastChar: function() { + return this.state.deleteLastChar(); + }, + clickSwitchSign: function() { + return this.state.switchSign(); + }, + clickAppendNewChar: function(event) { + var newChar; + newChar = event.currentTarget.innerText || event.currentTarget.textContent; + return this.state.appendNewChar(newChar); + }, + clickChangeMode: function(event) { + var newMode = event.currentTarget.attributes['data-mode'].nodeValue; + return this.state.changeMode(newMode); + }, + changedMode: function() { + var mode = this.state.get('mode'); + $('.selected-mode').removeClass('selected-mode'); + $(_.str.sprintf('.mode-button[data-mode="%s"]', mode), this.$element).addClass('selected-mode'); + }, + }); + /* + Gives access to the payment methods (aka. 'cash registers') + */ + module.PaypadWidget = instance.web.OldWidget.extend({ + init: function(parent, options) { + this._super(parent); + this.shop = options.shop; + }, + start: function() { + this.$element.find('button').click(_.bind(this.performPayment, this)); + }, + performPayment: function(event) { + if (this.shop.get('selectedOrder').get('step') === 'receipt') + return; + var cashRegister, cashRegisterCollection, cashRegisterId; + /* set correct view */ + this.shop.get('selectedOrder').set({'step': 'payment'}); + + cashRegisterId = event.currentTarget.attributes['cash-register-id'].nodeValue; + cashRegisterCollection = this.shop.get('cashRegisters'); + cashRegister = cashRegisterCollection.find(_.bind( function(item) { + return (item.get('id')) === parseInt(cashRegisterId, 10); + }, this)); + return (this.shop.get('selectedOrder')).addPaymentLine(cashRegister); + }, + renderElement: function() { + this.$element.empty(); + return (this.shop.get('cashRegisters')).each(_.bind( function(cashRegister) { + var button = new module.PaymentButtonWidget(); + button.model = cashRegister; + button.appendTo(this.$element); + }, this)); + } + }); + + module.PaymentButtonWidget = instance.web.OldWidget.extend({ + template_fct: qweb_template('pos-payment-button-template'), + renderElement: function() { + this.$element.html(this.template_fct({ + id: this.model.get('id'), + name: (this.model.get('journal_id'))[1] + })); + return this; + } + }); + /* + There are 3 steps in a POS workflow: + 1. prepare the order (i.e. chose products, quantities etc.) + 2. choose payment method(s) and amount(s) + 3. validae order and print receipt + It should be possible to go back to any step as long as step 3 hasn't been completed. + Modifying an order after validation shouldn't be allowed. + */ + module.StepSwitcher = instance.web.OldWidget.extend({ + init: function(parent, options) { + this._super(parent); + this.shop = options.shop; + this.change_order(); + this.shop.bind('change:selectedOrder', this.change_order, this); + }, + change_order: function() { + if (this.selected_order) { + this.selected_order.unbind('change:step', this.change_step); + } + this.selected_order = this.shop.get('selectedOrder'); + if (this.selected_order) { + this.selected_order.bind('change:step', this.change_step, this); + } + this.change_step(); + }, + change_step: function() { + var new_step = this.selected_order ? this.selected_order.get('step') : 'products'; + $('.step-screen').hide(); + $('#' + new_step + '-screen').show(); + }, + }); + /* + Shopping carts. + */ + module.OrderlineWidget = instance.web.OldWidget.extend({ + tagName: 'tr', + template_fct: qweb_template('pos-orderline-template'), + init: function(parent, options) { + this._super(parent); + this.model = options.model; + this.model.bind('change', _.bind( function() { + this.refresh(); + }, this)); + this.model.bind('remove', _.bind( function() { + this.$element.remove(); + }, this)); + this.order = options.order; + }, + start: function() { + this.$element.click(_.bind(this.clickHandler, this)); + this.refresh(); + }, + clickHandler: function() { + this.select(); + }, + renderElement: function() { + this.$element.html(this.template_fct(this.model.toJSON())); + this.select(); + }, + 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'); + this.order.selected = this.model; + this.on_selected(); + }, + on_selected: function() {}, + }); + + module.OrderWidget = instance.web.OldWidget.extend({ + init: function(parent, options) { + this._super(parent); + this.shop = options.shop; + this.setNumpadState(options.numpadState); + this.shop.bind('change:selectedOrder', this.changeSelectedOrder, this); + this.bindOrderLineEvents(); + }, + setNumpadState: function(numpadState) { + if (this.numpadState) { + this.numpadState.unbind('setValue', this.setValue); + } + this.numpadState = numpadState; + if (this.numpadState) { + this.numpadState.bind('setValue', this.setValue, this); + this.numpadState.reset(); + } + }, + setValue: function(val) { + var param = {}; + param[this.numpadState.get('mode')] = val; + var order = this.shop.get('selectedOrder'); + if (order.get('orderLines').length !== 0) { + order.selected.set(param); + } else { + this.shop.get('selectedOrder').destroy(); + } + }, + changeSelectedOrder: function() { + this.currentOrderLines.unbind(); + this.bindOrderLineEvents(); + this.renderElement(); + }, + bindOrderLineEvents: function() { + this.currentOrderLines = (this.shop.get('selectedOrder')).get('orderLines'); + this.currentOrderLines.bind('add', this.addLine, this); + this.currentOrderLines.bind('remove', this.renderElement, this); + }, + addLine: function(newLine) { + var line = new module.OrderlineWidget(null, { + model: newLine, + order: this.shop.get('selectedOrder') + }); + line.on_selected.add(_.bind(this.selectedLine, this)); + this.selectedLine(); + line.appendTo(this.$element); + this.updateSummary(); + }, + selectedLine: function() { + var reset = false; + if (this.currentSelected !== this.shop.get('selectedOrder').selected) { + reset = true; + } + this.currentSelected = this.shop.get('selectedOrder').selected; + if (reset && this.numpadState) + this.numpadState.reset(); + this.updateSummary(); + }, + renderElement: function() { + this.$element.empty(); + this.currentOrderLines.each(_.bind( function(orderLine) { + var line = new module.OrderlineWidget(null, { + model: orderLine, + order: this.shop.get('selectedOrder') + }); + line.on_selected.add(_.bind(this.selectedLine, this)); + line.appendTo(this.$element); + }, this)); + this.updateSummary(); + }, + updateSummary: function() { + var currentOrder, tax, total, totalTaxExcluded; + currentOrder = this.shop.get('selectedOrder'); + total = currentOrder.getTotal(); + totalTaxExcluded = currentOrder.getTotalTaxExcluded(); + tax = currentOrder.getTax(); + $('#subtotal').html(totalTaxExcluded.toFixed(2)).hide().fadeIn(); + $('#tax').html(tax.toFixed(2)).hide().fadeIn(); + $('#total').html(total.toFixed(2)).hide().fadeIn(); + }, + }); + + /* + "Products" step. + */ + module.CategoryWidget = instance.web.OldWidget.extend({ + init: function(parent, options){ + this._super(parent,options.element_id); + this.posmodel = options.posmodel; + }, + start: function() { + this.$element.find(".oe-pos-categories-list a").click(_.bind(this.changeCategory, this)); + }, + template_fct: qweb_template('pos-category-template'), + renderElement: function() { + var self = this; + var c; + this.$element.html(this.template_fct({ + breadcrumb: (function() { + var _i, _len, _results; + _results = []; + for (_i = 0, _len = self.ancestors.length; _i < _len; _i++) { + c = self.ancestors[_i]; + _results.push(self.posmodel.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.posmodel.categories[c]); + } + return _results; + })() + })); + }, + changeCategory: function(a) { + var id = $(a.target).data("category-id"); + this.on_change_category(id); + }, + on_change_category: function(id) {}, + }); + + module.ProductWidget = instance.web.OldWidget.extend({ + tagName:'li', + template_fct: qweb_template('pos-product-template'), + init: function(parent, options) { + this._super(parent); + this.model = options.model; + this.shop = options.shop; + }, + start: function(options) { + $("a", this.$element).click(_.bind(this.addToOrder, this)); + }, + addToOrder: function(event) { + /* Preserve the category URL */ + event.preventDefault(); + return (this.shop.get('selectedOrder')).addProduct(this.model); + }, + renderElement: function() { + this.$element.addClass("product"); + this.$element.html(this.template_fct(this.model.toJSON())); + return this; + }, + }); + + module.ProductListWidget = instance.web.OldWidget.extend({ + init: function(parent, options) { + this._super(parent); + this.model = options.model; + this.shop = options.shop; + this.shop.get('products').bind('reset', this.renderElement, this); + }, + renderElement: function() { + this.$element.empty(); + (this.shop.get('products')).each(_.bind( function(product) { + var p = new module.ProductWidget(null, { + model: product, + shop: this.shop + }); + p.appendTo(this.$element); + }, this)); + return this; + }, + }); + /* + "Payment" step. + */ + module.PaymentlineWidget = instance.web.OldWidget.extend({ + tagName: 'tr', + template_fct: qweb_template('pos-paymentline-template'), + init: function(parent, options) { + this._super(parent); + this.model = options.model; + this.model.bind('change', this.changedAmount, this); + }, + on_delete: function() {}, + changeAmount: function(event) { + var newAmount; + newAmount = event.currentTarget.value; + if (newAmount && !isNaN(newAmount)) { + this.amount = parseFloat(newAmount); + this.model.set({ + amount: this.amount, + }); + } + }, + changedAmount: function() { + if (this.amount !== this.model.get('amount')) + this.renderElement(); + }, + renderElement: function() { + this.amount = this.model.get('amount'); + this.$element.html(this.template_fct({ + name: (this.model.get('journal_id'))[1], + amount: this.amount, + })); + this.$element.addClass('paymentline'); + $('input', this.$element).keyup(_.bind(this.changeAmount, this)); + $('.delete-payment-line', this.$element).click(this.on_delete); + }, + }); + + module.PaymentWidget = instance.web.OldWidget.extend({ + init: function(parent, options) { + this._super(parent); + this.model = options.model; + this.shop = options.shop; + this.posmodel = options.posmodel; + this.shop.bind('change:selectedOrder', this.changeSelectedOrder, this); + this.bindPaymentLineEvents(); + this.bindOrderLineEvents(); + }, + paymentLineList: function() { + return this.$element.find('#paymentlines'); + }, + start: function() { + $('button#validate-order', this.$element).click(_.bind(this.validateCurrentOrder, this)); + $('.oe-back-to-products', this.$element).click(_.bind(this.back, this)); + }, + back: function() { + this.shop.get('selectedOrder').set({"step": "products"}); + }, + validateCurrentOrder: function() { + var callback, currentOrder; + currentOrder = this.shop.get('selectedOrder'); + $('button#validate-order', this.$element).attr('disabled', 'disabled'); + this.posmodel.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.shop.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.shop.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.paymentLineList().empty(); + this.currentPaymentLines.each(_.bind( function(paymentLine) { + this.addPaymentLine(paymentLine); + }, this)); + this.updatePaymentSummary(); + }, + deleteLine: function(lineWidget) { + this.currentPaymentLines.remove([lineWidget.model]); + }, + updatePaymentSummary: function() { + var currentOrder, dueTotal, paidTotal, remaining, remainingAmount; + currentOrder = this.shop.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.ReceiptWidget = instance.web.OldWidget.extend({ + init: function(parent, options) { + this._super(parent); + this.model = options.model; + this.shop = options.shop; + this.posmodel = options.posmodel; + this.user = this.posmodel.get('user'); + this.company = this.posmodel.get('company'); + this.shop_obj = this.posmodel.get('shop'); + }, + start: function() { + this.shop.bind('change:selectedOrder', this.changeSelectedOrder, this); + this.changeSelectedOrder(); + }, + renderElement: function() { + this.$element.html(qweb_template('pos-receipt-view')); + $('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.shop.get('selectedOrder').destroy(); + }, + changeSelectedOrder: function() { + if (this.currentOrderLines) + this.currentOrderLines.unbind(); + this.currentOrderLines = (this.shop.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.shop.get('selectedOrder')).get('paymentLines'); + this.currentPaymentLines.bind('all', this.refresh, this); + this.refresh(); + }, + refresh: function() { + this.currentOrder = this.shop.get('selectedOrder'); + $('.pos-receipt-container', this.$element).html(qweb_template('pos-ticket')({widget:this})); + }, + }); + + module.OrderButtonWidget = instance.web.OldWidget.extend({ + tagName: 'li', + template_fct: qweb_template('pos-order-selector-button-template'), + init: function(parent, options) { + this._super(parent); + this.order = options.order; + this.shop = options.shop; + this.order.bind('destroy', _.bind( function() { + this.destroy(); + }, this)); + this.shop.bind('change:selectedOrder', _.bind( function(shop) { + var selectedOrder; + selectedOrder = shop.get('selectedOrder'); + if (this.order === selectedOrder) { + this.setButtonSelected(); + } + }, this)); + }, + start: function() { + $('button.select-order', this.$element).click(_.bind(this.selectOrder, this)); + $('button.close-order', this.$element).click(_.bind(this.closeOrder, this)); + }, + selectOrder: function(event) { + this.shop.set({ + selectedOrder: this.order + }); + }, + setButtonSelected: function() { + $('.selected-order').removeClass('selected-order'); + this.$element.addClass('selected-order'); + }, + closeOrder: function(event) { + this.order.destroy(); + }, + renderElement: function() { + this.$element.html(this.template_fct({widget:this})); + this.$element.addClass('order-selector-button'); + } + }); + + module.ActionButtonWidget = instance.web.Widget.extend({ + template:'pos-action-button', + init: function(parent, options){ + this._super(parent, options); + this.label = options.label || 'button'; + this.rightalign = options.rightalign || false; + if(options.icon){ + this.icon = options.icon; + this.template = 'pos-action-button-with-icon'; + } + }, + }); + + module.ActionbarWidget = instance.web.Widget.extend({ + template:'pos-actionbar', + init: function(parent, options){ + this._super(parent,options); + this.left_button_list = []; + this.right_button_list = []; + }, + start: function(){ + console.log('hello world!'); + 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(); + } + 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'); + } + var button = new module.ActionButtonWidget(this,button_options); + button_list.push(button); + button.appendTo($button_list); + }else{ + for(var i = 1; i < arguments.length; i++){ + this.addNewButton(position,arguments[i]); + } + } + return this; + } + /* + renderElement: function() { + //this.$element.html(this.template_fct()); + },*/ + }); + + // A Widget that displays an onscreen keyboard. + // There are two options when creating the widget : + // + // * 'keyboard_model' : 'simple' | 'full' (default) + // The 'full' emulates a PC keyboard, while 'simple' emulates an 'android' one. + // + // * 'input_selector : (default: '.searchbox input') + // defines the dom element that the keyboard will write to. + // + // The widget is initially hidden. It can be shown with this.show(), and is + // automatically shown when the input_selector gets focused. + module.OnscreenKeyboardWidget = instance.web.Widget.extend({ + tagName: 'div', + + init: function(parent, options){ + var self = this; + + this._super(parent,options); + + function get_option(opt,default_value){ + if(options){ + return options[opt] || default_value; + }else{ + return default_value; + } + } + + this.keyboard_model = get_option('keyboard_model','full'); + this.template_simple = qweb_template('pos-onscreen-keyboard-simple-template'); + this.template_full = qweb_template('pos-onscreen-keyboard-full-template'); + + this.template_fct = function(){ + if( this.keyboard_model == 'full' ){ + return this.template_full.apply(this,arguments); + }else{ + return this.template_simple.apply(this,arguments); + } + }; + + this.input_selector = get_option('input_selector','.searchbox input'); + + //show the keyboard when the input zone is clicked. + $(this.input_selector).focus(function(){self.show();}); + + //Keyboard state + this.capslock = false; + this.shift = false; + this.numlock = false; + }, + + // Write a character to the input zone + writeCharacter: function(character){ + var $input = $(this.input_selector); + $input[0].value += character; + $input.keydown(); + $input.keyup(); + }, + + // Sends a 'return' character to the input zone. TODO + sendReturn: function(){ + }, + + // Removes the last character from the input zone. + deleteCharacter: function(){ + var $input = $(this.input_selector); + var input_value = $input[0].value; + $input[0].value = input_value.substr(0, input_value.length - 1); + $input.keydown(); + $input.keyup(); + }, + + // Clears the content of the input zone. + deleteAllCharacters: function(){ + var $input = $(this.input_selector); + $input[0].value = ""; + $input.keydown(); + $input.keyup(); + }, + renderElement: function(){ + this.$element.html(this.template_fct()); + }, + + // Makes the keyboard show and slide from the bottom of the screen. + show: function(){ + $('.keyboard_frame').show().animate({'height':'235px'}, 500, 'swing'); + }, + + // Makes the keyboard hide by sliding to the bottom of the screen. + hide: function(){ + var self = this; + var frame = $('.keyboard_frame'); + frame.animate({'height':'0'}, 500, 'swing', function(){ frame.hide(); self.reset(); }); + }, + + //What happens when the shift key is pressed : toggle case, remove capslock + toggleShift: function(){ + $('.letter').toggleClass('uppercase'); + $('.symbol span').toggle(); + + self.shift = (self.shift === true) ? false : true; + self.capslock = false; + }, + + //what happens when capslock is pressed : toggle case, set capslock + toggleCapsLock: function(){ + $('.letter').toggleClass('uppercase'); + self.capslock = true; + }, + + //What happens when numlock is pressed : toggle symbols and numlock label + toggleNumLock: function(){ + $('.symbol span').toggle(); + $('.numlock span').toggle(); + self.numlock = (self.numlock === true ) ? false : true; + }, + + //After a key is pressed, shift is disabled. + removeShift: function(){ + if (self.shift === true) { + $('.symbol span').toggle(); + if (this.capslock === false) $('.letter').toggleClass('uppercase'); + + self.shift = false; + } + }, + + // Resets the keyboard to its original state; capslock: false, shift: false, numlock: false + reset: function(){ + if(this.shift){ + this.toggleShift(); + } + if(this.capslock){ + this.toggleCapsLock(); + } + if(this.numlock){ + this.toggleNumLock(); + } + }, + + //called after the keyboard is in the DOM, sets up the key bindings. + start: function(){ + var self = this; + + //this.show(); + + + $('.close_button').click(function(){ + self.deleteAllCharacters(); + self.hide(); + }); + + // Keyboard key click handling + $('.keyboard li').click(function(){ + + var $this = $(this), + character = $this.html(); // If it's a lowercase letter, nothing happens to this variable + + if ($this.hasClass('left-shift') || $this.hasClass('right-shift')) { + self.toggleShift(); + return false; + } + + if ($this.hasClass('capslock')) { + self.toggleCapsLock(); + return false; + } + + if ($this.hasClass('delete')) { + self.deleteCharacter(); + return false; + } + + if ($this.hasClass('numlock')){ + self.toggleNumLock(); + return false; + } + + // Special characters + if ($this.hasClass('symbol')) character = $('span:visible', $this).html(); + if ($this.hasClass('space')) character = ' '; + if ($this.hasClass('tab')) character = "\t"; + if ($this.hasClass('return')) character = "\n"; + + // Uppercase letter + if ($this.hasClass('uppercase')) character = character.toUpperCase(); + + // Remove shift once a key is clicked. + self.removeShift(); + + self.writeCharacter(character); + }); + }, + }); + + module.ShopWidget = instance.web.OldWidget.extend({ + init: function(parent, options) { + this._super(parent); + this.shop = options.shop; + this.posmodel = options.posmodel; + }, + start: function() { + $('button#neworder-button', this.$element).click(_.bind(this.createNewOrder, this)); + + (this.shop.get('orders')).bind('add', this.orderAdded, this); + (this.shop.get('orders')).add(new module.Order({'posmodel':this.posmodel})); + this.productListView = new module.ProductListWidget(null, { + shop: this.shop + }); + this.productListView.$element = $("#products-screen-ol"); + this.productListView.renderElement(); + this.productListView.start(); + this.paypadView = new module.PaypadWidget(null, { + shop: this.shop + }); + this.paypadView.$element = $('#paypad'); + this.paypadView.renderElement(); + this.paypadView.start(); + this.numpadView = new module.NumpadWidget(null); + this.numpadView.$element = $('#numpad'); + this.numpadView.start(); + this.orderView = new module.OrderWidget(null, { + shop: this.shop, + }); + this.orderView.$element = $('#current-order-content'); + this.orderView.start(); + this.paymentView = new module.PaymentWidget(null, { + shop: this.shop, + posmodel: this.posmodel, + }); + this.paymentView.$element = $('#payment-screen'); + this.paymentView.renderElement(); + this.paymentView.start(); + this.receiptView = new module.ReceiptWidget(null, { + shop: this.shop, + posmodel: this.posmodel, + }); + this.receiptView.replace($('#receipt-screen')); + this.stepSwitcher = new module.StepSwitcher(this, {shop: this.shop}); + this.shop.bind('change:selectedOrder', this.changedSelectedOrder, this); + this.changedSelectedOrder(); + }, + createNewOrder: function() { + var newOrder; + newOrder = new module.Order({'posmodel': this.posmodel}); + (this.shop.get('orders')).add(newOrder); + this.shop.set({ + selectedOrder: newOrder + }); + }, + orderAdded: function(newOrder) { + var newOrderButton; + newOrderButton = new module.OrderButtonWidget(null, { + order: newOrder, + shop: this.shop + }); + newOrderButton.appendTo($('#orders')); + newOrderButton.selectOrder(); + }, + changedSelectedOrder: function() { + if (this.currentOrder) { + this.currentOrder.unbind('change:step', this.changedStep); + } + this.currentOrder = this.shop.get('selectedOrder'); + this.currentOrder.bind('change:step', this.changedStep, this); + this.changedStep(); + }, + changedStep: function() { + var step = this.currentOrder.get('step'); + this.orderView.setNumpadState(null); + this.paymentView.setNumpadState(null); + if (step === 'products') { + this.orderView.setNumpadState(this.numpadView.state); + } else if (step === 'payment') { + this.paymentView.setNumpadState(this.numpadView.state); + } + }, + }); + + namespace.SynchNotification = instance.web.OldWidget.extend({ + template: "pos-synch-notification", + init: function() { + this._super.apply(this, arguments); + this.nbr_pending = 0; + }, + renderElement: function() { + this._super.apply(this, arguments); + $('.oe_pos_synch-notification-button', this.$element).click(this.on_synch); + }, + on_change_nbr_pending: function(nbr_pending) { + this.nbr_pending = nbr_pending; + this.renderElement(); + }, + on_synch: function() {} + }); + + namespace.POSWidget = instance.web.OldWidget.extend({ + init: function() { + this._super.apply(this, arguments); + + this.posmodel = new namespace.PosModel(this.session); + + }, + start: function() { + var self = this; + return self.posmodel.ready.then(_.bind(function() { + this.renderElement(); + this.synch_notification = new namespace.SynchNotification(this); + this.synch_notification.replace($('.oe_pos_synch-notification', this.$element)); + this.synch_notification.on_synch.add(_.bind(self.posmodel.flush, self.posmodel)); + + self.posmodel.bind('change:nbr_pending_operations', this.changed_pending_operations, this); + this.changed_pending_operations(); + + this.$element.find("#loggedas button").click(function() { + self.try_close(); + }); + + self.posmodel.app = new namespace.App(self.$element, self.posmodel); + instance.webclient.set_content_full_screen(true); + + if (self.posmodel.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() { + return qweb_template("POSWidget")(); + }, + changed_pending_operations: function () { + var self = this; + this.synch_notification.on_change_nbr_pending(self.posmodel.get('nbr_pending_operations').length); + }, + try_close: function() { + var self = this; + self.posmodel.flush().then(_.bind(function() { + var close = _.bind(this.close, this); + if (self.posmodel.get('nbr_pending_operations').length > 0) { + var confirm = false; + $(QWeb.render('pos-close-warning')).dialog({ + resizable: false, + height:160, + modal: true, + title: "Warning", + buttons: { + "Yes": function() { + confirm = true; + $( this ).dialog( "close" ); + }, + "No": function() { + $( this ).dialog( "close" ); + } + }, + close: function() { + if (confirm) + close(); + } + }); + } else { + close(); + } + }, this)); + }, + close: function() { + // remove barcode reader event listener + $('body').undelegate('', 'keyup') + + 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); + self.posmodel = undefined; + this._super(); + } + }); + +} From bbbf338436d5769de7363bc465e15082c97b9990 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20van=20der=20Essen?= Date: Tue, 24 Apr 2012 18:28:02 +0200 Subject: [PATCH 49/54] [PoS] Removed old pos.js file bzr revid: fva@openerp.com-20120424162802-o2diexi57p02fjr8 --- addons/point_of_sale/static/src/js/pos.js | 1808 --------------------- 1 file changed, 1808 deletions(-) delete mode 100644 addons/point_of_sale/static/src/js/pos.js diff --git a/addons/point_of_sale/static/src/js/pos.js b/addons/point_of_sale/static/src/js/pos.js deleted file mode 100644 index e7566256648..00000000000 --- a/addons/point_of_sale/static/src/js/pos.js +++ /dev/null @@ -1,1808 +0,0 @@ -openerp.point_of_sale = function(instance) { - - instance.point_of_sale = {}; - - var namespace = instance.point_of_sale; - - var QWeb = instance.web.qweb; - - var qweb_template = function(template,posmodel){ - return function(ctx){ - if(!posmodel){ //this is a huge hack that needs to be removed ... TODO - var HackPosModel = Backbone.Model.extend({ - initialize:function(){ - this.set({ - 'currency': {symbol: '$', position: 'after'}, - }); - }, - }); - posmodel = new HackPosModel(); - } - return QWeb.render(template, _.extend({}, ctx,{ - 'currency': posmodel.get('currency'), - 'format_amount': function(amount) { - if (posmodel.get('currency').position == 'after') { - return amount + ' ' + posmodel.get('currency').symbol; - } else { - return posmodel.get('currency').symbol + ' ' + amount; - } - }, - })); - }; - }; - - namespace.LocalStorageDAO = instance.web.Class.extend({ - add_operation: function(operation) { - var self = this; - return $.async_when().pipe(function() { - var tmp = self._get('oe_pos_operations', []); - var last_id = self._get('oe_pos_operations_sequence', 1); - tmp.push({'id': last_id, 'data': operation}); - self._set('oe_pos_operations', tmp); - self._set('oe_pos_operations_sequence', last_id + 1); - }); - }, - remove_operation: function(id) { - var self = this; - return $.async_when().pipe(function() { - var tmp = self._get('oe_pos_operations', []); - tmp = _.filter(tmp, function(el) { - return el.id !== id; - }); - self._set('oe_pos_operations', tmp); - }); - }, - get_operations: function() { - var self = this; - return $.async_when().pipe(function() { - return self._get('oe_pos_operations', []); - }); - }, - _get: function(key, default_) { - var txt = localStorage[key]; - if (! txt) - return default_; - return JSON.parse(txt); - }, - _set: function(key, value) { - localStorage[key] = JSON.stringify(value); - }, - }); - - var fetch = function(osvModel, fields, domain){ - var dataSetSearch = new instance.web.DataSetSearch(null, osvModel, {}, domain); - return dataSetSearch.read_slice(fields, 0); - }; - - /* - Gets all the necessary data from the OpenERP web client (instance, shop data etc.) - */ - namespace.PosModel = Backbone.Model.extend({ - initialize: function(session, attributes) { - Backbone.Model.prototype.initialize.call(this, attributes); - var self = this; - this.dao = new namespace.LocalStorageDAO(); - this.ready = $.Deferred(); - this.flush_mutex = new $.Mutex(); - this.build_tree = _.bind(this.build_tree, this); - this.session = session; - this.categories = {}; - this.set({ - 'nbr_pending_operations': 0, - 'currency': {symbol: '$', position: 'after'}, - 'shop': {}, - 'company': {}, - 'user': {} - }); - - var cat_def = fetch('pos.category', ['name', 'parent_id', 'child_id']) - .pipe(function(result){ - return self.set({'categories': result}); - }); - - var prod_def = fetch( - 'product.product', - ['name', 'list_price', 'pos_categ_id', 'taxes_id','product_image_small'], - [['pos_categ_id','!=', 'false']] - ).then(function(result){ - 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){ - return self.set({'bank_statements': result}); - }); - - var tax_def = fetch('account.tax', ['amount','price_include','type']) - .then(function(result){ - return self.set({'taxes': result}); - }); - - $.when(cat_def,prod_def,bank_def,tax_def,this.get_app_data(), this.flush()) - .pipe(_.bind(this.build_tree, this)); - }, - - get_app_data: function() { - var self = this; - return $.when(new instance.web.Model("sale.shop").get_func("search_read")([]).pipe(function(result) { - self.set({'shop': result[0]}); - var company_id = result[0]['company_id'][0]; - return new instance.web.Model("res.company").get_func("read")(company_id, ['currency_id', 'name', 'phone']).pipe(function(result) { - self.set({'company': result}); - var currency_id = result['currency_id'][0] - return new instance.web.Model("res.currency").get_func("read")([currency_id], - ['symbol', 'position']).pipe(function(result) { - self.set({'currency': result[0]}); - - }); - }); - }), new instance.web.Model("res.users").get_func("read")(this.session.uid, ['name']).pipe(function(result) { - self.set({'user': result}); - })); - }, - push_order: function(record) { - var self = this; - return this.dao.add_operation(record).pipe(function(){ - return self.flush(); - }); - }, - flush: function() { - return this.flush_mutex.exec(_.bind(function() { - return this._int_flush(); - }, this)); - }, - _int_flush : function() { - var self = this; - - this.dao.get_operations().pipe(function(operations) { - self.set( {'nbr_pending_operations':operations.length} ); - if(operations.length === 0){ - return $.when(); - } - var op = operations[0].data; - - // we prevent the default error handler and assume errors - // are a normal use case, except we stop the current iteration - - return new instance.web.Model('pos.order').get_func('create_from_ui')([op]) - .fail(function(unused, event){ - event.preventDefault(); - }) - .pipe(function(){ - //console.debug('saved 1 record'); TODO Debug this - self.dao.remove_operation(op.id).pipe(function(){ - return self._int_flush(); - }); - }, function(){ - return $.when(); - }); - }); - }, - 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) - }; - return this.ready.resolve(); - }, - 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; - } - }); - - - /* - --- - Models - --- - */ - - namespace.CashRegister = Backbone.Model.extend({ - }); - - namespace.CashRegisterCollection = Backbone.Collection.extend({ - model: namespace.CashRegister, - }); - - namespace.Product = Backbone.Model.extend({ - }); - - namespace.ProductCollection = Backbone.Collection.extend({ - model: namespace.Product, - }); - - /* - 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. - To add more of the same product, just update the quantity accordingly. - The Order also contains payment information. - */ - namespace.Orderline = Backbone.Model.extend({ - defaults: { - quantity: 1, - list_price: 0, - discount: 0 - }, - initialize: function(attributes) { - this.posmodel = attributes.posmodel; - Backbone.Model.prototype.initialize.apply(this, arguments); - this.bind('change:quantity', function(unused, qty) { - if (qty == 0) - this.trigger('killme'); - }, this); - }, - incrementQuantity: function() { - return this.set({ - quantity: (this.get('quantity')) + 1 - }); - }, - getPriceWithoutTax: function() { - return this.getAllPrices().priceWithoutTax; - }, - getPriceWithTax: function() { - return this.getAllPrices().priceWithTax; - }, - getTax: function() { - return this.getAllPrices().tax; - }, - getAllPrices: function() { - var self = this; - var base = (this.get('quantity')) * (this.get('list_price')) * (1 - (this.get('discount')) / 100); - var totalTax = base; - var totalNoTax = base; - - var product_list = self.posmodel.get('product_list'); - var product = _.detect(product_list, function(el) {return el.id === self.get('id');}); - var taxes_ids = product.taxes_id; - var taxes = self.posmodel.get('taxes'); - var taxtotal = 0; - _.each(taxes_ids, function(el) { - var tax = _.detect(taxes, function(t) {return t.id === el;}); - if (tax.price_include) { - var tmp; - if (tax.type === "percent") { - tmp = base - (base / (1 + tax.amount)); - } else if (tax.type === "fixed") { - tmp = tax.amount * self.get('quantity'); - } else { - throw "This type of tax is not supported by the point of sale: " + tax.type; - } - taxtotal += tmp; - totalNoTax -= tmp; - } else { - var tmp; - if (tax.type === "percent") { - tmp = tax.amount * base; - } else if (tax.type === "fixed") { - tmp = tax.amount * self.get('quantity'); - } else { - throw "This type of tax is not supported by the point of sale: " + tax.type; - } - taxtotal += tmp; - totalTax += tmp; - } - }); - return { - "priceWithTax": totalTax, - "priceWithoutTax": totalNoTax, - "tax": taxtotal, - }; - }, - exportAsJSON: function() { - return { - qty: this.get('quantity'), - price_unit: this.get('list_price'), - discount: this.get('discount'), - product_id: this.get('id') - }; - }, - }); - - namespace.OrderlineCollection = Backbone.Collection.extend({ - model: namespace.Orderline, - }); - - // Every PaymentLine has all the attributes of the corresponding CashRegister. - namespace.Paymentline = Backbone.Model.extend({ - defaults: { - amount: 0, - }, - initialize: function(attributes) { - Backbone.Model.prototype.initialize.apply(this, arguments); - }, - getAmount: function(){ - return this.get('amount'); - }, - exportAsJSON: function(){ - return { - name: instance.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() - }; - }, - }); - - namespace.PaymentlineCollection = Backbone.Collection.extend({ - model: namespace.Paymentline, - }); - - namespace.Order = Backbone.Model.extend({ - defaults:{ - validated: false, - step: 'products', - }, - initialize: function(attributes){ - Backbone.Model.prototype.initialize.apply(this, arguments); - this.set({ - creationDate: new Date(), - orderLines: new namespace.OrderlineCollection(), - paymentLines: new namespace.PaymentlineCollection(), - name: "Order " + this.generateUniqueId(), - }); - this.posmodel = attributes.posmodel; //TODO put that in set and remember to use 'get' to read it ... - this.bind('change:validated', this.validatedChanged); - return this; - }, - events: { - 'change:validated': 'validatedChanged' - }, - validatedChanged: function() { - if (this.get("validated") && !this.previous("validated")) { - this.set({'step': 'receipt'}); - } - }, - generateUniqueId: function() { - return new Date().getTime(); - }, - addProduct: function(product) { - var existing; - existing = (this.get('orderLines')).get(product.id); - if (existing != null) { - existing.incrementQuantity(); - } else { - var attr = product.toJSON(); - attr.posmodel = this.posmodel; - var line = new namespace.Orderline(attr); - this.get('orderLines').add(line); - line.bind('killme', function() { - this.get('orderLines').remove(line); - }, this); - } - }, - addPaymentLine: function(cashRegister) { - var newPaymentline; - newPaymentline = new namespace.Paymentline(cashRegister); - /* TODO: Should be 0 for cash-like accounts */ - newPaymentline.set({ - amount: this.getDueLeft() - }); - return (this.get('paymentLines')).add(newPaymentline); - }, - getName: function() { - return this.get('name'); - }, - getTotal: function() { - return (this.get('orderLines')).reduce((function(sum, orderLine) { - return sum + orderLine.getPriceWithTax(); - }), 0); - }, - getTotalTaxExcluded: function() { - return (this.get('orderLines')).reduce((function(sum, orderLine) { - return sum + orderLine.getPriceWithoutTax(); - }), 0); - }, - getTax: function() { - return (this.get('orderLines')).reduce((function(sum, orderLine) { - return sum + orderLine.getTax(); - }), 0); - }, - getPaidTotal: function() { - return (this.get('paymentLines')).reduce((function(sum, paymentLine) { - return sum + paymentLine.getAmount(); - }), 0); - }, - getChange: function() { - return this.getPaidTotal() - this.getTotal(); - }, - getDueLeft: function() { - return this.getTotal() - this.getPaidTotal(); - }, - exportAsJSON: function() { - var orderLines, paymentLines; - orderLines = []; - (this.get('orderLines')).each(_.bind( function(item) { - return orderLines.push([0, 0, item.exportAsJSON()]); - }, this)); - paymentLines = []; - (this.get('paymentLines')).each(_.bind( function(item) { - return paymentLines.push([0, 0, item.exportAsJSON()]); - }, this)); - return { - name: this.getName(), - amount_paid: this.getPaidTotal(), - amount_total: this.getTotal(), - amount_tax: this.getTax(), - amount_return: this.getChange(), - lines: orderLines, - statement_ids: paymentLines - }; - }, - }); - - namespace.OrderCollection = Backbone.Collection.extend({ - model: namespace.Order, - }); - - namespace.Shop = Backbone.Model.extend({ - initialize: function(attributes) { - var self = this; - this.set({ - orders: new namespace.OrderCollection(), - products: new namespace.ProductCollection(), - }); - this.posmodel = attributes.posmodel; - this.set({ - cashRegisters: new namespace.CashRegisterCollection(this.posmodel.get('bank_statements')), - }); - return (this.get('orders')).bind('remove', _.bind( function(removedOrder) { - if ((this.get('orders')).isEmpty()) { - this.addAndSelectOrder(new namespace.Order({posmodel: self.posmodel})); - } - if ((this.get('selectedOrder')) === removedOrder) { - return this.set({ - selectedOrder: (this.get('orders')).last() - }); - } - }, this)); - }, - addAndSelectOrder: function(newOrder) { - (this.get('orders')).add(newOrder); - return this.set({ - selectedOrder: newOrder - }); - }, - }); - - /* - The numpad handles both the choice of the property currently being modified - (quantity, price or discount) and the edition of the corresponding numeric value. - */ - namespace.NumpadState = Backbone.Model.extend({ - defaults: { - buffer: "0", - mode: "quantity" - }, - appendNewChar: function(newChar) { - var oldBuffer; - oldBuffer = this.get('buffer'); - if (oldBuffer === '0') { - this.set({ - buffer: newChar - }); - } else if (oldBuffer === '-0') { - this.set({ - buffer: "-" + newChar - }); - } else { - this.set({ - buffer: (this.get('buffer')) + newChar - }); - } - this.updateTarget(); - }, - deleteLastChar: function() { - var tempNewBuffer; - tempNewBuffer = (this.get('buffer')).slice(0, -1) || "0"; - if (isNaN(tempNewBuffer)) { - tempNewBuffer = "0"; - } - this.set({ - buffer: tempNewBuffer - }); - this.updateTarget(); - }, - switchSign: function() { - var oldBuffer; - oldBuffer = this.get('buffer'); - this.set({ - buffer: oldBuffer[0] === '-' ? oldBuffer.substr(1) : "-" + oldBuffer - }); - this.updateTarget(); - }, - changeMode: function(newMode) { - this.set({ - buffer: "0", - mode: newMode - }); - }, - reset: function() { - this.set({ - buffer: "0", - mode: "quantity" - }); - }, - updateTarget: function() { - var bufferContent, params; - bufferContent = this.get('buffer'); - if (bufferContent && !isNaN(bufferContent)) { - this.trigger('setValue', parseFloat(bufferContent)); - } - }, - }); - - /* - --- - Views - --- - */ - namespace.NumpadWidget = instance.web.OldWidget.extend({ - init: function(parent, options) { - this._super(parent); - this.state = new namespace.NumpadState(); - }, - start: function() { - this.state.bind('change:mode', this.changedMode, this); - this.changedMode(); - this.$element.find('button#numpad-backspace').click(_.bind(this.clickDeleteLastChar, this)); - this.$element.find('button#numpad-minus').click(_.bind(this.clickSwitchSign, this)); - this.$element.find('button.number-char').click(_.bind(this.clickAppendNewChar, this)); - this.$element.find('button.mode-button').click(_.bind(this.clickChangeMode, this)); - }, - clickDeleteLastChar: function() { - return this.state.deleteLastChar(); - }, - clickSwitchSign: function() { - return this.state.switchSign(); - }, - clickAppendNewChar: function(event) { - var newChar; - newChar = event.currentTarget.innerText || event.currentTarget.textContent; - return this.state.appendNewChar(newChar); - }, - clickChangeMode: function(event) { - var newMode = event.currentTarget.attributes['data-mode'].nodeValue; - return this.state.changeMode(newMode); - }, - changedMode: function() { - var mode = this.state.get('mode'); - $('.selected-mode').removeClass('selected-mode'); - $(_.str.sprintf('.mode-button[data-mode="%s"]', mode), this.$element).addClass('selected-mode'); - }, - }); - /* - Gives access to the payment methods (aka. 'cash registers') - */ - namespace.PaypadWidget = instance.web.OldWidget.extend({ - init: function(parent, options) { - this._super(parent); - this.shop = options.shop; - }, - start: function() { - this.$element.find('button').click(_.bind(this.performPayment, this)); - }, - performPayment: function(event) { - if (this.shop.get('selectedOrder').get('step') === 'receipt') - return; - var cashRegister, cashRegisterCollection, cashRegisterId; - /* set correct view */ - this.shop.get('selectedOrder').set({'step': 'payment'}); - - cashRegisterId = event.currentTarget.attributes['cash-register-id'].nodeValue; - cashRegisterCollection = this.shop.get('cashRegisters'); - cashRegister = cashRegisterCollection.find(_.bind( function(item) { - return (item.get('id')) === parseInt(cashRegisterId, 10); - }, this)); - return (this.shop.get('selectedOrder')).addPaymentLine(cashRegister); - }, - renderElement: function() { - this.$element.empty(); - return (this.shop.get('cashRegisters')).each(_.bind( function(cashRegister) { - var button = new namespace.PaymentButtonWidget(); - button.model = cashRegister; - button.appendTo(this.$element); - }, this)); - } - }); - - namespace.PaymentButtonWidget = instance.web.OldWidget.extend({ - template_fct: qweb_template('pos-payment-button-template'), - renderElement: function() { - this.$element.html(this.template_fct({ - id: this.model.get('id'), - name: (this.model.get('journal_id'))[1] - })); - return this; - } - }); - /* - There are 3 steps in a POS workflow: - 1. prepare the order (i.e. chose products, quantities etc.) - 2. choose payment method(s) and amount(s) - 3. validae order and print receipt - It should be possible to go back to any step as long as step 3 hasn't been completed. - Modifying an order after validation shouldn't be allowed. - */ - namespace.StepSwitcher = instance.web.OldWidget.extend({ - init: function(parent, options) { - this._super(parent); - this.shop = options.shop; - this.change_order(); - this.shop.bind('change:selectedOrder', this.change_order, this); - }, - change_order: function() { - if (this.selected_order) { - this.selected_order.unbind('change:step', this.change_step); - } - this.selected_order = this.shop.get('selectedOrder'); - if (this.selected_order) { - this.selected_order.bind('change:step', this.change_step, this); - } - this.change_step(); - }, - change_step: function() { - var new_step = this.selected_order ? this.selected_order.get('step') : 'products'; - $('.step-screen').hide(); - $('#' + new_step + '-screen').show(); - }, - }); - /* - Shopping carts. - */ - namespace.OrderlineWidget = instance.web.OldWidget.extend({ - tagName: 'tr', - template_fct: qweb_template('pos-orderline-template'), - init: function(parent, options) { - this._super(parent); - this.model = options.model; - this.model.bind('change', _.bind( function() { - this.refresh(); - }, this)); - this.model.bind('remove', _.bind( function() { - this.$element.remove(); - }, this)); - this.order = options.order; - }, - start: function() { - this.$element.click(_.bind(this.clickHandler, this)); - this.refresh(); - }, - clickHandler: function() { - this.select(); - }, - renderElement: function() { - this.$element.html(this.template_fct(this.model.toJSON())); - this.select(); - }, - 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'); - this.order.selected = this.model; - this.on_selected(); - }, - on_selected: function() {}, - }); - - namespace.OrderWidget = instance.web.OldWidget.extend({ - init: function(parent, options) { - this._super(parent); - this.shop = options.shop; - this.setNumpadState(options.numpadState); - this.shop.bind('change:selectedOrder', this.changeSelectedOrder, this); - this.bindOrderLineEvents(); - }, - setNumpadState: function(numpadState) { - if (this.numpadState) { - this.numpadState.unbind('setValue', this.setValue); - } - this.numpadState = numpadState; - if (this.numpadState) { - this.numpadState.bind('setValue', this.setValue, this); - this.numpadState.reset(); - } - }, - setValue: function(val) { - var param = {}; - param[this.numpadState.get('mode')] = val; - var order = this.shop.get('selectedOrder'); - if (order.get('orderLines').length !== 0) { - order.selected.set(param); - } else { - this.shop.get('selectedOrder').destroy(); - } - }, - changeSelectedOrder: function() { - this.currentOrderLines.unbind(); - this.bindOrderLineEvents(); - this.renderElement(); - }, - bindOrderLineEvents: function() { - this.currentOrderLines = (this.shop.get('selectedOrder')).get('orderLines'); - this.currentOrderLines.bind('add', this.addLine, this); - this.currentOrderLines.bind('remove', this.renderElement, this); - }, - addLine: function(newLine) { - var line = new namespace.OrderlineWidget(null, { - model: newLine, - order: this.shop.get('selectedOrder') - }); - line.on_selected.add(_.bind(this.selectedLine, this)); - this.selectedLine(); - line.appendTo(this.$element); - this.updateSummary(); - }, - selectedLine: function() { - var reset = false; - if (this.currentSelected !== this.shop.get('selectedOrder').selected) { - reset = true; - } - this.currentSelected = this.shop.get('selectedOrder').selected; - if (reset && this.numpadState) - this.numpadState.reset(); - this.updateSummary(); - }, - renderElement: function() { - this.$element.empty(); - this.currentOrderLines.each(_.bind( function(orderLine) { - var line = new namespace.OrderlineWidget(null, { - model: orderLine, - order: this.shop.get('selectedOrder') - }); - line.on_selected.add(_.bind(this.selectedLine, this)); - line.appendTo(this.$element); - }, this)); - this.updateSummary(); - }, - updateSummary: function() { - var currentOrder, tax, total, totalTaxExcluded; - currentOrder = this.shop.get('selectedOrder'); - total = currentOrder.getTotal(); - totalTaxExcluded = currentOrder.getTotalTaxExcluded(); - tax = currentOrder.getTax(); - $('#subtotal').html(totalTaxExcluded.toFixed(2)).hide().fadeIn(); - $('#tax').html(tax.toFixed(2)).hide().fadeIn(); - $('#total').html(total.toFixed(2)).hide().fadeIn(); - }, - }); - - /* - "Products" step. - */ - namespace.CategoryWidget = instance.web.OldWidget.extend({ - init: function(parent, options){ - this._super(parent,options.element_id); - this.posmodel = options.posmodel; - }, - start: function() { - this.$element.find(".oe-pos-categories-list a").click(_.bind(this.changeCategory, this)); - }, - template_fct: qweb_template('pos-category-template'), - renderElement: function() { - var self = this; - var c; - this.$element.html(this.template_fct({ - breadcrumb: (function() { - var _i, _len, _results; - _results = []; - for (_i = 0, _len = self.ancestors.length; _i < _len; _i++) { - c = self.ancestors[_i]; - _results.push(self.posmodel.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.posmodel.categories[c]); - } - return _results; - })() - })); - }, - changeCategory: function(a) { - var id = $(a.target).data("category-id"); - this.on_change_category(id); - }, - on_change_category: function(id) {}, - }); - - namespace.ProductWidget = instance.web.OldWidget.extend({ - tagName:'li', - template_fct: qweb_template('pos-product-template'), - init: function(parent, options) { - this._super(parent); - this.model = options.model; - this.shop = options.shop; - }, - start: function(options) { - $("a", this.$element).click(_.bind(this.addToOrder, this)); - }, - addToOrder: function(event) { - /* Preserve the category URL */ - event.preventDefault(); - return (this.shop.get('selectedOrder')).addProduct(this.model); - }, - renderElement: function() { - this.$element.addClass("product"); - this.$element.html(this.template_fct(this.model.toJSON())); - return this; - }, - }); - - namespace.ProductListWidget = instance.web.OldWidget.extend({ - init: function(parent, options) { - this._super(parent); - this.model = options.model; - this.shop = options.shop; - this.shop.get('products').bind('reset', this.renderElement, this); - }, - renderElement: function() { - this.$element.empty(); - (this.shop.get('products')).each(_.bind( function(product) { - var p = new namespace.ProductWidget(null, { - model: product, - shop: this.shop - }); - p.appendTo(this.$element); - }, this)); - return this; - }, - }); - /* - "Payment" step. - */ - namespace.PaymentlineWidget = instance.web.OldWidget.extend({ - tagName: 'tr', - template_fct: qweb_template('pos-paymentline-template'), - init: function(parent, options) { - this._super(parent); - this.model = options.model; - this.model.bind('change', this.changedAmount, this); - }, - on_delete: function() {}, - changeAmount: function(event) { - var newAmount; - newAmount = event.currentTarget.value; - if (newAmount && !isNaN(newAmount)) { - this.amount = parseFloat(newAmount); - this.model.set({ - amount: this.amount, - }); - } - }, - changedAmount: function() { - if (this.amount !== this.model.get('amount')) - this.renderElement(); - }, - renderElement: function() { - this.amount = this.model.get('amount'); - this.$element.html(this.template_fct({ - name: (this.model.get('journal_id'))[1], - amount: this.amount, - })); - this.$element.addClass('paymentline'); - $('input', this.$element).keyup(_.bind(this.changeAmount, this)); - $('.delete-payment-line', this.$element).click(this.on_delete); - }, - }); - - namespace.PaymentWidget = instance.web.OldWidget.extend({ - init: function(parent, options) { - this._super(parent); - this.model = options.model; - this.shop = options.shop; - this.posmodel = options.posmodel; - this.shop.bind('change:selectedOrder', this.changeSelectedOrder, this); - this.bindPaymentLineEvents(); - this.bindOrderLineEvents(); - }, - paymentLineList: function() { - return this.$element.find('#paymentlines'); - }, - start: function() { - $('button#validate-order', this.$element).click(_.bind(this.validateCurrentOrder, this)); - $('.oe-back-to-products', this.$element).click(_.bind(this.back, this)); - }, - back: function() { - this.shop.get('selectedOrder').set({"step": "products"}); - }, - validateCurrentOrder: function() { - var callback, currentOrder; - currentOrder = this.shop.get('selectedOrder'); - $('button#validate-order', this.$element).attr('disabled', 'disabled'); - this.posmodel.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.shop.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.shop.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 namespace.PaymentlineWidget(null, { - model: newPaymentLine - }); - x.on_delete.add(_.bind(this.deleteLine, this, x)); - x.appendTo(this.paymentLineList()); - }, - renderElement: function() { - this.paymentLineList().empty(); - this.currentPaymentLines.each(_.bind( function(paymentLine) { - this.addPaymentLine(paymentLine); - }, this)); - this.updatePaymentSummary(); - }, - deleteLine: function(lineWidget) { - this.currentPaymentLines.remove([lineWidget.model]); - }, - updatePaymentSummary: function() { - var currentOrder, dueTotal, paidTotal, remaining, remainingAmount; - currentOrder = this.shop.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}); - }, - }); - - namespace.ReceiptWidget = instance.web.OldWidget.extend({ - init: function(parent, options) { - this._super(parent); - this.model = options.model; - this.shop = options.shop; - this.posmodel = options.posmodel; - this.user = this.posmodel.get('user'); - this.company = this.posmodel.get('company'); - this.shop_obj = this.posmodel.get('shop'); - }, - start: function() { - this.shop.bind('change:selectedOrder', this.changeSelectedOrder, this); - this.changeSelectedOrder(); - }, - renderElement: function() { - this.$element.html(qweb_template('pos-receipt-view')); - $('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.shop.get('selectedOrder').destroy(); - }, - changeSelectedOrder: function() { - if (this.currentOrderLines) - this.currentOrderLines.unbind(); - this.currentOrderLines = (this.shop.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.shop.get('selectedOrder')).get('paymentLines'); - this.currentPaymentLines.bind('all', this.refresh, this); - this.refresh(); - }, - refresh: function() { - this.currentOrder = this.shop.get('selectedOrder'); - $('.pos-receipt-container', this.$element).html(qweb_template('pos-ticket')({widget:this})); - }, - }); - - namespace.OrderButtonWidget = instance.web.OldWidget.extend({ - tagName: 'li', - template_fct: qweb_template('pos-order-selector-button-template'), - init: function(parent, options) { - this._super(parent); - this.order = options.order; - this.shop = options.shop; - this.order.bind('destroy', _.bind( function() { - this.destroy(); - }, this)); - this.shop.bind('change:selectedOrder', _.bind( function(shop) { - var selectedOrder; - selectedOrder = shop.get('selectedOrder'); - if (this.order === selectedOrder) { - this.setButtonSelected(); - } - }, this)); - }, - start: function() { - $('button.select-order', this.$element).click(_.bind(this.selectOrder, this)); - $('button.close-order', this.$element).click(_.bind(this.closeOrder, this)); - }, - selectOrder: function(event) { - this.shop.set({ - selectedOrder: this.order - }); - }, - setButtonSelected: function() { - $('.selected-order').removeClass('selected-order'); - this.$element.addClass('selected-order'); - }, - closeOrder: function(event) { - this.order.destroy(); - }, - renderElement: function() { - this.$element.html(this.template_fct({widget:this})); - this.$element.addClass('order-selector-button'); - } - }); - - namespace.ActionButtonWidget = instance.web.Widget.extend({ - template:'pos-action-button', - init: function(parent, options){ - this._super(parent, options); - this.label = options.label || 'button'; - this.rightalign = options.rightalign || false; - if(options.icon){ - this.icon = options.icon; - this.template = 'pos-action-button-with-icon'; - } - }, - }); - - namespace.ActionbarWidget = instance.web.Widget.extend({ - template:'pos-actionbar', - init: function(parent, options){ - this._super(parent,options); - this.left_button_list = []; - this.right_button_list = []; - }, - start: function(){ - console.log('hello world!'); - 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(); - } - 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'); - } - var button = new namespace.ActionButtonWidget(this,button_options); - button_list.push(button); - button.appendTo($button_list); - }else{ - for(var i = 1; i < arguments.length; i++){ - this.addNewButton(position,arguments[i]); - } - } - return this; - } - /* - renderElement: function() { - //this.$element.html(this.template_fct()); - },*/ - }); - - // A Widget that displays an onscreen keyboard. - // There are two options when creating the widget : - // - // * 'keyboard_model' : 'simple' | 'full' (default) - // The 'full' emulates a PC keyboard, while 'simple' emulates an 'android' one. - // - // * 'input_selector : (default: '.searchbox input') - // defines the dom element that the keyboard will write to. - // - // The widget is initially hidden. It can be shown with this.show(), and is - // automatically shown when the input_selector gets focused. - namespace.OnscreenKeyboardWidget = instance.web.Widget.extend({ - tagName: 'div', - - init: function(parent, options){ - var self = this; - - this._super(parent,options); - - function get_option(opt,default_value){ - if(options){ - return options[opt] || default_value; - }else{ - return default_value; - } - } - - this.keyboard_model = get_option('keyboard_model','full'); - this.template_simple = qweb_template('pos-onscreen-keyboard-simple-template'); - this.template_full = qweb_template('pos-onscreen-keyboard-full-template'); - - this.template_fct = function(){ - if( this.keyboard_model == 'full' ){ - return this.template_full.apply(this,arguments); - }else{ - return this.template_simple.apply(this,arguments); - } - }; - - this.input_selector = get_option('input_selector','.searchbox input'); - - //show the keyboard when the input zone is clicked. - $(this.input_selector).focus(function(){self.show();}); - - //Keyboard state - this.capslock = false; - this.shift = false; - this.numlock = false; - }, - - // Write a character to the input zone - writeCharacter: function(character){ - var $input = $(this.input_selector); - $input[0].value += character; - $input.keydown(); - $input.keyup(); - }, - - // Sends a 'return' character to the input zone. TODO - sendReturn: function(){ - }, - - // Removes the last character from the input zone. - deleteCharacter: function(){ - var $input = $(this.input_selector); - var input_value = $input[0].value; - $input[0].value = input_value.substr(0, input_value.length - 1); - $input.keydown(); - $input.keyup(); - }, - - // Clears the content of the input zone. - deleteAllCharacters: function(){ - var $input = $(this.input_selector); - $input[0].value = ""; - $input.keydown(); - $input.keyup(); - }, - renderElement: function(){ - this.$element.html(this.template_fct()); - }, - - // Makes the keyboard show and slide from the bottom of the screen. - show: function(){ - $('.keyboard_frame').show().animate({'height':'235px'}, 500, 'swing'); - }, - - // Makes the keyboard hide by sliding to the bottom of the screen. - hide: function(){ - var self = this; - var frame = $('.keyboard_frame'); - frame.animate({'height':'0'}, 500, 'swing', function(){ frame.hide(); self.reset(); }); - }, - - //What happens when the shift key is pressed : toggle case, remove capslock - toggleShift: function(){ - $('.letter').toggleClass('uppercase'); - $('.symbol span').toggle(); - - self.shift = (self.shift === true) ? false : true; - self.capslock = false; - }, - - //what happens when capslock is pressed : toggle case, set capslock - toggleCapsLock: function(){ - $('.letter').toggleClass('uppercase'); - self.capslock = true; - }, - - //What happens when numlock is pressed : toggle symbols and numlock label - toggleNumLock: function(){ - $('.symbol span').toggle(); - $('.numlock span').toggle(); - self.numlock = (self.numlock === true ) ? false : true; - }, - - //After a key is pressed, shift is disabled. - removeShift: function(){ - if (self.shift === true) { - $('.symbol span').toggle(); - if (this.capslock === false) $('.letter').toggleClass('uppercase'); - - self.shift = false; - } - }, - - // Resets the keyboard to its original state; capslock: false, shift: false, numlock: false - reset: function(){ - if(this.shift){ - this.toggleShift(); - } - if(this.capslock){ - this.toggleCapsLock(); - } - if(this.numlock){ - this.toggleNumLock(); - } - }, - - //called after the keyboard is in the DOM, sets up the key bindings. - start: function(){ - var self = this; - - //this.show(); - - - $('.close_button').click(function(){ - self.deleteAllCharacters(); - self.hide(); - }); - - // Keyboard key click handling - $('.keyboard li').click(function(){ - - var $this = $(this), - character = $this.html(); // If it's a lowercase letter, nothing happens to this variable - - if ($this.hasClass('left-shift') || $this.hasClass('right-shift')) { - self.toggleShift(); - return false; - } - - if ($this.hasClass('capslock')) { - self.toggleCapsLock(); - return false; - } - - if ($this.hasClass('delete')) { - self.deleteCharacter(); - return false; - } - - if ($this.hasClass('numlock')){ - self.toggleNumLock(); - return false; - } - - // Special characters - if ($this.hasClass('symbol')) character = $('span:visible', $this).html(); - if ($this.hasClass('space')) character = ' '; - if ($this.hasClass('tab')) character = "\t"; - if ($this.hasClass('return')) character = "\n"; - - // Uppercase letter - if ($this.hasClass('uppercase')) character = character.toUpperCase(); - - // Remove shift once a key is clicked. - self.removeShift(); - - self.writeCharacter(character); - }); - }, - }); - - namespace.ShopWidget = instance.web.OldWidget.extend({ - init: function(parent, options) { - this._super(parent); - this.shop = options.shop; - this.posmodel = options.posmodel; - }, - start: function() { - $('button#neworder-button', this.$element).click(_.bind(this.createNewOrder, this)); - - (this.shop.get('orders')).bind('add', this.orderAdded, this); - (this.shop.get('orders')).add(new namespace.Order({'posmodel':this.posmodel})); - this.productListView = new namespace.ProductListWidget(null, { - shop: this.shop - }); - this.productListView.$element = $("#products-screen-ol"); - this.productListView.renderElement(); - this.productListView.start(); - this.paypadView = new namespace.PaypadWidget(null, { - shop: this.shop - }); - this.paypadView.$element = $('#paypad'); - this.paypadView.renderElement(); - this.paypadView.start(); - this.numpadView = new namespace.NumpadWidget(null); - this.numpadView.$element = $('#numpad'); - this.numpadView.start(); - this.orderView = new namespace.OrderWidget(null, { - shop: this.shop, - }); - this.orderView.$element = $('#current-order-content'); - this.orderView.start(); - this.paymentView = new namespace.PaymentWidget(null, { - shop: this.shop, - posmodel: this.posmodel, - }); - this.paymentView.$element = $('#payment-screen'); - this.paymentView.renderElement(); - this.paymentView.start(); - this.receiptView = new namespace.ReceiptWidget(null, { - shop: this.shop, - posmodel: this.posmodel, - }); - this.receiptView.replace($('#receipt-screen')); - this.stepSwitcher = new namespace.StepSwitcher(this, {shop: this.shop}); - this.shop.bind('change:selectedOrder', this.changedSelectedOrder, this); - this.changedSelectedOrder(); - }, - createNewOrder: function() { - var newOrder; - newOrder = new namespace.Order({'posmodel': this.posmodel}); - (this.shop.get('orders')).add(newOrder); - this.shop.set({ - selectedOrder: newOrder - }); - }, - orderAdded: function(newOrder) { - var newOrderButton; - newOrderButton = new namespace.OrderButtonWidget(null, { - order: newOrder, - shop: this.shop - }); - newOrderButton.appendTo($('#orders')); - newOrderButton.selectOrder(); - }, - changedSelectedOrder: function() { - if (this.currentOrder) { - this.currentOrder.unbind('change:step', this.changedStep); - } - this.currentOrder = this.shop.get('selectedOrder'); - this.currentOrder.bind('change:step', this.changedStep, this); - this.changedStep(); - }, - changedStep: function() { - var step = this.currentOrder.get('step'); - this.orderView.setNumpadState(null); - this.paymentView.setNumpadState(null); - if (step === 'products') { - this.orderView.setNumpadState(this.numpadView.state); - } else if (step === 'payment') { - this.paymentView.setNumpadState(this.numpadView.state); - } - }, - }); - - namespace.App = (function() { - - function App($element, posmodel) { - this.initialize($element, posmodel); - } - - App.prototype.initialize = function($element, posmodel) { - this.posmodel = posmodel; - this.shop = new namespace.Shop({'posmodel': posmodel}); - this.shopView = new namespace.ShopWidget(null, { - shop: this.shop, - 'posmodel': posmodel, - }); - this.shopView.$element = $element; - this.shopView.start(); - this.categoryView = new namespace.CategoryWidget(null, {element_id: 'products-screen-categories', posmodel: posmodel} ); - this.categoryView.on_change_category.add_last(_.bind(this.category, this)); - this.category(); - - this.onscreenKeyboard = new namespace.OnscreenKeyboardWidget(null,{keyboard_model:'simple'}); - this.onscreenKeyboard.appendTo($(".point-of-sale #content")); - - this.actionBar = new namespace.ActionbarWidget(null); - this.actionBar.appendTo($(".point-of-sale #content")); - - this.actionBar.addNewButton('left',{ - label : 'Aide', - icon : '/point_of_sale/static/src/img/icons/png48/help-browser.png', - }); - this.actionBar.addNewButton('left',{'label':'test'}); - this.actionBar.addNewButton('left',{'label':'kikoo', rightalign:true}); - - this.actionBar.addNewButton('right',{'label':'boo'}); - this.actionBar.addNewButton('right',{ - label : 'Payer', - rightalign : true, - icon : '/point_of_sale/static/src/img/icons/png48/go-next.png', - }); - this.actionBar.addNewButton('right',{ - label : 'Ook Ook', - rightalign : true, - icon : '/point_of_sale/static/src/img/icons/png48/face-monkey.png', - }); - }; - - //returns true if the code is a valid EAN codebar number by checking the control digit. - App.checkEan = function(code){ - 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); - } - 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; - }; - - // returns a product that has a packaging with an EAN matching to provided ean string. - // returns undefined if no such product is found. - App.getProductByEAN = function(ean, allPackages, allProducts) { - 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; - }; - - App.prototype.category = function(id) { - var c, product_list, self = this; - - id = !id ? 0 : id; - - c = this.posmodel.categories[id]; - this.categoryView.ancestors = c.ancestors; - this.categoryView.children = c.children; - this.categoryView.renderElement(); - this.categoryView.start(); - - allProducts = this.posmodel.get('product_list'); - - allPackages = this.posmodel.get('product.packaging'); - - product_list = this.posmodel.get('product_list').filter( function(p) { - var _ref; - return _ref = p.pos_categ_id[0], _.indexOf(c.subtree, _ref) >= 0; - }); - (this.shop.get('products')).reset(product_list); - - var codeNumbers = []; - - // 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 - if (!isNaN(Number(String.fromCharCode(e.keyCode)))) { - - // 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 { - if (lastTimeStamp + 30 < new Date().getTime()) { - // not a barcode reader - codeNumbers = []; - timeStamp = new Date().getTime(); - } - } - codeNumbers.push(e.keyCode - 48); - lastTimeStamp = new Date().getTime(); - if (codeNumbers.length == 13) { - console.log('found code:', codeNumbers.join('')); - - // a barcode reader - if (!App.checkEan(codeNumbers)) { - // barcode read error, raise warning - $(QWeb.render('pos-scan-warning')).dialog({ - resizable: false, - height:220, - modal: true, - title: "Warning", - }); - } - var selectedOrder = self.shop.get('selectedOrder'); - var scannedProductModel = App.getProductByEAN(codeNumbers.join(''),allPackages,allProducts); - if (scannedProductModel === undefined) { - // product not recognized, raise warning - $(QWeb.render('pos-scan-warning')).dialog({ - resizable: false, - height:220, - modal: true, - title: "Warning", - /* - buttons: { - "OK": function() { - $( this ).dialog( "close" ); - return; - }, - }*/ - }); - } else { - selectedOrder.addProduct(new namespace.Product(scannedProductModel)); - } - - codeNumbers = []; - } - } else { - // NaN - codeNumbers = []; - } - }); - - $('.searchbox input').keyup(function() { - var m, s; - s = $(this).val().toLowerCase(); - if (s) { - m = product_list.filter( function(p) { - return p.name.toLowerCase().indexOf(s) != -1; - }); - $('.search-clear').fadeIn(); - } else { - m = product_list; - $('.search-clear').fadeOut(); - } - return (self.shop.get('products')).reset(m); - }); - return $('.search-clear').click( function() { - (self.shop.get('products')).reset(product_list); - $('.searchbox input').val('').focus(); - return $('.search-clear').fadeOut(); - }); - }; - return App; - })(); - - namespace.SynchNotification = instance.web.OldWidget.extend({ - template: "pos-synch-notification", - init: function() { - this._super.apply(this, arguments); - this.nbr_pending = 0; - }, - renderElement: function() { - this._super.apply(this, arguments); - $('.oe_pos_synch-notification-button', this.$element).click(this.on_synch); - }, - on_change_nbr_pending: function(nbr_pending) { - this.nbr_pending = nbr_pending; - this.renderElement(); - }, - on_synch: function() {} - }); - - instance.web.client_actions.add('pos.ui', 'instance.point_of_sale.POSWidget'); - - namespace.POSWidget = instance.web.OldWidget.extend({ - init: function() { - this._super.apply(this, arguments); - - this.posmodel = new namespace.PosModel(this.session); - - }, - start: function() { - var self = this; - return self.posmodel.ready.then(_.bind(function() { - this.renderElement(); - this.synch_notification = new namespace.SynchNotification(this); - this.synch_notification.replace($('.oe_pos_synch-notification', this.$element)); - this.synch_notification.on_synch.add(_.bind(self.posmodel.flush, self.posmodel)); - - self.posmodel.bind('change:nbr_pending_operations', this.changed_pending_operations, this); - this.changed_pending_operations(); - - this.$element.find("#loggedas button").click(function() { - self.try_close(); - }); - - self.posmodel.app = new namespace.App(self.$element, self.posmodel); - instance.webclient.set_content_full_screen(true); - - if (self.posmodel.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() { - return qweb_template("POSWidget")(); - }, - changed_pending_operations: function () { - var self = this; - this.synch_notification.on_change_nbr_pending(self.posmodel.get('nbr_pending_operations').length); - }, - try_close: function() { - var self = this; - self.posmodel.flush().then(_.bind(function() { - var close = _.bind(this.close, this); - if (self.posmodel.get('nbr_pending_operations').length > 0) { - var confirm = false; - $(QWeb.render('pos-close-warning')).dialog({ - resizable: false, - height:160, - modal: true, - title: "Warning", - buttons: { - "Yes": function() { - confirm = true; - $( this ).dialog( "close" ); - }, - "No": function() { - $( this ).dialog( "close" ); - } - }, - close: function() { - if (confirm) - close(); - } - }); - } else { - close(); - } - }, this)); - }, - close: function() { - // remove barcode reader event listener - $('body').undelegate('', 'keyup') - - 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); - self.posmodel = undefined; - this._super(); - } - }); -} From e8e7304723fa44a7e4d00a5272c396492f86a32e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20van=20der=20Essen?= Date: Tue, 24 Apr 2012 19:20:47 +0200 Subject: [PATCH 50/54] [PoS] Merged the PosModel and Shop classes bzr revid: fva@openerp.com-20120424172047-1wz31qi2hih3decs --- .../point_of_sale/static/src/js/pos_models.js | 140 ++++++++------ .../static/src/js/pos_widgets.js | 174 +++++++++--------- 2 files changed, 167 insertions(+), 147 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 a3e60f676df..9323b23e6aa 100644 --- a/addons/point_of_sale/static/src/js/pos_models.js +++ b/addons/point_of_sale/static/src/js/pos_models.js @@ -62,7 +62,11 @@ function pos_models(module, instance){ 'currency': {symbol: '$', position: 'after'}, 'shop': {}, 'company': {}, - 'user': {} + 'user': {}, + 'orders': new module.OrderCollection(), + 'products': new module.ProductCollection(), + //'cashRegisters': [], // new module.CashRegisterCollection(this.pos.get('bank_statements')), + 'selectedOrder': undefined, }); var cat_def = fetch('pos.category', ['name', 'parent_id', 'child_id']) @@ -92,7 +96,23 @@ function pos_models(module, instance){ }); $.when(cat_def,prod_def,bank_def,tax_def,this.get_app_data(), this.flush()) - .pipe(_.bind(this.build_tree, this)); + .pipe(_.bind(this.build_tree, this)) + .pipe(function(){ + self.set({'cashRegisters': new module.CashRegisterCollection(self.get('bank_statements')) }); + self.ready.resolve(); + }); + + return (this.get('orders')).bind('remove', _.bind( function(removedOrder) { + if ((this.get('orders')).isEmpty()) { + this.addAndSelectOrder(new module.Order({pos: self})); + } + if ((this.get('selectedOrder')) === removedOrder) { + return this.set({ + selectedOrder: (this.get('orders')).last() + }); + } + }, this)); + }, get_app_data: function() { @@ -119,6 +139,12 @@ function pos_models(module, instance){ return self.flush(); }); }, + addAndSelectOrder: function(newOrder) { + (this.get('orders')).add(newOrder); + return this.set({ + selectedOrder: newOrder + }); + }, flush: function() { return this.flush_mutex.exec(_.bind(function() { return this._int_flush(); @@ -197,7 +223,6 @@ function pos_models(module, instance){ return _results; }).call(this) }; - return this.ready.resolve(); }, build_ancestors: function(parent) { if (parent != null) { @@ -217,6 +242,36 @@ function pos_models(module, instance){ return _results; } }); + /* + module.Shop = Backbone.Model.extend({ + initialize: function(attributes) { + var self = this; + this.set({ + orders: new module.OrderCollection(), + products: new module.ProductCollection(), + }); + this.pos = attributes.pos; + this.set({ + cashRegisters: new module.CashRegisterCollection(this.pos.get('bank_statements')), + }); + return (this.get('orders')).bind('remove', _.bind( function(removedOrder) { + if ((this.get('orders')).isEmpty()) { + this.addAndSelectOrder(new module.Order({pos: self.pos})); + } + if ((this.get('selectedOrder')) === removedOrder) { + return this.set({ + selectedOrder: (this.get('orders')).last() + }); + } + }, this)); + }, + addAndSelectOrder: function(newOrder) { + (this.get('orders')).add(newOrder); + return this.set({ + selectedOrder: newOrder + }); + }, + });*/ module.CashRegister = Backbone.Model.extend({ }); @@ -245,7 +300,7 @@ function pos_models(module, instance){ discount: 0 }, initialize: function(attributes) { - this.posmodel = attributes.posmodel; + this.pos = attributes.pos; Backbone.Model.prototype.initialize.apply(this, arguments); this.bind('change:quantity', function(unused, qty) { if (qty == 0) @@ -272,10 +327,10 @@ function pos_models(module, instance){ var totalTax = base; var totalNoTax = base; - var product_list = self.posmodel.get('product_list'); + var product_list = self.pos.get('product_list'); var product = _.detect(product_list, function(el) {return el.id === self.get('id');}); var taxes_ids = product.taxes_id; - var taxes = self.posmodel.get('taxes'); + var taxes = self.pos.get('taxes'); var taxtotal = 0; _.each(taxes_ids, function(el) { var tax = _.detect(taxes, function(t) {return t.id === el;}); @@ -362,7 +417,7 @@ function pos_models(module, instance){ paymentLines: new module.PaymentlineCollection(), name: "Order " + this.generateUniqueId(), }); - this.posmodel = attributes.posmodel; //TODO put that in set and remember to use 'get' to read it ... + this.pos = attributes.pos; //TODO put that in set and remember to use 'get' to read it ... this.bind('change:validated', this.validatedChanged); return this; }, @@ -384,7 +439,7 @@ function pos_models(module, instance){ existing.incrementQuantity(); } else { var attr = product.toJSON(); - attr.posmodel = this.posmodel; + attr.pos = this.pos; var line = new module.Orderline(attr); this.get('orderLines').add(line); line.bind('killme', function() { @@ -456,35 +511,6 @@ function pos_models(module, instance){ model: module.Order, }); - module.Shop = Backbone.Model.extend({ - initialize: function(attributes) { - var self = this; - this.set({ - orders: new module.OrderCollection(), - products: new module.ProductCollection(), - }); - this.posmodel = attributes.posmodel; - this.set({ - cashRegisters: new module.CashRegisterCollection(this.posmodel.get('bank_statements')), - }); - return (this.get('orders')).bind('remove', _.bind( function(removedOrder) { - if ((this.get('orders')).isEmpty()) { - this.addAndSelectOrder(new module.Order({posmodel: self.posmodel})); - } - if ((this.get('selectedOrder')) === removedOrder) { - return this.set({ - selectedOrder: (this.get('orders')).last() - }); - } - }, this)); - }, - addAndSelectOrder: function(newOrder) { - (this.get('orders')).add(newOrder); - return this.set({ - selectedOrder: newOrder - }); - }, - }); /* The numpad handles both the choice of the property currently being modified @@ -553,29 +579,27 @@ function pos_models(module, instance){ }, }); - namespace.App = (function() { + module.App = (function() { - function App($element, posmodel) { - this.initialize($element, posmodel); + function App($element, pos) { + this.initialize($element, pos); } - App.prototype.initialize = function($element, posmodel) { - this.posmodel = posmodel; - this.shop = new namespace.Shop({'posmodel': posmodel}); - this.shopView = new namespace.ShopWidget(null, { - shop: this.shop, - 'posmodel': posmodel, + App.prototype.initialize = function($element, pos) { + this.pos = pos; + this.shopView = new module.ShopWidget(null, { + 'pos': pos, }); this.shopView.$element = $element; this.shopView.start(); - this.categoryView = new namespace.CategoryWidget(null, {element_id: 'products-screen-categories', posmodel: posmodel} ); + this.categoryView = new module.CategoryWidget(null, {element_id: 'products-screen-categories', pos: pos} ); this.categoryView.on_change_category.add_last(_.bind(this.category, this)); this.category(); - this.onscreenKeyboard = new namespace.OnscreenKeyboardWidget(null,{keyboard_model:'simple'}); + this.onscreenKeyboard = new module.OnscreenKeyboardWidget(null,{keyboard_model:'simple'}); this.onscreenKeyboard.appendTo($(".point-of-sale #content")); - this.actionBar = new namespace.ActionbarWidget(null); + this.actionBar = new module.ActionbarWidget(null); this.actionBar.appendTo($(".point-of-sale #content")); this.actionBar.addNewButton('left',{ @@ -665,21 +689,21 @@ function pos_models(module, instance){ id = !id ? 0 : id; - c = this.posmodel.categories[id]; + c = this.pos.categories[id]; this.categoryView.ancestors = c.ancestors; this.categoryView.children = c.children; this.categoryView.renderElement(); this.categoryView.start(); - allProducts = this.posmodel.get('product_list'); + allProducts = this.pos.get('product_list'); - allPackages = this.posmodel.get('product.packaging'); + allPackages = this.pos.get('product.packaging'); - product_list = this.posmodel.get('product_list').filter( function(p) { + product_list = this.pos.get('product_list').filter( function(p) { var _ref; return _ref = p.pos_categ_id[0], _.indexOf(c.subtree, _ref) >= 0; }); - (this.shop.get('products')).reset(product_list); + (this.pos.get('products')).reset(product_list); var codeNumbers = []; @@ -716,7 +740,7 @@ function pos_models(module, instance){ title: "Warning", }); } - var selectedOrder = self.shop.get('selectedOrder'); + var selectedOrder = self.pos.get('selectedOrder'); var scannedProductModel = App.getProductByEAN(codeNumbers.join(''),allPackages,allProducts); if (scannedProductModel === undefined) { // product not recognized, raise warning @@ -734,7 +758,7 @@ function pos_models(module, instance){ }*/ }); } else { - selectedOrder.addProduct(new namespace.Product(scannedProductModel)); + selectedOrder.addProduct(new module.Product(scannedProductModel)); } codeNumbers = []; @@ -757,10 +781,10 @@ function pos_models(module, instance){ m = product_list; $('.search-clear').fadeOut(); } - return (self.shop.get('products')).reset(m); + return (self.pos.get('products')).reset(m); }); return $('.search-clear').click( function() { - (self.shop.get('products')).reset(product_list); + (self.pos.get('products')).reset(product_list); $('.searchbox input').val('').focus(); return $('.search-clear').fadeOut(); }); 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 5afe52cddf4..6536b6be0a4 100644 --- a/addons/point_of_sale/static/src/js/pos_widgets.js +++ b/addons/point_of_sale/static/src/js/pos_widgets.js @@ -1,9 +1,9 @@ function pos_widgets(module, instance){ var QWeb = instance.web.qweb; - var qweb_template = function(template,posmodel){ + var qweb_template = function(template,pos){ return function(ctx){ - if(!posmodel){ //this is a huge hack that needs to be removed ... TODO + if(!pos){ //this is a huge hack that needs to be removed ... TODO var HackPosModel = Backbone.Model.extend({ initialize:function(){ this.set({ @@ -11,15 +11,15 @@ function pos_widgets(module, instance){ }); }, }); - posmodel = new HackPosModel(); + pos = new HackPosModel(); } return QWeb.render(template, _.extend({}, ctx,{ - 'currency': posmodel.get('currency'), + 'currency': pos.get('currency'), 'format_amount': function(amount) { - if (posmodel.get('currency').position == 'after') { - return amount + ' ' + posmodel.get('currency').symbol; + if (pos.get('currency').position == 'after') { + return amount + ' ' + pos.get('currency').symbol; } else { - return posmodel.get('currency').symbol + ' ' + amount; + return pos.get('currency').symbol + ' ' + amount; } }, })); @@ -66,28 +66,28 @@ function pos_widgets(module, instance){ module.PaypadWidget = instance.web.OldWidget.extend({ init: function(parent, options) { this._super(parent); - this.shop = options.shop; + this.pos = options.pos; }, start: function() { this.$element.find('button').click(_.bind(this.performPayment, this)); }, performPayment: function(event) { - if (this.shop.get('selectedOrder').get('step') === 'receipt') + if (this.pos.get('selectedOrder').get('step') === 'receipt') return; var cashRegister, cashRegisterCollection, cashRegisterId; /* set correct view */ - this.shop.get('selectedOrder').set({'step': 'payment'}); + this.pos.get('selectedOrder').set({'step': 'payment'}); cashRegisterId = event.currentTarget.attributes['cash-register-id'].nodeValue; - cashRegisterCollection = this.shop.get('cashRegisters'); + cashRegisterCollection = this.pos.get('cashRegisters'); cashRegister = cashRegisterCollection.find(_.bind( function(item) { return (item.get('id')) === parseInt(cashRegisterId, 10); }, this)); - return (this.shop.get('selectedOrder')).addPaymentLine(cashRegister); + return (this.pos.get('selectedOrder')).addPaymentLine(cashRegister); }, renderElement: function() { this.$element.empty(); - return (this.shop.get('cashRegisters')).each(_.bind( function(cashRegister) { + return (this.pos.get('cashRegisters')).each(_.bind( function(cashRegister) { var button = new module.PaymentButtonWidget(); button.model = cashRegister; button.appendTo(this.$element); @@ -116,15 +116,15 @@ function pos_widgets(module, instance){ module.StepSwitcher = instance.web.OldWidget.extend({ init: function(parent, options) { this._super(parent); - this.shop = options.shop; + this.pos = options.pos; this.change_order(); - this.shop.bind('change:selectedOrder', this.change_order, this); + this.pos.bind('change:selectedOrder', this.change_order, this); }, change_order: function() { if (this.selected_order) { this.selected_order.unbind('change:step', this.change_step); } - this.selected_order = this.shop.get('selectedOrder'); + this.selected_order = this.pos.get('selectedOrder'); if (this.selected_order) { this.selected_order.bind('change:step', this.change_step, this); } @@ -183,9 +183,9 @@ function pos_widgets(module, instance){ module.OrderWidget = instance.web.OldWidget.extend({ init: function(parent, options) { this._super(parent); - this.shop = options.shop; + this.pos = options.pos; this.setNumpadState(options.numpadState); - this.shop.bind('change:selectedOrder', this.changeSelectedOrder, this); + this.pos.bind('change:selectedOrder', this.changeSelectedOrder, this); this.bindOrderLineEvents(); }, setNumpadState: function(numpadState) { @@ -201,11 +201,11 @@ function pos_widgets(module, instance){ setValue: function(val) { var param = {}; param[this.numpadState.get('mode')] = val; - var order = this.shop.get('selectedOrder'); + var order = this.pos.get('selectedOrder'); if (order.get('orderLines').length !== 0) { order.selected.set(param); } else { - this.shop.get('selectedOrder').destroy(); + this.pos.get('selectedOrder').destroy(); } }, changeSelectedOrder: function() { @@ -214,14 +214,14 @@ function pos_widgets(module, instance){ this.renderElement(); }, bindOrderLineEvents: function() { - this.currentOrderLines = (this.shop.get('selectedOrder')).get('orderLines'); + this.currentOrderLines = (this.pos.get('selectedOrder')).get('orderLines'); this.currentOrderLines.bind('add', this.addLine, this); this.currentOrderLines.bind('remove', this.renderElement, this); }, addLine: function(newLine) { var line = new module.OrderlineWidget(null, { model: newLine, - order: this.shop.get('selectedOrder') + order: this.pos.get('selectedOrder') }); line.on_selected.add(_.bind(this.selectedLine, this)); this.selectedLine(); @@ -230,10 +230,10 @@ function pos_widgets(module, instance){ }, selectedLine: function() { var reset = false; - if (this.currentSelected !== this.shop.get('selectedOrder').selected) { + if (this.currentSelected !== this.pos.get('selectedOrder').selected) { reset = true; } - this.currentSelected = this.shop.get('selectedOrder').selected; + this.currentSelected = this.pos.get('selectedOrder').selected; if (reset && this.numpadState) this.numpadState.reset(); this.updateSummary(); @@ -243,7 +243,7 @@ function pos_widgets(module, instance){ this.currentOrderLines.each(_.bind( function(orderLine) { var line = new module.OrderlineWidget(null, { model: orderLine, - order: this.shop.get('selectedOrder') + order: this.pos.get('selectedOrder') }); line.on_selected.add(_.bind(this.selectedLine, this)); line.appendTo(this.$element); @@ -252,7 +252,7 @@ function pos_widgets(module, instance){ }, updateSummary: function() { var currentOrder, tax, total, totalTaxExcluded; - currentOrder = this.shop.get('selectedOrder'); + currentOrder = this.pos.get('selectedOrder'); total = currentOrder.getTotal(); totalTaxExcluded = currentOrder.getTotalTaxExcluded(); tax = currentOrder.getTax(); @@ -268,7 +268,7 @@ function pos_widgets(module, instance){ module.CategoryWidget = instance.web.OldWidget.extend({ init: function(parent, options){ this._super(parent,options.element_id); - this.posmodel = options.posmodel; + this.pos = options.pos; }, start: function() { this.$element.find(".oe-pos-categories-list a").click(_.bind(this.changeCategory, this)); @@ -283,7 +283,7 @@ function pos_widgets(module, instance){ _results = []; for (_i = 0, _len = self.ancestors.length; _i < _len; _i++) { c = self.ancestors[_i]; - _results.push(self.posmodel.categories[c]); + _results.push(self.pos.categories[c]); } return _results; })(), @@ -292,7 +292,7 @@ function pos_widgets(module, instance){ _results = []; for (_i = 0, _len = self.children.length; _i < _len; _i++) { c = self.children[_i]; - _results.push(self.posmodel.categories[c]); + _results.push(self.pos.categories[c]); } return _results; })() @@ -311,7 +311,7 @@ function pos_widgets(module, instance){ init: function(parent, options) { this._super(parent); this.model = options.model; - this.shop = options.shop; + this.pos = options.pos; }, start: function(options) { $("a", this.$element).click(_.bind(this.addToOrder, this)); @@ -319,7 +319,7 @@ function pos_widgets(module, instance){ addToOrder: function(event) { /* Preserve the category URL */ event.preventDefault(); - return (this.shop.get('selectedOrder')).addProduct(this.model); + return (this.pos.get('selectedOrder')).addProduct(this.model); }, renderElement: function() { this.$element.addClass("product"); @@ -332,15 +332,15 @@ function pos_widgets(module, instance){ init: function(parent, options) { this._super(parent); this.model = options.model; - this.shop = options.shop; - this.shop.get('products').bind('reset', this.renderElement, this); + this.pos = options.pos; + this.pos.get('products').bind('reset', this.renderElement, this); }, renderElement: function() { this.$element.empty(); - (this.shop.get('products')).each(_.bind( function(product) { + (this.pos.get('products')).each(_.bind( function(product) { var p = new module.ProductWidget(null, { model: product, - shop: this.shop + pos: this.pos }); p.appendTo(this.$element); }, this)); @@ -389,9 +389,8 @@ function pos_widgets(module, instance){ init: function(parent, options) { this._super(parent); this.model = options.model; - this.shop = options.shop; - this.posmodel = options.posmodel; - this.shop.bind('change:selectedOrder', this.changeSelectedOrder, this); + this.pos = options.pos; + this.pos.bind('change:selectedOrder', this.changeSelectedOrder, this); this.bindPaymentLineEvents(); this.bindOrderLineEvents(); }, @@ -403,13 +402,13 @@ function pos_widgets(module, instance){ $('.oe-back-to-products', this.$element).click(_.bind(this.back, this)); }, back: function() { - this.shop.get('selectedOrder').set({"step": "products"}); + this.pos.get('selectedOrder').set({"step": "products"}); }, validateCurrentOrder: function() { var callback, currentOrder; - currentOrder = this.shop.get('selectedOrder'); + currentOrder = this.pos.get('selectedOrder'); $('button#validate-order', this.$element).attr('disabled', 'disabled'); - this.posmodel.push_order(currentOrder.exportAsJSON()).then(_.bind(function() { + this.pos.push_order(currentOrder.exportAsJSON()).then(_.bind(function() { $('button#validate-order', this.$element).removeAttr('disabled'); return currentOrder.set({ validated: true @@ -417,13 +416,13 @@ function pos_widgets(module, instance){ }, this)); }, bindPaymentLineEvents: function() { - this.currentPaymentLines = (this.shop.get('selectedOrder')).get('paymentLines'); + 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.shop.get('selectedOrder')).get('orderLines'); + this.currentOrderLines = (this.pos.get('selectedOrder')).get('orderLines'); this.currentOrderLines.bind('all', this.updatePaymentSummary, this); }, changeSelectedOrder: function() { @@ -452,7 +451,7 @@ function pos_widgets(module, instance){ }, updatePaymentSummary: function() { var currentOrder, dueTotal, paidTotal, remaining, remainingAmount; - currentOrder = this.shop.get('selectedOrder'); + currentOrder = this.pos.get('selectedOrder'); paidTotal = currentOrder.getPaidTotal(); dueTotal = currentOrder.getTotal(); this.$element.find('#payment-due-total').html(dueTotal.toFixed(2)); @@ -486,14 +485,14 @@ function pos_widgets(module, instance){ init: function(parent, options) { this._super(parent); this.model = options.model; - this.shop = options.shop; - this.posmodel = options.posmodel; - this.user = this.posmodel.get('user'); - this.company = this.posmodel.get('company'); - this.shop_obj = this.posmodel.get('shop'); + 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.shop.bind('change:selectedOrder', this.changeSelectedOrder, this); + this.pos.bind('change:selectedOrder', this.changeSelectedOrder, this); this.changeSelectedOrder(); }, renderElement: function() { @@ -505,23 +504,23 @@ function pos_widgets(module, instance){ window.print(); }, finishOrder: function() { - this.shop.get('selectedOrder').destroy(); + this.pos.get('selectedOrder').destroy(); }, changeSelectedOrder: function() { if (this.currentOrderLines) this.currentOrderLines.unbind(); - this.currentOrderLines = (this.shop.get('selectedOrder')).get('orderLines'); + 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.shop.get('selectedOrder')).get('paymentLines'); + this.currentPaymentLines = (this.pos.get('selectedOrder')).get('paymentLines'); this.currentPaymentLines.bind('all', this.refresh, this); this.refresh(); }, refresh: function() { - this.currentOrder = this.shop.get('selectedOrder'); + this.currentOrder = this.pos.get('selectedOrder'); $('.pos-receipt-container', this.$element).html(qweb_template('pos-ticket')({widget:this})); }, }); @@ -532,13 +531,13 @@ function pos_widgets(module, instance){ init: function(parent, options) { this._super(parent); this.order = options.order; - this.shop = options.shop; + this.pos = options.pos; this.order.bind('destroy', _.bind( function() { this.destroy(); }, this)); - this.shop.bind('change:selectedOrder', _.bind( function(shop) { + this.pos.bind('change:selectedOrder', _.bind( function(pos) { var selectedOrder; - selectedOrder = shop.get('selectedOrder'); + selectedOrder = pos.get('selectedOrder'); if (this.order === selectedOrder) { this.setButtonSelected(); } @@ -549,7 +548,7 @@ function pos_widgets(module, instance){ $('button.close-order', this.$element).click(_.bind(this.closeOrder, this)); }, selectOrder: function(event) { - this.shop.set({ + this.pos.set({ selectedOrder: this.order }); }, @@ -830,22 +829,21 @@ function pos_widgets(module, instance){ module.ShopWidget = instance.web.OldWidget.extend({ init: function(parent, options) { this._super(parent); - this.shop = options.shop; - this.posmodel = options.posmodel; + this.pos = options.pos; }, start: function() { $('button#neworder-button', this.$element).click(_.bind(this.createNewOrder, this)); - (this.shop.get('orders')).bind('add', this.orderAdded, this); - (this.shop.get('orders')).add(new module.Order({'posmodel':this.posmodel})); + (this.pos.get('orders')).bind('add', this.orderAdded, this); + (this.pos.get('orders')).add(new module.Order({'pos':this.pos})); this.productListView = new module.ProductListWidget(null, { - shop: this.shop + pos: this.pos, }); this.productListView.$element = $("#products-screen-ol"); this.productListView.renderElement(); this.productListView.start(); this.paypadView = new module.PaypadWidget(null, { - shop: this.shop + pos: this.pos }); this.paypadView.$element = $('#paypad'); this.paypadView.renderElement(); @@ -854,31 +852,29 @@ function pos_widgets(module, instance){ this.numpadView.$element = $('#numpad'); this.numpadView.start(); this.orderView = new module.OrderWidget(null, { - shop: this.shop, + pos: this.pos, }); this.orderView.$element = $('#current-order-content'); this.orderView.start(); this.paymentView = new module.PaymentWidget(null, { - shop: this.shop, - posmodel: this.posmodel, + pos: this.pos, }); this.paymentView.$element = $('#payment-screen'); this.paymentView.renderElement(); this.paymentView.start(); this.receiptView = new module.ReceiptWidget(null, { - shop: this.shop, - posmodel: this.posmodel, + pos: this.pos, }); this.receiptView.replace($('#receipt-screen')); - this.stepSwitcher = new module.StepSwitcher(this, {shop: this.shop}); - this.shop.bind('change:selectedOrder', this.changedSelectedOrder, this); + this.stepSwitcher = new module.StepSwitcher(this, {pos: this.pos}); + this.pos.bind('change:selectedOrder', this.changedSelectedOrder, this); this.changedSelectedOrder(); }, createNewOrder: function() { var newOrder; - newOrder = new module.Order({'posmodel': this.posmodel}); - (this.shop.get('orders')).add(newOrder); - this.shop.set({ + newOrder = new module.Order({'pos': this.pos}); + (this.pos.get('orders')).add(newOrder); + this.pos.set({ selectedOrder: newOrder }); }, @@ -886,7 +882,7 @@ function pos_widgets(module, instance){ var newOrderButton; newOrderButton = new module.OrderButtonWidget(null, { order: newOrder, - shop: this.shop + pos: this.pos }); newOrderButton.appendTo($('#orders')); newOrderButton.selectOrder(); @@ -895,7 +891,7 @@ function pos_widgets(module, instance){ if (this.currentOrder) { this.currentOrder.unbind('change:step', this.changedStep); } - this.currentOrder = this.shop.get('selectedOrder'); + this.currentOrder = this.pos.get('selectedOrder'); this.currentOrder.bind('change:step', this.changedStep, this); this.changedStep(); }, @@ -911,7 +907,7 @@ function pos_widgets(module, instance){ }, }); - namespace.SynchNotification = instance.web.OldWidget.extend({ + module.SynchNotification = instance.web.OldWidget.extend({ template: "pos-synch-notification", init: function() { this._super.apply(this, arguments); @@ -928,32 +924,32 @@ function pos_widgets(module, instance){ on_synch: function() {} }); - namespace.POSWidget = instance.web.OldWidget.extend({ + module.POSWidget = instance.web.OldWidget.extend({ init: function() { this._super.apply(this, arguments); - this.posmodel = new namespace.PosModel(this.session); + this.pos = new module.PosModel(this.session); }, start: function() { var self = this; - return self.posmodel.ready.then(_.bind(function() { + return self.pos.ready.then(_.bind(function() { this.renderElement(); - this.synch_notification = new namespace.SynchNotification(this); + this.synch_notification = new module.SynchNotification(this); this.synch_notification.replace($('.oe_pos_synch-notification', this.$element)); - this.synch_notification.on_synch.add(_.bind(self.posmodel.flush, self.posmodel)); + this.synch_notification.on_synch.add(_.bind(self.pos.flush, self.pos)); - self.posmodel.bind('change:nbr_pending_operations', this.changed_pending_operations, this); + self.pos.bind('change:nbr_pending_operations', this.changed_pending_operations, this); this.changed_pending_operations(); this.$element.find("#loggedas button").click(function() { self.try_close(); }); - self.posmodel.app = new namespace.App(self.$element, self.posmodel); + self.pos.app = new module.App(self.$element, self.pos); instance.webclient.set_content_full_screen(true); - if (self.posmodel.get('bank_statements').length === 0) + 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) { @@ -968,13 +964,13 @@ function pos_widgets(module, instance){ }, changed_pending_operations: function () { var self = this; - this.synch_notification.on_change_nbr_pending(self.posmodel.get('nbr_pending_operations').length); + this.synch_notification.on_change_nbr_pending(self.pos.get('nbr_pending_operations').length); }, try_close: function() { var self = this; - self.posmodel.flush().then(_.bind(function() { + self.pos.flush().then(_.bind(function() { var close = _.bind(this.close, this); - if (self.posmodel.get('nbr_pending_operations').length > 0) { + if (self.pos.get('nbr_pending_operations').length > 0) { var confirm = false; $(QWeb.render('pos-close-warning')).dialog({ resizable: false, @@ -1015,7 +1011,7 @@ function pos_widgets(module, instance){ }, destroy: function() { instance.webclient.set_content_full_screen(false); - self.posmodel = undefined; + self.pos = undefined; this._super(); } }); From 2ae6854f17f82547776d1d1d27e5cb493d73f318 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20van=20der=20Essen?= Date: Wed, 25 Apr 2012 13:38:02 +0200 Subject: [PATCH 51/54] extracted the Barcode reader into a new submodule bzr revid: fva@openerp.com-20120425113802-3o5qty2auy50ne8r --- addons/point_of_sale/__openerp__.py | 1 + .../static/src/js/pos_devices.js | 143 +++++++++++++++++ .../point_of_sale/static/src/js/pos_main.js | 6 +- .../point_of_sale/static/src/js/pos_models.js | 150 +----------------- .../static/src/js/pos_widgets.js | 5 +- 5 files changed, 155 insertions(+), 150 deletions(-) create mode 100644 addons/point_of_sale/static/src/js/pos_devices.js diff --git a/addons/point_of_sale/__openerp__.py b/addons/point_of_sale/__openerp__.py index f64141aa9fc..c159193e299 100644 --- a/addons/point_of_sale/__openerp__.py +++ b/addons/point_of_sale/__openerp__.py @@ -86,6 +86,7 @@ Main features : 'static/lib/backbone/backbone-0.5.3.js', 'static/src/js/pos_models.js', 'static/src/js/pos_widgets.js', + 'static/src/js/pos_devices.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 new file mode 100644 index 00000000000..80af36f3669 --- /dev/null +++ b/addons/point_of_sale/static/src/js/pos_devices.js @@ -0,0 +1,143 @@ + +function openerp_pos_devices(module, instance){ //module is instance.point_of_sale + module.BarcodeReader = instance.web.Class.extend({ + init: function(attributes){ + this.pos = attributes.pos; + }, + //returns true if the code is a valid EAN codebar number by checking the control digit. + checkEan: function(code){ + 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); + } + 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; + }, + // returns a product that has a packaging with an EAN matching to provided ean string. + // returns undefined if no such product is found. + getProductByEAN: function(ean) { + var allProducts = this.pos.get('product_list'); + var allPackages = this.pos.get('product.packaging'); + 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; + }, + //starts catching keyboard events and tries to interpret codebar + connect: function(){ + var self = this; + var codeNumbers = []; + var timeStamp = 0; + var lastTimeStamp = 0; + + // 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 + if (!isNaN(Number(String.fromCharCode(e.keyCode)))) { + + // 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 { + if (lastTimeStamp + 30 < new Date().getTime()) { + // not a barcode reader + codeNumbers = []; + timeStamp = new Date().getTime(); + } + } + codeNumbers.push(e.keyCode - 48); + lastTimeStamp = new Date().getTime(); + if (codeNumbers.length == 13) { + //console.log('found code:', codeNumbers.join('')); + + // a barcode reader + if (!self.checkEan(codeNumbers)) { + // barcode read error, raise warning + $(QWeb.render('pos-scan-warning')).dialog({ + resizable: false, + height:220, + modal: true, + title: "Warning", + }); + } + var selectedOrder = self.pos.get('selectedOrder'); + var scannedProductModel = self.getProductByEAN(codeNumbers.join(''),allPackages,allProducts); + if (scannedProductModel === undefined) { + // product not recognized, raise warning + $(QWeb.render('pos-scan-warning')).dialog({ + resizable: false, + height:220, + modal: true, + title: "Warning", + /* + buttons: { + "OK": function() { + $( this ).dialog( "close" ); + return; + }, + }*/ + }); + } else { + selectedOrder.addProduct(new module.Product(scannedProductModel)); + } + + codeNumbers = []; + } + } else { + // NaN + codeNumbers = []; + } + }); + }, + disconnect: function(){ + $('body').undelegate('', 'keyup') + }, + }); + +} 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 38503e5cfb9..29f625a9f85 100644 --- a/addons/point_of_sale/static/src/js/pos_main.js +++ b/addons/point_of_sale/static/src/js/pos_main.js @@ -5,9 +5,11 @@ openerp.point_of_sale = function(instance) { var module = instance.point_of_sale; - pos_models(module,instance); // import pos_models.js + openerp_pos_models(module,instance); // import pos_models.js - pos_widgets(module,instance); // import pos_widgets.js + openerp_pos_widgets(module,instance); // import pos_widgets.js + + openerp_pos_devices(module,instance); // import pos_devices.js instance.web.client_actions.add('pos.ui', 'instance.point_of_sale.POSWidget'); }; 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 9323b23e6aa..e1dddfafadf 100644 --- a/addons/point_of_sale/static/src/js/pos_models.js +++ b/addons/point_of_sale/static/src/js/pos_models.js @@ -1,4 +1,4 @@ -function pos_models(module, instance){ +function openerp_pos_models(module, instance){ //module is instance.point_of_sale var QWeb = instance.web.qweb; module.LocalStorageDAO = instance.web.Class.extend({ @@ -599,89 +599,13 @@ function pos_models(module, instance){ this.onscreenKeyboard = new module.OnscreenKeyboardWidget(null,{keyboard_model:'simple'}); this.onscreenKeyboard.appendTo($(".point-of-sale #content")); +/* this.actionBar = new module.ActionbarWidget(null); this.actionBar.appendTo($(".point-of-sale #content")); - - this.actionBar.addNewButton('left',{ - label : 'Aide', - icon : '/point_of_sale/static/src/img/icons/png48/help-browser.png', - }); this.actionBar.addNewButton('left',{'label':'test'}); - this.actionBar.addNewButton('left',{'label':'kikoo', rightalign:true}); - - this.actionBar.addNewButton('right',{'label':'boo'}); - this.actionBar.addNewButton('right',{ - label : 'Payer', - rightalign : true, - icon : '/point_of_sale/static/src/img/icons/png48/go-next.png', - }); - this.actionBar.addNewButton('right',{ - label : 'Ook Ook', - rightalign : true, - icon : '/point_of_sale/static/src/img/icons/png48/face-monkey.png', - }); - }; - - //returns true if the code is a valid EAN codebar number by checking the control digit. - App.checkEan = function(code){ - 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); - } - 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; - }; - - // returns a product that has a packaging with an EAN matching to provided ean string. - // returns undefined if no such product is found. - App.getProductByEAN = function(ean, allPackages, allProducts) { - 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; +*/ + this.barcodeReader = new module.BarcodeReader({ 'pos': pos }); + this.barcodeReader.connect(); }; App.prototype.category = function(id) { @@ -705,70 +629,6 @@ function pos_models(module, instance){ }); (this.pos.get('products')).reset(product_list); - var codeNumbers = []; - - // 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 - if (!isNaN(Number(String.fromCharCode(e.keyCode)))) { - - // 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 { - if (lastTimeStamp + 30 < new Date().getTime()) { - // not a barcode reader - codeNumbers = []; - timeStamp = new Date().getTime(); - } - } - codeNumbers.push(e.keyCode - 48); - lastTimeStamp = new Date().getTime(); - if (codeNumbers.length == 13) { - console.log('found code:', codeNumbers.join('')); - - // a barcode reader - if (!App.checkEan(codeNumbers)) { - // barcode read error, raise warning - $(QWeb.render('pos-scan-warning')).dialog({ - resizable: false, - height:220, - modal: true, - title: "Warning", - }); - } - var selectedOrder = self.pos.get('selectedOrder'); - var scannedProductModel = App.getProductByEAN(codeNumbers.join(''),allPackages,allProducts); - if (scannedProductModel === undefined) { - // product not recognized, raise warning - $(QWeb.render('pos-scan-warning')).dialog({ - resizable: false, - height:220, - modal: true, - title: "Warning", - /* - buttons: { - "OK": function() { - $( this ).dialog( "close" ); - return; - }, - }*/ - }); - } else { - selectedOrder.addProduct(new module.Product(scannedProductModel)); - } - - codeNumbers = []; - } - } else { - // NaN - codeNumbers = []; - } - }); - $('.searchbox input').keyup(function() { var m, s; s = $(this).val().toLowerCase(); 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 6536b6be0a4..66dc30551a5 100644 --- a/addons/point_of_sale/static/src/js/pos_widgets.js +++ b/addons/point_of_sale/static/src/js/pos_widgets.js @@ -1,4 +1,4 @@ -function pos_widgets(module, instance){ +function openerp_pos_widgets(module, instance){ //module is instance.point_of_sale var QWeb = instance.web.qweb; var qweb_template = function(template,pos){ @@ -997,8 +997,7 @@ function pos_widgets(module, instance){ }, this)); }, close: function() { - // remove barcode reader event listener - $('body').undelegate('', 'keyup') + this.pos.app.barcodeReader.disconnect(); return new instance.web.Model("ir.model.data").get_func("search_read")([['name', '=', 'action_pos_close_statement']], ['res_id']).pipe( _.bind(function(res) { From 4a488a32bb9b878fd380859e7eec0785589c6002 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20van=20der=20Essen?= Date: Wed, 25 Apr 2012 16:55:55 +0200 Subject: [PATCH 52/54] Merged App and POSWidget bzr revid: fva@openerp.com-20120425145555-hk48b24efi6f3nti --- .../static/src/js/pos_devices.js | 12 +- .../point_of_sale/static/src/js/pos_models.js | 111 +----------------- .../static/src/js/pos_widgets.js | 89 ++++++++++++-- 3 files changed, 89 insertions(+), 123 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 80af36f3669..31ac45f856c 100644 --- a/addons/point_of_sale/static/src/js/pos_devices.js +++ b/addons/point_of_sale/static/src/js/pos_devices.js @@ -1,10 +1,13 @@ function openerp_pos_devices(module, instance){ //module is instance.point_of_sale module.BarcodeReader = instance.web.Class.extend({ + init: function(attributes){ this.pos = attributes.pos; + this.connect(); }, - //returns true if the code is a valid EAN codebar number by checking the control digit. + + // returns true if the code is a valid EAN codebar number by checking the control digit. checkEan: function(code){ var st1 = code.slice(); var st2 = st1.slice(0,st1.length-1).reverse(); @@ -33,6 +36,7 @@ function openerp_pos_devices(module, instance){ //module is instance.point_of_sa var cd = (10 - (st5%10)) % 10; return code[code.length-1] === cd; }, + // returns a product that has a packaging with an EAN matching to provided ean string. // returns undefined if no such product is found. getProductByEAN: function(ean) { @@ -66,7 +70,8 @@ function openerp_pos_devices(module, instance){ //module is instance.point_of_sa } return scannedProductModel; }, - //starts catching keyboard events and tries to interpret codebar + + // starts catching keyboard events and tries to interpret codebar connect: function(){ var self = this; var codeNumbers = []; @@ -135,9 +140,10 @@ function openerp_pos_devices(module, instance){ //module is instance.point_of_sa } }); }, + + // stops catching keyboard events disconnect: function(){ $('body').undelegate('', 'keyup') }, }); - } 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 e1dddfafadf..9d802b44597 100644 --- a/addons/point_of_sale/static/src/js/pos_models.js +++ b/addons/point_of_sale/static/src/js/pos_models.js @@ -158,7 +158,7 @@ function openerp_pos_models(module, instance){ //module is instance.point_of_sal if(operations.length === 0){ return $.when(); } - var op = operations[0].data; + var op = operations[0]; // we prevent the default error handler and assume errors // are a normal use case, except we stop the current iteration @@ -168,8 +168,8 @@ function openerp_pos_models(module, instance){ //module is instance.point_of_sal event.preventDefault(); }) .pipe(function(){ - //console.debug('saved 1 record'); TODO Debug this - self.dao.remove_operation(op.id).pipe(function(){ + console.debug('saved 1 record'); //TODO Debug this + self.dao.remove_operation(operations[0].id).pipe(function(){ return self._int_flush(); }); }, function(){ @@ -242,36 +242,6 @@ function openerp_pos_models(module, instance){ //module is instance.point_of_sal return _results; } }); - /* - module.Shop = Backbone.Model.extend({ - initialize: function(attributes) { - var self = this; - this.set({ - orders: new module.OrderCollection(), - products: new module.ProductCollection(), - }); - this.pos = attributes.pos; - this.set({ - cashRegisters: new module.CashRegisterCollection(this.pos.get('bank_statements')), - }); - return (this.get('orders')).bind('remove', _.bind( function(removedOrder) { - if ((this.get('orders')).isEmpty()) { - this.addAndSelectOrder(new module.Order({pos: self.pos})); - } - if ((this.get('selectedOrder')) === removedOrder) { - return this.set({ - selectedOrder: (this.get('orders')).last() - }); - } - }, this)); - }, - addAndSelectOrder: function(newOrder) { - (this.get('orders')).add(newOrder); - return this.set({ - selectedOrder: newOrder - }); - }, - });*/ module.CashRegister = Backbone.Model.extend({ }); @@ -511,7 +481,6 @@ function openerp_pos_models(module, instance){ //module is instance.point_of_sal model: module.Order, }); - /* The numpad handles both the choice of the property currently being modified (quantity, price or discount) and the edition of the corresponding numeric value. @@ -578,78 +547,4 @@ function openerp_pos_models(module, instance){ //module is instance.point_of_sal } }, }); - - module.App = (function() { - - function App($element, pos) { - this.initialize($element, pos); - } - - App.prototype.initialize = function($element, pos) { - this.pos = pos; - this.shopView = new module.ShopWidget(null, { - 'pos': pos, - }); - this.shopView.$element = $element; - this.shopView.start(); - this.categoryView = new module.CategoryWidget(null, {element_id: 'products-screen-categories', pos: pos} ); - this.categoryView.on_change_category.add_last(_.bind(this.category, this)); - this.category(); - - this.onscreenKeyboard = new module.OnscreenKeyboardWidget(null,{keyboard_model:'simple'}); - this.onscreenKeyboard.appendTo($(".point-of-sale #content")); - -/* - this.actionBar = new module.ActionbarWidget(null); - this.actionBar.appendTo($(".point-of-sale #content")); - this.actionBar.addNewButton('left',{'label':'test'}); -*/ - this.barcodeReader = new module.BarcodeReader({ 'pos': pos }); - this.barcodeReader.connect(); - }; - - App.prototype.category = function(id) { - var c, product_list, self = this; - - id = !id ? 0 : id; - - c = this.pos.categories[id]; - this.categoryView.ancestors = c.ancestors; - this.categoryView.children = c.children; - this.categoryView.renderElement(); - this.categoryView.start(); - - allProducts = this.pos.get('product_list'); - - allPackages = this.pos.get('product.packaging'); - - product_list = this.pos.get('product_list').filter( function(p) { - var _ref; - return _ref = p.pos_categ_id[0], _.indexOf(c.subtree, _ref) >= 0; - }); - (this.pos.get('products')).reset(product_list); - - $('.searchbox input').keyup(function() { - var m, s; - s = $(this).val().toLowerCase(); - if (s) { - m = product_list.filter( function(p) { - return p.name.toLowerCase().indexOf(s) != -1; - }); - $('.search-clear').fadeIn(); - } else { - m = product_list; - $('.search-clear').fadeOut(); - } - return (self.pos.get('products')).reset(m); - }); - return $('.search-clear').click( function() { - (self.pos.get('products')).reset(product_list); - $('.searchbox input').val('').focus(); - return $('.search-clear').fadeOut(); - }); - }; - return App; - })(); - } 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 66dc30551a5..7047d6ab5fe 100644 --- a/addons/point_of_sale/static/src/js/pos_widgets.js +++ b/addons/point_of_sale/static/src/js/pos_widgets.js @@ -136,9 +136,9 @@ function openerp_pos_widgets(module, instance){ //module is instance.point_of_sa $('#' + new_step + '-screen').show(); }, }); - /* - Shopping carts. - */ + +// ---------- "Shopping Carts" ---------- + module.OrderlineWidget = instance.web.OldWidget.extend({ tagName: 'tr', template_fct: qweb_template('pos-orderline-template'), @@ -262,9 +262,8 @@ function openerp_pos_widgets(module, instance){ //module is instance.point_of_sa }, }); - /* - "Products" step. - */ +// ---------- "Products" step. ---------- + module.CategoryWidget = instance.web.OldWidget.extend({ init: function(parent, options){ this._super(parent,options.element_id); @@ -347,9 +346,9 @@ function openerp_pos_widgets(module, instance){ //module is instance.point_of_sa return this; }, }); - /* - "Payment" step. - */ + +// ---------- "Payment" step. ---------- + module.PaymentlineWidget = instance.web.OldWidget.extend({ tagName: 'tr', template_fct: qweb_template('pos-paymentline-template'), @@ -927,7 +926,6 @@ function openerp_pos_widgets(module, instance){ //module is instance.point_of_sa module.POSWidget = instance.web.OldWidget.extend({ init: function() { this._super.apply(this, arguments); - this.pos = new module.PosModel(this.session); }, @@ -946,12 +944,31 @@ function openerp_pos_widgets(module, instance){ //module is instance.point_of_sa self.try_close(); }); - self.pos.app = new module.App(self.$element, self.pos); + //self.pos.app = new module.App(self.$element, self.pos); + + this.shopView = new module.ShopWidget(null, { 'pos': this.pos } ); + this.shopView.$element = self.$element; + this.shopView.start(); + + this.categoryView = new module.CategoryWidget(null, { + 'element_id': 'products-screen-categories', + 'pos': this.pos, + }); + this.categoryView.on_change_category.add_last(_.bind(self.search_and_categories, self)); + this.search_and_categories(); + + this.onscreenKeyboard = new module.OnscreenKeyboardWidget(null, { + 'keyboard_model': 'simple' + }); + this.onscreenKeyboard.appendTo($(".point-of-sale #content")); + + this.barcodeReader = new module.BarcodeReader({'pos': self.pos }); + 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) { + _.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); @@ -959,6 +976,54 @@ function openerp_pos_widgets(module, instance){ //module is instance.point_of_sa }, this)); }, this)); }, + search_and_categories: function(id){ + var self = this, + c, + product_list, + allProducts, + allPackages; + + id = id || 0; + + c = this.pos.categories[id]; + this.categoryView.ancestors = c.ancestors; + this.categoryView.children = c.children; + this.categoryView.renderElement(); + this.categoryView.start(); + + 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); + + $('.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; + }); + $('.search-clear').fadeIn(); + }else{ + results = product_list; + $('.search-clear').fadeOut(); + } + self.pos.get('products').reset(results); + }); + + $('.search-clear').click(function(){ + self.pos.get('products').reset(product_list); + $('.searchbox input').val('').focus(); + $('.search-clear').fadeOut(); + }); + }, + render: function() { return qweb_template("POSWidget")(); }, From 2416f64ca2c1dcabf6036f38f7170dfde6b9800d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20van=20der=20Essen?= Date: Thu, 26 Apr 2012 12:05:48 +0200 Subject: [PATCH 53/54] Merged POSWidget and ShopWidget bzr revid: fva@openerp.com-20120426100548-chrc9sdozi8hhjq6 --- .../point_of_sale/static/src/js/pos_main.js | 2 +- .../static/src/js/pos_widgets.js | 159 +++++++++--------- 2 files changed, 78 insertions(+), 83 deletions(-) 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 29f625a9f85..09e51dd41e1 100644 --- a/addons/point_of_sale/static/src/js/pos_main.js +++ b/addons/point_of_sale/static/src/js/pos_main.js @@ -11,7 +11,7 @@ openerp.point_of_sale = function(instance) { openerp_pos_devices(module,instance); // import pos_devices.js - instance.web.client_actions.add('pos.ui', 'instance.point_of_sale.POSWidget'); + instance.web.client_actions.add('pos.ui', 'instance.point_of_sale.PosWidget'); }; 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 7047d6ab5fe..625a423919a 100644 --- a/addons/point_of_sale/static/src/js/pos_widgets.js +++ b/addons/point_of_sale/static/src/js/pos_widgets.js @@ -642,6 +642,7 @@ function openerp_pos_widgets(module, instance){ //module is instance.point_of_sa // // The widget is initially hidden. It can be shown with this.show(), and is // automatically shown when the input_selector gets focused. + module.OnscreenKeyboardWidget = instance.web.Widget.extend({ tagName: 'div', @@ -825,12 +826,69 @@ function openerp_pos_widgets(module, instance){ //module is instance.point_of_sa }, }); - module.ShopWidget = instance.web.OldWidget.extend({ - init: function(parent, options) { - this._super(parent); - this.pos = options.pos; + module.SynchNotification = instance.web.OldWidget.extend({ + template: "pos-synch-notification", + init: function() { + this._super.apply(this, arguments); + this.nbr_pending = 0; + }, + renderElement: function() { + this._super.apply(this, arguments); + $('.oe_pos_synch-notification-button', this.$element).click(this.on_synch); + }, + on_change_nbr_pending: function(nbr_pending) { + this.nbr_pending = nbr_pending; + this.renderElement(); + }, + on_synch: function() {} + }); + + module.PosWidget = instance.web.OldWidget.extend({ + init: function() { + this._super.apply(this, arguments); + this.pos = new module.PosModel(this.session); }, start: function() { + var self = this; + return self.pos.ready.then(_.bind(function() { + this.renderElement(); + this.synch_notification = new module.SynchNotification(this); + this.synch_notification.replace($('.oe_pos_synch-notification', this.$element)); + this.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(); + + this.$element.find("#loggedas button").click(function() { + self.try_close(); + }); + + this.buildWidgets(); + + //self.pos.app = new module.App(self.$element, self.pos); + + //this.shopView = new module.ShopWidget(null, { 'pos': this.pos } ); + //this.shopView.$element = self.$element; + //this.shopView.start(); + + + + 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() { + return qweb_template("POSWidget")(); + }, + buildWidgets: function() { $('button#neworder-button', this.$element).click(_.bind(this.createNewOrder, this)); (this.pos.get('orders')).bind('add', this.orderAdded, this); @@ -868,6 +926,20 @@ function openerp_pos_widgets(module, instance){ //module is instance.point_of_sa this.stepSwitcher = new module.StepSwitcher(this, {pos: this.pos}); this.pos.bind('change:selectedOrder', this.changedSelectedOrder, this); this.changedSelectedOrder(); + + this.categoryView = new module.CategoryWidget(null, { + 'element_id': 'products-screen-categories', + 'pos': this.pos, + }); + this.categoryView.on_change_category.add_last(_.bind(this.search_and_categories, this)); + this.search_and_categories(); + + this.onscreenKeyboard = new module.OnscreenKeyboardWidget(null, { + 'keyboard_model': 'simple' + }); + this.onscreenKeyboard.appendTo($(".point-of-sale #content")); + + this.barcodeReader = new module.BarcodeReader({'pos': self.pos }); }, createNewOrder: function() { var newOrder; @@ -904,78 +976,6 @@ function openerp_pos_widgets(module, instance){ //module is instance.point_of_sa this.paymentView.setNumpadState(this.numpadView.state); } }, - }); - - module.SynchNotification = instance.web.OldWidget.extend({ - template: "pos-synch-notification", - init: function() { - this._super.apply(this, arguments); - this.nbr_pending = 0; - }, - renderElement: function() { - this._super.apply(this, arguments); - $('.oe_pos_synch-notification-button', this.$element).click(this.on_synch); - }, - on_change_nbr_pending: function(nbr_pending) { - this.nbr_pending = nbr_pending; - this.renderElement(); - }, - on_synch: function() {} - }); - - module.POSWidget = instance.web.OldWidget.extend({ - init: function() { - this._super.apply(this, arguments); - this.pos = new module.PosModel(this.session); - - }, - start: function() { - var self = this; - return self.pos.ready.then(_.bind(function() { - this.renderElement(); - this.synch_notification = new module.SynchNotification(this); - this.synch_notification.replace($('.oe_pos_synch-notification', this.$element)); - this.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(); - - this.$element.find("#loggedas button").click(function() { - self.try_close(); - }); - - //self.pos.app = new module.App(self.$element, self.pos); - - this.shopView = new module.ShopWidget(null, { 'pos': this.pos } ); - this.shopView.$element = self.$element; - this.shopView.start(); - - this.categoryView = new module.CategoryWidget(null, { - 'element_id': 'products-screen-categories', - 'pos': this.pos, - }); - this.categoryView.on_change_category.add_last(_.bind(self.search_and_categories, self)); - this.search_and_categories(); - - this.onscreenKeyboard = new module.OnscreenKeyboardWidget(null, { - 'keyboard_model': 'simple' - }); - this.onscreenKeyboard.appendTo($(".point-of-sale #content")); - - this.barcodeReader = new module.BarcodeReader({'pos': self.pos }); - - 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)); - }, search_and_categories: function(id){ var self = this, c, @@ -1023,10 +1023,6 @@ function openerp_pos_widgets(module, instance){ //module is instance.point_of_sa $('.search-clear').fadeOut(); }); }, - - render: function() { - return qweb_template("POSWidget")(); - }, changed_pending_operations: function () { var self = this; this.synch_notification.on_change_nbr_pending(self.pos.get('nbr_pending_operations').length); @@ -1062,7 +1058,7 @@ function openerp_pos_widgets(module, instance){ //module is instance.point_of_sa }, this)); }, close: function() { - this.pos.app.barcodeReader.disconnect(); + this.barcodeReader.disconnect(); return new instance.web.Model("ir.model.data").get_func("search_read")([['name', '=', 'action_pos_close_statement']], ['res_id']).pipe( _.bind(function(res) { @@ -1079,5 +1075,4 @@ function openerp_pos_widgets(module, instance){ //module is instance.point_of_sa this._super(); } }); - } From 67341a8cef901b3ea80d622dbe757d344e330ddb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20van=20der=20Essen?= Date: Thu, 26 Apr 2012 13:26:55 +0200 Subject: [PATCH 54/54] Add test button bzr revid: fva@openerp.com-20120426112655-s6j7qt098ptecwwf --- addons/point_of_sale/static/src/js/pos_widgets.js | 15 +++++++++++++++ 1 file changed, 15 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 625a423919a..870ce206ee8 100644 --- a/addons/point_of_sale/static/src/js/pos_widgets.js +++ b/addons/point_of_sale/static/src/js/pos_widgets.js @@ -570,11 +570,17 @@ function openerp_pos_widgets(module, instance){ //module is instance.point_of_sa this._super(parent, options); this.label = options.label || 'button'; this.rightalign = options.rightalign || false; + this.click_action = options.click; if(options.icon){ this.icon = options.icon; this.template = 'pos-action-button-with-icon'; } }, + start: function(){ + if(this.click_action){ + this.$element.click(_.bind(this.click_action, this)); + } + }, }); module.ActionbarWidget = instance.web.Widget.extend({ @@ -940,6 +946,15 @@ function openerp_pos_widgets(module, instance){ //module is instance.point_of_sa this.onscreenKeyboard.appendTo($(".point-of-sale #content")); this.barcodeReader = new module.BarcodeReader({'pos': self.pos }); + + this.actionBar = new module.ActionbarWidget(null); + this.actionBar.appendTo($(".point-of-sale #content")); + this.actionBar.addNewButton('left',{ + label : 'Hello World', + icon : '/point_of_sale/static/src/img/icons/png48/face-monkey.png', + click : function(){ console.log("Hello World!"); } + }); + }, createNewOrder: function() { var newOrder;