From 0668d35a574a613200cb679eb2301fb7137b12f7 Mon Sep 17 00:00:00 2001 From: Tiago Amaral Date: Wed, 26 Jun 2024 11:56:20 -0300 Subject: [PATCH] [ADD] field offset_footer in mapping --- .../account_statement_import_sheet_mapping.py | 8 +++++- .../account_statement_import_sheet_parser.py | 27 ++++++++++++++---- .../fixtures/sample_statement_offsets.xlsx | Bin 5993 -> 5650 bytes .../test_account_statement_import_txt_xlsx.py | 1 + ...account_statement_import_sheet_mapping.xml | 1 + 5 files changed, 31 insertions(+), 6 deletions(-) diff --git a/account_statement_import_txt_xlsx/models/account_statement_import_sheet_mapping.py b/account_statement_import_txt_xlsx/models/account_statement_import_sheet_mapping.py index a0a9e26fb..eba554d65 100644 --- a/account_statement_import_txt_xlsx/models/account_statement_import_sheet_mapping.py +++ b/account_statement_import_txt_xlsx/models/account_statement_import_sheet_mapping.py @@ -77,6 +77,13 @@ class AccountStatementImportSheetMapping(models.Model): default=0, help="Vertical spaces to ignore before starting to parse", ) + offset_footer = fields.Integer( + string="Footer lines skip count", + help="Set the Footer lines number." + "Used in some csv/xlsx file that integrate meta data in" + "last lines.", + default="0", + ) timestamp_column = fields.Char(string="Timestamp column", required=True) currency_column = fields.Char( string="Currency column", @@ -157,7 +164,6 @@ class AccountStatementImportSheetMapping(models.Model): string="Bank Account column", help="Partner's bank account", ) - _sql_constraints = [ ( "check_amount_columns", diff --git a/account_statement_import_txt_xlsx/models/account_statement_import_sheet_parser.py b/account_statement_import_txt_xlsx/models/account_statement_import_sheet_parser.py index 1d56b6ab8..d4928163e 100644 --- a/account_statement_import_txt_xlsx/models/account_statement_import_sheet_parser.py +++ b/account_statement_import_txt_xlsx/models/account_statement_import_sheet_parser.py @@ -4,6 +4,7 @@ import itertools import logging +from collections.abc import Iterable from datetime import datetime from decimal import Decimal from io import StringIO @@ -50,7 +51,10 @@ def parse_header(self, data_file, encoding, csv_options): data = StringIO(data_file.decode(encoding or "utf-8")) csv_data = reader(data, **csv_options) - return list(next(csv_data)) + csv_data_lst = list(csv_data) + header = [value.strip() for value in csv_data_lst[0]] + return header + # return list(next(csv_data)) @api.model def parse(self, data_file, mapping, filename): @@ -95,7 +99,11 @@ def parse(self, data_file, mapping, filename): def _get_column_indexes(self, header, column_name, mapping): column_indexes = [] - if mapping[column_name] and "," in mapping[column_name]: + if ( + mapping[column_name] + and isinstance(mapping[column_name], Iterable) + and "," in mapping[column_name] + ): # We have to concatenate the values column_names_or_indexes = mapping[column_name].split(",") else: @@ -182,7 +190,9 @@ def _parse_lines(self, mapping, data_file, currency_code): columns[column_name] = self._get_column_indexes( header, column_name, mapping ) - return self._parse_rows(mapping, currency_code, csv_or_xlsx, columns) + return self._parse_rows( + mapping, currency_code, csv_or_xlsx, columns, data_file=data_file + ) def _get_values_from_column(self, values, columns, column_name): indexes = columns[column_name] @@ -324,9 +334,16 @@ def _decimal(column_name): line["bank_account"] = bank_account return line - def _parse_rows(self, mapping, currency_code, csv_or_xlsx, columns): # noqa: C901 + def _parse_rows(self, mapping, currency_code, csv_or_xlsx, columns, data_file=None): + # Get the numbers of rows of the file + if isinstance(csv_or_xlsx, tuple): + numrows = csv_or_xlsx[1].nrows + else: + numrows = len(str(data_file.strip()).split("\\n")) + + footer_line = numrows - mapping.offset_footer if isinstance(csv_or_xlsx, tuple): - rows = range(mapping.offset_row + 1, csv_or_xlsx[1].nrows) + rows = range(mapping.offset_row + 1, footer_line) else: rows = csv_or_xlsx diff --git a/account_statement_import_txt_xlsx/tests/fixtures/sample_statement_offsets.xlsx b/account_statement_import_txt_xlsx/tests/fixtures/sample_statement_offsets.xlsx index 2cfc77a8a353e0e76434c0df93455f8ba8c0b17a..2e804dd4bef7100c4567a48a778e831741bd3efe 100644 GIT binary patch literal 5650 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 literal 5993 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 diff --git a/account_statement_import_txt_xlsx/tests/test_account_statement_import_txt_xlsx.py b/account_statement_import_txt_xlsx/tests/test_account_statement_import_txt_xlsx.py index 685807378..59e28fa40 100644 --- a/account_statement_import_txt_xlsx/tests/test_account_statement_import_txt_xlsx.py +++ b/account_statement_import_txt_xlsx/tests/test_account_statement_import_txt_xlsx.py @@ -476,6 +476,7 @@ def test_offsets(self): { "offset_column": 1, "offset_row": 2, + "offset_footer": 3, } ) wizard = self.AccountStatementImport.with_context(journal_id=journal.id).create( diff --git a/account_statement_import_txt_xlsx/views/account_statement_import_sheet_mapping.xml b/account_statement_import_txt_xlsx/views/account_statement_import_sheet_mapping.xml index 0ab58c800..4dd85f87e 100644 --- a/account_statement_import_txt_xlsx/views/account_statement_import_sheet_mapping.xml +++ b/account_statement_import_txt_xlsx/views/account_statement_import_sheet_mapping.xml @@ -59,6 +59,7 @@ name="offset_row" attrs="{'invisible': [('no_header', '=', True)]}" /> +