diff --git a/currency_rate_update_transferwise/i18n/currency_rate_update_transferwise.pot b/currency_rate_update_transferwise/i18n/currency_rate_update_transferwise.pot deleted file mode 100644 index 4cebc8db..00000000 --- a/currency_rate_update_transferwise/i18n/currency_rate_update_transferwise.pot +++ /dev/null @@ -1,93 +0,0 @@ -# Translation of Odoo Server. -# This file contains the translation of the following modules: -# * currency_rate_update_transferwise -# -msgid "" -msgstr "" -"Project-Id-Version: Odoo Server 14.0\n" -"Report-Msgid-Bugs-To: \n" -"Last-Translator: \n" -"Language-Team: \n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: \n" -"Plural-Forms: \n" - -#. module: currency_rate_update_transferwise -#: model_terms:ir.ui.view,arch_db:currency_rate_update_transferwise.res_config_settings_view_form -msgid "" -"TransferWise.com Provider\n" -" " -msgstr "" - -#. module: currency_rate_update_transferwise -#: model:ir.model.fields,field_description:currency_rate_update_transferwise.field_res_config_settings__transferwise_api_key -msgid "API Key" -msgstr "" - -#. module: currency_rate_update_transferwise -#: model:ir.model,name:currency_rate_update_transferwise.model_res_company -msgid "Companies" -msgstr "" - -#. module: currency_rate_update_transferwise -#: model:ir.model,name:currency_rate_update_transferwise.model_res_config_settings -msgid "Config Settings" -msgstr "" - -#. module: currency_rate_update_transferwise -#: model:ir.model,name:currency_rate_update_transferwise.model_res_currency_rate_provider -msgid "Currency Rates Provider" -msgstr "" - -#. module: currency_rate_update_transferwise -#: model:ir.model.fields,field_description:currency_rate_update_transferwise.field_res_company__display_name -#: model:ir.model.fields,field_description:currency_rate_update_transferwise.field_res_config_settings__display_name -#: model:ir.model.fields,field_description:currency_rate_update_transferwise.field_res_currency_rate_provider__display_name -msgid "Display Name" -msgstr "" - -#. module: currency_rate_update_transferwise -#: model:ir.model.fields,field_description:currency_rate_update_transferwise.field_res_company__id -#: model:ir.model.fields,field_description:currency_rate_update_transferwise.field_res_config_settings__id -#: model:ir.model.fields,field_description:currency_rate_update_transferwise.field_res_currency_rate_provider__id -msgid "ID" -msgstr "" - -#. module: currency_rate_update_transferwise -#: model:ir.model.fields,field_description:currency_rate_update_transferwise.field_res_company____last_update -#: model:ir.model.fields,field_description:currency_rate_update_transferwise.field_res_config_settings____last_update -#: model:ir.model.fields,field_description:currency_rate_update_transferwise.field_res_currency_rate_provider____last_update -msgid "Last Modified on" -msgstr "" - -#. module: currency_rate_update_transferwise -#: code:addons/currency_rate_update_transferwise/models/res_currency_rate_provider_TransferWise.py:0 -#, python-format -msgid "No Wise.com credentials specified!" -msgstr "" - -#. module: currency_rate_update_transferwise -#: model:ir.model.fields,field_description:currency_rate_update_transferwise.field_res_currency_rate_provider__service -msgid "Source Service" -msgstr "" - -#. module: currency_rate_update_transferwise -#: model_terms:ir.ui.view,arch_db:currency_rate_update_transferwise.res_config_settings_view_form -msgid "TransferWise.com" -msgstr "" - -#. module: currency_rate_update_transferwise -#: model:ir.model.fields.selection,name:currency_rate_update_transferwise.selection__res_currency_rate_provider__service__transferwise -msgid "Wise.com" -msgstr "" - -#. module: currency_rate_update_transferwise -#: model:ir.model.fields,field_description:currency_rate_update_transferwise.field_res_company__transferwise_api_key -msgid "Wise.com API Key" -msgstr "" - -#. module: currency_rate_update_transferwise -#: model_terms:ir.ui.view,arch_db:currency_rate_update_transferwise.res_config_settings_view_form -msgid "credentials" -msgstr "" diff --git a/currency_rate_update_transferwise/i18n/es.po b/currency_rate_update_transferwise/i18n/es.po deleted file mode 100644 index 00873935..00000000 --- a/currency_rate_update_transferwise/i18n/es.po +++ /dev/null @@ -1,101 +0,0 @@ -# Translation of Odoo Server. -# This file contains the translation of the following modules: -# * currency_rate_update_transferwise -# -msgid "" -msgstr "" -"Project-Id-Version: Odoo Server 14.0\n" -"Report-Msgid-Bugs-To: \n" -"PO-Revision-Date: 2023-12-19 14:36+0000\n" -"Last-Translator: Ivorra78 \n" -"Language-Team: none\n" -"Language: es\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: \n" -"Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 4.17\n" - -#. module: currency_rate_update_transferwise -#: model_terms:ir.ui.view,arch_db:currency_rate_update_transferwise.res_config_settings_view_form -msgid "" -"TransferWise.com Provider\n" -" " -msgstr "" -"Proveedor de TransferWise.com\n" -" " - -#. module: currency_rate_update_transferwise -#: model:ir.model.fields,field_description:currency_rate_update_transferwise.field_res_config_settings__transferwise_api_key -msgid "API Key" -msgstr "Clave API" - -#. module: currency_rate_update_transferwise -#: model:ir.model,name:currency_rate_update_transferwise.model_res_company -msgid "Companies" -msgstr "Compañías" - -#. module: currency_rate_update_transferwise -#: model:ir.model,name:currency_rate_update_transferwise.model_res_config_settings -msgid "Config Settings" -msgstr "Ajustes de Config" - -#. module: currency_rate_update_transferwise -#: model:ir.model,name:currency_rate_update_transferwise.model_res_currency_rate_provider -msgid "Currency Rates Provider" -msgstr "Proveedor de Tipos de Cambio" - -#. module: currency_rate_update_transferwise -#: model:ir.model.fields,field_description:currency_rate_update_transferwise.field_res_company__display_name -#: model:ir.model.fields,field_description:currency_rate_update_transferwise.field_res_config_settings__display_name -#: model:ir.model.fields,field_description:currency_rate_update_transferwise.field_res_currency_rate_provider__display_name -msgid "Display Name" -msgstr "Mostrar Nombre" - -#. module: currency_rate_update_transferwise -#: model:ir.model.fields,field_description:currency_rate_update_transferwise.field_res_company__id -#: model:ir.model.fields,field_description:currency_rate_update_transferwise.field_res_config_settings__id -#: model:ir.model.fields,field_description:currency_rate_update_transferwise.field_res_currency_rate_provider__id -msgid "ID" -msgstr "ID (identificación)" - -#. module: currency_rate_update_transferwise -#: model:ir.model.fields,field_description:currency_rate_update_transferwise.field_res_company____last_update -#: model:ir.model.fields,field_description:currency_rate_update_transferwise.field_res_config_settings____last_update -#: model:ir.model.fields,field_description:currency_rate_update_transferwise.field_res_currency_rate_provider____last_update -msgid "Last Modified on" -msgstr "Última Modificación el" - -#. module: currency_rate_update_transferwise -#: code:addons/currency_rate_update_transferwise/models/res_currency_rate_provider_TransferWise.py:0 -#, python-format -msgid "No Wise.com credentials specified!" -msgstr "¡No se han especificado credenciales de Wise.com!" - -#. module: currency_rate_update_transferwise -#: model:ir.model.fields,field_description:currency_rate_update_transferwise.field_res_currency_rate_provider__service -msgid "Source Service" -msgstr "Servicio Fuente" - -#. module: currency_rate_update_transferwise -#: model_terms:ir.ui.view,arch_db:currency_rate_update_transferwise.res_config_settings_view_form -msgid "TransferWise.com" -msgstr "TransferWise.com" - -#. module: currency_rate_update_transferwise -#: model:ir.model.fields.selection,name:currency_rate_update_transferwise.selection__res_currency_rate_provider__service__transferwise -msgid "Wise.com" -msgstr "Wise.com" - -#. module: currency_rate_update_transferwise -#: model:ir.model.fields,field_description:currency_rate_update_transferwise.field_res_company__transferwise_api_key -msgid "Wise.com API Key" -msgstr "Wise.com Clave API" - -#. module: currency_rate_update_transferwise -#: model_terms:ir.ui.view,arch_db:currency_rate_update_transferwise.res_config_settings_view_form -msgid "credentials" -msgstr "Credenciales" diff --git a/currency_rate_update_transferwise/i18n/fr.po b/currency_rate_update_transferwise/i18n/fr.po deleted file mode 100644 index 3da51eaf..00000000 --- a/currency_rate_update_transferwise/i18n/fr.po +++ /dev/null @@ -1,112 +0,0 @@ -# Translation of Odoo Server. -# This file contains the translation of the following modules: -# * currency_rate_update_transferwise -# -msgid "" -msgstr "" -"Project-Id-Version: Odoo Server 13.0\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: \n" -"PO-Revision-Date: \n" -"Last-Translator: Alpis Traduction et Interprétation \n" -"Language-Team: \n" -"Language: fr\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=2; plural=(n > 1);\n" -"X-Generator: Poedit 2.4.3\n" - -#. module: currency_rate_update_transferwise -#: model_terms:ir.ui.view,arch_db:currency_rate_update_transferwise.res_config_settings_view_form -msgid "" -"TransferWise.com Provider\n" -" " -msgstr "" - -#. module: currency_rate_update_transferwise -#: model:ir.model.fields,field_description:currency_rate_update_transferwise.field_res_config_settings__transferwise_api_key -msgid "API Key" -msgstr "Clé API" - -#. module: currency_rate_update_transferwise -#: model:ir.model,name:currency_rate_update_transferwise.model_res_company -msgid "Companies" -msgstr "Sociétés" - -#. module: currency_rate_update_transferwise -#: model:ir.model,name:currency_rate_update_transferwise.model_res_config_settings -msgid "Config Settings" -msgstr "Configuration" - -#. module: currency_rate_update_transferwise -#: model:ir.model,name:currency_rate_update_transferwise.model_res_currency_rate_provider -msgid "Currency Rates Provider" -msgstr "Fournisseur de taux de change" - -#. module: currency_rate_update_transferwise -#: model:ir.model.fields,field_description:currency_rate_update_transferwise.field_res_company__display_name -#: model:ir.model.fields,field_description:currency_rate_update_transferwise.field_res_config_settings__display_name -#: model:ir.model.fields,field_description:currency_rate_update_transferwise.field_res_currency_rate_provider__display_name -msgid "Display Name" -msgstr "" - -#. module: currency_rate_update_transferwise -#: model:ir.model.fields,field_description:currency_rate_update_transferwise.field_res_company__id -#: model:ir.model.fields,field_description:currency_rate_update_transferwise.field_res_config_settings__id -#: model:ir.model.fields,field_description:currency_rate_update_transferwise.field_res_currency_rate_provider__id -msgid "ID" -msgstr "" - -#. module: currency_rate_update_transferwise -#: model:ir.model.fields,field_description:currency_rate_update_transferwise.field_res_company____last_update -#: model:ir.model.fields,field_description:currency_rate_update_transferwise.field_res_config_settings____last_update -#: model:ir.model.fields,field_description:currency_rate_update_transferwise.field_res_currency_rate_provider____last_update -msgid "Last Modified on" -msgstr "" - -#. module: currency_rate_update_transferwise -#: code:addons/currency_rate_update_transferwise/models/res_currency_rate_provider_TransferWise.py:0 -#, python-format -msgid "No Wise.com credentials specified!" -msgstr "Aucun identifiant Wise.com fourni !" - -#. module: currency_rate_update_transferwise -#: model:ir.model.fields,field_description:currency_rate_update_transferwise.field_res_currency_rate_provider__service -msgid "Source Service" -msgstr "Fournisseur" - -#. module: currency_rate_update_transferwise -#: model_terms:ir.ui.view,arch_db:currency_rate_update_transferwise.res_config_settings_view_form -msgid "TransferWise.com" -msgstr "" - -#. module: currency_rate_update_transferwise -#: model:ir.model.fields.selection,name:currency_rate_update_transferwise.selection__res_currency_rate_provider__service__transferwise -msgid "Wise.com" -msgstr "Wise.com" - -#. module: currency_rate_update_transferwise -#: model:ir.model.fields,field_description:currency_rate_update_transferwise.field_res_company__transferwise_api_key -msgid "Wise.com API Key" -msgstr "Clé API Wise.com" - -#. module: currency_rate_update_transferwise -#: model_terms:ir.ui.view,arch_db:currency_rate_update_transferwise.res_config_settings_view_form -msgid "credentials" -msgstr "identifiants" - -#~ msgid "" -#~ "Wise.com Provider\n" -#~ " " -#~ msgstr "" -#~ "Fournisseur Wise.com \n" -#~ " " diff --git a/currency_rate_update_transferwise/i18n/it.po b/currency_rate_update_transferwise/i18n/it.po deleted file mode 100644 index 4f469186..00000000 --- a/currency_rate_update_transferwise/i18n/it.po +++ /dev/null @@ -1,94 +0,0 @@ -# Translation of Odoo Server. -# This file contains the translation of the following modules: -# * currency_rate_update_transferwise -# -msgid "" -msgstr "" -"Project-Id-Version: Odoo Server 14.0\n" -"Report-Msgid-Bugs-To: \n" -"Last-Translator: Automatically generated\n" -"Language-Team: none\n" -"Language: it\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: \n" -"Plural-Forms: nplurals=2; plural=n != 1;\n" - -#. module: currency_rate_update_transferwise -#: model_terms:ir.ui.view,arch_db:currency_rate_update_transferwise.res_config_settings_view_form -msgid "" -"TransferWise.com Provider\n" -" " -msgstr "" - -#. module: currency_rate_update_transferwise -#: model:ir.model.fields,field_description:currency_rate_update_transferwise.field_res_config_settings__transferwise_api_key -msgid "API Key" -msgstr "" - -#. module: currency_rate_update_transferwise -#: model:ir.model,name:currency_rate_update_transferwise.model_res_company -msgid "Companies" -msgstr "" - -#. module: currency_rate_update_transferwise -#: model:ir.model,name:currency_rate_update_transferwise.model_res_config_settings -msgid "Config Settings" -msgstr "" - -#. module: currency_rate_update_transferwise -#: model:ir.model,name:currency_rate_update_transferwise.model_res_currency_rate_provider -msgid "Currency Rates Provider" -msgstr "" - -#. module: currency_rate_update_transferwise -#: model:ir.model.fields,field_description:currency_rate_update_transferwise.field_res_company__display_name -#: model:ir.model.fields,field_description:currency_rate_update_transferwise.field_res_config_settings__display_name -#: model:ir.model.fields,field_description:currency_rate_update_transferwise.field_res_currency_rate_provider__display_name -msgid "Display Name" -msgstr "" - -#. module: currency_rate_update_transferwise -#: model:ir.model.fields,field_description:currency_rate_update_transferwise.field_res_company__id -#: model:ir.model.fields,field_description:currency_rate_update_transferwise.field_res_config_settings__id -#: model:ir.model.fields,field_description:currency_rate_update_transferwise.field_res_currency_rate_provider__id -msgid "ID" -msgstr "" - -#. module: currency_rate_update_transferwise -#: model:ir.model.fields,field_description:currency_rate_update_transferwise.field_res_company____last_update -#: model:ir.model.fields,field_description:currency_rate_update_transferwise.field_res_config_settings____last_update -#: model:ir.model.fields,field_description:currency_rate_update_transferwise.field_res_currency_rate_provider____last_update -msgid "Last Modified on" -msgstr "" - -#. module: currency_rate_update_transferwise -#: code:addons/currency_rate_update_transferwise/models/res_currency_rate_provider_TransferWise.py:0 -#, python-format -msgid "No Wise.com credentials specified!" -msgstr "" - -#. module: currency_rate_update_transferwise -#: model:ir.model.fields,field_description:currency_rate_update_transferwise.field_res_currency_rate_provider__service -msgid "Source Service" -msgstr "" - -#. module: currency_rate_update_transferwise -#: model_terms:ir.ui.view,arch_db:currency_rate_update_transferwise.res_config_settings_view_form -msgid "TransferWise.com" -msgstr "" - -#. module: currency_rate_update_transferwise -#: model:ir.model.fields.selection,name:currency_rate_update_transferwise.selection__res_currency_rate_provider__service__transferwise -msgid "Wise.com" -msgstr "" - -#. module: currency_rate_update_transferwise -#: model:ir.model.fields,field_description:currency_rate_update_transferwise.field_res_company__transferwise_api_key -msgid "Wise.com API Key" -msgstr "" - -#. module: currency_rate_update_transferwise -#: model_terms:ir.ui.view,arch_db:currency_rate_update_transferwise.res_config_settings_view_form -msgid "credentials" -msgstr "" diff --git a/currency_rate_update_transferwise/models/res_currency_rate_provider_TransferWise.py b/currency_rate_update_transferwise/models/res_currency_rate_provider_TransferWise.py deleted file mode 100644 index 28c6ea10..00000000 --- a/currency_rate_update_transferwise/models/res_currency_rate_provider_TransferWise.py +++ /dev/null @@ -1,110 +0,0 @@ -# Copyright 2019 Brainbean Apps (https://brainbeanapps.com) -# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). - -import itertools -import json -import urllib.parse -import urllib.request -from collections import defaultdict -from datetime import timedelta - -import dateutil.parser - -from odoo import _, fields, models -from odoo.exceptions import UserError - - -class ResCurrencyRateProviderTransferWise(models.Model): - _inherit = "res.currency.rate.provider" - - service = fields.Selection( - selection_add=[("TransferWise", "Wise.com")], - ondelete={"TransferWise": "set default", "Wise.com": "set default"}, - ) - - def _get_supported_currencies(self): - self.ensure_one() - if self.service != "TransferWise": - return super()._get_supported_currencies() # pragma: no cover - - url = "https://api.transferwise.com/v1/rates" - data = json.loads(self._transferwise_provider_retrieve(url)) - if "error" in data and data["error"]: - raise UserError( - data["error_description"] - if "error_description" in data - else "Unknown error" - ) - - return list( - set( - itertools.chain.from_iterable( - map(lambda entry: [entry["source"], entry["target"]], data) - ) - ) - ) - - def _obtain_rates(self, base_currency, currencies, date_from, date_to): - self.ensure_one() - if self.service != "TransferWise": - return super()._obtain_rates( - base_currency, currencies, date_from, date_to - ) # pragma: no cover - - content = defaultdict(dict) - - # NOTE: Step in 10 days is selected to reduce memory impact - step = timedelta(days=10) - for currency in currencies: - since = date_from - until = since + step - while since <= date_to: - url = ( - "https://api.transferwise.com/v1/rates" - + "?from=%(from)s" - + "&to=%(to)s" - + "&source=%(source)s" - + "&target=%(target)s" - + "&group=day" - ) % { - "source": base_currency, - "target": currency, - "from": str(since), - "to": str(min(until, date_to)), - } - data = json.loads(self._transferwise_provider_retrieve(url)) - if "error" in data and data["error"]: - raise UserError( - data["error_description"] - if "error_description" in data - else "Unknown error" - ) - for entry in data: - date = dateutil.parser.parse(entry["time"]).date() - date_content = content[date.isoformat()] - date_content[currency] = entry["rate"] - - since += step - until += step - - return content - - def _transferwise_provider_retrieve(self, url): - self.ensure_one() - with self._transferwise_provider_urlopen(url) as response: - content = response.read().decode( - response.headers.get_content_charset("utf-8") - ) - return content - - def _transferwise_provider_urlopen(self, url): - self.ensure_one() - - if not self.company_id.transferwise_api_key: - raise UserError(_("No Wise.com credentials specified!")) - - request = urllib.request.Request(url) - request.add_header( - "Authorization", "Bearer %s" % self.company_id.transferwise_api_key - ) - return urllib.request.urlopen(request) diff --git a/currency_rate_update_transferwise/readme/CONFIGURE.rst b/currency_rate_update_transferwise/readme/CONFIGURE.rst deleted file mode 100644 index 09812e19..00000000 --- a/currency_rate_update_transferwise/readme/CONFIGURE.rst +++ /dev/null @@ -1,4 +0,0 @@ -To configure Wise.com currency rates provider credentials: - -# Go to *Invoicing > Configuration > Settings* -# Fill application credentials in *Currencies > Wise.com Provider* section diff --git a/currency_rate_update_transferwise/readme/DESCRIPTION.rst b/currency_rate_update_transferwise/readme/DESCRIPTION.rst deleted file mode 100644 index 76851805..00000000 --- a/currency_rate_update_transferwise/readme/DESCRIPTION.rst +++ /dev/null @@ -1,2 +0,0 @@ -This module adds `Wise.com `_ -currency exchange rates provider. diff --git a/currency_rate_update_transferwise/tests/test_currency_rate_update_transferwise.py b/currency_rate_update_transferwise/tests/test_currency_rate_update_transferwise.py deleted file mode 100644 index cae59f1f..00000000 --- a/currency_rate_update_transferwise/tests/test_currency_rate_update_transferwise.py +++ /dev/null @@ -1,93 +0,0 @@ -# Copyright 2019 Brainbean Apps (https://brainbeanapps.com) -# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). - -from unittest import mock -from urllib.error import HTTPError - -from dateutil.relativedelta import relativedelta - -from odoo import fields -from odoo.exceptions import UserError -from odoo.tests import common - -_module_ns = "odoo.addons.currency_rate_update_transferwise" -_provider_class = ( - _module_ns - + ".models.res_currency_rate_provider_TransferWise" - + ".ResCurrencyRateProviderTransferWise" -) - - -class TestResCurrencyRateProviderTransferWise(common.TransactionCase): - def setUp(self): - super().setUp() - - self.Company = self.env["res.company"] - self.CurrencyRate = self.env["res.currency.rate"] - self.CurrencyRateProvider = self.env["res.currency.rate.provider"] - - self.today = fields.Date.today() - self.eur_currency = self.env.ref("base.EUR") - self.transferwise_provider = self.CurrencyRateProvider.create( - {"service": "TransferWise", "currency_ids": [(4, self.eur_currency.id)]} - ) - self.CurrencyRate.search([]).unlink() - - def test_supported_currencies(self): - mocked_response = """[ - { - "rate": 1.0, - "source": "EUR", - "target": "EUR", - "time": "2019-01-01T00:00:00+0000" - } -]""" - supported_currencies = [] - with mock.patch( - _provider_class + "._transferwise_provider_retrieve", - return_value=mocked_response, - ): - supported_currencies = ( - self.transferwise_provider._get_supported_currencies() - ) - self.assertEqual(len(supported_currencies), 1) - - def test_update(self): - date = self.today - relativedelta(days=1) - mocked_response = """[ - {{ - "rate": 0.86995, - "source": "USD", - "target": "EUR", - "time": "{date}T00:00:00+0000" - }} -]""".format( - date=str(date), - ) - with mock.patch( - _provider_class + "._transferwise_provider_retrieve", - return_value=mocked_response, - ): - self.transferwise_provider._update(date, date) - - rates = self.CurrencyRate.search( - [("currency_id", "=", self.eur_currency.id)], limit=1 - ) - self.assertTrue(rates) - - self.CurrencyRate.search([("currency_id", "=", self.eur_currency.id)]).unlink() - - def test_no_credentials(self): - self.env.user.company_id.transferwise_api_key = None - with self.assertRaises(UserError): - self.transferwise_provider._get_supported_currencies() - - def test_bad_credentials(self): - self.env.user.company_id.transferwise_api_key = "bad" - with self.assertRaises(HTTPError): - self.transferwise_provider._obtain_rates( - "USD", - ["EUR"], - self.today, - self.today, - ) diff --git a/currency_rate_update_transferwise/README.rst b/currency_rate_update_wise/README.rst similarity index 58% rename from currency_rate_update_transferwise/README.rst rename to currency_rate_update_wise/README.rst index f9732ca9..07424eab 100644 --- a/currency_rate_update_transferwise/README.rst +++ b/currency_rate_update_wise/README.rst @@ -17,10 +17,10 @@ Currency Rate Update: Wise.com :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html :alt: License: AGPL-3 .. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fcurrency-lightgray.png?logo=github - :target: https://github.com/OCA/currency/tree/14.0/currency_rate_update_transferwise + :target: https://github.com/OCA/currency/tree/14.0/currency_rate_update_wise :alt: OCA/currency .. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png - :target: https://translation.odoo-community.org/projects/currency-14-0/currency-14-0-currency_rate_update_transferwise + :target: https://translation.odoo-community.org/projects/currency-14-0/currency-14-0-currency_rate_update_wise :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/currency&target_branch=14.0 @@ -28,8 +28,28 @@ Currency Rate Update: Wise.com |badge1| |badge2| |badge3| |badge4| |badge5| -This module adds `Wise.com `_ -currency exchange rates provider. +This module adds `Wise.com `_ as a currency exchange rates +provider for ``currency_rate_update``. + +It is meant as a *worldwide* complement to the built-in ECB provider: the ECB +provider gives the official European, EUR-centric daily reference, while Wise +provides a global rate source with the following advantages: + +* **Rate quality**: Wise returns the interbank mid-market rate. On a working day, + aligned with the ECB fixing, it tracks the ECB reference very closely (closer + than xe.com in our measurements) and applies no markup to the published rate. +* **Worldwide coverage**: Wise quotes around 164 currencies, against the 29 of + the ECB reference list, including currencies the ECB does not publish (e.g. + AED, COP). ECB stays the European reference; Wise extends accurate rates to + the rest of the world. +* **Stable API**: rates are fetched through an authenticated JSON API rather than + by scraping a web page, which keeps updates robust over time. +* **Efficient and historical**: only the configured currency pairs are requested + (not a whole table), and past rates can be retrieved by timestamp or date + range (daily/hourly/minute granularity), which is useful for backfill and + corrections. + +A Wise.com API token is required (see Configuration). **Table of contents** @@ -44,13 +64,20 @@ To configure Wise.com currency rates provider credentials: # Go to *Invoicing > Configuration > Settings* # Fill application credentials in *Currencies > Wise.com Provider* section +.. note:: + + The API key is stored and sent as an opaque string of any length, so both the + current UUID-format personal tokens and the JWT format Wise is migrating + towards are supported. If a legacy UUID token is configured, a warning is + logged inviting you to regenerate it as JWT. + 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 to smash it by providing a detailed and welcomed -`feedback `_. +`feedback `_. Do not contact contributors directly about support or help with technical issues. @@ -61,6 +88,7 @@ Authors ~~~~~~~ * CorporateHub +* Altixia Contributors ~~~~~~~~~~~~ @@ -69,6 +97,10 @@ Contributors * Alexey Pelykh +* `Altixia `__ + + * Claude Perrin + Maintainers ~~~~~~~~~~~ @@ -82,6 +114,6 @@ 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/currency `_ project on GitHub. +This module is part of the `OCA/currency `_ project on GitHub. You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/currency_rate_update_transferwise/__init__.py b/currency_rate_update_wise/__init__.py similarity index 100% rename from currency_rate_update_transferwise/__init__.py rename to currency_rate_update_wise/__init__.py diff --git a/currency_rate_update_transferwise/__manifest__.py b/currency_rate_update_wise/__manifest__.py similarity index 71% rename from currency_rate_update_transferwise/__manifest__.py rename to currency_rate_update_wise/__manifest__.py index 90d9896e..a9089376 100644 --- a/currency_rate_update_transferwise/__manifest__.py +++ b/currency_rate_update_wise/__manifest__.py @@ -1,13 +1,14 @@ # Copyright 2019 Brainbean Apps (https://brainbeanapps.com) # Copyright 2020 CorporateHub (https://corporatehub.eu) +# Copyright 2026 Altixia (https://altixia.com) # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). { "name": "Currency Rate Update: Wise.com", - "version": "14.0.1.0.0", + "version": "14.0.2.0.0", "category": "Financial Management/Configuration", - "summary": "Update exchange rates using TransferWise.com", - "author": "CorporateHub, Odoo Community Association (OCA)", + "summary": "Update exchange rates using Wise.com", + "author": "CorporateHub, Altixia, Odoo Community Association (OCA)", "website": "https://github.com/OCA/currency", "license": "AGPL-3", "installable": True, diff --git a/currency_rate_update_wise/i18n/currency_rate_update_wise.pot b/currency_rate_update_wise/i18n/currency_rate_update_wise.pot new file mode 100644 index 00000000..89e31246 --- /dev/null +++ b/currency_rate_update_wise/i18n/currency_rate_update_wise.pot @@ -0,0 +1,89 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * currency_rate_update_wise +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 14.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: currency_rate_update_wise +#: model_terms:ir.ui.view,arch_db:currency_rate_update_wise.res_config_settings_view_form +msgid "" +"Wise.com Provider\n" +" " +msgstr "" + +#. module: currency_rate_update_wise +#: model:ir.model.fields,field_description:currency_rate_update_wise.field_res_config_settings__wise_api_key +msgid "API Key" +msgstr "" + +#. module: currency_rate_update_wise +#: model:ir.model,name:currency_rate_update_wise.model_res_company +msgid "Companies" +msgstr "" + +#. module: currency_rate_update_wise +#: model:ir.model,name:currency_rate_update_wise.model_res_config_settings +msgid "Config Settings" +msgstr "" + +#. module: currency_rate_update_wise +#: model:ir.model,name:currency_rate_update_wise.model_res_currency_rate_provider +msgid "Currency Rates Provider" +msgstr "" + +#. module: currency_rate_update_wise +#: model:ir.model.fields,field_description:currency_rate_update_wise.field_res_company__display_name +#: model:ir.model.fields,field_description:currency_rate_update_wise.field_res_config_settings__display_name +#: model:ir.model.fields,field_description:currency_rate_update_wise.field_res_currency_rate_provider__display_name +msgid "Display Name" +msgstr "" + +#. module: currency_rate_update_wise +#: model:ir.model.fields,field_description:currency_rate_update_wise.field_res_company__id +#: model:ir.model.fields,field_description:currency_rate_update_wise.field_res_config_settings__id +#: model:ir.model.fields,field_description:currency_rate_update_wise.field_res_currency_rate_provider__id +msgid "ID" +msgstr "" + +#. module: currency_rate_update_wise +#: model:ir.model.fields,field_description:currency_rate_update_wise.field_res_company____last_update +#: model:ir.model.fields,field_description:currency_rate_update_wise.field_res_config_settings____last_update +#: model:ir.model.fields,field_description:currency_rate_update_wise.field_res_currency_rate_provider____last_update +msgid "Last Modified on" +msgstr "" + +#. module: currency_rate_update_wise +#: code:addons/currency_rate_update_wise/models/res_currency_rate_provider_Wise.py:0 +#, python-format +msgid "No Wise.com credentials specified!" +msgstr "" + +#. module: currency_rate_update_wise +#: model:ir.model.fields,field_description:currency_rate_update_wise.field_res_currency_rate_provider__service +msgid "Source Service" +msgstr "" + +#. module: currency_rate_update_wise +#: model_terms:ir.ui.view,arch_db:currency_rate_update_wise.res_config_settings_view_form +#: model:ir.model.fields.selection,name:currency_rate_update_wise.selection__res_currency_rate_provider__service__wise +msgid "Wise.com" +msgstr "" + +#. module: currency_rate_update_wise +#: model:ir.model.fields,field_description:currency_rate_update_wise.field_res_company__wise_api_key +msgid "Wise.com API Key" +msgstr "" + +#. module: currency_rate_update_wise +#: model_terms:ir.ui.view,arch_db:currency_rate_update_wise.res_config_settings_view_form +msgid "credentials" +msgstr "" diff --git a/currency_rate_update_wise/i18n/es.po b/currency_rate_update_wise/i18n/es.po new file mode 100644 index 00000000..c9c396db --- /dev/null +++ b/currency_rate_update_wise/i18n/es.po @@ -0,0 +1,94 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * currency_rate_update_wise +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 14.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2023-12-19 14:36+0000\n" +"Last-Translator: Ivorra78 \n" +"Language-Team: none\n" +"Language: es\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 4.17\n" + +#. module: currency_rate_update_wise +#: model_terms:ir.ui.view,arch_db:currency_rate_update_wise.res_config_settings_view_form +msgid "" +"Wise.com Provider\n" +" " +msgstr "" +"Proveedor de Wise.com\n" +" " + +#. module: currency_rate_update_wise +#: model:ir.model.fields,field_description:currency_rate_update_wise.field_res_config_settings__wise_api_key +msgid "API Key" +msgstr "Clave API" + +#. module: currency_rate_update_wise +#: model:ir.model,name:currency_rate_update_wise.model_res_company +msgid "Companies" +msgstr "Compañías" + +#. module: currency_rate_update_wise +#: model:ir.model,name:currency_rate_update_wise.model_res_config_settings +msgid "Config Settings" +msgstr "Ajustes de Config" + +#. module: currency_rate_update_wise +#: model:ir.model,name:currency_rate_update_wise.model_res_currency_rate_provider +msgid "Currency Rates Provider" +msgstr "Proveedor de Tipos de Cambio" + +#. module: currency_rate_update_wise +#: model:ir.model.fields,field_description:currency_rate_update_wise.field_res_company__display_name +#: model:ir.model.fields,field_description:currency_rate_update_wise.field_res_config_settings__display_name +#: model:ir.model.fields,field_description:currency_rate_update_wise.field_res_currency_rate_provider__display_name +msgid "Display Name" +msgstr "Mostrar Nombre" + +#. module: currency_rate_update_wise +#: model:ir.model.fields,field_description:currency_rate_update_wise.field_res_company__id +#: model:ir.model.fields,field_description:currency_rate_update_wise.field_res_config_settings__id +#: model:ir.model.fields,field_description:currency_rate_update_wise.field_res_currency_rate_provider__id +msgid "ID" +msgstr "ID (identificación)" + +#. module: currency_rate_update_wise +#: model:ir.model.fields,field_description:currency_rate_update_wise.field_res_company____last_update +#: model:ir.model.fields,field_description:currency_rate_update_wise.field_res_config_settings____last_update +#: model:ir.model.fields,field_description:currency_rate_update_wise.field_res_currency_rate_provider____last_update +msgid "Last Modified on" +msgstr "Última Modificación el" + +#. module: currency_rate_update_wise +#: code:addons/currency_rate_update_wise/models/res_currency_rate_provider_Wise.py:0 +#, python-format +msgid "No Wise.com credentials specified!" +msgstr "¡No se han especificado credenciales de Wise.com!" + +#. module: currency_rate_update_wise +#: model:ir.model.fields,field_description:currency_rate_update_wise.field_res_currency_rate_provider__service +msgid "Source Service" +msgstr "Servicio Fuente" + +#. module: currency_rate_update_wise +#: model_terms:ir.ui.view,arch_db:currency_rate_update_wise.res_config_settings_view_form +#: model:ir.model.fields.selection,name:currency_rate_update_wise.selection__res_currency_rate_provider__service__wise +msgid "Wise.com" +msgstr "Wise.com" + +#. module: currency_rate_update_wise +#: model:ir.model.fields,field_description:currency_rate_update_wise.field_res_company__wise_api_key +msgid "Wise.com API Key" +msgstr "Wise.com Clave API" + +#. module: currency_rate_update_wise +#: model_terms:ir.ui.view,arch_db:currency_rate_update_wise.res_config_settings_view_form +msgid "credentials" +msgstr "Credenciales" diff --git a/currency_rate_update_wise/i18n/fr.po b/currency_rate_update_wise/i18n/fr.po new file mode 100644 index 00000000..e2003f90 --- /dev/null +++ b/currency_rate_update_wise/i18n/fr.po @@ -0,0 +1,92 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * currency_rate_update_wise +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 13.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: \n" +"PO-Revision-Date: \n" +"Last-Translator: Alpis Traduction et Interprétation \n" +"Language-Team: \n" +"Language: fr\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" +"X-Generator: Poedit 2.4.3\n" + +#. module: currency_rate_update_wise +#: model:ir.model.fields,field_description:currency_rate_update_wise.field_res_config_settings__wise_api_key +msgid "API Key" +msgstr "Clé API" + +#. module: currency_rate_update_wise +#: model:ir.model,name:currency_rate_update_wise.model_res_company +msgid "Companies" +msgstr "Sociétés" + +#. module: currency_rate_update_wise +#: model:ir.model,name:currency_rate_update_wise.model_res_config_settings +msgid "Config Settings" +msgstr "Configuration" + +#. module: currency_rate_update_wise +#: model:ir.model,name:currency_rate_update_wise.model_res_currency_rate_provider +msgid "Currency Rates Provider" +msgstr "Fournisseur de taux de change" + +#. module: currency_rate_update_wise +#: model:ir.model.fields,field_description:currency_rate_update_wise.field_res_company__display_name +#: model:ir.model.fields,field_description:currency_rate_update_wise.field_res_config_settings__display_name +#: model:ir.model.fields,field_description:currency_rate_update_wise.field_res_currency_rate_provider__display_name +msgid "Display Name" +msgstr "" + +#. module: currency_rate_update_wise +#: model:ir.model.fields,field_description:currency_rate_update_wise.field_res_company__id +#: model:ir.model.fields,field_description:currency_rate_update_wise.field_res_config_settings__id +#: model:ir.model.fields,field_description:currency_rate_update_wise.field_res_currency_rate_provider__id +msgid "ID" +msgstr "" + +#. module: currency_rate_update_wise +#: model:ir.model.fields,field_description:currency_rate_update_wise.field_res_company____last_update +#: model:ir.model.fields,field_description:currency_rate_update_wise.field_res_config_settings____last_update +#: model:ir.model.fields,field_description:currency_rate_update_wise.field_res_currency_rate_provider____last_update +msgid "Last Modified on" +msgstr "" + +#. module: currency_rate_update_wise +#: code:addons/currency_rate_update_wise/models/res_currency_rate_provider_Wise.py:0 +#, python-format +msgid "No Wise.com credentials specified!" +msgstr "Aucun identifiant Wise.com fourni !" + +#. module: currency_rate_update_wise +#: model:ir.model.fields,field_description:currency_rate_update_wise.field_res_currency_rate_provider__service +msgid "Source Service" +msgstr "Fournisseur" + +#. module: currency_rate_update_wise +#: model:ir.model.fields.selection,name:currency_rate_update_wise.selection__res_currency_rate_provider__service__wise +msgid "Wise.com" +msgstr "Wise.com" + +#. module: currency_rate_update_wise +#: model:ir.model.fields,field_description:currency_rate_update_wise.field_res_company__wise_api_key +msgid "Wise.com API Key" +msgstr "Clé API Wise.com" + +#. module: currency_rate_update_wise +#: model_terms:ir.ui.view,arch_db:currency_rate_update_wise.res_config_settings_view_form +msgid "credentials" +msgstr "identifiants" + +#~ msgid "" +#~ "Wise.com Provider\n" +#~ " " +#~ msgstr "" +#~ "Fournisseur Wise.com \n" +#~ " " diff --git a/currency_rate_update_wise/i18n/it.po b/currency_rate_update_wise/i18n/it.po new file mode 100644 index 00000000..b8b5ee19 --- /dev/null +++ b/currency_rate_update_wise/i18n/it.po @@ -0,0 +1,90 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * currency_rate_update_wise +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 14.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: it\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" + +#. module: currency_rate_update_wise +#: model_terms:ir.ui.view,arch_db:currency_rate_update_wise.res_config_settings_view_form +msgid "" +"Wise.com Provider\n" +" " +msgstr "" + +#. module: currency_rate_update_wise +#: model:ir.model.fields,field_description:currency_rate_update_wise.field_res_config_settings__wise_api_key +msgid "API Key" +msgstr "" + +#. module: currency_rate_update_wise +#: model:ir.model,name:currency_rate_update_wise.model_res_company +msgid "Companies" +msgstr "" + +#. module: currency_rate_update_wise +#: model:ir.model,name:currency_rate_update_wise.model_res_config_settings +msgid "Config Settings" +msgstr "" + +#. module: currency_rate_update_wise +#: model:ir.model,name:currency_rate_update_wise.model_res_currency_rate_provider +msgid "Currency Rates Provider" +msgstr "" + +#. module: currency_rate_update_wise +#: model:ir.model.fields,field_description:currency_rate_update_wise.field_res_company__display_name +#: model:ir.model.fields,field_description:currency_rate_update_wise.field_res_config_settings__display_name +#: model:ir.model.fields,field_description:currency_rate_update_wise.field_res_currency_rate_provider__display_name +msgid "Display Name" +msgstr "" + +#. module: currency_rate_update_wise +#: model:ir.model.fields,field_description:currency_rate_update_wise.field_res_company__id +#: model:ir.model.fields,field_description:currency_rate_update_wise.field_res_config_settings__id +#: model:ir.model.fields,field_description:currency_rate_update_wise.field_res_currency_rate_provider__id +msgid "ID" +msgstr "" + +#. module: currency_rate_update_wise +#: model:ir.model.fields,field_description:currency_rate_update_wise.field_res_company____last_update +#: model:ir.model.fields,field_description:currency_rate_update_wise.field_res_config_settings____last_update +#: model:ir.model.fields,field_description:currency_rate_update_wise.field_res_currency_rate_provider____last_update +msgid "Last Modified on" +msgstr "" + +#. module: currency_rate_update_wise +#: code:addons/currency_rate_update_wise/models/res_currency_rate_provider_Wise.py:0 +#, python-format +msgid "No Wise.com credentials specified!" +msgstr "" + +#. module: currency_rate_update_wise +#: model:ir.model.fields,field_description:currency_rate_update_wise.field_res_currency_rate_provider__service +msgid "Source Service" +msgstr "" + +#. module: currency_rate_update_wise +#: model_terms:ir.ui.view,arch_db:currency_rate_update_wise.res_config_settings_view_form +#: model:ir.model.fields.selection,name:currency_rate_update_wise.selection__res_currency_rate_provider__service__wise +msgid "Wise.com" +msgstr "" + +#. module: currency_rate_update_wise +#: model:ir.model.fields,field_description:currency_rate_update_wise.field_res_company__wise_api_key +msgid "Wise.com API Key" +msgstr "" + +#. module: currency_rate_update_wise +#: model_terms:ir.ui.view,arch_db:currency_rate_update_wise.res_config_settings_view_form +msgid "credentials" +msgstr "" diff --git a/currency_rate_update_wise/migrations/14.0.2.0.0/pre-migration.py b/currency_rate_update_wise/migrations/14.0.2.0.0/pre-migration.py new file mode 100644 index 00000000..3ee1ac3e --- /dev/null +++ b/currency_rate_update_wise/migrations/14.0.2.0.0/pre-migration.py @@ -0,0 +1,28 @@ +# Copyright 2026 Altixia (https://altixia.com) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +from openupgradelib import openupgrade + + +@openupgrade.migrate() +def migrate(env, version): + """Rename currency_rate_update_transferwise -> _wise (Wise rebrand, 2021). + + Preserves provider records, the API key and the rate history + (kept in core res_currency_rate). No data loss. + """ + if openupgrade.is_module_installed(env.cr, "currency_rate_update_transferwise"): + openupgrade.update_module_names( + env.cr, + [("currency_rate_update_transferwise", "currency_rate_update_wise")], + merge_modules=True, + ) + if openupgrade.column_exists(env.cr, "res_company", "transferwise_api_key"): + openupgrade.rename_columns( + env.cr, {"res_company": [("transferwise_api_key", "wise_api_key")]} + ) + openupgrade.logged_query( + env.cr, + "UPDATE res_currency_rate_provider SET service = 'Wise' " + "WHERE service = 'TransferWise'", + ) diff --git a/currency_rate_update_transferwise/models/__init__.py b/currency_rate_update_wise/models/__init__.py similarity index 70% rename from currency_rate_update_transferwise/models/__init__.py rename to currency_rate_update_wise/models/__init__.py index 777d6c8e..0c9baa21 100644 --- a/currency_rate_update_transferwise/models/__init__.py +++ b/currency_rate_update_wise/models/__init__.py @@ -2,4 +2,4 @@ from . import res_company from . import res_config_settings -from . import res_currency_rate_provider_TransferWise +from . import res_currency_rate_provider_Wise diff --git a/currency_rate_update_transferwise/models/res_company.py b/currency_rate_update_wise/models/res_company.py similarity index 69% rename from currency_rate_update_transferwise/models/res_company.py rename to currency_rate_update_wise/models/res_company.py index 478fe335..68c03bdb 100644 --- a/currency_rate_update_transferwise/models/res_company.py +++ b/currency_rate_update_wise/models/res_company.py @@ -1,4 +1,5 @@ # Copyright 2019 Brainbean Apps (https://brainbeanapps.com) +# Copyright 2026 Altixia (https://altixia.com) # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). from odoo import fields, models @@ -7,6 +8,7 @@ class ResCompany(models.Model): _inherit = "res.company" - transferwise_api_key = fields.Char( + wise_api_key = fields.Char( string="Wise.com API Key", + groups="base.group_system", ) diff --git a/currency_rate_update_transferwise/models/res_config_settings.py b/currency_rate_update_wise/models/res_config_settings.py similarity index 66% rename from currency_rate_update_transferwise/models/res_config_settings.py rename to currency_rate_update_wise/models/res_config_settings.py index 174bcab6..3679924e 100644 --- a/currency_rate_update_transferwise/models/res_config_settings.py +++ b/currency_rate_update_wise/models/res_config_settings.py @@ -1,4 +1,5 @@ # Copyright 2019 Brainbean Apps (https://brainbeanapps.com) +# Copyright 2026 Altixia (https://altixia.com) # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). from odoo import fields, models @@ -7,8 +8,9 @@ class ResConfigSettings(models.TransientModel): _inherit = "res.config.settings" - transferwise_api_key = fields.Char( + wise_api_key = fields.Char( string="API Key", - related="company_id.transferwise_api_key", + related="company_id.wise_api_key", readonly=False, + groups="base.group_system", ) diff --git a/currency_rate_update_wise/models/res_currency_rate_provider_Wise.py b/currency_rate_update_wise/models/res_currency_rate_provider_Wise.py new file mode 100644 index 00000000..dab84ab3 --- /dev/null +++ b/currency_rate_update_wise/models/res_currency_rate_provider_Wise.py @@ -0,0 +1,156 @@ +# Copyright 2019 Brainbean Apps (https://brainbeanapps.com) +# Copyright 2020 CorporateHub (https://corporatehub.eu) +# Copyright 2026 Altixia (https://altixia.com) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +import itertools +import logging +import re +from collections import defaultdict +from datetime import date, timedelta + +import dateutil.parser +import requests + +from odoo import _, fields, models +from odoo.exceptions import UserError + +_logger = logging.getLogger(__name__) + +WISE_API_URL = "https://api.wise.com/v1/rates" +# Network timeout (connect, read) in seconds: a scheduled cron must never hang +# indefinitely on an unresponsive endpoint. +TIMEOUT = (10, 30) +# Legacy UUID token format. Wise is migrating API tokens to JWT; a UUID token +# is still valid today but should eventually be regenerated as JWT. +_UUID_RE = re.compile( + r"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", + re.IGNORECASE, +) +# Warn only during the migration window. Wise starts issuing JWT tokens in +# production with the transition period beginning end of July 2026, so warning +# before then is not actionable (UUID is the only available format). A year +# later the migration is over, so we stop the check to avoid nagging for years. +_LEGACY_WARN_FROM = date(2026, 8, 1) +_LEGACY_WARN_UNTIL = date(2027, 8, 1) + + +class ResCurrencyRateProviderWise(models.Model): + _inherit = "res.currency.rate.provider" + + service = fields.Selection( + selection_add=[("Wise", "Wise.com")], + ondelete={"Wise": "set default"}, + ) + + def _register_hook(self): + # Emit the legacy-token warning at Odoo load time. + res = super()._register_hook() + self.search([("service", "=", "Wise")])._wise_warn_legacy_tokens() + return res + + def _get_supported_currencies(self): + self.ensure_one() + if self.service != "Wise": + return super()._get_supported_currencies() # pragma: no cover + + data = self._wise_provider_retrieve(WISE_API_URL) + self._wise_check_error(data) + + return list( + set( + itertools.chain.from_iterable( + [entry["source"], entry["target"]] for entry in data + ) + ) + ) + + def _obtain_rates(self, base_currency, currencies, date_from, date_to): + self.ensure_one() + if self.service != "Wise": + return super()._obtain_rates( + base_currency, currencies, date_from, date_to + ) # pragma: no cover + + # Also warn on each scheduled update run (once per run). + self._wise_warn_legacy_tokens() + content = defaultdict(dict) + + # NOTE: Step in 10 days is selected to reduce memory impact + step = timedelta(days=10) + for currency in currencies: + since = date_from + until = since + step + while since <= date_to: + # Wise answers HTTP 400 when "from" equals "to". That is + # exactly the shape of a scheduled run once the rates are up + # to date: the provider then asks for the single missing day. + # Widening the window by one day is enough — the endpoint only + # ever returns the days it actually has, so no spurious rate + # is created. + window_end = max(min(until, date_to), since + timedelta(days=1)) + params = { + "source": base_currency, + "target": currency, + "from": str(since), + "to": str(window_end), + "group": "day", + } + data = self._wise_provider_retrieve(WISE_API_URL, params) + self._wise_check_error(data) + for entry in data: + date = dateutil.parser.parse(entry["time"]).date() + date_content = content[date.isoformat()] + date_content[currency] = entry["rate"] + + since += step + until += step + + return content + + def _wise_warn_legacy_tokens(self): + """Warn for each Wise provider whose token still uses the UUID format. + + Wise is migrating API tokens from the legacy UUID format to JWT. A UUID + token is still valid today but should eventually be regenerated as JWT + in the Wise Developer Hub. Nothing is logged when no key is set, and the + token storage already accepts both formats. + """ + today = fields.Date.today() + if not (_LEGACY_WARN_FROM <= today < _LEGACY_WARN_UNTIL): + return + for provider in self: + key = provider.company_id.wise_api_key + if key and _UUID_RE.match(key): + _logger.warning( + "Wise.com API key for company %s is a legacy UUID-format " + "token. Wise is migrating API tokens to JWT; consider " + "regenerating the token in the Wise Developer Hub.", + provider.company_id.name, + ) + + def _wise_check_error(self, data): + """Raise a clear error if the API returned an error payload.""" + if isinstance(data, dict) and data.get("error"): + raise UserError(data.get("error_description") or _("Unknown error")) + + def _wise_provider_retrieve(self, url, params=None): + self.ensure_one() + if not self.company_id.wise_api_key: + raise UserError(_("No Wise.com credentials specified!")) + + try: + response = requests.get( + url, + params=params, + headers={"Authorization": f"Bearer {self.company_id.wise_api_key}"}, + timeout=TIMEOUT, + ) + response.raise_for_status() + return response.json() + except requests.exceptions.HTTPError: + raise + except requests.exceptions.RequestException as e: + raise UserError(_("Could not reach Wise.com: %s") % e) from e + except ValueError as e: # invalid JSON + raise UserError(_("Unexpected (non-JSON) response from Wise.com")) from e diff --git a/currency_rate_update_wise/readme/CONFIGURE.rst b/currency_rate_update_wise/readme/CONFIGURE.rst new file mode 100644 index 00000000..89493bcb --- /dev/null +++ b/currency_rate_update_wise/readme/CONFIGURE.rst @@ -0,0 +1,11 @@ +To configure Wise.com currency rates provider credentials: + +# Go to *Invoicing > Configuration > Settings* +# Fill application credentials in *Currencies > Wise.com Provider* section + +.. note:: + + The API key is stored and sent as an opaque string of any length, so both the + current UUID-format personal tokens and the JWT format Wise is migrating + towards are supported. If a legacy UUID token is configured, a warning is + logged inviting you to regenerate it as JWT. diff --git a/currency_rate_update_transferwise/readme/CONTRIBUTORS.rst b/currency_rate_update_wise/readme/CONTRIBUTORS.rst similarity index 50% rename from currency_rate_update_transferwise/readme/CONTRIBUTORS.rst rename to currency_rate_update_wise/readme/CONTRIBUTORS.rst index 724bc1d0..61cda138 100644 --- a/currency_rate_update_transferwise/readme/CONTRIBUTORS.rst +++ b/currency_rate_update_wise/readme/CONTRIBUTORS.rst @@ -1,3 +1,7 @@ * `CorporateHub `__ * Alexey Pelykh + +* `Altixia `__ + + * Claude Perrin diff --git a/currency_rate_update_wise/readme/DESCRIPTION.rst b/currency_rate_update_wise/readme/DESCRIPTION.rst new file mode 100644 index 00000000..f191c98f --- /dev/null +++ b/currency_rate_update_wise/readme/DESCRIPTION.rst @@ -0,0 +1,22 @@ +This module adds `Wise.com `_ as a currency exchange rates +provider for ``currency_rate_update``. + +It is meant as a *worldwide* complement to the built-in ECB provider: the ECB +provider gives the official European, EUR-centric daily reference, while Wise +provides a global rate source with the following advantages: + +* **Rate quality**: Wise returns the interbank mid-market rate. On a working day, + aligned with the ECB fixing, it tracks the ECB reference very closely (closer + than xe.com in our measurements) and applies no markup to the published rate. +* **Worldwide coverage**: Wise quotes around 164 currencies, against the 29 of + the ECB reference list, including currencies the ECB does not publish (e.g. + AED, COP). ECB stays the European reference; Wise extends accurate rates to + the rest of the world. +* **Stable API**: rates are fetched through an authenticated JSON API rather than + by scraping a web page, which keeps updates robust over time. +* **Efficient and historical**: only the configured currency pairs are requested + (not a whole table), and past rates can be retrieved by timestamp or date + range (daily/hourly/minute granularity), which is useful for backfill and + corrections. + +A Wise.com API token is required (see Configuration). diff --git a/currency_rate_update_transferwise/static/description/icon.png b/currency_rate_update_wise/static/description/icon.png similarity index 100% rename from currency_rate_update_transferwise/static/description/icon.png rename to currency_rate_update_wise/static/description/icon.png diff --git a/currency_rate_update_transferwise/static/description/index.html b/currency_rate_update_wise/static/description/index.html similarity index 77% rename from currency_rate_update_transferwise/static/description/index.html rename to currency_rate_update_wise/static/description/index.html index 896f5ac8..8a27eaad 100644 --- a/currency_rate_update_transferwise/static/description/index.html +++ b/currency_rate_update_wise/static/description/index.html @@ -1,4 +1,3 @@ - @@ -9,10 +8,11 @@ /* :Author: David Goodger (goodger@python.org) -: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. @@ -275,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 } @@ -301,7 +301,7 @@ span.pre { white-space: pre } -span.problematic { +span.problematic, pre.problematic { color: red } span.section-subtitle { @@ -369,9 +369,28 @@

Currency Rate Update: Wise.com

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !! source digest: sha256:2d06b7285657b548c0823662525d030d0925d9745747fc4acd748f575ad4d544 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! --> -

Beta License: AGPL-3 OCA/currency Translate me on Weblate Try me on Runboat

-

This module adds Wise.com -currency exchange rates provider.

+

Beta License: AGPL-3 OCA/currency Translate me on Weblate Try me on Runboat

+

This module adds Wise.com as a currency exchange rates +provider for currency_rate_update.

+

It is meant as a worldwide complement to the built-in ECB provider: the ECB +provider gives the official European, EUR-centric daily reference, while Wise +provides a global rate source with the following advantages:

+
    +
  • Rate quality: Wise returns the interbank mid-market rate. On a working day, +aligned with the ECB fixing, it tracks the ECB reference very closely (closer +than xe.com in our measurements) and applies no markup to the published rate.
  • +
  • Worldwide coverage: Wise quotes around 164 currencies, against the 29 of +the ECB reference list, including currencies the ECB does not publish (e.g. +AED, COP). ECB stays the European reference; Wise extends accurate rates to +the rest of the world.
  • +
  • Stable API: rates are fetched through an authenticated JSON API rather than +by scraping a web page, which keeps updates robust over time.
  • +
  • Efficient and historical: only the configured currency pairs are requested +(not a whole table), and past rates can be retrieved by timestamp or date +range (daily/hourly/minute granularity), which is useful for backfill and +corrections.
  • +
+

A Wise.com API token is required (see Configuration).

Table of contents

    @@ -390,13 +409,20 @@

    Configuration

    To configure Wise.com currency rates provider credentials:

    # Go to Invoicing > Configuration > Settings # Fill application credentials in Currencies > Wise.com Provider section

    +
    +

    Note

    +

    The API key is stored and sent as an opaque string of any length, so both the +current UUID-format personal tokens and the JWT format Wise is migrating +towards are supported. If a legacy UUID token is configured, a warning is +logged inviting you to regenerate it as JWT.

    +

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 to smash it by providing a detailed and welcomed -feedback.

+feedback.

Do not contact contributors directly about support or help with technical issues.

@@ -405,6 +431,7 @@

Credits

Authors

  • CorporateHub
  • +
  • Altixia
@@ -414,16 +441,22 @@

Contributors

  • Alexey Pelykh <alexey.pelykh@corphub.eu>
  • +
  • Altixia +
  • Maintainers

    This module is maintained by the OCA.

    -Odoo Community Association + +Odoo Community Association +

    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/currency project on GitHub.

    +

    This module is part of the OCA/currency project on GitHub.

    You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

    diff --git a/currency_rate_update_transferwise/tests/__init__.py b/currency_rate_update_wise/tests/__init__.py similarity index 57% rename from currency_rate_update_transferwise/tests/__init__.py rename to currency_rate_update_wise/tests/__init__.py index ca97f3e1..91f11f9e 100644 --- a/currency_rate_update_transferwise/tests/__init__.py +++ b/currency_rate_update_wise/tests/__init__.py @@ -1,3 +1,3 @@ # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). -from . import test_currency_rate_update_transferwise +from . import test_currency_rate_update_wise diff --git a/currency_rate_update_wise/tests/test_currency_rate_update_wise.py b/currency_rate_update_wise/tests/test_currency_rate_update_wise.py new file mode 100644 index 00000000..6fc944da --- /dev/null +++ b/currency_rate_update_wise/tests/test_currency_rate_update_wise.py @@ -0,0 +1,147 @@ +# Copyright 2019 Brainbean Apps (https://brainbeanapps.com) +# Copyright 2026 Altixia (https://altixia.com) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +from datetime import date +from unittest import mock + +import requests +from dateutil.relativedelta import relativedelta + +from odoo import fields +from odoo.exceptions import UserError +from odoo.tests import common + +from ..models import res_currency_rate_provider_Wise as wise_mod + +_module_ns = "odoo.addons.currency_rate_update_wise" +_provider_class = ( + _module_ns + + ".models.res_currency_rate_provider_Wise" + + ".ResCurrencyRateProviderWise" +) +_retrieve = _provider_class + "._wise_provider_retrieve" + + +class TestResCurrencyRateProviderWise(common.TransactionCase): + def setUp(self): + super().setUp() + + self.Company = self.env["res.company"] + self.CurrencyRate = self.env["res.currency.rate"] + self.CurrencyRateProvider = self.env["res.currency.rate.provider"] + + self.today = fields.Date.today() + self.eur_currency = self.env.ref("base.EUR") + self.wise_provider = self.CurrencyRateProvider.create( + {"service": "Wise", "currency_ids": [(4, self.eur_currency.id)]} + ) + self.env.user.company_id.wise_api_key = "test-token" + self.CurrencyRate.search([]).unlink() + + def test_supported_currencies(self): + mocked_response = [ + { + "rate": 1.0, + "source": "EUR", + "target": "EUR", + "time": "2019-01-01T00:00:00+0000", + } + ] + with mock.patch(_retrieve, return_value=mocked_response): + supported_currencies = self.wise_provider._get_supported_currencies() + self.assertEqual(len(supported_currencies), 1) + + def test_update(self): + date = self.today - relativedelta(days=1) + mocked_response = [ + { + "rate": 0.86995, + "source": "USD", + "target": "EUR", + "time": str(date) + "T00:00:00+0000", + } + ] + with mock.patch(_retrieve, return_value=mocked_response): + self.wise_provider._update(date, date) + + rates = self.CurrencyRate.search( + [("currency_id", "=", self.eur_currency.id)], limit=1 + ) + self.assertTrue(rates) + self.CurrencyRate.search([("currency_id", "=", self.eur_currency.id)]).unlink() + + def test_single_day_window_is_widened(self): + # Wise answers HTTP 400 when "from" equals "to", which is exactly what + # a scheduled run asks for once the rates are up to date. The provider + # must widen the window instead of sending a zero-length range. + captured = [] + + def _capture(_self, _url, params=None): + captured.append(params) + return [] + + with mock.patch(_retrieve, _capture): + self.wise_provider._obtain_rates("USD", ["EUR"], self.today, self.today) + + self.assertTrue(captured, "no request was issued") + for params in captured: + self.assertNotEqual( + params["from"], + params["to"], + "Wise rejects a zero-length window with HTTP 400", + ) + self.assertEqual(params["from"], str(self.today)) + + def test_no_credentials(self): + self.env.user.company_id.wise_api_key = None + with self.assertRaises(UserError): + self.wise_provider._get_supported_currencies() + + def test_bad_credentials(self): + # Hermetic: simulate Wise returning an HTTP error, no real network call. + with mock.patch( + _retrieve, side_effect=requests.exceptions.HTTPError("401") + ), self.assertRaises(requests.exceptions.HTTPError): + self.wise_provider._obtain_rates("USD", ["EUR"], self.today, self.today) + + def test_error_response(self): + # An API error payload must raise a clean UserError. + with mock.patch( + _retrieve, + return_value={"error": True, "error_description": "boom"}, + ), self.assertRaises(UserError): + self.wise_provider._get_supported_currencies() + + def _warn_calls(self, today): + # Run the legacy-token check at a given date, return the logger mock. + with mock.patch.object( + wise_mod.fields.Date, "today", return_value=today + ), mock.patch.object(wise_mod._logger, "warning") as warn: + self.wise_provider._wise_warn_legacy_tokens() + return warn + + def test_legacy_uuid_token_warning(self): + # UUID token, on/after JWT availability -> warning. + self.env.user.company_id.wise_api_key = "ca44a17e-9b28-4fb7-bcf7-9ba09340c26f" + self.assertTrue(self._warn_calls(date(2026, 8, 1)).called) + + def test_no_warning_before_jwt_availability(self): + # UUID token, before JWT is available -> no warning (not actionable). + self.env.user.company_id.wise_api_key = "ca44a17e-9b28-4fb7-bcf7-9ba09340c26f" + self.assertFalse(self._warn_calls(date(2026, 7, 15)).called) + + def test_no_warning_after_window(self): + # UUID token, after the migration window closed -> no warning. + self.env.user.company_id.wise_api_key = "ca44a17e-9b28-4fb7-bcf7-9ba09340c26f" + self.assertFalse(self._warn_calls(date(2027, 8, 1)).called) + + def test_no_warning_without_token(self): + # No key configured -> no warning. + self.env.user.company_id.wise_api_key = False + self.assertFalse(self._warn_calls(date(2026, 8, 1)).called) + + def test_no_warning_for_jwt_token(self): + # A JWT-format token is the target format -> no warning. + self.env.user.company_id.wise_api_key = "eyJhbGci.eyJzdWIi.sig" + self.assertFalse(self._warn_calls(date(2026, 8, 1)).called) diff --git a/currency_rate_update_transferwise/views/res_config_settings.xml b/currency_rate_update_wise/views/res_config_settings.xml similarity index 79% rename from currency_rate_update_transferwise/views/res_config_settings.xml rename to currency_rate_update_wise/views/res_config_settings.xml index 2e605a4d..894efdc5 100644 --- a/currency_rate_update_transferwise/views/res_config_settings.xml +++ b/currency_rate_update_wise/views/res_config_settings.xml @@ -1,6 +1,7 @@ @@ -13,10 +14,10 @@ expr="//div[@data-key='account']//field[@name='currency_rates_autoupdate']/../.." position="after" > -
    +
    - TransferWise.com Provider + Wise.com Provider
    - TransferWise.com credentials + Wise.com credentials
    diff --git a/setup/currency_rate_update_transferwise/odoo/addons/currency_rate_update_transferwise b/setup/currency_rate_update_transferwise/odoo/addons/currency_rate_update_transferwise deleted file mode 120000 index e8db3aa0..00000000 --- a/setup/currency_rate_update_transferwise/odoo/addons/currency_rate_update_transferwise +++ /dev/null @@ -1 +0,0 @@ -../../../../currency_rate_update_transferwise \ No newline at end of file diff --git a/setup/currency_rate_update_wise/odoo/addons/currency_rate_update_wise b/setup/currency_rate_update_wise/odoo/addons/currency_rate_update_wise new file mode 120000 index 00000000..bc363964 --- /dev/null +++ b/setup/currency_rate_update_wise/odoo/addons/currency_rate_update_wise @@ -0,0 +1 @@ +../../../../currency_rate_update_wise \ No newline at end of file diff --git a/setup/currency_rate_update_transferwise/setup.py b/setup/currency_rate_update_wise/setup.py similarity index 100% rename from setup/currency_rate_update_transferwise/setup.py rename to setup/currency_rate_update_wise/setup.py