[FIX] web: allow filtering with decimals on price filter + unit test

Issue

	- Install "Sales"
	- Switch to "German" language
	- Go to Sales -> Products -> Products
	- Try to filter on "Public Price" is equal to "2,3"

	Not possible to add decimal point at the end (only in middle of number).

Cause

	In case the 'decimal point' in DB params is not a dot '.',
	the filter input will be considered as 'text' instead of
	'number'.

	In case of the 'number' type; HTML do already a pre and post
	processing, including managing decimal point (who, for example,
	is not included in ev.target.value if last char is a '.').
	Unfortunalty, the library is not well working with other
	language and not supported on every browser, therefore,
	must use own logic.

	In case of a 'text' type, the value will be send to 'parseFloat'
	,then `parseNumber` will replace decimal_point by dot (also one the
	issues since needed to display decimal_point according user language),
	and `Number` will remove the decimal_point in case of '123,' -> '123',
	and therefore we will not be able to write decimals ( apart of adding
	the decimal point after writing the whole number...)

Solution

	If user input is well parsed, store parsed value in condition.value and
	set condition.displayedValue to the input value (an so without updating
	input value). Else, replace input value with previous value (who should
	be the condition.DisplayedValue).

opw-2463441

closes odoo/odoo#67978

X-original-commit: e795ce5bff14b6b748c1f4a2651946aafdc299f4
Signed-off-by: bon-odoo <nboulif@users.noreply.github.com>
This commit is contained in:
Julien Mougenot
2021-03-16 16:02:07 +00:00
committed by Nasreddin (bon)
parent 387e9a402a
commit 9bf7d15da4
3 changed files with 77 additions and 18 deletions
@@ -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;
}
}
}
+3 -3
View File
@@ -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)"
/>
<input t-elif="['integer', 'id'].includes(fieldType)"
class="o_input"
step="1"
type="number"
t-att-value="condition.value or 0"
t-att-value="condition.displayedValue"
t-on-input="_onValueInput(condition)"
/>
<input t-else=""
type="text"
class="o_input"
t-att-value="condition.value or ''"
t-att-value="condition.displayedValue"
t-on-input="_onValueInput(condition)"
/>
</span>
@@ -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();
});