Skip to content

Commit

Permalink
Merge pull request #566 from OCA/16.0
Browse files Browse the repository at this point in the history
Syncing from upstream OCA/bank-statement-import (16.0)
  • Loading branch information
bt-admin authored Aug 9, 2024
2 parents ea6fdf4 + 747c454 commit 4443c34
Show file tree
Hide file tree
Showing 6 changed files with 112 additions and 69 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ addon | version | maintainers | summary
[account_statement_import_file_reconcile_oca](account_statement_import_file_reconcile_oca/) | 16.0.1.0.0 | [![alexis-via](https://github.com/alexis-via.png?size=30px)](https://github.com/alexis-via) | Import Statement Files and Go Direct to Reconciliation
[account_statement_import_ofx](account_statement_import_ofx/) | 16.0.1.0.0 | [![alexis-via](https://github.com/alexis-via.png?size=30px)](https://github.com/alexis-via) | Import OFX Bank Statement
[account_statement_import_online](account_statement_import_online/) | 16.0.1.3.0 | [![alexey-pelykh](https://github.com/alexey-pelykh.png?size=30px)](https://github.com/alexey-pelykh) | Online bank statements update
[account_statement_import_online_gocardless](account_statement_import_online_gocardless/) | 16.0.1.1.0 | | Online Bank Statements: GoCardless
[account_statement_import_online_gocardless](account_statement_import_online_gocardless/) | 16.0.1.2.0 | | Online Bank Statements: GoCardless
[account_statement_import_online_ofx](account_statement_import_online_ofx/) | 16.0.1.0.0 | | Online bank statements for OFX
[account_statement_import_online_paypal](account_statement_import_online_paypal/) | 16.0.1.0.1 | [![alexey-pelykh](https://github.com/alexey-pelykh.png?size=30px)](https://github.com/alexey-pelykh) | Online bank statements for PayPal.com
[account_statement_import_online_ponto](account_statement_import_online_ponto/) | 16.0.1.1.1 | | Online Bank Statements: MyPonto.com
Expand Down
2 changes: 1 addition & 1 deletion account_statement_import_online_gocardless/README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Online Bank Statements: GoCardless
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:51449c9c5114f19ff7c7922925aa0ba856b5bc7ad5515ce1c6f2e7b1e209e9ab
!! source digest: sha256:7ab70d9d2a3900c480451072d76c9af5c6b540c14c5d8a4f60c3e38ca19134da
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
Expand Down
2 changes: 1 addition & 1 deletion account_statement_import_online_gocardless/__manifest__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
{
"name": "Online Bank Statements: GoCardless",
"version": "16.0.1.1.0",
"version": "16.0.1.2.0",
"category": "Account",
"website": "https://github.com/OCA/bank-statement-import",
"author": "ForgeFlow, Tecnativa, Odoo Community Association (OCA)",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from odoo.exceptions import UserError
from odoo.tools import DEFAULT_SERVER_DATE_FORMAT as DF

GOCARDLESS_ENDPOINT = "https://bankaccountdata.gocardless.com/api/v2"
GOCARDLESS_API = "https://bankaccountdata.gocardless.com/api/v2"
REQUESTS_TIMEOUT = 60


Expand Down Expand Up @@ -46,6 +46,31 @@ def _get_available_services(self):
("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):
content = {}
url = url_join(GOCARDLESS_API, endpoint)
response = getattr(requests, request_type)(
url,
data=data,
params=params,
headers=self._gocardless_get_headers(),
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
Expand All @@ -59,20 +84,16 @@ def _gocardless_get_token(self):
self.gocardless_refresh_token
and now > self.gocardless_refresh_expiration
):
url = f"{GOCARDLESS_ENDPOINT}/token/refresh/"
endpoint = "token/refresh"
else:
url = f"{GOCARDLESS_ENDPOINT}/token/new/"
response = requests.post(
url,
endpoint = "token/new"
_response, data = self._gocardless_request(
endpoint,
request_type="post",
data=json.dumps(
{"secret_id": self.username, "secret_key": self.password}
),
headers=self._gocardless_get_headers(basic=True),
timeout=REQUESTS_TIMEOUT,
)
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),
Expand All @@ -86,17 +107,6 @@ def _gocardless_get_token(self):
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(
Expand Down Expand Up @@ -137,15 +147,12 @@ def _gocardless_select_bank_instituion(self):
country = (
self.journal_id.bank_account_id.company_id or self.journal_id.company_id
).country_id
response = requests.get(
f"{GOCARDLESS_ENDPOINT}/institutions/",
params={"country": country.code},
headers=self._gocardless_get_headers(),
timeout=REQUESTS_TIMEOUT,
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 = json.loads(response.text)
institutions = data
# Prepare data for being showed in the JS widget
ctx = self.env.context.copy()
ctx.update(
Expand All @@ -172,24 +179,37 @@ def action_check_gocardless_agreement(self):
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/",
_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,
}
),
headers=self._gocardless_get_headers(),
timeout=REQUESTS_TIMEOUT,
)
if response.status_code == 201:
requisition_data = json.loads(response.text)
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
Expand All @@ -203,39 +223,25 @@ def _gocardless_finish_requisition(self, dry=False):
process, so no fail message is logged in chatter in this case.
"""
self.ensure_one()
requisition_response = requests.get(
f"{GOCARDLESS_ENDPOINT}/requisitions/{self.gocardless_requisition_id}/",
headers=self._gocardless_get_headers(),
timeout=REQUESTS_TIMEOUT,
)
requisition_data = json.loads(requisition_response.text)
requisition_data = self._gocardless_request_requisition()
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(),
timeout=REQUESTS_TIMEOUT,
)
if account_response.status_code == 200:
account_data = json.loads(account_response.text)
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"]
== account_data["iban"].upper()
):
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(),
timeout=REQUESTS_TIMEOUT,
agreement_data = self._gocardless_request_agreement(
requisition_data["agreement"]
)
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"])
Expand Down Expand Up @@ -279,19 +285,14 @@ def _gocardless_request_transactions(self, date_since, date_until):
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/",
_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),
},
headers=self._gocardless_get_headers(),
timeout=REQUESTS_TIMEOUT,
)
if transaction_response.status_code == 200:
return json.loads(transaction_response.text)
return {}
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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@

/*
:Author: David Goodger ([email protected])
:Id: $Id: html4css1.css 8954 2022-01-20 10:10:25Z milde $
: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.
Expand Down Expand Up @@ -274,7 +275,7 @@
margin-left: 2em ;
margin-right: 2em }

pre.code .ln { color: grey; } /* line numbers */
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 }
Expand All @@ -300,7 +301,7 @@
span.pre {
white-space: pre }

span.problematic {
span.problematic, pre.problematic {
color: red }

span.section-subtitle {
Expand Down Expand Up @@ -366,7 +367,7 @@ <h1 class="title">Online Bank Statements: GoCardless</h1>
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:51449c9c5114f19ff7c7922925aa0ba856b5bc7ad5515ce1c6f2e7b1e209e9ab
!! source digest: sha256:7ab70d9d2a3900c480451072d76c9af5c6b540c14c5d8a4f60c3e38ca19134da
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
<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/16.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-16-0/bank-statement-import-16-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=16.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,
Expand Down Expand Up @@ -498,7 +499,9 @@ <h2><a class="toc-backref" href="#toc-entry-8">Contributors</a></h2>
<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>
<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>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,14 @@ def setUpClass(cls):
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",
Expand All @@ -29,6 +37,7 @@ def setUpClass(cls):
"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
Expand Down Expand Up @@ -77,6 +86,31 @@ def setUpClass(cls):
_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 = {
Expand All @@ -100,3 +134,8 @@ def test_mocked_gocardless(self):
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!")

0 comments on commit 4443c34

Please sign in to comment.