From d271b213a569517320a0ae2fca2dece151eb74a7 Mon Sep 17 00:00:00 2001 From: "Loan (LSE)" Date: Fri, 16 Jun 2023 11:16:44 +0000 Subject: [PATCH] [FIX] barcodes: handle `^` with `|` barcode rule patterns Before this commit: If the barcode nomenclature uses the or `|` symbol. The part after it would be used as a "contains" instead of a "start with". e.g: `123|456` would be transformed in the JS code to `^123|456` instead of: `^123|^456` As such, you would have error "can't find product with barcode" if you set such a rule and a product barcode contains the second part. For e.g: the barcode `44445666` would match, but should not! After this commit: Force the second (and following if any) part to start with. Note: in practice it is pretty rare to have `|` in the pattern, but it is the case for a default rule in version 16, see: https://github.com/odoo/odoo/blob/5ac58ebf983c1c02019253c0430fde3502e13e7c/addons/pos_loyalty/data/default_barcode_patterns.xml#L10 In this case, if a regular product have `044` in its product barcode, the PoS will tell that there is no corresponding coupon instead of adding the product. But the issue itself still apply in version 14 in case of custom rules opw-3356951 closes odoo/odoo#130961 X-original-commit: 103566699d3492b682219efc3614f26d2ea972e5 Signed-off-by: Joseph Caburnay (jcb) Signed-off-by: Loan Sens (lse) --- .../barcodes/static/src/js/barcode_parser.js | 6 +-- .../tests/tours/BarcodeScanning.tour.js | 41 +++++++++++++++++++ .../tours/helpers/ProductScreenTourMethods.js | 23 +++++++++++ addons/point_of_sale/tests/test_frontend.py | 13 ++++++ 4 files changed, 80 insertions(+), 3 deletions(-) create mode 100644 addons/point_of_sale/static/tests/tours/BarcodeScanning.tour.js diff --git a/addons/barcodes/static/src/js/barcode_parser.js b/addons/barcodes/static/src/js/barcode_parser.js index 0b98785cd8b..e11b2fc7bcd 100644 --- a/addons/barcodes/static/src/js/barcode_parser.js +++ b/addons/barcodes/static/src/js/barcode_parser.js @@ -158,9 +158,9 @@ export class BarcodeParser { match.base_code = base_code.join(''); } - if (base_pattern[0] !== '^') { - base_pattern = "^" + base_pattern; - } + base_pattern = base_pattern.split('|') + .map(part => part.startsWith('^') ? part : '^' + part) + .join('|'); match.match = match.base_code.match(base_pattern); return match; diff --git a/addons/point_of_sale/static/tests/tours/BarcodeScanning.tour.js b/addons/point_of_sale/static/tests/tours/BarcodeScanning.tour.js new file mode 100644 index 00000000000..fbef6db6105 --- /dev/null +++ b/addons/point_of_sale/static/tests/tours/BarcodeScanning.tour.js @@ -0,0 +1,41 @@ +/** @odoo-module */ + +import { ProductScreen } from "@point_of_sale/../tests/tours/helpers/ProductScreenTourMethods"; +import { getSteps, startSteps } from "@point_of_sale/../tests/tours/helpers/utils"; +import { registry } from "@web/core/registry"; + +registry + .category("web_tour.tours") + .add("BarcodeScanningTour", { + test: true, + url: "/pos/ui", + steps: () => { + startSteps(); + + // The following step is to make sure that the Chrome widget initialization ends + // If we try to use the barcode parser before its initiation, we will have + // some inconsistent JS errors: + // TypeError: Cannot read properties of undefined (reading 'parse_barcode') + ProductScreen.do.confirmOpeningPopup(); + + // Add a product with its barcode + ProductScreen.do.scan_barcode("0123456789"); + ProductScreen.check.selectedOrderlineHas('Monitor Stand'); + ProductScreen.do.scan_barcode("0123456789"); + ProductScreen.check.selectedOrderlineHas('Monitor Stand', 2); + + // Test "Prices product" EAN-13 `23.....{NNNDD}` barcode pattern + ProductScreen.do.scan_ean13_barcode("2301000000006"); + ProductScreen.check.selectedOrderlineHas('Magnetic Board', 1, "0.00"); + ProductScreen.do.scan_ean13_barcode("2301000123453"); + ProductScreen.check.selectedOrderlineHas('Magnetic Board', 1, "123.45"); + + // Test "Weighted product" EAN-13 `21.....{NNDDD}` barcode pattern + ProductScreen.do.scan_ean13_barcode("2100002000000"); + ProductScreen.check.selectedOrderlineHas('Wall Shelf Unit', 0, "0.00"); + ProductScreen.do.scan_ean13_barcode("2100002080000"); + ProductScreen.check.selectedOrderlineHas('Wall Shelf Unit', 8); + + return getSteps(); + } + }); diff --git a/addons/point_of_sale/static/tests/tours/helpers/ProductScreenTourMethods.js b/addons/point_of_sale/static/tests/tours/helpers/ProductScreenTourMethods.js index 744503d7e6f..e12afd2ac46 100644 --- a/addons/point_of_sale/static/tests/tours/helpers/ProductScreenTourMethods.js +++ b/addons/point_of_sale/static/tests/tours/helpers/ProductScreenTourMethods.js @@ -277,6 +277,29 @@ class Do { }, ]; } + scan_barcode(barcode) { + return [ + { + content: `PoS model scan barcode '${barcode}'`, + trigger: '.pos', // The element here does not really matter as long as it is present + run: () => { window.posmodel.env.services.barcode_reader.scan(barcode); }, + }, + ]; + } + scan_ean13_barcode(barcode) { + return [ + { + content: `PoS model scan EAN13 barcode '${barcode}'`, + trigger: '.pos', // The element here does not really matter as long as it is present + run: () => { + const barcode_reader = window.posmodel.env.services.barcode_reader; + barcode_reader.scan( + barcode_reader.parser.sanitize_ean(barcode) + ); + }, + }, + ]; + } goBackToMainScreen() { return [ { diff --git a/addons/point_of_sale/tests/test_frontend.py b/addons/point_of_sale/tests/test_frontend.py index ceca57ce456..002b2148dfb 100644 --- a/addons/point_of_sale/tests/test_frontend.py +++ b/addons/point_of_sale/tests/test_frontend.py @@ -113,6 +113,7 @@ class TestPointOfSaleHttpCommon(AccountTestInvoicingHttpCommon): 'available_in_pos': True, 'list_price': 1.98, 'taxes_id': False, + 'barcode': '2100002000003', }) cls.small_shelf = env['product.product'].create({ 'name': 'Small Shelf', @@ -125,12 +126,14 @@ class TestPointOfSaleHttpCommon(AccountTestInvoicingHttpCommon): 'available_in_pos': True, 'list_price': 1.98, 'taxes_id': False, + 'barcode': '2301000000006', }) cls.monitor_stand = env['product.product'].create({ 'name': 'Monitor Stand', 'available_in_pos': True, 'list_price': 3.19, 'taxes_id': False, + 'barcode': '0123456789', # No pattern in barcode nomenclature }) cls.desk_pad = env['product.product'].create({ 'name': 'Desk Pad', @@ -151,6 +154,7 @@ class TestPointOfSaleHttpCommon(AccountTestInvoicingHttpCommon): 'available_in_pos': True, 'list_price': 5.10, 'taxes_id': False, + 'barcode': '2300001000008', }) configurable_chair = env['product.product'].create({ 'name': 'Configurable Chair', @@ -856,6 +860,15 @@ class TestUi(TestPointOfSaleHttpCommon): self.main_pos_config.with_user(self.pos_user).open_ui() self.start_tour("/pos/ui?config_id=%d" % self.main_pos_config.id, 'ReceiptScreenDiscountWithPricelistTour', login="pos_user") + def test_07_pos_barcodes_scan(self): + barcode_rule = self.env.ref("point_of_sale.barcode_rule_client") + barcode_rule.pattern = barcode_rule.pattern + "|234" + # should in theory be changed in the JS code to `|^234` + # If not, it will fail as it will mistakenly match with the product barcode "0123456789" + + self.main_pos_config.with_user(self.pos_user).open_ui() + self.start_tour("/pos/ui?config_id=%d" % self.main_pos_config.id, 'BarcodeScanningTour', login="pos_user") + def test_08_show_tax_excluded(self): # define a tax included tax record tax = self.env['account.tax'].create({