diff --git a/pos_barcode_tare/__init__.py b/pos_barcode_tare/__init__.py index f5ba686bc2..9a7e03eded 100644 --- a/pos_barcode_tare/__init__.py +++ b/pos_barcode_tare/__init__.py @@ -1,2 +1 @@ -# -*- coding: utf-8 -*- from . import models \ No newline at end of file diff --git a/pos_barcode_tare/static/src/css/pos_barcode_tare.css b/pos_barcode_tare/static/src/css/pos_barcode_tare.css index 2e12a1a006..55f4582786 100644 --- a/pos_barcode_tare/static/src/css/pos_barcode_tare.css +++ b/pos_barcode_tare/static/src/css/pos_barcode_tare.css @@ -6,22 +6,39 @@ 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 .centered-content { + text-align:center; } -.pos .pos-tare-label .caption { +@media screen { + .pos .tare-screen .pos-tare-label-container { + width: 300px; + background-color: white; + margin: 20px; + padding: 15px; + font-size: 21px; + padding-bottom:30px; + font-family: "Inconsolata"; + border: solid 1px rgb(220,220,220); + border-radius: 3px; + overflow: hidden; + text-align: center; + display: inline-block; + justify-content: center; + align-items: center; + flex-wrap: wrap; + } + + .pos .tare-screen .pos-tare-label-container img { + width: 50mm; + height: 45mm; + } +} + +.pos .tare-screen .pos-tare-label-container .caption { display: block; + text-overflow: ellipsis; + flex-basis: 100vw; } .pos .tare-screen .pos-directions-for-user { @@ -48,12 +65,7 @@ margin-right: auto; } -.pos .pos-tare-label img { - width: 50mm; - height: 45mm; -} - -.pos .tare-screen .print-label { +.pos .tare-screen .centered-content .print-label { text-align: center; font-size: 32px; background: rgb(110,200,155); @@ -65,47 +77,47 @@ } @media print { + body { + position: relative !important; + } + .pos .tare-screen header, .pos .tare-screen .top-content, - .pos .tare-screen .centered-content .print-label, + .pos .tare-screen .print-label, .pos .tare-screen .pos-directions-for-user { display: none !important; } + .pos .tare-screen .screen-content { + max-width: unset !important; + } + .pos .tare-screen .centered-content { position: static; + text-align: center; border: none; + width: 100vw; + height: 100vh; + margin: 0mm; + page-break-inside: avoid; + overflow: hidden; + padding: 0; + display: flex; + justify-content: center; + align-items: center; + flex-wrap: wrap; + left: unset !important; + right: unset !important; } - .pos .pos-tare-label .caption { - display: block; - } - - .pos .pos-tare-paper { - margin: 0; - margin-left: 0 !important; - margin-right: 0 !important; - width: 99% !important; - height: 99% !important; - display: block; - position: fixed; - display: flex !important; - justify-content: center !important; - align-items: center !important; - } - - .pos-tare-label img { - width: 27mm !important; - height: 21mm !important; - display: run-in; + .pos .tare-screen .centered-content .pos-tare-label-container img { + width: 90vw; + height: 70vh; } - .pos .pos-tare-label { - margin: 0; - margin-left: 0 !important; - margin-right: 0 !important; - position: fixed !important; - border: none !important; - font-size: 10px !important; + .pos .tare-screen .centered-content .pos-tare-label-container .caption { + text-overflow: ellipsis; + font-size: 5vh; + flex-basis: 100%; } } \ No newline at end of file diff --git a/pos_barcode_tare/static/src/js/pos_barcode_tare.js b/pos_barcode_tare/static/src/js/pos_barcode_tare.js index 927a86cd2d..8dd786a28c 100644 --- a/pos_barcode_tare/static/src/js/pos_barcode_tare.js +++ b/pos_barcode_tare/static/src/js/pos_barcode_tare.js @@ -1,9 +1,7 @@ odoo.define('pos_barcode_tare.screens', 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'); @@ -59,6 +57,18 @@ odoo.define('pos_barcode_tare.screens', function (require) { return result || 0; }; + // Format the tare value. + var format_tare = function (pos, qty, unit) { + if (unit.rounding) { + var q = round_pr(qty, unit.rounding); + var decimals = pos.dp['Product Unit of Measure']; + return formats.format_value( + round_di(q, decimals), + {type: 'float', digits: [69, decimals]}); + } + return qty.toFixed(0); + }; + // This configures read action for tare barcode. A tare barcode contains a // fake product ID and the weight to be subtracted from the product in the // latest order line. @@ -67,9 +77,9 @@ odoo.define('pos_barcode_tare.screens', function (require) { barcode_tare_action: function (code) { try { var order = this.pos.get_order(); - var last_order_line = order.get_last_orderline(); + var selected_order_line = order.get_selected_orderline(); var tare_weight = code.value; - last_order_line.set_tare(tare_weight); + selected_order_line.set_tare(tare_weight); } catch (error) { var title = _t("We can not apply this tare barcode."); var popup = {title: title, body: error.message}; @@ -223,6 +233,7 @@ odoo.define('pos_barcode_tare.screens', function (require) { }, print_web: function () { window.print(); + // TODO check this this.pos.get_order()._printed = true; }, print: function () { @@ -261,14 +272,17 @@ odoo.define('pos_barcode_tare.screens', function (require) { delete this.weight; this.pos.proxy_queue.clear(); }, + get_tare_str: function () { + return format_tare(this.pos, this.get_weight(), + get_unit(this.pos, "kg")); + }, }); gui.define_screen({name:'tare', widget: TareScreenWidget}); // Update Orderline model var _super_ = models.Orderline.prototype; - - models.Orderline = models.Orderline.extend({ + var OrderLineWithTare = models.Orderline.extend({ initialize: function (session, attributes) { this.tareQuantity = 0; this.tareQuantityStr = '0'; @@ -291,27 +305,14 @@ odoo.define('pos_barcode_tare.screens', function (require) { this.get_tare_str_with_unit(), this.product.display_name)); } - var self = this; - // This function is used to format the quantity into string - // according to the rounding specifications. - var stringify = function (qty) { - var unit = self.get_unit(); - if (unit.rounding) { - var q = round_pr(qty, unit.rounding); - var decimals = self.pos.dp['Product Unit of Measure']; - return formats.format_value( - round_di(q, decimals), - {type: 'float', digits: [69, decimals]}); - } - return qty.toFixed(0); - }; // We convert the tare that is always measured in kilogrammes into // the unit of measure for this order line. var kg = get_unit(this.pos, "kg"); var tare = parseFloat(quantity) || 0; var unit = this.get_unit(); var tare_in_product_uom = convert_mass(tare, kg, unit); - var tare_in_product_uom_string = stringify(tare_in_product_uom); + var tare_in_product_uom_string = format_tare(this.pos, + tare_in_product_uom, unit); var net_quantity = this.get_quantity() - tare_in_product_uom; // This method fails when the net weight is negative. if (net_quantity <= 0) { @@ -356,4 +357,10 @@ odoo.define('pos_barcode_tare.screens', function (require) { return result; }, }); + + models.Orderline = OrderLineWithTare; + + return {TareScreenWidget: TareScreenWidget, + OrderLineWithTare: OrderLineWithTare, + get_unit: get_unit}; }); diff --git a/pos_barcode_tare/static/src/xml/pos_barcode_tare.xml b/pos_barcode_tare/static/src/xml/pos_barcode_tare.xml index 0679472bc2..4ae6e4d295 100644 --- a/pos_barcode_tare/static/src/xml/pos_barcode_tare.xml +++ b/pos_barcode_tare/static/src/xml/pos_barcode_tare.xml @@ -25,14 +25,10 @@ -
-
-
- - tare = kg -
-
-
+ + + tare = kg +
diff --git a/pos_self_weighing/README.rst b/pos_self_weighing/README.rst new file mode 100644 index 0000000000..db80a45683 --- /dev/null +++ b/pos_self_weighing/README.rst @@ -0,0 +1,202 @@ +============================== +Point of Sale - Self weighing +============================== + +.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fpos-lightgray.png?logo=github + :target: https://github.com/OCA/pos/tree/9.0/pos_self_weighing + :alt: OCA/pos +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/pos-9-0/pos-9-0-pos_self_weighing + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png + :target: https://runbot.odoo-community.org/runbot/184/9.0 + :alt: Try me on Runbot + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This add-on allows you to run a grocery in a Bring Your Own Container (BYOC) schema. With this add-on your customers will be able to buy loose goods using their own reusable container. This add-on also enables customers to weight their vegetables and fruits before they checkout. To weight fruit and vegetable before checkout will speed up the checkout and make your customer happier. + +The *self service* BYOC scheme has six steps: + 1. The customer weighs her-his pot and sticks the tare barcode onto the pot. + 2. The customer puts loose goods into the labeled pot. + 3. The customer scans the tare barcode. POS saves the tare value. + 4. The customer weighs the pot with loose goods inside. POS computes the price to pay from gross weight and tare weight. + 5. The customer gets a price barcode label indicating the price to pay. + 6. During checkout, the cashier swiftly scans the price barcode label. + + +This add-on requires you to have a barcode label printer and an electronic scale. Your customers are expected to use this add-on in a self-service way. This add-on adds multiple news screens that are displayed only on the selected POS. + +This add-on displays only the products that are meant to be weighted with a scale. + +Home screen of the selected POS is: + +.. image:: https://raw.githubusercontent.com/OCA/pos/9.0/pos_self_weighing/static/description/default_screen.png + +To click on "tare a container" shows: + +.. image:: https://raw.githubusercontent.com/OCA/pos/9.0/pos_self_weighing/static/description/tare_screen.png + +To click on "weight a product in a container" shows: + +.. image:: https://raw.githubusercontent.com/OCA/pos/9.0/pos_self_weighing/static/description/tare_scan_screen.png + +To click on "weight a product" shows: + +.. image:: https://raw.githubusercontent.com/OCA/pos/9.0/pos_self_weighing/static/description/product_screen.png + +**Table of contents** + +.. contents:: + :local: + +Configuration +============= + +Introduction +~~~~~~~~~~~~ +Install this add-on and configure the point of sale where you want to use a self service weighing station. The label printing is done using web print. +This add-on requires to be connected to Odoo POS from a computer that'll be accessible by your customers/members. In order to limit the risk of unfortunate/malicious actions on this POS you should: + +* **Use a dedicated odoo account with the least possible rights** +* Configure the web browser to be in kiosk mode (only browsing without navigation bar) + +Those measures aren't sufficient *per se*, but should lower the risk significantly. + +Setup the self service POS +~~~~~~~~~~~~~~~~~~~~~~~~~~ +1. Create a new POS +2. Setup hardware proxy + * Make sure you have a valid SSL certificate issued for POSBOX host (see below). + * Enable electronic scale + * Enable barcode reader +3. Enable self service weighing + * Use the checkbox as shown on screen shot below. + * If your label printer is able to distribute several labels in a row you can select the `Enable weighing station to print multiple labels at once`. With this option your customers will be able to weigh several products and print all the labels at once. This is assumed to be more efficient. + +See the screenshot below, you new POS should page should like this. + +.. image:: https://raw.githubusercontent.com/OCA/pos/9.0/pos_self_weighing/static/description/configuration_details.png + +Setup a barcode for each product "to weight with a scale" +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +In the default odoo barcode nomenclature, a barcode with prefix 21 is a "weight" barcode. +The default way to process a weight barcode in odoo is to match the base part of the barcode (*ie*. the two digits prefix and the following five digits). +For instance, if you setup barcode **21 12345 00000 8** for your carrots, any barcode with pattern **21 12345 \*\*\*\*\* \*** will be a match for carrots. +This add-on uses weight barcode to encode labels. Therefore, with the previous barcode, **21 12345 01000 7** is be the label for 1kg of carrots. Be sure to have a barcode setup for each article you want to weight with a scale. See screenshot below. + +.. image:: https://raw.githubusercontent.com/OCA/pos/9.0/pos_self_weighing/static/description/barcode_config.png + + +SSL certificate generation +~~~~~~~~~~~~~~~~~~~~~~~~~~ +Nowadays for any internet facing service the most convenient, secure, and fast way to get SSL certificates is [letsencrypt](https://letsencrypt.org/). +However, it is likely (and expected) that your POSBox and the computer used to display to self weighing POS belong to same network. +In such a situation to use a self issued SSL certificate is a valid option. +To use a self issued SSL certificate is low maintenance as you can set an arbitrarily far expiration date. +As your HTTPS communications are expected to happen locally the security risk of using a self issued SSL certificate is fairly low. + +See bash scripts in the GIST below to: + * create a certification authority + * self issue a SSL certificate with this certification authority + * setup a nginx reverse proxy serving the posbox with the self issued SSL certificate + * Install the certification authority into the web browser of the computer displaying the self weighing POS belong so that the self issued SSL certificate is considered valid by chrome and firefox. + +https://gist.github.com/Fkawala/0d46376d3abb7369d34afefaa1ac98fa + +Firefox +~~~~~~~ +See below how to setup firefox in silent printing mode is described below [source](http://manual.koha-community.org/3.2/en/firefoxreceipt.html) + +1. Open File > Page Setup + + * Make all the headers and footers blank + * Set the margins to 0 (zero) + +2. In the address bar of Firefox, type about:config + + * Search for print.always_print_silent and double click it + * Change it from false to true + * If print.always_print_silent does not come up: + * Right click on a blank area of the preference window + * Select new > Boolean + * Enter "print.always_print_silent" as the name (without quotes) + * Click OK + * Select true for the value + +You may also want to check what is listed for print.print_printer. You may have to choose Generic/Text Only (or whatever your receipt printer might be named) + +Chrome +~~~~~~ +Chrome base browsers have a kiosk mode. In kiosk mode the navigation bar is disabled and user action are limited to web browsing. To start a chrome base browser in kiosk mode with silent printing use the command below. + +``chromium-browser --use-system-default-printer --kiosk --kiosk-printing http://localhost:8069/`` + +Usage +===== + +1. Setup the add-on as explained in the **configuration** section +2. Start the POS +3. Let your happy customers/members use the POS to print price labels for fruits, vegetables and loose goods, let them enjoy a swift checkout process + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us smashing it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +~~~~~~~ + +* Le Nid + +Contributors +~~~~~~~~~~~~ + +- Le Nid +- SPP Meyrin +- CoopItEasy +- Icons made by Freepik from flaticon.com + +Maintainers +~~~~~~~~~~~ + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +.. |maintainer-fkawala| image:: https://github.com/fkawala.png?size=40px + :target: https://github.com/fkawala + :alt: fkawala + +Current `maintainer `__: + +|maintainer-fkawala| + +This module is part of the `OCA/pos `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/pos_self_weighing/__init__.py b/pos_self_weighing/__init__.py new file mode 100644 index 0000000000..9a7e03eded --- /dev/null +++ b/pos_self_weighing/__init__.py @@ -0,0 +1 @@ +from . import models \ No newline at end of file diff --git a/pos_self_weighing/__openerp__.py b/pos_self_weighing/__openerp__.py new file mode 100644 index 0000000000..83a85d5f30 --- /dev/null +++ b/pos_self_weighing/__openerp__.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +# @author: François Kawala +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +{ + 'name': "Point of Sale - Self weighing ", + 'version': '9.0.0.0.1', + 'category': 'Point of Sale', + 'summary': """Point of Sale - Self service weighing station for loose\ + goods.""", + 'author': "Le Nid, Odoo Community Association (OCA)", + 'website': "https://github.com/OCA/pos", + 'license': 'AGPL-3', + 'maintainers': ['fkawala'], + 'depends': ['point_of_sale', 'pos_barcode_tare'], + 'demo': ['demo/pos_self_weighing_demo.xml'], + 'data': [ + 'views/assets.xml', + 'views/pos_config_view.xml' + ], + 'qweb': [ + 'static/src/xml/pos_self_weighing.xml', + ], + 'installable': True, +} diff --git a/pos_self_weighing/demo/pos_self_weighing_demo.xml b/pos_self_weighing/demo/pos_self_weighing_demo.xml new file mode 100644 index 0000000000..111a5358ff --- /dev/null +++ b/pos_self_weighing/demo/pos_self_weighing_demo.xml @@ -0,0 +1,16 @@ + + + + + Kg + + + + + Apples (with weight barcode) + 2112345000008 + 1.50 + + + + diff --git a/pos_self_weighing/models/__init__.py b/pos_self_weighing/models/__init__.py new file mode 100644 index 0000000000..f7dd4ebc8c --- /dev/null +++ b/pos_self_weighing/models/__init__.py @@ -0,0 +1 @@ +from . import pos_config \ No newline at end of file diff --git a/pos_self_weighing/models/pos_config.py b/pos_self_weighing/models/pos_config.py new file mode 100644 index 0000000000..8e7018e69a --- /dev/null +++ b/pos_self_weighing/models/pos_config.py @@ -0,0 +1,13 @@ +# -*- coding: utf-8 -*- +from openerp import models, fields + + +class PosConfig(models.Model): + _inherit = 'pos.config' + iface_self_weight = fields.Boolean( + 'Use that POS as self weighing station' + ) + + iface_self_weight_multi_label = fields.Boolean( + 'Enable weighing station to print multiple labels at once' + ) diff --git a/pos_self_weighing/readme/CONFIGURE.rst b/pos_self_weighing/readme/CONFIGURE.rst new file mode 100644 index 0000000000..c1b02d0e57 --- /dev/null +++ b/pos_self_weighing/readme/CONFIGURE.rst @@ -0,0 +1,78 @@ +Introduction +~~~~~~~~~~~~ +Install this add-on and configure the point of sale where you want to use a self service weighing station. The label printing is done using web print. +This add-on requires to be connected to Odoo POS from a computer that'll be accessible by your customers/members. In order to limit the risk of unfortunate/malicious actions on this POS you should: + +* **Use a dedicated odoo account with the least possible rights** +* Configure the web browser to be in kiosk mode (only browsing without navigation bar) + +Those measures aren't sufficient *per se*, but should lower the risk significantly. + +Setup the self service POS +~~~~~~~~~~~~~~~~~~~~~~~~~~ +1. Create a new POS +2. Setup hardware proxy + * Make sure you have a valid SSL certificate issued for POSBOX host (see below). + * Enable electronic scale + * Enable barcode reader +3. Enable self service weighing + * Use the checkbox as shown on screen shot below. + * If your label printer is able to distribute several labels in a row you can select the `Enable weighing station to print multiple labels at once`. With this option your customers will be able to weigh several products and print all the labels at once. This is assumed to be more efficient. + +See the screenshot below, you new POS should page should like this. + +.. image:: ../static/description/configuration_details.png + +Setup a barcode for each product "to weight with a scale" +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +In the default odoo barcode nomenclature, a barcode with prefix 21 is a "weight" barcode. +The default way to process a weight barcode in odoo is to match the base part of the barcode (*ie*. the two digits prefix and the following five digits). +For instance, if you setup barcode **21 12345 00000 8** for your carrots, any barcode with pattern **21 12345 \*\*\*\*\* \*** will be a match for carrots. +This add-on uses weight barcode to encode labels. Therefore, with the previous barcode, **21 12345 01000 7** is be the label for 1kg of carrots. Be sure to have a barcode setup for each article you want to weight with a scale. See screenshot below. + +.. image:: ../static/description/barcode_config.png + + +SSL certificate generation +~~~~~~~~~~~~~~~~~~~~~~~~~~ +Nowadays for any internet facing service the most convenient, secure, and fast way to get SSL certificates is [letsencrypt](https://letsencrypt.org/). +However, it is likely (and expected) that your POSBox and the computer used to display to self weighing POS belong to same network. +In such a situation to use a self issued SSL certificate is a valid option. +To use a self issued SSL certificate is low maintenance as you can set an arbitrarily far expiration date. +As your HTTPS communications are expected to happen locally the security risk of using a self issued SSL certificate is fairly low. + +See bash scripts in the GIST below to: + * create a certification authority + * self issue a SSL certificate with this certification authority + * setup a nginx reverse proxy serving the posbox with the self issued SSL certificate + * Install the certification authority into the web browser of the computer displaying the self weighing POS belong so that the self issued SSL certificate is considered valid by chrome and firefox. + +https://gist.github.com/Fkawala/0d46376d3abb7369d34afefaa1ac98fa + +Firefox +~~~~~~~ +See below how to setup firefox in silent printing mode is described below [source](http://manual.koha-community.org/3.2/en/firefoxreceipt.html) + +1. Open File > Page Setup + + * Make all the headers and footers blank + * Set the margins to 0 (zero) + +2. In the address bar of Firefox, type about:config + + * Search for print.always_print_silent and double click it + * Change it from false to true + * If print.always_print_silent does not come up: + * Right click on a blank area of the preference window + * Select new > Boolean + * Enter "print.always_print_silent" as the name (without quotes) + * Click OK + * Select true for the value + +You may also want to check what is listed for print.print_printer. You may have to choose Generic/Text Only (or whatever your receipt printer might be named) + +Chrome +~~~~~~ +Chrome base browsers have a kiosk mode. In kiosk mode the navigation bar is disabled and user action are limited to web browsing. To start a chrome base browser in kiosk mode with silent printing use the command below. + +``chromium-browser --use-system-default-printer --kiosk --kiosk-printing http://localhost:8069/`` diff --git a/pos_self_weighing/readme/CONTRIBUTORS.rst b/pos_self_weighing/readme/CONTRIBUTORS.rst new file mode 100644 index 0000000000..eebe58008c --- /dev/null +++ b/pos_self_weighing/readme/CONTRIBUTORS.rst @@ -0,0 +1,4 @@ +- Le Nid +- SPP Meyrin +- CoopItEasy +- Icons made by Freepik from flaticon.com diff --git a/pos_self_weighing/readme/DESCRIPTION.rst b/pos_self_weighing/readme/DESCRIPTION.rst new file mode 100644 index 0000000000..996fea1ab6 --- /dev/null +++ b/pos_self_weighing/readme/DESCRIPTION.rst @@ -0,0 +1,30 @@ +This add-on allows you to run a grocery in a Bring Your Own Container (BYOC) schema. With this add-on your customers will be able to buy loose goods using their own reusable container. This add-on also enables customers to weight their vegetables and fruits before they checkout. To weight fruit and vegetable before checkout will speed up the checkout and make your customer happier. + +The *self service* BYOC scheme has six steps: + 1. The customer weighs her-his pot and sticks the tare barcode onto the pot. + 2. The customer puts loose goods into the labeled pot. + 3. The customer scans the tare barcode. POS saves the tare value. + 4. The customer weighs the pot with loose goods inside. POS computes the price to pay from gross weight and tare weight. + 5. The customer gets a price barcode label indicating the price to pay. + 6. During checkout, the cashier swiftly scans the price barcode label. + + +This add-on requires you to have a barcode label printer and an electronic scale. Your customers are expected to use this add-on in a self-service way. This add-on adds multiple news screens that are displayed only on the selected POS. + +This add-on displays only the products that are meant to be weighted with a scale. + +Home screen of the selected POS is: + +.. image:: ../static/description/default_screen.png + +To click on "tare a container" shows: + +.. image:: ../static/description/tare_screen.png + +To click on "weight a product in a container" shows: + +.. image:: ../static/description/tare_scan_screen.png + +To click on "weight a product" shows: + +.. image:: ../static/description/product_screen.png diff --git a/pos_self_weighing/readme/USAGE.rst b/pos_self_weighing/readme/USAGE.rst new file mode 100644 index 0000000000..265683c839 --- /dev/null +++ b/pos_self_weighing/readme/USAGE.rst @@ -0,0 +1,3 @@ +1. Setup the add-on as explained in the **configuration** section +2. Start the POS +3. Let your happy customers/members use the POS to print price labels for fruits, vegetables and loose goods, let them enjoy a swift checkout process diff --git a/pos_self_weighing/static/description/barcode_config.png b/pos_self_weighing/static/description/barcode_config.png new file mode 100644 index 0000000000..1282137440 Binary files /dev/null and b/pos_self_weighing/static/description/barcode_config.png differ diff --git a/pos_self_weighing/static/description/barcode_price.png b/pos_self_weighing/static/description/barcode_price.png new file mode 100644 index 0000000000..2fbeb38da2 Binary files /dev/null and b/pos_self_weighing/static/description/barcode_price.png differ diff --git a/pos_self_weighing/static/description/configuration.png b/pos_self_weighing/static/description/configuration.png new file mode 100644 index 0000000000..4efd343205 Binary files /dev/null and b/pos_self_weighing/static/description/configuration.png differ diff --git a/pos_self_weighing/static/description/configuration_details.png b/pos_self_weighing/static/description/configuration_details.png new file mode 100644 index 0000000000..385bfbe27f Binary files /dev/null and b/pos_self_weighing/static/description/configuration_details.png differ diff --git a/pos_self_weighing/static/description/default_screen.png b/pos_self_weighing/static/description/default_screen.png new file mode 100644 index 0000000000..1ecbfaeb95 Binary files /dev/null and b/pos_self_weighing/static/description/default_screen.png differ diff --git a/pos_self_weighing/static/description/icon.png b/pos_self_weighing/static/description/icon.png new file mode 100644 index 0000000000..3a0328b516 Binary files /dev/null and b/pos_self_weighing/static/description/icon.png differ diff --git a/pos_self_weighing/static/description/index.html b/pos_self_weighing/static/description/index.html new file mode 100644 index 0000000000..2f0cf4418b --- /dev/null +++ b/pos_self_weighing/static/description/index.html @@ -0,0 +1,565 @@ + + + + + + +Point of Sale - Self weighing + + + +
+

Point of Sale - Self weighing

+ + +

Beta License: AGPL-3 OCA/pos Translate me on Weblate Try me on Runbot

+

This add-on allows you to run a grocery in a Bring Your Own Container (BYOC) schema. With this add-on your customers will be able to buy loose goods using their own reusable container. This add-on also enables customers to weight their vegetables and fruits before they checkout. To weight fruit and vegetable before checkout will speed up the checkout and make your customer happier.

+
+
The self service BYOC scheme has six steps:
+
    +
  1. The customer weighs her-his pot and sticks the tare barcode onto the pot.
  2. +
  3. The customer puts loose goods into the labeled pot.
  4. +
  5. The customer scans the tare barcode. POS saves the tare value.
  6. +
  7. The customer weighs the pot with loose goods inside. POS computes the price to pay from gross weight and tare weight.
  8. +
  9. The customer gets a price barcode label indicating the price to pay.
  10. +
  11. During checkout, the cashier swiftly scans the price barcode label.
  12. +
+
+
+

This add-on requires you to have a barcode label printer and an electronic scale. Your customers are expected to use this add-on in a self-service way. This add-on adds multiple news screens that are displayed only on the selected POS.

+

This add-on displays only the products that are meant to be weighted with a scale.

+

Home screen of the selected POS is:

+https://raw.githubusercontent.com/OCA/pos/9.0/pos_self_weighing/static/description/default_screen.png +

To click on “tare a container” shows:

+https://raw.githubusercontent.com/OCA/pos/9.0/pos_self_weighing/static/description/tare_screen.png +

To click on “weight a product in a container” shows:

+https://raw.githubusercontent.com/OCA/pos/9.0/pos_self_weighing/static/description/tare_scan_screen.png +

To click on “weight a product” shows:

+https://raw.githubusercontent.com/OCA/pos/9.0/pos_self_weighing/static/description/product_screen.png +

Table of contents

+ +
+

Configuration

+
+

Introduction

+

Install this add-on and configure the point of sale where you want to use a self service weighing station. The label printing is done using web print. +This add-on requires to be connected to Odoo POS from a computer that’ll be accessible by your customers/members. In order to limit the risk of unfortunate/malicious actions on this POS you should:

+
    +
  • Use a dedicated odoo account with the least possible rights
  • +
  • Configure the web browser to be in kiosk mode (only browsing without navigation bar)
  • +
+

Those measures aren’t sufficient per se, but should lower the risk significantly.

+
+
+

Setup the self service POS

+
    +
  1. Create a new POS
  2. +
  3. +
    Setup hardware proxy
    +
      +
    • Make sure you have a valid SSL certificate issued for POSBOX host (see below).
    • +
    • Enable electronic scale
    • +
    • Enable barcode reader
    • +
    +
    +
    +
  4. +
  5. +
    Enable self service weighing
    +
      +
    • Use the checkbox as shown on screen shot below.
    • +
    • If your label printer is able to distribute several labels in a row you can select the Enable weighing station to print multiple labels at once. With this option your customers will be able to weigh several products and print all the labels at once. This is assumed to be more efficient.
    • +
    +
    +
    +
  6. +
+

See the screenshot below, you new POS should page should like this.

+https://raw.githubusercontent.com/OCA/pos/9.0/pos_self_weighing/static/description/configuration_details.png +
+
+

Setup a barcode for each product “to weight with a scale”

+

In the default odoo barcode nomenclature, a barcode with prefix 21 is a “weight” barcode. +The default way to process a weight barcode in odoo is to match the base part of the barcode (ie. the two digits prefix and the following five digits). +For instance, if you setup barcode 21 12345 00000 8 for your carrots, any barcode with pattern 21 12345 ***** * will be a match for carrots. +This add-on uses weight barcode to encode labels. Therefore, with the previous barcode, 21 12345 01000 7 is be the label for 1kg of carrots. Be sure to have a barcode setup for each article you want to weight with a scale. See screenshot below.

+https://raw.githubusercontent.com/OCA/pos/9.0/pos_self_weighing/static/description/barcode_config.png +
+
+

SSL certificate generation

+

Nowadays for any internet facing service the most convenient, secure, and fast way to get SSL certificates is [letsencrypt](https://letsencrypt.org/). +However, it is likely (and expected) that your POSBox and the computer used to display to self weighing POS belong to same network. +In such a situation to use a self issued SSL certificate is a valid option. +To use a self issued SSL certificate is low maintenance as you can set an arbitrarily far expiration date. +As your HTTPS communications are expected to happen locally the security risk of using a self issued SSL certificate is fairly low.

+
+
See bash scripts in the GIST below to:
+
    +
  • create a certification authority
  • +
  • self issue a SSL certificate with this certification authority
  • +
  • setup a nginx reverse proxy serving the posbox with the self issued SSL certificate
  • +
  • Install the certification authority into the web browser of the computer displaying the self weighing POS belong so that the self issued SSL certificate is considered valid by chrome and firefox.
  • +
+
+
+

https://gist.github.com/Fkawala/0d46376d3abb7369d34afefaa1ac98fa

+
+
+

Firefox

+

See below how to setup firefox in silent printing mode is described below [source](http://manual.koha-community.org/3.2/en/firefoxreceipt.html)

+
    +
  1. Open File > Page Setup
      +
    • Make all the headers and footers blank
    • +
    • Set the margins to 0 (zero)
    • +
    +
  2. +
  3. In the address bar of Firefox, type about:config
      +
    • Search for print.always_print_silent and double click it
    • +
    • Change it from false to true
    • +
    • +
      If print.always_print_silent does not come up:
      +
        +
      • Right click on a blank area of the preference window
      • +
      • Select new > Boolean
      • +
      • Enter “print.always_print_silent” as the name (without quotes)
      • +
      • Click OK
      • +
      • Select true for the value
      • +
      +
      +
      +
    • +
    +
  4. +
+

You may also want to check what is listed for print.print_printer. You may have to choose Generic/Text Only (or whatever your receipt printer might be named)

+
+
+

Chrome

+

Chrome base browsers have a kiosk mode. In kiosk mode the navigation bar is disabled and user action are limited to web browsing. To start a chrome base browser in kiosk mode with silent printing use the command below.

+

chromium-browser --use-system-default-printer --kiosk --kiosk-printing http://localhost:8069/

+
+
+
+

Usage

+
    +
  1. Setup the add-on as explained in the configuration section
  2. +
  3. Start the POS
  4. +
  5. Let your happy customers/members use the POS to print price labels for fruits, vegetables and loose goods, let them enjoy a swift checkout process
  6. +
+
+
+

Bug Tracker

+

Bugs are tracked on GitHub Issues. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us smashing it by providing a detailed and welcomed +feedback.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • Le Nid
  • +
+
+
+

Contributors

+
    +
  • Le Nid
  • +
  • SPP Meyrin
  • +
  • CoopItEasy
  • +
  • Icons made by Freepik from flaticon.com
  • +
+
+
+

Maintainers

+

This module is maintained by the OCA.

+Odoo Community Association +

OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use.

+

Current maintainer:

+

fkawala

+

This module is part of the OCA/pos project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+ + diff --git a/pos_self_weighing/static/description/label.png b/pos_self_weighing/static/description/label.png new file mode 100644 index 0000000000..6857be07ef Binary files /dev/null and b/pos_self_weighing/static/description/label.png differ diff --git a/pos_self_weighing/static/description/null_weight.png b/pos_self_weighing/static/description/null_weight.png new file mode 100644 index 0000000000..df7f9199bd Binary files /dev/null and b/pos_self_weighing/static/description/null_weight.png differ diff --git a/pos_self_weighing/static/description/product_screen.png b/pos_self_weighing/static/description/product_screen.png new file mode 100644 index 0000000000..77907b2e85 Binary files /dev/null and b/pos_self_weighing/static/description/product_screen.png differ diff --git a/pos_self_weighing/static/description/ready_to_print.png b/pos_self_weighing/static/description/ready_to_print.png new file mode 100644 index 0000000000..4ff7866d8e Binary files /dev/null and b/pos_self_weighing/static/description/ready_to_print.png differ diff --git a/pos_self_weighing/static/description/tare_scan_screen.png b/pos_self_weighing/static/description/tare_scan_screen.png new file mode 100644 index 0000000000..a75d56a533 Binary files /dev/null and b/pos_self_weighing/static/description/tare_scan_screen.png differ diff --git a/pos_self_weighing/static/description/tare_screen.png b/pos_self_weighing/static/description/tare_screen.png new file mode 100644 index 0000000000..fad7326533 Binary files /dev/null and b/pos_self_weighing/static/description/tare_screen.png differ diff --git a/pos_self_weighing/static/img/blue/empty_foodbox_on_scale.svg b/pos_self_weighing/static/img/blue/empty_foodbox_on_scale.svg new file mode 100644 index 0000000000..872a80cf17 --- /dev/null +++ b/pos_self_weighing/static/img/blue/empty_foodbox_on_scale.svg @@ -0,0 +1,406 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/pos_self_weighing/static/img/blue/foodbox.svg b/pos_self_weighing/static/img/blue/foodbox.svg new file mode 100644 index 0000000000..5c7b2c34d8 --- /dev/null +++ b/pos_self_weighing/static/img/blue/foodbox.svg @@ -0,0 +1,148 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/pos_self_weighing/static/img/blue/foodbox_on_scale.svg b/pos_self_weighing/static/img/blue/foodbox_on_scale.svg new file mode 100644 index 0000000000..4503e56e6f --- /dev/null +++ b/pos_self_weighing/static/img/blue/foodbox_on_scale.svg @@ -0,0 +1,328 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/pos_self_weighing/static/img/food_in_box.svg b/pos_self_weighing/static/img/food_in_box.svg new file mode 100644 index 0000000000..15b8c25f1c --- /dev/null +++ b/pos_self_weighing/static/img/food_in_box.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/pos_self_weighing/static/img/foodbox.svg b/pos_self_weighing/static/img/foodbox.svg new file mode 100644 index 0000000000..36a0954dbd --- /dev/null +++ b/pos_self_weighing/static/img/foodbox.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/pos_self_weighing/static/img/foodbox_on_scale.svg b/pos_self_weighing/static/img/foodbox_on_scale.svg new file mode 100644 index 0000000000..547edf9c6a --- /dev/null +++ b/pos_self_weighing/static/img/foodbox_on_scale.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/pos_self_weighing/static/img/scale.svg b/pos_self_weighing/static/img/scale.svg new file mode 100644 index 0000000000..97cb78f819 --- /dev/null +++ b/pos_self_weighing/static/img/scale.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/pos_self_weighing/static/img/tare_label.png b/pos_self_weighing/static/img/tare_label.png new file mode 100644 index 0000000000..6857be07ef Binary files /dev/null and b/pos_self_weighing/static/img/tare_label.png differ diff --git a/pos_self_weighing/static/src/css/pos_self_weighing.css b/pos_self_weighing/static/src/css/pos_self_weighing.css new file mode 100644 index 0000000000..b74effa646 --- /dev/null +++ b/pos_self_weighing/static/src/css/pos_self_weighing.css @@ -0,0 +1,237 @@ +.self-service-screen .centered { + display: flex; + justify-content: center; + align-items: center; + overflow: auto; + width: 100%; + height: 100%; +} + +.self-service-screen .big-button { + display: flex; + text-align: center; + flex-direction: column; + justify-content: center; + align-items: center; + width: 90%; + height: 45%; + font-size: 55px; + cursor: pointer; + transition: background-color, border-color, color 150ms linear; +} + +.self-service-screen .red { + background: rgb(200,110,110); + border: solid 1px rgb(186,73,73); + color: rgb(232,232,232); +} + +.self-service-screen .green { + background: rgb(110, 200, 155); + border: solid 1px rgb(73,186,130); +} + +.self-service-screen .blue { + background: rgb(110,155,200); + border: solid 1px rgb(73,130,186); + color: rgb(232,232,232); +} + +.self-service-screen .blue:hover { + background: rgb(73,130,186); +} + +.self-service-screen .red:hover { + background: rgb(186,73,73); +} + +.self-service-screen .green:hover { + background: rgb(73,186,130); +} + +.self-service-screen .big-button:first-child { + border-radius: 12px; +} + +.self-service-screen .big-button .ico { + display: flex; + background-size: contain; + width: 170px; + height: 170px; + order: 2; +} + +.self-service-screen .foodbox { + background-image: url("../../img/blue/empty_foodbox_on_scale.svg"); +} + +.self-service-screen .scale { + background-image: url("../../img/scale.svg"); +} + +.self-service-screen .foodinbox { + background-image: url("../../img/blue/foodbox_on_scale.svg"); +} + +.pos .placeholder-OrderSelectorWidget { + width: 100%; +} + +.screen .right-content.pc33 { + left: 66%; + width: 33%; +} + +.screen .left-content.pc33 { + left: 0; + width: 33%; +} + +.screen .left-content.middle { + left: 33%; + width: 33%; +} + +.self-service-screen .screen-content { + max-width: 1920px; +} + +.self-product-screen.screen .searchbox { + position: relative; + right: 0px; +} + +.self-product-screen.screen .searchbox input { + width: 90%; + font-size: 24px; + background: white; + padding-left: 20px; +} + +.self-product-screen.screen .header-row { + height: 10%; +} + +.self-product-screen.screen .product-list { + display: grid; + grid-gap: 12px; + grid-template-columns: repeat(auto-fit, minmax(132px, 1fr)); + justify-items: center; + padding: 0px 8px 0px 8px; +} + +.self-product-screen.screen .product { + margin: 0 !important; +} + +.self-product-screen.screen .product-name { + font-size: 14px; +} + +.self-product-screen.screen .actionpad { + padding: 0px; + margin: 0px; + margin-top: 0px; + width: 100%; +} + +.self-product-screen.screen .actionpad .button.print { + height: 162px; + font-size: 18px; +} + +.self-product-screen.screen .actionpad .button.print .print-circle { + display: block; + font-size: 32px; + line-height: 54px; + padding-top: 6px; + background: rgb(86, 86, 86); + color: white; + width: 60px; + margin: auto; + border-radius: 30px; + margin-bottom: 10px; +} + +.self-product-screen.screen .actionpad .button.print .print-circle .fa { + position: relative; + top: -1px; + left: 3px; +} + +.self-product-screen.screen .searchbox input { + font-family: Arial, FontAwesome; +} + +.self-tare-scan-screen .centered-content { + padding: 1em; +} + +.self-tare-scan-screen .pos-tare-label-container { + margin: auto; + height: 246px; + width: 332px; + border: 1px; + padding-top: 10px; + background-size: 99%; + background-repeat: no-repeat; + background-color: white; + background-image: url("../../img/tare_label.png"); + background-position: center; + border: solid 1px rgb(220,220,220); +} + +.self-tare-scan-screen .pos-directions-for-user { + font-size: 25px; + margin: 8px; + text-align: center; + line-height: 2; +} + +@media screen { + .pos .self-product-screen .labels { + display: none; + } +} + +@media print { + body { + position: relative !important; + } + + .searchbox { + display: none !important; + } + + .labels { + } + + .label { + width: 100vw; + height: 100vh; + margin: 0mm; + page-break-inside: avoid; + overflow: hidden; + padding: 0; + display: flex; + justify-content: center; + align-items: center; + flex-wrap: wrap; + } + + .pos .self-product-screen .label img { + width: 90vw; + height: 70vh; + } + + .pos .self-product-screen .label .caption { + text-overflow: ellipsis; + font-size: 5vh; + flex-basis: 100%; + } + + .pos .self-product-screen .leftpane, + .pos .self-product-screen .rightpane { + display: none !important; + } +} \ No newline at end of file diff --git a/pos_self_weighing/static/src/js/pos_self_weighing.js b/pos_self_weighing/static/src/js/pos_self_weighing.js new file mode 100644 index 0000000000..c1b775574d --- /dev/null +++ b/pos_self_weighing/static/src/js/pos_self_weighing.js @@ -0,0 +1,503 @@ +odoo.define('pos_self_weighing.screens', function (require) { + + "use strict"; + var chrome = require('point_of_sale.chrome'); + var core = require('web.core'); + var gui = require('point_of_sale.gui'); + var utils = require('web.utils'); + var models = require('point_of_sale.models'); + var screens = require('point_of_sale.screens'); + var tare = require('pos_barcode_tare.screens'); + + var _t = core._t; + var round_pr = utils.round_precision; + var get_unit = tare.get_unit; + var QWeb = core.qweb; + + /* Widgets */ + + // Search and order products. + var SelfServiceSearchWidget = screens.ScreenWidget.extend({ + template:'SelfServiceSearchWidget', + init: function (parent, options) { + var self = this; + this._super(parent, options); + this.product_list_widget = options.product_list_widget || null; + + var root_category_id = this.pos.db.root_category_id; + this.category = this.pos.db.get_category_by_id(root_category_id); + + var search_timeout = null; + this.search_handler = function (event) { + if (event.type === "keypress" || event.keyCode === 46 || + event.keyCode === 8) { + clearTimeout(search_timeout); + var searchbox = this; + search_timeout = setTimeout(function () { + self.perform_search(self.category, + searchbox.value, event.which === 13); + }, 70); + } + }; + }, + renderElement: function () { + this._super(); + var search_box = this.el.querySelector('.searchbox input'); + search_box.addEventListener('keypress', this.search_handler); + search_box.addEventListener('keydown', this.search_handler); + }, + perform_search: function (category, query, buy_result) { + var db = this.pos.db; + var products = null; + if (query) { + products = db.search_product_in_category(category.id, query); + // Defines a shortcut to order a the one product displayed when + // a search has only one result. + if (buy_result && products.length === 1) { + var product_id = products[0].id; + // Triggers the click product action in order to display + // the scale screen instead of simply adding the product + // to the order. + this.product_list_widget.click_product_handler(null, + product_id); + this.clear_search(); + } else { + this.product_list_widget.set_product_list(products); + } + } else { + products = db.get_product_by_category(this.category.id); + this.product_list_widget.set_product_list(products); + } + }, + clear_search: function () { + var db = this.pos.db; + var products = db.get_product_by_category(this.category.id); + this.product_list_widget.set_product_list(products); + var input = this.el.querySelector('.searchbox input'); + input.value = ''; + input.focus(); + }, + }); + + // Display products. + var SelfServiceProductListWidget = screens.ProductListWidget.extend({ + init: function (parent, options) { + this._super(parent, options); + var self = this; + this.click_product_handler = function (event, product_id) { + var id = product_id || this.dataset.productId; + var product = self.pos.db.get_product_by_id(id); + options.click_product_action(null, product); + }; + }, + renderElement: function () { + var el_str = QWeb.render(this.template, {widget: this}); + var el_node = document.createElement('div'); + el_node.innerHTML = el_str; + el_node = el_node.childNodes[1]; + + if (this.el && this.el.parentNode) { + this.el.parentNode.replaceChild(el_node, this.el); + } + this.el = el_node; + var list_container = el_node.querySelector('.product-list'); + for (var i = 0, len = this.product_list.length; i < len; i++) { + var product = this.product_list[i]; + // We are displaying only products that need to be weighted to + // be sold. + if (product.to_weight) { + var product_node = this.render_product(product); + product_node.addEventListener('click', + this.click_product_handler); + list_container.appendChild(product_node); + } + } + }, + }); + + // Simple back and forward action pad. + var SelfServiceActionpadWidget = screens.ActionpadWidget.extend({ + template:'SelfServiceActionpadWidget', + init: function (parent, options) { + this._super(parent, options); + this.order_widget = options.order_widget; + this.price_labels = options.price_labels; + }, + click_back: function () { + var order = this.pos.get_order(); + var orderline = order.get_selected_orderline(); + if (orderline) { + order.remove_orderline(orderline); + } + if ( order.orderlines.length === 0) { + // Go back to the default screen when there is nothing left + // to remove from the current order. + this.gui.show_screen('selfService'); + } else { + this.order_widget.renderElement(); + } + }, + print: function () { + var order = this.pos.get_order(); + var orderline = order.get_selected_orderline(); + // The print button works only when there is at least one + // label to print. + if (orderline) { + this.price_labels.renderElement(); + window.print(); + this.gui.show_screen('selfService'); + this.pos.delete_current_order(); + } else { + var popup = { + title: _t("There is no product to print label for."), + body: + _t("You did not weighted any product. Start by " + + "weighing a product, then you'll be able to print a " + + "price barcode label for this product."), + }; + this.gui.show_popup('alert', popup); + } + }, + renderElement: function () { + var self = this; + this._super(); + + this.$('.back').click(function () { + self.click_back(); + }); + + this.$('.print').click(function () { + self.print(); + }); + }, + }); + + // This renders the price barcode labels. + var PriceLabelsWidget = screens.ScreenWidget.extend({ + template:'PosPriceLabels', + + init: function (parent, options) { + this._super(parent, options); + // Bind used to render the labels each time the order is modified. + this.pos.bind('change:selectedOrder', this.bind_order_events, this); + if (this.pos.get_order()) { + this.bind_order_events(); + } + }, + bind_order_events: function () { + // Ensure that any change to the current order will trigger this + // widget to re-render so that it will be ready whenever we want to + // print it. + if (this.pos.get_order()) { + var lines = this.pos.get_order().orderlines; + lines.unbind('add', this.renderElement, this); + lines.bind('add', this.renderElement, this); + lines.unbind('remove', this.renderElement, this); + lines.bind('remove', this.renderElement, this); + lines.unbind('change', this.renderElement, this); + lines.bind('change', this.renderElement, this); + } + }, + renderElement: function () { + this._super(); + var order = this.pos.get_order(); + // Render one label per orderline in the order. + this.$(".labels").html(QWeb.render("PosLabels", { + widget:this, + orderlines: order.get_orderlines(), + })); + }, + }); + + /* Screens */ + + // This replaces the scale widget to handle tare when needed. + var SelfServiceScaleScreenWidget = screens.ScaleScreenWidget.extend({ + next_screen: 'selfProducts', + previous_screen: 'selfProducts', + + show: function () { + this._super(); + var self = this; + var product = this.get_product(); + var tare_code = this.get_tare_code(); + // Change the buy action so that tare value is deleted. + this.$('.next,.buy-product').off('click').click(function () { + // This reset the screen params so that we do not apply the tare + // twice. + self.gui.show_screen(self.next_screen, {}); + // Add product *after* switching screen to scroll properly + self.order_product(product, tare_code); + }); + }, + get_tare_code: function () { + return this.gui.get_current_screen_param('tare_code'); + }, + order_product: function (product, tare_code) { + var order = this.pos.get_order(); + order.add_product(product, {quantity: this.weight}); + // Apply tare when needed. + if (tare_code) { + var tare_weight = tare_code.value; + var orderline = order.get_last_orderline(); + orderline.set_tare(tare_weight); + } + }, + }); + + // Update the tare printing screen for it to link to self service screens. + var SelfServiceTareScreenWidget = tare.TareScreenWidget.extend({ + next_screen: 'selfService', + previous_screen: 'selfService', + show: function () { + this._super(); + if (!this.pos.config.iface_electronic_scale) { + var popup = { + title: _t("We can not add this product to the order."), + body: + _t("You did not configured this POS to use the " + + "electronic scale. This add-on requires POS to use " + + "the scale. Reconfigure the POS to be able to use " + + "this add-on."), + }; + this.gui.show_popup('error', popup); + } + }, + }); + + // This is the home screen with the three call to action buttons. + var SelfServiceWidget = screens.ScreenWidget.extend({ + template:'SelfServiceWidgetHome', + next_screen: 'selfService', + previous_screen: 'selfService', + + renderElement: function () { + var self = this; + this._super(); + + this.$('.tare').click(function () { + // Create a tare barcode label. + self.gui.show_screen('selfTare'); + }); + + this.$('.weight').click(function () { + // Weight a product without a container. + self.gui.show_screen('selfProducts'); + }); + + this.$('.scan').click(function () { + // Scan a tare barcode label to get redirected to the product + // screen. + self.gui.show_screen('selfScan'); + }); + }, + }); + + // This is the tare barcode label scanning label. It is mostly a place + // holder to recall customers to scan their tare barcode label. + var SelfServiceScanScreenWidget = screens.ScreenWidget.extend({ + template:'SelfServiceTareScanWidget', + next_screen: 'selfProducts', + previous_screen: 'selfService', + + renderElement: function () { + this._super(); + var self = this; + this.$('.back').click(function () { + self.gui.show_screen(self.previous_screen); + }); + }, + }); + + // This is the updated product screen. This screen displays only products + // with the to_weight flag set to true. This screen gives priority to the + // text search with a big central search bar. + var SelfServiceProduct = screens.ProductScreenWidget.extend({ + template:'SelfServiceProduct', + next_screen: 'selfService', + previous_screen: 'selfService', + + start: function () { + var self = this; + // Reuse the original order widget in order not to break the whole + // event messaging system. + this.order_widget = this.gui.screen_instances.products.order_widget; + + // Displays the products pictures but it has no product category. + this.product_list_widget = new SelfServiceProductListWidget(this, + {product_list: this.pos.db.get_product_by_category(0), + click_product_action: function (event, product) { + self.click_product(product); + }}); + + // Search bar to find products by theirs names. + this.search_widget = new SelfServiceSearchWidget(this, + {product_list_widget: this.product_list_widget}); + + // Handles barcode rendering. + this.price_labels = new PriceLabelsWidget(this, + {order_widget: this.order_widget}); + + // A two button pad: back action undo last action, validate action + // prints the labels. + this.actionpad = new SelfServiceActionpadWidget(this, + {order_widget: this.order_widget, + price_labels: this.price_labels}); + + // To replace the OrderWidget in this screen would be the regular + // product screen so we replace the placeholders only when this + // pos is configured to be a self service POS. + if (this.pos.config.iface_self_weight) { + this.order_widget.replace(this.$('.placeholder-OrderWidget')); + this.product_list_widget.replace( + this.$('.placeholder-SelfServiceProductListWidget')); + this.actionpad.replace( + this.$('.placeholder-SelfServiceActionpadWidget')); + this.search_widget.replace( + this.$('.placeholder-SelfServiceSearchWidget')); + this.price_labels.replace( + this.$('.placeholder-SelfServiceProductListWidget')); + } + }, + get_tare_code: function () { + return this.gui.get_current_screen_param('tare_code'); + }, + reset_tare_code: function () { + var order = this.pos.get_order(); + if (order) { + delete order.screen_data.params.tare_code; + } + }, + click_product: function (product) { + var self = this; + var order = this.pos.get_order(); + var order_length = order.orderlines.length; + + if (order_length > 0 && + !this.pos.config.iface_self_weight_multi_label) { + var print_popup = { + title: _t("We can add this product to the order."), + confirm: function () { + self.actionpad.print(); + }, + body: + _t("You already have one label to print. " + + "Do you want to print it now?"), + }; + this.gui.show_popup('confirm', print_popup); + } else if (product.to_weight && + this.pos.config.iface_electronic_scale) { + var tare_code = this.get_tare_code(); + var scale_params = {product: product, tare_code: tare_code}; + this.gui.show_screen('selfScale', scale_params); + } else { + var popup = { + title: _t("We can add this product to the order."), + body: + _t("You did not configured this POS to use the " + + "electronic scale. This add-on requires POS to use " + + "the scale. Reconfigure the POS to be able to use " + + "this add-on."), + }; + this.gui.show_popup('error', popup); + } + }, + show: function () { + this._super(); + // Set focus on the search box in order to speed up the process. + this.el.querySelector('.searchbox input').focus(); + }, + }); + + /* Models */ + + // This order line models generates a price barcode per order line. + models.Orderline = models.Orderline.extend({ + pad_data: function (padding_size, data) { + // For padding size = 5, this function transforms 123 into 00123 + var data_str = data.toString(); + if (data_str.length >= padding_size) { + return data_str; + } + var padded = '0'.repeat(padding_size) + data_str; + return padded.substr(padded.length - padding_size); + }, + get_barcode_data: function () { + // Pad the values to match the EAN13 format. + var padding_size = 5; + var product_barcode = this.product.barcode || "0".repeat(13); + var product_base_code = product_barcode.substr(0, 7); + var unit = get_unit(this.pos, this.product.uom_id[1]); + var rounding = unit.rounding; + var product_qty_in_gram = this.get_quantity() * 1e3; + var product_qty_round = round_pr(product_qty_in_gram, rounding); + var qty = this.pad_data(padding_size, product_qty_round); + // Builds the barcode using a placeholder checksum. + var barcode = product_base_code.concat(qty, 0); + // Compute checksum. + var barcode_parser = this.pos.barcode_reader.barcode_parser; + var checksum = barcode_parser.ean_checksum(barcode); + // Replace checksum placeholder by the actual checksum. + return barcode.substr(0, 12).concat(checksum); + }, + }); + + screens.ScreenWidget.include({ + barcode_product_action: function (code) { + var self = this; + if (self.pos.scan_product(code)) { + if (this.pos.config.iface_self_weight) { + self.gui.show_screen("selfProducts"); + } else if (self.barcode_product_screen) { + self.gui.show_screen(self.barcode_product_screen); + } + } else { + this.barcode_error_action(code); + } + }, + barcode_tare_action_self_service: function (code) { + // Apply tare barcode function depending on POS configuration. + var current_screen = this.gui.get_current_screen(); + try { + if (this.pos.config.iface_self_weight && + current_screen === "selfScan") { + this.gui.show_screen('selfProducts', {tare_code: code}); + } else { + this.barcode_tare_action(code); + } + } catch (error) { + var title = _t("We can not apply this tare barcode."); + var popup = {title: title, body: error.message}; + this.gui.show_popup('error', popup); + } + }, + // Setup the callback action for the "weight" barcodes. + show: function () { + this._super(); + this.pos.barcode_reader.set_action_callback( + 'tare', + _.bind(this.barcode_tare_action_self_service, this)); + }, + }); + + // Redefines the default screen when POS is configured to be self service. + chrome.Chrome = chrome.Chrome.include({ + build_widgets: function () { + if (this.pos.config.iface_self_weight) { + this._super(); + this.gui.set_default_screen('selfService'); + this.gui.set_startup_screen('selfService'); + this.widget.close_button.hide(); + } else { + this._super(); + } + }, + }); + + gui.define_screen({name:'selfService', widget: SelfServiceWidget}); + gui.define_screen({name:'selfTare', widget: SelfServiceTareScreenWidget}); + gui.define_screen({name:'selfScale', widget: SelfServiceScaleScreenWidget}); + gui.define_screen({name:'selfProducts', widget: SelfServiceProduct}); + gui.define_screen({name:'selfScan', widget: SelfServiceScanScreenWidget}); + +}); diff --git a/pos_self_weighing/static/src/xml/pos_self_weighing.xml b/pos_self_weighing/static/src/xml/pos_self_weighing.xml new file mode 100644 index 0000000000..1ee8fc366e --- /dev/null +++ b/pos_self_weighing/static/src/xml/pos_self_weighing.xml @@ -0,0 +1,142 @@ + + + + +
+
+
+
+
+ Tare
a container + +
+
+
+
+
+
+ Weight a product in a container + +
+
+
+
+
+
+ Weight
a product + +
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+
+ +
+
+
+ + + + + + + +
+
+
+
+ +
+
+
+
+
+
+ + +
+ + +
+
+ + + + + + +
+
+
+ + + Back + +

Scan a tare label

+
+
+
+ Scan the tare label of your pot, it looks like the image below. +
+
+
+ Once the tare label scanned, the product selection screen will be displayed. +
+
+
+
+
+ + +
+
+
+
+ + + +
+ + + + + / + + + / Tare = + + +
+
+
+ +
diff --git a/pos_self_weighing/views/assets.xml b/pos_self_weighing/views/assets.xml new file mode 100644 index 0000000000..d4f1daed48 --- /dev/null +++ b/pos_self_weighing/views/assets.xml @@ -0,0 +1,8 @@ + + + diff --git a/pos_self_weighing/views/pos_config_view.xml b/pos_self_weighing/views/pos_config_view.xml new file mode 100644 index 0000000000..fa23e5f9ef --- /dev/null +++ b/pos_self_weighing/views/pos_config_view.xml @@ -0,0 +1,17 @@ + + + + + pos.config + + + + + + + + + + + +