diff --git a/addons/test_xlsx_export/tests/test_export.py b/addons/test_xlsx_export/tests/test_export.py index 85f1e8e7e26..d8a3c4eaebd 100644 --- a/addons/test_xlsx_export/tests/test_export.py +++ b/addons/test_xlsx_export/tests/test_export.py @@ -36,7 +36,12 @@ class XlsxCreatorCase(common.HttpCase): } def _mock_write(self, row, column, value, style=None): - self.worksheet[row, column] = str(value) + if isinstance(value, float): + decimal_places = style.num_format[::-1].find('.') + style_format = "{:." + str(decimal_places) + "f}" + self.worksheet[row, column] = style_format.format(value) + else: + self.worksheet[row, column] = str(value) def make(self, values, context=None): return self.model.with_context(**(context or {})).create(values) @@ -71,6 +76,7 @@ class XlsxCreatorCase(common.HttpCase): @tagged('-at_install', 'post_install') class TestGroupedExport(XlsxCreatorCase): model_name = 'export.group_operator' + # pylint: disable=bad-whitespace def test_int_sum_max(self): values = [ @@ -117,12 +123,12 @@ class TestGroupedExport(XlsxCreatorCase): ['Int Sum' ,'Float Min'], ['10 (2)' ,'111.00'], [' 111.0 (1)','111.00'], - ['10' ,'111.0'], + ['10' ,'111.00'], [' 222.0 (1)','222.00'], - ['10' ,'222.0'], + ['10' ,'222.00'], ['20 (1)' ,'333.00'], [' 333.0 (1)','333.00'], - ['20' ,'333.0'], + ['20' ,'333.00'], ]) def test_float_avg(self): @@ -137,12 +143,12 @@ class TestGroupedExport(XlsxCreatorCase): ['Int Sum' ,'Float Avg'], ['10 (2)' ,'150.00'], [' 100.0 (1)','100.00'], - ['10' ,'100.0'], + ['10' ,'100.00'], [' 200.0 (1)','200.00'], - ['10' ,'200.0'], + ['10' ,'200.00'], ['20 (1)' ,'300.00'], [' 300.0 (1)','300.00'], - ['20' ,'300.0'], + ['20' ,'300.00'], ]) def test_float_avg_nested(self): @@ -159,12 +165,12 @@ class TestGroupedExport(XlsxCreatorCase): ['10 (3)' ,'300.00'], [' 20 (1)' ,'600.00'], [' 600.0 (1)','600.00'], - ['10' ,'600.0'], + ['10' ,'600.00'], [' 30 (2)' ,'150.00'], [' 100.0 (1)','100.00'], - ['10' ,'100.0'], + ['10' ,'100.00'], [' 200.0 (1)','200.00'], - ['10' ,'200.0'], + ['10' ,'200.00'], ]) def test_float_avg_nested_no_value(self): @@ -181,11 +187,11 @@ class TestGroupedExport(XlsxCreatorCase): ['10 (3)' ,'0.00'], [' 20 (1)' ,'0.00'], [' Undefined (1)','0.00'], - ['10' ,'0.0'], + ['10' ,'0.00'], [' 30 (2)' ,'0.00'], [' Undefined (2)','0.00'], - ['10' ,'0.0'], - ['10' ,'0.0'], + ['10' ,'0.00'], + ['10' ,'0.00'], ]) def test_date_max(self): @@ -359,11 +365,11 @@ class TestGroupedExport(XlsxCreatorCase): ['Int Sum', 'Float Monetary'], ['1 (1)', '60739.200'], [' 60739.2 (1)', '60739.200'], - ['1', '60739.2'], + ['1', '60739.20'], ['2 (1)', '2.000'], [' 2.0 (1)', '2.000'], - ['2', '2.0'], + ['2', '2.00'], ['3 (1)', '1000.000'], [' 1000.0 (1)', '1000.000'], - ['3', '1000.0'], + ['3', '1000.00'], ]) diff --git a/addons/web/controllers/export.py b/addons/web/controllers/export.py index 2de0e7f1cd1..93eaba4ad6e 100644 --- a/addons/web/controllers/export.py +++ b/addons/web/controllers/export.py @@ -16,8 +16,8 @@ import odoo.modules.registry from odoo import http from odoo.exceptions import UserError from odoo.http import content_disposition, request -from odoo.tools import float_repr, lazy_property, osutil, pycompat -from odoo.tools.misc import xlsxwriter +from odoo.tools import lazy_property, osutil, pycompat +from odoo.tools.misc import xlsxwriter, get_lang from odoo.tools.translate import _ @@ -183,6 +183,11 @@ class ExportXlsxWriter: self.datetime_style = self.workbook.add_format({'text_wrap': True, 'num_format': 'yyyy-mm-dd hh:mm:ss'}) self.worksheet = self.workbook.add_worksheet() self.value = False + decimal_separator = get_lang(request.env).decimal_point + self.float_format = f'0{decimal_separator}00' + decimal_places = [res['decimal_places'] for res in + request.env['res.currency'].search_read([], ['decimal_places'])] + self.monetary_format = f'0{decimal_separator}{max(decimal_places or [2]) * "0"}' if row_count > self.worksheet.xls_rowmax: raise UserError(_('There are too many rows (%s rows, limit: %s) to export as Excel 2007-2013 (.xlsx) format. Consider splitting the export.') % (row_count, self.worksheet.xls_rowmax)) @@ -230,6 +235,8 @@ class ExportXlsxWriter: cell_style = self.datetime_style elif isinstance(cell_value, datetime.date): cell_style = self.date_style + elif isinstance(cell_value, float): + cell_style.set_num_format(self.float_format) self.write(row, column, cell_value, cell_style) @@ -264,26 +271,16 @@ 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']) - # 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) + if field.get('type') == 'monetary': + self.header_bold_style.set_num_format(self.monetary_format) + elif field.get('type') == 'float': + self.header_bold_style.set_num_format(self.float_format) + else: + aggregated_value = str(aggregated_value if aggregated_value is not None else '') + self.write(row, column, aggregated_value, self.header_bold_style) return row + 1, 0