[ADD] account_statement_import_online_gocardless

This module provides online bank statements from GoCardless Bank Account
Data, which provides a free API for connecting and getting transactions
for bank accounts.

TT45760

Co-Authored-By: Christopher Ormaza <chris.ormaza@forgeflow.com>
Co-Authored-By: Jordi Ballester <jordi.ballester@forgeflow.com>
This commit is contained in:
Pedro M. Baeza
2023-11-03 15:28:41 +01:00
committed by Enric Tobella
parent b92378f878
commit 29f1ace32e
23 changed files with 1683 additions and 0 deletions

View File

@@ -0,0 +1,145 @@
==================================
Online Bank Statements: GoCardless
==================================
..
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:5523e4b06eaa38f0f74b6181f2043b93037f673d8a5038474c13dc323f1492ac
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
.. |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/15.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-15-0/bank-statement-import-15-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=15.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
~~~~~~~~~~~~~~~~~~~~~~~~~
#. Go to https://bankaccountdata.gocardless.com, and create or login into your
"GoCardLess Bank Account Data" account.
#. Go to Developers > User secrets option on the left.
#. Click on the "+ Create new" button on the bottom part.
#. Put a name to the user secret (eg. Odoo), and optionally limit it to certain
IPs using CIDR subnet notation.
#. 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:
#. Add your user to the "Full Accounting Settings" group.
#. Go to *Invoicing > Configuration > Accounting > Journals*.
#. Select the journal representing your bank account (or create it).
#. The bank account number should be properly introduced.
#. Set *Bank Feeds* to *Online (OCA)*.
#. Select *GoCardless* as online bank statements provider in
*Online Bank Statements (OCA)* section.
#. Save the journal
#. Click on the created provider.
#. Put your secret ID and secret key on the existing fields.
#. Click on the button "Select Bank Account Identifier".
.. image:: https://raw.githubusercontent.com/OCA/bank-statement-import/15.0/account_statement_import_online_gocardless/static/img/gocardless_configuration.gif
#. A new window will appear for selecting the bank entity.
.. image:: https://raw.githubusercontent.com/OCA/bank-statement-import/15.0/account_statement_import_online_gocardless/static/img/gocardless_bank_selection.gif
#. Select it, and you will be redirected to the selected entity for introducing
your bank credentials to allow the connection.
#. 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.
#. If not, an error message will be logged either in the chatter.
Usage
=====
To pull historical bank statements:
#. Go to *Invoicing > Configuration > Accounting > Journals*.
#. Select the journal representing your bank account.
#. Launch *Actions > Online Bank Statements Pull Wizard*
#. 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:%2015.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
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/15.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,4 @@
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from . import models
from . import controllers

View File

@@ -0,0 +1,30 @@
# Copyright 2022 ForgeFlow S.L.
# Copyright 2023 Tecnativa - Pedro M. Baeza
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
{
"name": "Online Bank Statements: GoCardless",
"version": "15.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": ["view/online_bank_statement_provider.xml"],
"assets": {
"web.assets_backend": [
"account_statement_import_online_gocardless/static/src/"
"lib/gocardless-ui/selector.css",
"account_statement_import_online_gocardless/static/src/"
"lib/gocardless-ui/selector.js",
"account_statement_import_online_gocardless/static/src/"
"js/select_bank_widget.js",
],
"web.assets_qweb": [
"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,3 @@
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from . import online_bank_statement_provider

View File

@@ -0,0 +1,353 @@
# Copyright 2022 ForgeFlow S.L.
# Copyright 2023 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_ENDPOINT = "https://bankaccountdata.gocardless.com/api/v2"
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()
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_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
):
url = f"{GOCARDLESS_ENDPOINT}/token/refresh/"
else:
url = f"{GOCARDLESS_ENDPOINT}/token/new/"
response = requests.post(
url,
data=json.dumps(
{"secret_id": self.username, "secret_key": self.password}
),
headers=self._gocardless_get_headers(basic=True),
)
data = {}
if response.status_code == 200:
data = json.loads(response.text)
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 _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 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)
)
country = self.journal_id.bank_account_id.company_id.country_id
response = requests.get(
f"{GOCARDLESS_ENDPOINT}/institutions/",
params={"country": country.code},
headers=self._gocardless_get_headers(),
)
if response.status_code == 400:
raise UserError(_("Incorrect country code or country not supported."))
institutions = json.loads(response.text)
# 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 = requests.post(
f"{GOCARDLESS_ENDPOINT}/requisitions/",
data=json.dumps(
{
"redirect": redirect_url,
"institution_id": self.gocardless_institution_id,
"reference": self.gocardless_requisition_ref,
}
),
headers=self._gocardless_get_headers(),
)
if response.status_code == 201:
requisition_data = json.loads(response.text)
self.gocardless_requisition_id = requisition_data["id"]
# JS code expects here to return a plain link or nothing
return requisition_data["link"]
def _gocardless_finish_requisition(self):
"""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.
"""
self.ensure_one()
requisition_response = requests.get(
f"{GOCARDLESS_ENDPOINT}/requisitions/{self.gocardless_requisition_id}/",
headers=self._gocardless_get_headers(),
)
requisition_data = json.loads(requisition_response.text)
accounts = requisition_data.get("accounts", [])
found_account = False
accounts_iban = []
for account_id in accounts:
account_response = requests.get(
f"{GOCARDLESS_ENDPOINT}/accounts/{account_id}/",
headers=self._gocardless_get_headers(),
)
if account_response.status_code == 200:
account_data = json.loads(account_response.text)
accounts_iban.append(account_data["iban"])
if (
self.journal_id.bank_account_id.sanitized_acc_number
== account_data["iban"]
):
found_account = True
self.gocardless_account_id = account_data["id"]
break
if found_account:
agreement_response = requests.get(
f"{GOCARDLESS_ENDPOINT}/agreements/enduser/"
f"{requisition_data['agreement']}/",
headers=self._gocardless_get_headers(),
)
agreement_data = json.loads(agreement_response.text)
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}
)
else:
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),
}
)
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
transaction_response = requests.get(
f"{GOCARDLESS_ENDPOINT}/accounts/"
f"{self.gocardless_account_id}/transactions/",
params={
"date_from": date_since.strftime(DF),
"date_to": date_until.strftime(DF),
},
headers=self._gocardless_get_headers(),
)
if transaction_response.status_code == 200:
return json.loads(transaction_response.text)
return {}
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
res.append(
{
"sequence": sequence,
"date": current_date,
"ref": partner_name or "/",
"payment_ref": tr.get(
"remittanceInformationUnstructured", partner_name
),
"unique_import_id": tr.get("entryReference", False)
or tr.get("transactionId", False),
"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,42 @@
On the GoCardless website
~~~~~~~~~~~~~~~~~~~~~~~~~
#. Go to https://bankaccountdata.gocardless.com, and create or login into your
"GoCardLess Bank Account Data" account.
#. Go to Developers > User secrets option on the left.
#. Click on the "+ Create new" button on the bottom part.
#. Put a name to the user secret (eg. Odoo), and optionally limit it to certain
IPs using CIDR subnet notation.
#. 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:
#. Add your user to the "Full Accounting Settings" group.
#. Go to *Invoicing > Configuration > Accounting > Journals*.
#. Select the journal representing your bank account (or create it).
#. The bank account number should be properly introduced.
#. Set *Bank Feeds* to *Online (OCA)*.
#. Select *GoCardless* as online bank statements provider in
*Online Bank Statements (OCA)* section.
#. Save the journal
#. Click on the created provider.
#. Put your secret ID and secret key on the existing fields.
#. Click on the button "Select Bank Account Identifier".
.. image:: ../static/img/gocardless_configuration.gif
#. A new window will appear for selecting the bank entity.
.. image:: ../static/img/gocardless_bank_selection.gif
#. Select it, and you will be redirected to the selected entity for introducing
your bank credentials to allow the connection.
#. 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.
#. 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

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:
#. Go to *Invoicing > Configuration > Accounting > Journals*.
#. Select the journal representing your bank account.
#. Launch *Actions > Online Bank Statements Pull Wizard*
#. 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.

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

View File

@@ -0,0 +1,508 @@
<?xml version="1.0" encoding="utf-8"?>
<!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 8954 2022-01-20 10:10:25Z milde $
:Copyright: This stylesheet has been placed in the public domain.
Default cascading style sheet for the HTML output of Docutils.
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: grey; } /* 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 {
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:5523e4b06eaa38f0f74b6181f2043b93037f673d8a5038474c13dc323f1492ac
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
<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/15.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-15-0/bank-statement-import-15-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=15.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>
<img alt="https://raw.githubusercontent.com/OCA/bank-statement-import/15.0/account_statement_import_online_gocardless/static/img/gocardless_configuration.gif" src="https://raw.githubusercontent.com/OCA/bank-statement-import/15.0/account_statement_import_online_gocardless/static/img/gocardless_configuration.gif" />
</li>
<li><p class="first">A new window will appear for selecting the bank entity.</p>
<img alt="https://raw.githubusercontent.com/OCA/bank-statement-import/15.0/account_statement_import_online_gocardless/static/img/gocardless_bank_selection.gif" src="https://raw.githubusercontent.com/OCA/bank-statement-import/15.0/account_statement_import_online_gocardless/static/img/gocardless_bank_selection.gif" />
</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:%2015.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>
</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/15.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,121 @@
odoo.define(
"account_bank_statement_import_online_gocardless.acc_config_widget_gocardless",
function (require) {
"use strict";
require("web.dom_ready");
var core = require("web.core");
var AbstractAction = require("web.AbstractAction");
var QWeb = core.qweb;
var framework = require("web.framework");
var OnlineSyncAccountInstitutionSelector = AbstractAction.extend({
template: "OnlineSyncSearchBankGoCardless",
init: function (parent, action, options) {
this._super(parent, action, options);
this.context = action.context;
this.results = action.context.institutions;
this.country_names = action.context.country_names;
this.country_selected = action.context.country;
},
start: function () {
const self = this;
const $selectCountries = this.$el.find(".country_select");
const $countryOptions = $(
QWeb.render("OnlineSyncSearchBankGoCardlessCountries", {
country_names: this.country_names,
})
);
$countryOptions.appendTo($selectCountries);
if (
$selectCountries.find("option[value=" + this.country_selected + "]")
.length !== 0
) {
$selectCountries.val(this.country_selected);
$selectCountries.change();
}
$selectCountries.change(function () {
self.country_selected = this.selectedOptions[0].value;
return self.renderSearchResult();
});
this.displayState();
self.$el.find("#bank_search_input").on("keyup", function () {
const input = $(".institution-search-input");
const filter = input[0].value.toUpperCase();
const institutionList = $(".list-institution");
for (let i = 0; i < institutionList.length; i++) {
const txtValue = institutionList[i].textContent;
if (txtValue.toUpperCase().indexOf(filter) > -1) {
institutionList[i].style.display = "";
} else {
institutionList[i].style.display = "none";
}
}
});
},
displayState: function () {
if (this.results.length > 0) {
this.renderSearchResult();
}
},
renderElement: function () {
this._super.apply(this, arguments);
},
renderSearchResult: function () {
var self = this;
this.$(".institution-container").html("");
const filteredInstitutions = this.results.filter(function (
institution
) {
return institution.countries.includes(self.country_selected);
});
var $searchResults = $(
QWeb.render("OnlineSyncSearchBankGoCardlessList", {
institutions: filteredInstitutions,
})
);
$searchResults.find("a").click(function () {
framework.blockUI();
const id = this.getAttribute("data-institution");
if (id) {
return self
._rpc({
model: "online.bank.statement.provider",
method: "write",
args: [
[self.context.provider_id],
{gocardless_institution_id: id},
],
})
.then(function () {
return self
._rpc({
model: "online.bank.statement.provider",
method: "action_check_gocardless_agreement",
args: [[self.context.active_id]],
})
.then(function (redirect_url) {
if (redirect_url !== undefined) {
window.location.replace(redirect_url);
}
});
});
}
});
$searchResults.appendTo(self.$(".institution-container"));
},
});
core.action_registry.add(
"online_sync_institution_selector_gocardless",
OnlineSyncAccountInstitutionSelector
);
return {
OnlineSyncAccountInstitutionSelector: 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,58 @@
<templates xml:space="preserve">
<t t-name="OnlineSyncSearchBankGoCardless">
<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" />
</div>
<br />
<input
placeholder="Search..."
class="institution-search-input"
id="bank_search_input"
autofocus="true"
/>
</div>
</header>
<div class="institution-container institution-search-bx-body">
</div>
</div>
</div>
</t>
<t t-name="OnlineSyncSearchBankGoCardlessList">
<t t-if="institutions.length > 0">
<t t-foreach="institutions" t-as="institution">
<div class="list-institution">
<a
t-attf-class="#{'institution-' + institution.id}"
t-attf-data-institution="#{institution.id}"
style="cursor:pointer"
>
<img class="institution-logo" t-attf-src="#{institution.logo}" />
<span t-esc="institution.name" />
</a>
</div>
</t>
</t>
</t>
<t t-name="OnlineSyncSearchBankGoCardlessCountries">
<option>Select Country to Filter</option>
<t t-if="country_names.length > 0">
<t t-foreach="country_names" t-as="country">
<option t-attf-value="#{country.code}" t-esc="country.name" />
</t>
</t>
</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,103 @@
# 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})
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",
}
)
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,
)
def test_mocked_gocardless(self):
vals = {
"provider_ids": self.provider.ids,
"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])

View File

@@ -0,0 +1,48 @@
<?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"
attrs="{'invisible':[('service','!=','gocardless')]}"
>
<field name="username" string="Secret ID" />
<field name="password" string="Secret Key" />
<field
name="gocardless_requisition_id"
attrs="{'invisible': [('gocardless_requisition_id', '=', False)]}"
groups="base.group_no_one"
/>
<field
name="gocardless_requisition_expiration"
attrs="{'invisible': [('gocardless_requisition_id', '=', False)]}"
groups="base.group_no_one"
/>
<field
name="gocardless_institution_id"
attrs="{'invisible': [('gocardless_institution_id', '=', False)]}"
groups="base.group_no_one"
/>
<field
name="gocardless_account_id"
attrs="{'invisible': [('gocardless_account_id', '=', False)]}"
groups="base.group_no_one"
/>
<button
name="action_select_gocardless_bank"
string="Select Bank Account Identifier"
type="object"
/>
</group>
</xpath>
</field>
</record>
</odoo>