diff --git a/account_bank_statement_import_online_transferwise/README.rst b/account_bank_statement_import_online_transferwise/README.rst new file mode 100644 index 0000000..f2e61be --- /dev/null +++ b/account_bank_statement_import_online_transferwise/README.rst @@ -0,0 +1,109 @@ +======================================== +Online Bank Statements: TransferWise.com +======================================== + +.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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/12.0/account_bank_statement_import_online_transferwise + :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-12-0/bank-statement-import-12-0-account_bank_statement_import_online_transferwise + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png + :target: https://runbot.odoo-community.org/runbot/174/12.0 + :alt: Try me on Runbot + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module provides online bank statements from +`TransferWise.com `_. + +**Table of contents** + +.. contents:: + :local: + +Configuration +============= + +To configure online bank statements provider: + +#. Go to *Invoicing > Configuration > Bank Accounts* +#. Open bank account to configure and edit it +#. Set *Bank Feeds* to *Online* +#. Select *TransferWise.com* as online bank statements provider in + *Online Bank Statements (OCA)* section +#. Save the bank account +#. Click on provider and configure provider-specific settings. + +or, alternatively: + +#. Go to *Invoicing > Overview* +#. Open settings of the corresponding journal account +#. Switch to *Bank Account* tab +#. Set *Bank Feeds* to *Online* +#. Select *TransferWise.com* as online bank statements provider in + *Online Bank Statements (OCA)* section +#. Save the bank account +#. Click on provider and configure provider-specific settings. + +Usage +===== + +To pull historical bank statements: + +#. Go to *Invoicing > Configuration > Bank Accounts* +#. Select specific bank accounts +#. Launch *Actions > Online Bank Statements Pull Wizard* +#. Configure date interval and click *Pull* + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us smashing it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +~~~~~~~ + +* Brainbean Apps +* Dataplug + +Contributors +~~~~~~~~~~~~ + +* Alexey Pelykh + +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 `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/account_bank_statement_import_online_transferwise/__init__.py b/account_bank_statement_import_online_transferwise/__init__.py new file mode 100644 index 0000000..31660d6 --- /dev/null +++ b/account_bank_statement_import_online_transferwise/__init__.py @@ -0,0 +1,3 @@ +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from . import models diff --git a/account_bank_statement_import_online_transferwise/__manifest__.py b/account_bank_statement_import_online_transferwise/__manifest__.py new file mode 100644 index 0000000..9b7b66f --- /dev/null +++ b/account_bank_statement_import_online_transferwise/__manifest__.py @@ -0,0 +1,24 @@ +# Copyright 2019 Brainbean Apps (https://brainbeanapps.com) +# Copyright 2019 Dataplug (https://dataplug.io) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +{ + 'name': 'Online Bank Statements: TransferWise.com', + 'version': '12.0.1.0.0', + 'author': + 'Brainbean Apps, ' + 'Dataplug, ' + 'Odoo Community Association (OCA)', + 'website': 'https://github.com/OCA/bank-statement-import/', + 'license': 'AGPL-3', + 'category': 'Accounting', + 'summary': 'Online bank statements for TransferWise.com', + 'depends': [ + 'account_bank_statement_import_online', + 'web_widget_dropdown_dynamic', + ], + 'data': [ + 'views/online_bank_statement_provider.xml', + ], + 'installable': True, +} diff --git a/account_bank_statement_import_online_transferwise/models/__init__.py b/account_bank_statement_import_online_transferwise/models/__init__.py new file mode 100644 index 0000000..3a96b70 --- /dev/null +++ b/account_bank_statement_import_online_transferwise/models/__init__.py @@ -0,0 +1,3 @@ +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from . import online_bank_statement_provider_transferwise 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 new file mode 100644 index 0000000..f3e53a0 --- /dev/null +++ b/account_bank_statement_import_online_transferwise/models/online_bank_statement_provider_transferwise.py @@ -0,0 +1,278 @@ +# Copyright 2019 Brainbean Apps (https://brainbeanapps.com) +# Copyright 2019 Dataplug (https://dataplug.io) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +from dateutil.relativedelta import relativedelta +import dateutil.parser +from decimal import Decimal +import itertools +import json +import pytz +import urllib.parse +import urllib.request + +from odoo import models, api, _ +from odoo.exceptions import UserError + +TRANSFERWISE_API_BASE = 'https://api.transferwise.com' + + +class OnlineBankStatementProviderTransferwise(models.Model): + _inherit = 'online.bank.statement.provider' + + @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') + if not api_key: + return [] + try: + url = api_base + '/v1/profiles' + data = self._transferwise_retrieve(url, api_key) + except: + 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 + )) + + @api.model + def _get_available_services(self): + return super()._get_available_services() + [ + ('transferwise', 'TransferWise.com'), + ] + + @api.multi + def _obtain_statement_data(self, date_since, date_until): + self.ensure_one() + if self.service != 'transferwise': + return super()._obtain_statement_data( + date_since, + date_until, + ) # pragma: no cover + + api_base = self.api_base or TRANSFERWISE_API_BASE + api_key = self.password + 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) + if date_until.tzinfo: + 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, + ) + data = self._transferwise_retrieve(url, api_key) + borderless_account = data[0]['id'] + balance = list(filter( + lambda balance: balance['currency'] == currency, + data[0]['balances'] + )) + if not balance: + return None + + # Notes on /statement endpoint: + # - intervalStart <= date < intervalEnd + + # Get starting balance + starting_balance_timestamp = date_since.isoformat() + 'Z' + url = api_base + ( + '/v1/borderless-accounts/%s/statement.json' + + '?currency=%s&intervalStart=%s&intervalEnd=%s' + ) % ( + borderless_account, + currency, + starting_balance_timestamp, + starting_balance_timestamp, + ) + data = self._transferwise_retrieve(url, api_key) + balance_start = data['endOfStatementBalance']['value'] + + # Get statements, using 469 days (around 1 year 3 month) as step. + interval_step = relativedelta(days=469) + interval_start = date_since + interval_end = date_until + transactions = [] + balance_end = None + while interval_start < interval_end: + url = api_base + ( + '/v1/borderless-accounts/%s/statement.json' + + '?currency=%s&intervalStart=%s&intervalEnd=%s' + ) % ( + borderless_account, + currency, + interval_start.isoformat() + 'Z', + min( + interval_start + interval_step, interval_end + ).isoformat() + 'Z', + ) + data = self._transferwise_retrieve(url, api_key) + transactions += data['transactions'] + balance_end = data['endOfStatementBalance']['value'] + interval_start += interval_step + if balance_end is None: + raise UserError(_('Ending balance unavailable')) + + # Normalize transactions' date, sort by it, and get lines + transactions = map( + 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'] + ) + ))) + + 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) + return transaction + + @api.model + def _transferwise_transaction_to_lines(self, transaction): + 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()).copy_abs() + if amount_value.is_signed(): + fees_value = fees_value.copy_negate() + amount_value -= fees_value + unique_import_id = '%s-%s-%s' % ( + 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, + } + 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'], + }) + else: + 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'] + other_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'] + ) + 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 + ) + if other_amount_value and other_currency: + 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': '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']: + raise UserError( + content['error_description'] + if 'error_description' in content + else 'Unknown error' + ) + return content + + @api.model + def _transferwise_retrieve(self, url, api_key): + with self._transferwise_urlopen(url, api_key) as response: + content = response.read().decode( + response.headers.get_content_charset() + ) + return self._transferwise_validate(content) + + @api.model + def _transferwise_urlopen(self, url, api_key): + if not api_key: + raise UserError(_('No API key specified!')) + request = urllib.request.Request(url) + request.add_header( + 'Authorization', + 'Bearer %s' % api_key + ) + return urllib.request.urlopen(request) diff --git a/account_bank_statement_import_online_transferwise/readme/CONFIGURE.rst b/account_bank_statement_import_online_transferwise/readme/CONFIGURE.rst new file mode 100644 index 0000000..0a35ffe --- /dev/null +++ b/account_bank_statement_import_online_transferwise/readme/CONFIGURE.rst @@ -0,0 +1,20 @@ +To configure online bank statements provider: + +#. Go to *Invoicing > Configuration > Bank Accounts* +#. Open bank account to configure and edit it +#. Set *Bank Feeds* to *Online* +#. Select *TransferWise.com* as online bank statements provider in + *Online Bank Statements (OCA)* section +#. Save the bank account +#. Click on provider and configure provider-specific settings. + +or, alternatively: + +#. Go to *Invoicing > Overview* +#. Open settings of the corresponding journal account +#. Switch to *Bank Account* tab +#. Set *Bank Feeds* to *Online* +#. Select *TransferWise.com* as online bank statements provider in + *Online Bank Statements (OCA)* section +#. Save the bank account +#. Click on provider and configure provider-specific settings. diff --git a/account_bank_statement_import_online_transferwise/readme/CONTRIBUTORS.rst b/account_bank_statement_import_online_transferwise/readme/CONTRIBUTORS.rst new file mode 100644 index 0000000..1c6a35a --- /dev/null +++ b/account_bank_statement_import_online_transferwise/readme/CONTRIBUTORS.rst @@ -0,0 +1 @@ +* Alexey Pelykh diff --git a/account_bank_statement_import_online_transferwise/readme/DESCRIPTION.rst b/account_bank_statement_import_online_transferwise/readme/DESCRIPTION.rst new file mode 100644 index 0000000..1f37905 --- /dev/null +++ b/account_bank_statement_import_online_transferwise/readme/DESCRIPTION.rst @@ -0,0 +1,2 @@ +This module provides online bank statements from +`TransferWise.com `_. diff --git a/account_bank_statement_import_online_transferwise/readme/USAGE.rst b/account_bank_statement_import_online_transferwise/readme/USAGE.rst new file mode 100644 index 0000000..03845f1 --- /dev/null +++ b/account_bank_statement_import_online_transferwise/readme/USAGE.rst @@ -0,0 +1,6 @@ +To pull historical bank statements: + +#. Go to *Invoicing > Configuration > Bank Accounts* +#. Select specific bank accounts +#. Launch *Actions > Online Bank Statements Pull Wizard* +#. Configure date interval and click *Pull* diff --git a/account_bank_statement_import_online_transferwise/tests/__init__.py b/account_bank_statement_import_online_transferwise/tests/__init__.py new file mode 100644 index 0000000..130e465 --- /dev/null +++ b/account_bank_statement_import_online_transferwise/tests/__init__.py @@ -0,0 +1,3 @@ +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from . import test_account_bank_statement_import_online_transferwise 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 new file mode 100644 index 0000000..1e65cf5 --- /dev/null +++ b/account_bank_statement_import_online_transferwise/tests/test_account_bank_statement_import_online_transferwise.py @@ -0,0 +1,552 @@ +# Copyright 2019 Brainbean Apps (https://brainbeanapps.com) +# Copyright 2019 Dataplug (https://dataplug.io) +# 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 unittest import mock + +from odoo.tests import common +from odoo import fields + +_module_ns = 'odoo.addons.account_bank_statement_import_online_transferwise' +_provider_class = ( + _module_ns + + '.models.online_bank_statement_provider_transferwise' + + '.OnlineBankStatementProviderTransferwise' +) + + +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'] + + Provider = self.OnlineBankStatementProvider + self.transferwise_parse_transaction = lambda payload: ( + Provider._transferwise_transaction_to_lines( + Provider._transferwise_preparse_transaction( + json.loads( + payload, + parse_float=Decimal, + ) + ) + ) + ) + + def test_values_transferwise_profile(self): + mocked_response = json.loads( + """[ + { + "id": 1234567890, + "type": "personal", + "details": { + "firstName": "Alexey", + "lastName": "Pelykh" + } + }, + { + "id": 1234567891, + "type": "business", + "details": { + "name": "Brainbean Apps OÜ" + } + } +]""", parse_float=Decimal) + values_transferwise_profile = [] + with mock.patch( + _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() + ) + self.assertEqual( + values_transferwise_profile, + [ + ('1234567890', 'Alexey Pelykh (personal)'), + ('1234567891', 'Brainbean Apps OÜ'), + ] + ) + + 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', + }) + + provider = journal.online_bank_statement_provider_id + provider.origin = '1234567891' + + def mock_response(url, api_key): + if '/borderless-accounts?profileId=1234567891' in url: + payload = """[ + { + "id": 42, + "balances": [ + { + "currency": "EUR" + } + ] + } +]""" + elif '/borderless-accounts/42/statement.json' in url: + payload = """{ + "transactions": [], + "endOfStatementBalance": { + "value": 42.00, + "currency": "EUR" + } +}""" + return json.loads(payload, parse_float=Decimal) + with mock.patch( + _provider_class + '._transferwise_retrieve', + side_effect=mock_response, + ): + data = provider._obtain_statement_data( + 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) + + def test_transaction_parse_1(self): + lines = self.transferwise_parse_transaction("""{ + "type": "CREDIT", + "date": "2000-01-01T00:00:00.000Z", + "amount": { + "value": 0.42, + "currency": "EUR" + }, + "totalFees": { + "value": 0.00, + "currency": "EUR" + }, + "details": { + "type": "DEPOSIT", + "description": "Received money from SENDER with reference REF-XYZ", + "senderName": "SENDER", + "senderAccount": "XX00 0000 0000 0000", + "paymentReference": "REF-XYZ" + }, + "exchangeDetails": null, + "runningBalance": { + "value": 0.42, + "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', + }) + + def test_transaction_parse_2(self): + lines = self.transferwise_parse_transaction("""{ + "type": "DEBIT", + "date": "2000-01-01T00:00:00.000Z", + "amount": { + "value": -200.60, + "currency": "EUR" + }, + "totalFees": { + "value": 0.60, + "currency": "EUR" + }, + "details": { + "type": "TRANSFER", + "description": "Sent money to John Doe", + "recipient": { + "name": "John Doe", + "bankAccount": "XX00 0000 0000 0000" + }, + "paymentReference": "INVOICE 42-01" + }, + "exchangeDetails": null, + "runningBalance": { + "value": 100.42, + "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': 'TransferWise', + 'unique_import_id': 'DEBIT-TRANSFER-123456789-946684800-FEE', + }) + + def test_transaction_parse_3(self): + lines = self.transferwise_parse_transaction("""{ + "type": "DEBIT", + "date": "2000-01-01T00:00:00.000Z", + "amount": { + "value": -123.45, + "currency": "USD" + }, + "totalFees": { + "value": 0.00, + "currency": "USD" + }, + "details": { + "type": "CARD", + "description": + "Card transaction of 1234.56 USD issued by Paypal *XX CITY", + "amount": { + "value": 1234.56, + "currency": "USD" + }, + "category": "Professional Services not elsewh", + "merchant": { + "name": "Paypal *XX", + "firstLine": null, + "postCode": "12345", + "city": "CITY", + "state": null, + "country": "GB", + "category": "Professional Services not elsewh" + } + }, + "exchangeDetails": null, + "runningBalance": { + "value": 0.00, + "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', + }) + + def test_transaction_parse_4(self): + lines = self.transferwise_parse_transaction("""{ + "type": "DEBIT", + "date": "2000-01-01T00:00:00.000Z", + "amount": { + "value": -456.78, + "currency": "EUR" + }, + "totalFees": { + "value": 1.23, + "currency": "EUR" + }, + "details": { + "type": "CARD", + "description": + "Card transaction of 1234.56 USD issued by Paypal *XX CITY", + "amount": { + "value": 1234.56, + "currency": "USD" + }, + "category": "Professional Services not elsewh", + "merchant": { + "name": "Paypal *XX", + "firstLine": null, + "postCode": "12345", + "city": "CITY", + "state": null, + "country": "GB", + "category": "Professional Services not elsewh" + } + }, + "exchangeDetails": { + "toAmount": { + "value": 567.89, + "currency": "USD" + }, + "fromAmount": { + "value": 456.78, + "currency": "EUR" + }, + "rate": 1.12260 + }, + "runningBalance": { + "value": 0.00, + "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': 'TransferWise', + 'unique_import_id': 'DEBIT-CARD-123456789-946684800-FEE', + }) + + def test_transaction_parse_5(self): + lines = self.transferwise_parse_transaction("""{ + "type": "DEBIT", + "date": "2000-01-01T00:00:00.000Z", + "amount": { + "value": -270.55, + "currency": "EUR" + }, + "totalFees": { + "value": 5.21, + "currency": "EUR" + }, + "details": { + "type": "TRANSFER", + "description": "Sent money to Jane Doe", + "recipient": { + "name": "Jane Doe", + "bankAccount": "(ADBCDEF) 0000000000000000" + }, + "paymentReference": "Invoice A from DD MMM YYYY" + }, + "exchangeDetails": { + "toAmount": { + "value": 297.00, + "currency": "USD" + }, + "fromAmount": { + "value": 265.34, + "currency": "EUR" + }, + "rate": 1.11930 + }, + "runningBalance": { + "value": 2360.43, + "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': 'TransferWise', + 'amount': '-5.21', + 'unique_import_id': 'DEBIT-TRANSFER-123456789-946684800-FEE', + }) + + def test_transaction_parse_6(self): + lines = self.transferwise_parse_transaction("""{ + "type": "CREDIT", + "date": "2000-01-01T00:00:00.000Z", + "amount": { + "value": 5000.00, + "currency": "EUR" + }, + "totalFees": { + "value": 0.00, + "currency": "EUR" + }, + "details": { + "type": "MONEY_ADDED", + "description": "Topped up balance" + }, + "exchangeDetails": null, + "runningBalance": { + "value": 7071.13, + "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', + }) + + def test_transaction_parse_7(self): + lines = self.transferwise_parse_transaction("""{ + "type": "CREDIT", + "date": "2000-01-01T00:00:00.000Z", + "amount": { + "value": 6.93, + "currency": "EUR" + }, + "totalFees": { + "value": 0.00, + "currency": "EUR" + }, + "details": { + "type": "CONVERSION", + "description": "Converted 7.93 USD to 6.93 EUR", + "sourceAmount": { + "value": 7.93, + "currency": "USD" + }, + "targetAmount": { + "value": 6.93, + "currency": "EUR" + }, + "rate": 0.87944162 + }, + "exchangeDetails": { + "toAmount": { + "value": 6.93, + "currency": "EUR" + }, + "fromAmount": { + "value": 7.93, + "currency": "USD" + }, + "rate": 0.87944 + }, + "runningBalance": { + "value": 255.00, + "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', + }) + + def test_transaction_parse_8(self): + lines = self.transferwise_parse_transaction("""{ + "type": "DEBIT", + "date": "2000-01-01T00:00:00.000Z", + "amount": { + "value": -7.93, + "currency": "USD" + }, + "totalFees": { + "value": 0.05, + "currency": "USD" + }, + "details": { + "type": "CONVERSION", + "description": "Converted 7.93 USD to 6.93 EUR", + "sourceAmount": { + "value": 7.93, + "currency": "USD" + }, + "targetAmount": { + "value": 6.93, + "currency": "EUR" + }, + "rate": 0.87944162 + }, + "exchangeDetails": { + "toAmount": { + "value": 6.93, + "currency": "EUR" + }, + "fromAmount": { + "value": 7.93, + "currency": "USD" + }, + "rate": 0.87944 + }, + "runningBalance": { + "value": 0.00, + "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': 'TransferWise', + 'unique_import_id': 'DEBIT-BALANCE-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 new file mode 100644 index 0000000..2583a9f --- /dev/null +++ b/account_bank_statement_import_online_transferwise/views/online_bank_statement_provider.xml @@ -0,0 +1,44 @@ + + + + + + online.bank.statement.provider.form + online.bank.statement.provider + + + + + + + + + + + + + + + + +