diff --git a/docs/docs/settings/currency.md b/docs/docs/settings/currency.md index 6ceb9d0d4b..4a2e7834f5 100644 --- a/docs/docs/settings/currency.md +++ b/docs/docs/settings/currency.md @@ -6,13 +6,13 @@ title: Currency Support InvenTree provides support for multiple currencies, allowing pricing information to be stored with base currency rates. -### Configuration +### Supported Currencies -To specify which currencies are supported, refer to the [currency configuration](../start/config.md#supported-currencies) section +InvenTree uses the [django-money](https://github.com/django-money/django-money) library, which in turn uses the [py-moneyed library](https://py-moneyed.readthedocs.io/en/latest/index.html). `py-moneyed` supports any currency which is defined in the [ISO 3166 standard](https://en.wikipedia.org/wiki/List_of_ISO_3166_country_codes) standard. ### Currency Conversion -Currency conversion is provided via the [django-money](https://github.com/django-money/django-money) library. Pricing data can be converted seamlessly between the available currencies. +Currency conversion is provided via the `django-money` library. Pricing data can be converted seamlessly between the available currencies. ### Currency Rate Updates @@ -26,6 +26,11 @@ If a different currency exchange backend is needed, or a custom implementation i In the [settings screen](./global.md), under the *Pricing* section, the following currency settings are available: -{% with id="currency-settings", url="settings/currency.png", description="Currency Exchange Settings" %} -{% include 'img.html' %} -{% endwith %} +| Setting | Description | Default Value | +| --- | --- | +| Default Currency | The selected *default* currency for the system. | USD | +| Supported Currencies | The list of supported currencies for the system. | AUD, CAD, CNY, EUR, GBP, JPY, NZD, USD | + +#### Supported Currencies + +While InvenTree can support any of the currencies defined in the ISO 3166 standard, the list of supported currencies can be limited to only those which are relevant to the user. The supported currencies are used to populate the currency selection dropdowns throughout the InvenTree interface. diff --git a/docs/docs/start/config.md b/docs/docs/start/config.md index 13d2aebe98..98498f25fc 100644 --- a/docs/docs/start/config.md +++ b/docs/docs/start/config.md @@ -256,19 +256,6 @@ The "sender" email address is the address from which InvenTree emails are sent ( !!! info "Fallback" If `INVENTREE_EMAIL_SENDER` is not provided, the system will fall back to `INVENTREE_EMAIL_USERNAME` (if the username is a valid email address) -## Supported Currencies - -The currencies supported by InvenTree must be specified in the [configuration file](#configuration-file). - -A list of currency codes (e.g. *AUD*, *CAD*, *JPY*, *USD*) can be specified using the `currencies` variable (or using the `INVENTREE_CURRENCIES` environment variable). - -| Environment Variable | Configuration File | Description | Default | -| --- | --- | --- | --- | -| INVENTREE_CURRENCIES | currencies | List of supported currencies| `AUD`, `CAD`, `CNY`, `EUR`, `GBP`, `JPY`, `NZD`, `USD` | - -!!! tip "More Info" - Read the [currencies documentation](../settings/currency.md) for more information on currency support in InvenTree - ## File Storage Locations InvenTree requires some external directories for storing files: diff --git a/src/backend/InvenTree/InvenTree/api_version.py b/src/backend/InvenTree/InvenTree/api_version.py index 195b1631c3..e6c00a36a1 100644 --- a/src/backend/InvenTree/InvenTree/api_version.py +++ b/src/backend/InvenTree/InvenTree/api_version.py @@ -1,12 +1,15 @@ """InvenTree API version information.""" # InvenTree API version -INVENTREE_API_VERSION = 202 +INVENTREE_API_VERSION = 203 """Increment this API version number whenever there is a significant change to the API that any clients need to know about.""" INVENTREE_API_TEXT = """ +v203 - 2024-06-03 : https://github.com/inventree/InvenTree/pull/7390 + - Adjust default currency codes + v202 - 2024-05-27 : https://github.com/inventree/InvenTree/pull/7343 - Adjust "required" attribute of Part.category field to be optional diff --git a/src/backend/InvenTree/InvenTree/apps.py b/src/backend/InvenTree/InvenTree/apps.py index c10a2e156e..d4ecadeaf4 100644 --- a/src/backend/InvenTree/InvenTree/apps.py +++ b/src/backend/InvenTree/InvenTree/apps.py @@ -185,7 +185,7 @@ class InvenTreeConfig(AppConfig): try: from djmoney.contrib.exchange.models import ExchangeBackend - from common.settings import currency_code_default + from common.currency import currency_code_default from InvenTree.tasks import update_exchange_rates except AppRegistryNotReady: # pragma: no cover pass diff --git a/src/backend/InvenTree/InvenTree/exchange.py b/src/backend/InvenTree/InvenTree/exchange.py index 88584b43cc..2b118b696e 100644 --- a/src/backend/InvenTree/InvenTree/exchange.py +++ b/src/backend/InvenTree/InvenTree/exchange.py @@ -7,7 +7,7 @@ from django.db.transaction import atomic from djmoney.contrib.exchange.backends.base import SimpleExchangeBackend from djmoney.contrib.exchange.models import ExchangeBackend, Rate -from common.settings import currency_code_default, currency_codes +from common.currency import currency_code_default, currency_codes logger = logging.getLogger('inventree') diff --git a/src/backend/InvenTree/InvenTree/fields.py b/src/backend/InvenTree/InvenTree/fields.py index f4dc686212..9dd0d81fb0 100644 --- a/src/backend/InvenTree/InvenTree/fields.py +++ b/src/backend/InvenTree/InvenTree/fields.py @@ -59,7 +59,7 @@ class InvenTreeURLField(models.URLField): def money_kwargs(**kwargs): """Returns the database settings for MoneyFields.""" - from common.settings import currency_code_default, currency_code_mappings + from common.currency import currency_code_default, currency_code_mappings # Default values (if not specified) if 'max_digits' not in kwargs: diff --git a/src/backend/InvenTree/InvenTree/helpers.py b/src/backend/InvenTree/InvenTree/helpers.py index 54b2e9ee39..a3ca2ffaf5 100644 --- a/src/backend/InvenTree/InvenTree/helpers.py +++ b/src/backend/InvenTree/InvenTree/helpers.py @@ -28,7 +28,7 @@ from djmoney.money import Money from PIL import Image import InvenTree.version -from common.settings import currency_code_default +from common.currency import currency_code_default from .settings import MEDIA_URL, STATIC_URL diff --git a/src/backend/InvenTree/InvenTree/serializers.py b/src/backend/InvenTree/InvenTree/serializers.py index 08c53d20e5..d0b4ed8ac9 100644 --- a/src/backend/InvenTree/InvenTree/serializers.py +++ b/src/backend/InvenTree/InvenTree/serializers.py @@ -23,7 +23,7 @@ from rest_framework.utils import model_meta from taggit.serializers import TaggitSerializer import common.models as common_models -from common.settings import currency_code_default, currency_code_mappings +from common.currency import currency_code_default, currency_code_mappings from InvenTree.fields import InvenTreeRestURLField, InvenTreeURLField diff --git a/src/backend/InvenTree/InvenTree/settings.py b/src/backend/InvenTree/InvenTree/settings.py index 7cf73a18b5..7aed4489a5 100644 --- a/src/backend/InvenTree/InvenTree/settings.py +++ b/src/backend/InvenTree/InvenTree/settings.py @@ -20,7 +20,6 @@ from django.core.validators import URLValidator from django.http import Http404 from django.utils.translation import gettext_lazy as _ -import moneyed import pytz from dotenv import load_dotenv @@ -904,28 +903,9 @@ if get_boolean_setting('TEST_TRANSLATIONS', default_value=False): # pragma: no LANG_INFO = dict(django.conf.locale.LANG_INFO, **EXTRA_LANG_INFO) django.conf.locale.LANG_INFO = LANG_INFO -# Currencies available for use -CURRENCIES = get_setting( - 'INVENTREE_CURRENCIES', - 'currencies', - ['AUD', 'CAD', 'CNY', 'EUR', 'GBP', 'JPY', 'NZD', 'USD'], - typecast=list, -) - -# Ensure that at least one currency value is available -if len(CURRENCIES) == 0: # pragma: no cover - logger.warning('No currencies selected: Defaulting to USD') - CURRENCIES = ['USD'] - # Maximum number of decimal places for currency rendering CURRENCY_DECIMAL_PLACES = 6 -# Check that each provided currency is supported -for currency in CURRENCIES: - if currency not in moneyed.CURRENCIES: # pragma: no cover - logger.error("Currency code '%s' is not supported", currency) - sys.exit(1) - # Custom currency exchange backend EXCHANGE_BACKEND = 'InvenTree.exchange.InvenTreeExchange' diff --git a/src/backend/InvenTree/InvenTree/tasks.py b/src/backend/InvenTree/InvenTree/tasks.py index 4356509e12..9b8366ef82 100644 --- a/src/backend/InvenTree/InvenTree/tasks.py +++ b/src/backend/InvenTree/InvenTree/tasks.py @@ -571,8 +571,8 @@ def update_exchange_rates(force: bool = False): try: from djmoney.contrib.exchange.models import Rate + from common.currency import currency_code_default, currency_codes from common.models import InvenTreeSetting - from common.settings import currency_code_default, currency_codes from InvenTree.exchange import InvenTreeExchange except AppRegistryNotReady: # pragma: no cover # Apps not yet loaded! diff --git a/src/backend/InvenTree/InvenTree/templatetags/inventree_extras.py b/src/backend/InvenTree/InvenTree/templatetags/inventree_extras.py index 48fbe7ba99..c6408d8f34 100644 --- a/src/backend/InvenTree/InvenTree/templatetags/inventree_extras.py +++ b/src/backend/InvenTree/InvenTree/templatetags/inventree_extras.py @@ -16,7 +16,7 @@ import common.models import InvenTree.helpers import InvenTree.helpers_model import plugin.models -from common.settings import currency_code_default +from common.currency import currency_code_default from InvenTree import settings, version from plugin import registry from plugin.plugin import InvenTreePlugin diff --git a/src/backend/InvenTree/InvenTree/tests.py b/src/backend/InvenTree/InvenTree/tests.py index 891759c885..165c981edd 100644 --- a/src/backend/InvenTree/InvenTree/tests.py +++ b/src/backend/InvenTree/InvenTree/tests.py @@ -29,8 +29,8 @@ import InvenTree.format import InvenTree.helpers import InvenTree.helpers_model import InvenTree.tasks +from common.currency import currency_codes from common.models import CustomUnit, InvenTreeSetting -from common.settings import currency_codes from InvenTree.helpers_mixin import ClassProviderMixin, ClassValidationMixin from InvenTree.sanitizer import sanitize_svg from InvenTree.unit_test import InvenTreeTestCase diff --git a/src/backend/InvenTree/InvenTree/views.py b/src/backend/InvenTree/InvenTree/views.py index 05f69531a1..176e704b19 100644 --- a/src/backend/InvenTree/InvenTree/views.py +++ b/src/backend/InvenTree/InvenTree/views.py @@ -25,8 +25,8 @@ from allauth.socialaccount.views import ConnectionsView from djmoney.contrib.exchange.models import ExchangeBackend, Rate from user_sessions.views import SessionDeleteOtherView, SessionDeleteView +import common.currency import common.models as common_models -import common.settings as common_settings from part.models import PartCategory from users.models import RuleSet, check_user_role @@ -506,8 +506,8 @@ class SettingsView(TemplateView): ctx['settings'] = common_models.InvenTreeSetting.objects.all().order_by('key') - ctx['base_currency'] = common_settings.currency_code_default() - ctx['currencies'] = common_settings.currency_codes + ctx['base_currency'] = common.currency.currency_code_default() + ctx['currencies'] = common.currency.currency_codes ctx['rates'] = Rate.objects.filter(backend='InvenTreeExchange') diff --git a/src/backend/InvenTree/common/currency.py b/src/backend/InvenTree/common/currency.py new file mode 100644 index 0000000000..542f81d1fc --- /dev/null +++ b/src/backend/InvenTree/common/currency.py @@ -0,0 +1,226 @@ +"""Helper functions for currency support.""" + +import decimal +import logging +import math + +from django.core.cache import cache +from django.core.exceptions import ValidationError +from django.utils.translation import gettext_lazy as _ + +from moneyed import CURRENCIES + +import InvenTree.helpers + +logger = logging.getLogger('inventree') + + +def currency_code_default(): + """Returns the default currency code (or USD if not specified).""" + from common.models import InvenTreeSetting + + try: + cached_value = cache.get('currency_code_default', '') + except Exception: + cached_value = None + + if cached_value: + return cached_value + + try: + code = InvenTreeSetting.get_setting( + 'INVENTREE_DEFAULT_CURRENCY', backup_value='', create=True, cache=True + ) + except Exception: # pragma: no cover + # Database may not yet be ready, no need to throw an error here + code = '' + + if code not in CURRENCIES: + code = 'USD' # pragma: no cover + + # Cache the value for a short amount of time + try: + cache.set('currency_code_default', code, 30) + except Exception: + pass + + return code + + +def all_currency_codes() -> list: + """Returns a list of all currency codes.""" + return [(a, CURRENCIES[a].name) for a in CURRENCIES] + + +def currency_codes_default_list() -> str: + """Return a comma-separated list of default currency codes.""" + return 'AUD,CAD,CNY,EUR,GBP,JPY,NZD,USD' + + +def currency_codes() -> list: + """Returns the current currency codes.""" + from common.models import InvenTreeSetting + + codes = InvenTreeSetting.get_setting('CURRENCY_CODES', '', create=False).strip() + + if not codes: + codes = currency_codes_default_list() + + codes = codes.split(',') + + valid_codes = set() + + for code in codes: + code = code.strip().upper() + + if code in CURRENCIES: + valid_codes.add(code) + else: + logger.warning(f"Invalid currency code: '{code}'") + + if len(valid_codes) == 0: + valid_codes = set(currency_codes_default_list().split(',')) + + return list(valid_codes) + + +def currency_code_mappings() -> list: + """Returns the current currency choices.""" + return [(a, CURRENCIES[a].name) for a in currency_codes()] + + +def after_change_currency(setting) -> None: + """Callback function when base currency is changed. + + - Update exchange rates + - Recalculate prices for all parts + """ + import InvenTree.ready + import InvenTree.tasks + + if InvenTree.ready.isImportingData(): + return + + if not InvenTree.ready.canAppAccessDatabase(): + return + + from part import tasks as part_tasks + + # Immediately update exchange rates + InvenTree.tasks.update_exchange_rates(force=True) + + # Offload update of part prices to a background task + InvenTree.tasks.offload_task(part_tasks.check_missing_pricing, force_async=True) + + +def validate_currency_codes(value): + """Validate the currency codes.""" + values = value.strip().split(',') + + valid_currencies = set() + + for code in values: + code = code.strip().upper() + + if not code: + continue + + if code not in CURRENCIES: + raise ValidationError(_('Invalid currency code') + f": '{code}'") + elif code in valid_currencies: + raise ValidationError(_('Duplicate currency code') + f": '{code}'") + else: + valid_currencies.add(code) + + if len(valid_currencies) == 0: + raise ValidationError(_('No valid currency codes provided')) + + return list(valid_currencies) + + +def currency_exchange_plugins() -> list: + """Return a list of plugin choices which can be used for currency exchange.""" + try: + from plugin import registry + + plugs = registry.with_mixin('currencyexchange', active=True) + except Exception: + plugs = [] + + return [('', _('No plugin'))] + [(plug.slug, plug.human_name) for plug in plugs] + + +def get_price( + instance, + quantity, + moq=True, + multiples=True, + currency=None, + break_name: str = 'price_breaks', +): + """Calculate the price based on quantity price breaks. + + - Don't forget to add in flat-fee cost (base_cost field) + - If MOQ (minimum order quantity) is required, bump quantity + - If order multiples are to be observed, then we need to calculate based on that, too + """ + from common.currency import currency_code_default + + if hasattr(instance, break_name): + price_breaks = getattr(instance, break_name).all() + else: + price_breaks = [] + + # No price break information available? + if len(price_breaks) == 0: + return None + + # Check if quantity is fraction and disable multiples + multiples = quantity % 1 == 0 + + # Order multiples + if multiples: + quantity = int(math.ceil(quantity / instance.multiple) * instance.multiple) + + pb_found = False + pb_quantity = -1 + pb_cost = 0.0 + + if currency is None: + # Default currency selection + currency = currency_code_default() + + pb_min = None + for pb in price_breaks: + # Store smallest price break + if not pb_min: + pb_min = pb + + # Ignore this pricebreak (quantity is too high) + if pb.quantity > quantity: + continue + + pb_found = True + + # If this price-break quantity is the largest so far, use it! + if pb.quantity > pb_quantity: + pb_quantity = pb.quantity + + # Convert everything to the selected currency + pb_cost = pb.convert_to(currency) + + # Use smallest price break + if not pb_found and pb_min: + # Update price break information + pb_quantity = pb_min.quantity + pb_cost = pb_min.convert_to(currency) + # Trigger cost calculation using smallest price break + pb_found = True + + # Convert quantity to decimal.Decimal format + quantity = decimal.Decimal(f'{quantity}') + + if pb_found: + cost = pb_cost * quantity + return InvenTree.helpers.normalize(cost + instance.base_cost) + return None diff --git a/src/backend/InvenTree/common/migrations/0010_migrate_currency_setting.py b/src/backend/InvenTree/common/migrations/0010_migrate_currency_setting.py index 09b0100db4..116d139be8 100644 --- a/src/backend/InvenTree/common/migrations/0010_migrate_currency_setting.py +++ b/src/backend/InvenTree/common/migrations/0010_migrate_currency_setting.py @@ -9,7 +9,7 @@ def set_default_currency(apps, schema_editor): # get value from settings-file base_currency = get_setting('INVENTREE_BASE_CURRENCY', 'base_currency', 'USD') - from common.settings import currency_codes + from common.currency import currency_codes # check if value is valid if base_currency not in currency_codes(): diff --git a/src/backend/InvenTree/common/migrations/0023_auto_20240602_1332.py b/src/backend/InvenTree/common/migrations/0023_auto_20240602_1332.py new file mode 100644 index 0000000000..a311af62ac --- /dev/null +++ b/src/backend/InvenTree/common/migrations/0023_auto_20240602_1332.py @@ -0,0 +1,73 @@ +# Generated by Django 4.2.12 on 2024-06-02 13:32 + +from django.db import migrations + +from moneyed import CURRENCIES + +import InvenTree.config + + +def set_currencies(apps, schema_editor): + """Set the default currency codes. + + Ref: https://github.com/inventree/InvenTree/pull/7390 + + Previously, the allowed currency codes were set in the external configuration + (e.g via the configuration file or environment variables). + + Now, they are set in the database (via the InvenTreeSetting model). + + So, this data migration exists to transfer any configured currency codes, + from the external configuration, into the database settings model. + """ + + InvenTreeSetting = apps.get_model('common', 'InvenTreeSetting') + + key = 'CURRENCY_CODES' + + codes = InvenTree.config.get_setting('INVENTREE_CURRENCIES', 'currencies', None) + + if codes is None: + # No currency codes are defined in the configuration file + return + + if type(codes) == str: + codes = codes.split(',') + + valid_codes = set() + + for code in codes: + code = code.strip().upper() + + if code in CURRENCIES: + valid_codes.add(code) + + if len(valid_codes) == 0: + print(f"No valid currency codes found in configuration file") + return + + value = ','.join(valid_codes) + print(f"Found existing currency codes:", value) + + setting = InvenTreeSetting.objects.filter(key=key).first() + + if setting: + print(f"Updating existing setting for currency codes") + setting.value = value + setting.save() + else: + print(f"Creating new setting for currency codes") + setting = InvenTreeSetting(key=key, value=value) + setting.save() + + + +class Migration(migrations.Migration): + + dependencies = [ + ('common', '0022_projectcode_responsible'), + ] + + operations = [ + migrations.RunPython(set_currencies, reverse_code=migrations.RunPython.noop) + ] diff --git a/src/backend/InvenTree/common/models.py b/src/backend/InvenTree/common/models.py index a134167d02..417ef238e7 100644 --- a/src/backend/InvenTree/common/models.py +++ b/src/backend/InvenTree/common/models.py @@ -4,12 +4,10 @@ These models are 'generic' and do not fit a particular business logic object. """ import base64 -import decimal import hashlib import hmac import json import logging -import math import os import re import uuid @@ -41,6 +39,7 @@ from djmoney.settings import CURRENCY_CHOICES from rest_framework.exceptions import PermissionDenied import build.validators +import common.currency import InvenTree.fields import InvenTree.helpers import InvenTree.models @@ -1160,39 +1159,6 @@ def validate_email_domains(setting): raise ValidationError(_(f'Invalid domain name: {domain}')) -def currency_exchange_plugins(): - """Return a set of plugin choices which can be used for currency exchange.""" - try: - from plugin import registry - - plugs = registry.with_mixin('currencyexchange', active=True) - except Exception: - plugs = [] - - return [('', _('No plugin'))] + [(plug.slug, plug.human_name) for plug in plugs] - - -def after_change_currency(setting): - """Callback function when base currency is changed. - - - Update exchange rates - - Recalculate prices for all parts - """ - if InvenTree.ready.isImportingData(): - return - - if not InvenTree.ready.canAppAccessDatabase(): - return - - from part import tasks as part_tasks - - # Immediately update exchange rates - InvenTree.tasks.update_exchange_rates(force=True) - - # Offload update of part prices to a background task - InvenTree.tasks.offload_task(part_tasks.check_missing_pricing, force_async=True) - - def reload_plugin_registry(setting): """When a core plugin setting is changed, reload the plugin registry.""" from plugin import registry @@ -1305,8 +1271,15 @@ class InvenTreeSetting(BaseInvenTreeSetting): 'name': _('Default Currency'), 'description': _('Select base currency for pricing calculations'), 'default': 'USD', - 'choices': CURRENCY_CHOICES, - 'after_save': after_change_currency, + 'choices': common.currency.currency_code_mappings, + 'after_save': common.currency.after_change_currency, + }, + 'CURRENCY_CODES': { + 'name': _('Supported Currencies'), + 'description': _('List of supported currency codes'), + 'default': common.currency.currency_codes_default_list(), + 'validator': common.currency.validate_currency_codes, + 'after_save': common.currency.after_change_currency, }, 'CURRENCY_UPDATE_INTERVAL': { 'name': _('Currency Update Interval'), @@ -1320,7 +1293,7 @@ class InvenTreeSetting(BaseInvenTreeSetting): 'CURRENCY_UPDATE_PLUGIN': { 'name': _('Currency Update Plugin'), 'description': _('Currency update plugin to use'), - 'choices': currency_exchange_plugins, + 'choices': common.currency.currency_exchange_plugins, 'default': 'inventreecurrencyexchange', }, 'INVENTREE_DOWNLOAD_FROM_URL': { @@ -2567,82 +2540,6 @@ class PriceBreak(MetaMixin): return converted.amount -def get_price( - instance, - quantity, - moq=True, - multiples=True, - currency=None, - break_name: str = 'price_breaks', -): - """Calculate the price based on quantity price breaks. - - - Don't forget to add in flat-fee cost (base_cost field) - - If MOQ (minimum order quantity) is required, bump quantity - - If order multiples are to be observed, then we need to calculate based on that, too - """ - from common.settings import currency_code_default - - if hasattr(instance, break_name): - price_breaks = getattr(instance, break_name).all() - else: - price_breaks = [] - - # No price break information available? - if len(price_breaks) == 0: - return None - - # Check if quantity is fraction and disable multiples - multiples = quantity % 1 == 0 - - # Order multiples - if multiples: - quantity = int(math.ceil(quantity / instance.multiple) * instance.multiple) - - pb_found = False - pb_quantity = -1 - pb_cost = 0.0 - - if currency is None: - # Default currency selection - currency = currency_code_default() - - pb_min = None - for pb in price_breaks: - # Store smallest price break - if not pb_min: - pb_min = pb - - # Ignore this pricebreak (quantity is too high) - if pb.quantity > quantity: - continue - - pb_found = True - - # If this price-break quantity is the largest so far, use it! - if pb.quantity > pb_quantity: - pb_quantity = pb.quantity - - # Convert everything to the selected currency - pb_cost = pb.convert_to(currency) - - # Use smallest price break - if not pb_found and pb_min: - # Update price break information - pb_quantity = pb_min.quantity - pb_cost = pb_min.convert_to(currency) - # Trigger cost calculation using smallest price break - pb_found = True - - # Convert quantity to decimal.Decimal format - quantity = decimal.Decimal(f'{quantity}') - - if pb_found: - cost = pb_cost * quantity - return InvenTree.helpers.normalize(cost + instance.base_cost) - return None - - class ColorTheme(models.Model): """Color Theme Setting.""" diff --git a/src/backend/InvenTree/common/settings.py b/src/backend/InvenTree/common/settings.py index 3380def996..67a96c3510 100644 --- a/src/backend/InvenTree/common/settings.py +++ b/src/backend/InvenTree/common/settings.py @@ -1,61 +1,5 @@ """User-configurable settings for the common app.""" -import logging - -from django.conf import settings -from django.core.cache import cache - -from moneyed import CURRENCIES - -logger = logging.getLogger('inventree') - - -def currency_code_default(): - """Returns the default currency code (or USD if not specified).""" - from common.models import InvenTreeSetting - - try: - cached_value = cache.get('currency_code_default', '') - except Exception: - cached_value = None - - if cached_value: - return cached_value - - try: - code = InvenTreeSetting.get_setting( - 'INVENTREE_DEFAULT_CURRENCY', backup_value='', create=True, cache=True - ) - except Exception: # pragma: no cover - # Database may not yet be ready, no need to throw an error here - code = '' - - if code not in CURRENCIES: - code = 'USD' # pragma: no cover - - # Cache the value for a short amount of time - try: - cache.set('currency_code_default', code, 30) - except Exception: - pass - - return code - - -def all_currency_codes(): - """Returns a list of all currency codes.""" - return [(a, CURRENCIES[a].name) for a in CURRENCIES] - - -def currency_code_mappings(): - """Returns the current currency choices.""" - return [(a, CURRENCIES[a].name) for a in settings.CURRENCIES] - - -def currency_codes(): - """Returns the current currency codes.""" - return list(settings.CURRENCIES) - def stock_expiry_enabled(): """Returns True if the stock expiry feature is enabled.""" diff --git a/src/backend/InvenTree/common/tests.py b/src/backend/InvenTree/common/tests.py index 9a79609276..7aad13713c 100644 --- a/src/backend/InvenTree/common/tests.py +++ b/src/backend/InvenTree/common/tests.py @@ -372,6 +372,30 @@ class GlobalSettingsApiTest(InvenTreeAPITestCase): # Number of results should match the number of settings self.assertEqual(len(response.data), n_public_settings) + def test_currency_settings(self): + """Run tests for currency specific settings.""" + url = reverse('api-global-setting-detail', kwargs={'key': 'CURRENCY_CODES'}) + + response = self.patch(url, data={'value': 'USD,XYZ'}, expected_code=400) + + self.assertIn("Invalid currency code: 'XYZ'", str(response.data)) + + response = self.patch( + url, data={'value': 'AUD,USD, AUD,AUD,'}, expected_code=400 + ) + + self.assertIn("Duplicate currency code: 'AUD'", str(response.data)) + + response = self.patch(url, data={'value': ',,,,,'}, expected_code=400) + + self.assertIn('No valid currency codes provided', str(response.data)) + + response = self.patch(url, data={'value': 'AUD,USD,GBP'}, expected_code=200) + + codes = InvenTreeSetting.get_setting('CURRENCY_CODES') + + self.assertEqual(codes, 'AUD,USD,GBP') + def test_company_name(self): """Test a settings object lifecycle e2e.""" setting = InvenTreeSetting.get_setting_object('INVENTREE_COMPANY_NAME') diff --git a/src/backend/InvenTree/company/migrations/0025_auto_20201110_1001.py b/src/backend/InvenTree/company/migrations/0025_auto_20201110_1001.py index 117710daa7..f0db3f5f38 100644 --- a/src/backend/InvenTree/company/migrations/0025_auto_20201110_1001.py +++ b/src/backend/InvenTree/company/migrations/0025_auto_20201110_1001.py @@ -2,6 +2,7 @@ from django.db import migrations, connection import djmoney.models.fields +import common.currency import common.settings @@ -16,11 +17,11 @@ class Migration(migrations.Migration): migrations.AddField( model_name='supplierpricebreak', name='price', - field=djmoney.models.fields.MoneyField(decimal_places=4, default_currency=common.settings.currency_code_default(), help_text='Unit price at specified quantity', max_digits=19, null=True, verbose_name='Price'), + field=djmoney.models.fields.MoneyField(decimal_places=4, default_currency=common.currency.currency_code_default(), help_text='Unit price at specified quantity', max_digits=19, null=True, verbose_name='Price'), ), migrations.AddField( model_name='supplierpricebreak', name='price_currency', - field=djmoney.models.fields.CurrencyField(choices=common.settings.currency_code_mappings(), default=common.settings.currency_code_default(), editable=False, max_length=3), + field=djmoney.models.fields.CurrencyField(choices=common.currency.currency_code_mappings(), default=common.currency.currency_code_default(), editable=False, max_length=3), ), ] diff --git a/src/backend/InvenTree/company/migrations/0040_alter_company_currency.py b/src/backend/InvenTree/company/migrations/0040_alter_company_currency.py index f26f470191..10c22b3367 100644 --- a/src/backend/InvenTree/company/migrations/0040_alter_company_currency.py +++ b/src/backend/InvenTree/company/migrations/0040_alter_company_currency.py @@ -1,6 +1,7 @@ # Generated by Django 3.2.4 on 2021-07-02 13:21 import InvenTree.validators +import common.currency import common.settings from django.db import migrations, models @@ -15,6 +16,6 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='company', name='currency', - field=models.CharField(blank=True, default=common.settings.currency_code_default, help_text='Default currency used for this company', max_length=3, validators=[InvenTree.validators.validate_currency_code], verbose_name='Currency'), + field=models.CharField(blank=True, default=common.currency.currency_code_default, help_text='Default currency used for this company', max_length=3, validators=[InvenTree.validators.validate_currency_code], verbose_name='Currency'), ), ] diff --git a/src/backend/InvenTree/company/models.py b/src/backend/InvenTree/company/models.py index 4e102a160d..30b2dc5789 100644 --- a/src/backend/InvenTree/company/models.py +++ b/src/backend/InvenTree/company/models.py @@ -1,7 +1,6 @@ """Company database model definitions.""" import os -from datetime import datetime from decimal import Decimal from django.apps import apps @@ -20,6 +19,7 @@ from moneyed import CURRENCIES from stdimage.models import StdImageField from taggit.managers import TaggableManager +import common.currency import common.models import common.settings import InvenTree.conversion @@ -29,7 +29,7 @@ import InvenTree.models import InvenTree.ready import InvenTree.tasks import InvenTree.validators -from common.settings import currency_code_default +from common.currency import currency_code_default from InvenTree.fields import InvenTreeURLField, RoundingDecimalField from order.status_codes import PurchaseOrderStatusGroups @@ -212,7 +212,7 @@ class Company( code = self.currency if code not in CURRENCIES: - code = common.settings.currency_code_default() + code = common.currency.currency_code_default() return code @@ -967,7 +967,7 @@ class SupplierPart( SupplierPriceBreak.objects.create(part=self, quantity=quantity, price=price) - get_price = common.models.get_price + get_price = common.currency.get_price def open_orders(self): """Return a database query for PurchaseOrder line items for this SupplierPart, limited to purchase orders that are open / outstanding.""" diff --git a/src/backend/InvenTree/config_template.yaml b/src/backend/InvenTree/config_template.yaml index ab464a32d7..d1b13a5e1c 100644 --- a/src/backend/InvenTree/config_template.yaml +++ b/src/backend/InvenTree/config_template.yaml @@ -46,10 +46,7 @@ language: en-us timezone: UTC # Base URL for the InvenTree server (or use the environment variable INVENTREE_SITE_URL) -# site_url: 'http://localhost:8000' - -# Base currency code (or use env var INVENTREE_BASE_CURRENCY) -base_currency: USD +site_url: 'http://localhost:8000' # Add new user on first startup by either adding values here or from a file #admin_user: admin @@ -57,17 +54,6 @@ base_currency: USD #admin_password: inventree #admin_password_file: '/etc/inventree/admin_password.txt' -# List of currencies supported by default. Add other currencies here to allow use in InvenTree -currencies: - - AUD - - CAD - - CNY - - EUR - - GBP - - JPY - - NZD - - USD - # Email backend configuration # Ref: https://docs.djangoproject.com/en/dev/topics/email/ # Alternatively, these options can all be set using environment variables, diff --git a/src/backend/InvenTree/order/api.py b/src/backend/InvenTree/order/api.py index 7db194469f..b2dd13483b 100644 --- a/src/backend/InvenTree/order/api.py +++ b/src/backend/InvenTree/order/api.py @@ -3,6 +3,7 @@ from decimal import Decimal from typing import cast +from django.conf import settings from django.contrib.auth import authenticate, login from django.db import transaction from django.db.models import F, Q @@ -17,7 +18,6 @@ from rest_framework.exceptions import ValidationError from rest_framework.response import Response import common.models as common_models -from common.settings import settings from company.models import SupplierPart from generic.states.api import StatusView from InvenTree.api import ( diff --git a/src/backend/InvenTree/order/migrations/0038_auto_20201112_1737.py b/src/backend/InvenTree/order/migrations/0038_auto_20201112_1737.py index 121bdbe64f..efd157f5b6 100644 --- a/src/backend/InvenTree/order/migrations/0038_auto_20201112_1737.py +++ b/src/backend/InvenTree/order/migrations/0038_auto_20201112_1737.py @@ -2,6 +2,7 @@ from django.db import migrations import djmoney.models.fields +import common.currency import common.settings @@ -16,11 +17,11 @@ class Migration(migrations.Migration): migrations.AddField( model_name='purchaseorderlineitem', name='purchase_price', - field=djmoney.models.fields.MoneyField(decimal_places=4, default_currency=common.settings.currency_code_default(), help_text='Unit purchase price', max_digits=19, null=True, verbose_name='Purchase Price'), + field=djmoney.models.fields.MoneyField(decimal_places=4, default_currency=common.currency.currency_code_default(), help_text='Unit purchase price', max_digits=19, null=True, verbose_name='Purchase Price'), ), migrations.AddField( model_name='purchaseorderlineitem', name='purchase_price_currency', - field=djmoney.models.fields.CurrencyField(choices=common.settings.currency_code_mappings(), default=common.settings.currency_code_default(), editable=False, max_length=3), + field=djmoney.models.fields.CurrencyField(choices=common.currency.currency_code_mappings(), default=common.currency.currency_code_default(), editable=False, max_length=3), ), ] diff --git a/src/backend/InvenTree/order/migrations/0039_auto_20201112_2203.py b/src/backend/InvenTree/order/migrations/0039_auto_20201112_2203.py index 52b51b6ca9..0035f9a949 100644 --- a/src/backend/InvenTree/order/migrations/0039_auto_20201112_2203.py +++ b/src/backend/InvenTree/order/migrations/0039_auto_20201112_2203.py @@ -2,6 +2,7 @@ from django.db import migrations import djmoney.models.fields +import common.currency import common.settings @@ -15,6 +16,6 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='purchaseorderlineitem', name='purchase_price', - field=djmoney.models.fields.MoneyField(blank=True, decimal_places=4, default_currency=common.settings.currency_code_default(), help_text='Unit purchase price', max_digits=19, null=True, verbose_name='Purchase Price'), + field=djmoney.models.fields.MoneyField(blank=True, decimal_places=4, default_currency=common.currency.currency_code_default(), help_text='Unit purchase price', max_digits=19, null=True, verbose_name='Purchase Price'), ), ] diff --git a/src/backend/InvenTree/order/migrations/0045_auto_20210504_1946.py b/src/backend/InvenTree/order/migrations/0045_auto_20210504_1946.py index c1e5f23df2..4faadf2715 100644 --- a/src/backend/InvenTree/order/migrations/0045_auto_20210504_1946.py +++ b/src/backend/InvenTree/order/migrations/0045_auto_20210504_1946.py @@ -1,6 +1,7 @@ # Generated by Django 3.2 on 2021-05-04 19:46 from django.db import migrations +import common.currency import common.settings import djmoney.models.fields @@ -15,11 +16,11 @@ class Migration(migrations.Migration): migrations.AddField( model_name='salesorderlineitem', name='sale_price', - field=djmoney.models.fields.MoneyField(blank=True, decimal_places=4, default_currency=common.settings.currency_code_default(), help_text='Unit sale price', max_digits=19, null=True, verbose_name='Sale Price'), + field=djmoney.models.fields.MoneyField(blank=True, decimal_places=4, default_currency=common.currency.currency_code_default(), help_text='Unit sale price', max_digits=19, null=True, verbose_name='Sale Price'), ), migrations.AddField( model_name='salesorderlineitem', name='sale_price_currency', - field=djmoney.models.fields.CurrencyField(choices=common.settings.currency_code_mappings(), default=common.settings.currency_code_default(), editable=False, max_length=3), + field=djmoney.models.fields.CurrencyField(choices=common.currency.currency_code_mappings(), default=common.currency.currency_code_default(), editable=False, max_length=3), ), ] diff --git a/src/backend/InvenTree/order/migrations/0079_auto_20230304_0904.py b/src/backend/InvenTree/order/migrations/0079_auto_20230304_0904.py index 0d1dd8041c..ffc27b6b8e 100644 --- a/src/backend/InvenTree/order/migrations/0079_auto_20230304_0904.py +++ b/src/backend/InvenTree/order/migrations/0079_auto_20230304_0904.py @@ -8,7 +8,7 @@ from djmoney.contrib.exchange.exceptions import MissingRate from djmoney.contrib.exchange.models import convert_money from djmoney.money import Money -from common.settings import currency_code_default +from common.currency import currency_code_default logger = logging.getLogger('inventree') diff --git a/src/backend/InvenTree/order/models.py b/src/backend/InvenTree/order/models.py index 1f60121efa..36dad6aa9f 100644 --- a/src/backend/InvenTree/order/models.py +++ b/src/backend/InvenTree/order/models.py @@ -33,8 +33,8 @@ import order.validators import report.mixins import stock.models import users.models as UserModels +from common.currency import currency_code_default from common.notifications import InvenTreeNotificationBodies -from common.settings import currency_code_default from company.models import Address, Company, Contact, SupplierPart from generic.states import StateTransitionMixin from InvenTree.exceptions import log_error diff --git a/src/backend/InvenTree/order/test_api.py b/src/backend/InvenTree/order/test_api.py index 6e59e9a600..73518b7ac8 100644 --- a/src/backend/InvenTree/order/test_api.py +++ b/src/backend/InvenTree/order/test_api.py @@ -13,8 +13,8 @@ from djmoney.money import Money from icalendar import Calendar from rest_framework import status +from common.currency import currency_codes from common.models import InvenTreeSetting -from common.settings import currency_codes from company.models import Company, SupplierPart, SupplierPriceBreak from InvenTree.unit_test import InvenTreeAPITestCase from order import models diff --git a/src/backend/InvenTree/part/migrations/0055_auto_20201110_1001.py b/src/backend/InvenTree/part/migrations/0055_auto_20201110_1001.py index 911766ad6d..8bf472e4cb 100644 --- a/src/backend/InvenTree/part/migrations/0055_auto_20201110_1001.py +++ b/src/backend/InvenTree/part/migrations/0055_auto_20201110_1001.py @@ -2,7 +2,7 @@ from django.db import migrations import djmoney.models.fields -import common.settings +import common.currency class Migration(migrations.Migration): @@ -16,11 +16,11 @@ class Migration(migrations.Migration): migrations.AddField( model_name='partsellpricebreak', name='price', - field=djmoney.models.fields.MoneyField(decimal_places=4, default_currency=common.settings.currency_code_default(), help_text='Unit price at specified quantity', max_digits=19, null=True, verbose_name='Price'), + field=djmoney.models.fields.MoneyField(decimal_places=4, default_currency=common.currency.currency_code_default(), help_text='Unit price at specified quantity', max_digits=19, null=True, verbose_name='Price'), ), migrations.AddField( model_name='partsellpricebreak', name='price_currency', - field=djmoney.models.fields.CurrencyField(choices=common.settings.currency_code_mappings(), default=common.settings.currency_code_default(), editable=False, max_length=3), + field=djmoney.models.fields.CurrencyField(choices=common.currency.currency_code_mappings(), default=common.currency.currency_code_default(), editable=False, max_length=3), ), ] diff --git a/src/backend/InvenTree/part/migrations/0067_partinternalpricebreak.py b/src/backend/InvenTree/part/migrations/0067_partinternalpricebreak.py index f0e8561730..d8dc4f3c56 100644 --- a/src/backend/InvenTree/part/migrations/0067_partinternalpricebreak.py +++ b/src/backend/InvenTree/part/migrations/0067_partinternalpricebreak.py @@ -2,6 +2,7 @@ import InvenTree.fields import django.core.validators +import common.currency import common.settings from django.db import migrations, models import django.db.models.deletion @@ -20,8 +21,8 @@ class Migration(migrations.Migration): fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('quantity', InvenTree.fields.RoundingDecimalField(decimal_places=5, default=1, help_text='Price break quantity', max_digits=15, validators=[django.core.validators.MinValueValidator(1)], verbose_name='Quantity')), - ('price_currency', djmoney.models.fields.CurrencyField(choices=common.settings.currency_code_mappings(), default=common.settings.currency_code_default(), editable=False, max_length=3)), - ('price', djmoney.models.fields.MoneyField(decimal_places=4, default_currency=common.settings.currency_code_default(), help_text='Unit price at specified quantity', max_digits=19, null=True, verbose_name='Price')), + ('price_currency', djmoney.models.fields.CurrencyField(choices=common.currency.currency_code_mappings(), default=common.currency.currency_code_default(), editable=False, max_length=3)), + ('price', djmoney.models.fields.MoneyField(decimal_places=4, default_currency=common.currency.currency_code_default(), help_text='Unit price at specified quantity', max_digits=19, null=True, verbose_name='Price')), ('part', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='internalpricebreaks', to='part.part', verbose_name='Part')), ], options={ diff --git a/src/backend/InvenTree/part/migrations/0089_auto_20221112_0128.py b/src/backend/InvenTree/part/migrations/0089_auto_20221112_0128.py index 7d25a1f558..865153ffe2 100644 --- a/src/backend/InvenTree/part/migrations/0089_auto_20221112_0128.py +++ b/src/backend/InvenTree/part/migrations/0089_auto_20221112_0128.py @@ -1,13 +1,14 @@ # Generated by Django 3.2.16 on 2022-11-12 01:28 -import InvenTree.fields -import common.settings import django.core.validators from django.db import migrations, models import django.db.models.deletion import djmoney.models.fields import djmoney.models.validators +import InvenTree.fields +import common.currency +import common.settings class Migration(migrations.Migration): @@ -35,7 +36,7 @@ class Migration(migrations.Migration): name='PartPricing', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('currency', models.CharField(choices=common.settings.currency_code_mappings(), default=common.settings.currency_code_default, help_text='Currency used to cache pricing calculations', max_length=10, verbose_name='Currency')), + ('currency', models.CharField(choices=common.currency.currency_code_mappings(), default=common.currency.currency_code_default, help_text='Currency used to cache pricing calculations', max_length=10, verbose_name='Currency')), ('updated', models.DateTimeField(auto_now=True, help_text='Timestamp of last pricing update', verbose_name='Updated')), ('scheduled_for_update', models.BooleanField(default=False)), ('bom_cost_min_currency', djmoney.models.fields.CurrencyField(choices=[], default='', editable=False, max_length=3)), diff --git a/src/backend/InvenTree/part/models.py b/src/backend/InvenTree/part/models.py index 5d1b9cb82c..cd0282b908 100644 --- a/src/backend/InvenTree/part/models.py +++ b/src/backend/InvenTree/part/models.py @@ -33,6 +33,7 @@ from mptt.models import MPTTModel, TreeForeignKey from stdimage.models import StdImageField from taggit.managers import TaggableManager +import common.currency import common.models import common.settings import InvenTree.conversion @@ -47,8 +48,8 @@ import report.mixins import users.models from build import models as BuildModels from build.status_codes import BuildStatusGroups +from common.currency import currency_code_default from common.models import InvenTreeSetting -from common.settings import currency_code_default from company.models import SupplierPart from InvenTree import helpers, validators from InvenTree.fields import InvenTreeURLField @@ -2018,7 +2019,7 @@ class Part( help_text=_('Sell multiple'), ) - get_price = common.models.get_price + get_price = common.currency.get_price @property def has_price_breaks(self): @@ -2050,7 +2051,7 @@ class Part( def get_internal_price(self, quantity, moq=True, multiples=True, currency=None): """Return the internal price of this Part at the specified quantity.""" - return common.models.get_price( + return common.currency.get_price( self, quantity, moq, multiples, currency, break_name='internal_price_breaks' ) @@ -2646,7 +2647,7 @@ class PartPricing(common.models.MetaMixin): # Short circuit - no further operations required return - currency_code = common.settings.currency_code_default() + currency_code = common.currency.currency_code_default() cumulative_min = Money(0, currency_code) cumulative_max = Money(0, currency_code) @@ -3025,7 +3026,7 @@ class PartPricing(common.models.MetaMixin): max_length=10, verbose_name=_('Currency'), help_text=_('Currency used to cache pricing calculations'), - choices=common.settings.currency_code_mappings(), + choices=common.currency.currency_code_mappings(), ) scheduled_for_update = models.BooleanField(default=False) diff --git a/src/backend/InvenTree/part/serializers.py b/src/backend/InvenTree/part/serializers.py index 9ddf1447db..5c454293bd 100644 --- a/src/backend/InvenTree/part/serializers.py +++ b/src/backend/InvenTree/part/serializers.py @@ -21,6 +21,7 @@ from rest_framework import serializers from sql_util.utils import SubqueryCount, SubquerySum from taggit.serializers import TagListSerializerField +import common.currency import common.models import common.settings import company.models @@ -1286,7 +1287,7 @@ class PartPricingSerializer(InvenTree.serializers.InvenTreeModelSerializer): label=_('Minimum price currency'), read_only=False, required=False, - choices=common.settings.currency_code_mappings(), + choices=common.currency.currency_code_mappings(), ) override_max = InvenTree.serializers.InvenTreeMoneySerializer( @@ -1301,7 +1302,7 @@ class PartPricingSerializer(InvenTree.serializers.InvenTreeModelSerializer): label=_('Maximum price currency'), read_only=False, required=False, - choices=common.settings.currency_code_mappings(), + choices=common.currency.currency_code_mappings(), ) overall_min = InvenTree.serializers.InvenTreeMoneySerializer( @@ -1342,7 +1343,7 @@ class PartPricingSerializer(InvenTree.serializers.InvenTreeModelSerializer): override_min = data.get('override_min', None) override_max = data.get('override_max', None) - default_currency = common.settings.currency_code_default() + default_currency = common.currency.currency_code_default() if override_min is not None and override_max is not None: try: diff --git a/src/backend/InvenTree/part/stocktake.py b/src/backend/InvenTree/part/stocktake.py index 1bdd41f977..3705d84ae0 100644 --- a/src/backend/InvenTree/part/stocktake.py +++ b/src/backend/InvenTree/part/stocktake.py @@ -13,6 +13,7 @@ import tablib from djmoney.contrib.exchange.models import convert_money from djmoney.money import Money +import common.currency import common.models import InvenTree.helpers import part.models @@ -67,7 +68,7 @@ def perform_stocktake( pricing.update_pricing(cascade=False) pricing.refresh_from_db() - base_currency = common.settings.currency_code_default() + base_currency = common.currency.currency_code_default() # Keep track of total quantity and cost for this part total_quantity = 0 @@ -210,7 +211,7 @@ def generate_stocktake_report(**kwargs): logger.info('Generating new stocktake report for %s parts', n_parts) - base_currency = common.settings.currency_code_default() + base_currency = common.currency.currency_code_default() # Construct an initial dataset for the stocktake report dataset = tablib.Dataset( diff --git a/src/backend/InvenTree/part/tasks.py b/src/backend/InvenTree/part/tasks.py index 51a2d63547..768be1a440 100644 --- a/src/backend/InvenTree/part/tasks.py +++ b/src/backend/InvenTree/part/tasks.py @@ -8,6 +8,7 @@ from datetime import datetime, timedelta from django.core.exceptions import ValidationError from django.utils.translation import gettext_lazy as _ +import common.currency import common.models import common.notifications import common.settings @@ -110,7 +111,7 @@ def check_missing_pricing(limit=250): pp.schedule_for_update() # Find any pricing data which is in the wrong currency - currency = common.settings.currency_code_default() + currency = common.currency.currency_code_default() results = part.models.PartPricing.objects.exclude(currency=currency) if results.count() > 0: diff --git a/src/backend/InvenTree/part/test_pricing.py b/src/backend/InvenTree/part/test_pricing.py index a9572b2f16..2cdc4c54a6 100644 --- a/src/backend/InvenTree/part/test_pricing.py +++ b/src/backend/InvenTree/part/test_pricing.py @@ -5,6 +5,7 @@ from django.core.exceptions import ObjectDoesNotExist from djmoney.contrib.exchange.models import convert_money from djmoney.money import Money +import common.currency import common.models import common.settings import company.models @@ -179,7 +180,7 @@ class PartPricingTests(InvenTreeTestCase): self.assertIsNone(pricing.internal_cost_min) self.assertIsNone(pricing.internal_cost_max) - currency = common.settings.currency_code_default() + currency = common.currency.currency_code_default() for ii in range(5): # Let's add some internal price breaks diff --git a/src/backend/InvenTree/stock/migrations/0053_auto_20201110_0513.py b/src/backend/InvenTree/stock/migrations/0053_auto_20201110_0513.py index 0b0d6cbc5a..a7106f8af2 100644 --- a/src/backend/InvenTree/stock/migrations/0053_auto_20201110_0513.py +++ b/src/backend/InvenTree/stock/migrations/0053_auto_20201110_0513.py @@ -2,7 +2,7 @@ from django.db import migrations import djmoney.models.fields -import common.settings +import common.currency class Migration(migrations.Migration): @@ -16,11 +16,11 @@ class Migration(migrations.Migration): migrations.AddField( model_name='stockitem', name='purchase_price', - field=djmoney.models.fields.MoneyField(decimal_places=4, default_currency=common.settings.currency_code_default(), help_text='Single unit purchase price at time of purchase', max_digits=19, null=True, verbose_name='Purchase Price'), + field=djmoney.models.fields.MoneyField(decimal_places=4, default_currency=common.currency.currency_code_default(), help_text='Single unit purchase price at time of purchase', max_digits=19, null=True, verbose_name='Purchase Price'), ), migrations.AddField( model_name='stockitem', name='purchase_price_currency', - field=djmoney.models.fields.CurrencyField(choices=common.settings.all_currency_codes(), default=common.settings.currency_code_default(), editable=False, max_length=3), + field=djmoney.models.fields.CurrencyField(choices=common.currency.all_currency_codes(), default=common.currency.currency_code_default(), editable=False, max_length=3), ), ] diff --git a/src/backend/InvenTree/stock/migrations/0055_auto_20201117_1453.py b/src/backend/InvenTree/stock/migrations/0055_auto_20201117_1453.py index 12ad90cc0a..8aebb0c7c1 100644 --- a/src/backend/InvenTree/stock/migrations/0055_auto_20201117_1453.py +++ b/src/backend/InvenTree/stock/migrations/0055_auto_20201117_1453.py @@ -2,7 +2,7 @@ from django.db import migrations import djmoney.models.fields -import common.settings +import common.currency class Migration(migrations.Migration): @@ -15,6 +15,6 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='stockitem', name='purchase_price', - field=djmoney.models.fields.MoneyField(blank=True, decimal_places=4, default_currency=common.settings.currency_code_default(), help_text='Single unit purchase price at time of purchase', max_digits=19, null=True, verbose_name='Purchase Price'), + field=djmoney.models.fields.MoneyField(blank=True, decimal_places=4, default_currency=common.currency.currency_code_default(), help_text='Single unit purchase price at time of purchase', max_digits=19, null=True, verbose_name='Purchase Price'), ), ] diff --git a/src/backend/InvenTree/templates/InvenTree/settings/pricing.html b/src/backend/InvenTree/templates/InvenTree/settings/pricing.html index cba117ad14..8e99fbc983 100644 --- a/src/backend/InvenTree/templates/InvenTree/settings/pricing.html +++ b/src/backend/InvenTree/templates/InvenTree/settings/pricing.html @@ -12,6 +12,7 @@ {% include "InvenTree/settings/setting.html" with key="INVENTREE_DEFAULT_CURRENCY" icon="fa-globe" %} + {% include "InvenTree/settings/setting.html" with key="CURRENCY_CODES" %} {% include "InvenTree/settings/setting.html" with key="PART_INTERNAL_PRICE" %} {% include "InvenTree/settings/setting.html" with key="PART_BOM_USE_INTERNAL_PRICE" %} {% include "InvenTree/settings/setting.html" with key="PRICING_DECIMAL_PLACES_MIN" icon='fa-dollar-sign' %} diff --git a/src/frontend/src/pages/Index/Settings/SystemSettings.tsx b/src/frontend/src/pages/Index/Settings/SystemSettings.tsx index 7feb204820..53f2e18227 100644 --- a/src/frontend/src/pages/Index/Settings/SystemSettings.tsx +++ b/src/frontend/src/pages/Index/Settings/SystemSettings.tsx @@ -109,6 +109,7 @@ export default function SystemSettings() {