diff --git a/report_xlsx/report/report_xlsx.py b/report_xlsx/report/report_xlsx.py index 7a25cd37f..18037c46c 100644 --- a/report_xlsx/report/report_xlsx.py +++ b/report_xlsx/report/report_xlsx.py @@ -2,7 +2,7 @@ # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). from io import BytesIO - +import re from odoo import models import logging @@ -10,6 +10,63 @@ _logger = logging.getLogger(__name__) try: import xlsxwriter + + class PatchedXlsxWorkbook(xlsxwriter.Workbook): + def _check_sheetname(self, sheetname, is_chartsheet=False): + """We want to avoid duplicated sheet names exceptions the same + following the same philosophy that Odoo implements overriding the + main library to avoid the 31 characters limit triming the strings + before sending them to the library. + + In some cases, there's not much control over this as the reports + send automated data and the potential exception is hidden underneath + making it hard to debug the original issue. Even so, different names + can become the same one as their strings are trimmed to those 31 + character limit. + + This way, once we come across with a duplicated, we set that final 3 + characters with a sequence that we evaluate on the fly. So for + instance: + + - 'Sheet name' will be 'Sheet name~01' + - The next 'Sheet name' will try to rename to 'Sheet name~01' as + well and then that will give us 'Sheet name~02'. + - And the next 'Sheet name' will try to rename to 'Sheet name~01' + and then to 'Sheet name~02' and finally it will be able to + 'Sheet name~03'. + - An so on as many times as duplicated sheet names come to the + workbook up to 100 for each sheet name. We set such limit as we + don't want to truncate the strings too much and keeping in mind + that this issue don't usually ocurrs. + """ + try: + return super()._check_sheetname( + sheetname, is_chartsheet=is_chartsheet + ) + except xlsxwriter.exceptions.DuplicateWorksheetName: + pattern = re.compile(r"~[0-9]{2}$") + duplicated_secuence = ( + re.search(pattern, sheetname) and int(sheetname[-2:]) or 0 + ) + # Only up to 100 duplicates + deduplicated_secuence = "~{:02d}".format( + duplicated_secuence + 1 + ) + if duplicated_secuence > 99: + raise xlsxwriter.exceptions.DuplicateWorksheetName + if duplicated_secuence: + sheetname = re.sub( + pattern, deduplicated_secuence, sheetname + ) + elif len(sheetname) <= 28: + sheetname += deduplicated_secuence + else: + sheetname = sheetname[:28] + deduplicated_secuence + # Refeed the method until we get an unduplicated name + return self._check_sheetname(sheetname, is_chartsheet=is_chartsheet) + + xlsxwriter.Workbook = PatchedXlsxWorkbook + except ImportError: _logger.debug('Can not import xlsxwriter`.')