From b54df52774e43625c77d5c9e790141e82b83fff2 Mon Sep 17 00:00:00 2001 From: "Ruben Gomes (rugo)" Date: Fri, 2 Feb 2024 09:48:57 +0100 Subject: [PATCH] [IMP] odoo.tools: add rounding_mode and amount_rounding to formatLang Adds the possibility to give formatLang() a 'rounding_mode' (any Decimal rounding mode) and an amount_rounding ('decimals', 'units', 'thousands', 'millions' and 'lakhs'). 'amount_rounding' will display the amount in the given unit. For example, 10456 in 'thousands' will be 10. task-3626894 closes odoo/odoo#151314 Related: odoo/enterprise#55218 Signed-off-by: John Laterre (jol) --- odoo/addons/base/tests/test_misc.py | 22 +++++++++++ odoo/tools/misc.py | 58 +++++++++++++++++++++++------ 2 files changed, 68 insertions(+), 12 deletions(-) diff --git a/odoo/addons/base/tests/test_misc.py b/odoo/addons/base/tests/test_misc.py index f02d1c19713..8c3e638bf7c 100644 --- a/odoo/addons/base/tests/test_misc.py +++ b/odoo/addons/base/tests/test_misc.py @@ -583,6 +583,28 @@ class TestFormatLang(TransactionCase): # If we have a 'dp' and 'currency_obj', we use the decimal precision of 'dp' and the format of 'currency_obj'. self.assertEqual(misc.formatLang(self.env, 100, dp=decimal_precision.name, currency_obj=currency_object), '100.000%sfL' % u'\N{NO-BREAK SPACE}') + def test_rounding_method(self): + self.assertEqual(misc.formatLang(self.env, 100.205), '100.20') # Default is 'HALF-EVEN' + self.assertEqual(misc.formatLang(self.env, 100.215), '100.22') # Default is 'HALF-EVEN' + + self.assertEqual(misc.formatLang(self.env, 100.205, rounding_method='HALF-UP'), '100.21') + self.assertEqual(misc.formatLang(self.env, 100.215, rounding_method='HALF-UP'), '100.22') + + self.assertEqual(misc.formatLang(self.env, 100.205, rounding_method='HALF-DOWN'), '100.20') + self.assertEqual(misc.formatLang(self.env, 100.215, rounding_method='HALF-DOWN'), '100.21') + + def test_rounding_unit(self): + self.assertEqual(misc.formatLang(self.env, 1000000.00), '1,000,000.00') + self.assertEqual(misc.formatLang(self.env, 1000000.00, rounding_unit='units'), '1,000,000') + self.assertEqual(misc.formatLang(self.env, 1000000.00, rounding_unit='thousands'), '1,000') + self.assertEqual(misc.formatLang(self.env, 1000000.00, rounding_unit='lakhs'), '10') + self.assertEqual(misc.formatLang(self.env, 1000000.00, rounding_unit="millions"), '1') + + def test_rounding_method_and_rounding_unit(self): + self.assertEqual(misc.formatLang(self.env, 1822060000, rounding_method='HALF-UP', rounding_unit='lakhs'), '18,221') + self.assertEqual(misc.formatLang(self.env, 1822050000, rounding_method='HALF-UP', rounding_unit='lakhs'), '18,221') + self.assertEqual(misc.formatLang(self.env, 1822049900, rounding_method='HALF-UP', rounding_unit='lakhs'), '18,220') + class TestUrlValidate(BaseCase): def test_url_validate(self): diff --git a/odoo/tools/misc.py b/odoo/tools/misc.py index 09b93f86f18..cf55013139b 100644 --- a/odoo/tools/misc.py +++ b/odoo/tools/misc.py @@ -1373,24 +1373,58 @@ def babel_locale_parse(lang_code): except: return babel.Locale.parse("en_US") -def formatLang(env, value, digits=2, grouping=True, monetary=False, dp=None, currency_obj=None): +def formatLang(env, value, digits=2, grouping=True, monetary=False, dp=None, currency_obj=None, rounding_method='HALF-EVEN', rounding_unit='decimals'): """ - Assuming 'Account' decimal.precision=3: - formatLang(value) -> digits=2 (default) - formatLang(value, digits=4) -> digits=4 - formatLang(value, dp='Account') -> digits=3 - formatLang(value, digits=5, dp='Account') -> digits=5 + This function will format a number `value` to the appropriate format of the language used. + + :param Object env: The environment. + :param float value: The value to be formatted. + :param int digits: The number of decimals digits. + :param bool grouping: Usage of language grouping or not. + :param bool monetary: Usage of thousands separator or not. + .. deprecated:: 13.0 + :param str dp: Name of the decimals precision to be used. This will override ``digits`` + and ``currency_obj`` precision. + :param Object currency_obj: Currency to be used. This will override ``digits`` precision. + :param str rounding_method: The rounding method to be used: + **'HALF-UP'** will round to the closest number with ties going away from zero, + **'HALF-DOWN'** will round to the closest number with ties going towards zero, + **'HALF_EVEN'** will round to the closest number with ties going to the closest + even number, + **'UP'** will always round away from 0, + **'DOWN'** will always round towards 0. + :param str rounding_unit: The rounding unit to be used: + **decimals** will round to decimals with ``digits`` or ``dp`` precision, + **units** will round to units without any decimals, + **thousands** will round to thousands without any decimals, + **lakhs** will round to lakhs without any decimals, + **millions** will round to millions without any decimals. + + :returns: The value formatted. + :rtype: str """ # We don't want to return 0 - if isinstance(value, str) and not value: + if value == '': return '' - if dp: - digits = env['decimal.precision'].precision_get(dp) - elif currency_obj: - digits = currency_obj.decimal_places + if rounding_unit == 'decimals': + if dp: + digits = env['decimal.precision'].precision_get(dp) + elif currency_obj: + digits = currency_obj.decimal_places + else: + digits = 0 - rounded_value = float_round(value, precision_digits=digits, rounding_method='HALF-EVEN') + rounding_unit_mapping = { + 'decimals': 1, + 'thousands': 10**3, + 'lakhs': 10**5, + 'millions': 10**6, + } + + value /= rounding_unit_mapping.get(rounding_unit, 1) + + rounded_value = float_round(value, precision_digits=digits, rounding_method=rounding_method) formatted_value = get_lang(env).format(f'%.{digits}f', rounded_value, grouping=grouping, monetary=monetary) if currency_obj and currency_obj.symbol: