From b6a36df34a8fa7c45b99d1041e15aaba186c6458 Mon Sep 17 00:00:00 2001 From: Romeo Fragomeli Date: Fri, 9 Sep 2022 08:39:09 +0000 Subject: [PATCH] [IMP] web: convert many2one_barcode to OWL Before this commit there is a widget `many2one_barcode` that allows to scan a barcode. After this commit, we have converted it to OWL, but it's more than a new component. From now, the logic is in the basic Many2One as a property/option (`canScanBarcode`) and we also have the equivalent Component (`widget="many2one_barcode"`). This choice has been made to support multiple use cases (e.g. having the product_configurator and the barcode on the same product field). Note: now we use the `browser` class to allow to patch the browser feature during testing. Part-of: odoo/odoo#100082 --- .../views/fields/many2one/many2one_field.js | 56 +++++ .../views/fields/many2one/many2one_field.xml | 11 + .../many2one_barcode_field.js | 28 +++ .../src/views/fields/relational_utils.js | 9 +- .../src/views/fields/relational_utils.xml | 2 +- .../src/webclient/barcode/barcode_scanner.js | 5 +- .../fields/many2one_barcode_field_tests.js | 157 +++++++++++++ .../fields/many2one_barcode_field_tests.js | 210 ++++++++++++++++++ 8 files changed, 473 insertions(+), 5 deletions(-) create mode 100644 addons/web/static/src/views/fields/many2one_barcode/many2one_barcode_field.js create mode 100644 addons/web/static/tests/mobile/views/fields/many2one_barcode_field_tests.js create mode 100644 addons/web/static/tests/views/fields/many2one_barcode_field_tests.js diff --git a/addons/web/static/src/views/fields/many2one/many2one_field.js b/addons/web/static/src/views/fields/many2one/many2one_field.js index bde43160dad..84f78a7b1e3 100644 --- a/addons/web/static/src/views/fields/many2one/many2one_field.js +++ b/addons/web/static/src/views/fields/many2one/many2one_field.js @@ -1,5 +1,6 @@ /** @odoo-module **/ +import { browser } from "@web/core/browser/browser"; import { Dialog } from "@web/core/dialog/dialog"; import { registry } from "@web/core/registry"; import { _lt } from "@web/core/l10n/translation"; @@ -7,6 +8,8 @@ import { useChildRef, useOwnedDialogs, useService } from "@web/core/utils/hooks" import { sprintf } from "@web/core/utils/strings"; import { standardFieldProps } from "../standard_field_props"; import { Many2XAutocomplete, useOpenMany2XRecord } from "@web/views/fields/relational_utils"; +import { isMobileOS } from "@web/core/browser/feature_detection"; +import * as BarcodeScanner from "@web/webclient/barcode/barcode_scanner"; const { Component, onWillUpdateProps, useState } = owl; @@ -40,6 +43,7 @@ export class Many2OneField extends Component { this.orm = useService("orm"); this.action = useService("action"); this.dialog = useService("dialog"); + this.notification = useService("notification"); this.autocompleteContainerRef = useChildRef(); this.addDialog = useOwnedDialogs(); @@ -168,6 +172,54 @@ export class Many2OneField extends Component { onExternalBtnClick() { this.openDialog(this.resId); } + async onBarcodeBtnClick() { + const barcode = await BarcodeScanner.scanBarcode(); + if (barcode) { + await this.onBarcodeScanned(barcode); + if ("vibrate" in browser.navigator) { + browser.navigator.vibrate(100); + } + } else { + this.notification.add(this.env._t("Please, scan again !"), { + type: "warning", + }); + } + } + async search(barcode) { + const results = await this.orm.call(this.relation, "name_search", [], { + name: barcode, + args: this.getDomain(), + operator: "ilike", + limit: 2, // If one result we set directly and if more than one we use normal flow so no need to search more + context: this.context, + }); + return results.map((result) => { + const [id, displayName] = result; + return { + id, + name: displayName, + }; + }); + } + async onBarcodeScanned(barcode) { + const results = await this.search(barcode); + const records = results.filter((r) => !!r.id); + if (records.length === 1) { + this.update([{ id: records[0].id, name: records[0].name }]); + } else { + const searchInput = this.autocompleteContainerRef.el.querySelector("input"); + searchInput.value = barcode; + searchInput.dispatchEvent(new Event("input")); + if (this.env.isSmall) { + searchInput.click(); + } + } + } + get hasBarcodeButton() { + const canScanBarcode = this.props.canScanBarcode; + const supported = BarcodeScanner.isBarcodeScannerSupported(); + return canScanBarcode && isMobileOS() && supported && !this.hasExternalButton; + } } Many2OneField.SEARCH_MORE_LIMIT = 320; @@ -188,6 +240,7 @@ Many2OneField.props = { searchLimit: { type: Number, optional: true }, relation: { type: String, optional: true }, string: { type: String, optional: true }, + canScanBarcode: { type: Boolean, optional: true }, }; Many2OneField.defaultProps = { canOpen: true, @@ -198,6 +251,7 @@ Many2OneField.defaultProps = { createNameField: "name", searchLimit: 7, string: "", + canScanBarcode: false, }; Many2OneField.displayName = _lt("Many2one"); @@ -210,6 +264,7 @@ Many2OneField.extractProps = ({ attrs, field }) => { const canWrite = attrs.can_write && Boolean(JSON.parse(attrs.can_write)); const noQuickCreate = Boolean(attrs.options.no_quick_create); const noCreateEdit = Boolean(attrs.options.no_create_edit); + const canScanBarcode = Boolean(attrs.options.can_scan_barcode); return { placeholder: attrs.placeholder, @@ -221,6 +276,7 @@ Many2OneField.extractProps = ({ attrs, field }) => { relation: field.relation, string: attrs.string || field.string, createNameField: attrs.options.create_name_field, + canScanBarcode: canScanBarcode, }; }; diff --git a/addons/web/static/src/views/fields/many2one/many2one_field.xml b/addons/web/static/src/views/fields/many2one/many2one_field.xml index 2c85454007f..e855a381527 100644 --- a/addons/web/static/src/views/fields/many2one/many2one_field.xml +++ b/addons/web/static/src/views/fields/many2one/many2one_field.xml @@ -67,6 +67,17 @@ t-on-click="onExternalBtnClick" /> +