2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-07-02 11:40:58 +00:00

Client side currency conversion (#4293)

* Automatically update exchange rates when base currency is updated

* Adds API endpoint with currency exchange information

* Add unit testing for new endpoint

* Implement javascript code for client-side conversion

* Adds helper function for calculating total price of a dataset

* javascript cleanup

* Add functionality to sales order tables

* JS linting

* Update API version

* Prevent auto currency updates under certain conditions
This commit is contained in:
Oliver
2023-02-02 22:47:35 +11:00
committed by GitHub
parent 9a289948e5
commit eccd3be150
7 changed files with 344 additions and 116 deletions

View File

@ -9,6 +9,7 @@ from django.views.decorators.csrf import csrf_exempt
from django_filters.rest_framework import DjangoFilterBackend
from django_q.tasks import async_task
from djmoney.contrib.exchange.models import Rate
from rest_framework import filters, permissions, serializers
from rest_framework.exceptions import NotAcceptable, NotFound
from rest_framework.permissions import IsAdminUser
@ -102,6 +103,36 @@ class WebhookView(CsrfExemptMixin, APIView):
raise NotFound()
class CurrencyExchangeView(APIView):
"""API endpoint for displaying currency information
TODO: Add a POST hook to refresh / update the currency exchange data
"""
permission_classes = [
permissions.IsAuthenticated,
]
def get(self, request, format=None):
"""Return information on available currency conversions"""
# Extract a list of all available rates
try:
rates = Rate.objects.all()
except Exception:
rates = []
response = {
'base_currency': common.models.InvenTreeSetting.get_setting('INVENTREE_DEFAULT_CURRENCY', 'USD'),
'exchange_rates': {}
}
for rate in rates:
response['exchange_rates'][rate.currency] = rate.value
return Response(response)
class SettingsList(ListAPI):
"""Generic ListView for settings.
@ -418,6 +449,11 @@ common_api_urls = [
# Webhooks
path('webhook/<slug:endpoint>/', WebhookView.as_view(), name='api-webhook'),
# Currencies
re_path(r'^currency/', include([
re_path(r'^exchange/', CurrencyExchangeView.as_view(), name='api-currency-exchange'),
])),
# Notifications
re_path(r'^notifications/', include([
# Individual purchase order detail URLs

View File

@ -43,6 +43,7 @@ import build.validators
import InvenTree.fields
import InvenTree.helpers
import InvenTree.ready
import InvenTree.tasks
import InvenTree.validators
import order.validators
@ -821,6 +822,18 @@ def validate_email_domains(setting):
raise ValidationError(_(f'Invalid domain name: {domain}'))
def update_exchange_rates(setting):
"""Update exchange rates when base currency is changed"""
if InvenTree.ready.isImportingData():
return
if not InvenTree.ready.canAppAccessDatabase():
return
InvenTree.tasks.update_exchange_rates()
class InvenTreeSetting(BaseInvenTreeSetting):
"""An InvenTreeSetting object is a key:value pair used for storing single values (e.g. one-off settings values).
@ -901,9 +914,10 @@ class InvenTreeSetting(BaseInvenTreeSetting):
'INVENTREE_DEFAULT_CURRENCY': {
'name': _('Default Currency'),
'description': _('Default currency'),
'description': _('Select base currency for pricing caluclations'),
'default': 'USD',
'choices': CURRENCY_CHOICES,
'after_save': update_exchange_rates,
},
'INVENTREE_DOWNLOAD_FROM_URL': {

View File

@ -900,3 +900,15 @@ class ColorThemeTest(TestCase):
# check valid theme
self.assertFalse(ColorTheme.is_valid_choice(aa))
self.assertTrue(ColorTheme.is_valid_choice(ab))
class CurrencyAPITests(InvenTreeAPITestCase):
"""Unit tests for the currency exchange API endpoints"""
def test_exchange_endpoint(self):
"""Test that the currency exchange endpoint works as expected"""
response = self.get(reverse('api-currency-exchange'), expected_code=200)
self.assertIn('base_currency', response.data)
self.assertIn('exchange_rates', response.data)