Merge branch 'master' into part-import

This commit is contained in:
Matthias Mair 2021-05-26 13:08:58 +02:00 committed by GitHub
commit 859a3178a4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
39 changed files with 11181 additions and 8682 deletions

View File

@ -44,3 +44,8 @@ class InvenTreeConfig(AppConfig):
schedule_type=Schedule.MINUTES,
minutes=15
)
InvenTree.tasks.schedule_task(
'InvenTree.tasks.update_exchange_rates',
schedule_type=Schedule.DAILY,
)

View File

@ -1,4 +1,20 @@
from django.conf import settings as inventree_settings
from djmoney.contrib.exchange.backends.base import BaseExchangeBackend
from djmoney.contrib.exchange.models import Rate
from common.models import InvenTreeSetting
def get_exchange_rate_backend():
""" Return the exchange rate backend set by user """
custom = InvenTreeSetting.get_setting('CUSTOM_EXCHANGE_RATES', False)
if custom:
return InvenTreeManualExchangeBackend()
else:
return ExchangeRateHostBackend()
class InvenTreeManualExchangeBackend(BaseExchangeBackend):
@ -10,12 +26,95 @@ class InvenTreeManualExchangeBackend(BaseExchangeBackend):
Specifically: https://github.com/django-money/django-money/tree/master/djmoney/contrib/exchange/backends
"""
name = "inventree"
name = 'inventree'
url = None
custom_rates = True
base_currency = None
currencies = []
def update_default_currency(self):
""" Update to base currency """
self.base_currency = InvenTreeSetting.get_setting('INVENTREE_DEFAULT_CURRENCY', 'USD')
def __init__(self, url=None):
""" Overrides init to update url, base currency and currencies """
self.url = url
self.update_default_currency()
# Update name
self.name = self.name + '-' + self.base_currency.lower()
self.currencies = inventree_settings.CURRENCIES
super().__init__()
def get_rates(self, **kwargs):
"""
Do not get any rates...
""" Returns a mapping <currency>: <rate> """
return kwargs.get('rates', {})
def get_stored_rates(self):
""" Returns stored rate for specified backend and base currency """
stored_rates = {}
stored_rates_obj = Rate.objects.all().prefetch_related('backend')
for rate in stored_rates_obj:
# Find match for backend and base currency
if rate.backend.name == self.name and rate.backend.base_currency == self.base_currency:
# print(f'{rate.currency} | {rate.value} | {rate.backend} | {rate.backend.base_currency}')
stored_rates[rate.currency] = rate.value
return stored_rates
class ExchangeRateHostBackend(InvenTreeManualExchangeBackend):
"""
Backend for https://exchangerate.host/
"""
name = "exchangerate.host"
def __init__(self):
self.url = "https://api.exchangerate.host/latest"
self.custom_rates = False
super().__init__(url=self.url)
def get_params(self):
# No API key is required
return {}
def update_rates(self, base_currency=None):
""" Override update_rates method using currencies found in the settings
"""
if base_currency:
self.base_currency = base_currency
else:
self.update_default_currency()
symbols = ','.join(self.currencies)
super().update_rates(base_currency=self.base_currency, symbols=symbols)
def get_rates(self, **params):
""" Returns a mapping <currency>: <rate> """
# Set base currency
params.update(base=self.base_currency)
response = self.get_response(**params)
try:
return self.parse_json(response)['rates']
except KeyError:
# API response did not contain any rate
pass
return {}

View File

@ -7,13 +7,17 @@ from __future__ import unicode_literals
from django.utils.translation import ugettext_lazy as _
from django import forms
from django.contrib.auth.models import User
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Layout, Field
from crispy_forms.bootstrap import PrependedText, AppendedText, PrependedAppendedText, StrictButton, Div
from django.contrib.auth.models import User
from common.models import ColorTheme
from part.models import PartCategory
from .exchange import InvenTreeManualExchangeBackend
class HelperForm(forms.ModelForm):
""" Provides simple integration of crispy_forms extension. """
@ -236,3 +240,35 @@ class SettingCategorySelectForm(forms.ModelForm):
css_class='row',
),
)
class SettingExchangeRatesForm(forms.Form):
""" Form for displaying and setting currency exchange rates manually """
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
exchange_rate_backend = InvenTreeManualExchangeBackend()
# Update default currency (in case it has changed)
exchange_rate_backend.update_default_currency()
for currency in exchange_rate_backend.currencies:
if currency != exchange_rate_backend.base_currency:
# Set field name
field_name = currency
# Set field input box
self.fields[field_name] = forms.CharField(
label=field_name,
required=False,
widget=forms.NumberInput(attrs={
'name': field_name,
'class': 'numberinput',
'style': 'width: 200px;',
'type': 'number',
'min': '0',
'step': 'any',
'value': 0,
})
)

View File

@ -8,7 +8,7 @@ import json
import os.path
from PIL import Image
from decimal import Decimal
from decimal import Decimal, InvalidOperation
from wsgiref.util import FileWrapper
from django.http import StreamingHttpResponse
@ -606,3 +606,19 @@ def getNewestMigrationFile(app, exclude_extension=True):
newest_file = newest_file.replace('.py', '')
return newest_file
def clean_decimal(number):
""" Clean-up decimal value """
# Check if empty
if number is None or number == '':
return Decimal(0)
# Check if decimal type
try:
clean_number = Decimal(number)
except InvalidOperation:
clean_number = number
return clean_number.quantize(Decimal(1)) if clean_number == clean_number.to_integral() else clean_number.normalize()

View File

@ -514,9 +514,6 @@ CURRENCIES = CONFIG.get(
],
)
# TODO - Allow live web-based backends in the future
EXCHANGE_BACKEND = 'InvenTree.exchange.InvenTreeManualExchangeBackend'
# Extract email settings from the config file
email_config = CONFIG.get('email', {})

View File

@ -901,6 +901,15 @@ input[type="submit"] {
color: #e00;
}
.info-messages {
padding: 5px;
}
.info-messages .alert {
padding: 5px;
margin-bottom: 10px;
}
.part-allocation {
padding: 3px 10px;
border: 1px solid #ccc;

View File

@ -161,6 +161,28 @@ def check_for_updates():
)
def update_exchange_rates():
"""
Update currency exchange rates
"""
try:
import common.models
from InvenTree.exchange import ExchangeRateHostBackend
except AppRegistryNotReady:
# Apps not yet loaded!
return
backend = ExchangeRateHostBackend()
print(f"Updating exchange rates from {backend.url}")
base = common.models.InvenTreeSetting.get_setting('INVENTREE_DEFAULT_CURRENCY')
print(f"Using base currency '{base}'")
backend.update_rates(base_currency=base)
def send_email(subject, body, recipients, from_email=None):
"""
Send an email with the specified subject and body,

View File

@ -41,6 +41,7 @@ from .views import IndexView, SearchView, DatabaseStatsView
from .views import SettingsView, EditUserView, SetPasswordView
from .views import AppearanceSelectView, SettingCategorySelectView
from .views import DynamicJsView
from .views import CurrencySettingsView
from common.views import SettingEdit
@ -90,6 +91,7 @@ settings_urls = [
url(r'^build/?', SettingsView.as_view(template_name='InvenTree/settings/build.html'), name='settings-build'),
url(r'^purchase-order/?', SettingsView.as_view(template_name='InvenTree/settings/po.html'), name='settings-po'),
url(r'^sales-order/?', SettingsView.as_view(template_name='InvenTree/settings/so.html'), name='settings-so'),
url(r'^currencies/?', CurrencySettingsView.as_view(), name='settings-currencies'),
url(r'^(?P<pk>\d+)/edit/', SettingEdit.as_view(), name='setting-edit'),

View File

@ -10,8 +10,15 @@ import common.models
INVENTREE_SW_VERSION = "0.2.2 pre"
# Increment this number whenever there is a significant change to the API that any clients need to know about
INVENTREE_API_VERSION = 2
"""
Increment thi API version number whenever there is a significant change to the API that any clients need to know about
v3 -> 2021-05-22:
- The updated StockItem "history tracking" now uses a different interface
"""
INVENTREE_API_VERSION = 3
def inventreeInstanceName():

View File

@ -23,10 +23,13 @@ from part.models import Part, PartCategory
from stock.models import StockLocation, StockItem
from common.models import InvenTreeSetting, ColorTheme
from users.models import check_user_role, RuleSet
from InvenTree.helpers import clean_decimal
from .forms import DeleteForm, EditUserForm, SetPasswordForm
from .forms import ColorThemeSelectForm, SettingCategorySelectForm
from .forms import SettingExchangeRatesForm
from .helpers import str2bool
from .exchange import get_exchange_rate_backend
from rest_framework import views
@ -908,3 +911,89 @@ class DatabaseStatsView(AjaxView):
"""
return ctx
class CurrencySettingsView(FormView):
form_class = SettingExchangeRatesForm
template_name = 'InvenTree/settings/currencies.html'
success_url = reverse_lazy('settings-currencies')
exchange_rate_backend = None
def get_exchange_rate_backend(self):
if not self.exchange_rate_backend:
self.exchange_rate_backend = get_exchange_rate_backend()
return self.exchange_rate_backend
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
# Set default API result
if 'api_rates_success' not in context:
context['default_currency'] = True
else:
# Update form
context['form'] = self.get_form()
# Get exchange rate backend
exchange_rate_backend = self.get_exchange_rate_backend()
context['default_currency'] = exchange_rate_backend.base_currency
context['custom_rates'] = exchange_rate_backend.custom_rates
context['exchange_backend'] = exchange_rate_backend.name
return context
def get_form(self):
form = super().get_form()
# Get exchange rate backend
exchange_rate_backend = self.get_exchange_rate_backend()
# Get stored exchange rates
stored_rates = exchange_rate_backend.get_stored_rates()
for field in form.fields:
if not exchange_rate_backend.custom_rates:
# Disable all the fields
form.fields[field].disabled = True
form.fields[field].initial = clean_decimal(stored_rates.get(field, 0))
return form
def post(self, request, *args, **kwargs):
form = self.get_form()
# Get exchange rate backend
exchange_rate_backend = self.get_exchange_rate_backend()
if not exchange_rate_backend.custom_rates:
# Refresh rate from Fixer.IO API
exchange_rate_backend.update_rates(base_currency=exchange_rate_backend.base_currency)
# Check if rates have been updated
if not exchange_rate_backend.get_stored_rates():
# Update context
context = {'api_rates_success': False}
# Return view with updated context
return self.render_to_response(self.get_context_data(form=form, **context))
else:
# Update rates from form
manual_rates = {}
if form.is_valid():
for field, value in form.cleaned_data.items():
manual_rates[field] = clean_decimal(value)
exchange_rate_backend.update_rates(base_currency=exchange_rate_backend.base_currency, **{'rates': manual_rates})
else:
return self.form_invalid(form)
return self.form_valid(form)

View File

@ -9,44 +9,54 @@
{% inventree_title %} | {% trans "Build Order" %} - {{ build }}
{% endblock %}
{% block below_thumbnail %}
<div class='info-messages'>
{% if build.sales_order %}
<div class='alert alert-block alert-info'>
{% object_link 'so-detail' build.sales_order.id build.sales_order as link %}
{% blocktrans %}This Build Order is allocated to Sales Order {{link}}{% endblocktrans %}
</div>
{% endif %}
{% if build.parent %}
<div class='alert alert-block alert-info'>
{% object_link 'build-detail' build.parent.id build.parent as link %}
{% blocktrans %}This Build Order is a child of Build Order {{link}}{% endblocktrans %}
</div>
{% endif %}
{% if build.active %}
{% if build.can_complete %}
<div class='alert alert-block alert-success'>
{% trans "Build Order is ready to mark as completed" %}
</div>
{% endif %}
{% if build.incomplete_count > 0 %}
<div class='alert alert-block alert-danger'>
{% trans "Build Order cannot be completed as outstanding outputs remain" %}
</div>
{% endif %}
{% if build.completed < build.quantity %}
<div class='alert alert-block alert-warning'>
{% trans "Required build quantity has not yet been completed" %}
</div>
{% endif %}
{% if not build.areUntrackedPartsFullyAllocated %}
<div class='alert alert-block alert-warning'>
{% trans "Stock has not been fully allocated to this Build Order" %}
</div>
{% endif %}
{% endif %}
</div>
{% endblock %}
{% block header_pre_content %}
{% if build.sales_order %}
<div class='alert alert-block alert-info'>
{% object_link 'so-detail' build.sales_order.id build.sales_order as link %}
{% blocktrans %}This Build Order is allocated to Sales Order {{link}}{% endblocktrans %}
</div>
{% endif %}
{% if build.parent %}
<div class='alert alert-block alert-info'>
{% object_link 'build-detail' build.parent.id build.parent as link %}
{% blocktrans %}This Build Order is a child of Build Order {{link}}{% endblocktrans %}
</div>
{% endif %}
{% endblock %}
{% block header_post_content %}
{% if build.active %}
{% if build.can_complete %}
<div class='alert alert-block alert-success'>
{% trans "Build Order is ready to mark as completed" %}
</div>
{% endif %}
{% if build.incomplete_count > 0 %}
<div class='alert alert-block alert-danger'>
{% trans "Build Order cannot be completed as outstanding outputs remain" %}
</div>
{% endif %}
{% if build.completed < build.quantity %}
<div class='alert alert-block alert-warning'>
{% trans "Required build quantity has not yet been completed" %}
</div>
{% endif %}
{% if not build.areUntrackedPartsFullyAllocated %}
<div class='alert alert-block alert-warning'>
{% trans "Stock has not been fully allocated to this Build Order" %}
</div>
{% endif %}
{% endif %}
{% endblock %}
{% block thumbnail %}

View File

@ -5,12 +5,11 @@ Django forms for interacting with common objects
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from decimal import Decimal, InvalidOperation
from django import forms
from django.utils.translation import gettext as _
from InvenTree.forms import HelperForm
from InvenTree.helpers import clean_decimal
from .files import FileManager
from .models import InvenTreeSetting

View File

@ -87,6 +87,13 @@ class InvenTreeSetting(models.Model):
'choices': djmoney.settings.CURRENCY_CHOICES,
},
'CUSTOM_EXCHANGE_RATES': {
'name': _('Custom Exchange Rates'),
'description': _('Enable custom exchange rates'),
'validator': bool,
'default': False,
},
'INVENTREE_DOWNLOAD_FROM_URL': {
'name': _('Download from URL'),
'description': _('Allow download of remote images and files from external URL'),

View File

@ -40,13 +40,15 @@ class CompanySimpleTest(TestCase):
self.acme0002 = SupplierPart.objects.get(SKU='ACME0002')
self.zerglphs = SupplierPart.objects.get(SKU='ZERGLPHS')
self.zergm312 = SupplierPart.objects.get(SKU='ZERGM312')
InvenTreeManualExchangeBackend().update_rates()
# Exchange rate backend
backend = InvenTreeManualExchangeBackend()
backend.update_rates(base_currency=backend.base_currency)
Rate.objects.create(
currency='AUD',
value='1.35',
backend_id='inventree',
backend_id=backend.name,
)
def test_company_model(self):

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

@ -9,13 +9,15 @@
{% inventree_title %} | {% trans "Sales Order" %}
{% endblock %}
{% block pre_content %}
{% if order.status == SalesOrderStatus.PENDING and not order.is_fully_allocated %}
<div class='alert alert-block alert-danger'>
{% trans "This SalesOrder has not been fully allocated" %}
{% block below_thumbnail %}
<div class='info-messages'>
{% if order.status == SalesOrderStatus.PENDING and not order.is_fully_allocated %}
<div class='alert alert-block alert-danger'>
{% trans "This Sales Order has not been fully allocated" %}
</div>
{% endif %}
</div>
{% endif %}
{% endblock %}
{% endblock %}
{% block thumbnail %}
<img class='part-thumb'

View File

@ -12,13 +12,6 @@
<div class='panel panel-default panel-inventree'>
{% if part.variant_of %}
<div class='alert alert-info alert-block'>
{% object_link 'part-variants' part.variant_of.id part.variant_of.full_name as link %}
{% blocktrans %}This part is a variant of {{link}}{% endblocktrans %}
</div>
{% endif %}
<div class="row">
<div class="col-sm-6">
{% include "part/part_thumb.html" %}
@ -107,6 +100,15 @@
</table>
</div>
</div>
<div class='info-messages'>
{% if part.variant_of %}
<div class='alert alert-info alert-block'>
{% object_link 'part-variants' part.variant_of.id part.variant_of.full_name as link %}
{% blocktrans %}This part is a variant of {{link}}{% endblocktrans %}
</div>
{% endif %}
</div>
</div>
<div class="col-sm-6">
<table class="table table-striped">

View File

@ -119,6 +119,12 @@ def inventree_version(*args, **kwargs):
return version.inventreeVersion()
@register.simple_tag()
def inventree_api_version(*args, **kwargs):
""" Return InvenTree API version """
return version.inventreeApiVersion()
@register.simple_tag()
def django_version(*args, **kwargs):
""" Return Django version string """

View File

@ -1018,6 +1018,9 @@ class StockTrackingList(generics.ListAPIView):
for item in data:
deltas = item['deltas']
if not deltas:
deltas = {}
# Add location detail
if 'location' in deltas:
try:

View File

@ -14,68 +14,6 @@
{% block pre_content %}
{% include 'stock/loc_link.html' with location=item.location %}
{% setting_object 'STOCK_OWNERSHIP_CONTROL' as owner_control %}
{% if owner_control.value == "True" %}
{% authorized_owners item.owner as owners %}
{% if not user in owners and not user.is_superuser %}
<div class='alert alert-block alert-info'>
{% trans "You are not in the list of owners of this item. This stock item cannot be edited." %}<br>
</div>
{% endif %}
{% endif %}
{% if item.is_building %}
<div class='alert alert-block alert-info'>
{% trans "This stock item is in production and cannot be edited." %}<br>
{% trans "Edit the stock item from the build view." %}<br>
{% if item.build %}
<a href="{% url 'build-detail' item.build.id %}">
<b>{{ item.build }}</b>
</a>
{% endif %}
</div>
{% endif %}
{% if item.hasRequiredTests and not item.passedAllRequiredTests %}
<div class='alert alert-block alert-danger'>
{% trans "This stock item has not passed all required tests" %}
</div>
{% endif %}
{% for allocation in item.sales_order_allocations.all %}
<div class='alert alert-block alert-info'>
{% object_link 'so-detail' allocation.line.order.id allocation.line.order as link %}
{% decimal allocation.quantity as qty %}
{% blocktrans %}This stock item is allocated to Sales Order {{ link }} (Quantity: {{ qty }}){% endblocktrans %}
</div>
{% endfor %}
{% for allocation in item.allocations.all %}
<div class='alert alert-block alert-info'>
{% object_link 'build-detail' allocation.build.id allocation.build %}
{% decimal allocation.quantity as qty %}
{% blocktrans %}This stock item is allocated to Build {{ link }} (Quantity: {{ qty }}){% endblocktrans %}
</div>
{% endfor %}
{% if item.serialized %}
<div class='alert alert-block alert-warning'>
{% trans "This stock item is serialized - it has a unique serial number and the quantity cannot be adjusted." %}
</div>
{% elif item.child_count > 0 %}
<div class='alert alert-block alert-warning'>
{% trans "This stock item cannot be deleted as it has child items" %}
</div>
{% elif item.delete_on_deplete and item.can_delete %}
<div class='alert alert-block alert-warning'>
{% trans "This stock item will be automatically deleted when all stock is depleted." %}
</div>
{% endif %}
{% endblock %}
{% block thumbnail %}
@ -221,6 +159,73 @@
{% endblock %}
{% block below_thumbnail %}
<div class='info-messages'>
{% setting_object 'STOCK_OWNERSHIP_CONTROL' as owner_control %}
{% if owner_control.value == "True" %}
{% authorized_owners item.owner as owners %}
{% if not user in owners and not user.is_superuser %}
<div class='alert alert-block alert-info'>
{% trans "You are not in the list of owners of this item. This stock item cannot be edited." %}<br>
</div>
{% endif %}
{% endif %}
{% if item.is_building %}
<div class='alert alert-block alert-info'>
{% trans "This stock item is in production and cannot be edited." %}<br>
{% trans "Edit the stock item from the build view." %}<br>
{% if item.build %}
<a href="{% url 'build-detail' item.build.id %}">
<b>{{ item.build }}</b>
</a>
{% endif %}
</div>
{% endif %}
{% if item.hasRequiredTests and not item.passedAllRequiredTests %}
<div class='alert alert-block alert-danger'>
{% trans "This stock item has not passed all required tests" %}
</div>
{% endif %}
{% for allocation in item.sales_order_allocations.all %}
<div class='alert alert-block alert-info'>
{% object_link 'so-detail' allocation.line.order.id allocation.line.order as link %}
{% decimal allocation.quantity as qty %}
{% blocktrans %}This stock item is allocated to Sales Order {{ link }} (Quantity: {{ qty }}){% endblocktrans %}
</div>
{% endfor %}
{% for allocation in item.allocations.all %}
<div class='alert alert-block alert-info'>
{% object_link 'build-detail' allocation.build.id allocation.build %}
{% decimal allocation.quantity as qty %}
{% blocktrans %}This stock item is allocated to Build {{ link }} (Quantity: {{ qty }}){% endblocktrans %}
</div>
{% endfor %}
{% if item.serialized %}
<div class='alert alert-block alert-warning'>
{% trans "This stock item is serialized - it has a unique serial number and the quantity cannot be adjusted." %}
</div>
{% elif item.child_count > 0 %}
<div class='alert alert-block alert-warning'>
{% trans "This stock item cannot be deleted as it has child items" %}
</div>
{% elif item.delete_on_deplete and item.can_delete %}
<div class='alert alert-block alert-warning'>
{% trans "This stock item will be automatically deleted when all stock is depleted." %}
</div>
{% endif %}
</div>
{% endblock %}
{% block page_details %}
<h4>{% trans "Stock Item Details" %}</h4>
<table class="table table-striped">

View File

@ -0,0 +1,52 @@
{% extends "InvenTree/settings/settings.html" %}
{% load i18n %}
{% load inventree_extras %}
{% block tabs %}
{% include "InvenTree/settings/tabs.html" with tab='currencies' %}
{% endblock %}
{% block subtitle %}
{% trans "Currency Settings" %}
{% endblock %}
{% 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-dollar-sign" %}
{% include "InvenTree/settings/setting.html" with key="CUSTOM_EXCHANGE_RATES" icon="fa-edit" %}
</tbody>
</table>
<div class='row'>
<div class='col-sm-6'>
<h4>{% blocktrans with cur=default_currency %}Exchange Rates - Convert to {{cur}}{% endblocktrans %}</h4>
</div>
</div>
<form action="{% url 'settings-currencies' %}" method="post">
<div id='exchange_rate_form'>
{% csrf_token %}
{% load crispy_forms_tags %}
{% crispy form %}
{% if custom_rates is False %}
<button type="submit" class='btn btn-primary'>{% trans "Refresh Exchange Rates" %}</button>
{% else %}
<button type="submit" class='btn btn-primary'>{% trans "Update Exchange Rates" %}</button>
{% endif %}
</div>
</form>
{% endblock %}
{% block js_ready %}
{{ block.super }}
{% if api_rates_success is False %}
var alert_msg = {% blocktrans %}"Failed to refresh exchange rates" {% endblocktrans %};
showAlertOrCache("alert-danger", alert_msg, null, 5000);
{% endif %}
{% endblock %}

View File

@ -19,7 +19,6 @@
{% include "InvenTree/settings/setting.html" with key="INVENTREE_INSTANCE_TITLE" icon="fa-info-circle" %}
{% include "InvenTree/settings/setting.html" with key="INVENTREE_BASE_URL" icon="fa-globe" %}
{% include "InvenTree/settings/setting.html" with key="INVENTREE_COMPANY_NAME" icon="fa-building" %}
{% include "InvenTree/settings/setting.html" with key="INVENTREE_DEFAULT_CURRENCY" icon="fa-dollar-sign" %}
{% include "InvenTree/settings/setting.html" with key="INVENTREE_DOWNLOAD_FROM_URL" icon="fa-cloud-download-alt" %}
</tbody>
</table>
@ -32,4 +31,4 @@
</tbody>
</table>
{% endblock %}
{% endblock %}

View File

@ -36,5 +36,8 @@
<li {% if tab == 'so' %} class='active'{% endif %}>
<a href="{% url 'settings-so' %}"><span class='fas fa-truck'></span> {% trans "Sales Orders" %}</a>
</li>
<li {% if tab == 'currencies' %} class='active'{% endif %}>
<a href="{% url 'settings-currencies' %}"><span class='fas fa-dollar-sign'></span> {% trans "Currencies" %}</a>
</li>
</ul>
{% endif %}

View File

@ -29,6 +29,11 @@
{% endif %}
</td>
</tr>
<tr>
<td><span class='fas fa-code'></span></td>
<td>{% trans "API Version" %}</td>
<td>{% inventree_api_version %}{% include "clip.html" %}</td>
</tr>
<tr>
<td><span class='fas fa-hashtag'></span></td>
<td>{% trans "Django Version" %}</td>

View File

@ -1017,6 +1017,11 @@ function loadStockTrackingTable(table, options) {
formatter: function(details, row, index, field) {
var html = `<table class='table table-condensed' id='tracking-table-${row.pk}'>`;
if (!details) {
html += '</table>';
return html;
}
// Location information
if (details.location) {

View File

@ -27,6 +27,8 @@
<!-- Data next to image goes here -->
{% endblock %}
</div>
{% block below_thumbnail %}
{% endblock %}
</div>
<div class='col-sm-6'>
{% block page_details %}

View File

@ -1,3 +1,6 @@
"commit_message": "Fix: New translations %original_file_name% from Crowdin"
"append_commit_message": false
files:
- source: /InvenTree/locale/en/LC_MESSAGES/django.po
translation: /InvenTree/locale/%two_letters_code%/LC_MESSAGES/%original_file_name%

View File

@ -41,9 +41,10 @@ LABEL org.label-schema.schema-version="1.0" \
# Create user account
RUN addgroup -S inventreegroup && adduser -S inventree -G inventreegroup
WORKDIR ${INVENTREE_HOME}
RUN mkdir ${INVENTREE_STATIC_ROOT}
RUN mkdir -p ${INVENTREE_STATIC_ROOT}
# Install required system packages
RUN apk add --no-cache git make bash \

View File

@ -3,12 +3,12 @@
# Create required directory structure (if it does not already exist)
if [[ ! -d "$INVENTREE_STATIC_ROOT" ]]; then
echo "Creating directory $INVENTREE_STATIC_ROOT"
mkdir $INVENTREE_STATIC_ROOT
mkdir -p $INVENTREE_STATIC_ROOT
fi
if [[ ! -d "$INVENTREE_MEDIA_ROOT" ]]; then
echo "Creating directory $INVENTREE_MEDIA_ROOT"
mkdir $INVENTREE_MEDIA_ROOT
mkdir -p $INVENTREE_MEDIA_ROOT
fi
# Check if "config.yaml" has been copied into the correct location

View File

@ -3,12 +3,12 @@
# Create required directory structure (if it does not already exist)
if [[ ! -d "$INVENTREE_STATIC_ROOT" ]]; then
echo "Creating directory $INVENTREE_STATIC_ROOT"
mkdir $INVENTREE_STATIC_ROOT
mkdir -p $INVENTREE_STATIC_ROOT
fi
if [[ ! -d "$INVENTREE_MEDIA_ROOT" ]]; then
echo "Creating directory $INVENTREE_MEDIA_ROOT"
mkdir $INVENTREE_MEDIA_ROOT
mkdir -p $INVENTREE_MEDIA_ROOT
fi
# Check if "config.yaml" has been copied into the correct location