diff --git a/currency_rate_update_banxico/README.rst b/currency_rate_update_banxico/README.rst new file mode 100644 index 0000000..421bce8 --- /dev/null +++ b/currency_rate_update_banxico/README.rst @@ -0,0 +1,97 @@ +============================= +Currency Rate Update: Banxico +============================= + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:5fa37c60474118032bc96bb1d59b8ed8be27936c799a0609f9106817d89f64be + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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%2Fl10n--mexico-lightgray.png?logo=github + :target: https://github.com/OCA/l10n-mexico/tree/17.0/currency_rate_update_banxico + :alt: OCA/l10n-mexico +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/l10n-mexico-17-0/l10n-mexico-17-0-currency_rate_update_banxico + :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/l10n-mexico&target_branch=17.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module adds `Banxico `__ currency +exchange rate web services. + +**Table of contents** + +.. contents:: + :local: + +Configuration +============= + +Now you can choose the 'Bank of Mexico' service when configuring a +currency rates providers. + +1. Go to Banxico to `generate a + token `__. +2. Go to *Invoicing > Configuration > Currency Rates Providers*. +3. Create a new 'Currency Rates Providers' or edit an existing one and + you will see 'Bank of Mexico' among the available 'Source Services' + to choose and set the token generated in step 1. +4. If you choose 'Bank of Mexico' as a 'Source Service', the exchange + rates will be updated from that provider. + +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 `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +------- + +* Jarsa +* AMOdoo + +Contributors +------------ + +- `Jarsa `__: + + - Alan Ramos + +- `AMOdoo `__ + +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/l10n-mexico `_ 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_banxico/__init__.py b/currency_rate_update_banxico/__init__.py new file mode 100644 index 0000000..4b76c7b --- /dev/null +++ b/currency_rate_update_banxico/__init__.py @@ -0,0 +1,3 @@ +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +from . import models diff --git a/currency_rate_update_banxico/__manifest__.py b/currency_rate_update_banxico/__manifest__.py new file mode 100644 index 0000000..10f80f7 --- /dev/null +++ b/currency_rate_update_banxico/__manifest__.py @@ -0,0 +1,21 @@ +# Copyright 2025 Jarsa +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +{ + "name": "Currency Rate Update: Banxico", + "version": "17.0.1.0.0", + "category": "Financial Management/Configuration", + "summary": "Update exchange rates using Banxico", + "author": "Jarsa, AMOdoo, Odoo Community Association (OCA)", + "website": "https://github.com/OCA/l10n-mexico", + "license": "AGPL-3", + "installable": True, + "application": False, + "depends": [ + "currency_rate_update", + ], + "external_dependencies": {"python": ["requests-mock"]}, + "data": [ + "views/res_currency_rate_provider_views.xml", + ], +} diff --git a/currency_rate_update_banxico/models/__init__.py b/currency_rate_update_banxico/models/__init__.py new file mode 100644 index 0000000..ff17c6c --- /dev/null +++ b/currency_rate_update_banxico/models/__init__.py @@ -0,0 +1,4 @@ +# Copyright 2025 Jarsa +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +from . import res_currency_rate_provider_banxico diff --git a/currency_rate_update_banxico/models/res_currency_rate_provider_banxico.py b/currency_rate_update_banxico/models/res_currency_rate_provider_banxico.py new file mode 100644 index 0000000..df037b3 --- /dev/null +++ b/currency_rate_update_banxico/models/res_currency_rate_provider_banxico.py @@ -0,0 +1,85 @@ +# Copyright 2025 Jarsa +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +from datetime import datetime + +import requests + +from odoo import _, fields, models +from odoo.exceptions import UserError + + +class ResCurrencyRateProvider(models.Model): + _inherit = "res.currency.rate.provider" + + service = fields.Selection( + selection_add=[("banxico", "Bank of Mexico")], + ondelete={"banxico": "set default"}, + ) + banxico_token = fields.Char() + + def _get_supported_currencies(self): + self.ensure_one() + if self.service != "banxico": + return super()._get_supported_currencies() + return ["EUR", "CAD", "JPY", "GBP", "USD"] + + def _obtain_rates(self, base_currency, currencies, date_from, date_to): + """Get the exchange rates from the provider.""" + self.ensure_one() + if self.service != "banxico": + return super()._obtain_rates(base_currency, currencies, date_from, date_to) + result = {} + for currency in currencies: + url = self._get_banxico_webservice_url(currency, date_from, date_to) + data = self._request_data(url) + result.update(self._process_data(data, currency)) + return result + + def _get_banxico_webservice_url(self, currency, date_from, date_to): + """Get the URL to request the exchange rates""" + base_url = "https://www.banxico.org.mx/SieAPIRest/service/v1/series/" + currencies_map = { + "EUR": "SF46410", + "CAD": "SF60632", + "GBP": "SF46407", + "JPY": "SF46406", + "USD": "SF60653", + } + if date_from < fields.Date.context_today(self): + return ( + f"{base_url}{currencies_map[currency]}/datos/" + f"{date_from.strftime('%Y-%m-%d')}/{date_to.strftime('%Y-%m-%d')}" + ) + return f"{base_url}{currencies_map[currency]}/datos/oportuno" + + def _request_data(self, url): + try: + headers = { + "Bmx-Token": self.banxico_token, + } + response = requests.request("GET", url, headers=headers, timeout=10) + if response.status_code != 200: + raise UserError( + response.json() + .get("error", {}) + .get( + "mensaje", + _("Couldn't fetch data. Please contact your administrator."), + ) + ) + return response.json() + except Exception as e: + raise UserError( + _("Couldn't fetch data. Please contact your administrator.") + ) from e + + def _process_data(self, data, currency): + result = {} + records = data.get("bmx", {}).get("series", [{}])[0].get("datos", []) + for rec in records: + date = datetime.strptime(rec.get("fecha"), "%d/%m/%Y") + if rec.get("dato") == "N/E": + continue + result[date] = {currency: 1 / float(rec["dato"])} + return result diff --git a/currency_rate_update_banxico/pyproject.toml b/currency_rate_update_banxico/pyproject.toml new file mode 100644 index 0000000..4231d0c --- /dev/null +++ b/currency_rate_update_banxico/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/currency_rate_update_banxico/readme/CONFIGURE.md b/currency_rate_update_banxico/readme/CONFIGURE.md new file mode 100644 index 0000000..2bab01a --- /dev/null +++ b/currency_rate_update_banxico/readme/CONFIGURE.md @@ -0,0 +1,10 @@ +Now you can choose the 'Bank of Mexico' service when configuring a currency +rates providers. + +1. Go to Banxico to [generate a token](https://www.banxico.org.mx/SieAPIRest/service/v1/token). +2. Go to *Invoicing \> Configuration \> Currency Rates Providers*. +3. Create a new 'Currency Rates Providers' or edit an existing one and + you will see 'Bank of Mexico' among the available 'Source Services' to + choose and set the token generated in step 1. +4. If you choose 'Bank of Mexico' as a 'Source Service', the exchange rates + will be updated from that provider. diff --git a/currency_rate_update_banxico/readme/CONTRIBUTORS.md b/currency_rate_update_banxico/readme/CONTRIBUTORS.md new file mode 100644 index 0000000..097c90c --- /dev/null +++ b/currency_rate_update_banxico/readme/CONTRIBUTORS.md @@ -0,0 +1,3 @@ +- [Jarsa](https://www.jarsa.com): + - Alan Ramos +- [AMOdoo](https://amodoo.org/) diff --git a/currency_rate_update_banxico/readme/DESCRIPTION.md b/currency_rate_update_banxico/readme/DESCRIPTION.md new file mode 100644 index 0000000..7909b1c --- /dev/null +++ b/currency_rate_update_banxico/readme/DESCRIPTION.md @@ -0,0 +1,2 @@ +This module adds [Banxico](https://www.banxico.org.mx/) currency exchange rate +web services. diff --git a/currency_rate_update_banxico/static/description/index.html b/currency_rate_update_banxico/static/description/index.html new file mode 100644 index 0000000..630b60f --- /dev/null +++ b/currency_rate_update_banxico/static/description/index.html @@ -0,0 +1,445 @@ + + + + + +Currency Rate Update: Banxico + + + +
+

Currency Rate Update: Banxico

+ + +

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

+

This module adds Banxico currency +exchange rate web services.

+

Table of contents

+ +
+

Configuration

+

Now you can choose the ‘Bank of Mexico’ service when configuring a +currency rates providers.

+
    +
  1. Go to Banxico to generate a +token.
  2. +
  3. Go to Invoicing > Configuration > Currency Rates Providers.
  4. +
  5. Create a new ‘Currency Rates Providers’ or edit an existing one and +you will see ‘Bank of Mexico’ among the available ‘Source Services’ +to choose and set the token generated in step 1.
  6. +
  7. If you choose ‘Bank of Mexico’ as a ‘Source Service’, the exchange +rates will be updated from that provider.
  8. +
+
+
+

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.

+

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

+
+
+

Credits

+
+

Authors

+
    +
  • Jarsa
  • +
  • AMOdoo
  • +
+
+
+

Contributors

+ +
+
+

Maintainers

+

This module is maintained by the OCA.

+ +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/l10n-mexico 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_banxico/tests/__init__.py b/currency_rate_update_banxico/tests/__init__.py new file mode 100644 index 0000000..7f469ed --- /dev/null +++ b/currency_rate_update_banxico/tests/__init__.py @@ -0,0 +1,4 @@ +# Copyright 2025 Jarsa +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +from . import test_currency_rate_update_banxico diff --git a/currency_rate_update_banxico/tests/test_currency_rate_update_banxico.py b/currency_rate_update_banxico/tests/test_currency_rate_update_banxico.py new file mode 100644 index 0000000..83e5ecd --- /dev/null +++ b/currency_rate_update_banxico/tests/test_currency_rate_update_banxico.py @@ -0,0 +1,127 @@ +# Copyright 2025 Jarsa +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +from datetime import timedelta + +import requests_mock + +from odoo import fields +from odoo.tests import common + + +class TestResCurrencyRateProviderBanxico(common.TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.today = fields.Date.today() + cls.yesterday = cls.today - timedelta(days=1) + cls.currency_rate = cls.env["res.currency.rate"] + cls.usd_currency = cls.env.ref("base.USD") + cls.company = cls.env["res.company"].create( + {"name": "Test company", "currency_id": cls.env.ref("base.MXN").id} + ) + cls.env.user.company_ids += cls.company + cls.env.company = cls.company + cls.banxico_provider = cls.env["res.currency.rate.provider"].create( + { + "service": "banxico", + "banxico_token": "test_token", + "company_id": cls.company.id, + "last_successful_run": cls.yesterday, + "currency_ids": [ + (4, cls.usd_currency.id), + ], + } + ) + cls.currency_rate.search([]).unlink() + + def mock_response(self, mocker): + """Helper function to mock the API response.""" + url = "https://www.banxico.org.mx/SieAPIRest/service/v1/series/SF60653/datos/oportuno" + mocker.get( + url, + json={ + "bmx": { + "series": [ + { + "idSerie": "SF60653", + "titulo": "Test Title", + "datos": [ + { + "fecha": self.today.strftime("%d/%m/%Y"), + "dato": "20.4742", + } + ], + } + ] + } + }, + ) + url = ( + "https://www.banxico.org.mx/SieAPIRest/service/v1/series/SF60653/datos/" + f"{self.yesterday.strftime('%Y-%m-%d')}/{self.today.strftime('%Y-%m-%d')}" + ) + mocker.get( + url, + json={ + "bmx": { + "series": [ + { + "idSerie": "SF60653", + "titulo": "Test Title", + "datos": [ + { + "fecha": self.yesterday.strftime("%d/%m/%Y"), + "dato": "20.3440", + }, + { + "fecha": self.today.strftime("%d/%m/%Y"), + "dato": "20.3823", + }, + ], + } + ] + } + }, + ) + + def test_cron(self): + with requests_mock.Mocker() as mocker: + self.mock_response(mocker) + self.banxico_provider._scheduled_update() + rates = self.currency_rate.search([("company_id", "=", self.company.id)]) + + self.assertEqual(len(rates), 1) + self.assertEqual(rates.currency_id, self.usd_currency) + self.assertAlmostEqual(rates.rate, 1 / 20.4742, places=4) + + def test_wizard(self): + with requests_mock.Mocker() as mocker: + self.mock_response(mocker) + + wizard = ( + self.env["res.currency.rate.update.wizard"] + .with_context( + default_provider_ids=[(6, False, self.banxico_provider.ids)] + ) + .create( + { + "date_from": self.yesterday, + "date_to": self.today, + } + ) + ) + wizard.action_update() + + rates = self.currency_rate.search([("company_id", "=", self.company.id)]) + self.assertEqual(len(rates), 2) + + yesterday_rate = rates.filtered(lambda r: r.name == self.yesterday) + self.assertEqual(len(yesterday_rate), 1) + self.assertEqual(yesterday_rate.currency_id, self.usd_currency) + self.assertAlmostEqual(yesterday_rate.rate, 1 / 20.3440, places=4) + + today_rate = rates.filtered(lambda r: r.name == self.today) + self.assertEqual(len(today_rate), 1) + self.assertEqual(today_rate.currency_id, self.usd_currency) + self.assertAlmostEqual(today_rate.rate, 1 / 20.3823, places=4) diff --git a/currency_rate_update_banxico/views/res_currency_rate_provider_views.xml b/currency_rate_update_banxico/views/res_currency_rate_provider_views.xml new file mode 100644 index 0000000..bd626f3 --- /dev/null +++ b/currency_rate_update_banxico/views/res_currency_rate_provider_views.xml @@ -0,0 +1,36 @@ + + + + + res.currency.rate.provider.view.form + res.currency.rate.provider + + + + + + + + + + diff --git a/requirements.txt b/requirements.txt index cdb5551..3d4001c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ # generated from manifests external_dependencies facturama pdfminer.six==20220319 +requests-mock