From efe1f50ebb21a3896619dc990ab815dc9af113e0 Mon Sep 17 00:00:00 2001 From: Katherine Zaoral Date: Tue, 15 Nov 2022 11:11:17 -0300 Subject: [PATCH] [IMP] account_bank_statement_import_txt_xlsx: CSV [IMP] add csv meta data management [FIX] exclude footer meta data [IMP] import separated credit/debit column file [FIX] make comptatible with new version of multi_step_wizard module & add migration file [FIX] all not provided value are handled in_parse_decimal method [REF] Remove unnecessary \n --- .../data/map_data.xml | 3 + .../migrations/15.0.1.1.0/post-migration.py | 25 +++++ .../account_statement_import_sheet_mapping.py | 61 ++++++++++- .../account_statement_import_sheet_parser.py | 80 +++++++++++--- .../readme/CONTRIBUTORS.rst | 1 + ...lock.meta_data_separated_credit_debit.csv# | 1 + .../meta_data_separated_credit_debit.csv | 10 ++ .../meta_data_separated_credit_debit.xlsx | Bin 0 -> 5355 bytes .../test_account_statement_import_txt_xlsx.py | 98 +++++++++++++++++- ...account_statement_import_sheet_mapping.xml | 37 ++++++- ...t_statement_import_sheet_mapping_wizard.py | 47 ++++++++- ..._statement_import_sheet_mapping_wizard.xml | 28 ++++- 12 files changed, 361 insertions(+), 30 deletions(-) create mode 100644 account_statement_import_txt_xlsx/migrations/15.0.1.1.0/post-migration.py create mode 100644 account_statement_import_txt_xlsx/tests/fixtures/.~lock.meta_data_separated_credit_debit.csv# create mode 100644 account_statement_import_txt_xlsx/tests/fixtures/meta_data_separated_credit_debit.csv create mode 100644 account_statement_import_txt_xlsx/tests/fixtures/meta_data_separated_credit_debit.xlsx diff --git a/account_statement_import_txt_xlsx/data/map_data.xml b/account_statement_import_txt_xlsx/data/map_data.xml index f87466015..29f48cdd8 100644 --- a/account_statement_import_txt_xlsx/data/map_data.xml +++ b/account_statement_import_txt_xlsx/data/map_data.xml @@ -7,12 +7,15 @@ Sample Statement + 0 + 1 comma dot comma " %m/%d/%Y Date + simple_value Amount Currency Amount Currency diff --git a/account_statement_import_txt_xlsx/migrations/15.0.1.1.0/post-migration.py b/account_statement_import_txt_xlsx/migrations/15.0.1.1.0/post-migration.py new file mode 100644 index 000000000..498ef8435 --- /dev/null +++ b/account_statement_import_txt_xlsx/migrations/15.0.1.1.0/post-migration.py @@ -0,0 +1,25 @@ +# Copyright 2020 Akretion +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +from openupgradelib import openupgrade + + +@openupgrade.migrate() +def migrate(env, version): + openupgrade.logged_query( + env.cr, + """ +UPDATE account_statement_import_sheet_mapping + SET amount_type = 'absolute_value' + WHERE debit_credit_column IS NOT NULL; + """, + ) + + openupgrade.logged_query( + env.cr, + """ +UPDATE account_statement_import_sheet_mapping + SET amount_type = 'simple_value' + WHERE debit_credit_column IS NULL; + """, + ) 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 caefa7ac1..b53b04a35 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 @@ -70,14 +70,24 @@ class AccountStatementImportSheetMapping(models.Model): amount_column = fields.Char( help="Amount of transaction in journal's currency", ) - amount_debit_column = fields.Char( + + debit_column = fields.Char( string="Debit amount column", help="Debit amount of transaction in journal's currency", ) - amount_credit_column = fields.Char( + credit_column = fields.Char( string="Credit amount column", help="Credit amount of transaction in journal's currency", ) + + # TODO to avoid error in un customer, need to fix using migrate script + amount_debit_column = fields.Char( + related="debit_column", store=True, string="OCA Debit Column", readonly=False, + ) + amount_credit_column = fields.Char( + related="credit_column", store=True, string="OCA Credit Column", readonly=False, + ) + balance_column = fields.Char( help="Balance after transaction in journal's currency", ) @@ -95,10 +105,43 @@ class AccountStatementImportSheetMapping(models.Model): "transaction amount in original transaction currency from" ), ) + amount_type = fields.Selection( + selection=[ + ("simple_value", "Simple value"), + ("absolute_value", "Absolute value"), + ("distinct_credit_debit", "Distinct Credit/debit Column"), + ], + string="Amount type", + required=True, + default="simple_value", + help=( + "Simple value: use igned amount in ammount comlumn\n" + "Absolute Value: use a same comlumn for debit and credit\n" + "(absolute value + indicate sign)\n" + "Distinct Credit/debit Column: use a distinct comlumn for debit and credit" + ), + ) + amount_column = fields.Char( + string="Amount column", + help=( + 'Used if amount type is "Simple value" or "Absolute value"\n' + "Amount of transaction in journal's currency\n" + "Some statement formats use credit/debit columns" + ), + ) + debit_column = fields.Char( + string="Debit column", + help='Used if amount type is "Distinct Credit/debit Column"', + ) + credit_column = fields.Char( + string="Credit column", + help='Used if amount type is "Distinct Credit/debit Column"\n', + ) debit_credit_column = fields.Char( string="Debit/credit column", help=( - "Some statement formats use absolute amount value and indicate sign" + 'Used if amount type is "Absolute value"\n' + "Some statement formats use absolute amount value and indicate sign\n" "of the transaction by specifying if it was a debit or a credit one" ), ) @@ -123,6 +166,18 @@ class AccountStatementImportSheetMapping(models.Model): bank_account_column = fields.Char( help="Partner's bank account", ) + footer_lines_count = fields.Integer( + string="Footer lines number", + help="Set the Footer lines number." + "Used in some csv file that integrate meta data in" + "last lines.", + default="0", + ) + column_labels_row = fields.Integer( + string="Row number for column labels", + help="The number of line that contain column names.", + default="1", + ) _sql_constraints = [ ( 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 deee5fd02..0fbf3dc3e 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 @@ -36,21 +36,23 @@ class AccountStatementImportSheetParser(models.TransientModel): _description = "Bank Statement Import Sheet Parser" @api.model - def parse_header(self, data_file, encoding, csv_options): + def parse_header(self, data_file, encoding, csv_options, column_labels_row=1): try: workbook = xlrd.open_workbook( file_contents=data_file, encoding_override=encoding if encoding else None, ) sheet = workbook.sheet_by_index(0) - values = sheet.row_values(0) + values = sheet.row_values(column_labels_row - 1) return [str(value) for value in values] except xlrd.XLRDError: _logger.error("Pass this method") 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[column_labels_row - 1]] + return header @api.model def parse(self, data_file, mapping, filename): @@ -62,9 +64,12 @@ def parse(self, data_file, mapping, filename): if not lines: return currency_code, account_number, [{"transactions": []}] - lines = list(sorted(lines, key=lambda line: line["timestamp"])) - first_line = lines[0] - last_line = lines[-1] + if lines[0]["timestamp"] > lines[-1]["timestamp"]: + first_line = lines[-1] + last_line = lines[0] + else: + first_line = lines[0] + last_line = lines[-1] data = { "date": first_line["timestamp"].date(), "name": _("%(code)s: %(filename)s") @@ -118,6 +123,8 @@ def _get_column_indexes(self, header, column_name, mapping): return column_indexes def _get_column_names(self): + # NOTE no seria necesario debit_column y credit_column ya que tenemos + # los respectivos campos related return [ "timestamp_column", "currency_column", @@ -171,14 +178,25 @@ def _parse_lines(self, mapping, data_file, currency_code): header = False if not mapping.no_header: if isinstance(csv_or_xlsx, tuple): - header = [str(value) for value in csv_or_xlsx[1].row_values(0)] + header = [ + str(value).strip() + for value in csv_or_xlsx[1].row_values( + mapping.column_labels_row - 1 + ) + ] else: + for _i in range(mapping.column_labels_row - 1): + next(csv_or_xlsx) header = [value.strip() for value in next(csv_or_xlsx)] + + # NOTE no seria necesario [" y credit_column ya que tenemos los + # respectivos campos related for column_name in self._get_column_names(): columns[column_name] = self._get_column_indexes( header, column_name, mapping ) - return self._parse_rows(mapping, currency_code, csv_or_xlsx, columns) + data = csv_or_xlsx, data_file + return self._parse_rows(mapping, currency_code, data, columns) def _get_values_from_column(self, values, columns, column_name): indexes = columns[column_name] @@ -195,25 +213,38 @@ def _get_values_from_column(self, values, columns, column_name): return " ".join(content_l) return content_l[0] - def _parse_rows(self, mapping, currency_code, csv_or_xlsx, columns): # noqa: C901 + def _parse_rows(self, mapping, currency_code, data, columns): # noqa: C901 + csv_or_xlsx, data_file = data + + # 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")) + + label_line = mapping.column_labels_row + footer_line = numrows - mapping.footer_lines_count + if isinstance(csv_or_xlsx, tuple): - rows = range(1, csv_or_xlsx[1].nrows) + rows = range(mapping.column_labels_row, footer_line) else: rows = csv_or_xlsx lines = [] - for row in rows: + for index, row in enumerate(rows, label_line): if isinstance(csv_or_xlsx, tuple): book = csv_or_xlsx[0] sheet = csv_or_xlsx[1] values = [] - for col_index in range(sheet.row_len(row)): + for col_index in range(0, sheet.row_len(row)): cell_type = sheet.cell_type(row, col_index) cell_value = sheet.cell_value(row, col_index) if cell_type == xlrd.XL_CELL_DATE: cell_value = xldate_as_datetime(cell_value, book.datemode) values.append(cell_value) else: + if index >= footer_line: + continue values = list(row) timestamp = self._get_values_from_column( @@ -238,6 +269,11 @@ def _decimal(column_name): if not amount: amount = -abs(_decimal("amount_credit_column") or 0) + # amount_column = columns["amount_column"] + # if amount_column and values[columns["amount_column"]]: + # amount = self._parse_decimal( + # values[columns["amount_column"], mapping) + balance = ( self._get_values_from_column(values, columns, "balance_column") if columns["balance_column"] @@ -296,6 +332,17 @@ def _decimal(column_name): else None ) + debit_column = ( + self._get_values_from_column(values, columns, "amount_debit_column") + if columns["amount_debit_column"] + else None + ) + credit_column = ( + self._get_values_from_column(values, columns, "amount_credit_column") + if columns["amount_credit_column"] + else None + ) + if currency != currency_code: continue @@ -307,11 +354,17 @@ def _decimal(column_name): else: balance = None - if debit_credit: + if debit_credit is not None: amount = amount.copy_abs() if debit_credit == mapping.debit_value: amount = -amount + if debit_column and credit_column: + debit_amount = self._parse_decimal(values[debit_column], mapping) + debit_amount = debit_amount.copy_abs() + credit_amount = self._parse_decimal(values[credit_column], mapping) + amount = credit_amount - debit_amount + if original_amount: original_amount = self._parse_decimal( original_amount, mapping @@ -429,6 +482,7 @@ def _parse_decimal(self, value, mapping): return value elif isinstance(value, float): return Decimal(value) + value = value or "0" thousands, decimal = mapping._get_float_separators() value = value.replace(thousands, "") value = value.replace(decimal, ".") diff --git a/account_statement_import_txt_xlsx/readme/CONTRIBUTORS.rst b/account_statement_import_txt_xlsx/readme/CONTRIBUTORS.rst index b5e09af68..6b9085d24 100644 --- a/account_statement_import_txt_xlsx/readme/CONTRIBUTORS.rst +++ b/account_statement_import_txt_xlsx/readme/CONTRIBUTORS.rst @@ -1,5 +1,6 @@ * Alexis de Lattre * Sebastien BEAU +* Mourad EL HADJ MIMOUNE * Tecnativa (https://www.tecnativa.com) * Vicent Cubells diff --git a/account_statement_import_txt_xlsx/tests/fixtures/.~lock.meta_data_separated_credit_debit.csv# b/account_statement_import_txt_xlsx/tests/fixtures/.~lock.meta_data_separated_credit_debit.csv# new file mode 100644 index 000000000..48a88d51c --- /dev/null +++ b/account_statement_import_txt_xlsx/tests/fixtures/.~lock.meta_data_separated_credit_debit.csv# @@ -0,0 +1 @@ +,pablo,pam-nb2,19.04.2023 15:40,file:///home/pablo/.config/libreoffice/4; \ No newline at end of file diff --git a/account_statement_import_txt_xlsx/tests/fixtures/meta_data_separated_credit_debit.csv b/account_statement_import_txt_xlsx/tests/fixtures/meta_data_separated_credit_debit.csv new file mode 100644 index 000000000..0b3862128 --- /dev/null +++ b/account_statement_import_txt_xlsx/tests/fixtures/meta_data_separated_credit_debit.csv @@ -0,0 +1,10 @@ +Bank code : 1001010101,Agency Code : 10000,Download start date : 01/04/2020,Download end date : 02/04/2020,, +Account Number : 08088804068,Account Name : Account Owner,: EUR,,, +,,,,, +Balance at end of period,,,,"+31070,11", +Date,Operation Number,Label,Debit,Credit,Detail +01/04/20,UNIQUE OP 1,LABEL 1,"-50,00",,DETAILS 1 +01/04/20,UNIQUE OP 2,LABEL 2,"-100,00",,CLIENTS X +02/04/20,UNIQUE OP 3,LABEL 3,"-80,68",,DETAILS 2 +02/04/20,UNIQUE OP 4,LABEL 4,,"1300,00",DETAILS 3 +Balance at start of period,,,,"+30000,77", diff --git a/account_statement_import_txt_xlsx/tests/fixtures/meta_data_separated_credit_debit.xlsx b/account_statement_import_txt_xlsx/tests/fixtures/meta_data_separated_credit_debit.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..c7ae2b92e9c730a916127cf5e6b1c1f5a6decfe1 GIT binary patch literal 5355 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 literal 0 HcmV?d00001 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 71595b872..e597c97e3 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 @@ -8,6 +8,7 @@ from odoo import fields from odoo.exceptions import UserError from odoo.tests import common +from odoo.tools import float_round class TestAccountBankStatementImportTxtXlsx(common.TransactionCase): @@ -224,7 +225,8 @@ def test_original_currency(self): self.assertEqual(line.currency_id, self.currency_usd) self.assertEqual(line.amount, 1525.0) self.assertEqual(line.foreign_currency_id, self.currency_eur) - self.assertEqual(line.amount_currency, 1000.0) + line_amount_currency = float_round(line.amount_currency, precision_digits=1) + self.assertEqual(line_amount_currency, 1000.0) def test_original_currency_no_header(self): no_header_statement_map = self.AccountStatementImportSheetMapping.create( @@ -450,3 +452,97 @@ def test_debit_credit_amount(self): self.assertEqual(statement.balance_start, 10.0) self.assertEqual(statement.balance_end_real, 1510.0) self.assertEqual(statement.balance_end, 1510.0) + + def test_metadata_separated_debit_credit_csv(self): + journal = self.AccountJournal.create( + { + "name": "Bank", + "type": "bank", + "code": "BANK", + "currency_id": self.currency_usd.id, + "suspense_account_id": self.suspense_account.id, + } + ) + statement_map = self.sample_statement_map.copy( + { + "footer_lines_count": 1, + "column_labels_row": 5, + "amount_column": None, + "partner_name_column": None, + "bank_account_column": None, + "float_thousands_sep": "none", + "float_decimal_sep": "comma", + "timestamp_format": "%m/%d/%y", + "original_currency_column": None, + "original_amount_column": None, + "amount_type": "distinct_credit_debit", + "debit_column": "Debit", + "credit_column": "Credit", + } + ) + data = self._data_file("fixtures/meta_data_separated_credit_debit.csv", "utf-8") + wizard = self.AccountStatementImport.with_context(journal_id=journal.id).create( + { + "statement_filename": "fixtures/meta_data_separated_credit_debit.csv", + "statement_file": data, + "sheet_mapping_id": statement_map.id, + } + ) + wizard.with_context( + journal_id=journal.id, + account_bank_statement_import_txt_xlsx_test=True, + ).import_file_button() + statement = self.AccountBankStatement.search([("journal_id", "=", journal.id)]) + self.assertEqual(len(statement), 1) + self.assertEqual(len(statement.line_ids), 4) + line1 = statement.line_ids.filtered(lambda x: x.payment_ref == "LABEL 1") + line4 = statement.line_ids.filtered(lambda x: x.payment_ref == "LABEL 4") + self.assertEqual(line1.amount, 50) + self.assertEqual(line4.amount, -1300) + + def test_metadata_separated_debit_credit_xlsx(self): + journal = self.AccountJournal.create( + { + "name": "Bank", + "type": "bank", + "code": "BANK", + "currency_id": self.currency_usd.id, + "suspense_account_id": self.suspense_account.id, + } + ) + statement_map = self.sample_statement_map.copy( + { + "footer_lines_count": 1, + "column_labels_row": 5, + "amount_column": None, + "partner_name_column": None, + "bank_account_column": None, + "float_thousands_sep": "none", + "float_decimal_sep": "comma", + "timestamp_format": "%m/%d/%y", + "original_currency_column": None, + "original_amount_column": None, + "amount_type": "distinct_credit_debit", + "debit_column": "Debit", + "credit_column": "Credit", + } + ) + data = self._data_file("fixtures/meta_data_separated_credit_debit.xlsx") + wizard = self.AccountStatementImport.with_context(journal_id=journal.id).create( + { + "statement_filename": "fixtures/meta_data_separated_credit_debit.xlsx", + "statement_file": data, + "sheet_mapping_id": statement_map.id, + } + ) + wizard.with_context( + journal_id=journal.id, + account_bank_statement_import_txt_xlsx_test=True, + ).import_file_button() + statement = self.AccountBankStatement.search([("journal_id", "=", journal.id)]) + self.assertEqual(len(statement), 1) + self.assertEqual(len(statement.line_ids), 4) + line1 = statement.line_ids.filtered(lambda x: x.payment_ref == "LABEL 1") + line4 = statement.line_ids.filtered(lambda x: x.payment_ref == "LABEL 4") + self.assertEqual(line1.amount, 50) + self.assertEqual(line4.amount, -1300) 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 62d617dd0..73cd838ab 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 @@ -63,6 +63,10 @@ attrs="{'required': [('debit_credit_column', '!=', False)]}" /> + + + + @@ -77,13 +81,38 @@ - - - + + + + - + diff --git a/account_statement_import_txt_xlsx/wizards/account_statement_import_sheet_mapping_wizard.py b/account_statement_import_txt_xlsx/wizards/account_statement_import_sheet_mapping_wizard.py index d1314edb7..0472fe3ed 100644 --- a/account_statement_import_txt_xlsx/wizards/account_statement_import_sheet_mapping_wizard.py +++ b/account_statement_import_txt_xlsx/wizards/account_statement_import_sheet_mapping_wizard.py @@ -20,6 +20,13 @@ class AccountStatementImportSheetMappingWizard(models.TransientModel): required=True, relation="account_statement_import_sheet_mapping_wiz_attachment_rel", ) + column_labels_row = fields.Integer( + string="Header line", + help="The number of line that contan column names.\n" + "Used if csv/xls files contain\n" + "meta data in first lines\n ", + default="1", + ) header = fields.Char() file_encoding = fields.Selection( string="Encoding", @@ -39,16 +46,40 @@ class AccountStatementImportSheetMappingWizard(models.TransientModel): "transaction from" ), ) + amount_type = fields.Selection( + selection=[ + ("simple_value", "Simple value"), + ("absolute_value", "Absolute value"), + ("distinct_credit_debit", "Distinct Credit/debit Column"), + ], + string="Amount type", + required=True, + default="simple_value", + help=( + "Simple value: use igned amount in ammount comlumn\n" + "Absolute Value: use a same comlumn for debit and credit\n" + "(absolute value + indicate sign)\n" + "Distinct Credit/debit Column: use a distinct comlumn for debit and credit" + ), + ) amount_column = fields.Char( help="Amount of transaction in journal's currency", ) amount_debit_column = fields.Char( - string="Debit amount column", - help="Debit amount of transaction in journal's currency", + related="debit_column", store=True, string="OCA Debit colum" ) amount_credit_column = fields.Boolean( - string="Credit amount column", - help="Credit amount of transaction in journal's currency", + related="credit_column", store=True, string="OCA Credit colum" + ) + debit_column = fields.Char( + string="Debit amount column", + help="Debit amount of transaction in journal's currency" + "Used if amount type is 'Distinct Credit/debit Column'", + ) + credit_column = fields.Boolean( + string="Credit column", + help="Credit amount of transaction in journal's currency" + "Used if amount type is 'Distinct Credit/debit Column'", ) balance_column = fields.Char( help="Balance after transaction in journal's currency", @@ -128,7 +159,10 @@ def _onchange_attachment_ids(self): header = [] for data_file in self.attachment_ids: header += Parser.parse_header( - b64decode(data_file.datas), self.file_encoding, csv_options + b64decode(data_file.datas), + self.file_encoding, + csv_options, + self.column_labels_row, ) header = list(set(header)) self.header = json.dumps(header) @@ -154,6 +188,9 @@ def _get_mapping_values(self): "timestamp_format": "%d/%m/%Y", "timestamp_column": self.timestamp_column, "currency_column": self.currency_column, + "amount_type": self.amount_type, + "debit_column": self.debit_column, + "credit_column": self.credit_column, "amount_column": self.amount_column, "balance_column": self.balance_column, "original_currency_column": self.original_currency_column, diff --git a/account_statement_import_txt_xlsx/wizards/account_statement_import_sheet_mapping_wizard.xml b/account_statement_import_txt_xlsx/wizards/account_statement_import_sheet_mapping_wizard.xml index dc5021a73..257fd6136 100644 --- a/account_statement_import_txt_xlsx/wizards/account_statement_import_sheet_mapping_wizard.xml +++ b/account_statement_import_txt_xlsx/wizards/account_statement_import_sheet_mapping_wizard.xml @@ -24,6 +24,7 @@ /> + @@ -45,44 +46,63 @@ values="statement_columns" context="{'header': header}" /> +