Skip to content

Commit 2562dcc

Browse files
committed
[FIX] account_statement_import_sheet_file_bg: handle OpenPyXL Cell values in sheet header parsing
Prevent crash when importing XLSX files where header rows are iterated as OpenPyXL Cell objects instead of plain strings. add parser override in miscellaneous module to normalize header values support both raw strings and Cell instances before calling strip keep OCA module untouched as required preserve existing behavior for CSV/XLS flows Fixes: AttributeError: 'Cell' object has no attribute 'strip' Alternative (more concise) title: fix: normalize XLSX header cells before strip in bg statement import
1 parent f5a5ffc commit 2562dcc

3 files changed

Lines changed: 41 additions & 4 deletions

File tree

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
from . import account_statement_import
2+
from . import account_statement_import_sheet_parser

account_statement_import_sheet_file_bg/models/account_statement_import.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -171,15 +171,16 @@ def split_base64_excel(self, header_rows_count, rows_per_file_limit):
171171
mapping = self.sheet_mapping_id
172172
journal = self.env["account.journal"].browse(self.env.context.get("journal_id"))
173173
currency_code = (journal.currency_id or journal.company_id.currency_id).name
174-
175174
try:
176175
file_bytes = base64.b64decode(self.statement_file)
177176
read_buffer = BytesIO(file_bytes)
178177

179178
# Try openpyxl (xlsx)
180179
input_workbook = load_workbook(read_buffer)
181180
input_worksheet = input_workbook.active
182-
all_rows = list(input_worksheet.rows)
181+
# Normalize rows to plain values to keep parser/output logic
182+
# consistent with CSV/xls flows.
183+
all_rows = [[cell.value for cell in row] for row in input_worksheet.rows]
183184
csv_or_xlsx = (input_workbook, input_worksheet)
184185

185186
except Exception:
@@ -277,10 +278,14 @@ def _filter_rows_with_date(self, data_rows, date_column_index):
277278

278279
filtered_rows = []
279280
for row in data_rows:
281+
date_value = None
282+
if len(row) > date_column_index:
283+
date_cell_or_value = row[date_column_index]
284+
date_value = date_cell_or_value.value if hasattr(date_cell_or_value, "value") else date_cell_or_value
280285
# Check if the row has enough columns and the date column is not empty
281-
if len(row) > date_column_index and row[date_column_index].value:
286+
if len(row) > date_column_index and date_value:
282287
filtered_rows.append(row)
283-
elif len(row) > date_column_index and not row[date_column_index].value:
288+
elif len(row) > date_column_index and not date_value:
284289
# Stop processing when we find the first empty date
285290
break
286291

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Copyright 2026 ADHOC SA
2+
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
3+
4+
from odoo import api, models
5+
6+
7+
class AccountStatementImportSheetParser(models.TransientModel):
8+
_inherit = "account.statement.import.sheet.parser"
9+
10+
@api.model
11+
def parse_header(self, csv_or_xlsx, mapping):
12+
if mapping.no_header:
13+
return []
14+
15+
header_line = mapping.header_lines_skip_count
16+
# Prevent negative indexes.
17+
if header_line > 0:
18+
header_line -= 1
19+
20+
if isinstance(csv_or_xlsx, tuple):
21+
return super().parse_header(csv_or_xlsx, mapping)
22+
23+
[next(csv_or_xlsx) for _i in range(header_line)]
24+
header = []
25+
for value in next(csv_or_xlsx):
26+
raw_value = value.value if hasattr(value, "value") else value
27+
header.append(str(raw_value).strip() if raw_value is not None else "")
28+
29+
if mapping.offset_column:
30+
header = header[mapping.offset_column :]
31+
return header

0 commit comments

Comments
 (0)