Merge PR #751 into 17.0

Signed-off-by pedrobaeza
This commit is contained in:
OCA-git-bot
2025-01-19 21:31:27 +00:00
31 changed files with 2612 additions and 0 deletions

View File

@@ -0,0 +1,166 @@
==================================
Online Bank Statements: GoCardless
==================================
..
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:86c3a6944d510b258d44f2a74e4e9e433cb0e4f9745ca156414b7a5c0d83efb1
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
.. |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/17.0/account_statement_import_online_gocardless
: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-17-0/bank-statement-import-17-0-account_statement_import_online_gocardless
: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/bank-statement-import&target_branch=17.0
:alt: Try me on Runboat
|badge1| |badge2| |badge3| |badge4| |badge5|
This module provides online bank statements from GoCardless Bank Account
Data, which provides a free API for connecting and getting transactions
for bank accounts.
**Table of contents**
.. contents::
:local:
Configuration
=============
On the GoCardless website
-------------------------
1. Go to
`https://bankaccountdata.gocardless.com <https://bankaccountdata.gocardless.com>`__,
and create or login into your "GoCardLess Bank Account Data" account.
2. Go to Developers > User secrets option on the left.
3. Click on the "+ Create new" button on the bottom part.
4. Put a name to the user secret (eg. Odoo), and optionally limit it to
certain IPs using CIDR subnet notation.
5. Copy or download the secret ID and key for later use. The second one
won't be available anymore, so make sure you don't forget this step.
On Odoo
-------
To configure online bank statements provider:
1. Add your user to the "Full Accounting Settings" group.
2. Go to *Invoicing > Configuration > Accounting > Journals*.
3. Select the journal representing your bank account (or create it).
4. The bank account number should be properly introduced.
5. Set *Bank Feeds* to *Online (OCA)*.
6. Select *GoCardless* as online bank statements provider in *Online
Bank Statements (OCA)* section.
7. Save the journal
8. Click on the created provider.
9. Put your secret ID and secret key on the existing fields.
10. Click on the button "Select Bank Account Identifier".
|image_01|
11. A new window will appear for selecting the bank entity.
|image_02|
12. Select it, and you will be redirected to the selected entity for
introducing your bank credentials to allow the connection.
13. If the process is completed, and the bank account linked to the
journal is accessible, you'll be again redirected to the online
provider form, and everything will be linked and ready to start the
transaction pulling. A message is logged about it on the chatter.
14. If not, an error message will be logged either in the chatter.
.. |image_01| image:: https://raw.githubusercontent.com/OCA/bank-statement-import/17.0/account_statement_import_online_gocardless/static/img/gocardless_configuration.gif
.. |image_02| image:: https://raw.githubusercontent.com/OCA/bank-statement-import/17.0/account_statement_import_online_gocardless/static/img/gocardless_bank_selection.gif
Usage
=====
To pull historical bank statements:
1. Go to *Invoicing > Configuration > Accounting > Journals*.
2. Select the journal representing your bank account.
3. Launch *Actions > Online Bank Statements Pull Wizard*
4. Configure date interval and click on *Pull*.
If historical data is not needed, then just simply wait for the
scheduled activity "Pull Online Bank Statements" to be executed for
getting new transactions.
Bug Tracker
===========
Bugs are tracked on `GitHub Issues <https://github.com/OCA/bank-statement-import/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 <https://github.com/OCA/bank-statement-import/issues/new?body=module:%20account_statement_import_online_gocardless%0Aversion:%2017.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
Do not contact contributors directly about support or help with technical issues.
Credits
=======
Authors
-------
* ForgeFlow
* Tecnativa
Contributors
------------
- `ForgeFlow <https://www.forgeflow.com>`__:
- Christopher Ormaza
- Jordi Ballester
- `Tecnativa <https://www.tecnativa.com>`__:
- Pedro M. Baeza
- `Alusage <https://nicolas.alusage.fr>`__:
- Nicolas JEUDY
<`https://github.com/njeudy <https://github.com/njeudy>`__>
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 <https://github.com/OCA/bank-statement-import/tree/17.0/account_statement_import_online_gocardless>`_ project on GitHub.
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

View File

@@ -0,0 +1,5 @@
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from . import controllers
from . import models
from . import wizards

View File

@@ -0,0 +1,30 @@
# Copyright 2022 ForgeFlow S.L.
# Copyright 2023-2024 Tecnativa - Pedro M. Baeza
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
{
"name": "Online Bank Statements: GoCardless",
"version": "17.0.1.0.0",
"category": "Account",
"website": "https://github.com/OCA/bank-statement-import",
"author": "ForgeFlow, Tecnativa, Odoo Community Association (OCA)",
"license": "AGPL-3",
"installable": True,
"depends": [
"account_statement_import_online",
],
"data": [
"security/ir.model.access.csv",
"view/online_bank_statement_provider.xml",
"wizards/online_bank_statement_provider_existing_views.xml",
],
"assets": {
"web.assets_backend": [
"account_statement_import_online_gocardless/static/src/"
"lib/gocardless-ui/selector.css",
"account_statement_import_online_gocardless/static/src/"
"js/select_bank_widget.esm.js",
"account_statement_import_online_gocardless/static/src/"
"xml/select_bank_widget.xml",
],
},
}

View File

@@ -0,0 +1 @@
from . import main

View File

@@ -0,0 +1,35 @@
# Copyright 2022 ForgeFlow S.L.
# Copyright 2023 Tecnativa - Pedro M. Baeza
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
import werkzeug
from werkzeug.urls import url_encode
from odoo import http
from odoo.http import request
class GocardlessController(http.Controller):
@http.route("/gocardless/response", type="http", auth="public", csrf=False)
def gocardless_response(self, **post):
Provider = request.env["online.bank.statement.provider"].sudo()
current_provider = Provider.search(
[("gocardless_requisition_ref", "=", post["ref"])]
)
params = {
"action": request.env.ref(
"account_statement_import_online.online_bank_statement_provider_action"
).id,
"model": "online.bank.statement.provider",
}
if current_provider:
current_provider._gocardless_finish_requisition()
params.update(
{
"view_type": "form",
"id": current_provider.id,
}
)
else:
params["view_type"] = "list"
return werkzeug.utils.redirect("/web#" + url_encode(params), 303)

View File

@@ -0,0 +1,233 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * account_statement_import_online_gocardless
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 16.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_online_gocardless
#: model_terms:ir.ui.view,arch_db:account_statement_import_online_gocardless.view_online_bank_statement_provider_existing_form
msgid ""
"<i class=\"fa fa-info-circle\"/> There's already another journal from the "
"same bank institution linked with GoCardless. Do you want to reuse the "
"existing credentials or create new ones?"
msgstr ""
#. module: account_statement_import_online_gocardless
#. odoo-javascript
#: code:addons/account_statement_import_online_gocardless/static/src/xml/select_bank_widget.xml:0
#, python-format
msgid "Available countries:"
msgstr ""
#. module: account_statement_import_online_gocardless
#: model:ir.model.fields,field_description:account_statement_import_online_gocardless.field_online_bank_statement_provider_existing__create_uid
msgid "Created by"
msgstr ""
#. module: account_statement_import_online_gocardless
#: model:ir.model.fields,field_description:account_statement_import_online_gocardless.field_online_bank_statement_provider_existing__create_date
msgid "Created on"
msgstr ""
#. module: account_statement_import_online_gocardless
#: model:ir.model.fields,field_description:account_statement_import_online_gocardless.field_online_bank_statement_provider_existing__display_name
msgid "Display Name"
msgstr ""
#. module: account_statement_import_online_gocardless
#. odoo-python
#: code:addons/account_statement_import_online_gocardless/models/online_bank_statement_provider.py:0
#, python-format
msgid "Existing link"
msgstr ""
#. module: account_statement_import_online_gocardless
#: model_terms:ir.ui.view,arch_db:account_statement_import_online_gocardless.online_bank_statement_provider_form
msgid "GoCardless"
msgstr ""
#. module: account_statement_import_online_gocardless
#: model:ir.model.fields,field_description:account_statement_import_online_gocardless.field_online_bank_statement_provider__gocardless_account_id
msgid "Gocardless Account"
msgstr ""
#. module: account_statement_import_online_gocardless
#: model:ir.model.fields,field_description:account_statement_import_online_gocardless.field_online_bank_statement_provider__gocardless_institution_id
msgid "Gocardless Institution"
msgstr ""
#. module: account_statement_import_online_gocardless
#: model:ir.model.fields,field_description:account_statement_import_online_gocardless.field_online_bank_statement_provider__gocardless_refresh_expiration
msgid "Gocardless Refresh Expiration"
msgstr ""
#. module: account_statement_import_online_gocardless
#: model:ir.model.fields,field_description:account_statement_import_online_gocardless.field_online_bank_statement_provider__gocardless_refresh_token
msgid "Gocardless Refresh Token"
msgstr ""
#. module: account_statement_import_online_gocardless
#: model:ir.model.fields,field_description:account_statement_import_online_gocardless.field_online_bank_statement_provider__gocardless_requisition_id
msgid "Gocardless Requisition"
msgstr ""
#. module: account_statement_import_online_gocardless
#: model:ir.model.fields,field_description:account_statement_import_online_gocardless.field_online_bank_statement_provider__gocardless_requisition_expiration
msgid "Gocardless Requisition Expiration"
msgstr ""
#. module: account_statement_import_online_gocardless
#: model:ir.model.fields,field_description:account_statement_import_online_gocardless.field_online_bank_statement_provider__gocardless_requisition_ref
msgid "Gocardless Requisition Ref"
msgstr ""
#. module: account_statement_import_online_gocardless
#: model:ir.model.fields,field_description:account_statement_import_online_gocardless.field_online_bank_statement_provider__gocardless_token
msgid "Gocardless Token"
msgstr ""
#. module: account_statement_import_online_gocardless
#: model:ir.model.fields,field_description:account_statement_import_online_gocardless.field_online_bank_statement_provider__gocardless_token_expiration
msgid "Gocardless Token Expiration"
msgstr ""
#. module: account_statement_import_online_gocardless
#: model:ir.model.fields,field_description:account_statement_import_online_gocardless.field_online_bank_statement_provider_existing__id
msgid "ID"
msgstr ""
#. module: account_statement_import_online_gocardless
#. odoo-python
#: code:addons/account_statement_import_online_gocardless/models/online_bank_statement_provider.py:0
#, python-format
msgid "Incorrect country code or country not supported."
msgstr ""
#. module: account_statement_import_online_gocardless
#: model:ir.model.fields,field_description:account_statement_import_online_gocardless.field_online_bank_statement_provider_existing____last_update
msgid "Last Modified on"
msgstr ""
#. module: account_statement_import_online_gocardless
#: model:ir.model.fields,field_description:account_statement_import_online_gocardless.field_online_bank_statement_provider_existing__write_uid
msgid "Last Updated by"
msgstr ""
#. module: account_statement_import_online_gocardless
#: model:ir.model.fields,field_description:account_statement_import_online_gocardless.field_online_bank_statement_provider_existing__write_date
msgid "Last Updated on"
msgstr ""
#. module: account_statement_import_online_gocardless
#: model_terms:ir.ui.view,arch_db:account_statement_import_online_gocardless.view_online_bank_statement_provider_existing_form
msgid "New link"
msgstr ""
#. module: account_statement_import_online_gocardless
#: model:ir.model,name:account_statement_import_online_gocardless.model_online_bank_statement_provider
msgid "Online Bank Statement Provider"
msgstr ""
#. module: account_statement_import_online_gocardless
#: model:ir.model.fields,field_description:account_statement_import_online_gocardless.field_online_bank_statement_provider_existing__other_provider_id
msgid "Other Provider"
msgstr ""
#. module: account_statement_import_online_gocardless
#: model:ir.model.fields,field_description:account_statement_import_online_gocardless.field_online_bank_statement_provider_existing__provider_id
msgid "Provider"
msgstr ""
#. module: account_statement_import_online_gocardless
#: model_terms:ir.ui.view,arch_db:account_statement_import_online_gocardless.view_online_bank_statement_provider_existing_form
msgid "Reuse existing"
msgstr ""
#. module: account_statement_import_online_gocardless
#. odoo-javascript
#: code:addons/account_statement_import_online_gocardless/static/src/xml/select_bank_widget.xml:0
#, python-format
msgid "Search..."
msgstr ""
#. module: account_statement_import_online_gocardless
#: model_terms:ir.ui.view,arch_db:account_statement_import_online_gocardless.online_bank_statement_provider_form
msgid "Secret ID"
msgstr ""
#. module: account_statement_import_online_gocardless
#: model_terms:ir.ui.view,arch_db:account_statement_import_online_gocardless.online_bank_statement_provider_form
msgid "Secret Key"
msgstr ""
#. module: account_statement_import_online_gocardless
#: model_terms:ir.ui.view,arch_db:account_statement_import_online_gocardless.online_bank_statement_provider_form
msgid "Select Bank Account Identifier"
msgstr ""
#. module: account_statement_import_online_gocardless
#. odoo-python
#: code:addons/account_statement_import_online_gocardless/models/online_bank_statement_provider.py:0
#, python-format
msgid "Select Bank of your Account"
msgstr ""
#. module: account_statement_import_online_gocardless
#. odoo-javascript
#: code:addons/account_statement_import_online_gocardless/static/src/xml/select_bank_widget.xml:0
#, python-format
msgid "Select Country to Filter"
msgstr ""
#. module: account_statement_import_online_gocardless
#: model_terms:ir.ui.view,arch_db:account_statement_import_online_gocardless.view_online_bank_statement_provider_existing_form
msgid ""
"Some banks only allow one credentials, while others work better with "
"separate ones, so it's a matter of trying."
msgstr ""
#. module: account_statement_import_online_gocardless
#. odoo-python
#: code:addons/account_statement_import_online_gocardless/models/online_bank_statement_provider.py:0
#, python-format
msgid "To continue configure bank account on journal %s"
msgstr ""
#. module: account_statement_import_online_gocardless
#: model:ir.model,name:account_statement_import_online_gocardless.model_online_bank_statement_provider_existing
msgid "Wizard for reusing existing GoCardless provider"
msgstr ""
#. module: account_statement_import_online_gocardless
#. odoo-python
#: code:addons/account_statement_import_online_gocardless/models/online_bank_statement_provider.py:0
#, python-format
msgid ""
"You should renew the authorization process with your bank institution for "
"GoCardless."
msgstr ""
#. module: account_statement_import_online_gocardless
#. odoo-python
#: code:addons/account_statement_import_online_gocardless/models/online_bank_statement_provider.py:0
#, python-format
msgid "Your account number %(iban_number)s is successfully attached."
msgstr ""
#. module: account_statement_import_online_gocardless
#. odoo-python
#: code:addons/account_statement_import_online_gocardless/models/online_bank_statement_provider.py:0
#, python-format
msgid ""
"Your account number %(iban_number)s it's not in the IBAN account numbers "
"found %(accounts_iban)s, please check"
msgstr ""

View File

@@ -0,0 +1,246 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * account_statement_import_online_gocardless
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 16.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-04-22 18:19+0000\n"
"PO-Revision-Date: \n"
"Last-Translator: MayteGLC <mayte.gonzalez@factorlibre.com>\n"
"Language-Team: none\n"
"Language: es\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 3.0.1\n"
#. module: account_statement_import_online_gocardless
#: model_terms:ir.ui.view,arch_db:account_statement_import_online_gocardless.view_online_bank_statement_provider_existing_form
msgid ""
"<i class=\"fa fa-info-circle\"/> There's already another journal from the "
"same bank institution linked with GoCardless. Do you want to reuse the "
"existing credentials or create new ones?"
msgstr ""
"<i class=\"fa fa-info-circle\"/> Ya existe otro diario de la misma "
"institución bancaria enlazado con GoCardless. ¿Quiere reutilizar las "
"credenciales existentes o crear unas nuevas?"
#. module: account_statement_import_online_gocardless
#. odoo-javascript
#: code:addons/account_statement_import_online_gocardless/static/src/xml/select_bank_widget.xml:0
#, python-format
msgid "Available countries:"
msgstr "Países disponibles:"
#. module: account_statement_import_online_gocardless
#: model:ir.model.fields,field_description:account_statement_import_online_gocardless.field_online_bank_statement_provider_existing__create_uid
msgid "Created by"
msgstr "Creado por"
#. module: account_statement_import_online_gocardless
#: model:ir.model.fields,field_description:account_statement_import_online_gocardless.field_online_bank_statement_provider_existing__create_date
msgid "Created on"
msgstr "Creado el"
#. module: account_statement_import_online_gocardless
#: model:ir.model.fields,field_description:account_statement_import_online_gocardless.field_online_bank_statement_provider_existing__display_name
msgid "Display Name"
msgstr "Nombre mostrado"
#. module: account_statement_import_online_gocardless
#. odoo-python
#: code:addons/account_statement_import_online_gocardless/models/online_bank_statement_provider.py:0
#, python-format
msgid "Existing link"
msgstr "Enlace existente"
#. module: account_statement_import_online_gocardless
#: model_terms:ir.ui.view,arch_db:account_statement_import_online_gocardless.online_bank_statement_provider_form
msgid "GoCardless"
msgstr "GoCardless"
#. module: account_statement_import_online_gocardless
#: model:ir.model.fields,field_description:account_statement_import_online_gocardless.field_online_bank_statement_provider__gocardless_account_id
msgid "Gocardless Account"
msgstr "ID de la cuenta Gocardless"
#. module: account_statement_import_online_gocardless
#: model:ir.model.fields,field_description:account_statement_import_online_gocardless.field_online_bank_statement_provider__gocardless_institution_id
msgid "Gocardless Institution"
msgstr "Banco conectado"
#. module: account_statement_import_online_gocardless
#: model:ir.model.fields,field_description:account_statement_import_online_gocardless.field_online_bank_statement_provider__gocardless_refresh_expiration
msgid "Gocardless Refresh Expiration"
msgstr "Actualizar fecha de caducidad"
#. module: account_statement_import_online_gocardless
#: model:ir.model.fields,field_description:account_statement_import_online_gocardless.field_online_bank_statement_provider__gocardless_refresh_token
msgid "Gocardless Refresh Token"
msgstr "Actualizar Token"
#. module: account_statement_import_online_gocardless
#: model:ir.model.fields,field_description:account_statement_import_online_gocardless.field_online_bank_statement_provider__gocardless_requisition_id
msgid "Gocardless Requisition"
msgstr "ID de la conexión Gocardless"
#. module: account_statement_import_online_gocardless
#: model:ir.model.fields,field_description:account_statement_import_online_gocardless.field_online_bank_statement_provider__gocardless_requisition_expiration
msgid "Gocardless Requisition Expiration"
msgstr "Caducidad de la sincronización"
#. module: account_statement_import_online_gocardless
#: model:ir.model.fields,field_description:account_statement_import_online_gocardless.field_online_bank_statement_provider__gocardless_requisition_ref
msgid "Gocardless Requisition Ref"
msgstr "Gocardless Requisición Ref"
#. module: account_statement_import_online_gocardless
#: model:ir.model.fields,field_description:account_statement_import_online_gocardless.field_online_bank_statement_provider__gocardless_token
msgid "Gocardless Token"
msgstr "Token"
#. module: account_statement_import_online_gocardless
#: model:ir.model.fields,field_description:account_statement_import_online_gocardless.field_online_bank_statement_provider__gocardless_token_expiration
msgid "Gocardless Token Expiration"
msgstr "Caducdad del token"
#. module: account_statement_import_online_gocardless
#: model:ir.model.fields,field_description:account_statement_import_online_gocardless.field_online_bank_statement_provider_existing__id
msgid "ID"
msgstr "Identificador"
#. module: account_statement_import_online_gocardless
#. odoo-python
#: code:addons/account_statement_import_online_gocardless/models/online_bank_statement_provider.py:0
#, python-format
msgid "Incorrect country code or country not supported."
msgstr "Código de país incorrecto o país no admitido."
#. module: account_statement_import_online_gocardless
#: model:ir.model.fields,field_description:account_statement_import_online_gocardless.field_online_bank_statement_provider_existing____last_update
msgid "Last Modified on"
msgstr "Última modificación el"
#. module: account_statement_import_online_gocardless
#: model:ir.model.fields,field_description:account_statement_import_online_gocardless.field_online_bank_statement_provider_existing__write_uid
msgid "Last Updated by"
msgstr "Última modificación por"
#. module: account_statement_import_online_gocardless
#: model:ir.model.fields,field_description:account_statement_import_online_gocardless.field_online_bank_statement_provider_existing__write_date
msgid "Last Updated on"
msgstr "Última modificación el"
#. module: account_statement_import_online_gocardless
#: model_terms:ir.ui.view,arch_db:account_statement_import_online_gocardless.view_online_bank_statement_provider_existing_form
msgid "New link"
msgstr "Nuevo enlace"
#. module: account_statement_import_online_gocardless
#: model:ir.model,name:account_statement_import_online_gocardless.model_online_bank_statement_provider
msgid "Online Bank Statement Provider"
msgstr "Proveedor de Extractos Bancarios en Línea"
#. module: account_statement_import_online_gocardless
#: model:ir.model.fields,field_description:account_statement_import_online_gocardless.field_online_bank_statement_provider_existing__other_provider_id
msgid "Other Provider"
msgstr "Otro proveedor"
#. module: account_statement_import_online_gocardless
#: model:ir.model.fields,field_description:account_statement_import_online_gocardless.field_online_bank_statement_provider_existing__provider_id
msgid "Provider"
msgstr "Proveedor"
#. module: account_statement_import_online_gocardless
#: model_terms:ir.ui.view,arch_db:account_statement_import_online_gocardless.view_online_bank_statement_provider_existing_form
msgid "Reuse existing"
msgstr "Reutilizar existente"
#. module: account_statement_import_online_gocardless
#. odoo-javascript
#: code:addons/account_statement_import_online_gocardless/static/src/xml/select_bank_widget.xml:0
#, python-format
msgid "Search..."
msgstr "Búsqueda..."
#. module: account_statement_import_online_gocardless
#: model_terms:ir.ui.view,arch_db:account_statement_import_online_gocardless.online_bank_statement_provider_form
msgid "Secret ID"
msgstr "ID Secreto"
#. module: account_statement_import_online_gocardless
#: model_terms:ir.ui.view,arch_db:account_statement_import_online_gocardless.online_bank_statement_provider_form
msgid "Secret Key"
msgstr "Clave secreta"
#. module: account_statement_import_online_gocardless
#: model_terms:ir.ui.view,arch_db:account_statement_import_online_gocardless.online_bank_statement_provider_form
msgid "Select Bank Account Identifier"
msgstr "Validar identificador de banco"
#. module: account_statement_import_online_gocardless
#. odoo-python
#: code:addons/account_statement_import_online_gocardless/models/online_bank_statement_provider.py:0
#, python-format
msgid "Select Bank of your Account"
msgstr "Seleccione el Banco de su Cuenta"
#. module: account_statement_import_online_gocardless
#. odoo-javascript
#: code:addons/account_statement_import_online_gocardless/static/src/xml/select_bank_widget.xml:0
#, python-format
msgid "Select Country to Filter"
msgstr "Seleccione el País a Filtrar"
#. module: account_statement_import_online_gocardless
#: model_terms:ir.ui.view,arch_db:account_statement_import_online_gocardless.view_online_bank_statement_provider_existing_form
msgid ""
"Some banks only allow one credentials, while others work better with "
"separate ones, so it's a matter of trying."
msgstr ""
"Algunos bancos solo permiten unas credenciales, mientras otros funcionan "
"mejor con credenciales separadas, así que es una cuestión de probar ambas "
"opciones."
#. module: account_statement_import_online_gocardless
#. odoo-python
#: code:addons/account_statement_import_online_gocardless/models/online_bank_statement_provider.py:0
#, python-format
msgid "To continue configure bank account on journal %s"
msgstr "Para seguir configurando la cuenta bancaria en el diario %s"
#. module: account_statement_import_online_gocardless
#: model:ir.model,name:account_statement_import_online_gocardless.model_online_bank_statement_provider_existing
msgid "Wizard for reusing existing GoCardless provider"
msgstr "Asistente para reutilizar un proveedor de GoCardless existente"
#. module: account_statement_import_online_gocardless
#. odoo-python
#: code:addons/account_statement_import_online_gocardless/models/online_bank_statement_provider.py:0
#, python-format
msgid ""
"You should renew the authorization process with your bank institution for "
"GoCardless."
msgstr ""
"Debe renovar el proceso de autorización con su entidad bancaria para "
"GoCardless."
#. module: account_statement_import_online_gocardless
#. odoo-python
#: code:addons/account_statement_import_online_gocardless/models/online_bank_statement_provider.py:0
#, python-format
msgid "Your account number %(iban_number)s is successfully attached."
msgstr "Su número de cuenta %(iban_number)s se ha adjuntado correctamente."
#. module: account_statement_import_online_gocardless
#. odoo-python
#: code:addons/account_statement_import_online_gocardless/models/online_bank_statement_provider.py:0
#, python-format
msgid ""
"Your account number %(iban_number)s it's not in the IBAN account numbers "
"found %(accounts_iban)s, please check"
msgstr ""
"Su número de cuenta %(iban_number)s no está en los números de cuenta IBAN "
"encontrados %(accounts_iban)s, por favor compruébelo"

View File

@@ -0,0 +1,245 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * account_statement_import_online_gocardless
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 16.0\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2024-11-28 23:06+0000\n"
"Last-Translator: mymage <stefano.consolaro@mymage.it>\n"
"Language-Team: none\n"
"Language: it\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 5.6.2\n"
#. module: account_statement_import_online_gocardless
#: model_terms:ir.ui.view,arch_db:account_statement_import_online_gocardless.view_online_bank_statement_provider_existing_form
msgid ""
"<i class=\"fa fa-info-circle\"/> There's already another journal from the "
"same bank institution linked with GoCardless. Do you want to reuse the "
"existing credentials or create new ones?"
msgstr ""
"<i class=\"fa fa-info-circle\"/> Esiste già un altro registro dello stesso "
"istituto bancario collegato a GoCardless. Si vuole riutilizzare le "
"credenziali esistenti o crearne di nuove?"
#. module: account_statement_import_online_gocardless
#. odoo-javascript
#: code:addons/account_statement_import_online_gocardless/static/src/xml/select_bank_widget.xml:0
#, python-format
msgid "Available countries:"
msgstr "Nazioni disponibili:"
#. module: account_statement_import_online_gocardless
#: model:ir.model.fields,field_description:account_statement_import_online_gocardless.field_online_bank_statement_provider_existing__create_uid
msgid "Created by"
msgstr "Creato da"
#. module: account_statement_import_online_gocardless
#: model:ir.model.fields,field_description:account_statement_import_online_gocardless.field_online_bank_statement_provider_existing__create_date
msgid "Created on"
msgstr "Creato il"
#. module: account_statement_import_online_gocardless
#: model:ir.model.fields,field_description:account_statement_import_online_gocardless.field_online_bank_statement_provider_existing__display_name
msgid "Display Name"
msgstr "Nome visualizzato"
#. module: account_statement_import_online_gocardless
#. odoo-python
#: code:addons/account_statement_import_online_gocardless/models/online_bank_statement_provider.py:0
#, python-format
msgid "Existing link"
msgstr "Link esistente"
#. module: account_statement_import_online_gocardless
#: model_terms:ir.ui.view,arch_db:account_statement_import_online_gocardless.online_bank_statement_provider_form
msgid "GoCardless"
msgstr "GoCardless"
#. module: account_statement_import_online_gocardless
#: model:ir.model.fields,field_description:account_statement_import_online_gocardless.field_online_bank_statement_provider__gocardless_account_id
msgid "Gocardless Account"
msgstr "Conto GoCardless"
#. module: account_statement_import_online_gocardless
#: model:ir.model.fields,field_description:account_statement_import_online_gocardless.field_online_bank_statement_provider__gocardless_institution_id
msgid "Gocardless Institution"
msgstr "Istituto GoCardless"
#. module: account_statement_import_online_gocardless
#: model:ir.model.fields,field_description:account_statement_import_online_gocardless.field_online_bank_statement_provider__gocardless_refresh_expiration
msgid "Gocardless Refresh Expiration"
msgstr "Scadenza aggiornamento GoCadrless"
#. module: account_statement_import_online_gocardless
#: model:ir.model.fields,field_description:account_statement_import_online_gocardless.field_online_bank_statement_provider__gocardless_refresh_token
msgid "Gocardless Refresh Token"
msgstr "Token rinnovo GoCardless"
#. module: account_statement_import_online_gocardless
#: model:ir.model.fields,field_description:account_statement_import_online_gocardless.field_online_bank_statement_provider__gocardless_requisition_id
msgid "Gocardless Requisition"
msgstr "Richiesta GoCardless"
#. module: account_statement_import_online_gocardless
#: model:ir.model.fields,field_description:account_statement_import_online_gocardless.field_online_bank_statement_provider__gocardless_requisition_expiration
msgid "Gocardless Requisition Expiration"
msgstr "Scadenza richiesta GoCardless"
#. module: account_statement_import_online_gocardless
#: model:ir.model.fields,field_description:account_statement_import_online_gocardless.field_online_bank_statement_provider__gocardless_requisition_ref
msgid "Gocardless Requisition Ref"
msgstr "Rif. richiesta GoCardless"
#. module: account_statement_import_online_gocardless
#: model:ir.model.fields,field_description:account_statement_import_online_gocardless.field_online_bank_statement_provider__gocardless_token
msgid "Gocardless Token"
msgstr "Token GoCardless"
#. module: account_statement_import_online_gocardless
#: model:ir.model.fields,field_description:account_statement_import_online_gocardless.field_online_bank_statement_provider__gocardless_token_expiration
msgid "Gocardless Token Expiration"
msgstr "Scadenza token GoCardless"
#. module: account_statement_import_online_gocardless
#: model:ir.model.fields,field_description:account_statement_import_online_gocardless.field_online_bank_statement_provider_existing__id
msgid "ID"
msgstr "ID"
#. module: account_statement_import_online_gocardless
#. odoo-python
#: code:addons/account_statement_import_online_gocardless/models/online_bank_statement_provider.py:0
#, python-format
msgid "Incorrect country code or country not supported."
msgstr "Codice nazione errato o nazione non supportata."
#. module: account_statement_import_online_gocardless
#: model:ir.model.fields,field_description:account_statement_import_online_gocardless.field_online_bank_statement_provider_existing____last_update
msgid "Last Modified on"
msgstr "Ultima modifica il"
#. module: account_statement_import_online_gocardless
#: model:ir.model.fields,field_description:account_statement_import_online_gocardless.field_online_bank_statement_provider_existing__write_uid
msgid "Last Updated by"
msgstr "Ultimo aggiornamento di"
#. module: account_statement_import_online_gocardless
#: model:ir.model.fields,field_description:account_statement_import_online_gocardless.field_online_bank_statement_provider_existing__write_date
msgid "Last Updated on"
msgstr "Ultimo aggiornamento il"
#. module: account_statement_import_online_gocardless
#: model_terms:ir.ui.view,arch_db:account_statement_import_online_gocardless.view_online_bank_statement_provider_existing_form
msgid "New link"
msgstr "Nuovo link"
#. module: account_statement_import_online_gocardless
#: model:ir.model,name:account_statement_import_online_gocardless.model_online_bank_statement_provider
msgid "Online Bank Statement Provider"
msgstr "Fornitore estratto conto bancario online"
#. module: account_statement_import_online_gocardless
#: model:ir.model.fields,field_description:account_statement_import_online_gocardless.field_online_bank_statement_provider_existing__other_provider_id
msgid "Other Provider"
msgstr "Altro fornitore"
#. module: account_statement_import_online_gocardless
#: model:ir.model.fields,field_description:account_statement_import_online_gocardless.field_online_bank_statement_provider_existing__provider_id
msgid "Provider"
msgstr "Fornitore"
#. module: account_statement_import_online_gocardless
#: model_terms:ir.ui.view,arch_db:account_statement_import_online_gocardless.view_online_bank_statement_provider_existing_form
msgid "Reuse existing"
msgstr "Riusare esistente"
#. module: account_statement_import_online_gocardless
#. odoo-javascript
#: code:addons/account_statement_import_online_gocardless/static/src/xml/select_bank_widget.xml:0
#, python-format
msgid "Search..."
msgstr "Cerca..."
#. module: account_statement_import_online_gocardless
#: model_terms:ir.ui.view,arch_db:account_statement_import_online_gocardless.online_bank_statement_provider_form
msgid "Secret ID"
msgstr "ID segreto"
#. module: account_statement_import_online_gocardless
#: model_terms:ir.ui.view,arch_db:account_statement_import_online_gocardless.online_bank_statement_provider_form
msgid "Secret Key"
msgstr "Chiave segreta"
#. module: account_statement_import_online_gocardless
#: model_terms:ir.ui.view,arch_db:account_statement_import_online_gocardless.online_bank_statement_provider_form
msgid "Select Bank Account Identifier"
msgstr "Seleziona identificatore conto bancario"
#. module: account_statement_import_online_gocardless
#. odoo-python
#: code:addons/account_statement_import_online_gocardless/models/online_bank_statement_provider.py:0
#, python-format
msgid "Select Bank of your Account"
msgstr "Selezionare la banca del proprio conto"
#. module: account_statement_import_online_gocardless
#. odoo-javascript
#: code:addons/account_statement_import_online_gocardless/static/src/xml/select_bank_widget.xml:0
#, python-format
msgid "Select Country to Filter"
msgstr "Selezionare la nazione da filtrare"
#. module: account_statement_import_online_gocardless
#: model_terms:ir.ui.view,arch_db:account_statement_import_online_gocardless.view_online_bank_statement_provider_existing_form
msgid ""
"Some banks only allow one credentials, while others work better with "
"separate ones, so it's a matter of trying."
msgstr ""
"Alcune banche consentono solo una credenziale, mentre altre funzionano "
"meglio con credenziali separate, quindi è solo questione di tentativi."
#. module: account_statement_import_online_gocardless
#. odoo-python
#: code:addons/account_statement_import_online_gocardless/models/online_bank_statement_provider.py:0
#, python-format
msgid "To continue configure bank account on journal %s"
msgstr "Per procedere configurare conto bancario nel registro %s"
#. module: account_statement_import_online_gocardless
#: model:ir.model,name:account_statement_import_online_gocardless.model_online_bank_statement_provider_existing
msgid "Wizard for reusing existing GoCardless provider"
msgstr "Procedura guidata per riutilizzare il fornitore GoCardless esistente"
#. module: account_statement_import_online_gocardless
#. odoo-python
#: code:addons/account_statement_import_online_gocardless/models/online_bank_statement_provider.py:0
#, python-format
msgid ""
"You should renew the authorization process with your bank institution for "
"GoCardless."
msgstr ""
"Bisogna rinnovare il processo di autorizzazione per GoCardless con il "
"proprio istituto bancario."
#. module: account_statement_import_online_gocardless
#. odoo-python
#: code:addons/account_statement_import_online_gocardless/models/online_bank_statement_provider.py:0
#, python-format
msgid "Your account number %(iban_number)s is successfully attached."
msgstr "Il numero di conto %(iban_number)s è allegato correttamente."
#. module: account_statement_import_online_gocardless
#. odoo-python
#: code:addons/account_statement_import_online_gocardless/models/online_bank_statement_provider.py:0
#, python-format
msgid ""
"Your account number %(iban_number)s it's not in the IBAN account numbers "
"found %(accounts_iban)s, please check"
msgstr ""
"Il numero di conto %(iban_number)s non è nel conto IBAN %(accounts_iban)s "
"trovato , controllare"

View File

@@ -0,0 +1,3 @@
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from . import online_bank_statement_provider

View File

@@ -0,0 +1,423 @@
# Copyright 2022 ForgeFlow S.L.
# Copyright 2023-2024 Tecnativa - Pedro M. Baeza
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
import json
from datetime import datetime
from uuid import uuid4
import requests
from dateutil.relativedelta import relativedelta
from werkzeug.urls import url_join
from odoo import _, api, fields, models
from odoo.exceptions import UserError
from odoo.tools import DEFAULT_SERVER_DATE_FORMAT as DF
GOCARDLESS_API = "https://bankaccountdata.gocardless.com/api/v2/"
REQUESTS_TIMEOUT = 60
class OnlineBankStatementProvider(models.Model):
_inherit = "online.bank.statement.provider"
gocardless_token = fields.Char(readonly=True)
gocardless_token_expiration = fields.Datetime(readonly=True)
gocardless_refresh_token = fields.Char(readonly=True)
gocardless_refresh_expiration = fields.Datetime(readonly=True)
gocardless_requisition_ref = fields.Char(readonly=True)
gocardless_requisition_id = fields.Char(readonly=True)
gocardless_requisition_expiration = fields.Datetime(readonly=True)
gocardless_institution_id = fields.Char()
gocardless_account_id = fields.Char()
gocardless_country_id = fields.Many2one(
"res.country",
compute="_compute_gocardless_country_id",
store=True,
readonly=False,
)
@api.depends("journal_id", "company_id")
def _compute_gocardless_country_id(self):
for provider in self:
provider.gocardless_country_id = (
provider.journal_id.bank_account_id.company_id
or provider.journal_id.company_id
).country_id
def gocardless_reset_requisition(self):
self.write(
{
"gocardless_requisition_id": False,
"gocardless_requisition_ref": False,
"gocardless_requisition_expiration": False,
}
)
@api.model
def _get_available_services(self):
"""Include the new service GoCardless in the online providers."""
return super()._get_available_services() + [
("gocardless", "GoCardless"),
]
def _gocardless_get_headers(self, basic=False):
"""Generic method for providing the needed request headers."""
self.ensure_one()
headers = {
"accept": "application/json",
"Content-Type": "application/json",
}
if not basic:
headers["Authorization"] = f"Bearer {self._gocardless_get_token()}"
return headers
def _gocardless_request(
self, endpoint, request_type="get", params=None, data=None, basic_auth=False
):
content = {}
url = url_join(GOCARDLESS_API, endpoint) + "/"
response = getattr(requests, request_type)(
url,
data=data,
params=params,
headers=self._gocardless_get_headers(basic=basic_auth),
timeout=REQUESTS_TIMEOUT,
)
if response.status_code in [200, 201]:
content = json.loads(response.text)
return response, content
def _gocardless_get_token(self):
"""Resolve and return the corresponding GoCardless token for doing the requests.
If there's still no token, it's requested. If it exists, but it's expired and
the refresh token isn't, a refresh is requested.
"""
self.ensure_one()
now = fields.Datetime.now()
if not self.gocardless_token or now > self.gocardless_token_expiration:
# Refresh token
if (
self.gocardless_refresh_token
and now > self.gocardless_refresh_expiration
):
endpoint = "token/refresh"
else:
endpoint = "token/new"
_response, data = self._gocardless_request(
endpoint,
request_type="post",
data=json.dumps(
{"secret_id": self.username, "secret_key": self.password}
),
basic_auth=True,
)
expiration_date = now + relativedelta(seconds=data.get("access_expires", 0))
vals = {
"gocardless_token": data.get("access", False),
"gocardless_token_expiration": expiration_date,
}
if data.get("refresh"):
vals["gocardless_refresh_token"] = data["refresh"]
vals["gocardless_refresh_expiration"] = now + relativedelta(
seconds=data["refresh_expires"]
)
self.sudo().write(vals)
return self.gocardless_token
def action_select_gocardless_bank(self):
if not self.journal_id.bank_account_id:
raise UserError(
_("To continue configure bank account on journal %s")
% (self.journal_id.display_name)
)
# Check if there's another existing provider for the same bank institution,
# and ask for reusing it for this bank account, as some banks don't allow
# several requisitions from the same source (GoCardless).
other = self.search(
[
("service", "=", "gocardless"),
("gocardless_requisition_id", "!=", False),
("journal_id.bank_id", "=", self.journal_id.bank_id.id),
("id", "!=", self.id),
],
limit=1,
)
if other:
wizard = self.env["online.bank.statement.provider.existing"].create(
{
"provider_id": self.id,
"other_provider_id": other.id,
}
)
return {
"type": "ir.actions.act_window",
"res_model": wizard._name,
"res_id": wizard.id,
"name": _("Existing link"),
"view_mode": "form",
"target": "new",
}
return self._gocardless_select_bank_institution()
def _gocardless_select_bank_institution(self):
"""Ask for the GoCardless bank instituion and continue full linkage."""
country = self.gocardless_country_id
response, data = self._gocardless_request(
"institutions", params={"country": country.code}
)
if response.status_code == 400:
raise UserError(_("Incorrect country code or country not supported."))
institutions = data
# Prepare data for being showed in the JS widget
ctx = self.env.context.copy()
ctx.update(
{
"dialog_size": "medium",
"country": country.code,
"country_name": country.name,
"provider_id": self.id,
"institutions": institutions,
"country_names": [{"code": country.code, "name": country.name}],
}
)
return {
"type": "ir.actions.client",
"tag": "online_sync_institution_selector_gocardless",
"name": _("Select Bank of your Account"),
"params": {},
"target": "new",
"context": ctx,
}
def action_check_gocardless_agreement(self):
self.ensure_one()
self.gocardless_requisition_ref = str(uuid4())
base_url = self.env["ir.config_parameter"].sudo().get_param("web.base.url")
redirect_url = url_join(base_url, "gocardless/response")
_response, data = self._gocardless_request(
"requisitions",
request_type="post",
data=json.dumps(
{
"redirect": redirect_url,
"institution_id": self.gocardless_institution_id,
"reference": self.gocardless_requisition_ref,
}
),
)
if data:
requisition_data = data
self.gocardless_requisition_id = requisition_data["id"]
# JS code expects here to return a plain link or nothing
return requisition_data["link"]
def _gocardless_request_requisition(self):
_response, data = self._gocardless_request(
f"requisitions/{self.gocardless_requisition_id}"
)
return data
def _gocardless_request_account(self, account_id):
_response, data = self._gocardless_request(f"accounts/{account_id}")
return data
def _gocardless_request_agreement(self, agreement_id):
_response, data = self._gocardless_request(f"agreements/enduser/{agreement_id}")
return data
def _gocardless_finish_requisition(self, dry=False):
"""Once the requisiton to the bank institution has been made, and this is called
from the controller assigned to the redirect URL, we check that the IBAN account
of the linked journal is included in the accessible bank accounts, and if so,
we set the rest of the needed data.
A message in the chatter is logged both for sucessful or failed operation (this
last one only if not in dry mode).
:param: dry: If true, this is called as previous step before starting the whole
process, so no fail message is logged in chatter in this case.
"""
self.ensure_one()
requisition_data = self._gocardless_request_requisition()
accounts = requisition_data.get("accounts", [])
found_account = False
accounts_iban = []
for account_id in accounts:
account_data = self._gocardless_request_account(account_id)
if account_data:
accounts_iban.append(account_data["iban"])
if (
self.journal_id.bank_account_id.sanitized_acc_number
== account_data["iban"].upper()
):
found_account = True
self.gocardless_account_id = account_data["id"]
break
if found_account:
agreement_data = self._gocardless_request_agreement(
requisition_data["agreement"]
)
self.gocardless_requisition_expiration = datetime.strptime(
agreement_data["accepted"], "%Y-%m-%dT%H:%M:%S.%fZ"
) + relativedelta(days=agreement_data["access_valid_for_days"])
self.sudo().message_post(
body=_("Your account number %(iban_number)s is successfully attached.")
% {"iban_number": self.journal_id.bank_account_id.display_name}
)
return True
elif not dry:
self.sudo().write(
{
"gocardless_requisition_expiration": False,
"gocardless_requisition_id": False,
"gocardless_requisition_ref": False,
}
)
self.sudo().message_post(
body=_(
"Your account number %(iban_number)s it's not in the IBAN "
"account numbers found %(accounts_iban)s, please check"
)
% {
"iban_number": self.journal_id.bank_account_id.display_name,
"accounts_iban": " / ".join(accounts_iban),
}
)
return False
def _obtain_statement_data(self, date_since, date_until):
"""Generic online cron overrided for acting when the sync is for GoCardless."""
self.ensure_one()
if self.service == "gocardless":
return self._gocardless_obtain_statement_data(date_since, date_until)
return super()._obtain_statement_data(date_since, date_until)
def _gocardless_request_transactions(self, date_since, date_until):
"""Method for requesting GoCardless transactions.
Isolated for being mocked in tests.
"""
# We can't query dates in the future in GoCardless
now = fields.Datetime.now()
if now > date_since and now < date_until:
date_until = now
_response, data = self._gocardless_request(
f"accounts/{self.gocardless_account_id}/transactions",
params={
"date_from": date_since.strftime(DF),
"date_to": date_until.strftime(DF),
},
)
return data
def _gocardless_obtain_statement_data(self, date_since, date_until):
"""Called from the cron or the manual pull wizard to obtain transactions for
the given period.
"""
self.ensure_one()
if not self.gocardless_account_id:
return
currency_model = self.env["res.currency"]
if self.gocardless_requisition_expiration <= fields.Datetime.now():
self.sudo().message_post(
body=_(
"You should renew the authorization process with your bank "
"institution for GoCardless."
)
)
return [], {}
own_acc_number = self.journal_id.bank_account_id.sanitized_acc_number
transactions = self._gocardless_request_transactions(date_since, date_until)
res = []
sequence = 0
currencies_cache = {}
for tr in transactions.get("transactions", {}).get("booked", []):
# Reference: https://developer.gocardless.com/bank-account-data/transactions
string_date = tr.get("bookingDate") or tr.get("valueDate")
# CHECK ME: if there's not date string, is transaction still valid?
if not string_date:
continue
current_date = fields.Date.from_string(string_date)
sequence += 1
amount = float(tr.get("transactionAmount", {}).get("amount", 0.0))
currency_code = tr.get("transactionAmount", {}).get(
"currency", self.journal_id.currency_id.name
)
currency = currencies_cache.get(currency_code)
if not currency:
currency = currency_model.search([("name", "=", currency_code)])
currencies_cache[currency_code] = currency
amount_currency = amount
if (
currency
and self.journal_id.currency_id
and currency != self.journal_id.currency_id
):
amount_currency = currency._convert(
amount,
self.journal_id.currency_id,
self.journal_id.company_id,
current_date,
)
if amount_currency >= 0:
partner_name = tr.get("debtorName", False)
else:
partner_name = tr.get("creditorName", False)
account_number = tr.get("debtorAccount", {}).get("iban") or tr.get(
"creditorAccount", {}
).get("iban", False)
if account_number == own_acc_number:
account_number = False # Discard own bank account number
if "remittanceInformationUnstructured" in tr:
payment_ref = tr["remittanceInformationUnstructured"]
elif "remittanceInformationUnstructuredArray" in tr:
payment_ref = " ".join(tr["remittanceInformationUnstructuredArray"])
else:
payment_ref = partner_name
res.append(
{
"sequence": sequence,
"date": current_date,
"ref": partner_name or "/",
"payment_ref": payment_ref,
"unique_import_id": (
tr.get("entryReference")
or tr.get("transactionId")
or tr.get("internalTransactionId")
),
"amount": amount_currency,
"account_number": account_number,
"partner_name": partner_name,
"transaction_type": tr.get("bankTransactionCode", ""),
"narration": self._gocardless_get_note(tr),
}
)
return res, {}
def _gocardless_get_note(self, tr):
"""Override to get different notes."""
note_elements = [
"additionalInformation",
"balanceAfterTransaction",
"bankTransactionCode",
"bookingDate",
"checkId",
"creditorAccount",
"creditorAgent",
"creditorId",
"creditorName",
"currencyExchange",
"debtorAccount",
"debtorAgent",
"debtorName",
"entryReference",
"mandateId",
"proprietaryBank",
"remittanceInformation Unstructured",
"transactionAmount",
"transactionId",
"ultimateCreditor",
"ultimateDebtor",
"valueDate",
]
notes = [str(tr[element]) for element in note_elements if tr.get(element)]
return "\n".join(notes)

View File

@@ -0,0 +1,3 @@
[build-system]
requires = ["whool"]
build-backend = "whool.buildapi"

View File

@@ -0,0 +1,51 @@
## On the GoCardless website
1. Go to <https://bankaccountdata.gocardless.com>, and create or login
into your "GoCardLess Bank Account Data" account.
2. Go to Developers \> User secrets option on the left.
3. Click on the "+ Create new" button on the bottom part.
4. Put a name to the user secret (eg. Odoo), and optionally limit it to
certain IPs using CIDR subnet notation.
5. Copy or download the secret ID and key for later use. The second one
won't be available anymore, so make sure you don't forget this step.
## On Odoo
To configure online bank statements provider:
1. Add your user to the "Full Accounting Settings" group.
2. Go to *Invoicing \> Configuration \> Accounting \> Journals*.
3. Select the journal representing your bank account (or create it).
4. The bank account number should be properly introduced.
5. Set *Bank Feeds* to *Online (OCA)*.
6. Select *GoCardless* as online bank statements provider in *Online
Bank Statements (OCA)* section.
7. Save the journal
8. Click on the created provider.
9. Put your secret ID and secret key on the existing fields.
10. Click on the button "Select Bank Account Identifier".
![image_01](../static/img/gocardless_configuration.gif)
11. A new window will appear for selecting the bank entity.
![image_02](../static/img/gocardless_bank_selection.gif)
12. Select it, and you will be redirected to the selected entity for
introducing your bank credentials to allow the connection.
13. If the process is completed, and the bank account linked to the
journal is accessible, you'll be again redirected to the online
provider form, and everything will be linked and ready to start the
transaction pulling. A message is logged about it on the chatter.
14. If not, an error message will be logged either in the chatter.

View File

@@ -0,0 +1,7 @@
- [ForgeFlow](https://www.forgeflow.com):
- Christopher Ormaza
- Jordi Ballester
- [Tecnativa](https://www.tecnativa.com):
- Pedro M. Baeza
- [Alusage](https://nicolas.alusage.fr):
- Nicolas JEUDY \<<https://github.com/njeudy>\>

View File

@@ -0,0 +1,3 @@
This module provides online bank statements from GoCardless Bank Account
Data, which provides a free API for connecting and getting transactions
for bank accounts.

View File

@@ -0,0 +1,10 @@
To pull historical bank statements:
1. Go to *Invoicing \> Configuration \> Accounting \> Journals*.
2. Select the journal representing your bank account.
3. Launch *Actions \> Online Bank Statements Pull Wizard*
4. Configure date interval and click on *Pull*.
If historical data is not needed, then just simply wait for the
scheduled activity "Pull Online Bank Statements" to be executed for
getting new transactions.

View File

@@ -0,0 +1,2 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_online_bank_statement_provider_existing,access_online_bank_statement_provider_existing,model_online_bank_statement_provider_existing,account.group_account_user,1,1,1,0
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_online_bank_statement_provider_existing access_online_bank_statement_provider_existing model_online_bank_statement_provider_existing account.group_account_user 1 1 1 0

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

View File

@@ -0,0 +1,516 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="generator" content="Docutils: https://docutils.sourceforge.io/" />
<title>Online Bank Statements: GoCardless</title>
<style type="text/css">
/*
:Author: David Goodger (goodger@python.org)
:Id: $Id: html4css1.css 9511 2024-01-13 09:50:07Z milde $
:Copyright: This stylesheet has been placed in the public domain.
Default cascading style sheet for the HTML output of Docutils.
Despite the name, some widely supported CSS2 features are used.
See https://docutils.sourceforge.io/docs/howto/html-stylesheets.html for how to
customize this style sheet.
*/
/* used to remove borders from tables and images */
.borderless, table.borderless td, table.borderless th {
border: 0 }
table.borderless td, table.borderless th {
/* Override padding for "table.docutils td" with "! important".
The right padding separates the table cells. */
padding: 0 0.5em 0 0 ! important }
.first {
/* Override more specific margin styles with "! important". */
margin-top: 0 ! important }
.last, .with-subtitle {
margin-bottom: 0 ! important }
.hidden {
display: none }
.subscript {
vertical-align: sub;
font-size: smaller }
.superscript {
vertical-align: super;
font-size: smaller }
a.toc-backref {
text-decoration: none ;
color: black }
blockquote.epigraph {
margin: 2em 5em ; }
dl.docutils dd {
margin-bottom: 0.5em }
object[type="image/svg+xml"], object[type="application/x-shockwave-flash"] {
overflow: hidden;
}
/* Uncomment (and remove this text!) to get bold-faced definition list terms
dl.docutils dt {
font-weight: bold }
*/
div.abstract {
margin: 2em 5em }
div.abstract p.topic-title {
font-weight: bold ;
text-align: center }
div.admonition, div.attention, div.caution, div.danger, div.error,
div.hint, div.important, div.note, div.tip, div.warning {
margin: 2em ;
border: medium outset ;
padding: 1em }
div.admonition p.admonition-title, div.hint p.admonition-title,
div.important p.admonition-title, div.note p.admonition-title,
div.tip p.admonition-title {
font-weight: bold ;
font-family: sans-serif }
div.attention p.admonition-title, div.caution p.admonition-title,
div.danger p.admonition-title, div.error p.admonition-title,
div.warning p.admonition-title, .code .error {
color: red ;
font-weight: bold ;
font-family: sans-serif }
/* Uncomment (and remove this text!) to get reduced vertical space in
compound paragraphs.
div.compound .compound-first, div.compound .compound-middle {
margin-bottom: 0.5em }
div.compound .compound-last, div.compound .compound-middle {
margin-top: 0.5em }
*/
div.dedication {
margin: 2em 5em ;
text-align: center ;
font-style: italic }
div.dedication p.topic-title {
font-weight: bold ;
font-style: normal }
div.figure {
margin-left: 2em ;
margin-right: 2em }
div.footer, div.header {
clear: both;
font-size: smaller }
div.line-block {
display: block ;
margin-top: 1em ;
margin-bottom: 1em }
div.line-block div.line-block {
margin-top: 0 ;
margin-bottom: 0 ;
margin-left: 1.5em }
div.sidebar {
margin: 0 0 0.5em 1em ;
border: medium outset ;
padding: 1em ;
background-color: #ffffee ;
width: 40% ;
float: right ;
clear: right }
div.sidebar p.rubric {
font-family: sans-serif ;
font-size: medium }
div.system-messages {
margin: 5em }
div.system-messages h1 {
color: red }
div.system-message {
border: medium outset ;
padding: 1em }
div.system-message p.system-message-title {
color: red ;
font-weight: bold }
div.topic {
margin: 2em }
h1.section-subtitle, h2.section-subtitle, h3.section-subtitle,
h4.section-subtitle, h5.section-subtitle, h6.section-subtitle {
margin-top: 0.4em }
h1.title {
text-align: center }
h2.subtitle {
text-align: center }
hr.docutils {
width: 75% }
img.align-left, .figure.align-left, object.align-left, table.align-left {
clear: left ;
float: left ;
margin-right: 1em }
img.align-right, .figure.align-right, object.align-right, table.align-right {
clear: right ;
float: right ;
margin-left: 1em }
img.align-center, .figure.align-center, object.align-center {
display: block;
margin-left: auto;
margin-right: auto;
}
table.align-center {
margin-left: auto;
margin-right: auto;
}
.align-left {
text-align: left }
.align-center {
clear: both ;
text-align: center }
.align-right {
text-align: right }
/* reset inner alignment in figures */
div.align-right {
text-align: inherit }
/* div.align-center * { */
/* text-align: left } */
.align-top {
vertical-align: top }
.align-middle {
vertical-align: middle }
.align-bottom {
vertical-align: bottom }
ol.simple, ul.simple {
margin-bottom: 1em }
ol.arabic {
list-style: decimal }
ol.loweralpha {
list-style: lower-alpha }
ol.upperalpha {
list-style: upper-alpha }
ol.lowerroman {
list-style: lower-roman }
ol.upperroman {
list-style: upper-roman }
p.attribution {
text-align: right ;
margin-left: 50% }
p.caption {
font-style: italic }
p.credits {
font-style: italic ;
font-size: smaller }
p.label {
white-space: nowrap }
p.rubric {
font-weight: bold ;
font-size: larger ;
color: maroon ;
text-align: center }
p.sidebar-title {
font-family: sans-serif ;
font-weight: bold ;
font-size: larger }
p.sidebar-subtitle {
font-family: sans-serif ;
font-weight: bold }
p.topic-title {
font-weight: bold }
pre.address {
margin-bottom: 0 ;
margin-top: 0 ;
font: inherit }
pre.literal-block, pre.doctest-block, pre.math, pre.code {
margin-left: 2em ;
margin-right: 2em }
pre.code .ln { color: gray; } /* line numbers */
pre.code, code { background-color: #eeeeee }
pre.code .comment, code .comment { color: #5C6576 }
pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold }
pre.code .literal.string, code .literal.string { color: #0C5404 }
pre.code .name.builtin, code .name.builtin { color: #352B84 }
pre.code .deleted, code .deleted { background-color: #DEB0A1}
pre.code .inserted, code .inserted { background-color: #A3D289}
span.classifier {
font-family: sans-serif ;
font-style: oblique }
span.classifier-delimiter {
font-family: sans-serif ;
font-weight: bold }
span.interpreted {
font-family: sans-serif }
span.option {
white-space: nowrap }
span.pre {
white-space: pre }
span.problematic, pre.problematic {
color: red }
span.section-subtitle {
/* font-size relative to parent (h1..h6 element) */
font-size: 80% }
table.citation {
border-left: solid 1px gray;
margin-left: 1px }
table.docinfo {
margin: 2em 4em }
table.docutils {
margin-top: 0.5em ;
margin-bottom: 0.5em }
table.footnote {
border-left: solid 1px black;
margin-left: 1px }
table.docutils td, table.docutils th,
table.docinfo td, table.docinfo th {
padding-left: 0.5em ;
padding-right: 0.5em ;
vertical-align: top }
table.docutils th.field-name, table.docinfo th.docinfo-name {
font-weight: bold ;
text-align: left ;
white-space: nowrap ;
padding-left: 0 }
/* "booktabs" style (no vertical lines) */
table.docutils.booktabs {
border: 0px;
border-top: 2px solid;
border-bottom: 2px solid;
border-collapse: collapse;
}
table.docutils.booktabs * {
border: 0px;
}
table.docutils.booktabs th {
border-bottom: thin solid;
text-align: left;
}
h1 tt.docutils, h2 tt.docutils, h3 tt.docutils,
h4 tt.docutils, h5 tt.docutils, h6 tt.docutils {
font-size: 100% }
ul.auto-toc {
list-style-type: none }
</style>
</head>
<body>
<div class="document" id="online-bank-statements-gocardless">
<h1 class="title">Online Bank Statements: GoCardless</h1>
<!-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:86c3a6944d510b258d44f2a74e4e9e433cb0e4f9745ca156414b7a5c0d83efb1
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
<p><a class="reference external image-reference" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external image-reference" href="http://www.gnu.org/licenses/agpl-3.0-standalone.html"><img alt="License: AGPL-3" src="https://img.shields.io/badge/licence-AGPL--3-blue.png" /></a> <a class="reference external image-reference" href="https://github.com/OCA/bank-statement-import/tree/17.0/account_statement_import_online_gocardless"><img alt="OCA/bank-statement-import" src="https://img.shields.io/badge/github-OCA%2Fbank--statement--import-lightgray.png?logo=github" /></a> <a class="reference external image-reference" href="https://translation.odoo-community.org/projects/bank-statement-import-17-0/bank-statement-import-17-0-account_statement_import_online_gocardless"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external image-reference" href="https://runboat.odoo-community.org/builds?repo=OCA/bank-statement-import&amp;target_branch=17.0"><img alt="Try me on Runboat" src="https://img.shields.io/badge/runboat-Try%20me-875A7B.png" /></a></p>
<p>This module provides online bank statements from GoCardless Bank Account
Data, which provides a free API for connecting and getting transactions
for bank accounts.</p>
<p><strong>Table of contents</strong></p>
<div class="contents local topic" id="contents">
<ul class="simple">
<li><a class="reference internal" href="#configuration" id="toc-entry-1">Configuration</a><ul>
<li><a class="reference internal" href="#on-the-gocardless-website" id="toc-entry-2">On the GoCardless website</a></li>
<li><a class="reference internal" href="#on-odoo" id="toc-entry-3">On Odoo</a></li>
</ul>
</li>
<li><a class="reference internal" href="#usage" id="toc-entry-4">Usage</a></li>
<li><a class="reference internal" href="#bug-tracker" id="toc-entry-5">Bug Tracker</a></li>
<li><a class="reference internal" href="#credits" id="toc-entry-6">Credits</a><ul>
<li><a class="reference internal" href="#authors" id="toc-entry-7">Authors</a></li>
<li><a class="reference internal" href="#contributors" id="toc-entry-8">Contributors</a></li>
<li><a class="reference internal" href="#maintainers" id="toc-entry-9">Maintainers</a></li>
</ul>
</li>
</ul>
</div>
<div class="section" id="configuration">
<h1><a class="toc-backref" href="#toc-entry-1">Configuration</a></h1>
<div class="section" id="on-the-gocardless-website">
<h2><a class="toc-backref" href="#toc-entry-2">On the GoCardless website</a></h2>
<ol class="arabic simple">
<li>Go to
<a class="reference external" href="https://bankaccountdata.gocardless.com">https://bankaccountdata.gocardless.com</a>,
and create or login into your “GoCardLess Bank Account Data” account.</li>
<li>Go to Developers &gt; User secrets option on the left.</li>
<li>Click on the “+ Create new” button on the bottom part.</li>
<li>Put a name to the user secret (eg. Odoo), and optionally limit it to
certain IPs using CIDR subnet notation.</li>
<li>Copy or download the secret ID and key for later use. The second one
wont be available anymore, so make sure you dont forget this step.</li>
</ol>
</div>
<div class="section" id="on-odoo">
<h2><a class="toc-backref" href="#toc-entry-3">On Odoo</a></h2>
<p>To configure online bank statements provider:</p>
<ol class="arabic">
<li><p class="first">Add your user to the “Full Accounting Settings” group.</p>
</li>
<li><p class="first">Go to <em>Invoicing &gt; Configuration &gt; Accounting &gt; Journals</em>.</p>
</li>
<li><p class="first">Select the journal representing your bank account (or create it).</p>
</li>
<li><p class="first">The bank account number should be properly introduced.</p>
</li>
<li><p class="first">Set <em>Bank Feeds</em> to <em>Online (OCA)</em>.</p>
</li>
<li><p class="first">Select <em>GoCardless</em> as online bank statements provider in <em>Online
Bank Statements (OCA)</em> section.</p>
</li>
<li><p class="first">Save the journal</p>
</li>
<li><p class="first">Click on the created provider.</p>
</li>
<li><p class="first">Put your secret ID and secret key on the existing fields.</p>
</li>
<li><p class="first">Click on the button “Select Bank Account Identifier”.</p>
<p><img alt="image_01" src="https://raw.githubusercontent.com/OCA/bank-statement-import/17.0/account_statement_import_online_gocardless/static/img/gocardless_configuration.gif" /></p>
</li>
<li><p class="first">A new window will appear for selecting the bank entity.</p>
<p><img alt="image_02" src="https://raw.githubusercontent.com/OCA/bank-statement-import/17.0/account_statement_import_online_gocardless/static/img/gocardless_bank_selection.gif" /></p>
</li>
<li><p class="first">Select it, and you will be redirected to the selected entity for
introducing your bank credentials to allow the connection.</p>
</li>
<li><p class="first">If the process is completed, and the bank account linked to the
journal is accessible, youll be again redirected to the online
provider form, and everything will be linked and ready to start the
transaction pulling. A message is logged about it on the chatter.</p>
</li>
<li><p class="first">If not, an error message will be logged either in the chatter.</p>
</li>
</ol>
</div>
</div>
<div class="section" id="usage">
<h1><a class="toc-backref" href="#toc-entry-4">Usage</a></h1>
<p>To pull historical bank statements:</p>
<ol class="arabic simple">
<li>Go to <em>Invoicing &gt; Configuration &gt; Accounting &gt; Journals</em>.</li>
<li>Select the journal representing your bank account.</li>
<li>Launch <em>Actions &gt; Online Bank Statements Pull Wizard</em></li>
<li>Configure date interval and click on <em>Pull</em>.</li>
</ol>
<p>If historical data is not needed, then just simply wait for the
scheduled activity “Pull Online Bank Statements” to be executed for
getting new transactions.</p>
</div>
<div class="section" id="bug-tracker">
<h1><a class="toc-backref" href="#toc-entry-5">Bug Tracker</a></h1>
<p>Bugs are tracked on <a class="reference external" href="https://github.com/OCA/bank-statement-import/issues">GitHub Issues</a>.
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
<a class="reference external" href="https://github.com/OCA/bank-statement-import/issues/new?body=module:%20account_statement_import_online_gocardless%0Aversion:%2017.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**">feedback</a>.</p>
<p>Do not contact contributors directly about support or help with technical issues.</p>
</div>
<div class="section" id="credits">
<h1><a class="toc-backref" href="#toc-entry-6">Credits</a></h1>
<div class="section" id="authors">
<h2><a class="toc-backref" href="#toc-entry-7">Authors</a></h2>
<ul class="simple">
<li>ForgeFlow</li>
<li>Tecnativa</li>
</ul>
</div>
<div class="section" id="contributors">
<h2><a class="toc-backref" href="#toc-entry-8">Contributors</a></h2>
<ul class="simple">
<li><a class="reference external" href="https://www.forgeflow.com">ForgeFlow</a>:<ul>
<li>Christopher Ormaza</li>
<li>Jordi Ballester</li>
</ul>
</li>
<li><a class="reference external" href="https://www.tecnativa.com">Tecnativa</a>:<ul>
<li>Pedro M. Baeza</li>
</ul>
</li>
<li><a class="reference external" href="https://nicolas.alusage.fr">Alusage</a>:<ul>
<li>Nicolas JEUDY
&lt;<a class="reference external" href="https://github.com/njeudy">https://github.com/njeudy</a>&gt;</li>
</ul>
</li>
</ul>
</div>
<div class="section" id="maintainers">
<h2><a class="toc-backref" href="#toc-entry-9">Maintainers</a></h2>
<p>This module is maintained by the OCA.</p>
<a class="reference external image-reference" href="https://odoo-community.org">
<img alt="Odoo Community Association" src="https://odoo-community.org/logo.png" />
</a>
<p>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.</p>
<p>This module is part of the <a class="reference external" href="https://github.com/OCA/bank-statement-import/tree/17.0/account_statement_import_online_gocardless">OCA/bank-statement-import</a> project on GitHub.</p>
<p>You are welcome to contribute. To learn how please visit <a class="reference external" href="https://odoo-community.org/page/Contribute">https://odoo-community.org/page/Contribute</a>.</p>
</div>
</div>
</div>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -0,0 +1,87 @@
/** @odoo-module **/
import {Component, useState} from "@odoo/owl";
import {Dialog} from "@web/core/dialog/dialog";
import {_t} from "@web/core/l10n/translation";
import {registry} from "@web/core/registry";
export class GocardlessDialog extends Component {
setup() {
this.state = useState({
searchString: "",
country: false,
institutions: this.props.context.institutions,
});
}
onChangeCountry(event) {
var country = false;
if (
event.target.selectedOptions.length &&
event.target.selectedOptions[0].attributes.length
) {
country = event.target.selectedOptions[0].value;
}
this.state.country = country;
this.state.institutions = this.get_institutions(
country,
this.state.searchString
);
}
onInstitutionSearch(event) {
var searchString = event.target.value;
this.state.searchString = searchString;
this.state.institutions = this.get_institutions(
this.state.country,
searchString
);
}
get_institutions(country, searchString) {
var institutions = this.props.context.institutions;
if (country) {
institutions.filter((institution) =>
institution.countries.includes(country)
);
}
return institutions.filter((institution) =>
institution.name.toUpperCase().includes(searchString.toUpperCase())
);
}
get country_names() {
return this.props.context.country_names;
}
}
GocardlessDialog.template =
"account_statement_import_online_gocardless.OnlineSyncSearchBankGoCardless";
GocardlessDialog.components = {Dialog};
async function OnlineSyncAccountInstitutionSelector(env, action) {
env.services.dialog.add(GocardlessDialog, {
title: _t("Gocardless selection"),
context: action.context,
onClickInstitution: async function (institutionId) {
if (!institutionId) {
return;
}
await env.services.orm.write(
"online.bank.statement.provider",
[action.context.provider_id],
{gocardless_institution_id: institutionId}
);
var redirect_url = await env.services.orm.call(
"online.bank.statement.provider",
"action_check_gocardless_agreement",
[[action.context.provider_id]]
);
if (redirect_url !== undefined) {
window.location.replace(redirect_url);
}
},
});
}
registry
.category("actions")
.add(
"online_sync_institution_selector_gocardless",
OnlineSyncAccountInstitutionSelector
);

View File

@@ -0,0 +1,209 @@
* {
box-sizing: border-box;
}
@font-face {
font-family: "HK Grotesk";
src: url('fonts/HKGrotesk-Bold.ttf') format("truetype");
}
.institution-content-wrapper {
margin: 0 auto;
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: 2rem;
}
.institution-search-bx-body {
max-height: 52vh;
overflow-y: auto;
}
.institution-search-bx-body::-webkit-scrollbar-thumb {
background: #AEB0B0;
border-radius: 7px;
}
.institution-search-bx-body::-webkit-scrollbar {
width: 6px;
background-color: #F1F1F1;
border-radius: 7px;
}
#institution-modal-content {
width: 100%;
max-width: 40rem;
background-color: #fff;
padding: 4.5rem;
display: flex;
flex-direction: column;
box-shadow: 0px 1px 3px #00000029;
border-radius: 14px;
color: #1B2021;
}
.institution-modal-header {
text-align: right;
}
.institution-modal-header h2 {
text-align: left;
font-size: 2.5rem;
margin: 0;
}
.institution-modal-footer {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
padding: 2rem;
}
.institution-modal-footer > a {
line-height: 2.8rem;
text-decoration: none;
color: #808080;
}
.institution-modal-footer > a:hover {
text-decoration: underline;
}
.institution-modal-close {
color: #8D9090;
font-size: 2.6rem;
font-weight: bold;
cursor: pointer;
position: relative;
top: -20px;
}
.institution-search-container {
position: relative;
bottom: 2.2rem;
}
.institution-search-container input {
text-indent: 2.8rem;
}
.institution-search-icon {
position: relative;
padding: 1rem;
height: 3.8rem;
top: 3.8rem;
}
.institution-search-input {
width: 100%;
padding: 1rem;
border-radius: 9px;
color: #8C8E8F;
border: 1px solid #8c8e8f;
font-size: 1.6rem;
font-weight: 400;
outline-color: #70b1f7;
}
.institution-container {
display: flex;
flex-direction: column;
}
.list-institution {
padding: 14px 0;
border-bottom: 1px solid #D7D8D8;
}
.list-institution:last-child {
border: none;
}
.list-institution a {
padding-right: 2.5rem;
display: flex;
flex-direction: row;
align-items: center;
text-decoration: none;
}
.list-institution span {
margin-left: 1.5rem;
font-size: 1.5rem;
font-weight: 600;
color: #1B2021;
}
.list-institution:hover {
background-color: #F1F1F1;
}
img.institution-logo {
width: 100%;
max-width: 3.5rem;
height: 3.5rem;
border-radius: 6px;
}
.institution-company-logo {
max-width: 13rem;
height: 10rem;
margin: 2.5rem 0 7rem;
}
@media screen and (max-width: 640px) {
html {
font-size: 8px;
}
#institution-modal-content {
height: 100%;
max-width: 100%;
border-radius: 0;
}
#institution-modal-content,
.institution-company-logo {
margin: 0;
}
.institution-company-logo {
position: absolute;
bottom: 0;
padding: 2.5rem;
}
.institution-modal-close {
display: none;
}
.institution-modal-header {
margin-top: 3rem;
}
.institution-modal-header h2 {
font-size: 3rem;
}
.list-institution span {
font-size: 1.9rem;
}
}
@media screen and (device-height: 568px) and (orientation: portrait) and (-webkit-min-device-pixel-ratio: 2) {
html {
font-size: 7px;
}
}

View File

@@ -0,0 +1,75 @@
<templates xml:space="preserve">
<t
t-name="account_statement_import_online_gocardless.OnlineSyncSearchBankGoCardless"
>
<Dialog title="props.title" contentClass="props.contentClass">
<div class="institution-content-wrapper">
<div id="institution-modal-content">
<header class="institution-modal-header">
<div class="institution-search-container">
<img
class="institution-search-icon"
url="data:image/svg+xml;base64,PHN2ZyBmaWxsPSIjMDAwMDAwIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciICB2aWV3Qm94PSIwIDAgMzAgMzAiIHdpZHRoPSIzMHB4IiBoZWlnaHQ9IjMwcHgiPjxwYXRoIGQ9Ik0gMTMgMyBDIDcuNDg4OTk3MSAzIDMgNy40ODg5OTcxIDMgMTMgQyAzIDE4LjUxMTAwMyA3LjQ4ODk5NzEgMjMgMTMgMjMgQyAxNS4zOTY1MDggMjMgMTcuNTk3Mzg1IDIyLjE0ODk4NiAxOS4zMjIyNjYgMjAuNzM2MzI4IEwgMjUuMjkyOTY5IDI2LjcwNzAzMSBBIDEuMDAwMSAxLjAwMDEgMCAxIDAgMjYuNzA3MDMxIDI1LjI5Mjk2OSBMIDIwLjczNjMyOCAxOS4zMjIyNjYgQyAyMi4xNDg5ODYgMTcuNTk3Mzg1IDIzIDE1LjM5NjUwOCAyMyAxMyBDIDIzIDcuNDg4OTk3MSAxOC41MTEwMDMgMyAxMyAzIHogTSAxMyA1IEMgMTcuNDMwMTIzIDUgMjEgOC41Njk4Nzc0IDIxIDEzIEMgMjEgMTcuNDMwMTIzIDE3LjQzMDEyMyAyMSAxMyAyMSBDIDguNTY5ODc3NCAyMSA1IDE3LjQzMDEyMyA1IDEzIEMgNSA4LjU2OTg3NzQgOC41Njk4Nzc0IDUgMTMgNSB6Ii8+PC9zdmc+"
/>
<div class="row">
<label
for="country_select"
class="font-weight-bold"
>Available countries:</label>
<select
id="country_select"
class="country_select o_input"
t-on-change="onChangeCountry"
>
<option>Select Country to Filter</option>
<t t-if="country_names.length > 0">
<t
t-foreach="country_names"
t-as="country"
t-key="country.code"
>
<option
t-attf-value="#{country.code}"
t-esc="country.name"
/>
</t>
</t>
</select>
</div>
<br />
<input
placeholder="Search..."
class="institution-search-input"
id="bank_search_input"
autofocus="true"
t-on-keydown="onInstitutionSearch"
/>
</div>
</header>
<div class="institution-container institution-search-bx-body">
<t
t-foreach="state.institutions"
t-as="institution"
t-key="institution.id"
>
<div class="list-institution">
<a
t-attf-class="#{'institution-' + institution.id}"
t-attf-data-institution="#{institution.id}"
t-on-click.prevent="() => this.props.onClickInstitution(institution.id)"
style="cursor:pointer"
>
<img
class="institution-logo"
t-attf-src="#{institution.logo}"
/>
<span t-esc="institution.name" />
</a>
</div>
</t>
</div>
</div>
</div>
</Dialog>
</t>
</templates>

View File

@@ -0,0 +1,3 @@
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from . import test_account_statement_import_online_gocardless

View File

@@ -0,0 +1,141 @@
# Copyright 2023 Tecnativa - Pedro M.Baeza
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from unittest import mock
from dateutil.relativedelta import relativedelta
from odoo import fields
from odoo.tests import common
_module_ns = "odoo.addons.account_statement_import_online_gocardless"
_provider_class = (
_module_ns + ".models.online_bank_statement_provider.OnlineBankStatementProvider"
)
class TestAccountBankAccountStatementImportOnlineGocardless(common.TransactionCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.now = fields.Datetime.now()
cls.currency_eur = cls.env.ref("base.EUR")
cls.currency_eur.write({"active": True})
bank_account = cls.env["res.partner.bank"].create(
{
"acc_number": "NL77ABNA0574908765",
"partner_id": cls.env.ref("base.main_partner").id,
"company_id": cls.env.ref("base.main_company").id,
"bank_id": cls.env.ref("base.res_bank_1").id,
}
)
cls.journal = cls.env["account.journal"].create(
{
"name": "GoCardless Bank Test",
"type": "bank",
"code": "GCB",
"currency_id": cls.currency_eur.id,
"bank_statements_source": "online",
"online_bank_statement_provider": "gocardless",
"bank_account_id": bank_account.id,
}
)
cls.provider = cls.journal.online_bank_statement_provider_id
cls.provider.write(
{
"statement_creation_mode": "monthly",
"gocardless_account_id": "SANDBOXFINANCE_SFIN0000",
"gocardless_requisition_expiration": cls.now + relativedelta(days=30),
}
)
cls.return_value = { # GoCardless sample return
"transactions": {
"booked": [
{
"transactionId": "2020103000624289-1",
"debtorName": "MON MOTHMA",
"debtorAccount": {"iban": "GL53SAFI055151515"},
"transactionAmount": {"currency": "EUR", "amount": "45.00"},
"bookingDate": "2020-10-30",
"valueDate": "2020-10-30",
"remittanceInformationUnstructured": (
"For the support of Restoration of the Republic foundation"
),
},
{
"transactionId": "2020111101899195-1",
"transactionAmount": {"currency": "EUR", "amount": "-15.00"},
"bankTransactionCode": "PMNT",
"bookingDate": "2020-11-11",
"valueDate": "2020-11-11",
"remittanceInformationUnstructured": "PAYMENT Alderaan Coffe",
},
],
"pending": [
{
"transactionAmount": {"currency": "EUR", "amount": "-10.00"},
"valueDate": "2020-11-03",
"remittanceInformationUnstructured": (
"Reserved PAYMENT Emperor's Burgers"
),
}
],
}
}
cls.mock_transaction = lambda cls: mock.patch(
_provider_class + "._gocardless_request_transactions",
return_value=cls.return_value,
)
cls.request_requisition_value = {
"accounts": ["ACCOUNT-ID-1"],
"agreement": "TEST-AGREEMENT-ID",
}
cls.mock_requisition = lambda cls: mock.patch(
_provider_class + "._gocardless_request_requisition",
return_value=cls.request_requisition_value,
)
cls.request_account_value = {
"id": "ACCOUNT-ID-1",
"iban": "nl77abna0574908765",
}
cls.mock_account = lambda cls: mock.patch(
_provider_class + "._gocardless_request_account",
return_value=cls.request_account_value,
)
cls.request_agreement_value = {
"id": "TEST-AGREEMENT-ID",
"accepted": cls.now.strftime("%Y-%m-%dT%H:%M:%S.%fZ"),
"access_valid_for_days": 30,
}
cls.mock_agreement = lambda cls: mock.patch(
_provider_class + "._gocardless_request_agreement",
return_value=cls.request_agreement_value,
)
def test_mocked_gocardless(self):
vals = {
"date_since": "2020-10-30",
"date_until": "2020-11-11",
}
wizard = (
self.env["online.bank.statement.pull.wizard"]
.with_context(
active_model="account.journal",
active_id=self.journal.id,
)
.create(vals)
)
with self.mock_transaction():
wizard.action_pull()
statements = self.env["account.bank.statement"].search(
[("journal_id", "=", self.journal.id)]
)
self.assertEqual(len(statements), 2)
lines = statements.line_ids.sorted(lambda x: x.date)
self.assertEqual(len(lines), 2)
self.assertEqual(lines.mapped("amount"), [45.0, -15.0])
def test_provider_gocardless_finish_requisition(self):
with self.mock_requisition(), self.mock_account(), self.mock_agreement():
res = self.provider._gocardless_finish_requisition(dry=True)
self.assertTrue(res, "Bank account not found!")

View File

@@ -0,0 +1,54 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<record model="ir.ui.view" id="online_bank_statement_provider_form">
<field name="name">online.bank.statement.provider.form</field>
<field name="model">online.bank.statement.provider</field>
<field
name="inherit_id"
ref="account_statement_import_online.online_bank_statement_provider_form"
/>
<field name="arch" type="xml">
<xpath expr="//group[@name='main']" position="inside">
<group
name="gocardless"
string="GoCardless"
invisible="service != 'gocardless'"
>
<field name="username" string="Secret ID" />
<field name="password" string="Secret Key" />
<field
name="gocardless_country_id"
required="service == 'gocardless'"
/>
<field
name="gocardless_requisition_id"
invisible="not gocardless_requisition_id"
groups="base.group_no_one"
/>
<field
name="gocardless_requisition_expiration"
invisible="not gocardless_requisition_id"
groups="base.group_no_one"
/>
<field
name="gocardless_institution_id"
invisible="not gocardless_institution_id"
groups="base.group_no_one"
/>
<field
name="gocardless_account_id"
invisible="not gocardless_account_id"
groups="base.group_no_one"
/>
<button
name="action_select_gocardless_bank"
string="Select Bank Account Identifier"
colspan="2"
invisible="not username or not password"
type="object"
/>
</group>
</xpath>
</field>
</record>
</odoo>

View File

@@ -0,0 +1,3 @@
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from . import online_bank_statement_provider_existing

View File

@@ -0,0 +1,31 @@
# Copyright 2024 Tecnativa - Pedro M. Baeza
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo import fields, models
class OnlineBankStatementProviderExisting(models.TransientModel):
_name = "online.bank.statement.provider.existing"
_description = "Wizard for reusing existing GoCardless provider"
_rec_name = "provider_id"
provider_id = fields.Many2one(comodel_name="online.bank.statement.provider")
other_provider_id = fields.Many2one(comodel_name="online.bank.statement.provider")
def link_existing(self):
provider = self.provider_id
other = self.other_provider_id
provider.write(
{
"gocardless_requisition_ref": other.gocardless_requisition_ref,
"gocardless_requisition_id": other.gocardless_requisition_id,
"gocardless_requisition_expiration": (
other.gocardless_requisition_expiration
),
"gocardless_institution_id": other.gocardless_institution_id,
}
)
provider._gocardless_finish_requisition(dry=True)
def new_link(self):
return self.provider_id._gocardless_select_bank_institution()

View File

@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8" ?>
<!-- Copyright 2024 Tecnativa - Pedro M. Baeza
License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). -->
<odoo>
<record id="view_online_bank_statement_provider_existing_form" model="ir.ui.view">
<field name="model">online.bank.statement.provider.existing</field>
<field name="arch" type="xml">
<form>
<div class="oe_form_box_info bg-info oe_text_center">
<p><i
class="fa fa-info-circle"
/> There's already another journal from the same bank institution linked with GoCardless. Do you want to reuse the existing credentials or create new ones?</p>
</div>
<br />
<div>
<p
>Some banks only allow one credentials, while others work better with separate ones, so it's a matter of trying.</p>
</div>
<footer>
<button
name="link_existing"
type="object"
string="Reuse existing"
/>
<button name="new_link" type="object" string="New link" />
</footer>
</form>
</field>
</record>
</odoo>