mirror of
https://github.com/inventree/InvenTree.git
synced 2025-04-30 12:36:45 +00:00
Merge branch 'master' of https://github.com/inventree/InvenTree into one-pricing-view
This commit is contained in:
commit
f8e2d53ad4
@ -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
|
||||||
|
@ -21,6 +21,9 @@ import InvenTree.version
|
|||||||
|
|
||||||
from common.models import InvenTreeSetting
|
from common.models import InvenTreeSetting
|
||||||
from .settings import MEDIA_URL, STATIC_URL
|
from .settings import MEDIA_URL, STATIC_URL
|
||||||
|
from common.settings import currency_code_default
|
||||||
|
|
||||||
|
from djmoney.money import Money
|
||||||
|
|
||||||
|
|
||||||
def getSetting(key, backup_value=None):
|
def getSetting(key, backup_value=None):
|
||||||
@ -247,6 +250,22 @@ def decimal2string(d):
|
|||||||
return s.rstrip("0").rstrip(".")
|
return s.rstrip("0").rstrip(".")
|
||||||
|
|
||||||
|
|
||||||
|
def decimal2money(d, currency=None):
|
||||||
|
"""
|
||||||
|
Format a Decimal number as Money
|
||||||
|
|
||||||
|
Args:
|
||||||
|
d: A python Decimal object
|
||||||
|
currency: Currency of the input amount, defaults to default currency in settings
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A Money object from the input(s)
|
||||||
|
"""
|
||||||
|
if not currency:
|
||||||
|
currency = currency_code_default()
|
||||||
|
return Money(d, currency)
|
||||||
|
|
||||||
|
|
||||||
def WrapWithQuotes(text, quote='"'):
|
def WrapWithQuotes(text, quote='"'):
|
||||||
""" Wrap the supplied text with quotes
|
""" Wrap the supplied text with quotes
|
||||||
|
|
||||||
|
@ -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,14 +44,11 @@ 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:
|
|
||||||
data = OrderedDict()
|
|
||||||
else:
|
|
||||||
# Required to side-step immutability of a QueryDict
|
# Required to side-step immutability of a QueryDict
|
||||||
data = data.copy()
|
data = data.copy()
|
||||||
|
|
||||||
@ -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),
|
||||||
|
),
|
||||||
|
]
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -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),
|
||||||
|
),
|
||||||
|
]
|
@ -39,7 +39,7 @@ from InvenTree import helpers
|
|||||||
from InvenTree import validators
|
from InvenTree import validators
|
||||||
from InvenTree.models import InvenTreeTree, InvenTreeAttachment
|
from InvenTree.models import InvenTreeTree, InvenTreeAttachment
|
||||||
from InvenTree.fields import InvenTreeURLField
|
from InvenTree.fields import InvenTreeURLField
|
||||||
from InvenTree.helpers import decimal2string, normalize
|
from InvenTree.helpers import decimal2string, normalize, decimal2money
|
||||||
|
|
||||||
from InvenTree.status_codes import BuildStatus, PurchaseOrderStatus, SalesOrderStatus
|
from InvenTree.status_codes import BuildStatus, PurchaseOrderStatus, SalesOrderStatus
|
||||||
|
|
||||||
@ -2414,7 +2414,7 @@ class BomItem(models.Model):
|
|||||||
return "{n} x {child} to make {parent}".format(
|
return "{n} x {child} to make {parent}".format(
|
||||||
parent=self.part.full_name,
|
parent=self.part.full_name,
|
||||||
child=self.sub_part.full_name,
|
child=self.sub_part.full_name,
|
||||||
n=helpers.decimal2string(self.quantity))
|
n=decimal2string(self.quantity))
|
||||||
|
|
||||||
def available_stock(self):
|
def available_stock(self):
|
||||||
"""
|
"""
|
||||||
@ -2498,12 +2498,12 @@ class BomItem(models.Model):
|
|||||||
return required
|
return required
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def price_range(self):
|
def price_range(self, internal=False):
|
||||||
""" Return the price-range for this BOM item. """
|
""" Return the price-range for this BOM item. """
|
||||||
|
|
||||||
# get internal price setting
|
# get internal price setting
|
||||||
use_internal = common.models.InvenTreeSetting.get_setting('PART_BOM_USE_INTERNAL_PRICE', False)
|
use_internal = common.models.InvenTreeSetting.get_setting('PART_BOM_USE_INTERNAL_PRICE', False)
|
||||||
prange = self.sub_part.get_price_range(self.quantity, intenal=use_internal)
|
prange = self.sub_part.get_price_range(self.quantity, internal=use_internal and internal)
|
||||||
|
|
||||||
if prange is None:
|
if prange is None:
|
||||||
return prange
|
return prange
|
||||||
@ -2511,11 +2511,11 @@ class BomItem(models.Model):
|
|||||||
pmin, pmax = prange
|
pmin, pmax = prange
|
||||||
|
|
||||||
if pmin == pmax:
|
if pmin == pmax:
|
||||||
return decimal2string(pmin)
|
return decimal2money(pmin)
|
||||||
|
|
||||||
# Convert to better string representation
|
# Convert to better string representation
|
||||||
pmin = decimal2string(pmin)
|
pmin = decimal2money(pmin)
|
||||||
pmax = decimal2string(pmax)
|
pmax = decimal2money(pmax)
|
||||||
|
|
||||||
return "{pmin} to {pmax}".format(pmin=pmin, pmax=pmax)
|
return "{pmin} to {pmax}".format(pmin=pmin, pmax=pmax)
|
||||||
|
|
||||||
|
@ -377,7 +377,7 @@ class PartStarSerializer(InvenTreeModelSerializer):
|
|||||||
class BomItemSerializer(InvenTreeModelSerializer):
|
class BomItemSerializer(InvenTreeModelSerializer):
|
||||||
""" Serializer for BomItem object """
|
""" Serializer for BomItem object """
|
||||||
|
|
||||||
# price_range = serializers.CharField(read_only=True)
|
price_range = serializers.CharField(read_only=True)
|
||||||
|
|
||||||
quantity = serializers.FloatField()
|
quantity = serializers.FloatField()
|
||||||
|
|
||||||
@ -492,7 +492,7 @@ class BomItemSerializer(InvenTreeModelSerializer):
|
|||||||
'reference',
|
'reference',
|
||||||
'sub_part',
|
'sub_part',
|
||||||
'sub_part_detail',
|
'sub_part_detail',
|
||||||
# 'price_range',
|
'price_range',
|
||||||
'validated',
|
'validated',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -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'>
|
||||||
@ -112,12 +113,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>
|
||||||
|
@ -2982,7 +2982,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" %}
|
||||||
|
@ -259,26 +259,19 @@ function loadBomTable(table, options) {
|
|||||||
sortable: true,
|
sortable: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
// TODO - Re-introduce the pricing column at a later stage,
|
|
||||||
// once the pricing has been "fixed"
|
|
||||||
// O.W. 2020-11-24
|
|
||||||
|
|
||||||
cols.push(
|
cols.push(
|
||||||
{
|
{
|
||||||
field: 'price_range',
|
field: 'price_range',
|
||||||
title: '{% trans "Price" %}',
|
title: '{% trans "Buy Price" %}',
|
||||||
sortable: true,
|
sortable: true,
|
||||||
formatter: function(value, row, index, field) {
|
formatter: function(value, row, index, field) {
|
||||||
if (value) {
|
if (value) {
|
||||||
return value;
|
return value;
|
||||||
} else {
|
} else {
|
||||||
return "<span class='warning-msg'>{% trans "No pricing available" %}</span>";
|
return "<span class='warning-msg'>{% trans 'No pricing available' %}</span>";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
*/
|
|
||||||
|
|
||||||
cols.push({
|
cols.push({
|
||||||
field: 'optional',
|
field: 'optional',
|
||||||
|
@ -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,
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
FROM python:alpine as base
|
FROM alpine:3.13 as base
|
||||||
|
|
||||||
# GitHub source
|
# GitHub source
|
||||||
ARG repository="https://github.com/inventree/InvenTree.git"
|
ARG repository="https://github.com/inventree/InvenTree.git"
|
||||||
@ -57,7 +57,7 @@ RUN apk add --no-cache cairo cairo-dev pango pango-dev
|
|||||||
RUN apk add --no-cache fontconfig ttf-droid ttf-liberation ttf-dejavu ttf-opensans ttf-ubuntu-font-family font-croscore font-noto
|
RUN apk add --no-cache fontconfig ttf-droid ttf-liberation ttf-dejavu ttf-opensans ttf-ubuntu-font-family font-croscore font-noto
|
||||||
|
|
||||||
# Python
|
# Python
|
||||||
RUN apk add --no-cache python3 python3-dev
|
RUN apk add --no-cache python3 python3-dev py3-pip
|
||||||
|
|
||||||
# SQLite support
|
# SQLite support
|
||||||
RUN apk add --no-cache sqlite
|
RUN apk add --no-cache sqlite
|
||||||
|
@ -32,7 +32,7 @@ echo "Starting InvenTree server..."
|
|||||||
|
|
||||||
# Wait for the database to be ready
|
# Wait for the database to be ready
|
||||||
cd ${INVENTREE_HOME}/InvenTree
|
cd ${INVENTREE_HOME}/InvenTree
|
||||||
python manage.py wait_for_db
|
python3 manage.py wait_for_db
|
||||||
|
|
||||||
sleep 10
|
sleep 10
|
||||||
|
|
||||||
@ -40,10 +40,10 @@ echo "Running InvenTree database migrations..."
|
|||||||
|
|
||||||
# We assume at this stage that the database is up and running
|
# We assume at this stage that the database is up and running
|
||||||
# Ensure that the database schema are up to date
|
# Ensure that the database schema are up to date
|
||||||
python manage.py check || exit 1
|
python3 manage.py check || exit 1
|
||||||
python manage.py migrate --noinput || exit 1
|
python3 manage.py migrate --noinput || exit 1
|
||||||
python manage.py migrate --run-syncdb || exit 1
|
python3 manage.py migrate --run-syncdb || exit 1
|
||||||
python manage.py clearsessions || exit 1
|
python3 manage.py clearsessions || exit 1
|
||||||
|
|
||||||
# Launch a development server
|
# Launch a development server
|
||||||
python manage.py runserver ${INVENTREE_WEB_ADDR}:${INVENTREE_WEB_PORT}
|
python3 manage.py runserver ${INVENTREE_WEB_ADDR}:${INVENTREE_WEB_PORT}
|
||||||
|
@ -11,9 +11,9 @@ sleep 5
|
|||||||
|
|
||||||
# Wait for the database to be ready
|
# Wait for the database to be ready
|
||||||
cd InvenTree
|
cd InvenTree
|
||||||
python manage.py wait_for_db
|
python3 manage.py wait_for_db
|
||||||
|
|
||||||
sleep 10
|
sleep 10
|
||||||
|
|
||||||
# Now we can launch the background worker process
|
# Now we can launch the background worker process
|
||||||
python manage.py qcluster
|
python3 manage.py qcluster
|
||||||
|
@ -23,7 +23,7 @@ echo "Starting InvenTree server..."
|
|||||||
|
|
||||||
# Wait for the database to be ready
|
# Wait for the database to be ready
|
||||||
cd $INVENTREE_MNG_DIR
|
cd $INVENTREE_MNG_DIR
|
||||||
python manage.py wait_for_db
|
python3 manage.py wait_for_db
|
||||||
|
|
||||||
sleep 10
|
sleep 10
|
||||||
|
|
||||||
@ -31,12 +31,12 @@ echo "Running InvenTree database migrations and collecting static files..."
|
|||||||
|
|
||||||
# We assume at this stage that the database is up and running
|
# We assume at this stage that the database is up and running
|
||||||
# Ensure that the database schema are up to date
|
# Ensure that the database schema are up to date
|
||||||
python manage.py check || exit 1
|
python3 manage.py check || exit 1
|
||||||
python manage.py migrate --noinput || exit 1
|
python3 manage.py migrate --noinput || exit 1
|
||||||
python manage.py migrate --run-syncdb || exit 1
|
python3 manage.py migrate --run-syncdb || exit 1
|
||||||
python manage.py prerender || exit 1
|
python3 manage.py prerender || exit 1
|
||||||
python manage.py collectstatic --noinput || exit 1
|
python3 manage.py collectstatic --noinput || exit 1
|
||||||
python manage.py clearsessions || exit 1
|
python3 manage.py clearsessions || exit 1
|
||||||
|
|
||||||
# Now we can launch the server
|
# Now we can launch the server
|
||||||
gunicorn -c $INVENTREE_HOME/gunicorn.conf.py InvenTree.wsgi -b 0.0.0.0:$INVENTREE_WEB_PORT
|
gunicorn -c $INVENTREE_HOME/gunicorn.conf.py InvenTree.wsgi -b 0.0.0.0:$INVENTREE_WEB_PORT
|
@ -6,9 +6,9 @@ sleep 5
|
|||||||
|
|
||||||
# Wait for the database to be ready
|
# Wait for the database to be ready
|
||||||
cd $INVENTREE_MNG_DIR
|
cd $INVENTREE_MNG_DIR
|
||||||
python manage.py wait_for_db
|
python3 manage.py wait_for_db
|
||||||
|
|
||||||
sleep 10
|
sleep 10
|
||||||
|
|
||||||
# Now we can launch the background worker process
|
# Now we can launch the background worker process
|
||||||
python manage.py qcluster
|
python3 manage.py qcluster
|
Loading…
x
Reference in New Issue
Block a user