From f5ae704071d35c6e3cb8e04ff5e9a2b31a8e4331 Mon Sep 17 00:00:00 2001 From: Alexey Pelykh Date: Tue, 11 May 2021 21:48:18 +0200 Subject: [PATCH] [IMP] account_bank_statement_import_online_transferwise: black, isort, prettier --- .../__manifest__.py | 33 +- ...ne_bank_statement_provider_transferwise.py | 349 +++++----- ...nk_statement_import_online_transferwise.py | 631 ++++++++++-------- .../views/online_bank_statement_provider.xml | 14 +- 4 files changed, 519 insertions(+), 508 deletions(-) diff --git a/account_bank_statement_import_online_transferwise/__manifest__.py b/account_bank_statement_import_online_transferwise/__manifest__.py index fc18078d..0afb9ef8 100644 --- a/account_bank_statement_import_online_transferwise/__manifest__.py +++ b/account_bank_statement_import_online_transferwise/__manifest__.py @@ -3,25 +3,16 @@ # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). { - 'name': 'Online Bank Statements: Wise.com (TransferWise.com)', - 'version': '12.0.1.0.3', - 'author': - 'CorporateHub, ' - 'Odoo Community Association (OCA)', - 'maintainers': ['alexey-pelykh'], - 'website': 'https://github.com/OCA/bank-statement-import/', - 'license': 'AGPL-3', - 'category': 'Accounting', - 'summary': 'Online bank statements for Wise.com (TransferWise.com)', - 'depends': [ - 'account_bank_statement_import_online', - 'web_widget_dropdown_dynamic', - ], - 'external_dependencies': { - 'python': ['cryptography'], - }, - 'data': [ - 'views/online_bank_statement_provider.xml', - ], - 'installable': True, + "name": "Online Bank Statements: Wise.com (TransferWise.com)", + "version": "12.0.1.0.3", + "author": "CorporateHub, " "Odoo Community Association (OCA)", + "maintainers": ["alexey-pelykh"], + "website": "https://github.com/OCA/bank-statement-import/", + "license": "AGPL-3", + "category": "Accounting", + "summary": "Online bank statements for Wise.com (TransferWise.com)", + "depends": ["account_bank_statement_import_online", "web_widget_dropdown_dynamic"], + "external_dependencies": {"python": ["cryptography"]}, + "data": ["views/online_bank_statement_provider.xml"], + "installable": True, } diff --git a/account_bank_statement_import_online_transferwise/models/online_bank_statement_provider_transferwise.py b/account_bank_statement_import_online_transferwise/models/online_bank_statement_provider_transferwise.py index 5c571728..bcfc79fa 100644 --- a/account_bank_statement_import_online_transferwise/models/online_bank_statement_provider_transferwise.py +++ b/account_bank_statement_import_online_transferwise/models/online_bank_statement_provider_transferwise.py @@ -2,79 +2,76 @@ # Copyright 2020-2021 CorporateHub (https://corporatehub.eu) # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). -from base64 import b64encode -from cryptography.hazmat.backends import default_backend -from cryptography.hazmat.primitives import hashes, serialization -from cryptography.hazmat.primitives.asymmetric import rsa, padding -from dateutil.relativedelta import relativedelta -import dateutil.parser -from decimal import Decimal import itertools import json -import pytz +import logging import urllib.parse import urllib.request +from base64 import b64encode +from decimal import Decimal from urllib.error import HTTPError -from odoo import api, fields, models, _ +import dateutil.parser +import pytz +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives import hashes, serialization +from cryptography.hazmat.primitives.asymmetric import padding, rsa +from dateutil.relativedelta import relativedelta + +from odoo import _, api, fields, models from odoo.exceptions import UserError -import logging _logger = logging.getLogger(__name__) -TRANSFERWISE_API_BASE = 'https://api.transferwise.com' +TRANSFERWISE_API_BASE = "https://api.transferwise.com" class OnlineBankStatementProviderTransferwise(models.Model): - _inherit = 'online.bank.statement.provider' + _inherit = "online.bank.statement.provider" # NOTE: This is needed to workaround possible multiple 'origin' fields # present in the same view, resulting in wrong field view configuraion # if more than one is widget="dynamic_dropdown" - transferwise_profile = fields.Char( - related='origin', - readonly=False, - ) + transferwise_profile = fields.Char(related="origin", readonly=False,) @api.model def values_transferwise_profile(self): - api_base = self.env.context.get('api_base') or TRANSFERWISE_API_BASE - api_key = self.env.context.get('api_key') + api_base = self.env.context.get("api_base") or TRANSFERWISE_API_BASE + api_key = self.env.context.get("api_key") if not api_key: return [] try: - url = api_base + '/v1/profiles' + url = api_base + "/v1/profiles" data = self._transferwise_retrieve(url, api_key) - except: - _logger.warning('Unable to get profiles', exc_info=True) + except BaseException: + _logger.warning("Unable to get profiles", exc_info=True) return [] - return list(map( - lambda entry: ( - str(entry['id']), - '%s %s (personal)' % ( - entry['details']['firstName'], - entry['details']['lastName'], - ) - if entry['type'] == 'personal' - else entry['details']['name'] - ), - data - )) + return list( + map( + lambda entry: ( + str(entry["id"]), + "%s %s (personal)" + % (entry["details"]["firstName"], entry["details"]["lastName"],) + if entry["type"] == "personal" + else entry["details"]["name"], + ), + data, + ) + ) @api.model def _get_available_services(self): return super()._get_available_services() + [ - ('transferwise', 'Wise.com (TransferWise.com)'), + ("transferwise", "Wise.com (TransferWise.com)"), ] @api.multi def _obtain_statement_data(self, date_since, date_until): self.ensure_one() - if self.service != 'transferwise': + if self.service != "transferwise": return super()._obtain_statement_data( - date_since, - date_until, + date_since, date_until, ) # pragma: no cover api_base = self.api_base or TRANSFERWISE_API_BASE @@ -82,13 +79,9 @@ class OnlineBankStatementProviderTransferwise(models.Model): private_key = self.certificate_private_key if private_key: private_key = serialization.load_pem_private_key( - private_key.encode(), - password=None, - backend=default_backend(), + private_key.encode(), password=None, backend=default_backend(), ) - currency = ( - self.currency_id or self.company_id.currency_id - ).name + currency = (self.currency_id or self.company_id.currency_id).name if date_since.tzinfo: date_since = date_since.astimezone(pytz.utc).replace(tzinfo=None) @@ -96,17 +89,14 @@ class OnlineBankStatementProviderTransferwise(models.Model): date_until = date_until.astimezone(pytz.utc).replace(tzinfo=None) # Get corresponding balance by currency - url = api_base + '/v1/borderless-accounts?profileId=%s' % ( - self.origin, - ) + url = api_base + "/v1/borderless-accounts?profileId={}".format(self.origin) data = self._transferwise_retrieve(url, api_key, private_key) if not data: return None - borderless_account = data[0]['id'] - balance = list(filter( - lambda balance: balance['currency'] == currency, - data[0]['balances'] - )) + borderless_account = data[0]["id"] + balance = list( + filter(lambda balance: balance["currency"] == currency, data[0]["balances"]) + ) if not balance: return None @@ -114,10 +104,10 @@ class OnlineBankStatementProviderTransferwise(models.Model): # - intervalStart <= date < intervalEnd # Get starting balance - starting_balance_timestamp = date_since.isoformat() + 'Z' + starting_balance_timestamp = date_since.isoformat() + "Z" url = api_base + ( - '/v3/profiles/%s/borderless-accounts/%s/statement.json' + - '?currency=%s&intervalStart=%s&intervalEnd=%s&type=COMPACT' + "/v3/profiles/%s/borderless-accounts/%s/statement.json" + + "?currency=%s&intervalStart=%s&intervalEnd=%s&type=COMPACT" ) % ( self.origin, borderless_account, @@ -126,7 +116,7 @@ class OnlineBankStatementProviderTransferwise(models.Model): starting_balance_timestamp, ) data = self._transferwise_retrieve(url, api_key, private_key) - balance_start = data['endOfStatementBalance']['value'] + balance_start = data["endOfStatementBalance"]["value"] # Get statements, using 469 days (around 1 year 3 month) as step. interval_step = relativedelta(days=469) @@ -136,158 +126,139 @@ class OnlineBankStatementProviderTransferwise(models.Model): balance_end = None while interval_start < interval_end: url = api_base + ( - '/v3/profiles/%s/borderless-accounts/%s/statement.json' + - '?currency=%s&intervalStart=%s&intervalEnd=%s&type=COMPACT' + "/v3/profiles/%s/borderless-accounts/%s/statement.json" + + "?currency=%s&intervalStart=%s&intervalEnd=%s&type=COMPACT" ) % ( self.origin, borderless_account, currency, - interval_start.isoformat() + 'Z', - min( - interval_start + interval_step, interval_end - ).isoformat() + 'Z', + interval_start.isoformat() + "Z", + min(interval_start + interval_step, interval_end).isoformat() + "Z", ) data = self._transferwise_retrieve(url, api_key, private_key) - transactions += data['transactions'] - balance_end = data['endOfStatementBalance']['value'] + transactions += data["transactions"] + balance_end = data["endOfStatementBalance"]["value"] interval_start += interval_step if balance_end is None: - raise UserError(_('Ending balance unavailable')) + raise UserError(_("Ending balance unavailable")) # Normalize transactions' date, sort by it, and get lines transactions = map( - lambda transaction: self._transferwise_preparse_transaction( - transaction - ), - transactions + lambda transaction: self._transferwise_preparse_transaction(transaction), + transactions, ) - lines = list(itertools.chain.from_iterable(map( - lambda x: self._transferwise_transaction_to_lines(x), - sorted( - transactions, - key=lambda transaction: transaction['date'] + lines = list( + itertools.chain.from_iterable( + map( + lambda x: self._transferwise_transaction_to_lines(x), + sorted(transactions, key=lambda transaction: transaction["date"]), + ) ) - ))) + ) - return lines, { - 'balance_start': balance_start, - 'balance_end_real': balance_end, - } + return lines, {"balance_start": balance_start, "balance_end_real": balance_end} @api.model def _transferwise_preparse_transaction(self, transaction): - transaction['date'] = dateutil.parser.parse( - transaction['date'] - ).replace(tzinfo=None) + transaction["date"] = dateutil.parser.parse(transaction["date"]).replace( + tzinfo=None + ) return transaction @api.model def _transferwise_transaction_to_lines(self, transaction): - transaction_type = transaction['type'] - reference_number = transaction['referenceNumber'] - details = transaction.get('details', {}) - exchange_details = transaction.get('exchangeDetails') - recipient = details.get('recipient') - total_fees = transaction.get('totalFees') - date = transaction['date'] - payment_reference = details.get('paymentReference') - description = details.get('description') + transaction_type = transaction["type"] + reference_number = transaction["referenceNumber"] + details = transaction.get("details", {}) + exchange_details = transaction.get("exchangeDetails") + recipient = details.get("recipient") + total_fees = transaction.get("totalFees") + date = transaction["date"] + payment_reference = details.get("paymentReference") + description = details.get("description") note = reference_number if description: - note = '%s: %s' % ( - note, - description - ) - amount = transaction['amount'] - amount_value = amount.get('value', 0) - fees_value = total_fees.get('value', Decimal()) - if transaction_type == 'CREDIT' \ - and details.get('type') == 'MONEY_ADDED': + note = "{}: {}".format(note, description) + amount = transaction["amount"] + amount_value = amount.get("value", 0) + fees_value = total_fees.get("value", Decimal()) + if transaction_type == "CREDIT" and details.get("type") == "MONEY_ADDED": fees_value = fees_value.copy_negate() else: fees_value = fees_value.copy_sign(amount_value) amount_value -= fees_value - unique_import_id = '%s-%s-%s' % ( - transaction_type, - reference_number, - int(date.timestamp()), + unique_import_id = "{}-{}-{}".format( + transaction_type, reference_number, int(date.timestamp()), ) line = { - 'name': payment_reference or description or '', - 'amount': str(amount_value), - 'date': date, - 'note': note, - 'unique_import_id': unique_import_id, + "name": payment_reference or description or "", + "amount": str(amount_value), + "date": date, + "note": note, + "unique_import_id": unique_import_id, } if recipient: - if 'name' in recipient: - line.update({ - 'partner_name': recipient['name'], - }) - if 'bankAccount' in recipient: - line.update({ - 'account_number': recipient['bankAccount'], - }) - elif 'merchant' in details: - merchant = details['merchant'] - if 'name' in merchant: - line.update({ - 'partner_name': merchant['name'], - }) + if "name" in recipient: + line.update({"partner_name": recipient["name"]}) + if "bankAccount" in recipient: + line.update({"account_number": recipient["bankAccount"]}) + elif "merchant" in details: + merchant = details["merchant"] + if "name" in merchant: + line.update({"partner_name": merchant["name"]}) else: - if 'senderName' in details: - line.update({ - 'partner_name': details['senderName'], - }) - if 'senderAccount' in details: - line.update({ - 'account_number': details['senderAccount'], - }) + if "senderName" in details: + line.update({"partner_name": details["senderName"]}) + if "senderAccount" in details: + line.update({"account_number": details["senderAccount"]}) if exchange_details: - to_amount = exchange_details['toAmount'] - from_amount = exchange_details['fromAmount'] + to_amount = exchange_details["toAmount"] + from_amount = exchange_details["fromAmount"] other_amount_value = ( - to_amount['value'] - if to_amount['currency'] != amount['currency'] - else from_amount['value'] + to_amount["value"] + if to_amount["currency"] != amount["currency"] + else from_amount["value"] ) other_currency_name = ( - to_amount['currency'] - if to_amount['currency'] != amount['currency'] - else from_amount['currency'] + to_amount["currency"] + if to_amount["currency"] != amount["currency"] + else from_amount["currency"] ) other_amount_value = other_amount_value.copy_abs() if amount_value.is_signed(): other_amount_value = other_amount_value.copy_negate() - other_currency = self.env['res.currency'].search( - [('name', '=', other_currency_name)], - limit=1 + other_currency = self.env["res.currency"].search( + [("name", "=", other_currency_name)], limit=1 ) if other_amount_value and other_currency: - line.update({ - 'amount_currency': str(other_amount_value), - 'currency_id': other_currency.id, - }) + line.update( + { + "amount_currency": str(other_amount_value), + "currency_id": other_currency.id, + } + ) lines = [line] if fees_value: - lines += [{ - 'name': _('Fee for %s') % reference_number, - 'amount': str(fees_value), - 'date': date, - 'partner_name': 'Wise (former TransferWise)', - 'unique_import_id': '%s-FEE' % unique_import_id, - 'note': _('Transaction fee for %s') % reference_number, - }] + lines += [ + { + "name": _("Fee for %s") % reference_number, + "amount": str(fees_value), + "date": date, + "partner_name": "Wise (former TransferWise)", + "unique_import_id": "%s-FEE" % unique_import_id, + "note": _("Transaction fee for %s") % reference_number, + } + ] return lines @api.model def _transferwise_validate(self, content): content = json.loads(content, parse_float=Decimal) - if 'error' in content and content['error']: + if "error" in content and content["error"]: raise UserError( - content['error_description'] - if 'error_description' in content - else 'Unknown error' + content["error_description"] + if "error_description" in content + else "Unknown error" ) return content @@ -296,31 +267,23 @@ class OnlineBankStatementProviderTransferwise(models.Model): try: with self._transferwise_urlopen(url, api_key) as response: content = response.read().decode( - response.headers.get_content_charset() or 'utf-8' + response.headers.get_content_charset() or "utf-8" ) except HTTPError as e: - if e.code != 403 or \ - e.headers.get('X-2FA-Approval-Result') != 'REJECTED': + if e.code != 403 or e.headers.get("X-2FA-Approval-Result") != "REJECTED": raise e if not private_key: - raise UserError(_( - 'Strong Customer Authentication is not configured' - )) - one_time_token = e.headers['X-2FA-Approval'] + raise UserError(_("Strong Customer Authentication is not configured")) + one_time_token = e.headers["X-2FA-Approval"] signature = private_key.sign( - one_time_token.encode(), - padding.PKCS1v15(), - hashes.SHA256(), + one_time_token.encode(), padding.PKCS1v15(), hashes.SHA256(), ) with self._transferwise_urlopen( - url, - api_key, - one_time_token, - b64encode(signature).decode(), + url, api_key, one_time_token, b64encode(signature).decode(), ) as response: content = response.read().decode( - response.headers.get_content_charset() or 'utf-8' + response.headers.get_content_charset() or "utf-8" ) return self._transferwise_validate(content) @@ -328,17 +291,17 @@ class OnlineBankStatementProviderTransferwise(models.Model): @api.model def _transferwise_urlopen(self, url, api_key, ott=None, signature=None): if not api_key: - raise UserError(_('No API key specified!')) + raise UserError(_("No API key specified!")) request = urllib.request.Request(url) - request.add_header('Authorization', 'Bearer %s' % api_key) + request.add_header("Authorization", "Bearer %s" % api_key) if ott and signature: - request.add_header('X-2FA-Approval', ott) - request.add_header('X-Signature', signature) + request.add_header("X-2FA-Approval", ott) + request.add_header("X-Signature", signature) return urllib.request.urlopen(request) - @api.onchange('certificate_private_key', 'service') + @api.onchange("certificate_private_key", "service") def _onchange_transferwise_certificate_private_key(self): - if self.service != 'transferwise': + if self.service != "transferwise": return self.certificate_public_key = False @@ -351,22 +314,23 @@ class OnlineBankStatementProviderTransferwise(models.Model): password=None, backend=default_backend(), ) - self.certificate_public_key = private_key.public_key().public_bytes( - serialization.Encoding.PEM, - serialization.PublicFormat.PKCS1, - ).decode() - except: - _logger.warning('Unable to parse key', exc_info=True) - raise UserError(_('Unable to parse key')) + self.certificate_public_key = ( + private_key.public_key() + .public_bytes( + serialization.Encoding.PEM, serialization.PublicFormat.PKCS1, + ) + .decode() + ) + except BaseException: + _logger.warning("Unable to parse key", exc_info=True) + raise UserError(_("Unable to parse key")) @api.multi def _transferwise_generate_key(self): self.ensure_one() private_key = rsa.generate_private_key( - public_exponent=65537, - key_size=2048, - backend=default_backend(), + public_exponent=65537, key_size=2048, backend=default_backend(), ) self.certificate_private_key = private_key.private_bytes( serialization.Encoding.PEM, @@ -374,10 +338,11 @@ class OnlineBankStatementProviderTransferwise(models.Model): serialization.NoEncryption(), ).decode() - self.certificate_public_key = private_key.public_key().public_bytes( - serialization.Encoding.PEM, - serialization.PublicFormat.PKCS1, - ).decode() + self.certificate_public_key = ( + private_key.public_key() + .public_bytes(serialization.Encoding.PEM, serialization.PublicFormat.PKCS1,) + .decode() + ) @api.multi def button_transferwise_generate_key(self): diff --git a/account_bank_statement_import_online_transferwise/tests/test_account_bank_statement_import_online_transferwise.py b/account_bank_statement_import_online_transferwise/tests/test_account_bank_statement_import_online_transferwise.py index 3d378b19..e41296e9 100644 --- a/account_bank_statement_import_online_transferwise/tests/test_account_bank_statement_import_online_transferwise.py +++ b/account_bank_statement_import_online_transferwise/tests/test_account_bank_statement_import_online_transferwise.py @@ -2,26 +2,26 @@ # Copyright 2020-2021 CorporateHub (https://corporatehub.eu) # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). -from datetime import datetime -from dateutil.relativedelta import relativedelta -from decimal import Decimal import json +from datetime import datetime +from decimal import Decimal from unittest import mock from urllib.error import HTTPError -from odoo.tests import common -from odoo import fields +from dateutil.relativedelta import relativedelta -_module_ns = 'odoo.addons.account_bank_statement_import_online_transferwise' +from odoo import fields +from odoo.tests import common + +_module_ns = "odoo.addons.account_bank_statement_import_online_transferwise" _provider_class = ( _module_ns - + '.models.online_bank_statement_provider_transferwise' - + '.OnlineBankStatementProviderTransferwise' + + ".models.online_bank_statement_provider_transferwise" + + ".OnlineBankStatementProviderTransferwise" ) class MockedResponse: - class Headers(dict): def get_content_charset(self): return None @@ -37,41 +37,35 @@ class MockedResponse: def __exit__(self, exc_type, exc_value, traceback): pass + # pylint: disable=method-required-super def read(self): if self.exception is not None: raise self.exception() return self.data -class TestAccountBankAccountStatementImportOnlineTransferwise( - common.TransactionCase -): - +class TestAccountBankAccountStatementImportOnlineTransferwise(common.TransactionCase): def setUp(self): super().setUp() self.now = fields.Datetime.now() - self.currency_eur = self.env.ref('base.EUR') - self.currency_usd = self.env.ref('base.USD') - self.AccountJournal = self.env['account.journal'] - self.OnlineBankStatementProvider = self.env[ - 'online.bank.statement.provider' - ] - self.AccountBankStatement = self.env['account.bank.statement'] - self.AccountBankStatementLine = self.env['account.bank.statement.line'] + self.currency_eur = self.env.ref("base.EUR") + self.currency_usd = self.env.ref("base.USD") + self.AccountJournal = self.env["account.journal"] + self.OnlineBankStatementProvider = self.env["online.bank.statement.provider"] + self.AccountBankStatement = self.env["account.bank.statement"] + self.AccountBankStatementLine = self.env["account.bank.statement.line"] Provider = self.OnlineBankStatementProvider self.transferwise_parse_transaction = lambda payload: ( Provider._transferwise_transaction_to_lines( Provider._transferwise_preparse_transaction( - json.loads( - payload, - parse_float=Decimal, - ) + json.loads(payload, parse_float=Decimal,) ) ) ) - self.response_balance = MockedResponse(data="""[ + self.response_balance = MockedResponse( + data=b"""[ { "id": 42, "balances": [ @@ -80,24 +74,26 @@ class TestAccountBankAccountStatementImportOnlineTransferwise( } ] } - ]""".encode()) - self.response_ott = MockedResponse(exception=lambda: HTTPError( - 'https://wise.com/', - 403, - '403', - { - 'X-2FA-Approval-Result': 'REJECTED', - 'X-2FA-Approval': '0123456789', - }, - None, - )) - self.response_transactions = MockedResponse(data="""{ + ]""" + ) + self.response_ott = MockedResponse( + exception=lambda: HTTPError( + "https://wise.com/", + 403, + "403", + {"X-2FA-Approval-Result": "REJECTED", "X-2FA-Approval": "0123456789"}, + None, + ) + ) + self.response_transactions = MockedResponse( + data=b"""{ "transactions": [], "endOfStatementBalance": { "value": 42.00, "currency": "EUR" } - }""".encode()) + }""" + ) def test_values_transferwise_profile(self): mocked_response = json.loads( @@ -117,63 +113,58 @@ class TestAccountBankAccountStatementImportOnlineTransferwise( "name": "Brainbean Apps OÜ" } } -]""", parse_float=Decimal) +]""", + parse_float=Decimal, + ) values_transferwise_profile = [] with mock.patch( - _provider_class + '._transferwise_retrieve', - return_value=mocked_response, + _provider_class + "._transferwise_retrieve", return_value=mocked_response, ): - values_transferwise_profile = ( - self.OnlineBankStatementProvider.with_context({ - 'api_base': 'https://example.com', - 'api_key': 'dummy', - }).values_transferwise_profile() - ) + values_transferwise_profile = self.OnlineBankStatementProvider.with_context( + {"api_base": "https://example.com", "api_key": "dummy"} + ).values_transferwise_profile() self.assertEqual( values_transferwise_profile, [ - ('1234567890', 'Alexey Pelykh (personal)'), - ('1234567891', 'Brainbean Apps OÜ'), - ] + ("1234567890", "Alexey Pelykh (personal)"), + ("1234567891", "Brainbean Apps OÜ"), + ], ) def test_values_transferwise_profile_no_key(self): - values_transferwise_profile = ( - self.OnlineBankStatementProvider.with_context({ - 'api_base': 'https://example.com', - }).values_transferwise_profile() - ) + values_transferwise_profile = self.OnlineBankStatementProvider.with_context( + {"api_base": "https://example.com"} + ).values_transferwise_profile() self.assertEqual(values_transferwise_profile, []) def test_values_transferwise_profile_error(self): values_transferwise_profile = [] with mock.patch( - _provider_class + '._transferwise_retrieve', + _provider_class + "._transferwise_retrieve", side_effect=lambda: Exception(), ): - values_transferwise_profile = ( - self.OnlineBankStatementProvider.with_context({ - 'api_base': 'https://example.com', - 'api_key': 'dummy', - }).values_transferwise_profile() - ) + values_transferwise_profile = self.OnlineBankStatementProvider.with_context( + {"api_base": "https://example.com", "api_key": "dummy"} + ).values_transferwise_profile() self.assertEqual(values_transferwise_profile, []) def test_pull(self): - journal = self.AccountJournal.create({ - 'name': 'Bank', - 'type': 'bank', - 'code': 'BANK', - 'currency_id': self.currency_eur.id, - 'bank_statements_source': 'online', - 'online_bank_statement_provider': 'transferwise', - }) + journal = self.AccountJournal.create( + { + "name": "Bank", + "type": "bank", + "code": "BANK", + "currency_id": self.currency_eur.id, + "bank_statements_source": "online", + "online_bank_statement_provider": "transferwise", + } + ) provider = journal.online_bank_statement_provider_id - provider.origin = '1234567891' + provider.origin = "1234567891" def mock_response(url, api_key, private_key=None): - if '/borderless-accounts?profileId=1234567891' in url: + if "/borderless-accounts?profileId=1234567891" in url: payload = """[ { "id": 42, @@ -184,7 +175,7 @@ class TestAccountBankAccountStatementImportOnlineTransferwise( ] } ]""" - elif '/borderless-accounts/42/statement.json' in url: + elif "/borderless-accounts/42/statement.json" in url: payload = """{ "transactions": [], "endOfStatementBalance": { @@ -193,57 +184,58 @@ class TestAccountBankAccountStatementImportOnlineTransferwise( } }""" return json.loads(payload, parse_float=Decimal) + with mock.patch( - _provider_class + '._transferwise_retrieve', - side_effect=mock_response, + _provider_class + "._transferwise_retrieve", side_effect=mock_response, ): data = provider._obtain_statement_data( - self.now - relativedelta(hours=1), - self.now, + self.now - relativedelta(hours=1), self.now, ) self.assertEqual(len(data[0]), 0) - self.assertEqual(data[1]['balance_start'], 42.0) - self.assertEqual(data[1]['balance_end_real'], 42.0) + self.assertEqual(data[1]["balance_start"], 42.0) + self.assertEqual(data[1]["balance_end_real"], 42.0) def test_pull_no_data(self): - journal = self.AccountJournal.create({ - 'name': 'Bank', - 'type': 'bank', - 'code': 'BANK', - 'currency_id': self.currency_eur.id, - 'bank_statements_source': 'online', - 'online_bank_statement_provider': 'transferwise', - }) + journal = self.AccountJournal.create( + { + "name": "Bank", + "type": "bank", + "code": "BANK", + "currency_id": self.currency_eur.id, + "bank_statements_source": "online", + "online_bank_statement_provider": "transferwise", + } + ) provider = journal.online_bank_statement_provider_id - provider.origin = '1234567891' - provider.password = 'API_KEY' + provider.origin = "1234567891" + provider.password = "API_KEY" with mock.patch( - _provider_class + '._transferwise_retrieve', - return_value=[], + _provider_class + "._transferwise_retrieve", return_value=[], ): data = provider._obtain_statement_data( - self.now - relativedelta(hours=1), - self.now, + self.now - relativedelta(hours=1), self.now, ) self.assertFalse(data) def test_update_public_key(self): - journal = self.AccountJournal.create({ - 'name': 'Bank', - 'type': 'bank', - 'code': 'BANK', - 'currency_id': self.currency_eur.id, - 'bank_statements_source': 'online', - 'online_bank_statement_provider': 'transferwise', - }) + journal = self.AccountJournal.create( + { + "name": "Bank", + "type": "bank", + "code": "BANK", + "currency_id": self.currency_eur.id, + "bank_statements_source": "online", + "online_bank_statement_provider": "transferwise", + } + ) provider = journal.online_bank_statement_provider_id - provider.origin = '1234567891' - provider.password = 'API_KEY' + provider.origin = "1234567891" + provider.password = "API_KEY" with common.Form(provider) as provider_form: provider_form.certificate_private_key = """ @@ -279,22 +271,24 @@ edF6byMgXSzgOWYuRPXwmHpBQV0GiexQUAxVyUzaVWfil69LaFfXaw== self.assertTrue(provider.certificate_public_key) def test_sca(self): - journal = self.AccountJournal.create({ - 'name': 'Bank', - 'type': 'bank', - 'code': 'BANK', - 'currency_id': self.currency_eur.id, - 'bank_statements_source': 'online', - 'online_bank_statement_provider': 'transferwise', - }) + journal = self.AccountJournal.create( + { + "name": "Bank", + "type": "bank", + "code": "BANK", + "currency_id": self.currency_eur.id, + "bank_statements_source": "online", + "online_bank_statement_provider": "transferwise", + } + ) provider = journal.online_bank_statement_provider_id - provider.origin = '1234567891' - provider.password = 'API_KEY' + provider.origin = "1234567891" + provider.password = "API_KEY" provider.button_transferwise_generate_key() with mock.patch( - 'urllib.request.urlopen', + "urllib.request.urlopen", side_effect=[ self.response_balance, self.response_ott, @@ -304,16 +298,16 @@ edF6byMgXSzgOWYuRPXwmHpBQV0GiexQUAxVyUzaVWfil69LaFfXaw== ], ): data = provider._obtain_statement_data( - self.now - relativedelta(hours=1), - self.now, + self.now - relativedelta(hours=1), self.now, ) self.assertEqual(len(data[0]), 0) - self.assertEqual(data[1]['balance_start'], 42.0) - self.assertEqual(data[1]['balance_end_real'], 42.0) + self.assertEqual(data[1]["balance_start"], 42.0) + self.assertEqual(data[1]["balance_end_real"], 42.0) def test_transaction_parse_1(self): - lines = self.transferwise_parse_transaction("""{ + lines = self.transferwise_parse_transaction( + """{ "type": "CREDIT", "date": "2000-01-01T00:00:00.000Z", "amount": { @@ -337,23 +331,28 @@ edF6byMgXSzgOWYuRPXwmHpBQV0GiexQUAxVyUzaVWfil69LaFfXaw== "currency": "EUR" }, "referenceNumber": "TRANSFER-123456789" -}""") +}""" + ) self.assertEqual(len(lines), 1) - self.assertEqual(lines[0], { - 'date': datetime(2000, 1, 1), - 'amount': '0.42', - 'name': 'REF-XYZ', - 'note': ( - 'TRANSFER-123456789: Received money from SENDER with reference' - ' REF-XYZ' - ), - 'partner_name': 'SENDER', - 'account_number': 'XX00 0000 0000 0000', - 'unique_import_id': 'CREDIT-TRANSFER-123456789-946684800', - }) + self.assertEqual( + lines[0], + { + "date": datetime(2000, 1, 1), + "amount": "0.42", + "name": "REF-XYZ", + "note": ( + "TRANSFER-123456789: Received money from SENDER with reference" + " REF-XYZ" + ), + "partner_name": "SENDER", + "account_number": "XX00 0000 0000 0000", + "unique_import_id": "CREDIT-TRANSFER-123456789-946684800", + }, + ) def test_transaction_parse_2(self): - lines = self.transferwise_parse_transaction("""{ + lines = self.transferwise_parse_transaction( + """{ "type": "DEBIT", "date": "2000-01-01T00:00:00.000Z", "amount": { @@ -379,28 +378,36 @@ edF6byMgXSzgOWYuRPXwmHpBQV0GiexQUAxVyUzaVWfil69LaFfXaw== "currency": "EUR" }, "referenceNumber": "TRANSFER-123456789" -}""") +}""" + ) self.assertEqual(len(lines), 2) - self.assertEqual(lines[0], { - 'date': datetime(2000, 1, 1), - 'amount': '-200.00', - 'name': 'INVOICE 42-01', - 'note': 'TRANSFER-123456789: Sent money to John Doe', - 'partner_name': 'John Doe', - 'account_number': 'XX00 0000 0000 0000', - 'unique_import_id': 'DEBIT-TRANSFER-123456789-946684800', - }) - self.assertEqual(lines[1], { - 'date': datetime(2000, 1, 1), - 'amount': '-0.60', - 'name': 'Fee for TRANSFER-123456789', - 'note': 'Transaction fee for TRANSFER-123456789', - 'partner_name': 'Wise (former TransferWise)', - 'unique_import_id': 'DEBIT-TRANSFER-123456789-946684800-FEE', - }) + self.assertEqual( + lines[0], + { + "date": datetime(2000, 1, 1), + "amount": "-200.00", + "name": "INVOICE 42-01", + "note": "TRANSFER-123456789: Sent money to John Doe", + "partner_name": "John Doe", + "account_number": "XX00 0000 0000 0000", + "unique_import_id": "DEBIT-TRANSFER-123456789-946684800", + }, + ) + self.assertEqual( + lines[1], + { + "date": datetime(2000, 1, 1), + "amount": "-0.60", + "name": "Fee for TRANSFER-123456789", + "note": "Transaction fee for TRANSFER-123456789", + "partner_name": "Wise (former TransferWise)", + "unique_import_id": "DEBIT-TRANSFER-123456789-946684800-FEE", + }, + ) def test_transaction_parse_3(self): - lines = self.transferwise_parse_transaction("""{ + lines = self.transferwise_parse_transaction( + """{ "type": "DEBIT", "date": "2000-01-01T00:00:00.000Z", "amount": { @@ -436,24 +443,27 @@ edF6byMgXSzgOWYuRPXwmHpBQV0GiexQUAxVyUzaVWfil69LaFfXaw== "currency": "USD" }, "referenceNumber": "CARD-123456789" -}""") +}""" + ) self.assertEqual(len(lines), 1) - self.assertEqual(lines[0], { - 'date': datetime(2000, 1, 1), - 'amount': '-123.45', - 'name': ( - 'Card transaction of 1234.56 USD issued by Paypal *XX CITY' - ), - 'note': ( - 'CARD-123456789: Card transaction of 1234.56 USD issued by ' - 'Paypal *XX CITY' - ), - 'partner_name': 'Paypal *XX', - 'unique_import_id': 'DEBIT-CARD-123456789-946684800', - }) + self.assertEqual( + lines[0], + { + "date": datetime(2000, 1, 1), + "amount": "-123.45", + "name": ("Card transaction of 1234.56 USD issued by Paypal *XX CITY"), + "note": ( + "CARD-123456789: Card transaction of 1234.56 USD issued by " + "Paypal *XX CITY" + ), + "partner_name": "Paypal *XX", + "unique_import_id": "DEBIT-CARD-123456789-946684800", + }, + ) def test_transaction_parse_4(self): - lines = self.transferwise_parse_transaction("""{ + lines = self.transferwise_parse_transaction( + """{ "type": "DEBIT", "date": "2000-01-01T00:00:00.000Z", "amount": { @@ -499,34 +509,40 @@ edF6byMgXSzgOWYuRPXwmHpBQV0GiexQUAxVyUzaVWfil69LaFfXaw== "currency": "EUR" }, "referenceNumber": "CARD-123456789" -}""") +}""" + ) self.assertEqual(len(lines), 2) - self.assertEqual(lines[0], { - 'date': datetime(2000, 1, 1), - 'amount': '-455.55', - 'name': ( - 'Card transaction of 1234.56 USD issued by Paypal *XX CITY' - ), - 'note': ( - 'CARD-123456789: Card transaction of 1234.56 USD issued by' - ' Paypal *XX CITY' - ), - 'partner_name': 'Paypal *XX', - 'unique_import_id': 'DEBIT-CARD-123456789-946684800', - 'amount_currency': '-567.89', - 'currency_id': self.currency_usd.id, - }) - self.assertEqual(lines[1], { - 'date': datetime(2000, 1, 1), - 'amount': '-1.23', - 'name': 'Fee for CARD-123456789', - 'note': 'Transaction fee for CARD-123456789', - 'partner_name': 'Wise (former TransferWise)', - 'unique_import_id': 'DEBIT-CARD-123456789-946684800-FEE', - }) + self.assertEqual( + lines[0], + { + "date": datetime(2000, 1, 1), + "amount": "-455.55", + "name": ("Card transaction of 1234.56 USD issued by Paypal *XX CITY"), + "note": ( + "CARD-123456789: Card transaction of 1234.56 USD issued by" + " Paypal *XX CITY" + ), + "partner_name": "Paypal *XX", + "unique_import_id": "DEBIT-CARD-123456789-946684800", + "amount_currency": "-567.89", + "currency_id": self.currency_usd.id, + }, + ) + self.assertEqual( + lines[1], + { + "date": datetime(2000, 1, 1), + "amount": "-1.23", + "name": "Fee for CARD-123456789", + "note": "Transaction fee for CARD-123456789", + "partner_name": "Wise (former TransferWise)", + "unique_import_id": "DEBIT-CARD-123456789-946684800-FEE", + }, + ) def test_transaction_parse_5(self): - lines = self.transferwise_parse_transaction("""{ + lines = self.transferwise_parse_transaction( + """{ "type": "DEBIT", "date": "2000-01-01T00:00:00.000Z", "amount": { @@ -562,30 +578,38 @@ edF6byMgXSzgOWYuRPXwmHpBQV0GiexQUAxVyUzaVWfil69LaFfXaw== "currency": "EUR" }, "referenceNumber": "TRANSFER-123456789" -}""") +}""" + ) self.assertEqual(len(lines), 2) - self.assertEqual(lines[0], { - 'date': datetime(2000, 1, 1), - 'name': 'Invoice A from DD MMM YYYY', - 'note': 'TRANSFER-123456789: Sent money to Jane Doe', - 'partner_name': 'Jane Doe', - 'account_number': '(ADBCDEF) 0000000000000000', - 'amount': '-265.34', - 'amount_currency': '-297.00', - 'currency_id': self.currency_usd.id, - 'unique_import_id': 'DEBIT-TRANSFER-123456789-946684800', - }) - self.assertEqual(lines[1], { - 'date': datetime(2000, 1, 1), - 'name': 'Fee for TRANSFER-123456789', - 'note': 'Transaction fee for TRANSFER-123456789', - 'partner_name': 'Wise (former TransferWise)', - 'amount': '-5.21', - 'unique_import_id': 'DEBIT-TRANSFER-123456789-946684800-FEE', - }) + self.assertEqual( + lines[0], + { + "date": datetime(2000, 1, 1), + "name": "Invoice A from DD MMM YYYY", + "note": "TRANSFER-123456789: Sent money to Jane Doe", + "partner_name": "Jane Doe", + "account_number": "(ADBCDEF) 0000000000000000", + "amount": "-265.34", + "amount_currency": "-297.00", + "currency_id": self.currency_usd.id, + "unique_import_id": "DEBIT-TRANSFER-123456789-946684800", + }, + ) + self.assertEqual( + lines[1], + { + "date": datetime(2000, 1, 1), + "name": "Fee for TRANSFER-123456789", + "note": "Transaction fee for TRANSFER-123456789", + "partner_name": "Wise (former TransferWise)", + "amount": "-5.21", + "unique_import_id": "DEBIT-TRANSFER-123456789-946684800-FEE", + }, + ) def test_transaction_parse_6(self): - lines = self.transferwise_parse_transaction("""{ + lines = self.transferwise_parse_transaction( + """{ "type": "CREDIT", "date": "2000-01-01T00:00:00.000Z", "amount": { @@ -606,18 +630,23 @@ edF6byMgXSzgOWYuRPXwmHpBQV0GiexQUAxVyUzaVWfil69LaFfXaw== "currency": "EUR" }, "referenceNumber": "TRANSFER-123456789" -}""") +}""" + ) self.assertEqual(len(lines), 1) - self.assertEqual(lines[0], { - 'date': datetime(2000, 1, 1), - 'name': 'Topped up balance', - 'note': 'TRANSFER-123456789: Topped up balance', - 'amount': '5000.00', - 'unique_import_id': 'CREDIT-TRANSFER-123456789-946684800', - }) + self.assertEqual( + lines[0], + { + "date": datetime(2000, 1, 1), + "name": "Topped up balance", + "note": "TRANSFER-123456789: Topped up balance", + "amount": "5000.00", + "unique_import_id": "CREDIT-TRANSFER-123456789-946684800", + }, + ) def test_transaction_parse_7(self): - lines = self.transferwise_parse_transaction("""{ + lines = self.transferwise_parse_transaction( + """{ "type": "CREDIT", "date": "2000-01-01T00:00:00.000Z", "amount": { @@ -657,20 +686,25 @@ edF6byMgXSzgOWYuRPXwmHpBQV0GiexQUAxVyUzaVWfil69LaFfXaw== "currency": "EUR" }, "referenceNumber": "BALANCE-123456789" -}""") +}""" + ) self.assertEqual(len(lines), 1) - self.assertEqual(lines[0], { - 'date': datetime(2000, 1, 1), - 'name': 'Converted 7.93 USD to 6.93 EUR', - 'note': 'BALANCE-123456789: Converted 7.93 USD to 6.93 EUR', - 'amount': '6.93', - 'amount_currency': '7.93', - 'currency_id': self.currency_usd.id, - 'unique_import_id': 'CREDIT-BALANCE-123456789-946684800', - }) + self.assertEqual( + lines[0], + { + "date": datetime(2000, 1, 1), + "name": "Converted 7.93 USD to 6.93 EUR", + "note": "BALANCE-123456789: Converted 7.93 USD to 6.93 EUR", + "amount": "6.93", + "amount_currency": "7.93", + "currency_id": self.currency_usd.id, + "unique_import_id": "CREDIT-BALANCE-123456789-946684800", + }, + ) def test_transaction_parse_8(self): - lines = self.transferwise_parse_transaction("""{ + lines = self.transferwise_parse_transaction( + """{ "type": "DEBIT", "date": "2000-01-01T00:00:00.000Z", "amount": { @@ -710,28 +744,36 @@ edF6byMgXSzgOWYuRPXwmHpBQV0GiexQUAxVyUzaVWfil69LaFfXaw== "currency": "USD" }, "referenceNumber": "BALANCE-123456789" -}""") +}""" + ) self.assertEqual(len(lines), 2) - self.assertEqual(lines[0], { - 'date': datetime(2000, 1, 1), - 'name': 'Converted 7.93 USD to 6.93 EUR', - 'note': 'BALANCE-123456789: Converted 7.93 USD to 6.93 EUR', - 'amount': '-7.88', - 'amount_currency': '-6.93', - 'currency_id': self.currency_eur.id, - 'unique_import_id': 'DEBIT-BALANCE-123456789-946684800', - }) - self.assertEqual(lines[1], { - 'date': datetime(2000, 1, 1), - 'name': 'Fee for BALANCE-123456789', - 'note': 'Transaction fee for BALANCE-123456789', - 'amount': '-0.05', - 'partner_name': 'Wise (former TransferWise)', - 'unique_import_id': 'DEBIT-BALANCE-123456789-946684800-FEE', - }) + self.assertEqual( + lines[0], + { + "date": datetime(2000, 1, 1), + "name": "Converted 7.93 USD to 6.93 EUR", + "note": "BALANCE-123456789: Converted 7.93 USD to 6.93 EUR", + "amount": "-7.88", + "amount_currency": "-6.93", + "currency_id": self.currency_eur.id, + "unique_import_id": "DEBIT-BALANCE-123456789-946684800", + }, + ) + self.assertEqual( + lines[1], + { + "date": datetime(2000, 1, 1), + "name": "Fee for BALANCE-123456789", + "note": "Transaction fee for BALANCE-123456789", + "amount": "-0.05", + "partner_name": "Wise (former TransferWise)", + "unique_import_id": "DEBIT-BALANCE-123456789-946684800-FEE", + }, + ) def test_transaction_parse_9(self): - lines = self.transferwise_parse_transaction("""{ + lines = self.transferwise_parse_transaction( + """{ "type": "CREDIT", "date": "2000-01-01T00:00:00.000Z", "amount": { @@ -752,26 +794,34 @@ edF6byMgXSzgOWYuRPXwmHpBQV0GiexQUAxVyUzaVWfil69LaFfXaw== "currency": "USD" }, "referenceNumber": "TRANSFER-123456789" -}""") +}""" + ) self.assertEqual(len(lines), 2) - self.assertEqual(lines[0], { - 'date': datetime(2000, 1, 1), - 'name': 'Topped up balance', - 'note': 'TRANSFER-123456789: Topped up balance', - 'amount': '25.68', - 'unique_import_id': 'CREDIT-TRANSFER-123456789-946684800', - }) - self.assertEqual(lines[1], { - 'date': datetime(2000, 1, 1), - 'name': 'Fee for TRANSFER-123456789', - 'note': 'Transaction fee for TRANSFER-123456789', - 'amount': '-0.68', - 'partner_name': 'Wise (former TransferWise)', - 'unique_import_id': 'CREDIT-TRANSFER-123456789-946684800-FEE', - }) + self.assertEqual( + lines[0], + { + "date": datetime(2000, 1, 1), + "name": "Topped up balance", + "note": "TRANSFER-123456789: Topped up balance", + "amount": "25.68", + "unique_import_id": "CREDIT-TRANSFER-123456789-946684800", + }, + ) + self.assertEqual( + lines[1], + { + "date": datetime(2000, 1, 1), + "name": "Fee for TRANSFER-123456789", + "note": "Transaction fee for TRANSFER-123456789", + "amount": "-0.68", + "partner_name": "Wise (former TransferWise)", + "unique_import_id": "CREDIT-TRANSFER-123456789-946684800-FEE", + }, + ) def test_transaction_parse_10(self): - lines = self.transferwise_parse_transaction("""{ + lines = self.transferwise_parse_transaction( + """{ "type": "CREDIT", "date": "2000-01-01T00:00:00.000Z", "amount": { @@ -795,21 +845,28 @@ edF6byMgXSzgOWYuRPXwmHpBQV0GiexQUAxVyUzaVWfil69LaFfXaw== "currency": "USD" }, "referenceNumber": "TRANSFER-123456789" -}""") +}""" + ) self.assertEqual(len(lines), 2) - self.assertEqual(lines[0], { - 'date': datetime(2000, 1, 1), - 'name': 'Sent money to Acme Inc.', - 'note': 'TRANSFER-123456789: Sent money to Acme Inc.', - 'partner_name': 'Acme Inc.', - 'amount': '1800.00', - 'unique_import_id': 'CREDIT-TRANSFER-123456789-946684800', - }) - self.assertEqual(lines[1], { - 'date': datetime(2000, 1, 1), - 'name': 'Fee for TRANSFER-123456789', - 'note': 'Transaction fee for TRANSFER-123456789', - 'amount': '4.33', - 'partner_name': 'Wise (former TransferWise)', - 'unique_import_id': 'CREDIT-TRANSFER-123456789-946684800-FEE', - }) + self.assertEqual( + lines[0], + { + "date": datetime(2000, 1, 1), + "name": "Sent money to Acme Inc.", + "note": "TRANSFER-123456789: Sent money to Acme Inc.", + "partner_name": "Acme Inc.", + "amount": "1800.00", + "unique_import_id": "CREDIT-TRANSFER-123456789-946684800", + }, + ) + self.assertEqual( + lines[1], + { + "date": datetime(2000, 1, 1), + "name": "Fee for TRANSFER-123456789", + "note": "Transaction fee for TRANSFER-123456789", + "amount": "4.33", + "partner_name": "Wise (former TransferWise)", + "unique_import_id": "CREDIT-TRANSFER-123456789-946684800-FEE", + }, + ) diff --git a/account_bank_statement_import_online_transferwise/views/online_bank_statement_provider.xml b/account_bank_statement_import_online_transferwise/views/online_bank_statement_provider.xml index 5ded19b8..48119e97 100644 --- a/account_bank_statement_import_online_transferwise/views/online_bank_statement_provider.xml +++ b/account_bank_statement_import_online_transferwise/views/online_bank_statement_provider.xml @@ -1,15 +1,17 @@ - + - online.bank.statement.provider.form online.bank.statement.provider - + @@ -42,10 +44,7 @@ string="Private key" password="True" /> - +