diff --git a/pos_barcode_tare/__init__.py b/pos_barcode_tare/__init__.py new file mode 100644 index 00000000..7c68785e --- /dev/null +++ b/pos_barcode_tare/__init__.py @@ -0,0 +1 @@ +# -*- coding: utf-8 -*- \ No newline at end of file diff --git a/pos_barcode_tare/__openerp__.py b/pos_barcode_tare/__openerp__.py new file mode 100644 index 00000000..fe92aeca --- /dev/null +++ b/pos_barcode_tare/__openerp__.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- +{ + 'name': "pos_barecode_tare", + + 'summary': """ + Allows to scan a barcode to tare the latest product. + """, + + 'description': """ + This add-on enable POS to read and print tare bar codes. A tare bar code is used to sell unpackaged goods in a + BYOC (bring your own container) scheme. This scheme has four steps: + 1. The cashier weights the container and sticks the tare bar code onto the customer's container. + 2. The customer takes the desired quantity of whatever good s-he wants. + 3. The cashier weights the filled container and good, POS gives the corresponding price. + 4. The cashier scans the tare bar code, POS removes the container's weight from the latest product of the order. + """, + + 'author': "Le Nid", + 'website': "http://www.lenid.ch", + + 'category': 'Point of Sale', + 'version': '0.1', + + # any module necessary for this one to work correctly + 'depends': ['point_of_sale'], + + # always loaded + 'data': [ + 'views/templates.xml', + ], + 'qweb': [ + 'static/src/xml/tare_screen.xml', + 'static/src/xml/open_tare_screen_button.xml', + + ], +} diff --git a/pos_barcode_tare/static/description/icon.png b/pos_barcode_tare/static/description/icon.png new file mode 100644 index 00000000..3e885187 Binary files /dev/null and b/pos_barcode_tare/static/description/icon.png differ diff --git a/pos_barcode_tare/static/src/css/tare_screen.css b/pos_barcode_tare/static/src/css/tare_screen.css new file mode 100644 index 00000000..cefa72eb --- /dev/null +++ b/pos_barcode_tare/static/src/css/tare_screen.css @@ -0,0 +1,105 @@ +.pos .print-label.disabled { + background: #e2e2e2; + border: solid 1px #BEBEBE; + opacity: 0.5; + cursor: default; + color: inherit; +} + +.pos .pos-tare-label { + width: 300px; + background-color: white; + margin: 20px; + padding: 15px; + font-size: 21px; + padding-bottom:30px; + display: inline-block; + font-family: "Inconsolata"; + border: solid 1px rgb(220,220,220); + border-radius: 3px; + overflow: hidden; +} + +.pos .tare-screen .pos-directions-for-user { + font-size: 25px; + margin: 8px; + text-align: center; + line-height: 2; +} + +.pos .tare-screen .pos-directions-for-user span { + width:100px; + height: 50px; + background-color: rgb(49,174,218); + color: white !important; + font-weight: bold; + border: solid 1px black; + border-radius: 90% 30%; + display: flex; + align-items: center; + overflow: hidden; + vertical-align:middle; + justify-content: center; + margin-left: auto; + margin-right: auto; +} + +.pos .pos-tare-label img { + width: 50mm; + height: 45mm; +} + +.pos .tare-screen .print-label { + text-align: center; + font-size: 32px; + background: rgb(110,200,155); + color: white; + border-radius: 3px; + padding: 16px; + margin: 16px; + cursor: pointer; +} + +@media print { + .pos .tare-screen header, + .pos .tare-screen .top-content, + .pos .tare-screen .centered-content .print-label, + .pos .tare-screen .pos-directions-for-user { + display: none !important; + } + + .pos .tare-screen .centered-content { + position: static; + border: none; + } + + .pos .pos-tare-paper { + margin: 0; + margin-left: 0 !important; + margin-right: 0 !important; + width: 42mm !important; + height: 29mm !important; + background: white; + display: block; + position: fixed; + display: flex !important; + justify-content: center !important; + align-items: center !important; + } + + + .pos-tare-label img { + width: 30mm !important; + height: 25mm !important; + } + + .pos .pos-tare-label { + margin: 0; + margin-left: 0 !important; + margin-right: 0 !important; + position: fixed !important; + border: none !important; + font-size: 10px !important; + + } +} \ No newline at end of file diff --git a/pos_barcode_tare/static/src/js/barcode.js b/pos_barcode_tare/static/src/js/barcode.js new file mode 100644 index 00000000..200f1acc --- /dev/null +++ b/pos_barcode_tare/static/src/js/barcode.js @@ -0,0 +1,36 @@ +odoo.define('barcode_tare',function(require) { + "use strict"; + var screens = require('point_of_sale.screens'); + var gui = require('point_of_sale.gui'); + var core = require('web.core'); + var _t = core._t; + + screens.ScreenWidget.include( + { + barcode_weight_action: function(code){ + var self = this; + var order = this.pos.get_order(); + var last_order_line = order.get_last_orderline(); + var total_weight = last_order_line.get_quantity(); + var tare = code.value; + var paid_weight = total_weight - tare; + + if (paid_weight <= 0) { + this.gui.show_popup('confirm', { + 'title': _t('Poids négatif'), + 'body': _t('Le poids à payer est négatif. Avez-vous scanné le bon code bare ?'), + confirm: function(){ + last_order_line.set_quantity(paid_weight) + }}); + } else { + last_order_line.set_quantity(paid_weight) + } + }, + + show: function(){ + var self = this; + this._super() + this.pos.barcode_reader.set_action_callback('weight', _.bind(self.barcode_weight_action, self)) + }, + }); +}); diff --git a/pos_barcode_tare/static/src/js/open_tare_screen_button.js b/pos_barcode_tare/static/src/js/open_tare_screen_button.js new file mode 100644 index 00000000..1ac00e57 --- /dev/null +++ b/pos_barcode_tare/static/src/js/open_tare_screen_button.js @@ -0,0 +1,20 @@ +odoo.define('tare-screen-button.button', function (require) { + "use strict"; + var core = require('web.core'); + var screens = require('point_of_sale.screens'); + var gui = require('point_of_sale.gui'); + + var TareScreenButton = screens.ActionButtonWidget.extend({ + template: 'TareScreenButton', + + button_click: function(){ + var self = this; + this.gui.show_screen('tare'); + } + }); + + screens.define_action_button({ + 'name': 'tareScreenButton', + 'widget': TareScreenButton, + }); +}); diff --git a/pos_barcode_tare/static/src/js/tare_screen.js b/pos_barcode_tare/static/src/js/tare_screen.js new file mode 100644 index 00000000..f6fe0e1b --- /dev/null +++ b/pos_barcode_tare/static/src/js/tare_screen.js @@ -0,0 +1,139 @@ +odoo.define('tare-screen.screen', function (require) { + "use strict"; + var chrome = require('point_of_sale.chrome'); + var core = require('web.core'); + var devices = require('point_of_sale.devices'); + var gui = require('point_of_sale.gui'); + var models = require('point_of_sale.models'); + var screens = require('point_of_sale.screens'); + var QWeb = core.qweb; + + var TareScreenWidget = screens.ScreenWidget.extend({ + template: 'TareScreenWidget', + next_screen: 'products', + previous_screen: 'products', + default_tare_value_kg: 0.0, + + show: function(){ + this._super(); + var self = this; + var queue = this.pos.proxy_queue; + + queue.schedule(function(){ + return self.pos.proxy.scale_read().then(function(weight){ + self.set_weight(weight.weight); + }); + },{duration:150, repeat: true}); + + this.render_receipt(); + this.lock_screen(true); + }, + set_weight: function(weight){ + if (weight > 0){ + this.weight = weight; + this.render_receipt(); + this.lock_screen(false); + } + }, + get_weight: function(){ + if (typeof this.weight === 'undefined') { + return this.default_tare_value_kg; + } + return this.weight; + }, + ean13_checksum: function(s){ + var result = 0; + for (let counter = s.length-1; counter >=0; counter--){ + result = result + parseInt(s.charAt(counter)) * (1+(2*(counter % 2))); + } + return (10 - (result % 10)) % 10; + }, + barcode_data: function(weight, weight_prefix_id=21){ + var padding_size = 5; + var void_product_id = '0'.repeat(padding_size); + var weight_in_gram = weight * 10e2; + var weight_with_padding = '0'.repeat(padding_size) + weight_in_gram; + var padded_weight = weight_with_padding.substr(weight_with_padding.length - padding_size); + var barcode_data = `${weight_prefix_id}${void_product_id}${padded_weight}`; + var checksum = this.ean13_checksum(barcode_data); + console.log(`${barcode_data}${checksum}`); + return `${barcode_data}${checksum}`; + }, + get_barcode_data: function(){ + return this.barcode_data(this.get_weight()); + }, + should_auto_print: function() { + return this.pos.config.iface_print_auto && !this.pos.get_order()._printed; + }, + should_close_immediately: function() { + return this.pos.config.iface_print_via_proxy && this.pos.config.iface_print_skip_screen; + }, + lock_screen: function(locked) { + this._locked = locked; + if (locked) { + this.$('.print-label').addClass('disabled'); + } else { + this.$('.print-label').removeClass('disabled'); + } + }, + print_web: function() { + window.print(); + this.pos.get_order()._printed = true; + }, + print: function() { + var self = this; + + // The problem is that in chrome the print() is asynchronous and doesn't + // execute until all rpc are finished. So it conflicts with the rpc used + // to send the orders to the backend, and the user is able to go to the next + // screen before the printing dialog is opened. The problem is that what's + // printed is whatever is in the page when the dialog is opened and not when it's called, + // and so you end up printing the product list instead of the receipt... + // + // Fixing this would need a re-architecturing + // of the code to postpone sending of orders after printing. + // + // But since the print dialog also blocks the other asynchronous calls, the + // button enabling in the setTimeout() is blocked until the printing dialog is + // closed. But the timeout has to be big enough or else it doesn't work + // 1 seconds is the same as the default timeout for sending orders and so the dialog + // should have appeared before the timeout... so yeah that's not ultra reliable. + + this.lock_screen(true); + + setTimeout(function(){ + self.lock_screen(false); + }, 1000); + + this.print_web(); + this.click_back(); + }, + click_back: function() { + this.close() + this.gui.show_screen(this.previous_screen); + }, + renderElement: function() { + var self = this; + this._super(); + this.$('.back').click(function(){ + self.click_back(); + }); + this.$('.print-label').click(function(){ + if (!self._locked) { + self.print(); + } + }); + }, + render_receipt: function() { + this.$('.pos-tare-label-container').html(QWeb.render('PosTareLabel',{widget:this})); + }, + close: function(){ + this._super(); + delete this.weight; + this.pos.proxy_queue.clear(); + }, + }); + + gui.define_screen({name:'tare', widget: TareScreenWidget}); + + }); \ No newline at end of file diff --git a/pos_barcode_tare/static/src/xml/open_tare_screen_button.xml b/pos_barcode_tare/static/src/xml/open_tare_screen_button.xml new file mode 100644 index 00000000..a96a652e --- /dev/null +++ b/pos_barcode_tare/static/src/xml/open_tare_screen_button.xml @@ -0,0 +1,9 @@ + + + + + + Créer une étiquette de tare + + + diff --git a/pos_barcode_tare/static/src/xml/tare_screen.xml b/pos_barcode_tare/static/src/xml/tare_screen.xml new file mode 100644 index 00000000..996191c4 --- /dev/null +++ b/pos_barcode_tare/static/src/xml/tare_screen.xml @@ -0,0 +1,38 @@ + + + + + + + + + Back + + Création d'une étiquette de tare + + + + + Appuyez sur la touche print puis vérifiez le poids ci-dessus. + + + Imprimer + + + + + + + + + + + + + + tare = kg + + + + + \ No newline at end of file diff --git a/pos_barcode_tare/views/templates.xml b/pos_barcode_tare/views/templates.xml new file mode 100644 index 00000000..29894343 --- /dev/null +++ b/pos_barcode_tare/views/templates.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + +