mirror of
https://github.com/inventree/InvenTree.git
synced 2025-07-19 19:26:29 +00:00
Currency setting (#7390)
* Add new global setting for currency options - Moving away from external configuration - Refactor currency support code into new file * Refactoring - Move functions into currency.py * Limit choices for default currency * Improve validation * Adds data migration for existing currency selection * Docs updates * Remove currency config from external settings * bump api version * Add debug message * Add unit tests * Fix after_change_currency func * Fix after_change_currency func * Revert change to after_chance_currency * Revert other change
This commit is contained in:
docs/docs
src
backend
InvenTree
InvenTree
api_version.pyapps.pyexchange.pyfields.pyhelpers.pyserializers.pysettings.pytasks.py
templatetags
tests.pyviews.pycommon
company
config_template.yamlorder
api.py
migrations
0038_auto_20201112_1737.py0039_auto_20201112_2203.py0045_auto_20210504_1946.py0079_auto_20230304_0904.py
models.pytest_api.pypart
stock
templates
InvenTree
settings
frontend
src
pages
Index
Settings
@@ -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
|
||||
|
||||
|
@@ -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
|
||||
|
@@ -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')
|
||||
|
||||
|
@@ -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:
|
||||
|
@@ -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
|
||||
|
||||
|
@@ -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
|
||||
|
||||
|
||||
|
@@ -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'
|
||||
|
||||
|
@@ -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!
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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')
|
||||
|
||||
|
226
src/backend/InvenTree/common/currency.py
Normal file
226
src/backend/InvenTree/common/currency.py
Normal file
@@ -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
|
@@ -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():
|
||||
|
@@ -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)
|
||||
]
|
@@ -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."""
|
||||
|
||||
|
@@ -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."""
|
||||
|
@@ -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')
|
||||
|
@@ -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),
|
||||
),
|
||||
]
|
||||
|
@@ -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'),
|
||||
),
|
||||
]
|
||||
|
@@ -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."""
|
||||
|
@@ -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,
|
||||
|
@@ -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 (
|
||||
|
@@ -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),
|
||||
),
|
||||
]
|
||||
|
@@ -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'),
|
||||
),
|
||||
]
|
||||
|
@@ -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),
|
||||
),
|
||||
]
|
||||
|
@@ -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')
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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),
|
||||
),
|
||||
]
|
||||
|
@@ -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={
|
||||
|
@@ -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)),
|
||||
|
@@ -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)
|
||||
|
@@ -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:
|
||||
|
@@ -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(
|
||||
|
@@ -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:
|
||||
|
@@ -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
|
||||
|
@@ -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),
|
||||
),
|
||||
]
|
||||
|
@@ -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'),
|
||||
),
|
||||
]
|
||||
|
@@ -12,6 +12,7 @@
|
||||
<table class='table table-striped table-condensed'>
|
||||
<tbody>
|
||||
{% 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' %}
|
||||
|
Reference in New Issue
Block a user