From 4f9baf7108c05afb986bd11f7f401df532e579f8 Mon Sep 17 00:00:00 2001 From: Jordi Ballester Alomar Date: Tue, 1 Oct 2019 05:43:11 +0200 Subject: [PATCH 01/29] [12.0][add] account_bank_statement_import_txt_xslx --- .../README.rst | 103 ++++ .../__init__.py | 2 + .../__manifest__.py | 28 ++ .../data/txt_map_data.xml | 53 +++ ...account_bank_statement_import_txt_xlsx.pot | 420 ++++++++++++++++ .../models/__init__.py | 2 + .../account_bank_statement_import_txt_map.py | 136 ++++++ .../models/account_journal.py | 19 + .../readme/CONFIGURE.rst | 12 + .../readme/CONTRIBUTORS.rst | 7 + .../readme/DESCRIPTION.rst | 2 + .../readme/USAGE.rst | 3 + .../security/ir.model.access.csv | 3 + .../static/description/icon.png | Bin 0 -> 9455 bytes .../static/description/index.html | 449 ++++++++++++++++++ .../tests/__init__.py | 1 + .../tests/sample_statement_en.csv | 3 + .../tests/sample_statement_en.xlsx | Bin 0 -> 5092 bytes .../tests/test_txt_statement_import.py | 99 ++++ .../views/account_journal_views.xml | 16 + .../views/txt_map_views.xml | 70 +++ .../wizards/__init__.py | 2 + .../account_bank_statement_import_txt.py | 288 +++++++++++ .../account_bank_statement_import_view.xml | 14 + .../wizards/create_map_lines_from_file.py | 40 ++ .../create_map_lines_from_file_views.xml | 29 ++ 26 files changed, 1801 insertions(+) create mode 100644 account_bank_statement_import_txt_xlsx/README.rst create mode 100644 account_bank_statement_import_txt_xlsx/__init__.py create mode 100644 account_bank_statement_import_txt_xlsx/__manifest__.py create mode 100644 account_bank_statement_import_txt_xlsx/data/txt_map_data.xml create mode 100644 account_bank_statement_import_txt_xlsx/i18n/account_bank_statement_import_txt_xlsx.pot create mode 100644 account_bank_statement_import_txt_xlsx/models/__init__.py create mode 100644 account_bank_statement_import_txt_xlsx/models/account_bank_statement_import_txt_map.py create mode 100644 account_bank_statement_import_txt_xlsx/models/account_journal.py create mode 100644 account_bank_statement_import_txt_xlsx/readme/CONFIGURE.rst create mode 100644 account_bank_statement_import_txt_xlsx/readme/CONTRIBUTORS.rst create mode 100644 account_bank_statement_import_txt_xlsx/readme/DESCRIPTION.rst create mode 100644 account_bank_statement_import_txt_xlsx/readme/USAGE.rst create mode 100644 account_bank_statement_import_txt_xlsx/security/ir.model.access.csv create mode 100644 account_bank_statement_import_txt_xlsx/static/description/icon.png create mode 100644 account_bank_statement_import_txt_xlsx/static/description/index.html create mode 100644 account_bank_statement_import_txt_xlsx/tests/__init__.py create mode 100644 account_bank_statement_import_txt_xlsx/tests/sample_statement_en.csv create mode 100644 account_bank_statement_import_txt_xlsx/tests/sample_statement_en.xlsx create mode 100644 account_bank_statement_import_txt_xlsx/tests/test_txt_statement_import.py create mode 100644 account_bank_statement_import_txt_xlsx/views/account_journal_views.xml create mode 100644 account_bank_statement_import_txt_xlsx/views/txt_map_views.xml create mode 100644 account_bank_statement_import_txt_xlsx/wizards/__init__.py create mode 100644 account_bank_statement_import_txt_xlsx/wizards/account_bank_statement_import_txt.py create mode 100644 account_bank_statement_import_txt_xlsx/wizards/account_bank_statement_import_view.xml create mode 100644 account_bank_statement_import_txt_xlsx/wizards/create_map_lines_from_file.py create mode 100644 account_bank_statement_import_txt_xlsx/wizards/create_map_lines_from_file_views.xml diff --git a/account_bank_statement_import_txt_xlsx/README.rst b/account_bank_statement_import_txt_xlsx/README.rst new file mode 100644 index 00000000..2500d574 --- /dev/null +++ b/account_bank_statement_import_txt_xlsx/README.rst @@ -0,0 +1,103 @@ +====================================== +Account Bank Statement Import TXT XLSX +====================================== + +.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fbank--statement--import-lightgray.png?logo=github + :target: https://github.com/OCA/bank-statement-import/tree/12.0/account_bank_statement_import_txt_xlsx + :alt: OCA/bank-statement-import +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/bank-statement-import-12-0/bank-statement-import-12-0-account_bank_statement_import_txt_xlsx + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png + :target: https://runbot.odoo-community.org/runbot/174/12.0 + :alt: Try me on Runbot + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module allows you to import the any TXT/CSV or XLSX file in Odoo as bank +statements. + +**Table of contents** + +.. contents:: + :local: + +Configuration +============= + +* Create or go to a bank journal where you want to import the txt statement. +* Edit that journal and set a Txt map in **Statement Import Map** section in **Advanced + Settings** tab. + +* Now you can import Text based statements in that journal. + +Note: if existent Txt Map does not fit to your file to import, you can +create another map in **Invoicing > Configuration > Accounting > +Statement Import Map**. + +You can import headers from any Txt file in **Action > Create Map +Lines** and set every line with which field of statement have to match. + +Usage +===== + +To use this module, you need to: + +#. Go to your bank online and download your Bank Statement in TXT/CSV or XLSX format. + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us smashing it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +~~~~~~~ + +* Eficent + +Contributors +~~~~~~~~~~~~ + +* Alexis de Lattre +* Sebastien BEAU +* Tecnativa (https://www.tecnativa.com) + * Vicent Cubells + * Victor M.M. Torres +* Eficent (https://www.eficent.com) + * Jordi Ballester Alomar + +Maintainers +~~~~~~~~~~~ + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +This module is part of the `OCA/bank-statement-import `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/account_bank_statement_import_txt_xlsx/__init__.py b/account_bank_statement_import_txt_xlsx/__init__.py new file mode 100644 index 00000000..aee8895e --- /dev/null +++ b/account_bank_statement_import_txt_xlsx/__init__.py @@ -0,0 +1,2 @@ +from . import models +from . import wizards diff --git a/account_bank_statement_import_txt_xlsx/__manifest__.py b/account_bank_statement_import_txt_xlsx/__manifest__.py new file mode 100644 index 00000000..08b8fba3 --- /dev/null +++ b/account_bank_statement_import_txt_xlsx/__manifest__.py @@ -0,0 +1,28 @@ +# Copyright 2014-2017 Akretion (http://www.akretion.com). +# @author Alexis de Lattre +# @author Sébastien BEAU +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +{ + "name": "Account Bank Statement Import TXT XLSX", + 'summary': 'Import TXT/CSV or XLSX files as Bank Statements in Odoo', + "version": "12.0.1.0.1", + "category": "Accounting", + "website": "https://github.com/OCA/bank-statement-import", + "author": " Eficent, Odoo Community Association (OCA)", + "license": "AGPL-3", + "installable": True, + "depends": [ + "account_bank_statement_import", + ], + "external_dependencies": { + "python": ["xlrd"], + }, + "data": [ + "security/ir.model.access.csv", + "data/txt_map_data.xml", + "wizards/create_map_lines_from_file_views.xml", + "wizards/account_bank_statement_import_view.xml", + "views/account_journal_views.xml", + "views/txt_map_views.xml", + ] +} diff --git a/account_bank_statement_import_txt_xlsx/data/txt_map_data.xml b/account_bank_statement_import_txt_xlsx/data/txt_map_data.xml new file mode 100644 index 00000000..fb4c899a --- /dev/null +++ b/account_bank_statement_import_txt_xlsx/data/txt_map_data.xml @@ -0,0 +1,53 @@ + + + + + Sample Statement + comma + dot + + + + Date + 0 + + date + %m/%d/%Y + + + Label + 1 + + name + + + Currency + 2 + + currency + + + Amount + 3 + + amount + + + Amount Currency + 4 + + amount_currency + + + Partner Name + 5 + + partner_name + + + Bank Account + 6 + + account_number + + diff --git a/account_bank_statement_import_txt_xlsx/i18n/account_bank_statement_import_txt_xlsx.pot b/account_bank_statement_import_txt_xlsx/i18n/account_bank_statement_import_txt_xlsx.pot new file mode 100644 index 00000000..4d5a9148 --- /dev/null +++ b/account_bank_statement_import_txt_xlsx/i18n/account_bank_statement_import_txt_xlsx.pot @@ -0,0 +1,420 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * account_bank_statement_import_txt_xlsx +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 12.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: <>\n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: account_bank_statement_import_txt_xlsx +#: code:addons/account_bank_statement_import_txt_xlsx/wizards/account_bank_statement_import_txt.py:236 +#, python-format +msgid "%s Import %s > %s" +msgstr "" + +#. module: account_bank_statement_import_txt_xlsx +#: model:ir.model,name:account_bank_statement_import_txt_xlsx.model_account_bank_statement_import_map +msgid "Account Bank Statement Import Txt Map" +msgstr "" + +#. module: account_bank_statement_import_txt_xlsx +#: model:ir.model,name:account_bank_statement_import_txt_xlsx.model_account_bank_statement_import_map_line +msgid "Account Bank Statement Import Txt Map Line" +msgstr "" + +#. module: account_bank_statement_import_txt_xlsx +#: model_terms:ir.ui.view,arch_db:account_bank_statement_import_txt_xlsx.create_txt_map_lines_view +msgid "All the txt map lines will be created automatically." +msgstr "" + +#. module: account_bank_statement_import_txt_xlsx +#: selection:account.bank.statement.import.map.line,field_to_assign:0 +msgid "Amount in foreign currency" +msgstr "" + +#. module: account_bank_statement_import_txt_xlsx +#: selection:account.bank.statement.import.map.line,field_to_assign:0 +msgid "Amount in the journal currency" +msgstr "" + +#. module: account_bank_statement_import_txt_xlsx +#: code:addons/account_bank_statement_import_txt_xlsx/wizards/account_bank_statement_import_txt.py:160 +#, python-format +msgid "An error was found translating a date field from the file: %s" +msgstr "" + +#. module: account_bank_statement_import_txt_xlsx +#: selection:account.bank.statement.import.map.line,field_to_assign:0 +msgid "Bank Account Number" +msgstr "" + +#. module: account_bank_statement_import_txt_xlsx +#: model:ir.model.fields,field_description:account_bank_statement_import_txt_xlsx.field_wizard_txt_map_create__data_file +msgid "Bank Statement File" +msgstr "" + +#. module: account_bank_statement_import_txt_xlsx +#: model_terms:ir.ui.view,arch_db:account_bank_statement_import_txt_xlsx.create_txt_map_lines_view +msgid "Cancel" +msgstr "" + +#. module: account_bank_statement_import_txt_xlsx +#: model_terms:ir.ui.view,arch_db:account_bank_statement_import_txt_xlsx.create_txt_map_lines_view +msgid "Choose a file to import..." +msgstr "" + +#. module: account_bank_statement_import_txt_xlsx +#: model_terms:ir.ui.view,arch_db:account_bank_statement_import_txt_xlsx.create_txt_map_lines_view +msgid "Create Lines" +msgstr "" + +#. module: account_bank_statement_import_txt_xlsx +#: model:ir.actions.act_window,name:account_bank_statement_import_txt_xlsx.action_create_txt_map_lines +msgid "Create Statement Map Lines" +msgstr "" + +#. module: account_bank_statement_import_txt_xlsx +#: model:ir.model.fields,field_description:account_bank_statement_import_txt_xlsx.field_account_bank_statement_import_map__create_uid +#: model:ir.model.fields,field_description:account_bank_statement_import_txt_xlsx.field_account_bank_statement_import_map_line__create_uid +#: model:ir.model.fields,field_description:account_bank_statement_import_txt_xlsx.field_wizard_txt_map_create__create_uid +msgid "Created by" +msgstr "" + +#. module: account_bank_statement_import_txt_xlsx +#: model:ir.model.fields,field_description:account_bank_statement_import_txt_xlsx.field_account_bank_statement_import_map__create_date +#: model:ir.model.fields,field_description:account_bank_statement_import_txt_xlsx.field_account_bank_statement_import_map_line__create_date +#: model:ir.model.fields,field_description:account_bank_statement_import_txt_xlsx.field_wizard_txt_map_create__create_date +msgid "Created on" +msgstr "" + +#. module: account_bank_statement_import_txt_xlsx +#: selection:account.bank.statement.import.map.line,field_to_assign:0 +msgid "Currency" +msgstr "" + +#. module: account_bank_statement_import_txt_xlsx +#: selection:account.bank.statement.import.map.line,field_to_assign:0 +msgid "Date" +msgstr "" + +#. module: account_bank_statement_import_txt_xlsx +#: model:ir.model.fields,field_description:account_bank_statement_import_txt_xlsx.field_account_bank_statement_import_map_line__date_format +msgid "Date Format" +msgstr "" + +#. module: account_bank_statement_import_txt_xlsx +#: code:addons/account_bank_statement_import_txt_xlsx/wizards/account_bank_statement_import_txt.py:111 +#, python-format +msgid "Date format of map file and Txt date does not match." +msgstr "" + +#. module: account_bank_statement_import_txt_xlsx +#: model:ir.model.fields,field_description:account_bank_statement_import_txt_xlsx.field_account_bank_statement_import_map__float_decimal_sep +msgid "Decimals separator" +msgstr "" + +#. module: account_bank_statement_import_txt_xlsx +#: model:ir.model.fields,field_description:account_bank_statement_import_txt_xlsx.field_account_bank_statement_import_map__display_name +#: model:ir.model.fields,field_description:account_bank_statement_import_txt_xlsx.field_account_bank_statement_import_map_line__display_name +#: model:ir.model.fields,field_description:account_bank_statement_import_txt_xlsx.field_wizard_txt_map_create__display_name +msgid "Display Name" +msgstr "" + +#. module: account_bank_statement_import_txt_xlsx +#: model_terms:ir.ui.view,arch_db:account_bank_statement_import_txt_xlsx.create_txt_map_lines_view +msgid "Download a bank statement from your bank and import it here." +msgstr "" + +#. module: account_bank_statement_import_txt_xlsx +#: model:ir.model.fields,field_description:account_bank_statement_import_txt_xlsx.field_account_bank_statement_import_map__file_encoding +msgid "Encoding" +msgstr "" + +#. module: account_bank_statement_import_txt_xlsx +#: model:ir.model.fields,field_description:account_bank_statement_import_txt_xlsx.field_account_bank_statement_import_map_line__sequence +msgid "Field number" +msgstr "" + +#. module: account_bank_statement_import_txt_xlsx +#: model:ir.model.fields,field_description:account_bank_statement_import_txt_xlsx.field_wizard_txt_map_create__filename +msgid "Filename" +msgstr "" + +#. module: account_bank_statement_import_txt_xlsx +#: model:ir.model.fields,field_description:account_bank_statement_import_txt_xlsx.field_account_bank_statement_import_map_line__name +msgid "Header Name" +msgstr "" + +#. module: account_bank_statement_import_txt_xlsx +#: code:addons/account_bank_statement_import_txt_xlsx/wizards/account_bank_statement_import_txt.py:74 +#: code:addons/account_bank_statement_import_txt_xlsx/wizards/account_bank_statement_import_txt.py:91 +#, python-format +msgid "Headers of file to import and Txt map lines does not match." +msgstr "" + +#. module: account_bank_statement_import_txt_xlsx +#: model:ir.model.fields,field_description:account_bank_statement_import_txt_xlsx.field_account_bank_statement_import_map__id +#: model:ir.model.fields,field_description:account_bank_statement_import_txt_xlsx.field_account_bank_statement_import_map_line__id +#: model:ir.model.fields,field_description:account_bank_statement_import_txt_xlsx.field_wizard_txt_map_create__id +msgid "ID" +msgstr "" + +#. module: account_bank_statement_import_txt_xlsx +#: model:ir.model,name:account_bank_statement_import_txt_xlsx.model_account_bank_statement_import +msgid "Import Bank Statement" +msgstr "" + +#. module: account_bank_statement_import_txt_xlsx +#: model_terms:ir.ui.view,arch_db:account_bank_statement_import_txt_xlsx.create_txt_map_lines_view +msgid "Import Txt Map Lines" +msgstr "" + +#. module: account_bank_statement_import_txt_xlsx +#: model:ir.model,name:account_bank_statement_import_txt_xlsx.model_account_journal +msgid "Journal" +msgstr "" + +#. module: account_bank_statement_import_txt_xlsx +#: selection:account.bank.statement.import.map.line,field_to_assign:0 +msgid "Label" +msgstr "" + +#. module: account_bank_statement_import_txt_xlsx +#: model:ir.model.fields,field_description:account_bank_statement_import_txt_xlsx.field_account_bank_statement_import_map____last_update +#: model:ir.model.fields,field_description:account_bank_statement_import_txt_xlsx.field_account_bank_statement_import_map_line____last_update +#: model:ir.model.fields,field_description:account_bank_statement_import_txt_xlsx.field_wizard_txt_map_create____last_update +msgid "Last Modified on" +msgstr "" + +#. module: account_bank_statement_import_txt_xlsx +#: model:ir.model.fields,field_description:account_bank_statement_import_txt_xlsx.field_account_bank_statement_import_map__write_uid +#: model:ir.model.fields,field_description:account_bank_statement_import_txt_xlsx.field_account_bank_statement_import_map_line__write_uid +#: model:ir.model.fields,field_description:account_bank_statement_import_txt_xlsx.field_wizard_txt_map_create__write_uid +msgid "Last Updated by" +msgstr "" + +#. module: account_bank_statement_import_txt_xlsx +#: model:ir.model.fields,field_description:account_bank_statement_import_txt_xlsx.field_account_bank_statement_import_map__write_date +#: model:ir.model.fields,field_description:account_bank_statement_import_txt_xlsx.field_account_bank_statement_import_map_line__write_date +#: model:ir.model.fields,field_description:account_bank_statement_import_txt_xlsx.field_wizard_txt_map_create__write_date +msgid "Last Updated on" +msgstr "" + +#. module: account_bank_statement_import_txt_xlsx +#: model:ir.model.fields,field_description:account_bank_statement_import_txt_xlsx.field_account_bank_statement_import_map_line__map_parent_id +msgid "Map Parent" +msgstr "" + +#. module: account_bank_statement_import_txt_xlsx +#: model:ir.model.fields,field_description:account_bank_statement_import_txt_xlsx.field_account_bank_statement_import_map__map_line_ids +msgid "Map lines" +msgstr "" + +#. module: account_bank_statement_import_txt_xlsx +#: model_terms:ir.ui.view,arch_db:account_bank_statement_import_txt_xlsx.statement_import_map_tax_form +msgid "Mapping Lines" +msgstr "" + +#. module: account_bank_statement_import_txt_xlsx +#: selection:account.bank.statement.import.map.line,field_to_assign:0 +#: model:ir.model.fields,field_description:account_bank_statement_import_txt_xlsx.field_account_bank_statement_import_map__name +msgid "Name" +msgstr "" + +#. module: account_bank_statement_import_txt_xlsx +#: selection:account.bank.statement.import.map.line,field_to_assign:0 +msgid "Notes" +msgstr "" + +#. module: account_bank_statement_import_txt_xlsx +#: selection:account.bank.statement.import.map.line,field_to_assign:0 +msgid "Reference" +msgstr "" + +#. module: account_bank_statement_import_txt_xlsx +#: model_terms:ir.ui.view,arch_db:account_bank_statement_import_txt_xlsx.create_txt_map_lines_view +msgid "Select a TXT/CSV or XLSX bank statement file to create all the map lines from headers file." +msgstr "" + +#. module: account_bank_statement_import_txt_xlsx +#: model:ir.model.fields,field_description:account_bank_statement_import_txt_xlsx.field_account_bank_statement_import_map__delimiter +msgid "Separated by" +msgstr "" + +#. module: account_bank_statement_import_txt_xlsx +#: selection:account.bank.statement.import.map,delimiter:0 +msgid "Space" +msgstr "" + +#. module: account_bank_statement_import_txt_xlsx +#: model:ir.model.fields,field_description:account_bank_statement_import_txt_xlsx.field_account_bank_statement_import_map_line__field_to_assign +msgid "Statement Field to Assign" +msgstr "" + +#. module: account_bank_statement_import_txt_xlsx +#: model:ir.ui.menu,name:account_bank_statement_import_txt_xlsx.menu_statement_import_txt_mapping +msgid "Statement Import Map" +msgstr "" + +#. module: account_bank_statement_import_txt_xlsx +#: model:ir.actions.act_window,name:account_bank_statement_import_txt_xlsx.action_statement_import_txt_mappging +msgid "Statement Import Mapping" +msgstr "" + +#. module: account_bank_statement_import_txt_xlsx +#: model:ir.model.fields,field_description:account_bank_statement_import_txt_xlsx.field_account_journal__statement_import_txt_map_id +#: model_terms:ir.ui.view,arch_db:account_bank_statement_import_txt_xlsx.view_account_journal_form_n43 +msgid "Statement Import Txt Map" +msgstr "" + +#. module: account_bank_statement_import_txt_xlsx +#: model:ir.model.fields,field_description:account_bank_statement_import_txt_xlsx.field_account_bank_statement_import_map__quotechar +msgid "String delimiter" +msgstr "" + +#. module: account_bank_statement_import_txt_xlsx +#: selection:account.bank.statement.import.map,delimiter:0 +msgid "Tab" +msgstr "" + +#. module: account_bank_statement_import_txt_xlsx +#: model:ir.model.fields,field_description:account_bank_statement_import_txt_xlsx.field_account_bank_statement_import_map__float_thousands_sep +msgid "Thousands separator" +msgstr "" + +#. module: account_bank_statement_import_txt_xlsx +#: model_terms:ir.ui.view,arch_db:account_bank_statement_import_txt_xlsx.statement_import_map_tax_form +#: model_terms:ir.ui.view,arch_db:account_bank_statement_import_txt_xlsx.statement_import_map_tax_tree +msgid "Txt Map" +msgstr "" + +#. module: account_bank_statement_import_txt_xlsx +#: model:ir.model.fields,field_description:account_bank_statement_import_txt_xlsx.field_account_bank_statement_import__txt_map_id +msgid "Txt map" +msgstr "" + +#. module: account_bank_statement_import_txt_xlsx +#: model_terms:ir.ui.view,arch_db:account_bank_statement_import_txt_xlsx.statement_import_map_tax_line_form +msgid "Txt mapping line" +msgstr "" + +#. module: account_bank_statement_import_txt_xlsx +#: model_terms:ir.ui.view,arch_db:account_bank_statement_import_txt_xlsx.statement_import_map_tax_line_tree +msgid "Txt mapping lines" +msgstr "" + +#. module: account_bank_statement_import_txt_xlsx +#: model_terms:ir.ui.view,arch_db:account_bank_statement_import_txt_xlsx.account_bank_statement_import_view +msgid "Txt/XLSX file with Template:" +msgstr "" + +#. module: account_bank_statement_import_txt_xlsx +#: selection:account.bank.statement.import.map,file_encoding:0 +msgid "UTF-16" +msgstr "" + +#. module: account_bank_statement_import_txt_xlsx +#: selection:account.bank.statement.import.map,file_encoding:0 +msgid "UTF-8" +msgstr "" + +#. module: account_bank_statement_import_txt_xlsx +#: code:addons/account_bank_statement_import_txt_xlsx/wizards/account_bank_statement_import_txt.py:120 +#, python-format +msgid "Value '%s' for the field '%s' on line %d, cannot be converted to float" +msgstr "" + +#. module: account_bank_statement_import_txt_xlsx +#: selection:account.bank.statement.import.map,file_encoding:0 +msgid "Windows-1252" +msgstr "" + +#. module: account_bank_statement_import_txt_xlsx +#: model:ir.model,name:account_bank_statement_import_txt_xlsx.model_wizard_txt_map_create +msgid "Wizard Txt Map Create" +msgstr "" + +#. module: account_bank_statement_import_txt_xlsx +#: code:addons/account_bank_statement_import_txt_xlsx/wizards/account_bank_statement_import_txt.py:190 +#, python-format +msgid "You must run this wizard from the journal" +msgstr "" + +#. module: account_bank_statement_import_txt_xlsx +#: selection:account.bank.statement.import.map,file_encoding:0 +msgid "big5" +msgstr "" + +#. module: account_bank_statement_import_txt_xlsx +#: selection:account.bank.statement.import.map,delimiter:0 +#: selection:account.bank.statement.import.map,float_decimal_sep:0 +#: selection:account.bank.statement.import.map,float_thousands_sep:0 +msgid "comma (,)" +msgstr "" + +#. module: account_bank_statement_import_txt_xlsx +#: selection:account.bank.statement.import.map,delimiter:0 +#: selection:account.bank.statement.import.map,float_decimal_sep:0 +#: selection:account.bank.statement.import.map,float_thousands_sep:0 +msgid "dot (.)" +msgstr "" + +#. module: account_bank_statement_import_txt_xlsx +#: selection:account.bank.statement.import.map,file_encoding:0 +msgid "gb18030" +msgstr "" + +#. module: account_bank_statement_import_txt_xlsx +#: selection:account.bank.statement.import.map.line,date_format:0 +msgid "i.e. 12/15/2019" +msgstr "" + +#. module: account_bank_statement_import_txt_xlsx +#: selection:account.bank.statement.import.map.line,date_format:0 +msgid "i.e. 15/12/2019" +msgstr "" + +#. module: account_bank_statement_import_txt_xlsx +#: selection:account.bank.statement.import.map,file_encoding:0 +msgid "koir9_r" +msgstr "" + +#. module: account_bank_statement_import_txt_xlsx +#: selection:account.bank.statement.import.map,file_encoding:0 +msgid "latin1" +msgstr "" + +#. module: account_bank_statement_import_txt_xlsx +#: selection:account.bank.statement.import.map,file_encoding:0 +msgid "latin2" +msgstr "" + +#. module: account_bank_statement_import_txt_xlsx +#: selection:account.bank.statement.import.map,delimiter:0 +#: selection:account.bank.statement.import.map,float_decimal_sep:0 +#: selection:account.bank.statement.import.map,float_thousands_sep:0 +msgid "none" +msgstr "" + +#. module: account_bank_statement_import_txt_xlsx +#: selection:account.bank.statement.import.map,delimiter:0 +msgid "semicolon (;)" +msgstr "" + +#. module: account_bank_statement_import_txt_xlsx +#: selection:account.bank.statement.import.map,file_encoding:0 +msgid "shift_jis" +msgstr "" + +#. module: account_bank_statement_import_txt_xlsx +#: selection:account.bank.statement.import.map,file_encoding:0 +msgid "windows-1251" +msgstr "" + diff --git a/account_bank_statement_import_txt_xlsx/models/__init__.py b/account_bank_statement_import_txt_xlsx/models/__init__.py new file mode 100644 index 00000000..ff680f4c --- /dev/null +++ b/account_bank_statement_import_txt_xlsx/models/__init__.py @@ -0,0 +1,2 @@ +from . import account_bank_statement_import_txt_map +from . import account_journal diff --git a/account_bank_statement_import_txt_xlsx/models/account_bank_statement_import_txt_map.py b/account_bank_statement_import_txt_xlsx/models/account_bank_statement_import_txt_map.py new file mode 100644 index 00000000..9a6bc07f --- /dev/null +++ b/account_bank_statement_import_txt_xlsx/models/account_bank_statement_import_txt_map.py @@ -0,0 +1,136 @@ +# Copyright 2019 Tecnativa - Vicent Cubells +# Copyright 2019 Eficent Business and IT Consulting Services, S.L. +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import fields, models, api + + +class AccountBankStatementImportTxtMap(models.Model): + _name = 'account.bank.statement.import.map' + _description = 'Account Bank Statement Import Txt Map' + + name = fields.Char( + required=True, + ) + map_line_ids = fields.One2many( + comodel_name='account.bank.statement.import.map.line', + inverse_name='map_parent_id', + string="Map lines", + required=True, + copy=True, + ) + float_thousands_sep = fields.Selection( + [('dot', 'dot (.)'), + ('comma', 'comma (,)'), + ('none', 'none'), + ], + string='Thousands separator', + # forward compatibility: this was the value assumed + # before the field was added. + default='dot', + required=True + ) + float_decimal_sep = fields.Selection( + [('dot', 'dot (.)'), + ('comma', 'comma (,)'), + ('none', 'none'), + ], + string='Decimals separator', + # forward compatibility: this was the value assumed + # before the field was added. + default='comma', + required=True + ) + file_encoding = fields.Selection( + string='Encoding', + selection=[ + ('utf-8', 'UTF-8'), + ('utf-16 ', 'UTF-16'), + ('windows-1252', 'Windows-1252'), + ('latin1', 'latin1'), + ('latin2', 'latin2'), + ('big5', 'big5'), + ('gb18030', 'gb18030'), + ('shift_jis', 'shift_jis'), + ('windows-1251', 'windows-1251'), + ('koir8_r', 'koir9_r'), + ], + default='utf-8', + ) + delimiter = fields.Selection( + string='Separated by', + selection=[ + ('.', 'dot (.)'), + (',', 'comma (,)'), + (';', 'semicolon (;)'), + ('', 'none'), + ('\t', 'Tab'), + (' ', 'Space'), + ], + default=',', + ) + quotechar = fields.Char(string='String delimiter', size=1, + default='"') + + @api.onchange('float_thousands_sep') + def onchange_thousands_separator(self): + if 'dot' == self.float_thousands_sep == self.float_decimal_sep: + self.float_decimal_sep = 'comma' + elif 'comma' == self.float_thousands_sep == self.float_decimal_sep: + self.float_decimal_sep = 'dot' + + @api.onchange('float_decimal_sep') + def onchange_decimal_separator(self): + if 'dot' == self.float_thousands_sep == self.float_decimal_sep: + self.float_thousands_sep = 'comma' + elif 'comma' == self.float_thousands_sep == self.float_decimal_sep: + self.float_thousands_sep = 'dot' + + def _get_separators(self): + separators = {'dot': '.', + 'comma': ',', + 'none': '', + } + return (separators[self.float_thousands_sep], + separators[self.float_decimal_sep]) + + +class AccountBankStatementImportTxtMapLine(models.Model): + _name = 'account.bank.statement.import.map.line' + _description = 'Account Bank Statement Import Txt Map Line' + _order = "sequence asc, id asc" + + sequence = fields.Integer( + string="Field number", + required=True, + ) + name = fields.Char( + string="Header Name", + required=True, + ) + map_parent_id = fields.Many2one( + comodel_name='account.bank.statement.import.map', + required=True, + ondelete='cascade', + ) + field_to_assign = fields.Selection( + selection=[ + ('date', 'Date'), + ('name', 'Label'), + ('currency', 'Currency'), + ('amount', 'Amount in the journal currency'), + ('amount_currency', 'Amount in foreign currency'), + ('ref', 'Reference'), + ('note', 'Notes'), + ('partner_name', 'Name'), + ('account_number', 'Bank Account Number'), + ], + string="Statement Field to Assign", + ) + date_format = fields.Selection( + selection=[ + ('%d/%m/%Y', 'i.e. 15/12/2019'), + ('%m/%d/%Y', 'i.e. 12/15/2019'), + ], + string="Date Format", + ) diff --git a/account_bank_statement_import_txt_xlsx/models/account_journal.py b/account_bank_statement_import_txt_xlsx/models/account_journal.py new file mode 100644 index 00000000..261df272 --- /dev/null +++ b/account_bank_statement_import_txt_xlsx/models/account_journal.py @@ -0,0 +1,19 @@ +# Copyright 2019 Tecnativa - Vicent Cubells +# Copyright 2019 Eficent Business and IT Consulting Services, S.L. +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import fields, models + + +class AccountJournal(models.Model): + _inherit = "account.journal" + + statement_import_txt_map_id = fields.Many2one( + comodel_name='account.bank.statement.import.map', + string='Statement Import Txt Map', + ) + + def _get_bank_statements_available_import_formats(self): + res = super()._get_bank_statements_available_import_formats() + res.append('Txt') + return res diff --git a/account_bank_statement_import_txt_xlsx/readme/CONFIGURE.rst b/account_bank_statement_import_txt_xlsx/readme/CONFIGURE.rst new file mode 100644 index 00000000..5c81f99c --- /dev/null +++ b/account_bank_statement_import_txt_xlsx/readme/CONFIGURE.rst @@ -0,0 +1,12 @@ +* Create or go to a bank journal where you want to import the txt statement. +* Edit that journal and set a Txt map in **Statement Import Map** section in **Advanced + Settings** tab. + +* Now you can import Text based statements in that journal. + +Note: if existent Txt Map does not fit to your file to import, you can +create another map in **Invoicing > Configuration > Accounting > +Statement Import Map**. + +You can import headers from any Txt file in **Action > Create Map +Lines** and set every line with which field of statement have to match. diff --git a/account_bank_statement_import_txt_xlsx/readme/CONTRIBUTORS.rst b/account_bank_statement_import_txt_xlsx/readme/CONTRIBUTORS.rst new file mode 100644 index 00000000..0e583091 --- /dev/null +++ b/account_bank_statement_import_txt_xlsx/readme/CONTRIBUTORS.rst @@ -0,0 +1,7 @@ +* Alexis de Lattre +* Sebastien BEAU +* Tecnativa (https://www.tecnativa.com) + * Vicent Cubells + * Victor M.M. Torres +* Eficent (https://www.eficent.com) + * Jordi Ballester Alomar diff --git a/account_bank_statement_import_txt_xlsx/readme/DESCRIPTION.rst b/account_bank_statement_import_txt_xlsx/readme/DESCRIPTION.rst new file mode 100644 index 00000000..25648404 --- /dev/null +++ b/account_bank_statement_import_txt_xlsx/readme/DESCRIPTION.rst @@ -0,0 +1,2 @@ +This module allows you to import the any TXT/CSV or XLSX file in Odoo as bank +statements. diff --git a/account_bank_statement_import_txt_xlsx/readme/USAGE.rst b/account_bank_statement_import_txt_xlsx/readme/USAGE.rst new file mode 100644 index 00000000..2a8fb28a --- /dev/null +++ b/account_bank_statement_import_txt_xlsx/readme/USAGE.rst @@ -0,0 +1,3 @@ +To use this module, you need to: + +#. Go to your bank online and download your Bank Statement in TXT/CSV or XLSX format. diff --git a/account_bank_statement_import_txt_xlsx/security/ir.model.access.csv b/account_bank_statement_import_txt_xlsx/security/ir.model.access.csv new file mode 100644 index 00000000..b282f31b --- /dev/null +++ b/account_bank_statement_import_txt_xlsx/security/ir.model.access.csv @@ -0,0 +1,3 @@ +"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink" +access_account_bank_statement_import_map,map manager,model_account_bank_statement_import_map,account.group_account_manager,1,1,1,1 +access_account_bank_statement_import_map_line,map line manager,model_account_bank_statement_import_map_line,account.group_account_manager,1,1,1,1 diff --git a/account_bank_statement_import_txt_xlsx/static/description/icon.png b/account_bank_statement_import_txt_xlsx/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..3a0328b516c4980e8e44cdb63fd945757ddd132d GIT binary patch literal 9455 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 z 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 Xviia!U7SGha1wx#SCgwmn*{w2TRX*I literal 0 HcmV?d00001 diff --git a/account_bank_statement_import_txt_xlsx/static/description/index.html b/account_bank_statement_import_txt_xlsx/static/description/index.html new file mode 100644 index 00000000..cc273236 --- /dev/null +++ b/account_bank_statement_import_txt_xlsx/static/description/index.html @@ -0,0 +1,449 @@ + + + + + + +Account Bank Statement Import TXT XLSX + + + +
+

Account Bank Statement Import TXT XLSX

+ + +

Beta License: AGPL-3 OCA/bank-statement-import Translate me on Weblate Try me on Runbot

+

This module allows you to import the any TXT/CSV or XLSX file in Odoo as bank +statements.

+

Table of contents

+ +
+

Configuration

+
    +
  • Create or go to a bank journal where you want to import the txt statement.
  • +
  • Edit that journal and set a Txt map in Statement Import Map section in Advanced +Settings tab.
  • +
  • Now you can import Text based statements in that journal.
  • +
+

Note: if existent Txt Map does not fit to your file to import, you can +create another map in Invoicing > Configuration > Accounting > +Statement Import Map.

+

You can import headers from any Txt file in Action > Create Map +Lines and set every line with which field of statement have to match.

+
+
+

Usage

+

To use this module, you need to:

+
    +
  1. Go to your bank online and download your Bank Statement in TXT/CSV or XLSX format.
  2. +
+
+
+

Bug Tracker

+

Bugs are tracked on GitHub Issues. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us smashing it by providing a detailed and welcomed +feedback.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • Eficent
  • +
+
+
+

Contributors

+ +
+
+

Maintainers

+

This module is maintained by the OCA.

+Odoo Community Association +

OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use.

+

This module is part of the OCA/bank-statement-import project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+ + diff --git a/account_bank_statement_import_txt_xlsx/tests/__init__.py b/account_bank_statement_import_txt_xlsx/tests/__init__.py new file mode 100644 index 00000000..3260f753 --- /dev/null +++ b/account_bank_statement_import_txt_xlsx/tests/__init__.py @@ -0,0 +1 @@ +from . import test_txt_statement_import diff --git a/account_bank_statement_import_txt_xlsx/tests/sample_statement_en.csv b/account_bank_statement_import_txt_xlsx/tests/sample_statement_en.csv new file mode 100644 index 00000000..c50bfc83 --- /dev/null +++ b/account_bank_statement_import_txt_xlsx/tests/sample_statement_en.csv @@ -0,0 +1,3 @@ +"Date","Label","Currency","Amount","Amount Currency","Partner Name","Bank Account" +"12/15/2018","Your best supplier","USD","-33.50","0.0","John Doe","123456789" +"12/15/2018","Your payment","EUR","1,525.00","1,000.00","Azure Interior","" diff --git a/account_bank_statement_import_txt_xlsx/tests/sample_statement_en.xlsx b/account_bank_statement_import_txt_xlsx/tests/sample_statement_en.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..b3365b205d4ff97fe88181c5654983dcfec100d2 GIT binary patch literal 5092 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 literal 0 HcmV?d00001 diff --git a/account_bank_statement_import_txt_xlsx/tests/test_txt_statement_import.py b/account_bank_statement_import_txt_xlsx/tests/test_txt_statement_import.py new file mode 100644 index 00000000..11ceb77e --- /dev/null +++ b/account_bank_statement_import_txt_xlsx/tests/test_txt_statement_import.py @@ -0,0 +1,99 @@ +# Copyright 2019 Tecnativa - Vicent Cubells +# Copyright 2019 Eficent Business and IT Consulting Services, S.L. +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import os +import base64 +from odoo.tests import common + + +class TestTxtFile(common.TransactionCase): + + def setUp(self): + super(TestTxtFile, self).setUp() + + self.map = self.env['account.bank.statement.import.map'].create({ + 'name': 'Txt Map Test', + }) + usd = self.env.ref('base.USD') + self.journal = self.env['account.journal'].create({ + 'name': 'Txt Bank', + 'type': 'bank', + 'code': 'TXT', + 'currency_id': ( + usd.id if self.env.user.company_id.currency_id != usd + else False + ), + }) + + def _do_import_xlsx(self, file_name): + file_name = os.path.join(os.path.dirname(__file__), file_name) + with open(file_name, 'rb') as fin: + data = fin.read() + return data + + def _do_import(self, file_name): + file_name = os.path.join(os.path.dirname(__file__), file_name) + return open(file_name).read() + + def test_import_header(self): + file = self._do_import('sample_statement_en.csv') + file = base64.b64encode(file.encode("utf-8")) + wizard = self.env['wizard.txt.map.create'].with_context({ + 'journal_id': self.journal.id, + 'active_ids': [self.map.id], + }).create({'data_file': file}) + wizard.create_map_lines() + self.assertEqual(len(self.map.map_line_ids.ids), 7) + + def test_import_txt_file(self): + # Current statements before to run the wizard + old_statements = self.env['account.bank.statement'].search([]) + # This journal is for Txt statements + txt_map = self.env.ref( + 'account_bank_statement_import_txt_xlsx.txt_map' + ) + self.journal.statement_import_txt_map_id = txt_map.id + file = self._do_import('sample_statement_en.csv') + file = base64.b64encode(file.encode("utf-8")) + wizard = self.env['account.bank.statement.import'].with_context({ + 'journal_id': self.journal.id, + }).create({'data_file': file}) + wizard.import_file() + staments_now = self.env['account.bank.statement'].search([]) + statement = staments_now - old_statements + self.assertEqual(len(statement.line_ids), 2) + self.assertEqual(len(statement.mapped('line_ids').filtered( + lambda x: x.partner_id)), 1) + self.assertAlmostEqual( + sum(statement.mapped('line_ids.amount')), 1491.50 + ) + self.assertAlmostEqual( + sum(statement.mapped('line_ids.amount_currency')), 1000.00 + ) + + def test_import_xlsx_file(self): + # Current statements before to run the wizard + old_statements = self.env['account.bank.statement'].search([]) + # This journal is for Txt statements + txt_map = self.env.ref( + 'account_bank_statement_import_txt_xlsx.txt_map' + ) + self.journal.statement_import_txt_map_id = txt_map.id + file = self._do_import_xlsx('sample_statement_en.xlsx') + file = base64.b64encode(file) + wizard = self.env['account.bank.statement.import'].with_context({ + 'journal_id': self.journal.id, + }).create({'data_file': file}) + wizard.import_file() + staments_now = self.env['account.bank.statement'].search([]) + statement = staments_now - old_statements + self.assertEqual(len(statement.line_ids), 2) + self.assertEqual(len(statement.mapped('line_ids').filtered( + lambda x: x.partner_id)), 1) + self.assertAlmostEqual( + sum(statement.mapped('line_ids.amount')), 1491.50 + ) + self.assertAlmostEqual( + sum(statement.mapped('line_ids.amount_currency')), 1000.00 + ) diff --git a/account_bank_statement_import_txt_xlsx/views/account_journal_views.xml b/account_bank_statement_import_txt_xlsx/views/account_journal_views.xml new file mode 100644 index 00000000..b7be6cd2 --- /dev/null +++ b/account_bank_statement_import_txt_xlsx/views/account_journal_views.xml @@ -0,0 +1,16 @@ + + + + + account.journal + + + + + + + + + + + diff --git a/account_bank_statement_import_txt_xlsx/views/txt_map_views.xml b/account_bank_statement_import_txt_xlsx/views/txt_map_views.xml new file mode 100644 index 00000000..c7ab7550 --- /dev/null +++ b/account_bank_statement_import_txt_xlsx/views/txt_map_views.xml @@ -0,0 +1,70 @@ + + + + + account.bank.statement.import.map + + + + + + + + + account.bank.statement.import.map + +
+ + + + + + + + + + + + +
+ + + account.bank.statement.import.map.line + + + + + + + + + + + + account.bank.statement.import.map.line + +
+ + + + + + + +
+
+
+ + + Statement Import Mapping + account.bank.statement.import.map + form + tree,form + + + + +
diff --git a/account_bank_statement_import_txt_xlsx/wizards/__init__.py b/account_bank_statement_import_txt_xlsx/wizards/__init__.py new file mode 100644 index 00000000..ec27082d --- /dev/null +++ b/account_bank_statement_import_txt_xlsx/wizards/__init__.py @@ -0,0 +1,2 @@ +from . import create_map_lines_from_file +from . import account_bank_statement_import_txt diff --git a/account_bank_statement_import_txt_xlsx/wizards/account_bank_statement_import_txt.py b/account_bank_statement_import_txt_xlsx/wizards/account_bank_statement_import_txt.py new file mode 100644 index 00000000..228b4314 --- /dev/null +++ b/account_bank_statement_import_txt_xlsx/wizards/account_bank_statement_import_txt.py @@ -0,0 +1,288 @@ +# Copyright 2014-2017 Akretion (http://www.akretion.com). +# @author Alexis de Lattre +# @author Sébastien BEAU +# Copyright 2019 Eficent Business and IT Consulting Services, S.L. +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import xlrd +import logging +import datetime as dtm +from datetime import datetime +from odoo import _, api, fields, models +from odoo.exceptions import UserError +import re +from io import StringIO +_logger = logging.getLogger(__name__) + +try: + import csv +except (ImportError, IOError) as err: + _logger.debug(err) + + +class AccountBankStatementImport(models.TransientModel): + _inherit = 'account.bank.statement.import' + + txt_map_id = fields.Many2one( + comodel_name='account.bank.statement.import.map', + string='Txt map', + readonly=True, + ) + + @api.model + def _get_txt_encoding(self): + if self.txt_map_id.file_encoding: + return self.txt_map_id.file_encoding + return 'utf-8-sig' + + @api.model + def _get_txt_str_data(self, data_file): + if not isinstance(data_file, str): + data_file = data_file.decode(self._get_txt_encoding()) + return data_file.strip() + + @api.model + def _txt_convert_amount(self, amount_str): + if not amount_str: + return 0.0 + if self.txt_map_id: + thousands, decimal = self.txt_map_id._get_separators() + else: + thousands, decimal = ',', '.' + valstr = re.sub(r'[^\d%s%s.-]' % (thousands, decimal), '', amount_str) + valstrdot = valstr.replace(thousands, '') + valstrdot = valstrdot.replace(decimal, '.') + return float(valstrdot) + + @api.model + def _check_xls(self, data_file): + # Try if it is an Excel file + headers = self.mapped('txt_map_id.map_line_ids.name') + try: + file_headers = [] + book = xlrd.open_workbook(file_contents=data_file) + xl_sheet = book.sheet_by_index(0) + row = xl_sheet.row(0) # 1st row + for idx, cell_obj in enumerate(row): + cell_type_str = xlrd.sheet.ctype_text.get(cell_obj.ctype, False) + if cell_type_str: + file_headers.append(cell_obj.value) + else: + return False + if any(item not in file_headers for item in headers): + raise UserError( + _("Headers of file to import and Txt map lines does not " + "match.")) + except xlrd.XLRDError as e: + return False + except Exception as e: + return False + return True + + @api.model + def _check_txt(self, data_file): + data_file = self._get_txt_str_data(data_file) + if not self.txt_map_id: + return False + headers = self.mapped('txt_map_id.map_line_ids.name') + file_headers = data_file.split('\n', 1)[0] + if any(item not in file_headers for item in headers): + raise UserError( + _("Headers of file to import and Txt map lines does not " + "match.")) + return True + + def _get_currency_fields(self): + return ['amount', 'amount_currency'] + + def _convert_txt_line_to_dict(self, idx, line): + rline = dict() + for item in range(len(line)): + txt_map = self.mapped('txt_map_id.map_line_ids')[item] + value = line[item] + if not txt_map.field_to_assign: + continue + if txt_map.date_format: + try: + value = fields.Date.to_string( + datetime.strptime(value, txt_map.date_format)) + except Exception: + raise UserError( + _("Date format of map file and Txt date does " + "not match.")) + rline[txt_map.field_to_assign] = value + for field in self._get_currency_fields(): + _logger.debug('Trying to convert %s to float' % rline[field]) + try: + rline[field] = self._txt_convert_amount(rline[field]) + except Exception: + raise UserError( + _("Value '%s' for the field '%s' on line %d, " + "cannot be converted to float") + % (rline[field], field, idx)) + return rline + + def _parse_txt_file(self, data_file): + data_file = self._get_txt_str_data(data_file) + f = StringIO(data_file) + f.seek(0) + raw_lines = [] + if not self.txt_map_id.quotechar: + reader = csv.reader(f, + delimiter=self.txt_map_id.delimiter or False) + else: + reader = csv.reader(f, + quotechar=self.txt_map_id.quotechar, + delimiter=self.txt_map_id.delimiter or False) + next(reader) # Drop header + for idx, line in enumerate(reader): + _logger.debug("Line %d: %s" % (idx, line)) + raw_lines.append(self._convert_txt_line_to_dict(idx, line)) + return raw_lines + + def _convert_xls_line_to_dict(self, row_idx, xl_sheet): + rline = dict() + for col_idx in range(0, xl_sheet.ncols): # Iterate through columns + txt_map = self.mapped('txt_map_id.map_line_ids')[col_idx] + cell_obj = xl_sheet.cell(row_idx, col_idx) # Get cell + ctype = xl_sheet.cell(row_idx, col_idx).ctype + value = cell_obj.value + if not txt_map.field_to_assign: + continue + if ctype == xlrd.XL_CELL_DATE: + ms_date_number = xl_sheet.cell(row_idx, col_idx).value + try: + year, month, day, hour, minute, \ + second = xlrd.xldate_as_tuple( + ms_date_number, 0) + except xlrd.XLDateError as e: + raise UserError( + _('An error was found translating a date ' + 'field from the file: %s') % e) + value = dtm.date(year, month, day) + value = value.strftime('%Y-%m-%d') + rline[txt_map.field_to_assign] = value + + return rline + + def _parse_xls_file(self, data_file): + try: + raw_lines = [] + book = xlrd.open_workbook(file_contents=data_file) + xl_sheet = book.sheet_by_index(0) + for row_idx in range(1, xl_sheet.nrows): + _logger.debug("Line %d" % row_idx) + raw_lines.append(self._convert_xls_line_to_dict( + row_idx, xl_sheet)) + except xlrd.XLRDError: + return False + except Exception as e: + return False + return raw_lines + + def _post_process_statement_line(self, raw_lines): + """ Enter your additional logic here. """ + return raw_lines + + def _get_journal(self): + journal_id = self.env.context.get('journal_id') + if not journal_id: + raise UserError(_('You must run this wizard from the journal')) + return self.env['account.journal'].browse(journal_id) + + def _get_currency_id(self, fline): + journal = self._get_journal() + line_currency_name = fline.get('currency', False) + currency = journal.currency_id or journal.company_id.currency_id + if line_currency_name and line_currency_name != currency.name: + currency = self.env['res.currency'].search( + [('name', '=', fline['currency'])], limit=1) + return currency.id + return False + + @api.model + def _get_partner_id(self, fline): + partner_name = fline.get('partner_name', False) + if partner_name: + partner = self.env['res.partner'].search([ + ('name', '=ilike', partner_name)]) + if partner and len(partner) == 1: + return partner.commercial_partner_id.id + return None + + def _prepare_txt_statement_line(self, fline): + currency_id = self._get_currency_id(fline) + return { + 'date': fline.get('date', False), + 'name': fline.get('name', ''), + 'ref': fline.get('ref', False), + 'note': fline.get('Notes', False), + 'amount': fline.get('amount', 0.0), + 'currency_id': self._get_currency_id(fline), + 'amount_currency': currency_id and fline.get( + 'amount_currency', 0.0) or 0.0, + 'partner_id': self._get_partner_id(fline), + 'account_number': fline.get('account_number', False), + } + + def _prepare_txt_statement(self, lines): + balance_end_real = 0.0 + for line in lines: + if 'amount' in line and line['amount']: + balance_end_real += line['amount'] + + return { + 'name': + _('%s Import %s > %s') + % (self.txt_map_id.name, + lines[0]['date'], lines[-1]['date']), + 'date': lines[-1]['date'], + 'balance_start': 0.0, + 'balance_end_real': balance_end_real, + } + + @api.model + def _parse_file(self, data_file): + """ Import a file in Txt CSV format """ + is_txt = False + is_xls = self._check_xls(data_file) + if not is_xls: + is_txt = self._check_txt(data_file) + if not is_txt and not is_xls: + return super(AccountBankStatementImport, self)._parse_file( + data_file) + if is_txt: + raw_lines = self._parse_txt_file(data_file) + else: + raw_lines = self._parse_xls_file(data_file) + final_lines = self._post_process_statement_line(raw_lines) + vals_bank_statement = self._prepare_txt_statement(final_lines) + transactions = [] + for fline in final_lines: + vals_line = self._prepare_txt_statement_line(fline) + _logger.debug("vals_line = %s" % vals_line) + transactions.append(vals_line) + vals_bank_statement['transactions'] = transactions + return None, None, [vals_bank_statement] + + @api.model + def _complete_txt_statement_line(self, line): + """ Enter additional logic here. """ + return None + + @api.model + def _complete_stmts_vals(self, stmts_vals, journal_id, account_number): + stmts_vals = super(AccountBankStatementImport, self). \ + _complete_stmts_vals(stmts_vals, journal_id, account_number) + for line in stmts_vals[0]['transactions']: + vals = self._complete_txt_statement_line(line) + if vals: + line.update(vals) + return stmts_vals + + @api.model + def default_get(self, fields): + res = super(AccountBankStatementImport, self).default_get(fields) + journal = self._get_journal() + res['txt_map_id'] = journal.statement_import_txt_map_id.id + return res diff --git a/account_bank_statement_import_txt_xlsx/wizards/account_bank_statement_import_view.xml b/account_bank_statement_import_txt_xlsx/wizards/account_bank_statement_import_view.xml new file mode 100644 index 00000000..14e682d0 --- /dev/null +++ b/account_bank_statement_import_txt_xlsx/wizards/account_bank_statement_import_view.xml @@ -0,0 +1,14 @@ + + + + + account.bank.statement.import + + + +
  • Txt/XLSX file with Template:
  • +
    +
    +
    + +
    diff --git a/account_bank_statement_import_txt_xlsx/wizards/create_map_lines_from_file.py b/account_bank_statement_import_txt_xlsx/wizards/create_map_lines_from_file.py new file mode 100644 index 00000000..65d279ed --- /dev/null +++ b/account_bank_statement_import_txt_xlsx/wizards/create_map_lines_from_file.py @@ -0,0 +1,40 @@ +# Copyright 2019 Tecnativa - Vicent Cubells +# Copyright 2019 Eficent Business and IT Consulting Services, S.L. +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import csv +import base64 +from odoo import api, fields, models +from io import StringIO + + +class WizardTxtMapCreate(models.TransientModel): + _name = 'wizard.txt.map.create' + _description = 'Wizard Txt Map Create' + + data_file = fields.Binary( + string='Bank Statement File', + required=True, + ) + filename = fields.Char() + + @api.multi + def create_map_lines(self): + statement_obj = self.env['account.bank.statement.import.map'] + data_file = base64.b64decode(self.data_file) + if not isinstance(data_file, str): + data_file = data_file.decode('utf-8-sig').strip() + file = StringIO(data_file) + file.seek(0) + reader = csv.reader(file) + headers = [] + for row in reader: + headers = row + break + lines = [] + for idx, title in enumerate(headers): + lines.append((0, 0, {'sequence': idx, 'name': title})) + if lines: + for statement in statement_obj.browse( + self.env.context.get('active_ids')): + statement.map_line_ids = lines diff --git a/account_bank_statement_import_txt_xlsx/wizards/create_map_lines_from_file_views.xml b/account_bank_statement_import_txt_xlsx/wizards/create_map_lines_from_file_views.xml new file mode 100644 index 00000000..d94d868c --- /dev/null +++ b/account_bank_statement_import_txt_xlsx/wizards/create_map_lines_from_file_views.xml @@ -0,0 +1,29 @@ + + + + + Create Statement Map Lines + wizard.txt.map.create + +
    +

    Select a TXT/CSV or XLSX bank statement file to create all the map lines from headers file.

    +

    Download a bank statement from your bank and import it here.

    +

    All the txt map lines will be created automatically.

    + + +
    +
    + +
    +
    + + +
    From 50e73b9871bb05342f0388f79cadbfb7ae0278e0 Mon Sep 17 00:00:00 2001 From: Alexey Pelykh Date: Sat, 28 Mar 2020 10:26:54 +0100 Subject: [PATCH 02/29] [IMP] account_bank_statement_import_txt_xlsx --- .../__manifest__.py | 33 +- .../data/map_data.xml | 25 ++ .../data/txt_map_data.xml | 53 --- .../migrations/12.0.2.0.0/post-migration.py | 98 ++++++ .../models/__init__.py | 6 +- .../models/account_bank_statement_import.py | 33 ++ ...unt_bank_statement_import_sheet_mapping.py | 188 ++++++++++ ...ount_bank_statement_import_sheet_parser.py | 331 ++++++++++++++++++ .../account_bank_statement_import_txt_map.py | 136 ------- .../models/account_journal.py | 17 +- .../readme/CONFIGURE.rst | 14 +- .../readme/CONTRIBUTORS.rst | 1 + .../readme/HISTORY.rst | 7 + .../readme/USAGE.rst | 3 +- .../security/ir.model.access.csv | 4 +- .../tests/__init__.py | 4 +- .../tests/fixtures/balance.csv | 3 + .../tests/fixtures/debit_credit.csv | 3 + .../tests/fixtures/empty_statement_en.csv | 1 + .../tests/fixtures/empty_statement_en.xlsx | Bin 0 -> 9041 bytes .../tests/fixtures/multi_currency.csv | 3 + .../tests/fixtures/original_currency.csv | 2 + .../{ => fixtures}/sample_statement_en.csv | 0 .../{ => fixtures}/sample_statement_en.xlsx | Bin ..._account_bank_statement_import_txt_xlsx.py | 327 +++++++++++++++++ .../tests/test_txt_statement_import.py | 99 ------ .../account_bank_statement_import.xml} | 10 +- ...nt_bank_statement_import_sheet_mapping.xml | 82 +++++ .../views/account_journal_views.xml | 16 - .../views/txt_map_views.xml | 70 ---- .../wizards/__init__.py | 5 +- ...k_statement_import_sheet_mapping_wizard.py | 191 ++++++++++ ..._statement_import_sheet_mapping_wizard.xml | 144 ++++++++ .../account_bank_statement_import_txt.py | 288 --------------- .../wizards/create_map_lines_from_file.py | 40 --- .../create_map_lines_from_file_views.xml | 29 -- 36 files changed, 1492 insertions(+), 774 deletions(-) create mode 100644 account_bank_statement_import_txt_xlsx/data/map_data.xml delete mode 100644 account_bank_statement_import_txt_xlsx/data/txt_map_data.xml create mode 100644 account_bank_statement_import_txt_xlsx/migrations/12.0.2.0.0/post-migration.py create mode 100644 account_bank_statement_import_txt_xlsx/models/account_bank_statement_import.py create mode 100644 account_bank_statement_import_txt_xlsx/models/account_bank_statement_import_sheet_mapping.py create mode 100644 account_bank_statement_import_txt_xlsx/models/account_bank_statement_import_sheet_parser.py delete mode 100644 account_bank_statement_import_txt_xlsx/models/account_bank_statement_import_txt_map.py create mode 100644 account_bank_statement_import_txt_xlsx/readme/HISTORY.rst create mode 100644 account_bank_statement_import_txt_xlsx/tests/fixtures/balance.csv create mode 100644 account_bank_statement_import_txt_xlsx/tests/fixtures/debit_credit.csv create mode 100644 account_bank_statement_import_txt_xlsx/tests/fixtures/empty_statement_en.csv create mode 100644 account_bank_statement_import_txt_xlsx/tests/fixtures/empty_statement_en.xlsx create mode 100644 account_bank_statement_import_txt_xlsx/tests/fixtures/multi_currency.csv create mode 100644 account_bank_statement_import_txt_xlsx/tests/fixtures/original_currency.csv rename account_bank_statement_import_txt_xlsx/tests/{ => fixtures}/sample_statement_en.csv (100%) rename account_bank_statement_import_txt_xlsx/tests/{ => fixtures}/sample_statement_en.xlsx (100%) create mode 100644 account_bank_statement_import_txt_xlsx/tests/test_account_bank_statement_import_txt_xlsx.py delete mode 100644 account_bank_statement_import_txt_xlsx/tests/test_txt_statement_import.py rename account_bank_statement_import_txt_xlsx/{wizards/account_bank_statement_import_view.xml => views/account_bank_statement_import.xml} (55%) create mode 100644 account_bank_statement_import_txt_xlsx/views/account_bank_statement_import_sheet_mapping.xml delete mode 100644 account_bank_statement_import_txt_xlsx/views/account_journal_views.xml delete mode 100644 account_bank_statement_import_txt_xlsx/views/txt_map_views.xml create mode 100644 account_bank_statement_import_txt_xlsx/wizards/account_bank_statement_import_sheet_mapping_wizard.py create mode 100644 account_bank_statement_import_txt_xlsx/wizards/account_bank_statement_import_sheet_mapping_wizard.xml delete mode 100644 account_bank_statement_import_txt_xlsx/wizards/account_bank_statement_import_txt.py delete mode 100644 account_bank_statement_import_txt_xlsx/wizards/create_map_lines_from_file.py delete mode 100644 account_bank_statement_import_txt_xlsx/wizards/create_map_lines_from_file_views.xml diff --git a/account_bank_statement_import_txt_xlsx/__manifest__.py b/account_bank_statement_import_txt_xlsx/__manifest__.py index 08b8fba3..6d1f50c3 100644 --- a/account_bank_statement_import_txt_xlsx/__manifest__.py +++ b/account_bank_statement_import_txt_xlsx/__manifest__.py @@ -1,28 +1,35 @@ -# Copyright 2014-2017 Akretion (http://www.akretion.com). -# @author Alexis de Lattre -# @author Sébastien BEAU -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +# Copyright 2019 ForgeFlow, S.L. +# Copyright 2020 Brainbean Apps (https://brainbeanapps.com) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + { "name": "Account Bank Statement Import TXT XLSX", - 'summary': 'Import TXT/CSV or XLSX files as Bank Statements in Odoo', - "version": "12.0.1.0.1", + "summary": "Import TXT/CSV or XLSX files as Bank Statements in Odoo", + "version": "12.0.2.0.0", "category": "Accounting", "website": "https://github.com/OCA/bank-statement-import", - "author": " Eficent, Odoo Community Association (OCA)", + "author": + "ForgeFlow, " + "Brainbean Apps, " + "Odoo Community Association (OCA)", "license": "AGPL-3", "installable": True, "depends": [ "account_bank_statement_import", + "multi_step_wizard", + "web_widget_dropdown_dynamic", ], "external_dependencies": { - "python": ["xlrd"], + "python": [ + "csv", + "xlrd", + ] }, "data": [ "security/ir.model.access.csv", - "data/txt_map_data.xml", - "wizards/create_map_lines_from_file_views.xml", - "wizards/account_bank_statement_import_view.xml", - "views/account_journal_views.xml", - "views/txt_map_views.xml", + "data/map_data.xml", + "views/account_bank_statement_import_sheet_mapping.xml", + "views/account_bank_statement_import.xml", + "wizards/account_bank_statement_import_sheet_mapping_wizard.xml", ] } diff --git a/account_bank_statement_import_txt_xlsx/data/map_data.xml b/account_bank_statement_import_txt_xlsx/data/map_data.xml new file mode 100644 index 00000000..3025d9c9 --- /dev/null +++ b/account_bank_statement_import_txt_xlsx/data/map_data.xml @@ -0,0 +1,25 @@ + + + + + + Sample Statement + comma + dot + comma + " + %m/%d/%Y + Date + Amount + Currency + Amount Currency + Label + Partner Name + Bank Account + + + diff --git a/account_bank_statement_import_txt_xlsx/data/txt_map_data.xml b/account_bank_statement_import_txt_xlsx/data/txt_map_data.xml deleted file mode 100644 index fb4c899a..00000000 --- a/account_bank_statement_import_txt_xlsx/data/txt_map_data.xml +++ /dev/null @@ -1,53 +0,0 @@ - - - - - Sample Statement - comma - dot - - - - Date - 0 - - date - %m/%d/%Y - - - Label - 1 - - name - - - Currency - 2 - - currency - - - Amount - 3 - - amount - - - Amount Currency - 4 - - amount_currency - - - Partner Name - 5 - - partner_name - - - Bank Account - 6 - - account_number - - diff --git a/account_bank_statement_import_txt_xlsx/migrations/12.0.2.0.0/post-migration.py b/account_bank_statement_import_txt_xlsx/migrations/12.0.2.0.0/post-migration.py new file mode 100644 index 00000000..49e0e6a4 --- /dev/null +++ b/account_bank_statement_import_txt_xlsx/migrations/12.0.2.0.0/post-migration.py @@ -0,0 +1,98 @@ +# Copyright 2020 Brainbean Apps (https://brainbeanapps.com) +# 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, + """ +WITH _mappings AS ( + SELECT + m.id, + l.field_to_assign, + l.name, + l.date_format + FROM + account_bank_statement_import_map AS m + RIGHT OUTER JOIN ( + SELECT + *, + ROW_NUMBER() OVER ( + PARTITION BY map_parent_id, field_to_assign + ORDER BY id ASC + ) AS row_number + FROM account_bank_statement_import_map_line + WHERE field_to_assign IS NOT NULL + ) AS l ON m.id = l.map_parent_id AND l.row_number = 1 +) + +INSERT INTO account_bank_statement_import_sheet_mapping ( + name, + float_thousands_sep, + float_decimal_sep, + file_encoding, + delimiter, + quotechar, + timestamp_format, + timestamp_column, + amount_column, + original_currency_column, + original_amount_column, + description_column, + reference_column, + notes_column, + partner_name_column, + bank_account_column +) +SELECT + m.name, + m.float_thousands_sep, + m.float_decimal_sep, + m.file_encoding, + ( + CASE + WHEN m.delimiter='.' THEN 'dot' + WHEN m.delimiter=',' THEN 'comma' + WHEN m.delimiter=';' THEN 'semicolon' + WHEN m.delimiter='' THEN 'n/a' + WHEN m.delimiter='\t' THEN 'tab' + WHEN m.delimiter=' ' THEN 'space' + ELSE 'n/a' + END + ) AS delimiter, + m.quotechar, + COALESCE(_date.date_format, '%m/%d/%Y') AS timestamp_format, + COALESCE(_date.name, 'Date') AS timestamp_column, + COALESCE(_amount.name, 'Amount') AS amount_column, + _o_currency.name AS original_currency_column, + _o_amount.name AS original_amount_column, + _description.name AS description_column, + _ref.name AS reference_column, + _notes.name AS notes_column, + _p_name.name AS partner_name_column, + _bank_acc.name AS bank_account_column +FROM + account_bank_statement_import_map AS m +LEFT JOIN _mappings AS _date + ON m.id = _date.id AND _date.field_to_assign = 'date' +LEFT JOIN _mappings AS _description + ON m.id = _description.id AND _description.field_to_assign = 'name' +LEFT JOIN _mappings AS _o_currency + ON m.id = _o_currency.id AND _o_currency.field_to_assign = 'currency' +LEFT JOIN _mappings AS _amount + ON m.id = _amount.id AND _amount.field_to_assign = 'amount' +LEFT JOIN _mappings AS _o_amount + ON m.id = _o_amount.id AND _o_amount.field_to_assign = 'amount_currency' +LEFT JOIN _mappings AS _ref + ON m.id = _ref.id AND _ref.field_to_assign = 'ref' +LEFT JOIN _mappings AS _notes + ON m.id = _notes.id AND _notes.field_to_assign = 'note' +LEFT JOIN _mappings AS _p_name + ON m.id = _p_name.id AND _p_name.field_to_assign = 'partner_name' +LEFT JOIN _mappings AS _bank_acc + ON m.id = _bank_acc.id AND _bank_acc.field_to_assign = 'account_number'; + """ + ) diff --git a/account_bank_statement_import_txt_xlsx/models/__init__.py b/account_bank_statement_import_txt_xlsx/models/__init__.py index ff680f4c..cddf714a 100644 --- a/account_bank_statement_import_txt_xlsx/models/__init__.py +++ b/account_bank_statement_import_txt_xlsx/models/__init__.py @@ -1,2 +1,6 @@ -from . import account_bank_statement_import_txt_map +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from . import account_bank_statement_import_sheet_mapping +from . import account_bank_statement_import_sheet_parser +from . import account_bank_statement_import from . import account_journal diff --git a/account_bank_statement_import_txt_xlsx/models/account_bank_statement_import.py b/account_bank_statement_import_txt_xlsx/models/account_bank_statement_import.py new file mode 100644 index 00000000..dd869e9b --- /dev/null +++ b/account_bank_statement_import_txt_xlsx/models/account_bank_statement_import.py @@ -0,0 +1,33 @@ +# Copyright 2020 Brainbean Apps (https://brainbeanapps.com) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import api, fields, models + +import logging +_logger = logging.getLogger(__name__) + + +class AccountBankStatementImport(models.TransientModel): + _inherit = 'account.bank.statement.import' + + sheet_mapping_id = fields.Many2one( + string='Sheet mapping', + comodel_name='account.bank.statement.import.sheet.mapping', + ) + + @api.multi + def _parse_file(self, data_file): + self.ensure_one() + try: + Parser = self.env['account.bank.statement.import.sheet.parser'] + return Parser.parse( + self.sheet_mapping_id, + data_file, + self.filename + ) + except: + if self.env.context.get( + 'account_bank_statement_import_txt_xlsx_test'): + raise + _logger.warning('Sheet parser error', exc_info=True) + return super()._parse_file(data_file) diff --git a/account_bank_statement_import_txt_xlsx/models/account_bank_statement_import_sheet_mapping.py b/account_bank_statement_import_txt_xlsx/models/account_bank_statement_import_sheet_mapping.py new file mode 100644 index 00000000..2e91fdae --- /dev/null +++ b/account_bank_statement_import_txt_xlsx/models/account_bank_statement_import_sheet_mapping.py @@ -0,0 +1,188 @@ +# Copyright 2019 ForgeFlow, S.L. +# Copyright 2020 Brainbean Apps (https://brainbeanapps.com) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import api, fields, models + + +class AccountBankStatementImportSheetMapping(models.Model): + _name = 'account.bank.statement.import.sheet.mapping' + _description = 'Account Bank Statement Import Sheet Mapping' + + name = fields.Char( + required=True, + ) + float_thousands_sep = fields.Selection( + string='Thousands Separator', + selection=[ + ('dot', 'dot (.)'), + ('comma', 'comma (,)'), + ('none', 'none'), + ], + default='dot', + ) + float_decimal_sep = fields.Selection( + string='Decimals Separator', + selection=[ + ('dot', 'dot (.)'), + ('comma', 'comma (,)'), + ('none', 'none'), + ], + default='comma', + ) + file_encoding = fields.Selection( + string='Encoding', + selection=[ + ('utf-8', 'UTF-8'), + ('utf-8-sig', 'UTF-8 (with BOM)'), + ('utf-16', 'UTF-16'), + ('utf-16-sig', 'UTF-16 (with BOM)'), + ('windows-1252', 'Western (Windows-1252)'), + ('iso-8859-1', 'Western (Latin-1 / ISO 8859-1)'), + ('iso-8859-2', 'Central European (Latin-2 / ISO 8859-2)'), + ('iso-8859-4', 'Baltic (Latin-4 / ISO 8859-4)'), + ('big5', 'Traditional Chinese (big5)'), + ('gb18030', 'Unified Chinese (gb18030)'), + ('shift_jis', 'Japanese (Shift JIS)'), + ('windows-1251', 'Cyrillic (Windows-1251)'), + ('koi8_r', 'Cyrillic (KOI8-R)'), + ('koi8_u', 'Cyrillic (KOI8-U)'), + ], + default='utf-8', + ) + delimiter = fields.Selection( + string='Delimiter', + selection=[ + ('dot', 'dot (.)'), + ('comma', 'comma (,)'), + ('semicolon', 'semicolon (;)'), + ('tab', 'tab'), + ('space', 'space'), + ('n/a', 'N/A'), + ], + default='comma', + ) + quotechar = fields.Char( + string='Text qualifier', + size=1, + default='"', + ) + timestamp_format = fields.Char( + string='Timestamp Format', + required=True, + ) + timestamp_column = fields.Char( + string='Timestamp column', + required=True, + ) + currency_column = fields.Char( + string='Currency column', + help=( + 'In case statement is multi-currency, column to get currency of ' + 'transaction from' + ), + ) + amount_column = fields.Char( + string='Amount column', + required=True, + help='Amount of transaction in journal\'s currency', + ) + balance_column = fields.Char( + string='Balance column', + help='Balance after transaction in journal\'s currency', + ) + original_currency_column = fields.Char( + string='Original currency column', + help=( + 'In case statement provides original currency for transactions ' + 'with automatic currency conversion, column to get original ' + 'currency of transaction from' + ), + ) + original_amount_column = fields.Char( + string='Original amount column', + help=( + 'In case statement provides original currency for transactions ' + 'with automatic currency conversion, column to get original ' + 'transaction amount in original transaction currency from' + ), + ) + debit_credit_column = fields.Char( + string='Debit/credit column', + help=( + 'Some statement formats use absolute amount value and indicate sign' + 'of the transaction by specifying if it was a debit or a credit one' + ), + ) + debit_value = fields.Char( + string='Debit value', + help='Value of debit/credit column that indicates if it\'s a debit', + default='D', + ) + credit_value = fields.Char( + string='Credit value', + help='Value of debit/credit column that indicates if it\'s a credit', + default='C', + ) + transaction_id_column = fields.Char( + string='Unique transaction ID column', + ) + description_column = fields.Char( + string='Description column', + ) + notes_column = fields.Char( + string='Notes column', + ) + reference_column = fields.Char( + string='Reference column', + ) + partner_name_column = fields.Char( + string='Partner Name column', + ) + bank_name_column = fields.Char( + string='Bank Name column', + help='Partner\'s bank', + ) + bank_account_column = fields.Char( + string='Bank Account column', + help='Partner\'s bank account', + ) + + @api.onchange('float_thousands_sep') + def onchange_thousands_separator(self): + if 'dot' == self.float_thousands_sep == self.float_decimal_sep: + self.float_decimal_sep = 'comma' + elif 'comma' == self.float_thousands_sep == self.float_decimal_sep: + self.float_decimal_sep = 'dot' + + @api.onchange('float_decimal_sep') + def onchange_decimal_separator(self): + if 'dot' == self.float_thousands_sep == self.float_decimal_sep: + self.float_thousands_sep = 'comma' + elif 'comma' == self.float_thousands_sep == self.float_decimal_sep: + self.float_thousands_sep = 'dot' + + @api.multi + def _get_float_separators(self): + self.ensure_one() + separators = { + 'dot': '.', + 'comma': ',', + 'none': '', + } + return (separators[self.float_thousands_sep], + separators[self.float_decimal_sep]) + + @api.model + def _decode_column_delimiter_character(self, delimiter): + return ({ + 'dot': '.', + 'comma': ',', + 'semicolon': ';', + 'tab': '\t', + 'space': ' ', + }).get(delimiter) + + @api.multi + def _get_column_delimiter_character(self): + return self._decode_column_delimiter_character(self.delimiter) diff --git a/account_bank_statement_import_txt_xlsx/models/account_bank_statement_import_sheet_parser.py b/account_bank_statement_import_txt_xlsx/models/account_bank_statement_import_sheet_parser.py new file mode 100644 index 00000000..89257845 --- /dev/null +++ b/account_bank_statement_import_txt_xlsx/models/account_bank_statement_import_sheet_parser.py @@ -0,0 +1,331 @@ +# Copyright 2019 ForgeFlow, S.L. +# Copyright 2020 Brainbean Apps (https://brainbeanapps.com) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import api, models, _ + +from datetime import datetime +from decimal import Decimal +from io import StringIO +from os import path +import itertools + +import logging +_logger = logging.getLogger(__name__) + +try: + from csv import reader + import xlrd + from xlrd.xldate import xldate_as_datetime +except (ImportError, IOError) as err: # pragma: no cover + _logger.error(err) + + +class AccountBankStatementImportSheetParser(models.TransientModel): + _name = 'account.bank.statement.import.sheet.parser' + _description = 'Account Bank Statement Import Sheet Parser' + + @api.model + def parse_header(self, data_file, encoding, csv_options): + 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) + return [str(value) for value in values] + except xlrd.XLRDError: + pass + + data = StringIO(data_file.decode(encoding or 'utf-8')) + csv_data = reader(data, **csv_options) + return list(next(csv_data)) + + @api.model + def parse(self, mapping, data_file, filename): + journal = self.env['account.journal'].browse( + self.env.context.get('journal_id') + ) + currency_code = ( + journal.currency_id or journal.company_id.currency_id + ).name + account_number = journal.bank_account_id.acc_number + + name = _('%s: %s') % ( + journal.code, + path.basename(filename), + ) + lines = self._parse_lines(mapping, data_file, currency_code) + if not lines: + return currency_code, account_number, [{ + 'name': name, + 'transactions': [], + }] + + lines = list(sorted( + lines, + key=lambda line: line['timestamp'] + )) + first_line = lines[0] + last_line = lines[-1] + data = { + 'name': name, + 'date': first_line['timestamp'].date(), + } + + if mapping.balance_column: + balance_start = first_line['balance'] + balance_start -= first_line['amount'] + balance_end = last_line['balance'] + data.update({ + 'balance_start': float(balance_start), + 'balance_end_real': float(balance_end), + }) + + transactions = list(itertools.chain.from_iterable(map( + lambda line: self._convert_line_to_transactions(line), + lines + ))) + data.update({ + 'transactions': transactions, + }) + + return currency_code, account_number, [data] + + def _parse_lines(self, mapping, data_file, currency_code): + try: + workbook = xlrd.open_workbook( + file_contents=data_file, + encoding_override=( + mapping.file_encoding if mapping.file_encoding else None + ), + ) + csv_or_xlsx = (workbook, workbook.sheet_by_index(0),) + except xlrd.XLRDError: + csv_options = {} + csv_delimiter = mapping._get_column_delimiter_character() + if csv_delimiter: + csv_options['delimiter'] = csv_delimiter + if mapping.quotechar: + csv_options['quotechar'] = mapping.quotechar + csv_or_xlsx = reader( + StringIO(data_file.decode(mapping.file_encoding or 'utf-8')), + **csv_options + ) + + if isinstance(csv_or_xlsx, tuple): + header = [str(value) for value in csv_or_xlsx[1].row_values(0)] + else: + header = list(next(csv_or_xlsx)) + timestamp_column = header.index(mapping.timestamp_column) + currency_column = header.index(mapping.currency_column) \ + if mapping.currency_column else None + amount_column = header.index(mapping.amount_column) + balance_column = header.index(mapping.balance_column) \ + if mapping.balance_column else None + original_currency_column = ( + header.index(mapping.original_currency_column) + if mapping.original_currency_column else None + ) + original_amount_column = ( + header.index(mapping.original_amount_column) + if mapping.original_amount_column else None + ) + debit_credit_column = header.index(mapping.debit_credit_column) \ + if mapping.debit_credit_column else None + transaction_id_column = header.index(mapping.transaction_id_column) \ + if mapping.transaction_id_column else None + description_column = header.index(mapping.description_column) \ + if mapping.description_column else None + notes_column = header.index(mapping.notes_column) \ + if mapping.notes_column else None + reference_column = header.index(mapping.reference_column) \ + if mapping.reference_column else None + partner_name_column = header.index(mapping.partner_name_column) \ + if mapping.partner_name_column else None + bank_name_column = header.index(mapping.bank_name_column) \ + if mapping.bank_name_column else None + bank_account_column = header.index(mapping.bank_account_column) \ + if mapping.bank_account_column else None + + if isinstance(csv_or_xlsx, tuple): + rows = range(1, csv_or_xlsx[1].nrows) + else: + rows = csv_or_xlsx + + lines = [] + for row in rows: + 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)): + 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: + values = list(row) + + timestamp = values[timestamp_column] + currency = values[currency_column] \ + if currency_column is not None else currency_code + amount = values[amount_column] + balance = values[balance_column] \ + if balance_column is not None else None + original_currency = values[original_currency_column] \ + if original_currency_column is not None else None + original_amount = values[original_amount_column] \ + if original_amount_column is not None else None + debit_credit = values[debit_credit_column] \ + if debit_credit_column is not None else None + transaction_id = values[transaction_id_column] \ + if transaction_id_column is not None else None + description = values[description_column] \ + if description_column is not None else None + notes = values[notes_column] \ + if notes_column is not None else None + reference = values[reference_column] \ + if reference_column is not None else None + partner_name = values[partner_name_column] \ + if partner_name_column is not None else None + bank_name = values[bank_name_column] \ + if bank_name_column is not None else None + bank_account = values[bank_account_column] \ + if bank_account_column is not None else None + + if currency != currency_code: + continue + + if isinstance(timestamp, str): + timestamp = datetime.strptime( + timestamp, + mapping.timestamp_format + ) + + amount = self._parse_decimal(amount, mapping) + if balance is not None: + balance = self._parse_decimal(balance, mapping) + + if debit_credit is not None: + amount = amount.copy_abs() + if debit_credit == mapping.debit_value: + amount = -amount + + if original_currency is None: + original_currency = currency + original_amount = amount + elif original_currency == currency: + original_amount = amount + + original_amount = self._parse_decimal(original_amount, mapping) + + line = { + 'timestamp': timestamp, + 'amount': amount, + 'currency': currency, + 'original_amount': original_amount, + 'original_currency': original_currency, + } + if balance is not None: + line['balance'] = balance + if transaction_id is not None: + line['transaction_id'] = transaction_id + if description is not None: + line['description'] = description + if notes is not None: + line['notes'] = notes + if reference is not None: + line['reference'] = reference + if partner_name is not None: + line['partner_name'] = partner_name + if bank_name is not None: + line['bank_name'] = bank_name + if bank_account is not None: + line['bank_account'] = bank_account + lines.append(line) + return lines + + @api.model + def _convert_line_to_transactions(self, line): + """Hook for extension""" + timestamp = line['timestamp'] + amount = line['amount'] + currency = line['currency'] + original_amount = line['original_amount'] + original_currency = line['original_currency'] + transaction_id = line.get('transaction_id') + description = line.get('description') + notes = line.get('notes') + reference = line.get('reference') + partner_name = line.get('partner_name') + bank_name = line.get('bank_name') + bank_account = line.get('bank_account') + + transaction = { + 'date': timestamp, + 'amount': str(amount), + } + if currency != original_currency: + original_currency = self.env['res.currency'].search( + [('name', '=', original_currency)], + limit=1, + ) + if original_currency: + transaction.update({ + 'amount_currency': str(original_amount), + 'currency_id': original_currency.id, + }) + + if transaction_id: + transaction['unique_import_id'] = '%s-%s' % ( + transaction_id, + int(timestamp.timestamp()), + ) + + transaction['name'] = description or _('N/A') + if reference: + transaction['ref'] = reference + + note = '' + if bank_name: + note += _('Bank: %s; ') % ( + bank_name, + ) + if bank_account: + note += _('Account: %s; ') % ( + bank_account, + ) + if transaction_id: + note += _('Transaction ID: %s; ') % ( + transaction_id, + ) + if note and notes: + note = '%s\n%s' % ( + note, + note.strip(), + ) + elif note: + note = note.strip() + if note: + transaction['note'] = note + + if partner_name: + transaction['partner_name'] = partner_name + if bank_account: + transaction['account_number'] = bank_account + + return [transaction] + + @api.model + def _parse_decimal(self, value, mapping): + if isinstance(value, Decimal): + return value + elif isinstance(value, float): + return Decimal(value) + thousands, decimal = mapping._get_float_separators() + value = value.replace(thousands, '') + value = value.replace(decimal, '.') + return Decimal(value) diff --git a/account_bank_statement_import_txt_xlsx/models/account_bank_statement_import_txt_map.py b/account_bank_statement_import_txt_xlsx/models/account_bank_statement_import_txt_map.py deleted file mode 100644 index 9a6bc07f..00000000 --- a/account_bank_statement_import_txt_xlsx/models/account_bank_statement_import_txt_map.py +++ /dev/null @@ -1,136 +0,0 @@ -# Copyright 2019 Tecnativa - Vicent Cubells -# Copyright 2019 Eficent Business and IT Consulting Services, S.L. -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). - -from odoo import fields, models, api - - -class AccountBankStatementImportTxtMap(models.Model): - _name = 'account.bank.statement.import.map' - _description = 'Account Bank Statement Import Txt Map' - - name = fields.Char( - required=True, - ) - map_line_ids = fields.One2many( - comodel_name='account.bank.statement.import.map.line', - inverse_name='map_parent_id', - string="Map lines", - required=True, - copy=True, - ) - float_thousands_sep = fields.Selection( - [('dot', 'dot (.)'), - ('comma', 'comma (,)'), - ('none', 'none'), - ], - string='Thousands separator', - # forward compatibility: this was the value assumed - # before the field was added. - default='dot', - required=True - ) - float_decimal_sep = fields.Selection( - [('dot', 'dot (.)'), - ('comma', 'comma (,)'), - ('none', 'none'), - ], - string='Decimals separator', - # forward compatibility: this was the value assumed - # before the field was added. - default='comma', - required=True - ) - file_encoding = fields.Selection( - string='Encoding', - selection=[ - ('utf-8', 'UTF-8'), - ('utf-16 ', 'UTF-16'), - ('windows-1252', 'Windows-1252'), - ('latin1', 'latin1'), - ('latin2', 'latin2'), - ('big5', 'big5'), - ('gb18030', 'gb18030'), - ('shift_jis', 'shift_jis'), - ('windows-1251', 'windows-1251'), - ('koir8_r', 'koir9_r'), - ], - default='utf-8', - ) - delimiter = fields.Selection( - string='Separated by', - selection=[ - ('.', 'dot (.)'), - (',', 'comma (,)'), - (';', 'semicolon (;)'), - ('', 'none'), - ('\t', 'Tab'), - (' ', 'Space'), - ], - default=',', - ) - quotechar = fields.Char(string='String delimiter', size=1, - default='"') - - @api.onchange('float_thousands_sep') - def onchange_thousands_separator(self): - if 'dot' == self.float_thousands_sep == self.float_decimal_sep: - self.float_decimal_sep = 'comma' - elif 'comma' == self.float_thousands_sep == self.float_decimal_sep: - self.float_decimal_sep = 'dot' - - @api.onchange('float_decimal_sep') - def onchange_decimal_separator(self): - if 'dot' == self.float_thousands_sep == self.float_decimal_sep: - self.float_thousands_sep = 'comma' - elif 'comma' == self.float_thousands_sep == self.float_decimal_sep: - self.float_thousands_sep = 'dot' - - def _get_separators(self): - separators = {'dot': '.', - 'comma': ',', - 'none': '', - } - return (separators[self.float_thousands_sep], - separators[self.float_decimal_sep]) - - -class AccountBankStatementImportTxtMapLine(models.Model): - _name = 'account.bank.statement.import.map.line' - _description = 'Account Bank Statement Import Txt Map Line' - _order = "sequence asc, id asc" - - sequence = fields.Integer( - string="Field number", - required=True, - ) - name = fields.Char( - string="Header Name", - required=True, - ) - map_parent_id = fields.Many2one( - comodel_name='account.bank.statement.import.map', - required=True, - ondelete='cascade', - ) - field_to_assign = fields.Selection( - selection=[ - ('date', 'Date'), - ('name', 'Label'), - ('currency', 'Currency'), - ('amount', 'Amount in the journal currency'), - ('amount_currency', 'Amount in foreign currency'), - ('ref', 'Reference'), - ('note', 'Notes'), - ('partner_name', 'Name'), - ('account_number', 'Bank Account Number'), - ], - string="Statement Field to Assign", - ) - date_format = fields.Selection( - selection=[ - ('%d/%m/%Y', 'i.e. 15/12/2019'), - ('%m/%d/%Y', 'i.e. 12/15/2019'), - ], - string="Date Format", - ) diff --git a/account_bank_statement_import_txt_xlsx/models/account_journal.py b/account_bank_statement_import_txt_xlsx/models/account_journal.py index 261df272..9281be07 100644 --- a/account_bank_statement_import_txt_xlsx/models/account_journal.py +++ b/account_bank_statement_import_txt_xlsx/models/account_journal.py @@ -1,19 +1,14 @@ -# Copyright 2019 Tecnativa - Vicent Cubells -# Copyright 2019 Eficent Business and IT Consulting Services, S.L. -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +# Copyright 2019 ForgeFlow, S.L. +# Copyright 2020 Brainbean Apps (https://brainbeanapps.com) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). -from odoo import fields, models +from odoo import models class AccountJournal(models.Model): - _inherit = "account.journal" - - statement_import_txt_map_id = fields.Many2one( - comodel_name='account.bank.statement.import.map', - string='Statement Import Txt Map', - ) + _inherit = 'account.journal' def _get_bank_statements_available_import_formats(self): res = super()._get_bank_statements_available_import_formats() - res.append('Txt') + res.append('TXT/CSV/XSLX') return res diff --git a/account_bank_statement_import_txt_xlsx/readme/CONFIGURE.rst b/account_bank_statement_import_txt_xlsx/readme/CONFIGURE.rst index 5c81f99c..ca7a8b50 100644 --- a/account_bank_statement_import_txt_xlsx/readme/CONFIGURE.rst +++ b/account_bank_statement_import_txt_xlsx/readme/CONFIGURE.rst @@ -1,12 +1,4 @@ -* Create or go to a bank journal where you want to import the txt statement. -* Edit that journal and set a Txt map in **Statement Import Map** section in **Advanced - Settings** tab. +To create TXT/CSV/XLSX statement sheet columns mapping: -* Now you can import Text based statements in that journal. - -Note: if existent Txt Map does not fit to your file to import, you can -create another map in **Invoicing > Configuration > Accounting > -Statement Import Map**. - -You can import headers from any Txt file in **Action > Create Map -Lines** and set every line with which field of statement have to match. +#. Open *Invoicing > Configuration > Accounting > Statement Sheet Mappings* +#. Create mapping(s) according to your online banking software statement format diff --git a/account_bank_statement_import_txt_xlsx/readme/CONTRIBUTORS.rst b/account_bank_statement_import_txt_xlsx/readme/CONTRIBUTORS.rst index 0e583091..04a803d5 100644 --- a/account_bank_statement_import_txt_xlsx/readme/CONTRIBUTORS.rst +++ b/account_bank_statement_import_txt_xlsx/readme/CONTRIBUTORS.rst @@ -5,3 +5,4 @@ * Victor M.M. Torres * Eficent (https://www.eficent.com) * Jordi Ballester Alomar +* Alexey Pelykh diff --git a/account_bank_statement_import_txt_xlsx/readme/HISTORY.rst b/account_bank_statement_import_txt_xlsx/readme/HISTORY.rst new file mode 100644 index 00000000..fe7b1add --- /dev/null +++ b/account_bank_statement_import_txt_xlsx/readme/HISTORY.rst @@ -0,0 +1,7 @@ +12.0.2.0.0 +~~~~~~~~~~ + +* [BREAKING] New mapping, please review mappings after upgrade. +* [BREAKING] Different bank accounts have to be used per each currency. +* [ADD] Support for both Statement and Activity reports. +* [ADD] Separate fee and currency exchange parsing. diff --git a/account_bank_statement_import_txt_xlsx/readme/USAGE.rst b/account_bank_statement_import_txt_xlsx/readme/USAGE.rst index 2a8fb28a..1ed86b40 100644 --- a/account_bank_statement_import_txt_xlsx/readme/USAGE.rst +++ b/account_bank_statement_import_txt_xlsx/readme/USAGE.rst @@ -1,3 +1,4 @@ To use this module, you need to: -#. Go to your bank online and download your Bank Statement in TXT/CSV or XLSX format. +#. Get statement in TXT/CSV or XLSX from your online banking software +#. Go to Odoo and and import the statement file, selecting corresponding format diff --git a/account_bank_statement_import_txt_xlsx/security/ir.model.access.csv b/account_bank_statement_import_txt_xlsx/security/ir.model.access.csv index b282f31b..1e4598ba 100644 --- a/account_bank_statement_import_txt_xlsx/security/ir.model.access.csv +++ b/account_bank_statement_import_txt_xlsx/security/ir.model.access.csv @@ -1,3 +1,3 @@ "id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink" -access_account_bank_statement_import_map,map manager,model_account_bank_statement_import_map,account.group_account_manager,1,1,1,1 -access_account_bank_statement_import_map_line,map line manager,model_account_bank_statement_import_map_line,account.group_account_manager,1,1,1,1 +access_account_bank_statement_import_sheet_mapping_manager,account.bank.statement.import.sheet.mapping:account.group_account_manager,model_account_bank_statement_import_sheet_mapping,account.group_account_manager,1,1,1,1 +access_account_bank_statement_import_sheet_mapping_user,account.bank.statement.import.sheet.mapping:account.group_account_user,model_account_bank_statement_import_sheet_mapping,account.group_account_user,1,0,0,0 diff --git a/account_bank_statement_import_txt_xlsx/tests/__init__.py b/account_bank_statement_import_txt_xlsx/tests/__init__.py index 3260f753..3073e38a 100644 --- a/account_bank_statement_import_txt_xlsx/tests/__init__.py +++ b/account_bank_statement_import_txt_xlsx/tests/__init__.py @@ -1 +1,3 @@ -from . import test_txt_statement_import +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from . import test_account_bank_statement_import_txt_xlsx diff --git a/account_bank_statement_import_txt_xlsx/tests/fixtures/balance.csv b/account_bank_statement_import_txt_xlsx/tests/fixtures/balance.csv new file mode 100644 index 00000000..c6a000f0 --- /dev/null +++ b/account_bank_statement_import_txt_xlsx/tests/fixtures/balance.csv @@ -0,0 +1,3 @@ +"Date","Label","Amount","Balance","Partner Name","Bank Account" +"12/15/2018","Your best supplier","-33.50","-23.50","John Doe","123456789" +"12/15/2018","Your payment","1,533.50","1,510.00","Azure Interior","" diff --git a/account_bank_statement_import_txt_xlsx/tests/fixtures/debit_credit.csv b/account_bank_statement_import_txt_xlsx/tests/fixtures/debit_credit.csv new file mode 100644 index 00000000..936e32aa --- /dev/null +++ b/account_bank_statement_import_txt_xlsx/tests/fixtures/debit_credit.csv @@ -0,0 +1,3 @@ +"Date","Label","Amount","D/C","Balance","Partner Name","Bank Account" +"12/15/2018","Your best supplier","33.50","D","-23.50","John Doe","123456789" +"12/15/2018","Your payment","-1,533.50","C","1,510.00","Azure Interior","" diff --git a/account_bank_statement_import_txt_xlsx/tests/fixtures/empty_statement_en.csv b/account_bank_statement_import_txt_xlsx/tests/fixtures/empty_statement_en.csv new file mode 100644 index 00000000..696c59ba --- /dev/null +++ b/account_bank_statement_import_txt_xlsx/tests/fixtures/empty_statement_en.csv @@ -0,0 +1 @@ +"Date","Label","Currency","Amount","Amount Currency","Partner Name","Bank Account" diff --git a/account_bank_statement_import_txt_xlsx/tests/fixtures/empty_statement_en.xlsx b/account_bank_statement_import_txt_xlsx/tests/fixtures/empty_statement_en.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..5ca47652c2d0e6661f9bf8f0bc5ee7e0ab5e21e7 GIT binary patch literal 9041 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∨jcF2*vX3Cti3NH(fX`Uw7mVfXh6|N3UVmh=}bGPjvg1rMeOry z4`Lb7a^H*NSm~-NeFd~+xz=`m-2+65Fl6dnAEtaGu!`!GYM&GI|aB;^v%@aCjgX-#` zM`16J>q3k7aK=^DPK_oG%c7w}%~e!FzN02Vb%+?C)w%`+M)ng_CAPAe7UnIxIdlkA zSdqRvjJkgatRR&l>JaJpT(u)f%$3|Y91X*O)KQ#jB&xfXW98kjd{67VT60eUSc1N& zISOt?2nO_(a|+uR!LSgTqDAwGAOY{+k{xHBD|J9+SfG~7J3jxBId(kKnQHH4v?3v8 z48O`&ID#5Ug{;(+rqn-ly6$rOYEY1J6TC&=V3^1@EX6srx?sxkSSRWpx4(O8+#@Jo zWttJ6Vj*+E)-w4Kkb_KKzBQgmMHXzfn?Ej zDd%UED`&eqJQw2p{k;SI!9ByRbv*!#I4>N1W;w0k$yPQ;?Jqlb>*qwkw_rQ)6)J-# zz=G#|BW6y5lq?a7G4WMbw1fxP$E z%+{|8e)Y!xP*8&VpAY|=Z~m*6U;T + + account.bank.statement.import account.bank.statement.import -
  • Txt/XLSX file with Template:
  • +
  • + TXT/CSV/XLSX mapping: +
  • diff --git a/account_bank_statement_import_txt_xlsx/views/account_bank_statement_import_sheet_mapping.xml b/account_bank_statement_import_txt_xlsx/views/account_bank_statement_import_sheet_mapping.xml new file mode 100644 index 00000000..38eaa92b --- /dev/null +++ b/account_bank_statement_import_txt_xlsx/views/account_bank_statement_import_sheet_mapping.xml @@ -0,0 +1,82 @@ + + + + + + account.bank.statement.import.sheet.mapping.form + account.bank.statement.import.sheet.mapping + + + + + + + + + account.bank.statement.import.sheet.mapping.tree + account.bank.statement.import.sheet.mapping + +
    + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    +
    +
    + + + Statement Sheet Mappings + account.bank.statement.import.sheet.mapping + form + tree,form + + + + +
    diff --git a/account_bank_statement_import_txt_xlsx/views/account_journal_views.xml b/account_bank_statement_import_txt_xlsx/views/account_journal_views.xml deleted file mode 100644 index b7be6cd2..00000000 --- a/account_bank_statement_import_txt_xlsx/views/account_journal_views.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - account.journal - - - - - - - - - - - diff --git a/account_bank_statement_import_txt_xlsx/views/txt_map_views.xml b/account_bank_statement_import_txt_xlsx/views/txt_map_views.xml deleted file mode 100644 index c7ab7550..00000000 --- a/account_bank_statement_import_txt_xlsx/views/txt_map_views.xml +++ /dev/null @@ -1,70 +0,0 @@ - - - - - account.bank.statement.import.map - - - - - - - - - account.bank.statement.import.map - -
    - - - - - - - - - - - - -
    - - - account.bank.statement.import.map.line - - - - - - - - - - - - account.bank.statement.import.map.line - -
    - - - - - - - -
    -
    -
    - - - Statement Import Mapping - account.bank.statement.import.map - form - tree,form - - - - -
    diff --git a/account_bank_statement_import_txt_xlsx/wizards/__init__.py b/account_bank_statement_import_txt_xlsx/wizards/__init__.py index ec27082d..2cb91b64 100644 --- a/account_bank_statement_import_txt_xlsx/wizards/__init__.py +++ b/account_bank_statement_import_txt_xlsx/wizards/__init__.py @@ -1,2 +1,3 @@ -from . import create_map_lines_from_file -from . import account_bank_statement_import_txt +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from . import account_bank_statement_import_sheet_mapping_wizard diff --git a/account_bank_statement_import_txt_xlsx/wizards/account_bank_statement_import_sheet_mapping_wizard.py b/account_bank_statement_import_txt_xlsx/wizards/account_bank_statement_import_sheet_mapping_wizard.py new file mode 100644 index 00000000..115f1b73 --- /dev/null +++ b/account_bank_statement_import_txt_xlsx/wizards/account_bank_statement_import_sheet_mapping_wizard.py @@ -0,0 +1,191 @@ +# Copyright 2019 ForgeFlow, S.L. +# Copyright 2020 Brainbean Apps (https://brainbeanapps.com) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import api, fields, models, _ + +from base64 import b64decode +import json +from os import path + + +class AccountBankStatementImportSheetMappingWizard(models.TransientModel): + _name = 'account.bank.statement.import.sheet.mapping.wizard' + _description = 'Account Bank Statement Import Sheet Mapping Wizard' + _inherit = ['multi.step.wizard.mixin'] + + data_file = fields.Binary( + string='Bank Statement File', + required=True, + ) + filename = fields.Char() + header = fields.Char() + file_encoding = fields.Selection( + string='Encoding', + selection=lambda self: self._selection_file_encoding(), + ) + delimiter = fields.Selection( + string='Delimiter', + selection=lambda self: self._selection_delimiter(), + ) + quotechar = fields.Char( + string='Text qualifier', + size=1, + ) + timestamp_column = fields.Char( + string='Timestamp column', + ) + currency_column = fields.Char( + string='Currency column', + help=( + 'In case statement is multi-currency, column to get currency of ' + 'transaction from' + ), + ) + amount_column = fields.Char( + string='Amount column', + help='Amount of transaction in journal\'s currency', + ) + balance_column = fields.Char( + string='Balance column', + help='Balance after transaction in journal\'s currency', + ) + original_currency_column = fields.Char( + string='Original currency column', + help=( + 'In case statement provides original currency for transactions ' + 'with automatic currency conversion, column to get original ' + 'currency of transaction from' + ), + ) + original_amount_column = fields.Char( + string='Original amount column', + help=( + 'In case statement provides original currency for transactions ' + 'with automatic currency conversion, column to get original ' + 'transaction amount in original transaction currency from' + ), + ) + debit_credit_column = fields.Char( + string='Debit/credit column', + help=( + 'Some statement formats use absolute amount value and indicate sign' + 'of the transaction by specifying if it was a debit or a credit one' + ), + ) + debit_value = fields.Char( + string='Debit value', + help='Value of debit/credit column that indicates if it\'s a debit', + default='D', + ) + credit_value = fields.Char( + string='Credit value', + help='Value of debit/credit column that indicates if it\'s a credit', + default='C', + ) + transaction_id_column = fields.Char( + string='Unique transaction ID column', + ) + description_column = fields.Char( + string='Description column', + ) + notes_column = fields.Char( + string='Notes column', + ) + reference_column = fields.Char( + string='Reference column', + ) + partner_name_column = fields.Char( + string='Partner Name column', + ) + bank_name_column = fields.Char( + string='Bank Name column', + help='Partner\'s bank', + ) + bank_account_column = fields.Char( + string='Bank Account column', + help='Partner\'s bank account', + ) + + @api.model + def _selection_file_encoding(self): + return self.env['account.bank.statement.import.sheet.mapping']._fields[ + 'file_encoding' + ].selection + + @api.model + def _selection_delimiter(self): + return self.env['account.bank.statement.import.sheet.mapping']._fields[ + 'delimiter' + ].selection + + @api.onchange('data_file') + def _onchange_data_file(self): + Parser = self.env['account.bank.statement.import.sheet.parser'] + Mapping = self.env['account.bank.statement.import.sheet.mapping'] + if not self.data_file: + return + csv_options = {} + if self.delimiter: + csv_options['delimiter'] = \ + Mapping._decode_column_delimiter_character(self.delimiter) + if self.quotechar: + csv_options['quotechar'] = self.quotechar + header = Parser.parse_header( + b64decode(self.data_file), + self.file_encoding, + csv_options + ) + self.header = json.dumps(header) + + @api.model + def statement_columns(self): + header = self.env.context.get('header') + if not header: + return [] + return [(x, x) for x in json.loads(header)] + + @api.multi + def _get_mapping_values(self): + """Hook for extension""" + self.ensure_one() + return { + 'name': _('Mapping from %s') % path.basename(self.filename), + 'float_thousands_sep': 'comma', + 'float_decimal_sep': 'dot', + 'file_encoding': self.file_encoding, + 'delimiter': self.delimiter, + 'quotechar': self.quotechar, + 'timestamp_format': '%d/%m/%Y', + 'timestamp_column': self.timestamp_column, + 'currency_column': self.currency_column, + 'amount_column': self.amount_column, + 'balance_column': self.balance_column, + 'original_currency_column': self.original_currency_column, + 'original_amount_column': self.original_amount_column, + 'debit_credit_column': self.debit_credit_column, + 'debit_value': self.debit_value, + 'credit_value': self.credit_value, + 'transaction_id_column': self.transaction_id_column, + 'description_column': self.description_column, + 'notes_column': self.notes_column, + 'reference_column': self.reference_column, + 'partner_name_column': self.partner_name_column, + 'bank_name_column': self.bank_name_column, + 'bank_account_column': self.bank_account_column, + } + + @api.multi + def import_mapping(self): + self.ensure_one() + mapping = self.env['account.bank.statement.import.sheet.mapping']\ + .create(self._get_mapping_values()) + return { + 'type': 'ir.actions.act_window', + 'name': _('Imported Mapping'), + 'res_model': 'account.bank.statement.import.sheet.mapping', + 'res_id': mapping.id, + 'view_mode': 'form', + 'view_id': False, + 'target': 'new', + } diff --git a/account_bank_statement_import_txt_xlsx/wizards/account_bank_statement_import_sheet_mapping_wizard.xml b/account_bank_statement_import_txt_xlsx/wizards/account_bank_statement_import_sheet_mapping_wizard.xml new file mode 100644 index 00000000..9a5f372b --- /dev/null +++ b/account_bank_statement_import_txt_xlsx/wizards/account_bank_statement_import_sheet_mapping_wizard.xml @@ -0,0 +1,144 @@ + + + + + + account.bank.statement.import.sheet.mapping.wizard.form + account.bank.statement.import.sheet.mapping.wizard + primary + + + +

    Select a statement file to import mapping

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + btn-default + Cancel + + +