From 01077a32a671df93d0a19e3294cb913214c8981c Mon Sep 17 00:00:00 2001 From: "Andrea Grazioso (agr-odoo)" Date: Wed, 10 Feb 2021 18:34:25 +0000 Subject: [PATCH] [FIX] web: fix float value representation in export - Create a product with: Cost: 60.80 Quantity On Hand: 999.0 - Go to Inventory / Reporting / Inventory Valuation - Click on export all (little button next to "Inventory at date") The field "Total value" have too many decimals: 60739.2000000004 This occur because of the multiplication: it yield the correct value (60739.2), but every rounding attempt done, even in the ORM, will mess up the representation https://github.com/odoo/odoo/blob/042298f8c949fba470eda6ad90f94c95ca291030/odoo/fields.py#L1333 opw-2438384 closes odoo/odoo#67558 X-original-commit: 67cf82962688360cfe25c6b2118a7dbd17a6ee95 Signed-off-by: agr-odoo --- addons/test_xlsx_export/models.py | 2 ++ addons/test_xlsx_export/tests/test_export.py | 29 +++++++++++++++++++ addons/web/controllers/main.py | 20 ++++++++++--- .../web/static/src/js/widgets/data_export.js | 1 + .../static/tests/widgets/data_export_tests.js | 4 +++ 5 files changed, 52 insertions(+), 4 deletions(-) diff --git a/addons/test_xlsx_export/models.py b/addons/test_xlsx_export/models.py index 99d96ba63cb..6e4d6d9fa0a 100644 --- a/addons/test_xlsx_export/models.py +++ b/addons/test_xlsx_export/models.py @@ -20,6 +20,8 @@ class GroupOperator(models.Model): int_max = fields.Integer(group_operator='max') float_min = fields.Float(group_operator='min') float_avg = fields.Float(group_operator='avg') + float_monetary = fields.Monetary(currency_field='currency_id', group_operator='sum') + currency_id = fields.Many2one('res.currency') date_max = fields.Date(group_operator='max') bool_and = fields.Boolean(group_operator='bool_and') bool_or = fields.Boolean(group_operator='bool_or') diff --git a/addons/test_xlsx_export/tests/test_export.py b/addons/test_xlsx_export/tests/test_export.py index 22b759e7940..0eaab615d17 100644 --- a/addons/test_xlsx_export/tests/test_export.py +++ b/addons/test_xlsx_export/tests/test_export.py @@ -50,6 +50,7 @@ class XlsxCreatorCase(common.HttpCase): params['fields'] = [{ 'name': self.model._fields[f].name, 'label': self.model._fields[f].string, + 'type': self.model._fields[f].type, } for f in fields] with patch.object(ExportXlsxWriter, 'write', self._mock_write): @@ -340,3 +341,31 @@ class TestGroupedExport(XlsxCreatorCase): [' Undefined (1)' ,''], ['10' ,''], ]) + + def test_float_representation(self): + currency = self.env['res.currency'].create({ + 'name': "bottlecap", + 'symbol': "b", + 'rounding': 0.001, + 'decimal_places': 3, + }) + + values = [ + {'int_sum': 1, 'currency_id': currency.id, 'float_monetary': 60739.2000000004}, + {'int_sum': 2, 'currency_id': currency.id, 'float_monetary': 2.0}, + {'int_sum': 3, 'currency_id': currency.id, 'float_monetary': 999.9995999}, + ] + export = self.export(values, fields=['int_sum', 'float_monetary'], params={'groupby': ['int_sum', 'float_monetary']}) + + self.assertExportEqual(export, [ + ['Int Sum', 'Float Monetary'], + ['1 (1)', '60739.200'], + [' 60739.2 (1)', '60739.200'], + ['1', '60739.2'], + ['2 (1)', '2.000'], + [' 2.0 (1)', '2.000'], + ['2', '2.0'], + ['3 (1)', '1000.000'], + [' 1000.0 (1)', '1000.000'], + ['3', '1000.0'], + ]) diff --git a/addons/web/controllers/main.py b/addons/web/controllers/main.py index 05d35a66575..a5e46ae3a99 100644 --- a/addons/web/controllers/main.py +++ b/addons/web/controllers/main.py @@ -854,13 +854,25 @@ class GroupExportXlsxWriter(ExportXlsxWriter): label = '%s%s (%s)' % (' ' * group_depth, label, group.count) self.write(row, column, label, self.header_bold_style) + if any(f.get('type') == 'monetary' for f in self.fields[1:]): + + decimal_places = [res['decimal_places'] for res in group._model.env['res.currency'].search_read([], ['decimal_places'])] + decimal_places = max(decimal_places) if decimal_places else 2 for field in self.fields[1:]: # No aggregates allowed in the first column because of the group title column += 1 aggregated_value = aggregates.get(field['name']) - # Non-stored float fields may not be displayed properly because of float representation - # => we force 2 digits - if not field.get('store') and isinstance(aggregated_value, float): - aggregated_value = float_repr(aggregated_value, 2) + # Float fields may not be displayed properly because of float + # representation issue with non stored fields or with values + # that, even stored, cannot be rounded properly and it is not + # acceptable to display useless digits (i.e. monetary) + # + # non stored field -> we force 2 digits + # stored monetary -> we force max digits of installed currencies + if isinstance(aggregated_value, float): + if field.get('type') == 'monetary': + aggregated_value = float_repr(aggregated_value, decimal_places) + elif not field.get('store'): + aggregated_value = float_repr(aggregated_value, 2) self.write(row, column, str(aggregated_value if aggregated_value is not None else ''), self.header_bold_style) return row + 1, 0 diff --git a/addons/web/static/src/js/widgets/data_export.js b/addons/web/static/src/js/widgets/data_export.js index 7f4d43d6d66..b491bdb7ae0 100644 --- a/addons/web/static/src/js/widgets/data_export.js +++ b/addons/web/static/src/js/widgets/data_export.js @@ -121,6 +121,7 @@ var DataExport = Dialog.extend({ name: field, label: this.record.fields[field].string, store: this.record.fields[field].store, + type: this.record.fields[field].type, })); this._exportData(exportedFields, 'xlsx', false); }, diff --git a/addons/web/static/tests/widgets/data_export_tests.js b/addons/web/static/tests/widgets/data_export_tests.js index 8890ee48c20..7f9a74e9ce4 100644 --- a/addons/web/static/tests/widgets/data_export_tests.js +++ b/addons/web/static/tests/widgets/data_export_tests.js @@ -323,9 +323,11 @@ QUnit.module('widgets', { fields: [{ name: 'foo', label: 'Foo', + type: 'char', }, { name: 'bar', label: 'Bar', + type: 'char', }] }, "should be called with correct params"); args.complete(); @@ -368,9 +370,11 @@ QUnit.module('widgets', { fields: [{ name: 'foo', label: 'Foo', + type: 'char', }, { name: 'bar', label: 'Bar', + type: 'char', }] }, "should be called with correct params"); args.complete();