mirror of
				https://github.com/inventree/InvenTree.git
				synced 2025-11-03 22:55:43 +00:00 
			
		
		
		
	Merge remote-tracking branch 'inventree/master'
This commit is contained in:
		@@ -4,7 +4,6 @@ import logging
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
from django.apps import AppConfig
 | 
					from django.apps import AppConfig
 | 
				
			||||||
from django.core.exceptions import AppRegistryNotReady
 | 
					from django.core.exceptions import AppRegistryNotReady
 | 
				
			||||||
from django.conf import settings
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
from InvenTree.ready import isInTestMode, canAppAccessDatabase
 | 
					from InvenTree.ready import isInTestMode, canAppAccessDatabase
 | 
				
			||||||
import InvenTree.tasks
 | 
					import InvenTree.tasks
 | 
				
			||||||
@@ -66,10 +65,11 @@ class InvenTreeConfig(AppConfig):
 | 
				
			|||||||
            from djmoney.contrib.exchange.models import ExchangeBackend
 | 
					            from djmoney.contrib.exchange.models import ExchangeBackend
 | 
				
			||||||
            from datetime import datetime, timedelta
 | 
					            from datetime import datetime, timedelta
 | 
				
			||||||
            from InvenTree.tasks import update_exchange_rates
 | 
					            from InvenTree.tasks import update_exchange_rates
 | 
				
			||||||
 | 
					            from common.settings import currency_code_default
 | 
				
			||||||
        except AppRegistryNotReady:
 | 
					        except AppRegistryNotReady:
 | 
				
			||||||
            pass
 | 
					            pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        base_currency = settings.BASE_CURRENCY
 | 
					        base_currency = currency_code_default()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        update = False
 | 
					        update = False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,4 @@
 | 
				
			|||||||
from django.conf import settings as inventree_settings
 | 
					from common.settings import currency_code_default, currency_codes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from djmoney.contrib.exchange.backends.base import SimpleExchangeBackend
 | 
					from djmoney.contrib.exchange.backends.base import SimpleExchangeBackend
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -22,8 +22,8 @@ class InvenTreeExchange(SimpleExchangeBackend):
 | 
				
			|||||||
        return {
 | 
					        return {
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def update_rates(self, base_currency=inventree_settings.BASE_CURRENCY):
 | 
					    def update_rates(self, base_currency=currency_code_default()):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        symbols = ','.join(inventree_settings.CURRENCIES)
 | 
					        symbols = ','.join(currency_codes())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        super().update_rates(base=base_currency, symbols=symbols)
 | 
					        super().update_rates(base=base_currency, symbols=symbols)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,6 +2,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
# -*- coding: utf-8 -*-
 | 
					# -*- coding: utf-8 -*-
 | 
				
			||||||
from __future__ import unicode_literals
 | 
					from __future__ import unicode_literals
 | 
				
			||||||
 | 
					import sys
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from .validators import allowable_url_schemes
 | 
					from .validators import allowable_url_schemes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -13,8 +14,11 @@ from django.core import validators
 | 
				
			|||||||
from django import forms
 | 
					from django import forms
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from decimal import Decimal
 | 
					from decimal import Decimal
 | 
				
			||||||
 | 
					from djmoney.models.fields import MoneyField as ModelMoneyField
 | 
				
			||||||
 | 
					from djmoney.forms.fields import MoneyField
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import InvenTree.helpers
 | 
					import InvenTree.helpers
 | 
				
			||||||
 | 
					import common.settings
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class InvenTreeURLFormField(FormURLField):
 | 
					class InvenTreeURLFormField(FormURLField):
 | 
				
			||||||
@@ -34,6 +38,42 @@ class InvenTreeURLField(models.URLField):
 | 
				
			|||||||
        })
 | 
					        })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def money_kwargs():
 | 
				
			||||||
 | 
					    """ returns the database settings for MoneyFields """
 | 
				
			||||||
 | 
					    kwargs = {}
 | 
				
			||||||
 | 
					    kwargs['currency_choices'] = common.settings.currency_code_mappings()
 | 
				
			||||||
 | 
					    kwargs['default_currency'] = common.settings.currency_code_default
 | 
				
			||||||
 | 
					    return kwargs
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class InvenTreeModelMoneyField(ModelMoneyField):
 | 
				
			||||||
 | 
					    """ custom MoneyField for clean migrations while using dynamic currency settings """
 | 
				
			||||||
 | 
					    def __init__(self, **kwargs):
 | 
				
			||||||
 | 
					        # detect if creating migration
 | 
				
			||||||
 | 
					        if 'makemigrations' in sys.argv:
 | 
				
			||||||
 | 
					            # remove currency information for a clean migration
 | 
				
			||||||
 | 
					            kwargs['default_currency'] = ''
 | 
				
			||||||
 | 
					            kwargs['currency_choices'] = []
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            # set defaults
 | 
				
			||||||
 | 
					            kwargs.update(money_kwargs())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        super().__init__(**kwargs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def formfield(self, **kwargs):
 | 
				
			||||||
 | 
					        """ override form class to use own function """
 | 
				
			||||||
 | 
					        kwargs['form_class'] = InvenTreeMoneyField
 | 
				
			||||||
 | 
					        return super().formfield(**kwargs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class InvenTreeMoneyField(MoneyField):
 | 
				
			||||||
 | 
					    """ custom MoneyField for clean migrations while using dynamic currency settings """
 | 
				
			||||||
 | 
					    def __init__(self, *args, **kwargs):
 | 
				
			||||||
 | 
					        # override initial values with the real info from database
 | 
				
			||||||
 | 
					        kwargs.update(money_kwargs())
 | 
				
			||||||
 | 
					        super().__init__(*args, **kwargs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class DatePickerFormField(forms.DateField):
 | 
					class DatePickerFormField(forms.DateField):
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    Custom date-picker field
 | 
					    Custom date-picker field
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,8 +7,6 @@ from __future__ import unicode_literals
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import os
 | 
					import os
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from collections import OrderedDict
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from django.conf import settings
 | 
					from django.conf import settings
 | 
				
			||||||
from django.contrib.auth.models import User
 | 
					from django.contrib.auth.models import User
 | 
				
			||||||
from django.core.exceptions import ValidationError as DjangoValidationError
 | 
					from django.core.exceptions import ValidationError as DjangoValidationError
 | 
				
			||||||
@@ -46,16 +44,13 @@ class InvenTreeModelSerializer(serializers.ModelSerializer):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    def __init__(self, instance=None, data=empty, **kwargs):
 | 
					    def __init__(self, instance=None, data=empty, **kwargs):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.instance = instance
 | 
					        # self.instance = instance
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # If instance is None, we are creating a new instance
 | 
					        # If instance is None, we are creating a new instance
 | 
				
			||||||
        if instance is None:
 | 
					        if instance is None and data is not empty:
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            if data is empty:
 | 
					            # Required to side-step immutability of a QueryDict
 | 
				
			||||||
                data = OrderedDict()
 | 
					            data = data.copy()
 | 
				
			||||||
            else:
 | 
					 | 
				
			||||||
                # Required to side-step immutability of a QueryDict
 | 
					 | 
				
			||||||
                data = data.copy()
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            # Add missing fields which have default values
 | 
					            # Add missing fields which have default values
 | 
				
			||||||
            ModelClass = self.Meta.model
 | 
					            ModelClass = self.Meta.model
 | 
				
			||||||
@@ -64,6 +59,11 @@ class InvenTreeModelSerializer(serializers.ModelSerializer):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            for field_name, field in fields.fields.items():
 | 
					            for field_name, field in fields.fields.items():
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                """
 | 
				
			||||||
 | 
					                Update the field IF (and ONLY IF):
 | 
				
			||||||
 | 
					                - The field has a specified default value
 | 
				
			||||||
 | 
					                - The field does not already have a value set
 | 
				
			||||||
 | 
					                """
 | 
				
			||||||
                if field.has_default() and field_name not in data:
 | 
					                if field.has_default() and field_name not in data:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    value = field.default
 | 
					                    value = field.default
 | 
				
			||||||
@@ -85,7 +85,7 @@ class InvenTreeModelSerializer(serializers.ModelSerializer):
 | 
				
			|||||||
        Use the 'default' values specified by the django model definition
 | 
					        Use the 'default' values specified by the django model definition
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        initials = super().get_initial()
 | 
					        initials = super().get_initial().copy()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Are we creating a new instance?
 | 
					        # Are we creating a new instance?
 | 
				
			||||||
        if self.instance is None:
 | 
					        if self.instance is None:
 | 
				
			||||||
@@ -111,7 +111,8 @@ class InvenTreeModelSerializer(serializers.ModelSerializer):
 | 
				
			|||||||
        return initials
 | 
					        return initials
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def run_validation(self, data=empty):
 | 
					    def run_validation(self, data=empty):
 | 
				
			||||||
        """ Perform serializer validation.
 | 
					        """
 | 
				
			||||||
 | 
					        Perform serializer validation.
 | 
				
			||||||
        In addition to running validators on the serializer fields,
 | 
					        In addition to running validators on the serializer fields,
 | 
				
			||||||
        this class ensures that the underlying model is also validated.
 | 
					        this class ensures that the underlying model is also validated.
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -522,10 +522,6 @@ for currency in CURRENCIES:
 | 
				
			|||||||
        print(f"Currency code '{currency}' is not supported")
 | 
					        print(f"Currency code '{currency}' is not supported")
 | 
				
			||||||
        sys.exit(1)
 | 
					        sys.exit(1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
BASE_CURRENCY = get_setting(
 | 
					 | 
				
			||||||
    'INVENTREE_BASE_CURRENCY',
 | 
					 | 
				
			||||||
    CONFIG.get('base_currency', 'USD')
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Custom currency exchange backend
 | 
					# Custom currency exchange backend
 | 
				
			||||||
EXCHANGE_BACKEND = 'InvenTree.exchange.InvenTreeExchange'
 | 
					EXCHANGE_BACKEND = 'InvenTree.exchange.InvenTreeExchange'
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -170,7 +170,7 @@ def update_exchange_rates():
 | 
				
			|||||||
    try:
 | 
					    try:
 | 
				
			||||||
        from InvenTree.exchange import InvenTreeExchange
 | 
					        from InvenTree.exchange import InvenTreeExchange
 | 
				
			||||||
        from djmoney.contrib.exchange.models import ExchangeBackend, Rate
 | 
					        from djmoney.contrib.exchange.models import ExchangeBackend, Rate
 | 
				
			||||||
        from django.conf import settings
 | 
					        from common.settings import currency_code_default, currency_codes
 | 
				
			||||||
    except AppRegistryNotReady:
 | 
					    except AppRegistryNotReady:
 | 
				
			||||||
        # Apps not yet loaded!
 | 
					        # Apps not yet loaded!
 | 
				
			||||||
        logger.info("Could not perform 'update_exchange_rates' - App registry not ready")
 | 
					        logger.info("Could not perform 'update_exchange_rates' - App registry not ready")
 | 
				
			||||||
@@ -192,14 +192,14 @@ def update_exchange_rates():
 | 
				
			|||||||
    backend = InvenTreeExchange()
 | 
					    backend = InvenTreeExchange()
 | 
				
			||||||
    print(f"Updating exchange rates from {backend.url}")
 | 
					    print(f"Updating exchange rates from {backend.url}")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    base = settings.BASE_CURRENCY
 | 
					    base = currency_code_default()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    print(f"Using base currency '{base}'")
 | 
					    print(f"Using base currency '{base}'")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    backend.update_rates(base_currency=base)
 | 
					    backend.update_rates(base_currency=base)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Remove any exchange rates which are not in the provided currencies
 | 
					    # Remove any exchange rates which are not in the provided currencies
 | 
				
			||||||
    Rate.objects.filter(backend="InvenTreeExchange").exclude(currency__in=settings.CURRENCIES).delete()
 | 
					    Rate.objects.filter(backend="InvenTreeExchange").exclude(currency__in=currency_codes()).delete()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def send_email(subject, body, recipients, from_email=None):
 | 
					def send_email(subject, body, recipients, from_email=None):
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,6 +2,11 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
from rest_framework import status
 | 
					from rest_framework import status
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from django.test import TestCase
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from django.contrib.auth import get_user_model
 | 
				
			||||||
 | 
					from django.contrib.auth.models import Group
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from django.urls import reverse
 | 
					from django.urls import reverse
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from InvenTree.api_tester import InvenTreeAPITestCase
 | 
					from InvenTree.api_tester import InvenTreeAPITestCase
 | 
				
			||||||
@@ -11,6 +16,87 @@ from users.models import RuleSet
 | 
				
			|||||||
from base64 import b64encode
 | 
					from base64 import b64encode
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class HTMLAPITests(TestCase):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Test that we can access the REST API endpoints via the HTML interface.
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    History: Discovered on 2021-06-28 a bug in InvenTreeModelSerializer,
 | 
				
			||||||
 | 
					    which raised an AssertionError when using the HTML API interface,
 | 
				
			||||||
 | 
					    while the regular JSON interface continued to work as expected.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def setUp(self):
 | 
				
			||||||
 | 
					        super().setUp()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Create a user
 | 
				
			||||||
 | 
					        user = get_user_model()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.user = user.objects.create_user(
 | 
				
			||||||
 | 
					            username='username',
 | 
				
			||||||
 | 
					            email='user@email.com',
 | 
				
			||||||
 | 
					            password='password'
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Put the user into a group with the correct permissions
 | 
				
			||||||
 | 
					        group = Group.objects.create(name='mygroup')
 | 
				
			||||||
 | 
					        self.user.groups.add(group)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Give the group *all* the permissions!
 | 
				
			||||||
 | 
					        for rule in group.rule_sets.all():
 | 
				
			||||||
 | 
					            rule.can_view = True
 | 
				
			||||||
 | 
					            rule.can_change = True
 | 
				
			||||||
 | 
					            rule.can_add = True
 | 
				
			||||||
 | 
					            rule.can_delete = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            rule.save()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.client.login(username='username', password='password')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_part_api(self):
 | 
				
			||||||
 | 
					        url = reverse('api-part-list')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Check JSON response
 | 
				
			||||||
 | 
					        response = self.client.get(url, HTTP_ACCEPT='application/json')
 | 
				
			||||||
 | 
					        self.assertEqual(response.status_code, 200)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Check HTTP response
 | 
				
			||||||
 | 
					        response = self.client.get(url, HTTP_ACCEPT='text/html')
 | 
				
			||||||
 | 
					        self.assertEqual(response.status_code, 200)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_build_api(self):
 | 
				
			||||||
 | 
					        url = reverse('api-build-list')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Check JSON response
 | 
				
			||||||
 | 
					        response = self.client.get(url, HTTP_ACCEPT='application/json')
 | 
				
			||||||
 | 
					        self.assertEqual(response.status_code, 200)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Check HTTP response
 | 
				
			||||||
 | 
					        response = self.client.get(url, HTTP_ACCEPT='text/html')
 | 
				
			||||||
 | 
					        self.assertEqual(response.status_code, 200)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_stock_api(self):
 | 
				
			||||||
 | 
					        url = reverse('api-stock-list')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Check JSON response
 | 
				
			||||||
 | 
					        response = self.client.get(url, HTTP_ACCEPT='application/json')
 | 
				
			||||||
 | 
					        self.assertEqual(response.status_code, 200)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Check HTTP response
 | 
				
			||||||
 | 
					        response = self.client.get(url, HTTP_ACCEPT='text/html')
 | 
				
			||||||
 | 
					        self.assertEqual(response.status_code, 200)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_company_list(self):
 | 
				
			||||||
 | 
					        url = reverse('api-company-list')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Check JSON response
 | 
				
			||||||
 | 
					        response = self.client.get(url, HTTP_ACCEPT='application/json')
 | 
				
			||||||
 | 
					        self.assertEqual(response.status_code, 200)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Check HTTP response
 | 
				
			||||||
 | 
					        response = self.client.get(url, HTTP_ACCEPT='text/html')
 | 
				
			||||||
 | 
					        self.assertEqual(response.status_code, 200)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class APITests(InvenTreeAPITestCase):
 | 
					class APITests(InvenTreeAPITestCase):
 | 
				
			||||||
    """ Tests for the InvenTree API """
 | 
					    """ Tests for the InvenTree API """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,8 +5,6 @@ from django.test import TestCase
 | 
				
			|||||||
import django.core.exceptions as django_exceptions
 | 
					import django.core.exceptions as django_exceptions
 | 
				
			||||||
from django.core.exceptions import ValidationError
 | 
					from django.core.exceptions import ValidationError
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from django.conf import settings
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from djmoney.money import Money
 | 
					from djmoney.money import Money
 | 
				
			||||||
from djmoney.contrib.exchange.models import Rate, convert_money
 | 
					from djmoney.contrib.exchange.models import Rate, convert_money
 | 
				
			||||||
from djmoney.contrib.exchange.exceptions import MissingRate
 | 
					from djmoney.contrib.exchange.exceptions import MissingRate
 | 
				
			||||||
@@ -22,6 +20,7 @@ from decimal import Decimal
 | 
				
			|||||||
import InvenTree.tasks
 | 
					import InvenTree.tasks
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from stock.models import StockLocation
 | 
					from stock.models import StockLocation
 | 
				
			||||||
 | 
					from common.settings import currency_codes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class ValidatorTest(TestCase):
 | 
					class ValidatorTest(TestCase):
 | 
				
			||||||
@@ -337,13 +336,11 @@ class CurrencyTests(TestCase):
 | 
				
			|||||||
        with self.assertRaises(MissingRate):
 | 
					        with self.assertRaises(MissingRate):
 | 
				
			||||||
            convert_money(Money(100, 'AUD'), 'USD')
 | 
					            convert_money(Money(100, 'AUD'), 'USD')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        currencies = settings.CURRENCIES
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        InvenTree.tasks.update_exchange_rates()
 | 
					        InvenTree.tasks.update_exchange_rates()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        rates = Rate.objects.all()
 | 
					        rates = Rate.objects.all()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.assertEqual(rates.count(), len(currencies))
 | 
					        self.assertEqual(rates.count(), len(currency_codes()))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Now that we have some exchange rate information, we can perform conversions
 | 
					        # Now that we have some exchange rate information, we can perform conversions
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -12,7 +12,6 @@ from django.utils.translation import gettext_lazy as _
 | 
				
			|||||||
from django.template.loader import render_to_string
 | 
					from django.template.loader import render_to_string
 | 
				
			||||||
from django.http import HttpResponse, JsonResponse, HttpResponseRedirect
 | 
					from django.http import HttpResponse, JsonResponse, HttpResponseRedirect
 | 
				
			||||||
from django.urls import reverse_lazy
 | 
					from django.urls import reverse_lazy
 | 
				
			||||||
from django.conf import settings
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
from django.contrib.auth.mixins import PermissionRequiredMixin
 | 
					from django.contrib.auth.mixins import PermissionRequiredMixin
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -21,6 +20,7 @@ from django.views.generic import ListView, DetailView, CreateView, FormView, Del
 | 
				
			|||||||
from django.views.generic.base import RedirectView, TemplateView
 | 
					from django.views.generic.base import RedirectView, TemplateView
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from djmoney.contrib.exchange.models import ExchangeBackend, Rate
 | 
					from djmoney.contrib.exchange.models import ExchangeBackend, Rate
 | 
				
			||||||
 | 
					from common.settings import currency_code_default, currency_codes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from part.models import Part, PartCategory
 | 
					from part.models import Part, PartCategory
 | 
				
			||||||
from stock.models import StockLocation, StockItem
 | 
					from stock.models import StockLocation, StockItem
 | 
				
			||||||
@@ -820,8 +820,8 @@ class CurrencySettingsView(TemplateView):
 | 
				
			|||||||
        ctx = super().get_context_data(**kwargs).copy()
 | 
					        ctx = super().get_context_data(**kwargs).copy()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        ctx['settings'] = InvenTreeSetting.objects.all().order_by('key')
 | 
					        ctx['settings'] = InvenTreeSetting.objects.all().order_by('key')
 | 
				
			||||||
        ctx["base_currency"] = settings.BASE_CURRENCY
 | 
					        ctx["base_currency"] = currency_code_default()
 | 
				
			||||||
        ctx["currencies"] = settings.CURRENCIES
 | 
					        ctx["currencies"] = currency_codes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        ctx["rates"] = Rate.objects.filter(backend="InvenTreeExchange")
 | 
					        ctx["rates"] = Rate.objects.filter(backend="InvenTreeExchange")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										23
									
								
								InvenTree/common/migrations/0010_migrate_currency_setting.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								InvenTree/common/migrations/0010_migrate_currency_setting.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,23 @@
 | 
				
			|||||||
 | 
					# Generated by Django 3.2.4 on 2021-07-01 15:39
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from django.db import migrations
 | 
				
			||||||
 | 
					from common.models import InvenTreeSetting
 | 
				
			||||||
 | 
					from InvenTree.settings import get_setting, CONFIG
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def set_default_currency(apps, schema_editor):
 | 
				
			||||||
 | 
					    """ migrate the currency setting from config.yml to db """
 | 
				
			||||||
 | 
					    # get value from settings-file
 | 
				
			||||||
 | 
					    base_currency = get_setting('INVENTREE_BASE_CURRENCY', CONFIG.get('base_currency', 'USD'))
 | 
				
			||||||
 | 
					    # write to database
 | 
				
			||||||
 | 
					    InvenTreeSetting.set_setting('INVENTREE_DEFAULT_CURRENCY', base_currency, None, create=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Migration(migrations.Migration):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    dependencies = [
 | 
				
			||||||
 | 
					        ('common', '0009_delete_currency'),
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    operations = [
 | 
				
			||||||
 | 
					        migrations.RunPython(set_default_currency),
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
@@ -14,11 +14,11 @@ from django.db import models, transaction
 | 
				
			|||||||
from django.db.utils import IntegrityError, OperationalError
 | 
					from django.db.utils import IntegrityError, OperationalError
 | 
				
			||||||
from django.conf import settings
 | 
					from django.conf import settings
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from djmoney.models.fields import MoneyField
 | 
					from djmoney.settings import CURRENCY_CHOICES
 | 
				
			||||||
from djmoney.contrib.exchange.models import convert_money
 | 
					from djmoney.contrib.exchange.models import convert_money
 | 
				
			||||||
from djmoney.contrib.exchange.exceptions import MissingRate
 | 
					from djmoney.contrib.exchange.exceptions import MissingRate
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from common.settings import currency_code_default
 | 
					import common.settings
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from django.utils.translation import ugettext_lazy as _
 | 
					from django.utils.translation import ugettext_lazy as _
 | 
				
			||||||
from django.core.validators import MinValueValidator, URLValidator
 | 
					from django.core.validators import MinValueValidator, URLValidator
 | 
				
			||||||
@@ -81,6 +81,13 @@ class InvenTreeSetting(models.Model):
 | 
				
			|||||||
            'default': '',
 | 
					            'default': '',
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        'INVENTREE_DEFAULT_CURRENCY': {
 | 
				
			||||||
 | 
					            'name': _('Default Currency'),
 | 
				
			||||||
 | 
					            'description': _('Default currency'),
 | 
				
			||||||
 | 
					            'default': 'USD',
 | 
				
			||||||
 | 
					            'choices': CURRENCY_CHOICES,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        'INVENTREE_DOWNLOAD_FROM_URL': {
 | 
					        'INVENTREE_DOWNLOAD_FROM_URL': {
 | 
				
			||||||
            'name': _('Download from URL'),
 | 
					            'name': _('Download from URL'),
 | 
				
			||||||
            'description': _('Allow download of remote images and files from external URL'),
 | 
					            'description': _('Allow download of remote images and files from external URL'),
 | 
				
			||||||
@@ -219,6 +226,13 @@ class InvenTreeSetting(models.Model):
 | 
				
			|||||||
            'validator': bool,
 | 
					            'validator': bool,
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        'PART_SHOW_RELATED': {
 | 
				
			||||||
 | 
					            'name': _('Show related parts'),
 | 
				
			||||||
 | 
					            'description': _('Display related parts for a part'),
 | 
				
			||||||
 | 
					            'default': True,
 | 
				
			||||||
 | 
					            'validator': bool,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        'PART_INTERNAL_PRICE': {
 | 
					        'PART_INTERNAL_PRICE': {
 | 
				
			||||||
            'name': _('Internal Prices'),
 | 
					            'name': _('Internal Prices'),
 | 
				
			||||||
            'description': _('Enable internal prices for parts'),
 | 
					            'description': _('Enable internal prices for parts'),
 | 
				
			||||||
@@ -728,10 +742,9 @@ class PriceBreak(models.Model):
 | 
				
			|||||||
        help_text=_('Price break quantity'),
 | 
					        help_text=_('Price break quantity'),
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    price = MoneyField(
 | 
					    price = InvenTree.fields.InvenTreeModelMoneyField(
 | 
				
			||||||
        max_digits=19,
 | 
					        max_digits=19,
 | 
				
			||||||
        decimal_places=4,
 | 
					        decimal_places=4,
 | 
				
			||||||
        default_currency=currency_code_default(),
 | 
					 | 
				
			||||||
        null=True,
 | 
					        null=True,
 | 
				
			||||||
        verbose_name=_('Price'),
 | 
					        verbose_name=_('Price'),
 | 
				
			||||||
        help_text=_('Unit price at specified quantity'),
 | 
					        help_text=_('Unit price at specified quantity'),
 | 
				
			||||||
@@ -784,7 +797,7 @@ def get_price(instance, quantity, moq=True, multiples=True, currency=None, break
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    if currency is None:
 | 
					    if currency is None:
 | 
				
			||||||
        # Default currency selection
 | 
					        # Default currency selection
 | 
				
			||||||
        currency = currency_code_default()
 | 
					        currency = common.settings.currency_code_default()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pb_min = None
 | 
					    pb_min = None
 | 
				
			||||||
    for pb in price_breaks:
 | 
					    for pb in price_breaks:
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,9 +6,9 @@ User-configurable settings for the common app
 | 
				
			|||||||
from __future__ import unicode_literals
 | 
					from __future__ import unicode_literals
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from moneyed import CURRENCIES
 | 
					from moneyed import CURRENCIES
 | 
				
			||||||
 | 
					from django.conf import settings
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import common.models
 | 
					import common.models
 | 
				
			||||||
from django.conf import settings
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def currency_code_default():
 | 
					def currency_code_default():
 | 
				
			||||||
@@ -16,7 +16,7 @@ def currency_code_default():
 | 
				
			|||||||
    Returns the default currency code (or USD if not specified)
 | 
					    Returns the default currency code (or USD if not specified)
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    code = settings.BASE_CURRENCY
 | 
					    code = common.models.InvenTreeSetting.get_setting('INVENTREE_DEFAULT_CURRENCY')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if code not in CURRENCIES:
 | 
					    if code not in CURRENCIES:
 | 
				
			||||||
        code = 'USD'
 | 
					        code = 'USD'
 | 
				
			||||||
@@ -24,6 +24,20 @@ def currency_code_default():
 | 
				
			|||||||
    return code
 | 
					    return code
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def currency_code_mappings():
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Returns the current currency choices
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    return [(a, a) for a in settings.CURRENCIES]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def currency_codes():
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Returns the current currency codes
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    return [a for a in settings.CURRENCIES]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def stock_expiry_enabled():
 | 
					def stock_expiry_enabled():
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    Returns True if the stock expiry feature is enabled
 | 
					    Returns True if the stock expiry feature is enabled
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,13 +6,12 @@ Django Forms for interacting with Company app
 | 
				
			|||||||
from __future__ import unicode_literals
 | 
					from __future__ import unicode_literals
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from InvenTree.forms import HelperForm
 | 
					from InvenTree.forms import HelperForm
 | 
				
			||||||
from InvenTree.fields import RoundingDecimalFormField
 | 
					from InvenTree.fields import InvenTreeMoneyField, RoundingDecimalFormField
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from django.utils.translation import ugettext_lazy as _
 | 
					from django.utils.translation import ugettext_lazy as _
 | 
				
			||||||
import django.forms
 | 
					import django.forms
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import djmoney.settings
 | 
					import djmoney.settings
 | 
				
			||||||
from djmoney.forms.fields import MoneyField
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
from common.settings import currency_code_default
 | 
					from common.settings import currency_code_default
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -129,9 +128,8 @@ class EditSupplierPartForm(HelperForm):
 | 
				
			|||||||
        'note': 'fa-pencil-alt',
 | 
					        'note': 'fa-pencil-alt',
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    single_pricing = MoneyField(
 | 
					    single_pricing = InvenTreeMoneyField(
 | 
				
			||||||
        label=_('Single Price'),
 | 
					        label=_('Single Price'),
 | 
				
			||||||
        default_currency=currency_code_default(),
 | 
					 | 
				
			||||||
        help_text=_('Single quantity price'),
 | 
					        help_text=_('Single quantity price'),
 | 
				
			||||||
        decimal_places=4,
 | 
					        decimal_places=4,
 | 
				
			||||||
        max_digits=19,
 | 
					        max_digits=19,
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										25
									
								
								InvenTree/company/migrations/0039_auto_20210701_0509.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								InvenTree/company/migrations/0039_auto_20210701_0509.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,25 @@
 | 
				
			|||||||
 | 
					# Generated by Django 3.2.4 on 2021-07-01 05:09
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import InvenTree.fields
 | 
				
			||||||
 | 
					from django.db import migrations
 | 
				
			||||||
 | 
					import djmoney.models.fields
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Migration(migrations.Migration):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    dependencies = [
 | 
				
			||||||
 | 
					        ('company', '0038_manufacturerpartparameter'),
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    operations = [
 | 
				
			||||||
 | 
					        migrations.AlterField(
 | 
				
			||||||
 | 
					            model_name='supplierpricebreak',
 | 
				
			||||||
 | 
					            name='price',
 | 
				
			||||||
 | 
					            field=InvenTree.fields.InvenTreeModelMoneyField(currency_choices=[], decimal_places=4, default_currency='', help_text='Unit price at specified quantity', max_digits=19, null=True, verbose_name='Price'),
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					        migrations.AlterField(
 | 
				
			||||||
 | 
					            model_name='supplierpricebreak',
 | 
				
			||||||
 | 
					            name='price_currency',
 | 
				
			||||||
 | 
					            field=djmoney.models.fields.CurrencyField(choices=[], default='', editable=False, max_length=3),
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
@@ -10,15 +10,12 @@ from django.utils.translation import ugettext_lazy as _
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
from mptt.fields import TreeNodeChoiceField
 | 
					from mptt.fields import TreeNodeChoiceField
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from djmoney.forms.fields import MoneyField
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from InvenTree.forms import HelperForm
 | 
					from InvenTree.forms import HelperForm
 | 
				
			||||||
from InvenTree.fields import RoundingDecimalFormField
 | 
					from InvenTree.fields import InvenTreeMoneyField, RoundingDecimalFormField
 | 
				
			||||||
from InvenTree.fields import DatePickerFormField
 | 
					from InvenTree.fields import DatePickerFormField
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from InvenTree.helpers import clean_decimal
 | 
					from InvenTree.helpers import clean_decimal
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from common.models import InvenTreeSetting
 | 
					 | 
				
			||||||
from common.forms import MatchItemForm
 | 
					from common.forms import MatchItemForm
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import part.models
 | 
					import part.models
 | 
				
			||||||
@@ -321,9 +318,8 @@ class OrderMatchItemForm(MatchItemForm):
 | 
				
			|||||||
            )
 | 
					            )
 | 
				
			||||||
        # set price field
 | 
					        # set price field
 | 
				
			||||||
        elif 'price' in col_guess.lower():
 | 
					        elif 'price' in col_guess.lower():
 | 
				
			||||||
            return MoneyField(
 | 
					            return InvenTreeMoneyField(
 | 
				
			||||||
                label=_(col_guess),
 | 
					                label=_(col_guess),
 | 
				
			||||||
                default_currency=InvenTreeSetting.get_setting('INVENTREE_DEFAULT_CURRENCY'),
 | 
					 | 
				
			||||||
                decimal_places=5,
 | 
					                decimal_places=5,
 | 
				
			||||||
                max_digits=19,
 | 
					                max_digits=19,
 | 
				
			||||||
                required=False,
 | 
					                required=False,
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										35
									
								
								InvenTree/order/migrations/0047_auto_20210701_0509.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								InvenTree/order/migrations/0047_auto_20210701_0509.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,35 @@
 | 
				
			|||||||
 | 
					# Generated by Django 3.2.4 on 2021-07-01 05:09
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import InvenTree.fields
 | 
				
			||||||
 | 
					from django.db import migrations
 | 
				
			||||||
 | 
					import djmoney.models.fields
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Migration(migrations.Migration):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    dependencies = [
 | 
				
			||||||
 | 
					        ('order', '0046_purchaseorderlineitem_destination'),
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    operations = [
 | 
				
			||||||
 | 
					        migrations.AlterField(
 | 
				
			||||||
 | 
					            model_name='purchaseorderlineitem',
 | 
				
			||||||
 | 
					            name='purchase_price',
 | 
				
			||||||
 | 
					            field=InvenTree.fields.InvenTreeModelMoneyField(blank=True, currency_choices=[], decimal_places=4, default_currency='', help_text='Unit purchase price', max_digits=19, null=True, verbose_name='Purchase Price'),
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					        migrations.AlterField(
 | 
				
			||||||
 | 
					            model_name='purchaseorderlineitem',
 | 
				
			||||||
 | 
					            name='purchase_price_currency',
 | 
				
			||||||
 | 
					            field=djmoney.models.fields.CurrencyField(choices=[], default='', editable=False, max_length=3),
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					        migrations.AlterField(
 | 
				
			||||||
 | 
					            model_name='salesorderlineitem',
 | 
				
			||||||
 | 
					            name='sale_price',
 | 
				
			||||||
 | 
					            field=InvenTree.fields.InvenTreeModelMoneyField(blank=True, currency_choices=[], decimal_places=4, default_currency='', help_text='Unit sale price', max_digits=19, null=True, verbose_name='Sale Price'),
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					        migrations.AlterField(
 | 
				
			||||||
 | 
					            model_name='salesorderlineitem',
 | 
				
			||||||
 | 
					            name='sale_price_currency',
 | 
				
			||||||
 | 
					            field=djmoney.models.fields.CurrencyField(choices=[], default='', editable=False, max_length=3),
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
@@ -17,19 +17,15 @@ from django.contrib.auth.models import User
 | 
				
			|||||||
from django.urls import reverse
 | 
					from django.urls import reverse
 | 
				
			||||||
from django.utils.translation import ugettext_lazy as _
 | 
					from django.utils.translation import ugettext_lazy as _
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from common.settings import currency_code_default
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from markdownx.models import MarkdownxField
 | 
					from markdownx.models import MarkdownxField
 | 
				
			||||||
from mptt.models import TreeForeignKey
 | 
					from mptt.models import TreeForeignKey
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from djmoney.models.fields import MoneyField
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from users import models as UserModels
 | 
					from users import models as UserModels
 | 
				
			||||||
from part import models as PartModels
 | 
					from part import models as PartModels
 | 
				
			||||||
from stock import models as stock_models
 | 
					from stock import models as stock_models
 | 
				
			||||||
from company.models import Company, SupplierPart
 | 
					from company.models import Company, SupplierPart
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from InvenTree.fields import RoundingDecimalField
 | 
					from InvenTree.fields import InvenTreeModelMoneyField, RoundingDecimalField
 | 
				
			||||||
from InvenTree.helpers import decimal2string, increment, getSetting
 | 
					from InvenTree.helpers import decimal2string, increment, getSetting
 | 
				
			||||||
from InvenTree.status_codes import PurchaseOrderStatus, SalesOrderStatus, StockStatus, StockHistoryCode
 | 
					from InvenTree.status_codes import PurchaseOrderStatus, SalesOrderStatus, StockStatus, StockHistoryCode
 | 
				
			||||||
from InvenTree.models import InvenTreeAttachment
 | 
					from InvenTree.models import InvenTreeAttachment
 | 
				
			||||||
@@ -664,10 +660,9 @@ class PurchaseOrderLineItem(OrderLineItem):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    received = models.DecimalField(decimal_places=5, max_digits=15, default=0, verbose_name=_('Received'), help_text=_('Number of items received'))
 | 
					    received = models.DecimalField(decimal_places=5, max_digits=15, default=0, verbose_name=_('Received'), help_text=_('Number of items received'))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    purchase_price = MoneyField(
 | 
					    purchase_price = InvenTreeModelMoneyField(
 | 
				
			||||||
        max_digits=19,
 | 
					        max_digits=19,
 | 
				
			||||||
        decimal_places=4,
 | 
					        decimal_places=4,
 | 
				
			||||||
        default_currency=currency_code_default(),
 | 
					 | 
				
			||||||
        null=True, blank=True,
 | 
					        null=True, blank=True,
 | 
				
			||||||
        verbose_name=_('Purchase Price'),
 | 
					        verbose_name=_('Purchase Price'),
 | 
				
			||||||
        help_text=_('Unit purchase price'),
 | 
					        help_text=_('Unit purchase price'),
 | 
				
			||||||
@@ -716,10 +711,9 @@ class SalesOrderLineItem(OrderLineItem):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    part = models.ForeignKey('part.Part', on_delete=models.SET_NULL, related_name='sales_order_line_items', null=True, verbose_name=_('Part'), help_text=_('Part'), limit_choices_to={'salable': True})
 | 
					    part = models.ForeignKey('part.Part', on_delete=models.SET_NULL, related_name='sales_order_line_items', null=True, verbose_name=_('Part'), help_text=_('Part'), limit_choices_to={'salable': True})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    sale_price = MoneyField(
 | 
					    sale_price = InvenTreeModelMoneyField(
 | 
				
			||||||
        max_digits=19,
 | 
					        max_digits=19,
 | 
				
			||||||
        decimal_places=4,
 | 
					        decimal_places=4,
 | 
				
			||||||
        default_currency=currency_code_default(),
 | 
					 | 
				
			||||||
        null=True, blank=True,
 | 
					        null=True, blank=True,
 | 
				
			||||||
        verbose_name=_('Sale Price'),
 | 
					        verbose_name=_('Sale Price'),
 | 
				
			||||||
        help_text=_('Unit sale price'),
 | 
					        help_text=_('Unit sale price'),
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										35
									
								
								InvenTree/part/migrations/0069_auto_20210701_0509.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								InvenTree/part/migrations/0069_auto_20210701_0509.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,35 @@
 | 
				
			|||||||
 | 
					# Generated by Django 3.2.4 on 2021-07-01 05:09
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import InvenTree.fields
 | 
				
			||||||
 | 
					from django.db import migrations
 | 
				
			||||||
 | 
					import djmoney.models.fields
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Migration(migrations.Migration):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    dependencies = [
 | 
				
			||||||
 | 
					        ('part', '0068_part_unique_part'),
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    operations = [
 | 
				
			||||||
 | 
					        migrations.AlterField(
 | 
				
			||||||
 | 
					            model_name='partinternalpricebreak',
 | 
				
			||||||
 | 
					            name='price',
 | 
				
			||||||
 | 
					            field=InvenTree.fields.InvenTreeModelMoneyField(currency_choices=[], decimal_places=4, default_currency='', help_text='Unit price at specified quantity', max_digits=19, null=True, verbose_name='Price'),
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					        migrations.AlterField(
 | 
				
			||||||
 | 
					            model_name='partinternalpricebreak',
 | 
				
			||||||
 | 
					            name='price_currency',
 | 
				
			||||||
 | 
					            field=djmoney.models.fields.CurrencyField(choices=[], default='', editable=False, max_length=3),
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					        migrations.AlterField(
 | 
				
			||||||
 | 
					            model_name='partsellpricebreak',
 | 
				
			||||||
 | 
					            name='price',
 | 
				
			||||||
 | 
					            field=InvenTree.fields.InvenTreeModelMoneyField(currency_choices=[], decimal_places=4, default_currency='', help_text='Unit price at specified quantity', max_digits=19, null=True, verbose_name='Price'),
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					        migrations.AlterField(
 | 
				
			||||||
 | 
					            model_name='partsellpricebreak',
 | 
				
			||||||
 | 
					            name='price_currency',
 | 
				
			||||||
 | 
					            field=djmoney.models.fields.CurrencyField(choices=[], default='', editable=False, max_length=3),
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
@@ -3,6 +3,7 @@
 | 
				
			|||||||
{% load inventree_extras %}
 | 
					{% load inventree_extras %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
{% settings_value "PART_INTERNAL_PRICE" as show_internal_price %}
 | 
					{% settings_value "PART_INTERNAL_PRICE" as show_internal_price %}
 | 
				
			||||||
 | 
					{% settings_value 'PART_SHOW_RELATED' as show_related %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<ul class='list-group'>
 | 
					<ul class='list-group'>
 | 
				
			||||||
    <li class='list-group-item'>
 | 
					    <li class='list-group-item'>
 | 
				
			||||||
@@ -124,12 +125,14 @@
 | 
				
			|||||||
        </a>
 | 
					        </a>
 | 
				
			||||||
    </li>
 | 
					    </li>
 | 
				
			||||||
    {% endif %}
 | 
					    {% endif %}
 | 
				
			||||||
 | 
					    {% if show_related %}
 | 
				
			||||||
    <li class='list-group-item {% if tab == "related" %}active{% endif %}' title='{% trans "Related Parts" %}'>
 | 
					    <li class='list-group-item {% if tab == "related" %}active{% endif %}' title='{% trans "Related Parts" %}'>
 | 
				
			||||||
        <a href='{% url "part-related" part.id %}'>
 | 
					        <a href='{% url "part-related" part.id %}'>
 | 
				
			||||||
            <span class='menu-tab-icon fas fa-random sidebar-icon'></span>
 | 
					            <span class='menu-tab-icon fas fa-random sidebar-icon'></span>
 | 
				
			||||||
            {% trans "Related Parts" %}
 | 
					            {% trans "Related Parts" %}
 | 
				
			||||||
        </a>
 | 
					        </a>
 | 
				
			||||||
    </li>
 | 
					    </li>
 | 
				
			||||||
 | 
					    {% endif %}
 | 
				
			||||||
    <li class='list-group-item {% if tab == "attachments" %}active{% endif %}' title='{% trans "Attachments" %}'>
 | 
					    <li class='list-group-item {% if tab == "attachments" %}active{% endif %}' title='{% trans "Attachments" %}'>
 | 
				
			||||||
        <a href='{% url "part-attachments" part.id %}'>
 | 
					        <a href='{% url "part-attachments" part.id %}'>
 | 
				
			||||||
            <span class='menu-tab-icon fas fa-paperclip sidebar-icon'></span>
 | 
					            <span class='menu-tab-icon fas fa-paperclip sidebar-icon'></span>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2949,7 +2949,7 @@ class PartSalePriceBreakCreate(AjaxCreateView):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        initials['part'] = self.get_part()
 | 
					        initials['part'] = self.get_part()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        default_currency = settings.BASE_CURRENCY
 | 
					        default_currency = inventree_settings.currency_code_default()
 | 
				
			||||||
        currency = CURRENCIES.get(default_currency, None)
 | 
					        currency = CURRENCIES.get(default_currency, None)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if currency is not None:
 | 
					        if currency is not None:
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										25
									
								
								InvenTree/stock/migrations/0065_auto_20210701_0509.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								InvenTree/stock/migrations/0065_auto_20210701_0509.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,25 @@
 | 
				
			|||||||
 | 
					# Generated by Django 3.2.4 on 2021-07-01 05:09
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import InvenTree.fields
 | 
				
			||||||
 | 
					from django.db import migrations
 | 
				
			||||||
 | 
					import djmoney.models.fields
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Migration(migrations.Migration):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    dependencies = [
 | 
				
			||||||
 | 
					        ('stock', '0064_auto_20210621_1724'),
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    operations = [
 | 
				
			||||||
 | 
					        migrations.AlterField(
 | 
				
			||||||
 | 
					            model_name='stockitem',
 | 
				
			||||||
 | 
					            name='purchase_price',
 | 
				
			||||||
 | 
					            field=InvenTree.fields.InvenTreeModelMoneyField(blank=True, currency_choices=[], decimal_places=4, default_currency='', help_text='Single unit purchase price at time of purchase', max_digits=19, null=True, verbose_name='Purchase Price'),
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					        migrations.AlterField(
 | 
				
			||||||
 | 
					            model_name='stockitem',
 | 
				
			||||||
 | 
					            name='purchase_price_currency',
 | 
				
			||||||
 | 
					            field=djmoney.models.fields.CurrencyField(choices=[], default='', editable=False, max_length=3),
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
@@ -20,14 +20,10 @@ from django.contrib.auth.models import User
 | 
				
			|||||||
from django.db.models.signals import pre_delete
 | 
					from django.db.models.signals import pre_delete
 | 
				
			||||||
from django.dispatch import receiver
 | 
					from django.dispatch import receiver
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from common.settings import currency_code_default
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from markdownx.models import MarkdownxField
 | 
					from markdownx.models import MarkdownxField
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from mptt.models import MPTTModel, TreeForeignKey
 | 
					from mptt.models import MPTTModel, TreeForeignKey
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from djmoney.models.fields import MoneyField
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from decimal import Decimal, InvalidOperation
 | 
					from decimal import Decimal, InvalidOperation
 | 
				
			||||||
from datetime import datetime, timedelta
 | 
					from datetime import datetime, timedelta
 | 
				
			||||||
from InvenTree import helpers
 | 
					from InvenTree import helpers
 | 
				
			||||||
@@ -38,7 +34,7 @@ import label.models
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
from InvenTree.status_codes import StockStatus, StockHistoryCode
 | 
					from InvenTree.status_codes import StockStatus, StockHistoryCode
 | 
				
			||||||
from InvenTree.models import InvenTreeTree, InvenTreeAttachment
 | 
					from InvenTree.models import InvenTreeTree, InvenTreeAttachment
 | 
				
			||||||
from InvenTree.fields import InvenTreeURLField
 | 
					from InvenTree.fields import InvenTreeModelMoneyField, InvenTreeURLField
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from users.models import Owner
 | 
					from users.models import Owner
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -533,10 +529,9 @@ class StockItem(MPTTModel):
 | 
				
			|||||||
        help_text=_('Stock Item Notes')
 | 
					        help_text=_('Stock Item Notes')
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    purchase_price = MoneyField(
 | 
					    purchase_price = InvenTreeModelMoneyField(
 | 
				
			||||||
        max_digits=19,
 | 
					        max_digits=19,
 | 
				
			||||||
        decimal_places=4,
 | 
					        decimal_places=4,
 | 
				
			||||||
        default_currency=currency_code_default(),
 | 
					 | 
				
			||||||
        blank=True,
 | 
					        blank=True,
 | 
				
			||||||
        null=True,
 | 
					        null=True,
 | 
				
			||||||
        verbose_name=_('Purchase Price'),
 | 
					        verbose_name=_('Purchase Price'),
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -12,6 +12,13 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
{% block settings %}
 | 
					{% block settings %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<table class='table table-striped table-condensed'>
 | 
				
			||||||
 | 
					    {% include "InvenTree/settings/header.html" %}
 | 
				
			||||||
 | 
					    <tbody>
 | 
				
			||||||
 | 
					        {% include "InvenTree/settings/setting.html" with key="INVENTREE_DEFAULT_CURRENCY" icon="fa-globe" %}
 | 
				
			||||||
 | 
					    </tbody>
 | 
				
			||||||
 | 
					</table>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<table class='table table-striped table-condensed'>
 | 
					<table class='table table-striped table-condensed'>
 | 
				
			||||||
    <tbody>
 | 
					    <tbody>
 | 
				
			||||||
        <tr>
 | 
					        <tr>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -21,6 +21,7 @@
 | 
				
			|||||||
        {% include "InvenTree/settings/setting.html" with key="PART_ALLOW_EDIT_IPN" %}
 | 
					        {% include "InvenTree/settings/setting.html" with key="PART_ALLOW_EDIT_IPN" %}
 | 
				
			||||||
        {% include "InvenTree/settings/setting.html" with key="PART_SHOW_QUANTITY_IN_FORMS" icon="fa-hashtag" %}
 | 
					        {% include "InvenTree/settings/setting.html" with key="PART_SHOW_QUANTITY_IN_FORMS" icon="fa-hashtag" %}
 | 
				
			||||||
        {% include "InvenTree/settings/setting.html" with key="PART_SHOW_PRICE_IN_FORMS" icon="fa-dollar-sign" %}
 | 
					        {% include "InvenTree/settings/setting.html" with key="PART_SHOW_PRICE_IN_FORMS" icon="fa-dollar-sign" %}
 | 
				
			||||||
 | 
					        {% include "InvenTree/settings/setting.html" with key="PART_SHOW_RELATED" icon="fa-random" %}
 | 
				
			||||||
        {% include "InvenTree/settings/setting.html" with key="PART_RECENT_COUNT" icon="fa-clock" %}
 | 
					        {% include "InvenTree/settings/setting.html" with key="PART_RECENT_COUNT" icon="fa-clock" %}
 | 
				
			||||||
        <tr><td colspan='5  '></td></tr>
 | 
					        <tr><td colspan='5  '></td></tr>
 | 
				
			||||||
        {% include "InvenTree/settings/setting.html" with key="PART_TEMPLATE" icon="fa-clone" %}
 | 
					        {% include "InvenTree/settings/setting.html" with key="PART_TEMPLATE" icon="fa-clone" %}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -231,6 +231,7 @@ function loadBuildOrderAllocationTable(table, options={}) {
 | 
				
			|||||||
            {
 | 
					            {
 | 
				
			||||||
                field: 'quantity',
 | 
					                field: 'quantity',
 | 
				
			||||||
                title: '{% trans "Quantity" %}',
 | 
					                title: '{% trans "Quantity" %}',
 | 
				
			||||||
 | 
					                sortable: true,
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        ]
 | 
					        ]
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -391,6 +391,7 @@ function loadSalesOrderAllocationTable(table, options={}) {
 | 
				
			|||||||
            {
 | 
					            {
 | 
				
			||||||
                field: 'quantity',
 | 
					                field: 'quantity',
 | 
				
			||||||
                title: '{% trans "Quantity" %}',
 | 
					                title: '{% trans "Quantity" %}',
 | 
				
			||||||
 | 
					                sortable: true,
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        ]
 | 
					        ]
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user