diff --git a/addons/web/static/src/js/control_panel/custom_filter_item.js b/addons/web/static/src/js/control_panel/custom_filter_item.js index 29fc35084a5..a996cfe97cf 100644 --- a/addons/web/static/src/js/control_panel/custom_filter_item.js +++ b/addons/web/static/src/js/control_panel/custom_filter_item.js @@ -107,6 +107,7 @@ odoo.define('web.CustomFilterItem', function (require) { const fieldType = this.fields[condition.field].type; const genericType = FIELD_TYPES[fieldType]; const operator = FIELD_OPERATORS[genericType][condition.operator]; + // Logical value switch (genericType) { case 'id': case 'number': @@ -131,6 +132,12 @@ odoo.define('web.CustomFilterItem', function (require) { default: condition.value = ""; } + // Displayed value + if (["float", "monetary"].includes(fieldType)) { + condition.displayedValue = `0${this.DECIMAL_POINT}0`; + } else { + condition.displayedValue = String(condition.value); + } } //--------------------------------------------------------------------- @@ -237,20 +244,25 @@ odoo.define('web.CustomFilterItem', function (require) { * @param {Event} ev */ _onValueInput(condition, ev) { - const type = this.fields[condition.field].type; - if (['float', 'integer', 'id'].includes(type)) { - const previousValue = condition.value; - const parser = field_utils.parse[type === 'float' ? 'float' : 'integer']; + if (!ev.target.value) { + return this._setDefaultValue(condition); + } + let { type } = this.fields[condition.field]; + if (type === "id") { + type = "integer"; + } + if (FIELD_TYPES[type] === "number") { try { - const parsed = parser(ev.target.value || 0); - // Force parsed value in the input. - ev.target.value = condition.value = (parsed || 0); + // Write logical value into the 'value' property + condition.value = field_utils.parse[type](ev.target.value); + // Write displayed value in the input and 'displayedValue' property + condition.displayedValue = ev.target.value; } catch (err) { - // Force previous value if non-parseable. - ev.target.value = previousValue || 0; + // Parsing error: reverts to previous value + ev.target.value = condition.displayedValue; } } else { - condition.value = ev.target.value || ""; + condition.value = condition.displayedValue = ev.target.value; } } } diff --git a/addons/web/static/src/xml/base.xml b/addons/web/static/src/xml/base.xml index 8dcde9c934e..c3655c8d47c 100644 --- a/addons/web/static/src/xml/base.xml +++ b/addons/web/static/src/xml/base.xml @@ -390,20 +390,20 @@ t-att-type="DECIMAL_POINT === '.' ? 'number' : 'text'" t-attf-title="Number using {{ DECIMAL_POINT }} as decimal separator." t-attf-pattern="[0-9]+([\\{{ DECIMAL_POINT }}][0-9]+)?" - t-att-value="condition.value or (0 + DECIMAL_POINT + 0)" + t-att-value="condition.displayedValue" t-on-input="_onValueInput(condition)" /> diff --git a/addons/web/static/tests/control_panel/custom_filter_item_tests.js b/addons/web/static/tests/control_panel/custom_filter_item_tests.js index e00610bba44..89e64d2d613 100644 --- a/addons/web/static/tests/control_panel/custom_filter_item_tests.js +++ b/addons/web/static/tests/control_panel/custom_filter_item_tests.js @@ -327,7 +327,7 @@ odoo.define('web.filter_menu_generator_tests', function (require) { }); QUnit.test('input value parsing', async function (assert) { - assert.expect(6); + assert.expect(7); const cfi = await createComponent(CustomFilterItem, { props: { @@ -352,16 +352,63 @@ odoo.define('web.filter_menu_generator_tests', function (require) { assert.strictEqual(idInput.value, "0"); // Float parsing - await testUtils.fields.editInput(floatInput, 4.2); + await testUtils.fields.editInput(floatInput, "4.2"); assert.strictEqual(floatInput.value, "4.2"); await testUtils.fields.editInput(floatInput, "DefinitelyValidFloat"); - assert.strictEqual(floatInput.value, "4.2"); + // String input in a number input gives "", which is parsed as 0 + assert.strictEqual(floatInput.value, "0.0"); // Number parsing - await testUtils.fields.editInput(idInput, 4); + await testUtils.fields.editInput(idInput, "4"); + assert.strictEqual(idInput.value, "4"); + await testUtils.fields.editInput(idInput, "4.2"); assert.strictEqual(idInput.value, "4"); await testUtils.fields.editInput(idInput, "DefinitelyValidID"); - assert.strictEqual(idInput.value, "4"); + // String input in a number input gives "", which is parsed as 0 + assert.strictEqual(idInput.value, "0"); + + cfi.destroy(); + }); + + QUnit.test('input value parsing with language', async function (assert) { + assert.expect(5); + + const cfi = await createComponent(CustomFilterItem, { + props: { + fields: this.fields, + }, + env: { + searchModel: new ActionModel(), + _t: Object.assign(s => s, { database: { parameters: { decimal_point: "," } }}), + }, + translateParameters: { + decimal_point: ",", + thousands_sep: "", + grouping: [3, 0], + }, + }); + + await cpHelpers.toggleAddCustomFilter(cfi); + await testUtils.dom.click('button.o_add_condition'); + + const [floatSelect] = cfi.el.querySelectorAll('.o_generator_menu_field'); + await testUtils.fields.editSelect(floatSelect, 'float_field'); + + const [floatInput] = cfi.el.querySelectorAll('.o_generator_menu_value .o_input'); + + // Default values + assert.strictEqual(floatInput.value, "0,0"); + + // Float parsing + await testUtils.fields.editInput(floatInput, '4,'); + assert.strictEqual(floatInput.value, "4,"); + await testUtils.fields.editInput(floatInput, '4,2'); + assert.strictEqual(floatInput.value, "4,2"); + await testUtils.fields.editInput(floatInput, '4,2,'); + assert.strictEqual(floatInput.value, "4,2"); + await testUtils.fields.editInput(floatInput, "DefinitelyValidFloat"); + // The input here is a string, resulting in a parsing error instead of 0 + assert.strictEqual(floatInput.value, "4,2"); cfi.destroy(); });