From a19ad0fe24b37fbd4414fcdde960a8881d1160bf Mon Sep 17 00:00:00 2001 From: "Lucas Perais (lpe)" Date: Tue, 7 Sep 2021 06:22:31 +0000 Subject: [PATCH] [FIX] web: formatter: monetary with human number HumanNumber now correctly rounds the number according to decimals (bug with value < 1000 eg: 0.02) It also correctly keeps `decimals` significant figures whatever powers of 10^3 it should keep The monetary formatter now passes the currency's digits to human number Part-of: odoo/odoo#73311 --- addons/web/static/src/fields/formatters.js | 19 ++++++++--- .../static/tests/fields/formatters_tests.js | 34 ++++++++++++++----- 2 files changed, 40 insertions(+), 13 deletions(-) diff --git a/addons/web/static/src/fields/formatters.js b/addons/web/static/src/fields/formatters.js index e8d6395aa4c..e7f98f9b611 100644 --- a/addons/web/static/src/fields/formatters.js +++ b/addons/web/static/src/fields/formatters.js @@ -36,7 +36,9 @@ function insertThousandsSep(number, thousandsSep = ",", grouping = [3, 0]) { * @private * @param {number} number to format * @param {Object} [options] Options to format - * @param {number} [options.decimals=0] maximum number of decimals to use + * @param {number} [options.decimals=0] number of decimals to use + * if minDigits > 1 is used and effective on the number then decimals + * will be shrunk to zero, to avoid displaying irrelevant figures ( 0.01 compared to 1000 ) * @param {number} [options.minDigits=1] * the minimum number of digits to preserve when switching to another * level of thousands (e.g. with a value of '2', 4321 will still be @@ -44,11 +46,11 @@ function insertThousandsSep(number, thousandsSep = ",", grouping = [3, 0]) { * @returns {string} */ function humanNumber(number, options = { decimals: 0, minDigits: 1 }) { - number = Math.round(number); const decimals = options.decimals || 0; const minDigits = options.minDigits || 1; const d2 = Math.pow(10, decimals); const numberMagnitude = +number.toExponential().split("e+")[1]; + number = Math.round(number * d2) / d2; // the case numberMagnitude >= 21 corresponds to a number // better expressed in the scientific format. if (numberMagnitude >= 21) { @@ -72,7 +74,10 @@ function humanNumber(number, options = { decimals: 0, minDigits: 1 }) { } } const { decimalPoint, grouping, thousandsSep } = l10n; - const [integerPart, decimalPart] = String(number).split("."); + + // determine if we should keep the decimals (we don't want to display 1,020.02k for 1020020) + const decimalsToKeep = number >= 1000 ? 0 : decimals; + const [integerPart, decimalPart] = number.toFixed(decimalsToKeep).split("."); const int = insertThousandsSep(sign * Number(integerPart), thousandsSep, grouping); if (!decimalPart) { return int + symbol; @@ -282,7 +287,13 @@ export function formatMonetary(value, options = {}) { const currency = session.currencies[currencyId]; const digits = (currency && currency.digits) || options.digits; - const formatted = options.humanReadable ? humanNumber(value) : formatFloat(value, { digits }); + let formatted; + if (options.humanReadable) { + formatted = humanNumber(value, { decimals: digits ? digits[1] : 2 }); + } else { + formatted = formatFloat(value, { digits }); + } + if (!currency || options.noSymbol) { return formatted; } diff --git a/addons/web/static/tests/fields/formatters_tests.js b/addons/web/static/tests/fields/formatters_tests.js index 86bd0391164..152052b9453 100644 --- a/addons/web/static/tests/fields/formatters_tests.js +++ b/addons/web/static/tests/fields/formatters_tests.js @@ -61,7 +61,7 @@ QUnit.module("Fields", (hooks) => { ); assert.strictEqual( formatFloat(10200000, { humanReadable: true, decimals: 2, minDigits: 2 }), - "10.2M" + "10.20M" ); assert.strictEqual( formatFloat(1020, { humanReadable: true, decimals: 2, minDigits: 1 }), @@ -69,15 +69,15 @@ QUnit.module("Fields", (hooks) => { ); assert.strictEqual( formatFloat(1002, { humanReadable: true, decimals: 2, minDigits: 1 }), - "1k" + "1.00k" ); assert.strictEqual( formatFloat(101, { humanReadable: true, decimals: 2, minDigits: 1 }), - "101" + "101.00" ); assert.strictEqual( formatFloat(64.2, { humanReadable: true, decimals: 2, minDigits: 1 }), - "64" + "64.20" ); assert.strictEqual(formatFloat(1e18, { humanReadable: true }), "1E"); assert.strictEqual( @@ -110,7 +110,7 @@ QUnit.module("Fields", (hooks) => { ); assert.strictEqual( formatFloat(-10200000, { humanReadable: true, decimals: 2, minDigits: 2 }), - "-10.2M" + "-10.20M" ); assert.strictEqual( formatFloat(-1020, { humanReadable: true, decimals: 2, minDigits: 1 }), @@ -118,15 +118,15 @@ QUnit.module("Fields", (hooks) => { ); assert.strictEqual( formatFloat(-1002, { humanReadable: true, decimals: 2, minDigits: 1 }), - "-1k" + "-1.00k" ); assert.strictEqual( formatFloat(-101, { humanReadable: true, decimals: 2, minDigits: 1 }), - "-101" + "-101.00" ); assert.strictEqual( formatFloat(-64.2, { humanReadable: true, decimals: 2, minDigits: 1 }), - "-64" + "-64.20" ); assert.strictEqual(formatFloat(-1e18, { humanReadable: true }), "-1E"); assert.strictEqual( @@ -235,9 +235,14 @@ QUnit.module("Fields", (hooks) => { formatMonetary(1234567.654, { currencyId: 10, noSymbol: true }), "1,234,567.65" ); + assert.deepEqual(formatMonetary(8.0, { currencyId: 10, humanReadable: true }), "8.00 €"); assert.deepEqual( formatMonetary(1234567.654, { currencyId: 10, humanReadable: true }), - "1M €" + "1.23M €" + ); + assert.deepEqual( + formatMonetary(1990000.001, { currencyId: 10, humanReadable: true }), + "1.99M €" ); assert.deepEqual( formatMonetary(1234567.654, { currencyId: 44, digits: [69, 1] }), @@ -269,6 +274,17 @@ QUnit.module("Fields", (hooks) => { assert.strictEqual(formatMonetary(200, { field: floatField, data }), "$ 200.00"); }); + QUnit.test("formatMonetary without currency", function (assert) { + patchWithCleanup(session, { + currencies: {}, + }); + assert.deepEqual( + formatMonetary(1234567.654, { currencyId: 10, humanReadable: true }), + "1.23M" + ); + assert.deepEqual(formatMonetary(1234567.654, { currencyId: 10 }), "1,234,567.65"); + }); + QUnit.test("formatPercentage", function (assert) { assert.strictEqual(formatPercentage(false), "0%"); assert.strictEqual(formatPercentage(0), "0%");