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..31620993 --- /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/13.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-13.0/bank-statement-import-13.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/13.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 +~~~~~~~ + +* ForgeFlow + +Contributors +~~~~~~~~~~~~ + +* Alexis de Lattre +* Sebastien BEAU +* Tecnativa (https://www.tecnativa.com) + * Vicent Cubells + * Victor M.M. Torres +* ForgeFlow (https://www.forgeflow.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..bd0c37ea --- /dev/null +++ b/account_bank_statement_import_txt_xlsx/__manifest__.py @@ -0,0 +1,28 @@ +# 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": "13.0.1.0.0", + "category": "Accounting", + "website": "https://github.com/OCA/bank-statement-import", + "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": ["csv", "xlrd"]}, + "data": [ + "security/ir.model.access.csv", + "data/map_data.xml", + "views/account_bank_statement_import_sheet_mapping.xml", + "views/account_bank_statement_import.xml", + "views/account_journal_views.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..04116d0b --- /dev/null +++ b/account_bank_statement_import_txt_xlsx/data/map_data.xml @@ -0,0 +1,26 @@ + + + + + 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/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..d48878bc --- /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 13.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..cddf714a --- /dev/null +++ b/account_bank_statement_import_txt_xlsx/models/__init__.py @@ -0,0 +1,6 @@ +# 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..cad3e106 --- /dev/null +++ b/account_bank_statement_import_txt_xlsx/models/account_bank_statement_import.py @@ -0,0 +1,36 @@ +# Copyright 2020 Brainbean Apps (https://brainbeanapps.com) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +import logging + +from odoo import fields, models + +_logger = logging.getLogger(__name__) + + +class AccountBankStatementImport(models.TransientModel): + _inherit = "account.bank.statement.import" + + def _get_default_mapping_id(self): + return ( + self.env["account.journal"] + .browse(self.env.context.get("journal_id")) + .default_sheet_mapping_id + ) + + sheet_mapping_id = fields.Many2one( + string="Sheet mapping", + comodel_name="account.bank.statement.import.sheet.mapping", + default=_get_default_mapping_id, + ) + + def _parse_file(self, data_file): + self.ensure_one() + try: + Parser = self.env["account.bank.statement.import.sheet.parser"] + return Parser.parse(data_file, self.sheet_mapping_id) + except BaseException: + 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..41fd8b6b --- /dev/null +++ b/account_bank_statement_import_txt_xlsx/models/account_bank_statement_import_sheet_mapping.py @@ -0,0 +1,149 @@ +# 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" + + 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) + + 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..61dd69cf --- /dev/null +++ b/account_bank_statement_import_txt_xlsx/models/account_bank_statement_import_sheet_parser.py @@ -0,0 +1,368 @@ +# Copyright 2019 ForgeFlow, S.L. +# Copyright 2020 Brainbean Apps (https://brainbeanapps.com) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +import itertools +import logging +from datetime import datetime +from decimal import Decimal +from io import StringIO + +from odoo import _, api, models + +_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, data_file, mapping): + 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 + + lines = self._parse_lines(mapping, data_file, currency_code) + if not lines: + return currency_code, account_number, [{"transactions": []}] + + lines = list(sorted(lines, key=lambda line: line["timestamp"])) + first_line = lines[0] + last_line = lines[-1] + data = { + "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): + columns = dict() + 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 = [value.strip() for value in next(csv_or_xlsx)] + columns["timestamp_column"] = header.index(mapping.timestamp_column) + columns["currency_column"] = ( + header.index(mapping.currency_column) if mapping.currency_column else None + ) + columns["amount_column"] = header.index(mapping.amount_column) + columns["balance_column"] = ( + header.index(mapping.balance_column) if mapping.balance_column else None + ) + columns["original_currency_column"] = ( + header.index(mapping.original_currency_column) + if mapping.original_currency_column + else None + ) + columns["original_amount_column"] = ( + header.index(mapping.original_amount_column) + if mapping.original_amount_column + else None + ) + columns["debit_credit_column"] = ( + header.index(mapping.debit_credit_column) + if mapping.debit_credit_column + else None + ) + columns["transaction_id_column"] = ( + header.index(mapping.transaction_id_column) + if mapping.transaction_id_column + else None + ) + columns["description_column"] = ( + header.index(mapping.description_column) + if mapping.description_column + else None + ) + columns["notes_column"] = ( + header.index(mapping.notes_column) if mapping.notes_column else None + ) + columns["reference_column"] = ( + header.index(mapping.reference_column) if mapping.reference_column else None + ) + columns["partner_name_column"] = ( + header.index(mapping.partner_name_column) + if mapping.partner_name_column + else None + ) + columns["bank_name_column"] = ( + header.index(mapping.bank_name_column) if mapping.bank_name_column else None + ) + columns["bank_account_column"] = ( + header.index(mapping.bank_account_column) + if mapping.bank_account_column + else None + ) + return self._parse_rows(mapping, currency_code, csv_or_xlsx, columns) + + def _parse_rows(self, mapping, currency_code, csv_or_xlsx, columns): # noqa: C901 + 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[columns["timestamp_column"]] + currency = ( + values[columns["currency_column"]] + if columns["currency_column"] is not None + else currency_code + ) + amount = values[columns["amount_column"]] + balance = ( + values[columns["balance_column"]] + if columns["balance_column"] is not None + else None + ) + original_currency = ( + values[columns["original_currency_column"]] + if columns["original_currency_column"] is not None + else None + ) + original_amount = ( + values[columns["original_amount_column"]] + if columns["original_amount_column"] is not None + else None + ) + debit_credit = ( + values[columns["debit_credit_column"]] + if columns["debit_credit_column"] is not None + else None + ) + transaction_id = ( + values[columns["transaction_id_column"]] + if columns["transaction_id_column"] is not None + else None + ) + description = ( + values[columns["description_column"]] + if columns["description_column"] is not None + else None + ) + notes = ( + values[columns["notes_column"]] + if columns["notes_column"] is not None + else None + ) + reference = ( + values[columns["reference_column"]] + if columns["reference_column"] is not None + else None + ) + partner_name = ( + values[columns["partner_name_column"]] + if columns["partner_name_column"] is not None + else None + ) + bank_name = ( + values[columns["bank_name_column"]] + if columns["bank_name_column"] is not None + else None + ) + bank_account = ( + values[columns["bank_account_column"]] + if columns["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"] = "{}-{}".format( + 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 = "{}\n{}".format(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_journal.py b/account_bank_statement_import_txt_xlsx/models/account_journal.py new file mode 100644 index 00000000..5636d600 --- /dev/null +++ b/account_bank_statement_import_txt_xlsx/models/account_journal.py @@ -0,0 +1,18 @@ +# 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 + + +class AccountJournal(models.Model): + _inherit = "account.journal" + + default_sheet_mapping_id = fields.Many2one( + comodel_name="account.bank.statement.import.sheet.mapping", + ) + + def _get_bank_statements_available_import_formats(self): + res = super()._get_bank_statements_available_import_formats() + 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 new file mode 100644 index 00000000..ca7a8b50 --- /dev/null +++ b/account_bank_statement_import_txt_xlsx/readme/CONFIGURE.rst @@ -0,0 +1,4 @@ +To create TXT/CSV/XLSX statement sheet columns mapping: + +#. 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 new file mode 100644 index 00000000..c10ac1de --- /dev/null +++ b/account_bank_statement_import_txt_xlsx/readme/CONTRIBUTORS.rst @@ -0,0 +1,9 @@ +* Alexis de Lattre +* Sebastien BEAU +* Tecnativa (https://www.tecnativa.com) + * Vicent Cubells + * Victor M.M. Torres +* ForgeFlow (https://www.forgeflow.com) + * Jordi Ballester Alomar + * Miquel Raïch Regué +* Alexey Pelykh 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/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 new file mode 100644 index 00000000..1ed86b40 --- /dev/null +++ b/account_bank_statement_import_txt_xlsx/readme/USAGE.rst @@ -0,0 +1,4 @@ +To use this module, you need to: + +#. 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 new file mode 100644 index 00000000..1e4598ba --- /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_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/static/description/icon.png b/account_bank_statement_import_txt_xlsx/static/description/icon.png new file mode 100644 index 00000000..3a0328b5 Binary files /dev/null and b/account_bank_statement_import_txt_xlsx/static/description/icon.png differ 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..719e6f1b --- /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

+
    +
  • ForgeFlow
  • +
+
+
+

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..3073e38a --- /dev/null +++ b/account_bank_statement_import_txt_xlsx/tests/__init__.py @@ -0,0 +1,3 @@ +# 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 00000000..5ca47652 Binary files /dev/null and b/account_bank_statement_import_txt_xlsx/tests/fixtures/empty_statement_en.xlsx differ diff --git a/account_bank_statement_import_txt_xlsx/tests/fixtures/multi_currency.csv b/account_bank_statement_import_txt_xlsx/tests/fixtures/multi_currency.csv new file mode 100644 index 00000000..c64bd180 --- /dev/null +++ b/account_bank_statement_import_txt_xlsx/tests/fixtures/multi_currency.csv @@ -0,0 +1,3 @@ +"Date","Label","Currency","Amount","Partner Name","Bank Account" +"12/15/2018","Your best supplier","USD","-33.50","John Doe","123456789" +"12/15/2018","Your payment","EUR","1,525.00","Azure Interior","" diff --git a/account_bank_statement_import_txt_xlsx/tests/fixtures/original_currency.csv b/account_bank_statement_import_txt_xlsx/tests/fixtures/original_currency.csv new file mode 100644 index 00000000..e025cf08 --- /dev/null +++ b/account_bank_statement_import_txt_xlsx/tests/fixtures/original_currency.csv @@ -0,0 +1,2 @@ +"Date","Label","Currency","Amount","Amount Currency","Partner Name","Bank Account" +"12/15/2018","Your payment","EUR","1,525.00","1,000.00","Azure Interior","" diff --git a/account_bank_statement_import_txt_xlsx/tests/fixtures/sample_statement_en.csv b/account_bank_statement_import_txt_xlsx/tests/fixtures/sample_statement_en.csv new file mode 100644 index 00000000..c50bfc83 --- /dev/null +++ b/account_bank_statement_import_txt_xlsx/tests/fixtures/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/fixtures/sample_statement_en.xlsx b/account_bank_statement_import_txt_xlsx/tests/fixtures/sample_statement_en.xlsx new file mode 100644 index 00000000..b3365b20 Binary files /dev/null and b/account_bank_statement_import_txt_xlsx/tests/fixtures/sample_statement_en.xlsx differ diff --git a/account_bank_statement_import_txt_xlsx/tests/test_account_bank_statement_import_txt_xlsx.py b/account_bank_statement_import_txt_xlsx/tests/test_account_bank_statement_import_txt_xlsx.py new file mode 100644 index 00000000..39568b53 --- /dev/null +++ b/account_bank_statement_import_txt_xlsx/tests/test_account_bank_statement_import_txt_xlsx.py @@ -0,0 +1,339 @@ +# 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 base64 import b64encode +from os import path + +from odoo import fields +from odoo.exceptions import UserError +from odoo.tests import common + + +class TestAccountBankStatementImportTxtXlsx(common.TransactionCase): + def setUp(self): + super().setUp() + + self.now = fields.Datetime.now() + self.currency_eur = self.env.ref("base.EUR") + self.currency_usd = self.env.ref("base.USD") + self.sample_statement_map = self.env.ref( + "account_bank_statement_import_txt_xlsx.sample_statement_map" + ) + self.AccountJournal = self.env["account.journal"] + self.AccountBankStatement = self.env["account.bank.statement"] + self.AccountBankStatementImport = self.env["account.bank.statement.import"] + self.AccountBankStatementImportSheetMapping = self.env[ + "account.bank.statement.import.sheet.mapping" + ] + self.AccountBankStatementImportSheetMappingWizard = self.env[ + "account.bank.statement.import.sheet.mapping.wizard" + ] + + def _data_file(self, filename, encoding=None): + mode = "rt" if encoding else "rb" + with open(path.join(path.dirname(__file__), filename), mode) as file: + data = file.read() + if encoding: + data = data.encode(encoding) + return b64encode(data) + + def test_import_csv_file(self): + journal = self.AccountJournal.create( + { + "name": "Bank", + "type": "bank", + "code": "BANK", + "currency_id": self.currency_usd.id, + } + ) + data = self._data_file("fixtures/sample_statement_en.csv", "utf-8") + wizard = self.AccountBankStatementImport.with_context( + journal_id=journal.id + ).create( + { + "attachment_ids": [ + (0, 0, {"name": "fixtures/sample_statement_en.csv", "datas": data}) + ], + "sheet_mapping_id": self.sample_statement_map.id, + } + ) + wizard.with_context( + account_bank_statement_import_txt_xlsx_test=True + ).import_file() + statement = self.AccountBankStatement.search([("journal_id", "=", journal.id)]) + self.assertEqual(len(statement), 1) + self.assertEqual(len(statement.line_ids), 2) + + def test_import_empty_csv_file(self): + journal = self.AccountJournal.create( + { + "name": "Bank", + "type": "bank", + "code": "BANK", + "currency_id": self.currency_usd.id, + } + ) + data = self._data_file("fixtures/empty_statement_en.csv", "utf-8") + wizard = self.AccountBankStatementImport.with_context( + journal_id=journal.id + ).create( + { + "attachment_ids": [ + (0, 0, {"name": "fixtures/empty_statement_en.csv", "datas": data}) + ], + "sheet_mapping_id": self.sample_statement_map.id, + } + ) + with self.assertRaises(UserError): + wizard.with_context( + account_bank_statement_import_txt_xlsx_test=True + ).import_file() + statement = self.AccountBankStatement.search([("journal_id", "=", journal.id)]) + self.assertEqual(len(statement), 0) + + def test_import_xlsx_file(self): + journal = self.AccountJournal.create( + { + "name": "Bank", + "type": "bank", + "code": "BANK", + "currency_id": self.currency_usd.id, + } + ) + data = self._data_file("fixtures/sample_statement_en.xlsx") + wizard = self.AccountBankStatementImport.with_context( + journal_id=journal.id + ).create( + { + "attachment_ids": [ + (0, 0, {"name": "fixtures/sample_statement_en.xlsx", "datas": data}) + ], + "sheet_mapping_id": self.sample_statement_map.id, + } + ) + wizard.with_context( + account_bank_statement_import_txt_xlsx_test=True + ).import_file() + statement = self.AccountBankStatement.search([("journal_id", "=", journal.id)]) + self.assertEqual(len(statement), 1) + self.assertEqual(len(statement.line_ids), 2) + + def test_import_empty_xlsx_file(self): + journal = self.AccountJournal.create( + { + "name": "Bank", + "type": "bank", + "code": "BANK", + "currency_id": self.currency_usd.id, + } + ) + data = self._data_file("fixtures/empty_statement_en.xlsx") + wizard = self.AccountBankStatementImport.with_context( + journal_id=journal.id + ).create( + { + "attachment_ids": [ + (0, 0, {"name": "fixtures/empty_statement_en.xlsx", "datas": data}) + ], + "sheet_mapping_id": self.sample_statement_map.id, + } + ) + with self.assertRaises(UserError): + wizard.with_context( + account_bank_statement_import_txt_xlsx_test=True + ).import_file() + statement = self.AccountBankStatement.search([("journal_id", "=", journal.id)]) + self.assertEqual(len(statement), 0) + + def test_mapping_import_wizard_xlsx(self): + with common.Form(self.AccountBankStatementImportSheetMappingWizard) as form: + attachment = self.env["ir.attachment"].create( + { + "name": "fixtures/empty_statement_en.xlsx", + "datas": self._data_file("fixtures/empty_statement_en.xlsx"), + } + ) + form.attachment_ids.add(attachment) + self.assertEqual(len(form.header), 90) + self.assertEqual( + len( + self.AccountBankStatementImportSheetMappingWizard.with_context( + header=form.header, + ).statement_columns() + ), + 7, + ) + form.timestamp_column = "Date" + form.amount_column = "Amount" + wizard = form.save() + wizard.import_mapping() + + def test_mapping_import_wizard_csv(self): + with common.Form(self.AccountBankStatementImportSheetMappingWizard) as form: + attachment = self.env["ir.attachment"].create( + { + "name": "fixtures/empty_statement_en.csv", + "datas": self._data_file("fixtures/empty_statement_en.csv"), + } + ) + form.attachment_ids.add(attachment) + self.assertEqual(len(form.header), 90) + self.assertEqual( + len( + self.AccountBankStatementImportSheetMappingWizard.with_context( + header=form.header, + ).statement_columns() + ), + 7, + ) + form.timestamp_column = "Date" + form.amount_column = "Amount" + wizard = form.save() + wizard.import_mapping() + + def test_original_currency(self): + journal = self.AccountJournal.create( + { + "name": "Bank", + "type": "bank", + "code": "BANK", + "currency_id": self.currency_usd.id, + } + ) + data = self._data_file("fixtures/original_currency.csv", "utf-8") + wizard = self.AccountBankStatementImport.with_context( + journal_id=journal.id + ).create( + { + "attachment_ids": [ + (0, 0, {"name": "fixtures/original_currency.csv", "datas": data}) + ], + "sheet_mapping_id": self.sample_statement_map.id, + } + ) + wizard.with_context( + account_bank_statement_import_txt_xlsx_test=True + ).import_file() + statement = self.AccountBankStatement.search([("journal_id", "=", journal.id)]) + self.assertEqual(len(statement), 1) + self.assertEqual(len(statement.line_ids), 1) + + line = statement.line_ids + self.assertEqual(line.currency_id, self.currency_eur) + self.assertEqual(line.amount_currency, 1000.0) + + def test_multi_currency(self): + journal = self.AccountJournal.create( + { + "name": "Bank", + "type": "bank", + "code": "BANK", + "currency_id": self.currency_usd.id, + } + ) + statement_map = self.sample_statement_map.copy( + { + "currency_column": "Currency", + "original_currency_column": None, + "original_amount_column": None, + } + ) + data = self._data_file("fixtures/multi_currency.csv", "utf-8") + wizard = self.AccountBankStatementImport.with_context( + journal_id=journal.id + ).create( + { + "attachment_ids": [ + (0, 0, {"name": "fixtures/multi_currency.csv", "datas": data}) + ], + "sheet_mapping_id": statement_map.id, + } + ) + wizard.with_context( + account_bank_statement_import_txt_xlsx_test=True + ).import_file() + statement = self.AccountBankStatement.search([("journal_id", "=", journal.id)]) + self.assertEqual(len(statement), 1) + self.assertEqual(len(statement.line_ids), 1) + + line = statement.line_ids + self.assertFalse(line.currency_id) + self.assertEqual(line.amount, -33.5) + + def test_balance(self): + journal = self.AccountJournal.create( + { + "name": "Bank", + "type": "bank", + "code": "BANK", + "currency_id": self.currency_usd.id, + } + ) + statement_map = self.sample_statement_map.copy( + { + "balance_column": "Balance", + "original_currency_column": None, + "original_amount_column": None, + } + ) + data = self._data_file("fixtures/balance.csv", "utf-8") + wizard = self.AccountBankStatementImport.with_context( + journal_id=journal.id + ).create( + { + "attachment_ids": [ + (0, 0, {"name": "fixtures/balance.csv", "datas": data}) + ], + "sheet_mapping_id": statement_map.id, + } + ) + wizard.with_context( + account_bank_statement_import_txt_xlsx_test=True + ).import_file() + statement = self.AccountBankStatement.search([("journal_id", "=", journal.id)]) + self.assertEqual(len(statement), 1) + self.assertEqual(len(statement.line_ids), 2) + self.assertEqual(statement.balance_start, 10.0) + self.assertEqual(statement.balance_end_real, 1510.0) + self.assertEqual(statement.balance_end, 1510.0) + + def test_debit_credit(self): + journal = self.AccountJournal.create( + { + "name": "Bank", + "type": "bank", + "code": "BANK", + "currency_id": self.currency_usd.id, + } + ) + statement_map = self.sample_statement_map.copy( + { + "balance_column": "Balance", + "original_currency_column": None, + "original_amount_column": None, + "debit_credit_column": "D/C", + "debit_value": "D", + "credit_value": "C", + } + ) + data = self._data_file("fixtures/debit_credit.csv", "utf-8") + wizard = self.AccountBankStatementImport.with_context( + journal_id=journal.id + ).create( + { + "attachment_ids": [ + (0, 0, {"name": "fixtures/debit_credit.csv", "datas": data}) + ], + "sheet_mapping_id": statement_map.id, + } + ) + wizard.with_context( + account_bank_statement_import_txt_xlsx_test=True + ).import_file() + statement = self.AccountBankStatement.search([("journal_id", "=", journal.id)]) + self.assertEqual(len(statement), 1) + self.assertEqual(len(statement.line_ids), 2) + self.assertEqual(statement.balance_start, 10.0) + self.assertEqual(statement.balance_end_real, 1510.0) + self.assertEqual(statement.balance_end, 1510.0) diff --git a/account_bank_statement_import_txt_xlsx/views/account_bank_statement_import.xml b/account_bank_statement_import_txt_xlsx/views/account_bank_statement_import.xml new file mode 100644 index 00000000..b3e6ba5f --- /dev/null +++ b/account_bank_statement_import_txt_xlsx/views/account_bank_statement_import.xml @@ -0,0 +1,23 @@ + + + + + account.bank.statement.import + account.bank.statement.import + + + +
  • + 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..162a24a6 --- /dev/null +++ b/account_bank_statement_import_txt_xlsx/views/account_bank_statement_import_sheet_mapping.xml @@ -0,0 +1,89 @@ + + + + + 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 + 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 new file mode 100644 index 00000000..92b17a96 --- /dev/null +++ b/account_bank_statement_import_txt_xlsx/views/account_journal_views.xml @@ -0,0 +1,17 @@ + + + + account.journal + + + + + + + + + + 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..2cb91b64 --- /dev/null +++ b/account_bank_statement_import_txt_xlsx/wizards/__init__.py @@ -0,0 +1,3 @@ +# 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..2919f7b0 --- /dev/null +++ b/account_bank_statement_import_txt_xlsx/wizards/account_bank_statement_import_sheet_mapping_wizard.py @@ -0,0 +1,175 @@ +# Copyright 2019 ForgeFlow, S.L. +# Copyright 2020 Brainbean Apps (https://brainbeanapps.com) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +import json +from base64 import b64decode +from os import path + +from odoo import _, api, fields, models + + +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"] + + attachment_ids = fields.Many2many( + comodel_name="ir.attachment", + string="Files", + required=True, + relation="account_bank_statement_import_sheet_mapping_wiz_attachment_rel", + ) + 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("attachment_ids") + def _onchange_attachment_ids(self): + Parser = self.env["account.bank.statement.import.sheet.parser"] + Mapping = self.env["account.bank.statement.import.sheet.mapping"] + if not self.attachment_ids: + 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 = [] + for data_file in self.attachment_ids: + header += Parser.parse_header( + b64decode(data_file.datas), self.file_encoding, csv_options + ) + header = list(set(header)) + 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)] + + def _get_mapping_values(self): + """Hook for extension""" + self.ensure_one() + filename = "& ".join(self.attachment_ids.mapped("name")) + return { + "name": _("Mapping from %s") % path.basename(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, + } + + 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..93f2a6c8 --- /dev/null +++ b/account_bank_statement_import_txt_xlsx/wizards/account_bank_statement_import_sheet_mapping_wizard.xml @@ -0,0 +1,165 @@ + + + + + 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 + + +