diff --git a/account_statement_import_fr_cfonb/README.rst b/account_statement_import_fr_cfonb/README.rst new file mode 100644 index 000000000..194c7f6fa --- /dev/null +++ b/account_statement_import_fr_cfonb/README.rst @@ -0,0 +1,103 @@ +=================================== +Import French CFONB Bank Statements +=================================== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:c627ce2e95357ac95655a00cf597d6ac7f24e00eed84f685a11199472fca8cf9 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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%2Fl10n--france-lightgray.png?logo=github + :target: https://github.com/OCA/l10n-france/tree/18.0/account_statement_import_fr_cfonb + :alt: OCA/l10n-france +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/l10n-france-18-0/l10n-france-18-0-account_statement_import_fr_cfonb + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/builds?repo=OCA/l10n-france&target_branch=18.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module allows you to import the text-based France-specific CFONB +bank statements files in Odoo. + +The specifications are available on the `official CFONB +website `__ under the section *Espace +documentaire > Organisation des échanges > Restituation d'information*. +The document is named *Relevé de compte sur support informatique* +(`direct +link `__). + +Since Odoo v14, multi-account CFONB files are supported. When you import +such a file, several bank statements will be generated: one for each +bank account. + +**Table of contents** + +.. contents:: + :local: + +Usage +===== + +In the dashboard of the *Invoicing* menu, click on the button *Import +(OCA)* on any bank journal and follow the instructions of the bank +statement import wizard. + +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 to smash it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +------- + +* Akretion + +Contributors +------------ + +- Alexis de Lattre + +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. + +.. |maintainer-alexis-via| image:: https://github.com/alexis-via.png?size=40px + :target: https://github.com/alexis-via + :alt: alexis-via + +Current `maintainer `__: + +|maintainer-alexis-via| + +This module is part of the `OCA/l10n-france `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/account_statement_import_fr_cfonb/__init__.py b/account_statement_import_fr_cfonb/__init__.py new file mode 100644 index 000000000..9b4296142 --- /dev/null +++ b/account_statement_import_fr_cfonb/__init__.py @@ -0,0 +1,2 @@ +from . import models +from . import wizard diff --git a/account_statement_import_fr_cfonb/__manifest__.py b/account_statement_import_fr_cfonb/__manifest__.py new file mode 100644 index 000000000..6f0ce25dc --- /dev/null +++ b/account_statement_import_fr_cfonb/__manifest__.py @@ -0,0 +1,16 @@ +# Copyright 2014-2023 Akretion France (http://www.akretion.com) +# @author Alexis de Lattre +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +{ + "name": "Import French CFONB Bank Statements", + "version": "18.0.1.0.0", + "license": "AGPL-3", + "author": "Akretion,Odoo Community Association (OCA)", + "maintainers": ["alexis-via"], + "website": "https://github.com/OCA/l10n-france", + "summary": "Import CFONB bank statements files in Odoo", + "depends": ["account_statement_import_file"], + "data": ["views/account_statement_import.xml"], + "installable": True, +} diff --git a/account_statement_import_fr_cfonb/i18n/account_statement_import_fr_cfonb.pot b/account_statement_import_fr_cfonb/i18n/account_statement_import_fr_cfonb.pot new file mode 100644 index 000000000..79b8f2bec --- /dev/null +++ b/account_statement_import_fr_cfonb/i18n/account_statement_import_fr_cfonb.pot @@ -0,0 +1,50 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * account_statement_import_fr_cfonb +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 17.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_statement_import_fr_cfonb +#: model_terms:ir.ui.view,arch_db:account_statement_import_fr_cfonb.account_statement_import_form +msgid "CFONB (French format)" +msgstr "" + +#. module: account_statement_import_fr_cfonb +#: model:ir.model,name:account_statement_import_fr_cfonb.model_account_statement_import +msgid "Import Bank Statement Files" +msgstr "" + +#. module: account_statement_import_fr_cfonb +#: model:ir.model,name:account_statement_import_fr_cfonb.model_account_journal +msgid "Journal" +msgstr "" + +#. module: account_statement_import_fr_cfonb +#. odoo-python +#: code:addons/account_statement_import_fr_cfonb/wizard/account_statement_import.py:0 +#, python-format +msgid "The CFONB file is inconsistent. Error on line %d." +msgstr "" + +#. module: account_statement_import_fr_cfonb +#. odoo-python +#: code:addons/account_statement_import_fr_cfonb/wizard/account_statement_import.py:0 +#, python-format +msgid "The file is empty." +msgstr "" + +#. module: account_statement_import_fr_cfonb +#. odoo-python +#: code:addons/account_statement_import_fr_cfonb/wizard/account_statement_import.py:0 +#, python-format +msgid "The file is not divisible in 120 char lines" +msgstr "" diff --git a/account_statement_import_fr_cfonb/i18n/es.po b/account_statement_import_fr_cfonb/i18n/es.po new file mode 100644 index 000000000..a93de6497 --- /dev/null +++ b/account_statement_import_fr_cfonb/i18n/es.po @@ -0,0 +1,53 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * account_statement_import_fr_cfonb +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2023-08-09 15:09+0000\n" +"Last-Translator: Ivorra78 \n" +"Language-Team: none\n" +"Language: es\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 4.17\n" + +#. module: account_statement_import_fr_cfonb +#: model_terms:ir.ui.view,arch_db:account_statement_import_fr_cfonb.account_statement_import_form +msgid "CFONB (French format)" +msgstr "CFONB (formato francés)" + +#. module: account_statement_import_fr_cfonb +#: model:ir.model,name:account_statement_import_fr_cfonb.model_account_statement_import +msgid "Import Bank Statement Files" +msgstr "Importar archivos de extractos bancarios" + +#. module: account_statement_import_fr_cfonb +#: model:ir.model,name:account_statement_import_fr_cfonb.model_account_journal +msgid "Journal" +msgstr "Dario" + +#. module: account_statement_import_fr_cfonb +#. odoo-python +#: code:addons/account_statement_import_fr_cfonb/wizard/account_statement_import.py:0 +#, python-format +msgid "The CFONB file is inconsistent. Error on line %d." +msgstr "El archivo CFONB es inconsistente. Error en la línea %d." + +#. module: account_statement_import_fr_cfonb +#. odoo-python +#: code:addons/account_statement_import_fr_cfonb/wizard/account_statement_import.py:0 +#, python-format +msgid "The file is empty." +msgstr "El archivo está vacío." + +#. module: account_statement_import_fr_cfonb +#. odoo-python +#: code:addons/account_statement_import_fr_cfonb/wizard/account_statement_import.py:0 +#, python-format +msgid "The file is not divisible in 120 char lines" +msgstr "El fichero no es divisible en líneas de 120 caracteres" diff --git a/account_statement_import_fr_cfonb/i18n/fr.po b/account_statement_import_fr_cfonb/i18n/fr.po new file mode 100644 index 000000000..65e8954a0 --- /dev/null +++ b/account_statement_import_fr_cfonb/i18n/fr.po @@ -0,0 +1,53 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * account_statement_import_fr_cfonb +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2023-06-08 16:08+0000\n" +"Last-Translator: Alexis de Lattre \n" +"Language-Team: none\n" +"Language: fr\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n > 1;\n" +"X-Generator: Weblate 4.17\n" + +#. module: account_statement_import_fr_cfonb +#: model_terms:ir.ui.view,arch_db:account_statement_import_fr_cfonb.account_statement_import_form +msgid "CFONB (French format)" +msgstr "CFONB (format français)" + +#. module: account_statement_import_fr_cfonb +#: model:ir.model,name:account_statement_import_fr_cfonb.model_account_statement_import +msgid "Import Bank Statement Files" +msgstr "Importer des fichiers de relevé de compte" + +#. module: account_statement_import_fr_cfonb +#: model:ir.model,name:account_statement_import_fr_cfonb.model_account_journal +msgid "Journal" +msgstr "Journal" + +#. module: account_statement_import_fr_cfonb +#. odoo-python +#: code:addons/account_statement_import_fr_cfonb/wizard/account_statement_import.py:0 +#, python-format +msgid "The CFONB file is inconsistent. Error on line %d." +msgstr "Le fichier CFONB est incohérent. Erreur sur la ligne %d." + +#. module: account_statement_import_fr_cfonb +#. odoo-python +#: code:addons/account_statement_import_fr_cfonb/wizard/account_statement_import.py:0 +#, python-format +msgid "The file is empty." +msgstr "Le fichier est vide." + +#. module: account_statement_import_fr_cfonb +#. odoo-python +#: code:addons/account_statement_import_fr_cfonb/wizard/account_statement_import.py:0 +#, python-format +msgid "The file is not divisible in 120 char lines" +msgstr "Le fichier n'est pas divisible en lignes de 120 caractères" diff --git a/account_statement_import_fr_cfonb/i18n/nb_NO.po b/account_statement_import_fr_cfonb/i18n/nb_NO.po new file mode 100644 index 000000000..99f5d92e9 --- /dev/null +++ b/account_statement_import_fr_cfonb/i18n/nb_NO.po @@ -0,0 +1,72 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * account_bank_statement_import_fr_cfonb +# +# Translators: +# OCA Transbot , 2017 +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 10.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2017-11-30 10:45+0000\n" +"PO-Revision-Date: 2017-11-30 10:45+0000\n" +"Last-Translator: OCA Transbot , 2017\n" +"Language-Team: Norwegian Bokmål (Norway) (https://www.transifex.com/oca/" +"teams/23907/nb_NO/)\n" +"Language: nb_NO\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#. module: account_statement_import_fr_cfonb +#: model_terms:ir.ui.view,arch_db:account_statement_import_fr_cfonb.account_statement_import_form +msgid "CFONB (French format)" +msgstr "" + +#. module: account_statement_import_fr_cfonb +#: model:ir.model,name:account_statement_import_fr_cfonb.model_account_statement_import +#, fuzzy +msgid "Import Bank Statement Files" +msgstr "Importer bankutsagn" + +#. module: account_statement_import_fr_cfonb +#: model:ir.model,name:account_statement_import_fr_cfonb.model_account_journal +msgid "Journal" +msgstr "" + +#. module: account_statement_import_fr_cfonb +#. odoo-python +#: code:addons/account_statement_import_fr_cfonb/wizard/account_statement_import.py:0 +#, python-format +msgid "The CFONB file is inconsistent. Error on line %d." +msgstr "" + +#. module: account_statement_import_fr_cfonb +#. odoo-python +#: code:addons/account_statement_import_fr_cfonb/wizard/account_statement_import.py:0 +#, python-format +msgid "The file is empty." +msgstr "Filen er tom." + +#. module: account_statement_import_fr_cfonb +#. odoo-python +#: code:addons/account_statement_import_fr_cfonb/wizard/account_statement_import.py:0 +#, python-format +msgid "The file is not divisible in 120 char lines" +msgstr "" + +#, python-format +#~ msgid "" +#~ "Line %d is %d caracters long. All lines of a CFONB bank statement file " +#~ "must be 120 caracters long." +#~ msgstr "" +#~ "Linje %d er %d tegn langt. Alle linjer i en CFONB-bankutsagnsfil må være " +#~ "120 tegn langt." + +#~ msgid "" +#~ "Only single-account files and single-currency files are supported for the " +#~ "moment. It is not the case starting from line %d." +#~ msgstr "" +#~ "Kun enkeltkontofiler og enkeltmyntenhetsfiler støttes for øyeblikket. " +#~ "Dette er ikke tilfellet fra og med linje %d og utover." diff --git a/account_statement_import_fr_cfonb/models/__init__.py b/account_statement_import_fr_cfonb/models/__init__.py new file mode 100644 index 000000000..2388e1196 --- /dev/null +++ b/account_statement_import_fr_cfonb/models/__init__.py @@ -0,0 +1 @@ +from . import account_journal diff --git a/account_statement_import_fr_cfonb/models/account_journal.py b/account_statement_import_fr_cfonb/models/account_journal.py new file mode 100644 index 000000000..08c835fe7 --- /dev/null +++ b/account_statement_import_fr_cfonb/models/account_journal.py @@ -0,0 +1,14 @@ +# Copyright 2019-2020 Akretion France (http://www.akretion.com/) +# @author: Alexis de Lattre +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import models + + +class AccountJournal(models.Model): + _inherit = "account.journal" + + def _get_bank_statements_available_import_formats(self): + res = super()._get_bank_statements_available_import_formats() + res.append("CFONB") + return res diff --git a/account_statement_import_fr_cfonb/pyproject.toml b/account_statement_import_fr_cfonb/pyproject.toml new file mode 100644 index 000000000..4231d0ccc --- /dev/null +++ b/account_statement_import_fr_cfonb/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/account_statement_import_fr_cfonb/readme/CONTRIBUTORS.md b/account_statement_import_fr_cfonb/readme/CONTRIBUTORS.md new file mode 100644 index 000000000..b61afe5d0 --- /dev/null +++ b/account_statement_import_fr_cfonb/readme/CONTRIBUTORS.md @@ -0,0 +1 @@ +- Alexis de Lattre \<\> diff --git a/account_statement_import_fr_cfonb/readme/DESCRIPTION.md b/account_statement_import_fr_cfonb/readme/DESCRIPTION.md new file mode 100644 index 000000000..4958c7bcc --- /dev/null +++ b/account_statement_import_fr_cfonb/readme/DESCRIPTION.md @@ -0,0 +1,12 @@ +This module allows you to import the text-based France-specific CFONB bank +statements files in Odoo. + +The specifications are available on the [official CFONB +website](https://www.cfonb.org) under the section *Espace documentaire +\> Organisation des échanges \> Restituation d'information*. The +document is named *Relevé de compte sur support informatique* ([direct +link](https://www.cfonb.org/fichiers/20130612113947_7_4_Releve_de_Compte_sur_support_informatique_2004_07.pdf)). + +Since Odoo v14, multi-account CFONB files are supported. When you import +such a file, several bank statements will be generated: one for each +bank account. diff --git a/account_statement_import_fr_cfonb/readme/USAGE.md b/account_statement_import_fr_cfonb/readme/USAGE.md new file mode 100644 index 000000000..495744546 --- /dev/null +++ b/account_statement_import_fr_cfonb/readme/USAGE.md @@ -0,0 +1,2 @@ +In the dashboard of the *Invoicing* menu, click on the button *Import (OCA)* +on any bank journal and follow the instructions of the bank statement import wizard. diff --git a/account_statement_import_fr_cfonb/static/description/icon.png b/account_statement_import_fr_cfonb/static/description/icon.png new file mode 100644 index 000000000..3a0328b51 Binary files /dev/null and b/account_statement_import_fr_cfonb/static/description/icon.png differ diff --git a/account_statement_import_fr_cfonb/static/description/index.html b/account_statement_import_fr_cfonb/static/description/index.html new file mode 100644 index 000000000..9551033f4 --- /dev/null +++ b/account_statement_import_fr_cfonb/static/description/index.html @@ -0,0 +1,442 @@ + + + + + +Import French CFONB Bank Statements + + + +
+

Import French CFONB Bank Statements

+ + +

Beta License: AGPL-3 OCA/l10n-france Translate me on Weblate Try me on Runboat

+

This module allows you to import the text-based France-specific CFONB +bank statements files in Odoo.

+

The specifications are available on the official CFONB +website under the section Espace +documentaire > Organisation des échanges > Restituation d’information. +The document is named Relevé de compte sur support informatique +(direct +link).

+

Since Odoo v14, multi-account CFONB files are supported. When you import +such a file, several bank statements will be generated: one for each +bank account.

+

Table of contents

+ +
+

Usage

+

In the dashboard of the Invoicing menu, click on the button Import +(OCA) on any bank journal and follow the instructions of the bank +statement import wizard.

+
+
+

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 to smash it by providing a detailed and welcomed +feedback.

+

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

+
+
+

Credits

+
+

Authors

+
    +
  • Akretion
  • +
+
+
+

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.

+

Current maintainer:

+

alexis-via

+

This module is part of the OCA/l10n-france project on GitHub.

+

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

+
+
+
+ + diff --git a/account_statement_import_fr_cfonb/tests/__init__.py b/account_statement_import_fr_cfonb/tests/__init__.py new file mode 100644 index 000000000..ece777496 --- /dev/null +++ b/account_statement_import_fr_cfonb/tests/__init__.py @@ -0,0 +1 @@ +from . import test_cfonb diff --git a/account_statement_import_fr_cfonb/tests/samples/cfonb_classic.cfo b/account_statement_import_fr_cfonb/tests/samples/cfonb_classic.cfo new file mode 100644 index 000000000..134660a4b --- /dev/null +++ b/account_statement_import_fr_cfonb/tests/samples/cfonb_classic.cfo @@ -0,0 +1,77 @@ +0130003 01234EUR2 01234567890 120124 0000010173118I +0430003000501234EUR2E0123456789018130124 130124VIR INST MA JOLIE BOUTIQUE 0000000000000000010401G +0530003000501234EUR2E0123456789018130124 LIBfacture F230875 +0530003000501234EUR2E0123456789018130124 NPYMA JOLIE BOUTIQUE +0530003000501234EUR2E0123456789018130124 NBEAKRETION FRANCE +0430003000501234EUR2E0123456789018130124 130124VIR DURANCIA MONTGENEVRE 0000000000000000013210{ +0530003000501234EUR2E0123456789018130124 LIBF230866 F230765 F238765 +0530003000501234EUR2E0123456789018130124 NPYDURANCIA MONTGENEVRE +0530003000501234EUR2E0123456789018130124 NBEAKRETION FRANCE +0530003000501234EUR2E0123456789018130124 LCCF230866 F230765 F238765 +0530003000501234EUR2E0123456789018130124 RCN0018937 +0430003000501234EUR2E0123456789018130124 130124VIR DITRI CAMPING NORD 0000000000000000047729A +0530003000501234EUR2E0123456789018130124 LIBACOMPTE PROJET CIMENT LOT2 +0530003000501234EUR2E0123456789018130124 NPYDITRI CAMPING NORD +0530003000501234EUR2E0123456789018130124 NBEAKRETION FRANCE +0530003000501234EUR2E0123456789018130124 LCCACOMPTE PROJET CIMENT LOT2 +0530003000501234EUR2E0123456789018130124 RCNF230987, F231234 +0430003062101234EUR2E0123456789045130124 130124VIR Econymo Software SARL 0000000000000000023411B +0530003062101234EUR2E0123456789045130124 LIBInvoice F235678 +0530003062101234EUR2E0123456789045130124 NPYEconymo Software SARL +0530003000501234EUR2E0123456789018130124 NBEAKRETION FRANCE +0530003062101234EUR2E0123456789045130124 LCCInvoice F235678 +0430003000501234EUR2E0123456789018130124 130124VIR RATP 0000000000000000002188{ +0530003000501234EUR2E0123456789018130124 LIBAkretion factures F239765/F237654/F239875/F240001 +0530003000501234EUR2E0123456789018130124 NPYRATP +0530003000501234EUR2E0123456789018130124 NBEAKRETION FRANCE +0430003000501234EUR2E0123456789018130124 130124VIR DGA FRANCE 0000000000000000005108{ +0530003000501234EUR2E0123456789018130124 LIBF238765+F229876 +0530003000501234EUR2E0123456789018130124 NPYDGA FRANCE +0530003000501234EUR2E0123456789018130124 NBEAKRETION FRANCE +0530003000501234EUR2E0123456789018130124 RCN9 +0430003000501234EUR2E0123456789018130124 130124VIR BROMBIJ BV 0000000000000000005207H +0530003000501234EUR2E0123456789018130124 LIBDV8762382 DV2937982 +0530003000501234EUR2E0123456789018130124 NPYBROMBIJ BV +0530003000501234EUR2E0123456789018130124 NBEAKRETION FRANCE +0530003000501234EUR2E0123456789018130124 RCN0088023 +0430003000501234EUR2E0123456789018130124 130124VIR PRINT SARL 0000000000000000015522C +0530003000501234EUR2E0123456789018130124 NPYPRINT SARL +0530003000501234EUR2E0123456789018130124 NBEAKRETION +0530003000501234EUR2E0123456789018130124 RCNREGLEMENT PRINT SARL +0430003000501234EUR2E0123456789018130124 130124VIR PRARIAL OT MONTG 0000000000000000056907F +0530003000501234EUR2E0123456789018130124 LIBVIR -F238765 AKR +0530003000501234EUR2E0123456789018130124 NPYPRARIAL OT MONTG +0530003000501234EUR2E0123456789018130124 NBEAKRETION FRANCE +0530003000501234EUR2E0123456789018130124 RCN2023-12-31 +0430003029101234EUR2E01234567890B1130124 130124PRLV SEPA FREE MOBILE 0000000000000000000199R +0530003029101234EUR2E01234567890B1130124 NPYNIMPORTEQUOI +0530003029101234EUR2E01234567890B1130124 IPYfmc-31375826 +0530003029101234EUR2E01234567890B1130124 RCNfmpmt-1168225394 +0530003029101234EUR2E01234567890B1130124 IBEFR07ZZZ591778 IDENTIFIANT CREANCIER SEPA +0530003029101234EUR2E01234567890B1130124 RUMFM-47933521-1 RCUR +0430003029101234EUR2E01234567890B1130124 130124PRLV SEPA FREE MOBILE 0000000000000000000199R +0530003029101234EUR2E01234567890B1130124 NPYNIMPORTEQUOI +0530003029101234EUR2E01234567890B1130124 IPYfmc-7239746387 +0530003029101234EUR2E01234567890B1130124 NBEFREE MOBILE +0530003029101234EUR2E01234567890B1130124 RCNfmpmt-1290437493 +0530003029101234EUR2E01234567890B1130124 IBEFR07ZZZ591778 IDENTIFIANT CREANCIER SEPA +0530003029101234EUR2E01234567890B1130124 RUMFM-793478343-1 RCUR +0430003029101234EUR2E01234567890B1130124 130124PRLV SEPA Orange 0000000000000000006171M +0530003029101234EUR2E01234567890B1130124 LIBFIBENT - FR - Facture: 76662391 - Compte Fact: 30008765 - Ident: 00003 +0530003029101234EUR2E01234567890B1130124 NPYAKRETION FRANCE +0530003029101234EUR2E01234567890B1130124 NBEOrange +0530003029101234EUR2E01234567890B1130124 LCCFIBENT - FR - Facture: 76662391 - Compte Fact: 30008765 - Ident: 00003 +0530003029101234EUR2E01234567890B1130124 LC2263 +0530003029101234EUR2E01234567890B1130124 RCN)30008765 76662391 45 3 +0530003029101234EUR2E01234567890B1130124 IBEFR18ZZZ002305 IDENTIFIANT CREANCIER SEPA +0530003029101234EUR2E01234567890B1130124 RUM++48398735847564876543 RCUR +0430003029101234EUR2E01234567890B1130124 130124PRLV SEPA Orange 0000000000000000030375N +0530003029101234EUR2E01234567890B1130124 LIBFIBENT - FR - Facture: 76662392 - Compte Fact: 30008765 - Ident: 00003 +0530003029101234EUR2E01234567890B1130124 NPYAKRETION FRANCE +0530003029101234EUR2E01234567890B1130124 NBEOrange +0530003029101234EUR2E01234567890B1130124 LCCFIBENT - FR - Facture: 76662392 - Compte Fact: 30008765 - Ident: 00003 +0530003029101234EUR2E01234567890B1130124 LC2074 +0530003029101234EUR2E01234567890B1130124 RCN)30008765 76662392 45 3 +0530003029101234EUR2E01234567890B1130124 IBEFR18ZZZ002305 IDENTIFIANT CREANCIER SEPA +0530003029101234EUR2E01234567890B1130124 RUM++97765287983503577563 RCUR +0730003 01234EUR2 01234567890 130124 0000010315857I diff --git a/account_statement_import_fr_cfonb/tests/samples/cfonb_no_carriage_return.cfo b/account_statement_import_fr_cfonb/tests/samples/cfonb_no_carriage_return.cfo new file mode 100644 index 000000000..71d25d311 --- /dev/null +++ b/account_statement_import_fr_cfonb/tests/samples/cfonb_no_carriage_return.cfo @@ -0,0 +1 @@ +0130003 01234EUR2 01234567890 120124 0000010173118I 0430003000501234EUR2E0123456789018130124 130124VIR INST MA JOLIE BOUTIQUE 0000000000000000010401G 0530003000501234EUR2E0123456789018130124 LIBfacture F230875 0530003000501234EUR2E0123456789018130124 NPYMA JOLIE BOUTIQUE 0530003000501234EUR2E0123456789018130124 NBEAKRETION FRANCE 0430003000501234EUR2E0123456789018130124 130124VIR DURANCIA MONTGENEVRE 0000000000000000013210{ 0530003000501234EUR2E0123456789018130124 LIBF230866 F230765 F238765 0530003000501234EUR2E0123456789018130124 NPYDURANCIA MONTGENEVRE 0530003000501234EUR2E0123456789018130124 NBEAKRETION FRANCE 0530003000501234EUR2E0123456789018130124 LCCF230866 F230765 F238765 0530003000501234EUR2E0123456789018130124 RCN0018937 0430003000501234EUR2E0123456789018130124 130124VIR DITRI CAMPING NORD 0000000000000000047729A 0530003000501234EUR2E0123456789018130124 LIBACOMPTE PROJET CIMENT LOT2 0530003000501234EUR2E0123456789018130124 NPYDITRI CAMPING NORD 0530003000501234EUR2E0123456789018130124 NBEAKRETION FRANCE 0530003000501234EUR2E0123456789018130124 LCCACOMPTE PROJET CIMENT LOT2 0530003000501234EUR2E0123456789018130124 RCNF230987, F231234 0430003062101234EUR2E0123456789045130124 130124VIR Econymo Software SARL 0000000000000000023411B 0530003062101234EUR2E0123456789045130124 LIBInvoice F235678 0530003062101234EUR2E0123456789045130124 NPYEconymo Software SARL 0530003000501234EUR2E0123456789018130124 NBEAKRETION FRANCE 0530003062101234EUR2E0123456789045130124 LCCInvoice F235678 0430003000501234EUR2E0123456789018130124 130124VIR RATP 0000000000000000002188{ 0530003000501234EUR2E0123456789018130124 LIBAkretion factures F239765/F237654/F239875/F240001 0530003000501234EUR2E0123456789018130124 NPYRATP 0530003000501234EUR2E0123456789018130124 NBEAKRETION FRANCE 0430003000501234EUR2E0123456789018130124 130124VIR DGA FRANCE 0000000000000000005108{ 0530003000501234EUR2E0123456789018130124 LIBF238765+F229876 0530003000501234EUR2E0123456789018130124 NPYDGA FRANCE 0530003000501234EUR2E0123456789018130124 NBEAKRETION FRANCE 0530003000501234EUR2E0123456789018130124 RCN9 0430003000501234EUR2E0123456789018130124 130124VIR BROMBIJ BV 0000000000000000005207H 0530003000501234EUR2E0123456789018130124 LIBDV8762382 DV2937982 0530003000501234EUR2E0123456789018130124 NPYBROMBIJ BV 0530003000501234EUR2E0123456789018130124 NBEAKRETION FRANCE 0530003000501234EUR2E0123456789018130124 RCN0088023 0430003000501234EUR2E0123456789018130124 130124VIR PRINT SARL 0000000000000000015522C 0530003000501234EUR2E0123456789018130124 NPYPRINT SARL 0530003000501234EUR2E0123456789018130124 NBEAKRETION 0530003000501234EUR2E0123456789018130124 RCNREGLEMENT PRINT SARL 0430003000501234EUR2E0123456789018130124 130124VIR PRARIAL OT MONTG 0000000000000000056907F 0530003000501234EUR2E0123456789018130124 LIBVIR -F238765 AKR 0530003000501234EUR2E0123456789018130124 NPYPRARIAL OT MONTG 0530003000501234EUR2E0123456789018130124 NBEAKRETION FRANCE 0530003000501234EUR2E0123456789018130124 RCN2023-12-31 0430003029101234EUR2E01234567890B1130124 130124PRLV SEPA FREE MOBILE 0000000000000000000199R 0530003029101234EUR2E01234567890B1130124 NPYNIMPORTEQUOI 0530003029101234EUR2E01234567890B1130124 IPYfmc-31375826 0530003029101234EUR2E01234567890B1130124 RCNfmpmt-1168225394 0530003029101234EUR2E01234567890B1130124 IBEFR07ZZZ591778 IDENTIFIANT CREANCIER SEPA 0530003029101234EUR2E01234567890B1130124 RUMFM-47933521-1 RCUR 0430003029101234EUR2E01234567890B1130124 130124PRLV SEPA FREE MOBILE 0000000000000000000199R 0530003029101234EUR2E01234567890B1130124 NPYNIMPORTEQUOI 0530003029101234EUR2E01234567890B1130124 IPYfmc-7239746387 0530003029101234EUR2E01234567890B1130124 NBEFREE MOBILE 0530003029101234EUR2E01234567890B1130124 RCNfmpmt-1290437493 0530003029101234EUR2E01234567890B1130124 IBEFR07ZZZ591778 IDENTIFIANT CREANCIER SEPA 0530003029101234EUR2E01234567890B1130124 RUMFM-793478343-1 RCUR 0430003029101234EUR2E01234567890B1130124 130124PRLV SEPA Orange 0000000000000000006171M 0530003029101234EUR2E01234567890B1130124 LIBFIBENT - FR - Facture: 76662391 - Compte Fact: 30008765 - Ident: 00003 0530003029101234EUR2E01234567890B1130124 NPYAKRETION FRANCE 0530003029101234EUR2E01234567890B1130124 NBEOrange 0530003029101234EUR2E01234567890B1130124 LCCFIBENT - FR - Facture: 76662391 - Compte Fact: 30008765 - Ident: 00003 0530003029101234EUR2E01234567890B1130124 LC2263 0530003029101234EUR2E01234567890B1130124 RCN)30008765 76662391 45 3 0530003029101234EUR2E01234567890B1130124 IBEFR18ZZZ002305 IDENTIFIANT CREANCIER SEPA 0530003029101234EUR2E01234567890B1130124 RUM++48398735847564876543 RCUR 0430003029101234EUR2E01234567890B1130124 130124PRLV SEPA Orange 0000000000000000030375N 0530003029101234EUR2E01234567890B1130124 LIBFIBENT - FR - Facture: 76662392 - Compte Fact: 30008765 - Ident: 00003 0530003029101234EUR2E01234567890B1130124 NPYAKRETION FRANCE 0530003029101234EUR2E01234567890B1130124 NBEOrange 0530003029101234EUR2E01234567890B1130124 LCCFIBENT - FR - Facture: 76662392 - Compte Fact: 30008765 - Ident: 00003 0530003029101234EUR2E01234567890B1130124 LC2074 0530003029101234EUR2E01234567890B1130124 RCN)30008765 76662392 45 3 0530003029101234EUR2E01234567890B1130124 IBEFR18ZZZ002305 IDENTIFIANT CREANCIER SEPA 0530003029101234EUR2E01234567890B1130124 RUM++97765287983503577563 RCUR 0730003 01234EUR2 01234567890 130124 0000010315857I diff --git a/account_statement_import_fr_cfonb/tests/test_cfonb.py b/account_statement_import_fr_cfonb/tests/test_cfonb.py new file mode 100644 index 000000000..746f19506 --- /dev/null +++ b/account_statement_import_fr_cfonb/tests/test_cfonb.py @@ -0,0 +1,65 @@ +# Copyright 2025 Akretion France (https://www.akretion.com/) +# @author: Alexis de Lattre +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +import base64 + +from odoo.tests.common import TransactionCase +from odoo.tools import file_open + + +class TestAccountStatementImportFrCfonb(TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.env = cls.env(context=dict(cls.env.context, tracking_disable=True)) + cls.eur_cur = cls.env.ref("base.EUR") + cls.wiz_model = cls.env["account.statement.import"] + cls.st_model = cls.env["account.bank.statement"] + partner_bank = cls.env["res.partner.bank"].create( + { + "acc_number": "01234567890", + "partner_id": cls.env.ref("base.main_partner").id, + "company_id": cls.env.ref("base.main_company").id, + } + ) + cls.bank_journal = cls.env["account.journal"].create( + { + "name": "CFONB Test journal", + "code": "CFOZZ", + "type": "bank", + "bank_account_id": partner_bank.id, + "currency_id": cls.eur_cur.id, + } + ) + + def cfonb_file_generic(self, filename): + path = f"account_statement_import_fr_cfonb/tests/samples/{filename}" + with file_open(path, "rb") as cfonb_file: + cfonb_bin = cfonb_file.read() + wizard = self.wiz_model.create( + { + "statement_file": base64.b64encode(cfonb_bin), + "statement_filename": filename, + } + ) + res = wizard.import_file_button() + self.assertEqual(res["res_model"], "account.bank.statement") + st_id = res["domain"][0][2][0] + stmt = self.st_model.browse(st_id) + self.assertFalse( + self.eur_cur.compare_amounts(stmt.balance_start, 1017311.89) + ) + self.assertFalse( + self.eur_cur.compare_amounts(stmt.balance_end_real, 1031585.79) + ) + self.assertEqual(len(stmt.line_ids), 13) + self.assertEqual(stmt.journal_id.id, self.bank_journal.id) + for line in stmt.line_ids: + self.assertEqual(line.date.strftime("%Y%m%d"), "20240113") + + def test_cfonb_file_classic(self): + self.cfonb_file_generic("cfonb_classic.cfo") + + def test_cfonb_file_no_carriage_return(self): + self.cfonb_file_generic("cfonb_no_carriage_return.cfo") diff --git a/account_statement_import_fr_cfonb/views/account_statement_import.xml b/account_statement_import_fr_cfonb/views/account_statement_import.xml new file mode 100644 index 000000000..3769256ae --- /dev/null +++ b/account_statement_import_fr_cfonb/views/account_statement_import.xml @@ -0,0 +1,21 @@ + + + + + cfonb.account.bank.statement.import.form + account.statement.import + + + +
  • CFONB (France-specific format)
  • +
    +
    +
    +
    diff --git a/account_statement_import_fr_cfonb/wizard/__init__.py b/account_statement_import_fr_cfonb/wizard/__init__.py new file mode 100644 index 000000000..ae69bca27 --- /dev/null +++ b/account_statement_import_fr_cfonb/wizard/__init__.py @@ -0,0 +1 @@ +from . import account_statement_import diff --git a/account_statement_import_fr_cfonb/wizard/account_statement_import.py b/account_statement_import_fr_cfonb/wizard/account_statement_import.py new file mode 100644 index 000000000..18a9223c5 --- /dev/null +++ b/account_statement_import_fr_cfonb/wizard/account_statement_import.py @@ -0,0 +1,230 @@ +# Copyright 2014-2020 Akretion (http://www.akretion.com) +# @author Alexis de Lattre +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import logging +from datetime import datetime + +from odoo import _, api, fields, models +from odoo.exceptions import UserError + +_logger = logging.getLogger(__name__) + +CFONB_WIDTH = 120 + + +class AccountStatementImport(models.TransientModel): + _inherit = "account.statement.import" + + _excluded_accounts = [] + + @api.model + def _get_allow_cfonb_complementary_types(self): + """ + LIB: Libellé Libre + LCC: Libellé de Client à Client ligne 1 + LC2: Libellé de Client à Client ligne 2 + RCN: Référence de Client à Client / Nature du paiement + Full list: http://journeeutilisateurs.free.fr/cariboost_files/SEPA_20PART2.pdf + on last page + """ + return (" ", "LIB", "LCC", "LC2", "RCN") + + def _parse_cfonb_amount(self, amount_str, nb_of_dec): + """Taken from the cfonb lib""" + if nb_of_dec: + amount_str = amount_str[:-nb_of_dec] + "." + amount_str[-nb_of_dec:] + # translate the last char and set the sign + credit_trans = { + "A": "1", + "B": "2", + "C": "3", + "D": "4", + "E": "5", + "F": "6", + "G": "7", + "H": "8", + "I": "9", + "{": "0", + } + debit_trans = { + "J": "1", + "K": "2", + "L": "3", + "M": "4", + "N": "5", + "O": "6", + "P": "7", + "Q": "8", + "R": "9", + "}": "0", + } + assert ( + amount_str[-1] in debit_trans or amount_str[-1] in credit_trans + ), "Invalid amount in CFONB file" + if amount_str[-1] in debit_trans: + amount_num = float("-" + amount_str[:-1] + debit_trans[amount_str[-1]]) + elif amount_str[-1] in credit_trans: + amount_num = float(amount_str[:-1] + credit_trans[amount_str[-1]]) + return amount_num + + @api.model + def _check_cfonb(self, data_file): + return data_file.decode("latin1").strip().startswith("01") + + def _parse_file(self, data_file): + """Import a file in French CFONB format""" + cfonb = self._check_cfonb(data_file) + if not cfonb: + return super()._parse_file(data_file) + result = {} + # The CFONB spec says you should only have digits, capital letters + # and * - . / + # But many banks don't respect that and use regular letters for exemple + # And I found one (LCL) that even uses caracters with accents + # So the best method is probably to decode with latin1 + data_file_decoded = data_file.decode("latin1") + lines = self._cfonb_split_lines(data_file_decoded) + i = 0 + seq = 1 + account_key = False + decimals = start_balance = False + start_balance = end_balance = False + transactions = [] + for line in lines: + i += 1 + _logger.debug("Line %d: %s" % (i, line)) + assert len(line) == CFONB_WIDTH + rec_type = line[0:2] + bank_code = line[2:7] + guichet_code = line[11:16] + account_number = line[21:32] + # Some LCL files are invalid: they leave decimals and + # currency fields empty on lines that start with '01' and '07', + # so I give default values in the code for those fields + currency_code = line[16:19] != " " and line[16:19] or "EUR" + account_key = f"{bank_code}-{guichet_code}-{account_number}-{currency_code}" + decimals = line[19:20] == " " and 2 or int(line[19:20]) + # decimals=2 for EUR, 0 for XPF + date_cfonb_str = line[34:40] + date_dt = False + if date_cfonb_str != " ": + date_dt = datetime.strptime(date_cfonb_str, "%d%m%y") + if account_number in self._excluded_accounts: + continue + + if rec_type == "01": + transactions = [] + start_balance = self._parse_cfonb_amount(line[90:104], decimals) + if account_key not in result: + result[account_key] = { + "currency_code": currency_code, + "account_number": account_number, + "name": account_number, + "date": False, + "balance_start": start_balance, + "balance_end_real": False, + "transactions": [], + } + + elif rec_type == "07": + end_balance = self._parse_cfonb_amount(line[90:104], decimals) + self._cfonb_unique_import_id_postprocess(transactions) + result[account_key]["balance_end_real"] = end_balance + result[account_key]["date"] = date_dt + result[account_key]["transactions"] += transactions + + elif rec_type == "04": + amount = self._parse_cfonb_amount(line[90:104], decimals) + ref = line[81:88].strip() # This is not unique + name = line[48:79].strip() + transactions.append( + { + "sequence": seq, + "date": date_dt, + "payment_ref": name, + "unique_import_id": ( + f"{fields.Date.to_string(date_dt)}-" + f"{ref}-{round(amount, decimals)}-{name}" + ), + "amount": amount, + } + ) + seq += 1 + + elif rec_type == "05": + complementary_info_type = line[45:48] + complementary_info = line[48:118].strip() + # Strategy: + # We use ALL complementary_info_types in unique_import_id + # because it lowers the risk to get the error + # caused by 2 different lines with same amount/date/label, + # but we add the complementary_info in 'payment_ref' only + # when it's interesting for the user, in order to avoid + # too long labels with too much "pollution" + transactions[-1]["unique_import_id"] += complementary_info + if ( + complementary_info_type + in self._get_allow_cfonb_complementary_types() + and complementary_info + ): + transactions[-1]["payment_ref"] += " " + complementary_info + + if rec_type in ("04", "05", "07") and account_key not in result: + raise UserError( + _("The CFONB file is inconsistent. Error on line %d.") % i + ) + res = [] + for rdict in result.values(): + if rdict["transactions"]: + res.append( + (rdict.pop("currency_code"), rdict.pop("account_number"), [rdict]) + ) + return res + + @api.model + def _cfonb_unique_import_id_postprocess(self, transactions): + """In the CFONB spec, there is no requirement for a unique identifier for + each statement line. The 'ref' (Numéro d'écriture) is often filled with + zeros. So it's possible to have the same unique_import_id for 2 statement + lines, for example if you buy a train ticket and then the return train ticket + on the same day. To avoid an error upon import in this scenario, + we postprocess the unique_import_id appending '-2', '-3', etc... + """ + unique_import_ids = {} + for transaction in transactions: + unique_import_id = transaction["unique_import_id"] + if unique_import_id in unique_import_ids: + unique_import_ids[unique_import_id] += 1 + transaction["unique_import_id"] += ( + f"-{unique_import_ids[unique_import_id]}" + ) + else: + unique_import_ids[unique_import_id] = 1 + + def _cfonb_split_lines(self, data_file): + """Split the data file into lines. + + Returns a list of the lines in the file provided. + """ + # According to the standard each line has to be 120 chars long, but + # some banks ship the files without line break. + # so we want to split the file after 120 chars, no matter if there + # is a newline there or not. + + # remove linebreaks + data_file_without_linebreaks = data_file.replace("\n", "").replace("\r", "") + + # check length of file + max_len = len(data_file_without_linebreaks) + lines = [] + + # make sure the length is a multiple of 120 otherwise it isn't valid + if max_len % CFONB_WIDTH: + raise UserError(_("The file is not divisible in 120 char lines")) + if max_len == 0: + raise UserError(_("The file is empty.")) + for index in range(0, max_len, CFONB_WIDTH): + # append a 120 char slice of the file to the list of lines + lines.append(data_file_without_linebreaks[index : index + CFONB_WIDTH]) + return lines