[REF] barcodes,web: barcode_handler and field_float_scannable to wowl

closes odoo/odoo#102150

X-original-commit: c57a0ada02bfc1f02b3516f92f87b510942c5a87
Signed-off-by: Aaron Bohy (aab) <aab@odoo.com>
This commit is contained in:
Joseph Caburnay
2022-10-04 20:57:44 +02:00
parent 75ca493a15
commit ec2b66fb13
13 changed files with 234 additions and 40 deletions
+3 -7
View File
@@ -15,13 +15,9 @@
'web.assets_backend': [
'barcodes/static/src/**/*',
],
'web.qunit_suite_tests': [
'barcodes/static/tests/barcode_tests.js',
'barcodes/static/tests/barcode_parser_tests.js',
],
'web.qunit_mobile_suite_tests': [
'barcodes/static/tests/barcode_mobile_tests.js',
],
'web.tests_assets': ['barcodes/static/tests/helpers.js'],
'web.qunit_suite_tests': ['barcodes/static/tests/basic/*.js'],
'web.qunit_mobile_suite_tests': ['barcodes/static/tests/mobile/*.js'],
},
'license': 'LGPL-3',
}
@@ -0,0 +1,23 @@
/** @odoo-module **/
import { registry } from "@web/core/registry";
import { standardFieldProps } from "@web/views/fields/standard_field_props";
import { useBus, useService } from "@web/core/utils/hooks";
const { Component, xml } = owl;
export class BarcodeHandlerField extends Component {
setup() {
const barcode = useService("barcode");
useBus(barcode.bus, "barcode_scanned", this.onBarcodeScanned);
}
onBarcodeScanned(event) {
const { barcode } = event.detail;
this.props.update(barcode);
}
}
BarcodeHandlerField.template = xml``;
BarcodeHandlerField.props = { ...standardFieldProps };
registry.category("fields").add("barcode_handler", BarcodeHandlerField);
@@ -43,7 +43,8 @@ export const barcodeService = {
function handleBarcode(barcode, target) {
bus.trigger('barcode_scanned', {barcode,target});
if (target.getAttribute('barcode_events') === "true") {
$(target).trigger('barcode_scanned', barcode);
const barcodeScannedEvent = new CustomEvent("barcode_scanned", { detail: { barcode, target } });
target.dispatchEvent(barcodeScannedEvent);
}
}
@@ -0,0 +1,13 @@
/** @odoo-module **/
import { registry } from "@web/core/registry";
import { FloatField } from "@web/views/fields/float/float_field";
export class FloatScannableField extends FloatField {
onBarcodeScanned() {
this.inputRef.el.dispatchEvent(new InputEvent("input"));
}
}
FloatScannableField.template = "barcodes.FloatScannableField";
registry.category("fields").add("field_float_scannable", FloatScannableField);
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates id="template" xml:space="preserve">
<t t-name="barcodes.FloatScannableField" t-inherit="web.FloatField" t-inherit-mode="primary" owl="1">
<xpath expr="//input" position="attributes">
<attribute name="t-on-barcode_scanned">onBarcodeScanned</attribute>
<attribute name="t-att-data-enable-barcode">true</attribute>
<attribute name="barcode_events">true</attribute>
</xpath>
</t>
</templates>
@@ -7,34 +7,17 @@ const {barcodeRemapperService} = require("@barcodes/js/barcode_events");
const { makeTestEnv } = require("@web/../tests/helpers/mock_env");
const { registry } = require("@web/core/registry");
const { mockTimeout } = require("@web/../tests/helpers/utils");
const { simulateBarCode } = require("@barcodes/../tests/helpers");
var FormController = require('web.FormController');
var FormView = require('web.FormView');
var testUtils = require('web.test_utils');
var createView = testUtils.createView;
var triggerEvent = testUtils.dom.triggerEvent;
var core = require('web.core');
const maxTimeBetweenKeysInMs = barcodeService.maxTimeBetweenKeysInMs;
function simulateBarCode(chars, target = document.body) {
for (let char of chars) {
let keycode;
if (char === 'Enter') {
keycode = $.ui.keyCode.ENTER;
} else if (char === "Tab") {
keycode = $.ui.keyCode.TAB;
} else {
keycode = char.charCodeAt(0);
}
triggerEvent(target, 'keydown', {
key: char,
keyCode: keycode,
});
}
}
QUnit.module('Barcodes', {
beforeEach: function () {
barcodeService.maxTimeBetweenKeysInMs = 0;
@@ -0,0 +1,142 @@
/** @odoo-module **/
import { nextTick, patchWithCleanup, getFixture } from "@web/../tests/helpers/utils";
import { makeView, setupViewRegistries } from "@web/../tests/views/helpers";
import { barcodeGenericHandlers } from "@barcodes/barcode_handlers";
import { barcodeService } from "@barcodes/barcode_service";
import { simulateBarCode } from "../helpers";
import { FormController } from "@web/views/form/form_controller";
import { registry } from "@web/core/registry";
const maxTimeBetweenKeysInMs = barcodeService.maxTimeBetweenKeysInMs;
let target;
let serverData;
QUnit.module("Fields", (hooks) => {
hooks.beforeEach(() => {
target = getFixture();
barcodeService.maxTimeBetweenKeysInMs = 0;
registry.category("services").add("barcode", barcodeService, { force: true });
registry
.category("services")
.add("barcode_autoclick", barcodeGenericHandlers, { force: true });
serverData = {
models: {
product: {
fields: {
name: { string: "Product name", type: "char" },
float_field: { string: "Float", type: "float" },
float_field_2: { string: "Float", type: "float" },
barcode: { string: "Barcode", type: "char" },
},
records: [
{ id: 1, name: "Large Cabinet", barcode: "1234567890" },
{ id: 2, name: "Cabinet with Doors", barcode: "0987654321" },
],
},
},
};
setupViewRegistries();
});
hooks.afterEach(() => {
barcodeService.maxTimeBetweenKeysInMs = maxTimeBetweenKeysInMs;
});
QUnit.module("FloatScannableField");
QUnit.test("widget field_float_scannable", async function (assert) {
serverData.models.product.records[0].float_field = 4;
const onBarcodeScanned = (event) => {
assert.step(`barcode scanned ${event.detail.barcode}`);
};
const view = await makeView({
type: "form",
resModel: "product",
serverData,
resId: 1,
arch: /*xml*/ `
<form>
<field name="display_name"/>
<field name="float_field" widget="field_float_scannable"/>
<field name="float_field_2"/>
</form>
`,
});
view.env.services.barcode.bus.addEventListener("barcode_scanned", onBarcodeScanned);
const inputDiv1 = target.querySelector(".o_field_widget[name=float_field]");
assert.strictEqual(
inputDiv1.querySelector("input").value,
"4.00",
"should display the correct value"
);
// we check here that a scan on the field_float_scannable widget triggers a
// barcode event
inputDiv1.querySelector("input").focus();
simulateBarCode(["6", "0", "1", "6", "4", "7", "8", "5"], inputDiv1, "input");
await nextTick();
assert.verifySteps(["barcode scanned 60164785"]);
assert.strictEqual(
inputDiv1.querySelector("input"),
document.activeElement,
"float field should stay focused"
);
// we check here that a scan on the field without widget does not trigger a
// barcode event
const inputDiv2 = target.querySelector(".o_field_widget[name=float_field_2]");
inputDiv2.querySelector("input").focus();
simulateBarCode(["6", "0", "1", "6", "4", "7", "8", "5"], inputDiv2, "input");
await nextTick();
assert.verifySteps([]);
view.env.services.barcode.bus.removeEventListener("barcode_scanned", onBarcodeScanned);
});
QUnit.test("do no update form twice after a command barcode scanned", async function (assert) {
assert.expect(5);
patchWithCleanup(FormController.prototype, {
onPagerUpdate() {
assert.step("update");
return this._super.apply(this, arguments);
},
});
await makeView({
type: "form",
resModel: "product",
serverData,
arch: /*xml*/ `
<form>
<field name="display_name"/>
<field name="float_field" widget="field_float_scannable"/>
</form>
`,
mockRPC(_route, args) {
if (args.method === "read") {
assert.step("read");
}
},
resId: 1,
resIds: [1, 2],
});
assert.verifySteps(["read"], "update should not have been called yet");
// switch to next record
simulateBarCode(
["O", "-", "C", "M", "D", ".", "N", "E", "X", "T", "Enter"],
document.activeElement
);
await nextTick();
// a first update is done to reload the data (thus followed by a read), but
// update shouldn't be called afterwards
assert.verifySteps(["update", "read"]);
});
});
+20
View File
@@ -0,0 +1,20 @@
/* @odoo-module */
import { triggerEvent } from "@web/../tests/helpers/utils";
export function simulateBarCode(chars, target = document.body, selector = undefined) {
for (let char of chars) {
let keycode;
if (char === "Enter") {
keycode = $.ui.keyCode.ENTER;
} else if (char === "Tab") {
keycode = $.ui.keyCode.TAB;
} else {
keycode = char.charCodeAt(0);
}
triggerEvent(target, selector, "keydown", {
key: char,
keyCode: keycode,
});
}
}
@@ -12,7 +12,7 @@ const { Component } = owl;
export class FloatField extends Component {
setup() {
useInputField({
this.inputRef = useInputField({
getValue: () => this.formattedValue,
refName: "numpadDecimal",
parse: (v) => this.parse(v),
@@ -152,4 +152,6 @@ export function useInputField(params) {
}
}
}
return inputRef;
}
@@ -201,19 +201,7 @@ export class FormController extends Component {
offset: resIds.indexOf(this.model.root.resId),
limit: 1,
total: resIds.length,
onUpdate: async ({ offset }) => {
await this.model.root.askChanges(); // ensures that isDirty is correct
let canProceed = true;
if (this.model.root.isDirty) {
canProceed = await this.model.root.save({
stayInEdition: true,
useSaveErrorDialog: true,
});
}
if (canProceed) {
return this.model.load({ resId: resIds[offset] });
}
},
onUpdate: ({ offset }) => this.onPagerUpdate({ offset, resIds }),
};
}
});
@@ -263,6 +251,20 @@ export class FormController extends Component {
return this.model.root.data.display_name || this.env._t("New");
}
async onPagerUpdate({ offset, resIds }) {
await this.model.root.askChanges(); // ensures that isDirty is correct
let canProceed = true;
if (this.model.root.isDirty) {
canProceed = await this.model.root.save({
stayInEdition: true,
useSaveErrorDialog: true,
});
}
if (canProceed) {
return this.model.load({ resId: resIds[offset] });
}
}
beforeUnload() {
return this.model.root.urgentSave();
}