From b8284f6818301d4e6c40585c8904e447f37f2a16 Mon Sep 17 00:00:00 2001 From: Felipe Garcia Suez Date: Mon, 14 Sep 2026 15:47:07 +0000 Subject: [PATCH] [FIX] account_statement_import_sheet_file_bg: read the rows of an xls, and every row of a csv Importing an xls with a sheet mapping has been failing since the split was introduced: the xlrd branch of split_base64_excel builds the (workbook, sheet) tuple but never reads the rows, so `all_rows` is left unbound and the method dies on `if not all_rows` before anything is imported. The branch that was meant to cover it could not work either. It delegated to `super().parse_header()`, a method no module defines -- the sheet parser has one per format, `parse_header_csv`, `parse_header_xls` and `parse_header_xlsx` -- and it handed `_parse_rows` a two-value tuple where it unpacks three. Past that, it assigned the parsed statement lines to `all_rows`, which the split below writes out as if they were the rows of the file. So the xlrd branch now reads the rows the way the openpyxl one does, plain values of every row and every column, and that dead branch goes away with the two variables only it used. The header is parsed the same way for the three formats, from the rows already read. The csv branch read its rows differently from the other two: it dropped the empty ones before the split. That shifted every line number below them, so a mapping whose header row number is 2 found the header on the second *non empty* line instead of the second line of the file, and a statement with a blank line above the header failed with `'Date' is not in list` -- while the same file as xlsx imported fine. It now keeps every row, like the other two branches; empty rows are the parser's business, through skip_empty_lines. Change note: cuando el importador de extractos corre en segundo plano, un archivo .xls ya no falla, y un .csv cuenta el numero de fila de la cabecera sobre las lineas que el archivo realmente tiene, igual que un Excel. Antes, una linea en blanco arriba de la cabecera corria la cuenta y la importacion fallaba con "'Date' is not in list". closes ingadhoc/miscellaneous#447 Signed-off-by: Camila Vives --- .../models/account_statement_import.py | 28 +++++++++---------- .../account_statement_import_sheet_parser.py | 3 -- 2 files changed, 13 insertions(+), 18 deletions(-) diff --git a/account_statement_import_sheet_file_bg/models/account_statement_import.py b/account_statement_import_sheet_file_bg/models/account_statement_import.py index 3f924a94..17063f47 100644 --- a/account_statement_import_sheet_file_bg/models/account_statement_import.py +++ b/account_statement_import_sheet_file_bg/models/account_statement_import.py @@ -168,8 +168,7 @@ def split_base64_excel(self, header_rows_count, rows_per_file_limit): output_base64_list = [] mapping = self.sheet_mapping_id - journal = self.env["account.journal"].browse(self.env.context.get("journal_id")) - currency_code = (journal.currency_id or journal.company_id.currency_id).name + all_rows = [] try: file_bytes = base64.b64decode(self.statement_file) read_buffer = BytesIO(file_bytes) @@ -191,6 +190,9 @@ def split_base64_excel(self, header_rows_count, rows_per_file_limit): ) sheet = workbook.sheet_by_index(0) csv_or_xlsx = (workbook, sheet) + # same as the openpyxl branch: plain values, every row and + # every column, which is what the split below writes out + all_rows = [sheet.row_values(row) for row in range(sheet.nrows)] except Exception: # Try CSV @@ -211,24 +213,20 @@ def split_base64_excel(self, header_rows_count, rows_per_file_limit): csv_reader = reader(StringIO(decoded), **csv_options) csv_or_xlsx = csv_reader - all_rows = [row for row in list(csv_or_xlsx) if any(cell for cell in row)] + # same as the two spreadsheet branches: every row, empty ones + # included, so that the header row number the user configured + # counts the lines the file actually has. Dropping the empty + # rows here shifted that count and made the header land on a + # transaction. Empty rows are skipped later by the parser, + # which is what skip_empty_lines is for. + all_rows = list(csv_or_xlsx) parser = self.env["account.statement.import.sheet.parser"] - # Only parse header and rows for Excel files (when all_rows is not yet populated) - if not all_rows: - header = parser.parse_header(csv_or_xlsx, mapping) - columns = dict() - for column_name in parser._get_column_names(): - columns[column_name] = parser._get_column_indexes(header, column_name, mapping) - data = csv_or_xlsx, self.statement_file - all_rows = parser._parse_rows(mapping, currency_code, data, columns) - else: - # For CSV files, we already have all_rows, convert list to iterator for parse_header - header = parser.parse_header(iter(all_rows), mapping) - if not all_rows: return [] + header = parser.parse_header(iter(all_rows), mapping) + header_rows = all_rows[:header_rows_count] data_rows = all_rows[header_rows_count:] diff --git a/account_statement_import_sheet_file_bg/models/account_statement_import_sheet_parser.py b/account_statement_import_sheet_file_bg/models/account_statement_import_sheet_parser.py index cfb631a6..72d2607c 100644 --- a/account_statement_import_sheet_file_bg/models/account_statement_import_sheet_parser.py +++ b/account_statement_import_sheet_file_bg/models/account_statement_import_sheet_parser.py @@ -17,9 +17,6 @@ def parse_header(self, csv_or_xlsx, mapping): if header_line > 0: header_line -= 1 - if isinstance(csv_or_xlsx, tuple): - return super().parse_header(csv_or_xlsx, mapping) - [next(csv_or_xlsx) for _i in range(header_line)] header = [] for value in next(csv_or_xlsx):