Merge branch 'master' of https://github.com/inventree/InvenTree into one-pricing-view

This commit is contained in:
Matthias 2021-07-02 16:44:43 +02:00
commit f8e2d53ad4
45 changed files with 6912 additions and 5754 deletions

View File

@ -4,7 +4,6 @@ import logging
from django.apps import AppConfig
from django.core.exceptions import AppRegistryNotReady
from django.conf import settings
from InvenTree.ready import isInTestMode, canAppAccessDatabase
import InvenTree.tasks
@ -66,10 +65,11 @@ class InvenTreeConfig(AppConfig):
from djmoney.contrib.exchange.models import ExchangeBackend
from datetime import datetime, timedelta
from InvenTree.tasks import update_exchange_rates
from common.settings import currency_code_default
except AppRegistryNotReady:
pass
base_currency = settings.BASE_CURRENCY
base_currency = currency_code_default()
update = False

View File

@ -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
@ -22,8 +22,8 @@ class InvenTreeExchange(SimpleExchangeBackend):
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)

View File

@ -2,6 +2,7 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import sys
from .validators import allowable_url_schemes
@ -13,8 +14,11 @@ from django.core import validators
from django import forms
from decimal import Decimal
from djmoney.models.fields import MoneyField as ModelMoneyField
from djmoney.forms.fields import MoneyField
import InvenTree.helpers
import common.settings
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):
"""
Custom date-picker field

View File

@ -21,6 +21,9 @@ import InvenTree.version
from common.models import InvenTreeSetting
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):
@ -247,6 +250,22 @@ def decimal2string(d):
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='"'):
""" Wrap the supplied text with quotes

View File

@ -7,8 +7,6 @@ from __future__ import unicode_literals
import os
from collections import OrderedDict
from django.conf import settings
from django.contrib.auth.models import User
from django.core.exceptions import ValidationError as DjangoValidationError
@ -46,16 +44,13 @@ class InvenTreeModelSerializer(serializers.ModelSerializer):
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:
if instance is None and data is not empty:
if data is empty:
data = OrderedDict()
else:
# Required to side-step immutability of a QueryDict
data = data.copy()
# Required to side-step immutability of a QueryDict
data = data.copy()
# Add missing fields which have default values
ModelClass = self.Meta.model
@ -64,6 +59,11 @@ class InvenTreeModelSerializer(serializers.ModelSerializer):
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:
value = field.default
@ -85,7 +85,7 @@ class InvenTreeModelSerializer(serializers.ModelSerializer):
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?
if self.instance is None:
@ -111,7 +111,8 @@ class InvenTreeModelSerializer(serializers.ModelSerializer):
return initials
def run_validation(self, data=empty):
""" Perform serializer validation.
"""
Perform serializer validation.
In addition to running validators on the serializer fields,
this class ensures that the underlying model is also validated.
"""

View File

@ -522,10 +522,6 @@ for currency in CURRENCIES:
print(f"Currency code '{currency}' is not supported")
sys.exit(1)
BASE_CURRENCY = get_setting(
'INVENTREE_BASE_CURRENCY',
CONFIG.get('base_currency', 'USD')
)
# Custom currency exchange backend
EXCHANGE_BACKEND = 'InvenTree.exchange.InvenTreeExchange'

View File

@ -170,7 +170,7 @@ def update_exchange_rates():
try:
from InvenTree.exchange import InvenTreeExchange
from djmoney.contrib.exchange.models import ExchangeBackend, Rate
from django.conf import settings
from common.settings import currency_code_default, currency_codes
except AppRegistryNotReady:
# Apps not yet loaded!
logger.info("Could not perform 'update_exchange_rates' - App registry not ready")
@ -192,14 +192,14 @@ def update_exchange_rates():
backend = InvenTreeExchange()
print(f"Updating exchange rates from {backend.url}")
base = settings.BASE_CURRENCY
base = currency_code_default()
print(f"Using base currency '{base}'")
backend.update_rates(base_currency=base)
# 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):

View File

@ -2,6 +2,11 @@
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 InvenTree.api_tester import InvenTreeAPITestCase
@ -11,6 +16,87 @@ from users.models import RuleSet
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):
""" Tests for the InvenTree API """

View File

@ -5,8 +5,6 @@ from django.test import TestCase
import django.core.exceptions as django_exceptions
from django.core.exceptions import ValidationError
from django.conf import settings
from djmoney.money import Money
from djmoney.contrib.exchange.models import Rate, convert_money
from djmoney.contrib.exchange.exceptions import MissingRate
@ -22,6 +20,7 @@ from decimal import Decimal
import InvenTree.tasks
from stock.models import StockLocation
from common.settings import currency_codes
class ValidatorTest(TestCase):
@ -337,13 +336,11 @@ class CurrencyTests(TestCase):
with self.assertRaises(MissingRate):
convert_money(Money(100, 'AUD'), 'USD')
currencies = settings.CURRENCIES
InvenTree.tasks.update_exchange_rates()
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

View File

@ -12,7 +12,6 @@ from django.utils.translation import gettext_lazy as _
from django.template.loader import render_to_string
from django.http import HttpResponse, JsonResponse, HttpResponseRedirect
from django.urls import reverse_lazy
from django.conf import settings
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 djmoney.contrib.exchange.models import ExchangeBackend, Rate
from common.settings import currency_code_default, currency_codes
from part.models import Part, PartCategory
from stock.models import StockLocation, StockItem
@ -820,8 +820,8 @@ class CurrencySettingsView(TemplateView):
ctx = super().get_context_data(**kwargs).copy()
ctx['settings'] = InvenTreeSetting.objects.all().order_by('key')
ctx["base_currency"] = settings.BASE_CURRENCY
ctx["currencies"] = settings.CURRENCIES
ctx["base_currency"] = currency_code_default()
ctx["currencies"] = currency_codes
ctx["rates"] = Rate.objects.filter(backend="InvenTreeExchange")

View 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),
]

View File

@ -14,11 +14,11 @@ from django.db import models, transaction
from django.db.utils import IntegrityError, OperationalError
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.exceptions import MissingRate
from common.settings import currency_code_default
import common.settings
from django.utils.translation import ugettext_lazy as _
from django.core.validators import MinValueValidator, URLValidator
@ -81,6 +81,13 @@ class InvenTreeSetting(models.Model):
'default': '',
},
'INVENTREE_DEFAULT_CURRENCY': {
'name': _('Default Currency'),
'description': _('Default currency'),
'default': 'USD',
'choices': CURRENCY_CHOICES,
},
'INVENTREE_DOWNLOAD_FROM_URL': {
'name': _('Download from URL'),
'description': _('Allow download of remote images and files from external URL'),
@ -219,6 +226,13 @@ class InvenTreeSetting(models.Model):
'validator': bool,
},
'PART_SHOW_RELATED': {
'name': _('Show related parts'),
'description': _('Display related parts for a part'),
'default': True,
'validator': bool,
},
'PART_INTERNAL_PRICE': {
'name': _('Internal Prices'),
'description': _('Enable internal prices for parts'),
@ -728,10 +742,9 @@ class PriceBreak(models.Model):
help_text=_('Price break quantity'),
)
price = MoneyField(
price = InvenTree.fields.InvenTreeModelMoneyField(
max_digits=19,
decimal_places=4,
default_currency=currency_code_default(),
null=True,
verbose_name=_('Price'),
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:
# Default currency selection
currency = currency_code_default()
currency = common.settings.currency_code_default()
pb_min = None
for pb in price_breaks:

View File

@ -6,9 +6,9 @@ User-configurable settings for the common app
from __future__ import unicode_literals
from moneyed import CURRENCIES
from django.conf import settings
import common.models
from django.conf import settings
def currency_code_default():
@ -16,7 +16,7 @@ def currency_code_default():
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:
code = 'USD'
@ -24,6 +24,20 @@ def currency_code_default():
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():
"""
Returns True if the stock expiry feature is enabled

View File

@ -6,13 +6,12 @@ Django Forms for interacting with Company app
from __future__ import unicode_literals
from InvenTree.forms import HelperForm
from InvenTree.fields import RoundingDecimalFormField
from InvenTree.fields import InvenTreeMoneyField, RoundingDecimalFormField
from django.utils.translation import ugettext_lazy as _
import django.forms
import djmoney.settings
from djmoney.forms.fields import MoneyField
from common.settings import currency_code_default
@ -129,9 +128,8 @@ class EditSupplierPartForm(HelperForm):
'note': 'fa-pencil-alt',
}
single_pricing = MoneyField(
single_pricing = InvenTreeMoneyField(
label=_('Single Price'),
default_currency=currency_code_default(),
help_text=_('Single quantity price'),
decimal_places=4,
max_digits=19,

View 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

View File

@ -10,15 +10,12 @@ from django.utils.translation import ugettext_lazy as _
from mptt.fields import TreeNodeChoiceField
from djmoney.forms.fields import MoneyField
from InvenTree.forms import HelperForm
from InvenTree.fields import RoundingDecimalFormField
from InvenTree.fields import InvenTreeMoneyField, RoundingDecimalFormField
from InvenTree.fields import DatePickerFormField
from InvenTree.helpers import clean_decimal
from common.models import InvenTreeSetting
from common.forms import MatchItemForm
import part.models
@ -321,9 +318,8 @@ class OrderMatchItemForm(MatchItemForm):
)
# set price field
elif 'price' in col_guess.lower():
return MoneyField(
return InvenTreeMoneyField(
label=_(col_guess),
default_currency=InvenTreeSetting.get_setting('INVENTREE_DEFAULT_CURRENCY'),
decimal_places=5,
max_digits=19,
required=False,

View 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),
),
]

View File

@ -17,19 +17,15 @@ from django.contrib.auth.models import User
from django.urls import reverse
from django.utils.translation import ugettext_lazy as _
from common.settings import currency_code_default
from markdownx.models import MarkdownxField
from mptt.models import TreeForeignKey
from djmoney.models.fields import MoneyField
from users import models as UserModels
from part import models as PartModels
from stock import models as stock_models
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.status_codes import PurchaseOrderStatus, SalesOrderStatus, StockStatus, StockHistoryCode
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'))
purchase_price = MoneyField(
purchase_price = InvenTreeModelMoneyField(
max_digits=19,
decimal_places=4,
default_currency=currency_code_default(),
null=True, blank=True,
verbose_name=_('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})
sale_price = MoneyField(
sale_price = InvenTreeModelMoneyField(
max_digits=19,
decimal_places=4,
default_currency=currency_code_default(),
null=True, blank=True,
verbose_name=_('Sale Price'),
help_text=_('Unit sale price'),

View 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),
),
]

View File

@ -39,7 +39,7 @@ from InvenTree import helpers
from InvenTree import validators
from InvenTree.models import InvenTreeTree, InvenTreeAttachment
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
@ -2414,7 +2414,7 @@ class BomItem(models.Model):
return "{n} x {child} to make {parent}".format(
parent=self.part.full_name,
child=self.sub_part.full_name,
n=helpers.decimal2string(self.quantity))
n=decimal2string(self.quantity))
def available_stock(self):
"""
@ -2498,12 +2498,12 @@ class BomItem(models.Model):
return required
@property
def price_range(self):
def price_range(self, internal=False):
""" Return the price-range for this BOM item. """
# get internal price setting
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:
return prange
@ -2511,11 +2511,11 @@ class BomItem(models.Model):
pmin, pmax = prange
if pmin == pmax:
return decimal2string(pmin)
return decimal2money(pmin)
# Convert to better string representation
pmin = decimal2string(pmin)
pmax = decimal2string(pmax)
pmin = decimal2money(pmin)
pmax = decimal2money(pmax)
return "{pmin} to {pmax}".format(pmin=pmin, pmax=pmax)

View File

@ -377,7 +377,7 @@ class PartStarSerializer(InvenTreeModelSerializer):
class BomItemSerializer(InvenTreeModelSerializer):
""" Serializer for BomItem object """
# price_range = serializers.CharField(read_only=True)
price_range = serializers.CharField(read_only=True)
quantity = serializers.FloatField()
@ -492,7 +492,7 @@ class BomItemSerializer(InvenTreeModelSerializer):
'reference',
'sub_part',
'sub_part_detail',
# 'price_range',
'price_range',
'validated',
]

View File

@ -3,6 +3,7 @@
{% load inventree_extras %}
{% settings_value "PART_INTERNAL_PRICE" as show_internal_price %}
{% settings_value 'PART_SHOW_RELATED' as show_related %}
<ul class='list-group'>
<li class='list-group-item'>
@ -112,12 +113,14 @@
</a>
</li>
{% endif %}
{% if show_related %}
<li class='list-group-item {% if tab == "related" %}active{% endif %}' title='{% trans "Related Parts" %}'>
<a href='{% url "part-related" part.id %}'>
<span class='menu-tab-icon fas fa-random sidebar-icon'></span>
{% trans "Related Parts" %}
</a>
</li>
{% endif %}
<li class='list-group-item {% if tab == "attachments" %}active{% endif %}' title='{% trans "Attachments" %}'>
<a href='{% url "part-attachments" part.id %}'>
<span class='menu-tab-icon fas fa-paperclip sidebar-icon'></span>

View File

@ -2982,7 +2982,7 @@ class PartSalePriceBreakCreate(AjaxCreateView):
initials['part'] = self.get_part()
default_currency = settings.BASE_CURRENCY
default_currency = inventree_settings.currency_code_default()
currency = CURRENCIES.get(default_currency, None)
if currency is not None:

View 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),
),
]

View File

@ -20,14 +20,10 @@ from django.contrib.auth.models import User
from django.db.models.signals import pre_delete
from django.dispatch import receiver
from common.settings import currency_code_default
from markdownx.models import MarkdownxField
from mptt.models import MPTTModel, TreeForeignKey
from djmoney.models.fields import MoneyField
from decimal import Decimal, InvalidOperation
from datetime import datetime, timedelta
from InvenTree import helpers
@ -38,7 +34,7 @@ import label.models
from InvenTree.status_codes import StockStatus, StockHistoryCode
from InvenTree.models import InvenTreeTree, InvenTreeAttachment
from InvenTree.fields import InvenTreeURLField
from InvenTree.fields import InvenTreeModelMoneyField, InvenTreeURLField
from users.models import Owner
@ -533,10 +529,9 @@ class StockItem(MPTTModel):
help_text=_('Stock Item Notes')
)
purchase_price = MoneyField(
purchase_price = InvenTreeModelMoneyField(
max_digits=19,
decimal_places=4,
default_currency=currency_code_default(),
blank=True,
null=True,
verbose_name=_('Purchase Price'),

View File

@ -12,6 +12,13 @@
{% 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'>
<tbody>
<tr>

View File

@ -21,6 +21,7 @@
{% 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_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" %}
<tr><td colspan='5 '></td></tr>
{% include "InvenTree/settings/setting.html" with key="PART_TEMPLATE" icon="fa-clone" %}

View File

@ -259,26 +259,19 @@ function loadBomTable(table, options) {
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(
{
field: 'price_range',
title: '{% trans "Price" %}',
title: '{% trans "Buy Price" %}',
sortable: true,
formatter: function(value, row, index, field) {
if (value) {
return value;
} else {
return "<span class='warning-msg'>{% trans "No pricing available" %}</span>";
return "<span class='warning-msg'>{% trans 'No pricing available' %}</span>";
}
}
});
*/
cols.push({
field: 'optional',

View File

@ -231,6 +231,7 @@ function loadBuildOrderAllocationTable(table, options={}) {
{
field: 'quantity',
title: '{% trans "Quantity" %}',
sortable: true,
}
]
});

View File

@ -391,6 +391,7 @@ function loadSalesOrderAllocationTable(table, options={}) {
{
field: 'quantity',
title: '{% trans "Quantity" %}',
sortable: true,
}
]
});

View File

@ -1,4 +1,4 @@
FROM python:alpine as base
FROM alpine:3.13 as base
# GitHub source
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
# Python
RUN apk add --no-cache python3 python3-dev
RUN apk add --no-cache python3 python3-dev py3-pip
# SQLite support
RUN apk add --no-cache sqlite

View File

@ -32,7 +32,7 @@ echo "Starting InvenTree server..."
# Wait for the database to be ready
cd ${INVENTREE_HOME}/InvenTree
python manage.py wait_for_db
python3 manage.py wait_for_db
sleep 10
@ -40,10 +40,10 @@ echo "Running InvenTree database migrations..."
# We assume at this stage that the database is up and running
# Ensure that the database schema are up to date
python manage.py check || exit 1
python manage.py migrate --noinput || exit 1
python manage.py migrate --run-syncdb || exit 1
python manage.py clearsessions || exit 1
python3 manage.py check || exit 1
python3 manage.py migrate --noinput || exit 1
python3 manage.py migrate --run-syncdb || exit 1
python3 manage.py clearsessions || exit 1
# Launch a development server
python manage.py runserver ${INVENTREE_WEB_ADDR}:${INVENTREE_WEB_PORT}
python3 manage.py runserver ${INVENTREE_WEB_ADDR}:${INVENTREE_WEB_PORT}

View File

@ -11,9 +11,9 @@ sleep 5
# Wait for the database to be ready
cd InvenTree
python manage.py wait_for_db
python3 manage.py wait_for_db
sleep 10
# Now we can launch the background worker process
python manage.py qcluster
python3 manage.py qcluster

View File

@ -23,7 +23,7 @@ echo "Starting InvenTree server..."
# Wait for the database to be ready
cd $INVENTREE_MNG_DIR
python manage.py wait_for_db
python3 manage.py wait_for_db
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
# Ensure that the database schema are up to date
python manage.py check || exit 1
python manage.py migrate --noinput || exit 1
python manage.py migrate --run-syncdb || exit 1
python manage.py prerender || exit 1
python manage.py collectstatic --noinput || exit 1
python manage.py clearsessions || exit 1
python3 manage.py check || exit 1
python3 manage.py migrate --noinput || exit 1
python3 manage.py migrate --run-syncdb || exit 1
python3 manage.py prerender || exit 1
python3 manage.py collectstatic --noinput || exit 1
python3 manage.py clearsessions || exit 1
# Now we can launch the server
gunicorn -c $INVENTREE_HOME/gunicorn.conf.py InvenTree.wsgi -b 0.0.0.0:$INVENTREE_WEB_PORT

View File

@ -6,9 +6,9 @@ sleep 5
# Wait for the database to be ready
cd $INVENTREE_MNG_DIR
python manage.py wait_for_db
python3 manage.py wait_for_db
sleep 10
# Now we can launch the background worker process
python manage.py qcluster
python3 manage.py qcluster