Merge branch 'inventree:master' into webhooks

This commit is contained in:
Matthias Mair 2021-12-02 09:22:48 +01:00 committed by GitHub
commit fcf7f615aa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
51 changed files with 14255 additions and 13007 deletions

View File

@ -257,7 +257,7 @@ INSTALLED_APPS = [
'django.contrib.admin', 'django.contrib.admin',
'django.contrib.auth', 'django.contrib.auth',
'django.contrib.contenttypes', 'django.contrib.contenttypes',
'django.contrib.sessions', 'user_sessions', # db user sessions
'django.contrib.messages', 'django.contrib.messages',
'django.contrib.staticfiles', 'django.contrib.staticfiles',
'django.contrib.sites', 'django.contrib.sites',
@ -299,7 +299,7 @@ INSTALLED_APPS = [
MIDDLEWARE = CONFIG.get('middleware', [ MIDDLEWARE = CONFIG.get('middleware', [
'django.middleware.security.SecurityMiddleware', 'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware', 'user_sessions.middleware.SessionMiddleware', # db user sessions
'django.middleware.locale.LocaleMiddleware', 'django.middleware.locale.LocaleMiddleware',
'django.middleware.common.CommonMiddleware', 'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware', 'django.middleware.csrf.CsrfViewMiddleware',
@ -626,6 +626,12 @@ if _cache_host:
# as well # as well
Q_CLUSTER["django_redis"] = "worker" Q_CLUSTER["django_redis"] = "worker"
# database user sessions
SESSION_ENGINE = 'user_sessions.backends.db'
LOGOUT_REDIRECT_URL = 'index'
SILENCED_SYSTEM_CHECKS = [
'admin.E410',
]
# Password validation # Password validation
# https://docs.djangoproject.com/en/1.10/ref/settings/#auth-password-validators # https://docs.djangoproject.com/en/1.10/ref/settings/#auth-password-validators

View File

@ -781,6 +781,7 @@ input[type="submit"] {
.btn-small { .btn-small {
padding: 3px; padding: 3px;
padding-left: 5px; padding-left: 5px;
padding-right: 5px;
} }
.btn-remove { .btn-remove {

View File

@ -38,6 +38,7 @@ from rest_framework.documentation import include_docs_urls
from .views import auth_request from .views import auth_request
from .views import IndexView, SearchView, DatabaseStatsView from .views import IndexView, SearchView, DatabaseStatsView
from .views import SettingsView, EditUserView, SetPasswordView, CustomEmailView, CustomConnectionsView, CustomPasswordResetFromKeyView from .views import SettingsView, EditUserView, SetPasswordView, CustomEmailView, CustomConnectionsView, CustomPasswordResetFromKeyView
from .views import CustomSessionDeleteView, CustomSessionDeleteOtherView
from .views import CurrencyRefreshView from .views import CurrencyRefreshView
from .views import AppearanceSelectView, SettingCategorySelectView from .views import AppearanceSelectView, SettingCategorySelectView
from .views import DynamicJsView from .views import DynamicJsView
@ -159,6 +160,10 @@ urlpatterns = [
url(r'^markdownx/', include('markdownx.urls')), url(r'^markdownx/', include('markdownx.urls')),
# DB user sessions
url(r'^accounts/sessions/other/delete/$', view=CustomSessionDeleteOtherView.as_view(), name='session_delete_other', ),
url(r'^accounts/sessions/(?P<pk>\w+)/delete/$', view=CustomSessionDeleteView.as_view(), name='session_delete', ),
# Single Sign On / allauth # Single Sign On / allauth
# overrides of urlpatterns # overrides of urlpatterns
url(r'^accounts/email/', CustomEmailView.as_view(), name='account_email'), url(r'^accounts/email/', CustomEmailView.as_view(), name='account_email'),

View File

@ -12,11 +12,15 @@ import common.models
INVENTREE_SW_VERSION = "0.6.0 dev" INVENTREE_SW_VERSION = "0.6.0 dev"
# InvenTree API version # InvenTree API version
INVENTREE_API_VERSION = 18 INVENTREE_API_VERSION = 19
""" """
Increment this API version number whenever there is a significant change to the API that any clients need to know about Increment this API version number whenever there is a significant change to the API that any clients need to know about
v19 -> 2021-12-02
- Adds the ability to filter the StockItem API by "part_tree"
- Returns only stock items which match a particular part.tree_id field
v18 -> 2021-11-15 v18 -> 2021-11-15
- Adds the ability to filter BomItem API by "uses" field - Adds the ability to filter BomItem API by "uses" field
- This returns a list of all BomItems which "use" the specified part - This returns a list of all BomItems which "use" the specified part

View File

@ -14,6 +14,7 @@ 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.utils.timezone import now
from django.shortcuts import redirect from django.shortcuts import redirect
from django.conf import settings from django.conf import settings
@ -29,6 +30,7 @@ from allauth.socialaccount.forms import DisconnectForm
from allauth.account.models import EmailAddress from allauth.account.models import EmailAddress
from allauth.account.views import EmailView, PasswordResetFromKeyView from allauth.account.views import EmailView, PasswordResetFromKeyView
from allauth.socialaccount.views import ConnectionsView from allauth.socialaccount.views import ConnectionsView
from user_sessions.views import SessionDeleteView, SessionDeleteOtherView
from common.settings import currency_code_default, currency_codes from common.settings import currency_code_default, currency_codes
@ -733,6 +735,10 @@ class SettingsView(TemplateView):
ctx["request"] = self.request ctx["request"] = self.request
ctx['social_form'] = DisconnectForm(request=self.request) ctx['social_form'] = DisconnectForm(request=self.request)
# user db sessions
ctx['session_key'] = self.request.session.session_key
ctx['session_list'] = self.request.user.session_set.filter(expire_date__gt=now()).order_by('-last_activity')
return ctx return ctx
@ -766,6 +772,20 @@ class CustomPasswordResetFromKeyView(PasswordResetFromKeyView):
success_url = reverse_lazy("account_login") success_url = reverse_lazy("account_login")
class UserSessionOverride():
"""overrides sucessurl to lead to settings"""
def get_success_url(self):
return str(reverse_lazy('settings'))
class CustomSessionDeleteView(UserSessionOverride, SessionDeleteView):
pass
class CustomSessionDeleteOtherView(UserSessionOverride, SessionDeleteOtherView):
pass
class CurrencyRefreshView(RedirectView): class CurrencyRefreshView(RedirectView):
""" """
POST endpoint to refresh / update exchange rates POST endpoint to refresh / update exchange rates

View File

@ -12,7 +12,7 @@
{% block breadcrumbs %} {% block breadcrumbs %}
<li class='breadcrumb-item'><a href='{% url "build-index" %}'>{% trans "Build Orders" %}</a></li> <li class='breadcrumb-item'><a href='{% url "build-index" %}'>{% trans "Build Orders" %}</a></li>
<li class="breadcrumb-item active" aria-current="page"><a href='{% url "build-detail" build.id %}'>{{ build }}</a></li> <li class="breadcrumb-item active" aria-current="page"><a href='{% url "build-detail" build.id %}'>{{ build }}</a></li>
{% endblock %} {% endblock breadcrumbs %}
{% block thumbnail %} {% block thumbnail %}
<img class="part-thumb" <img class="part-thumb"
@ -21,7 +21,7 @@ src="{{ build.part.image.url }}"
{% else %} {% else %}
src="{% static 'img/blank_image.png' %}" src="{% static 'img/blank_image.png' %}"
{% endif %}/> {% endif %}/>
{% endblock %} {% endblock thumbnail %}
{% block heading %} {% block heading %}
{% trans "Build Order" %} {{ build }} {% trans "Build Order" %} {{ build }}
@ -66,11 +66,23 @@ src="{% static 'img/blank_image.png' %}"
</button> </button>
{% endif %} {% endif %}
{% endif %} {% endif %}
{% endblock %} {% endblock actions %}
{% block details %} {% block details %}
<p>{{ build.title }}</p> <table class='table table-striped table-condensed'>
<col width='25'>
<tr>
<td><span class='fas fa-shapes'></span></td>
<td>{% trans "Part" %}</td>
<td><a href="{% url 'part-detail' build.part.id %}?display=build-orders">{{ build.part.full_name }}</a></td>
</tr>
<tr>
<td><span class='fas fa-info-circle'></span></td>
<td>{% trans "Build Description" %}</td>
<td>{{ build.title }}</td>
</tr>
</table>
<div class='info-messages'> <div class='info-messages'>
{% if build.sales_order %} {% if build.sales_order %}
@ -114,11 +126,7 @@ src="{% static 'img/blank_image.png' %}"
{% block details_right %} {% block details_right %}
<table class='table table-striped table-condensed'> <table class='table table-striped table-condensed'>
<tr> <col width='25'>
<td><span class='fas fa-shapes'></span></td>
<td>{% trans "Part" %}</td>
<td><a href="{% url 'part-detail' build.part.id %}?display=build-orders">{{ build.part.full_name }}</a></td>
</tr>
<tr> <tr>
<td></td> <td></td>
<td>{% trans "Quantity" %}</td> <td>{% trans "Quantity" %}</td>

View File

@ -19,21 +19,26 @@
{% include "admin_button.html" with url=url %} {% include "admin_button.html" with url=url %}
{% endif %} {% endif %}
{% if company.is_supplier and roles.purchase_order.add %} {% if company.is_supplier and roles.purchase_order.add %}
<button type='button' class='btn btn-outline-secondary' id='company-order-2' title='{% trans "Create Purchase Order" %}'> <button type='button' class='btn btn-outline-primary' id='company-order-2' title='{% trans "Create Purchase Order" %}'>
<span class='fas fa-shopping-cart'/> <span class='fas fa-shopping-cart'/>
</button> </button>
{% endif %} {% endif %}
{% if perms.company.change_company %} <button id='company-edit-actions' title='{% trans "Company actions" %}' class='btn btn-outline-secondary dropdown-toggle' type='button' data-bs-toggle='dropdown'>
<button type='button' class='btn btn-outline-secondary' id='company-edit' title='{% trans "Edit company information" %}'> <span class='fas fa-tools'></span> <span class='caret'></span>
<span class='fas fa-edit icon-green'/>
</button> </button>
{% endif %} <ul class='dropdown-menu' role='menu'>
{% if perms.company.delete_company %} {% if perms.company.change_company %}
<button type='button' class='btn btn-outline-secondary' id='company-delete' title='{% trans "Delete Company" %}'> <li><a class='dropdown-item' href='#' id='company-edit' title='{% trans "Edit company information" %}'>
<span class='fas fa-trash-alt icon-red'/> <span class='fas fa-edit icon-green'></span> {% trans "Edit Company" %}
</button> </a></li>
{% endif %} {% endif %}
{% endblock %} {% if perms.company.delete_company %}
<li><a class='dropdown-item' href='#' id='company-delete' title='{% trans "Delete company" %}'>
<span class='fas fa-trash-alt icon-red'></span> {% trans "Delete Company" %}
</a></li>
{% endif %}
</ul>
{% endblock actions %}
{% block thumbnail %} {% block thumbnail %}
<div class='dropzone part-thumb-container' id='company-thumb'> <div class='dropzone part-thumb-container' id='company-thumb'>
@ -56,7 +61,29 @@
{% endblock %} {% endblock %}
{% block details %} {% block details %}
<p>{{ company.description }}</p> <table class='table table-striped table-condensed'>
<col width='25'>
<tr>
<td><span class='fas fa-info-circle'></span></td>
<td>{% trans "Description" %}</td>
<td>{{ company.description }}</td>
</tr>
<tr>
<td><span class='fas fa-industry'></span></td>
<td>{%trans "Manufacturer" %}</td>
<td>{% include "yesnolabel.html" with value=company.is_manufacturer %}</td>
</tr>
<tr>
<td><span class='fas fa-building'></span></td>
<td>{% trans "Supplier" %}</td>
<td>{% include 'yesnolabel.html' with value=company.is_supplier %}</td>
</tr>
<tr>
<td><span class='fas fa-user-tie'></span></td>
<td>{% trans "Customer" %}</td>
<td>{% include 'yesnolabel.html' with value=company.is_customer %}</td>
</tr>
</table>
{% endblock %} {% endblock %}
@ -110,22 +137,6 @@
<td>{{ company.contact }}{% include "clip.html"%}</td> <td>{{ company.contact }}{% include "clip.html"%}</td>
</tr> </tr>
{% endif %} {% endif %}
<tr>
<td><span class='fas fa-industry'></span></td>
<td>{%trans "Manufacturer" %}</td>
<td>{% include "yesnolabel.html" with value=company.is_manufacturer %}</td>
</tr>
<tr>
<td><span class='fas fa-building'></span></td>
<td>{% trans "Supplier" %}</td>
<td>{% include 'yesnolabel.html' with value=company.is_supplier %}</td>
</tr>
<tr>
<td><span class='fas fa-user-tie'></span></td>
<td>{% trans "Customer" %}</td>
<td>{% include 'yesnolabel.html' with value=company.is_customer %}</td>
</tr>
</table> </table>
{% endblock %} {% endblock %}

View File

@ -8,7 +8,7 @@ InvenTree | {% trans "Manufacturer Part" %}
{% block sidebar %} {% block sidebar %}
{% include "company/manufacturer_part_sidebar.html" %} {% include "company/manufacturer_part_sidebar.html" %}
{% endblock %} {% endblock sidebar %}
{% block breadcrumbs %} {% block breadcrumbs %}
<li class='breadcrumb-item'><a href='{% url "manufacturer-index" %}'>{% trans "Manufacturers" %}</a></li> <li class='breadcrumb-item'><a href='{% url "manufacturer-index" %}'>{% trans "Manufacturers" %}</a></li>
@ -16,13 +16,13 @@ InvenTree | {% trans "Manufacturer Part" %}
<li class='breadcrumb-item'><a href='{% url "company-detail" part.manufacturer.id %}'>{{ part.manufacturer.name }}</a></li> <li class='breadcrumb-item'><a href='{% url "company-detail" part.manufacturer.id %}'>{{ part.manufacturer.name }}</a></li>
{% endif %} {% endif %}
<li class="breadcrumb-item active" aria-current="page"><a href='{% url "manufacturer-part-detail" part.id %}'>{{ part.MPN }}</a></li> <li class="breadcrumb-item active" aria-current="page"><a href='{% url "manufacturer-part-detail" part.id %}'>{{ part.MPN }}</a></li>
{% endblock %} {% endblock breadcrumbs %}
{% block heading %} {% block heading %}
<h4> <h4>
{% trans "Manufacturer Part" %}: {{ part.part.full_name }} {% trans "Manufacturer Part" %}: {{ part.part.full_name }}
</h4> </h4>
{% endblock %} {% endblock heading %}
{% block actions %} {% block actions %}
{% if user.is_staff and perms.company.change_company %} {% if user.is_staff and perms.company.change_company %}
@ -46,7 +46,7 @@ InvenTree | {% trans "Manufacturer Part" %}
</button> </button>
{% endif %} {% endif %}
{% endif %} {% endif %}
{% endblock %} {% endblock actions %}
{% block thumbnail %} {% block thumbnail %}
<img class='part-thumb' <img class='part-thumb'
@ -55,15 +55,11 @@ src='{{ part.part.image.url }}'
{% else %} {% else %}
src="{% static 'img/blank_image.png' %}" src="{% static 'img/blank_image.png' %}"
{% endif %}/> {% endif %}/>
{% endblock %} {% endblock thumbnail %}
{% block details %} {% block details %}
{% endblock %} <table class='table table-striped table-condensed'>
{% block details_right %}
<table class="table table-striped table-condensed">
<col width='25'> <col width='25'>
<tr> <tr>
<td><span class='fas fa-shapes'></span></td> <td><span class='fas fa-shapes'></span></td>
@ -81,6 +77,25 @@ src="{% static 'img/blank_image.png' %}"
<td>{{ part.description }}{% include "clip.html"%}</td> <td>{{ part.description }}{% include "clip.html"%}</td>
</tr> </tr>
{% endif %} {% endif %}
</table>
{% endblock details %}
{% block details_right %}
<table class="table table-striped table-condensed">
<col width='25'>
<tr>
<td><span class='fas fa-industry'></span></td>
<td>{% trans "Manufacturer" %}</td>
<td><a href="{% url 'company-detail' part.manufacturer.id %}">{{ part.manufacturer.name }}</a>{% include "clip.html"%}</td>
</tr>
<tr>
<td><span class='fas fa-hashtag'></span></td>
<td>{% trans "MPN" %}</td>
<td>{{ part.MPN }}{% include "clip.html"%}</td>
</tr>
{% if part.link %} {% if part.link %}
<tr> <tr>
<td><span class='fas fa-link'></span></td> <td><span class='fas fa-link'></span></td>
@ -88,17 +103,8 @@ src="{% static 'img/blank_image.png' %}"
<td><a href="{{ part.link }}">{{ part.link }}</a>{% include "clip.html"%}</td> <td><a href="{{ part.link }}">{{ part.link }}</a>{% include "clip.html"%}</td>
</tr> </tr>
{% endif %} {% endif %}
<tr>
<td><span class='fas fa-industry'></span></td>
<td>{% trans "Manufacturer" %}</td>
<td><a href="{% url 'company-detail' part.manufacturer.id %}">{{ part.manufacturer.name }}</a>{% include "clip.html"%}</td></tr>
<tr>
<td><span class='fas fa-hashtag'></span></td>
<td>{% trans "MPN" %}</td>
<td>{{ part.MPN }}{% include "clip.html"%}</td>
</tr>
</table> </table>
{% endblock %} {% endblock details_right %}
{% block page_content %} {% block page_content %}

View File

@ -5,11 +5,11 @@
{% block page_title %} {% block page_title %}
{% inventree_title %} | {% trans "Supplier Part" %} {% inventree_title %} | {% trans "Supplier Part" %}
{% endblock %} {% endblock page_title %}
{% block sidebar %} {% block sidebar %}
{% include "company/supplier_part_sidebar.html" %} {% include "company/supplier_part_sidebar.html" %}
{% endblock %} {% endblock sidebar %}
{% block breadcrumbs %} {% block breadcrumbs %}
<li class='breadcrumb-item'><a href='{% url "supplier-index" %}'>{% trans "Suppliers" %}</a></li> <li class='breadcrumb-item'><a href='{% url "supplier-index" %}'>{% trans "Suppliers" %}</a></li>
@ -17,13 +17,13 @@
<li class='breadcrumb-item'><a href='{% url "company-detail" part.supplier.id %}'>{{ part.supplier.name }}</a></li> <li class='breadcrumb-item'><a href='{% url "company-detail" part.supplier.id %}'>{{ part.supplier.name }}</a></li>
{% endif %} {% endif %}
<li class="breadcrumb-item active" aria-current="page"><a href='{% url "supplier-part-detail" part.id %}'>{{ part.SKU }}</a></li> <li class="breadcrumb-item active" aria-current="page"><a href='{% url "supplier-part-detail" part.id %}'>{{ part.SKU }}</a></li>
{% endblock %} {% endblock breadcrumbs %}
{% block heading %} {% block heading %}
<h4> <h4>
{% trans "Supplier Part" %}: {{ part.SKU }} {% trans "Supplier Part" %}: {{ part.SKU }}
</h4> </h4>
{% endblock %} {% endblock heading %}
{% block actions %} {% block actions %}
{% if user.is_staff and perms.company.change_company %} {% if user.is_staff and perms.company.change_company %}
@ -43,7 +43,7 @@
<span class='fas fa-trash-alt icon-red'/> <span class='fas fa-trash-alt icon-red'/>
</button> </button>
{% endif %} {% endif %}
{% endblock %} {% endblock actions %}
{% block thumbnail %} {% block thumbnail %}
<img class='part-thumb' <img class='part-thumb'
@ -56,15 +56,7 @@ src="{% static 'img/blank_image.png' %}"
{% block details %} {% block details %}
<p> <table class='table table-striped table-condensed'>
{{ part.part.full_name }}
</p>
{% endblock %}
{% block details_right %}
<table class="table table-striped table-condensed">
<col width='25'> <col width='25'>
<tr> <tr>
<td><span class='fas fa-shapes'></span></td> <td><span class='fas fa-shapes'></span></td>
@ -82,13 +74,14 @@ src="{% static 'img/blank_image.png' %}"
<td>{{ part.description }}{% include "clip.html"%}</td> <td>{{ part.description }}{% include "clip.html"%}</td>
</tr> </tr>
{% endif %} {% endif %}
{% if part.link %} </table>
<tr>
<td><span class='fas fa-link'></span></td> {% endblock details %}
<td>{% trans "External Link" %}</td>
<td><a href="{{ part.link }}">{{ part.link }}</a>{% include "clip.html"%}</td> {% block details_right %}
</tr>
{% endif %} <table class="table table-striped table-condensed">
<col width='25'>
<tr> <tr>
<td><span class='fas fa-building'></span></td> <td><span class='fas fa-building'></span></td>
<td>{% trans "Supplier" %}</td> <td>{% trans "Supplier" %}</td>
@ -127,6 +120,13 @@ src="{% static 'img/blank_image.png' %}"
<td>{{ part.note }}{% include "clip.html"%}</td> <td>{{ part.note }}{% include "clip.html"%}</td>
</tr> </tr>
{% endif %} {% endif %}
{% if part.link %}
<tr>
<td><span class='fas fa-link'></span></td>
<td>{% trans "External Link" %}</td>
<td><a href="{{ part.link }}">{{ part.link }}</a>{% include "clip.html"%}</td>
</tr>
{% endif %}
</table> </table>
{% endblock %} {% endblock %}

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

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

File diff suppressed because it is too large Load Diff

View File

@ -53,15 +53,17 @@
<span class='fas fa-shopping-cart icon-blue'></span> <span class='fas fa-shopping-cart icon-blue'></span>
</button> </button>
{% elif order.status == PurchaseOrderStatus.PLACED %} {% elif order.status == PurchaseOrderStatus.PLACED %}
<button type='button' class='btn btn-outline-secondary' id='receive-order' title='{% trans "Receive items" %}'> <button type='button' class='btn btn-primary' id='receive-order' title='{% trans "Receive items" %}'>
<span class='fas fa-sign-in-alt icon-blue'></span> <span class='fas fa-sign-in-alt'></span>
{% trans "Receive Items" %}
</button> </button>
<button type='button' class='btn btn-outline-secondary' id='complete-order' title='{% trans "Mark order as complete" %}'> <button type='button' class='btn btn-success' id='complete-order' title='{% trans "Mark order as complete" %}'>
<span class='fas fa-check-circle icon-green'></span> <span class='fas fa-check-circle'></span>
{% trans "Complete Order" %}
</button> </button>
{% endif %} {% endif %}
{% endif %} {% endif %}
{% endblock %} {% endblock actions %}
{% block thumbnail %} {% block thumbnail %}
<img class='part-thumb' <img class='part-thumb'
@ -75,24 +77,18 @@ src="{% static 'img/blank_image.png' %}"
{% block details %} {% block details %}
<h4> <table class='table table-striped table-condensed'>
{% purchase_order_status_label order.status large=True %}
{% if order.is_overdue %}
<span class='badge rounded-pill bg-danger'>{% trans "Overdue" %}</span>
{% endif %}
</h4>
<p>{{ order.description }}{% include "clip.html"%}</p>
{% endblock %}
{% block details_right %}
<table class='table'>
<col width='25'> <col width='25'>
<tr> <tr>
<td><span class='fas fa-hashtag'></span></td> <td><span class='fas fa-hashtag'></span></td>
<td>{% trans "Order Reference" %}</td> <td>{% trans "Order Reference" %}</td>
<td>{% settings_value 'PURCHASEORDER_REFERENCE_PREFIX' %}{{ order.reference }}{% include "clip.html"%}</td> <td>{% settings_value 'PURCHASEORDER_REFERENCE_PREFIX' %}{{ order.reference }}{% include "clip.html"%}</td>
</tr> </tr>
<tr>
<td><span class='fas fa-info-circle'></span></td>
<td>{% trans "Order Description" %}</td>
<td>{{ order.description }}{% include "clip.html" %}</td>
</tr>
<tr> <tr>
<td><span class='fas fa-info'></span></td> <td><span class='fas fa-info'></span></td>
<td>{% trans "Order Status" %}</td> <td>{% trans "Order Status" %}</td>
@ -103,6 +99,14 @@ src="{% static 'img/blank_image.png' %}"
{% endif %} {% endif %}
</td> </td>
</tr> </tr>
</table>
{% endblock %}
{% block details_right %}
<table class='table table-condensed table-striped'>
<col width='25'>
<tr> <tr>
<td><span class='fas fa-building'></span></td> <td><span class='fas fa-building'></span></td>
<td>{% trans "Supplier" %}</td> <td>{% trans "Supplier" %}</td>

View File

@ -27,7 +27,7 @@
<span class='fas fa-plus-circle'></span> {% trans "Add Line Item" %} <span class='fas fa-plus-circle'></span> {% trans "Add Line Item" %}
</button> </button>
{% elif order.status == PurchaseOrderStatus.PLACED %} {% elif order.status == PurchaseOrderStatus.PLACED %}
<button type='button' class='btn btn-success' id='receive-selected-items' title='{% trans "Receive selected items" %}'> <button type='button' class='btn btn-primary' id='receive-selected-items' title='{% trans "Receive selected items" %}'>
<span class='fas fa-sign-in-alt'></span> {% trans "Receive Items" %} <span class='fas fa-sign-in-alt'></span> {% trans "Receive Items" %}
</button> </button>
{% endif %} {% endif %}

View File

@ -68,17 +68,33 @@ src="{% static 'img/blank_image.png' %}"
</button> </button>
{% endif %} {% endif %}
{% endif %} {% endif %}
{% endblock %} {% endblock actions %}
{% block details %} {% block details %}
<h4> <table class='table table-striped table-condensed'>
{% sales_order_status_label order.status large=True %} <col width='25'>
<tr>
<td><span class='fas fa-hashtag'></span></td>
<td>{% trans "Order Reference" %}</td>
<td>{% settings_value 'SALESORDER_REFERENCE_PREFIX' %}{{ order.reference }}{% include "clip.html"%}</td>
</tr>
<tr>
<td><span class='fas fa-info-circle'></span></td>
<td>{% trans "Order Description" %}</td>
<td>{{ order.description }}{% include "clip.html" %}</td>
</tr>
<tr>
<td><span class='fas fa-info'></span></td>
<td>{% trans "Order Status" %}</td>
<td>
{% sales_order_status_label order.status %}
{% if order.is_overdue %} {% if order.is_overdue %}
<span class='badge rounded-pill bg-danger'>{% trans "Overdue" %}</span> <span class='badge rounded-pill bg-danger'>{% trans "Overdue" %}</span>
{% endif %} {% endif %}
</h4> </td>
<p>{{ order.description }}{% include "clip.html"%}</p> </tr>
</table>
<div class='info-messages'> <div class='info-messages'>
{% if order.status == SalesOrderStatus.PENDING and not order.is_fully_allocated %} {% if order.status == SalesOrderStatus.PENDING and not order.is_fully_allocated %}
@ -93,21 +109,6 @@ src="{% static 'img/blank_image.png' %}"
{% block details_right %} {% block details_right %}
<table class='table table-striped table-condensed'> <table class='table table-striped table-condensed'>
<col width='25'> <col width='25'>
<tr>
<td><span class='fas fa-hashtag'></span></td>
<td>{% trans "Order Reference" %}</td>
<td>{% settings_value 'SALESORDER_REFERENCE_PREFIX' %}{{ order.reference }}{% include "clip.html"%}</td>
</tr>
<tr>
<td><span class='fas fa-info'></span></td>
<td>{% trans "Order Status" %}</td>
<td>
{% sales_order_status_label order.status %}
{% if order.is_overdue %}
<span class='badge rounded-pill bg-danger'>{% trans "Overdue" %}</span>
{% endif %}
</td>
</tr>
{% if order.customer %} {% if order.customer %}
<tr> <tr>
<td><span class='fas fa-building'></span></td> <td><span class='fas fa-building'></span></td>

View File

@ -1075,6 +1075,7 @@ class PartList(generics.ListCreateAPIView):
'revision', 'revision',
'keywords', 'keywords',
'category__name', 'category__name',
'manufacturer_parts__MPN',
] ]

View File

@ -61,29 +61,43 @@
{% endblock %} {% endblock %}
{% block details_left %} {% block details_left %}
{% if category %} <table class='table table-striped table-condensed'>
<p>{{ category.description }}</p> <col width='25'>
{% else %} {% if category %}
<p>{% trans "Top level part category" %}</p> {% if category.description %}
{% endif %} <tr>
<td><span class='fas fa-info-circle'></span></td>
{% endblock %} <td>{% trans "Description" %}</td>
<td>{{ category.description }}</td>
</tr>
{% endif %}
<tr>
<td><span class='fas fa-sitemap'></span></td>
<td>{% trans "Category Path" %}</td>
<td>{{ category.pathstring }}</td>
</tr>
{% if category.default_keywords %}
<tr>
<td><span class='fas fa-key'></span></td>
<td>{% trans "Keywords" %}</td>
<td>{{ category.default_keywords }}</td>
</tr>
{% endif %}
{% else %}
<tr>
<td><span class='fas fa-sitemap'></span></td>
<td>{% trans "Category Path" %}</td>
<td><em>{% trans "Top level part category" %}</em></td>
</tr>
{% endif %}
</table>
{% endblock details_left %}
{% block details_right %} {% block details_right %}
{% if category %} {% if category %}
<table class='table table-condensed table-striped'> <table class='table table-condensed table-striped'>
<col width='25'> <col width='25'>
<tr>
<td><span class='fas fa-sitemap'></span></td>
<td>{% trans "Category Path" %}</td>
<td>{{ category.pathstring }}</td>
</tr>
<tr>
<td><span class='fas fa-info-circle'></span></td>
<td>{% trans "Category Description" %}</td>
<td>{{ category.description }}</td>
</tr>
{% if category.default_location %} {% if category.default_location %}
<tr> <tr>
<td><span class='fas fa-map-marker-alt'></span></td> <td><span class='fas fa-map-marker-alt'></span></td>
@ -91,13 +105,6 @@
<td><a href="{% url 'stock-location-detail' category.default_location.pk %}">{{ category.default_location.pathstring }}</a></td> <td><a href="{% url 'stock-location-detail' category.default_location.pk %}">{{ category.default_location.pathstring }}</a></td>
</tr> </tr>
{% endif %} {% endif %}
{% if category.default_keywords %}
<tr>
<td><span class='fas fa-key'></span></td>
<td>{% trans "Keywords" %}</td>
<td>{{ category.default_keywords }}</td>
</tr>
{% endif %}
<tr> <tr>
<td><span class='fas fa-sitemap'></span></td> <td><span class='fas fa-sitemap'></span></td>
<td>{% trans "Subcategories" %}</td> <td>{% trans "Subcategories" %}</td>
@ -124,7 +131,7 @@
</tr> </tr>
</table> </table>
{% endif %} {% endif %}
{% endblock %} {% endblock details_right %}
{% block page_content %} {% block page_content %}

View File

@ -11,113 +11,6 @@
{% block page_content %} {% block page_content %}
<div class='panel panel-hidden' id='panel-part-details'>
<div class='panel-heading'>
<h4>{% trans "Part Details" %}</h4>
</div>
<div class='panel-content'>
<!-- Details Table -->
<table class="table table-striped table-condensed">
<col width='25'>
<tr>
<td><span class='fas fa-shapes'></span></td>
<td>{% trans "Name" %}</td>
<td>{{ part.name }}{% include "clip.html"%}</td>
</tr>
<tr>
<td><span class='fas fa-info-circle'></span></td>
<td>{% trans "Description" %}</td>
<td>{{ part.description }}{% include "clip.html"%}</td>
</tr>
{% if part.category %}
<tr>
<td><span class='fas fa-sitemap'></span></td>
<td>{% trans "Category" %}</td>
<td>
<a href='{% url "category-detail" part.category.pk %}'>{{ part.category.name }}</a>
</td>
</tr>
{% endif %}
{% if part.IPN %}
<tr>
<td><span class='fas fa-tag'></span></td>
<td>{% trans "IPN" %}</td>
<td>{{ part.IPN }}{% include "clip.html"%}</td>
</tr>
{% endif %}
{% if part.revision %}
<tr>
<td><span class='fas fa-code-branch'></span></td>
<td>{% trans "Revision" %}</td>
<td>{{ part.revision }}{% include "clip.html"%}</td>
</tr>
{% endif %}
{% if part.units %}
<tr>
<td></td>
<td>{% trans "Units" %}</td>
<td>{{ part.units }}</td>
</tr>
{% endif %}
{% if part.minimum_stock %}
<tr>
<td><span class='fas fa-flag'></span></td>
<td>{% trans "Minimum stock level" %}</td>
<td>{{ part.minimum_stock }}</td>
</tr>
{% endif %}
{% if part.keywords %}
<tr>
<td><span class='fas fa-key'></span></td>
<td>{% trans "Keywords" %}</td>
<td>{{ part.keywords }}{% include "clip.html"%}</td>
</tr>
{% endif %}
{% if part.link %}
<tr>
<td><span class='fas fa-link'></span></td>
<td>{% trans "External Link" %}</td>
<td><a href="{{ part.link }}">{{ part.link }}</a>{% include "clip.html"%}</td>
</tr>
{% endif %}
<tr>
<td><span class='fas fa-calendar-alt'></span></td>
<td>{% trans "Creation Date" %}</td>
<td>
{{ part.creation_date }}
{% if part.creation_user %}
<span class='badge badge-right rounded-pill bg-dark'>{{ part.creation_user }}</span>
{% endif %}
</td>
</tr>
{% if part.trackable and part.getLatestSerialNumber %}
<tr>
<td><span class='fas fa-hashtag'></span></td>
<td>{% trans "Latest Serial Number" %}</td>
<td>{{ part.getLatestSerialNumber }}{% include "clip.html"%}</td>
</tr>
{% endif %}
{% if part.default_location %}
<tr>
<td><span class='fas fa-search-location'></span></td>
<td>{% trans "Default Location" %}</td>
<td>
<a href='{% url "stock-location-detail" part.default_location.pk %}'>{{ part.default_location }}</a>
</td>
</tr>
{% endif %}
{% if part.default_supplier %}
<tr>
<td><span class='fas fa-building'></span></td>
<td>{% trans "Default Supplier" %}</td>
<td>{{ part.default_supplier }}</td>
</tr>
{% endif %}
</table>
</div>
</div>
<div class='panel panel-hidden' id='panel-part-stock'> <div class='panel panel-hidden' id='panel-part-stock'>
<div class='panel-heading'> <div class='panel-heading'>
<div class='d-flex flex-wrap'> <div class='d-flex flex-wrap'>

View File

@ -99,11 +99,14 @@
{% block details %} {% block details %}
</h4>
<!-- Properties --> <!-- Properties -->
<h4> <table class='table table-striped table-condensed' id='part-info-table'>
<div id='part-properties' class='btn-group'> <col width='25'>
<tr>
<td colspan='3' style='padding: 3px;'>
<div id='part-properties-wrapper' class='d-flex flex-wrap'>
<div id='part-properties' class='btn-group' role='group';'>
<h5>
{% if part.is_template %} {% if part.is_template %}
&ensp; &ensp;
<span class='fas fa-clone' title='{% trans "Part is a template part (variants can be made from this part)" %}'></span> <span class='fas fa-clone' title='{% trans "Part is a template part (variants can be made from this part)" %}'></span>
@ -144,8 +147,23 @@
{% trans 'Virtual' %} {% trans 'Virtual' %}
</div> </div>
{% endif %} {% endif %}
</h5>
</div> </div>
</h4>
{% include "spacer.html" %}
<button type='button' class='btn btn-outline-secondary' data-bs-toggle='collapse' href='#collapse-part-details' role='button' id='toggle-details-button'>
{% trans "Show Part Details" %}
</button>
</div>
</td>
</tr>
<tr>
<td><span class='fas fa-info-circle'></span></td>
<td>{% trans "Description" %}</td>
<td>{{ part.description }}{% include "clip.html"%}</td>
</tr>
</table>
<!-- Part info messages --> <!-- Part info messages -->
<div class='info-messages'> <div class='info-messages'>
@ -157,7 +175,7 @@
{% endif %} {% endif %}
</div> </div>
{% endblock %} {% endblock details %}
{% block details_right %} {% block details_right %}
<table class='table table-condensed table-striped'> <table class='table table-condensed table-striped'>
@ -231,7 +249,118 @@
{% endif %} {% endif %}
{% endif %} {% endif %}
</table> </table>
{% endblock %} {% endblock details_right %}
{% block details_below %}
<!-- Part Details -->
<div class='collapse' id='collapse-part-details'>
<div class='row flex-wrap'>
<div class='col-sm-6'>
<!-- Details Table -->
<table class="table table-striped table-condensed">
<col width='25'>
{% if part.category %}
<tr>
<td><span class='fas fa-sitemap'></span></td>
<td>{% trans "Category" %}</td>
<td>
<a href='{% url "category-detail" part.category.pk %}'>{{ part.category.name }}</a>
</td>
</tr>
{% endif %}
{% if part.IPN %}
<tr>
<td><span class='fas fa-tag'></span></td>
<td>{% trans "IPN" %}</td>
<td>{{ part.IPN }}{% include "clip.html"%}</td>
</tr>
{% endif %}
{% if part.revision %}
<tr>
<td><span class='fas fa-code-branch'></span></td>
<td>{% trans "Revision" %}</td>
<td>{{ part.revision }}{% include "clip.html"%}</td>
</tr>
{% endif %}
{% if part.units %}
<tr>
<td></td>
<td>{% trans "Units" %}</td>
<td>{{ part.units }}</td>
</tr>
{% endif %}
{% if part.minimum_stock %}
<tr>
<td><span class='fas fa-flag'></span></td>
<td>{% trans "Minimum stock level" %}</td>
<td>{{ part.minimum_stock }}</td>
</tr>
{% endif %}
{% if part.keywords %}
<tr>
<td><span class='fas fa-key'></span></td>
<td>{% trans "Keywords" %}</td>
<td>{{ part.keywords }}{% include "clip.html"%}</td>
</tr>
{% endif %}
</table>
</div>
<div class='col-sm-6'>
<table class="table table-striped table-condensed">
<col width='25'>
<tr>
<td><span class='fas fa-calendar-alt'></span></td>
<td>{% trans "Creation Date" %}</td>
<td>
{{ part.creation_date }}
{% if part.creation_user %}
<span class='badge badge-right rounded-pill bg-dark'>{{ part.creation_user }}</span>
{% endif %}
</td>
</tr>
{% if part.trackable and part.getLatestSerialNumber %}
<tr>
<td><span class='fas fa-hashtag'></span></td>
<td>{% trans "Latest Serial Number" %}</td>
<td>
{{ part.getLatestSerialNumber }}
<div class='btn-group float-right' role='group'>
<a class='btn btn-small btn-outline-secondary text-sm' href='#' id='serial-number-search' title='{% trans "Search for serial number" %}'>
<span class='fas fa-search'></span>
</a>
</div>
</td>
</tr>
{% endif %}
{% if part.default_location %}
<tr>
<td><span class='fas fa-search-location'></span></td>
<td>{% trans "Default Location" %}</td>
<td>
<a href='{% url "stock-location-detail" part.default_location.pk %}'>{{ part.default_location }}</a>
</td>
</tr>
{% endif %}
{% if part.default_supplier %}
<tr>
<td><span class='fas fa-building'></span></td>
<td>{% trans "Default Supplier" %}</td>
<td>{{ part.default_supplier }}</td>
</tr>
{% endif %}
{% if part.link %}
<tr>
<td><span class='fas fa-link'></span></td>
<td>{% trans "External Link" %}</td>
<td><a href="{{ part.link }}">{{ part.link }}</a>{% include "clip.html"%}</td>
</tr>
{% endif %}
</table>
</div>
</div>
</div>
{% endblock details_below %}
{% block js_ready %} {% block js_ready %}
{{ block.super }} {{ block.super }}
@ -439,4 +568,24 @@
}); });
{% endif %} {% endif %}
// Callback function when the "part details" panel is shown
$('#collapse-part-details').on('show.bs.collapse', function() {
$('#toggle-details-button').html('{% trans "Hide Part Details" %}');
inventreeSave('show-part-details', true);
});
// Callback function when the "part details" panel is hidden
$('#collapse-part-details').on('hide.bs.collapse', function() {
$('#toggle-details-button').html('{% trans "Show Part Details" %}');
inventreeSave('show-part-details', false);
});
if (inventreeLoad('show-part-details', false).toString() == 'true') {
$('#collapse-part-details').collapse('show');
}
$('#serial-number-search').click(function() {
findStockItemBySerialNumber({{ part.pk }});
});
{% endblock %} {% endblock %}

View File

@ -5,8 +5,6 @@
{% 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 %} {% settings_value 'PART_SHOW_RELATED' as show_related %}
{% trans "Details" as text %}
{% include "sidebar_item.html" with label="part-details" text=text icon="fa-shapes" %}
{% trans "Parameters" as text %} {% trans "Parameters" as text %}
{% include "sidebar_item.html" with label="part-parameters" text=text icon="fa-th-list" %} {% include "sidebar_item.html" with label="part-parameters" text=text icon="fa-th-list" %}
{% if part.is_template %} {% if part.is_template %}

View File

@ -439,10 +439,14 @@ class PartDetail(InvenTreeRoleMixin, DetailView):
line['price_part'] = stock_item.supplier_part.unit_pricing line['price_part'] = stock_item.supplier_part.unit_pricing
# set date for graph labels # set date for graph labels
if stock_item.purchase_order: if stock_item.purchase_order and stock_item.purchase_order.issue_date:
line['date'] = stock_item.purchase_order.issue_date.strftime('%d.%m.%Y') line['date'] = stock_item.purchase_order.issue_date.strftime('%d.%m.%Y')
else: elif stock_item.tracking_info.count() > 0:
line['date'] = stock_item.tracking_info.first().date.strftime('%d.%m.%Y') line['date'] = stock_item.tracking_info.first().date.strftime('%d.%m.%Y')
else:
# Not enough information
continue
price_history.append(line) price_history.append(line)
ctx['price_history'] = price_history ctx['price_history'] = price_history

View File

@ -313,7 +313,7 @@ class StockFilter(rest_filters.FilterSet):
# Serial number filtering # Serial number filtering
serial_gte = rest_filters.NumberFilter(label='Serial number GTE', field_name='serial', lookup_expr='gte') serial_gte = rest_filters.NumberFilter(label='Serial number GTE', field_name='serial', lookup_expr='gte')
serial_lte = rest_filters.NumberFilter(label='Serial number LTE', field_name='serial', lookup_expr='lte') serial_lte = rest_filters.NumberFilter(label='Serial number LTE', field_name='serial', lookup_expr='lte')
serial = rest_filters.NumberFilter(label='Serial number', field_name='serial', lookup_expr='exact') serial = rest_filters.CharFilter(label='Serial number', field_name='serial', lookup_expr='exact')
serialized = rest_filters.BooleanFilter(label='Has serial number', method='filter_serialized') serialized = rest_filters.BooleanFilter(label='Has serial number', method='filter_serialized')
@ -703,6 +703,18 @@ class StockList(generics.ListCreateAPIView):
except (ValueError, StockItem.DoesNotExist): except (ValueError, StockItem.DoesNotExist):
pass pass
# Filter by "part tree" - only allow parts within a given variant tree
part_tree = params.get('part_tree', None)
if part_tree is not None:
try:
part = Part.objects.get(pk=part_tree)
if part.tree_id is not None:
queryset = queryset.filter(part__tree_id=part.tree_id)
except:
pass
# Filter by 'allocated' parts? # Filter by 'allocated' parts?
allocated = params.get('allocated', None) allocated = params.get('allocated', None)

View File

@ -14,7 +14,7 @@
{% block heading %} {% block heading %}
{% trans "Stock Item" %}: {{ item.part.full_name}} {% trans "Stock Item" %}: {{ item.part.full_name}}
{% endblock %} {% endblock heading %}
{% block actions %} {% block actions %}
@ -100,7 +100,9 @@
<!-- Edit stock item --> <!-- Edit stock item -->
{% if roles.stock.change and not item.is_building %} {% if roles.stock.change and not item.is_building %}
<div class='btn-group'> <div class='btn-group'>
<button id='stock-edit-actions' title='{% trans "Stock actions" %}' class='btn btn-outline-secondary dropdown-toggle' type='button' data-bs-toggle='dropdown'><span class='fas fa-tools'></span> <span class='caret'></span></button> <button id='stock-edit-actions' title='{% trans "Stock actions" %}' class='btn btn-outline-secondary dropdown-toggle' type='button' data-bs-toggle='dropdown'>
<span class='fas fa-tools'></span> <span class='caret'></span>
</button>
<ul class='dropdown-menu' role='menu'> <ul class='dropdown-menu' role='menu'>
{% if item.part.can_convert %} {% if item.part.can_convert %}
<li><a class='dropdown-item' href='#' id='stock-convert' title='{% trans "Convert to variant" %}'><span class='fas fa-screwdriver'></span> {% trans "Convert to variant" %}</a></li> <li><a class='dropdown-item' href='#' id='stock-convert' title='{% trans "Convert to variant" %}'><span class='fas fa-screwdriver'></span> {% trans "Convert to variant" %}</a></li>
@ -118,38 +120,101 @@
</div> </div>
{% endif %} {% endif %}
{% endif %} {% endif %}
{% endblock %} {% endblock actions %}
{% block thumbnail %} {% block thumbnail %}
<img class='part-thumb' {% if item.part.image %}src="{{ item.part.image.url }}"{% else %}src="{% static 'img/blank_image.png' %}"{% endif %}/> <img class='part-thumb' {% if item.part.image %}src="{{ item.part.image.url }}"{% else %}src="{% static 'img/blank_image.png' %}"{% endif %}/>
{% endblock %} {% endblock thumbnail %}
{% block details %} {% block details %}
<table class='table table-striped table-condensed'>
<col width='25'>
<tr>
<td><span class='fas fa-shapes'></span></td>
<td>{% trans "Base Part" %}</td>
<td>
{% if roles.part.view %}
<a href="{% url 'part-detail' item.part.id %}">
{% endif %}
{{ item.part.full_name }}
{% if roles.part.view %}
</a>
{% endif %}
</td>
</tr>
{% if item.serialized %}
<tr>
<td><span class='fas fa-hashtag'></span></td>
<td>{% trans "Serial Number" %}</td>
<td>
{{ item.serial }}
<div class='btn-group float-right' role='group'>
{% if previous %}
<a class="btn btn-small btn-outline-secondary" aria-label="{% trans 'previous page' %}" href="{% url request.resolver_match.url_name previous.id %}" title='{% trans "Navigate to previous serial number" %}'>
<span class='fas fa-angle-left'></span>
<small>{{ previous.serial }}</small>
</a>
{% endif %}
<a class='btn btn-small btn-outline-secondary text-sm' href='#' id='serial-number-search' title='{% trans "Search for serial number" %}'>
<span class='fas fa-search'></span>
</a>
{% if next %}
<a class="btn btn-small btn-outline-secondary text-sm" aria-label="{% trans 'next page' %}" href="{% url request.resolver_match.url_name next.id %}" title='{% trans "Navigate to next serial number" %}'>
<small>{{ next.serial }}</small>
<span class='fas fa-angle-right'></span>
</a>
{% endif %}
</div>
</td>
</tr>
{% else %}
<tr>
<td></td>
<td>{% trans "Quantity" %}</td>
<td>{% decimal item.quantity %} {% if item.part.units %}{{ item.part.units }}{% endif %}</td>
</tr>
{% endif %}
<tr>
<td><span class='fas fa-info'></span></td>
<td>{% trans "Status" %}</td>
<td>{% stock_status_label item.status %}</td>
</tr>
{% if item.expiry_date %}
<tr>
<td><span class='fas fa-calendar-alt{% if item.is_expired %} icon-red{% endif %}'></span></td>
<td>{% trans "Expiry Date" %}</td>
<td>
{{ item.expiry_date }}
{% if item.is_expired %}
<span title='{% blocktrans %}This StockItem expired on {{ item.expiry_date }}{% endblocktrans %}' class='badge rounded-pill bg-danger badge-right'>{% trans "Expired" %}</span>
{% elif item.is_stale %}
<span title='{% blocktrans %}This StockItem expires on {{ item.expiry_date }}{% endblocktrans %}' class='badge rounded-pill bg-warning badge-right'>{% trans "Stale" %}</span>
{% endif %}
</td>
</tr>
{% endif %}
<tr>
<td><span class='fas fa-calendar-alt'></span></td>
<td>{% trans "Last Updated" %}</td>
<td>{{ item.updated }}</td>
</tr>
<tr>
<td><span class='fas fa-calendar-alt'></span></td>
<td>{% trans "Last Stocktake" %}</td>
{% if item.stocktake_date %}
<td>{{ item.stocktake_date }} <span class='badge badge-right rounded-pill bg-dark'>{{ item.stocktake_user }}</span></td>
{% else %}
<td><em>{% trans "No stocktake performed" %}</em></td>
{% endif %}
</tr>
</table>
{% setting_object 'STOCK_OWNERSHIP_CONTROL' as owner_control %} {% setting_object 'STOCK_OWNERSHIP_CONTROL' as owner_control %}
{% if owner_control.value == "True" %} {% if owner_control.value == "True" %}
{% authorized_owners item.owner as owners %} {% authorized_owners item.owner as owners %}
{% endif %} {% endif %}
<h4>
{% if item.is_expired %}
<span class='badge rounded-pill bg-danger'>{% trans "Expired" %}</span>
{% else %}
{% if roles.stock.change %}
<a href='#' id='stock-edit-status'>
{% endif %}
{% stock_status_label item.status large=True %}
{% if roles.stock.change %}
</a>
{% endif %}
{% if item.is_stale %}
<span class='badge rounded-pill bg-warning'>{% trans "Stale" %}</span>
{% endif %}
{% endif %}
</h4>
<div class='info-messages'> <div class='info-messages'>
{% setting_object 'STOCK_OWNERSHIP_CONTROL' as owner_control %} {% setting_object 'STOCK_OWNERSHIP_CONTROL' as owner_control %}
@ -214,49 +279,12 @@
{% endif %} {% endif %}
</div> </div>
{% endblock %} {% endblock details %}
{% block details_right %} {% block details_right %}
<table class="table table-striped"> <table class="table table-striped table-condensed">
<col width='25'> <col width='25'>
<tr>
<td><span class='fas fa-shapes'></span></td>
<td>{% trans "Base Part" %}</td>
<td>
{% if roles.part.view %}
<a href="{% url 'part-detail' item.part.id %}">
{% endif %}
{{ item.part.full_name }}
{% if roles.part.view %}
</a>
{% endif %}
</td>
</tr>
{% if item.serialized %}
<tr>
<td><span class='fas fa-hashtag'></span></td>
<td>{% trans "Serial Number" %}</td>
<td>
{% if previous %}
<a class="btn btn-outline-secondary" aria-label="{% trans 'previous page' %}" href="{% url request.resolver_match.url_name previous.id %}">
<small>{{ previous.serial }}</small>
</a>
{% endif %}
{{ item.serial }}
{% if next %}
<a class="btn btn-outline-secondary text-sm" aria-label="{% trans 'next page' %}" href="{% url request.resolver_match.url_name next.id %}">
<small>{{ next.serial }}</small>
</a>
{% endif %}
</td>
</tr>
{% else %}
<tr>
<td></td>
<td>{% trans "Quantity" %}</td>
<td>{% decimal item.quantity %} {% if item.part.units %}{{ item.part.units }}{% endif %}</td>
</tr>
{% endif %}
{% if item.customer %} {% if item.customer %}
<tr> <tr>
<td><span class='fas fa-user-tie'></span></td> <td><span class='fas fa-user-tie'></span></td>
@ -376,39 +404,6 @@
<td><a href="{% url 'supplier-part-detail' item.supplier_part.id %}">{{ item.supplier_part.SKU }}</a></td> <td><a href="{% url 'supplier-part-detail' item.supplier_part.id %}">{{ item.supplier_part.SKU }}</a></td>
</tr> </tr>
{% endif %} {% endif %}
{% if item.expiry_date %}
<tr>
<td><span class='fas fa-calendar-alt{% if item.is_expired %} icon-red{% endif %}'></span></td>
<td>{% trans "Expiry Date" %}</td>
<td>
{{ item.expiry_date }}
{% if item.is_expired %}
<span title='{% blocktrans %}This StockItem expired on {{ item.expiry_date }}{% endblocktrans %}' class='badge rounded-pill bg-danger'>{% trans "Expired" %}</span>
{% elif item.is_stale %}
<span title='{% blocktrans %}This StockItem expires on {{ item.expiry_date }}{% endblocktrans %}' class='badge rounded-pill bg-warning'>{% trans "Stale" %}</span>
{% endif %}
</td>
</tr>
{% endif %}
<tr>
<td><span class='fas fa-calendar-alt'></span></td>
<td>{% trans "Last Updated" %}</td>
<td>{{ item.updated }}</td>
</tr>
<tr>
<td><span class='fas fa-calendar-alt'></span></td>
<td>{% trans "Last Stocktake" %}</td>
{% if item.stocktake_date %}
<td>{{ item.stocktake_date }} <span class='badge badge-right rounded-pill bg-dark'>{{ item.stocktake_user }}</span></td>
{% else %}
<td><em>{% trans "No stocktake performed" %}</em></td>
{% endif %}
</tr>
<tr>
<td><span class='fas fa-info'></span></td>
<td>{% trans "Status" %}</td>
<td>{% stock_status_label item.status %}</td>
</tr>
{% if item.hasRequiredTests %} {% if item.hasRequiredTests %}
<tr> <tr>
<td><span class='fas fa-vial'></span></td> <td><span class='fas fa-vial'></span></td>
@ -604,4 +599,8 @@ $("#stock-return-from-customer").click(function() {
{% endif %} {% endif %}
$('#serial-number-search').click(function() {
findStockItemBySerialNumber({{ item.part.pk }});
});
{% endblock %} {% endblock %}

View File

@ -80,12 +80,32 @@
{% endblock %} {% endblock %}
{% block details_left %} {% block details_left %}
{% if location %} <table class='table table-striped table-condensed'>
<p>{{ location.description }}</p> <col width='25'>
{% else %} {% if location %}
<p>{% trans "Top level stock location" %}</p> {% if location.description %}
{% endif %} <tr>
<td><span class='fas fa-info-circle'></span></td>
<td>{% trans "Description" %}</td>
<td>{{ location.description }}</td>
</tr>
{% endif %}
<tr>
<td><span class='fas fa-sitemap'></span></td>
<td>{% trans "Location Path" %}</td>
<td>{{ location.pathstring }}</td>
</tr>
{% else %}
<tr>
<td><span class='fas fa-sitemap'></span></td>
<td>{% trans "Location Path" %}</td>
<td><em>{% trans "Top level stock location" %}</em></td>
</tr>
{% endif %}
</table>
{% endblock details_left %}
{% block details_below %}
{% setting_object 'STOCK_OWNERSHIP_CONTROL' as owner_control %} {% setting_object 'STOCK_OWNERSHIP_CONTROL' as owner_control %}
{% if owner_control.value == "True" %} {% if owner_control.value == "True" %}
{% authorized_owners location.owner as owners %} {% authorized_owners location.owner as owners %}
@ -97,17 +117,12 @@
{% endif %} {% endif %}
{% endif %} {% endif %}
{% endblock %} {% endblock details_below %}
{% block details_right %} {% block details_right %}
{% if location %} {% if location %}
<table class='table table-striped table-condensed'> <table class='table table-striped table-condensed'>
<col width='25'> <col width='25'>
<tr>
<td><span class='fas fa-info-circle'></span></td>
<td>{% trans "Description" %}</td>
<td>{{ location.description }}</td>
</tr>
<tr> <tr>
<td><span class='fas fa-map-marker-alt'></span></td> <td><span class='fas fa-map-marker-alt'></span></td>
<td>{% trans "Sublocations" %}</td> <td>{% trans "Sublocations" %}</td>
@ -134,7 +149,7 @@
</tr> </tr>
</table> </table>
{% endif %} {% endif %}
{% endblock %} {% endblock details_right %}
{% block page_content %} {% block page_content %}

View File

@ -4,6 +4,7 @@
{% load inventree_extras %} {% load inventree_extras %}
{% load socialaccount %} {% load socialaccount %}
{% load crispy_forms_tags %} {% load crispy_forms_tags %}
{% load user_sessions i18n %}
{% block label %}account{% endblock %} {% block label %}account{% endblock %}
@ -14,12 +15,12 @@
{% block actions %} {% block actions %}
{% inventree_demo_mode as demo %} {% inventree_demo_mode as demo %}
{% if not demo %} {% if not demo %}
<div class='btn btn-outline-primary' type='button' id='edit-password' title='{% trans "Change Password" %}'>
<span class='fas fa-key'></span> {% trans "Set Password" %}
</div>
<div class='btn btn-primary' type='button' id='edit-user' title='{% trans "Edit User Information" %}'> <div class='btn btn-primary' type='button' id='edit-user' title='{% trans "Edit User Information" %}'>
<span class='fas fa-user-cog'></span> {% trans "Edit" %} <span class='fas fa-user-cog'></span> {% trans "Edit" %}
</div> </div>
<div class='btn btn-primary' type='button' id='edit-password' title='{% trans "Change Password" %}'>
<span class='fas fa-key'></span> {% trans "Set Password" %}
</div>
{% endif %} {% endif %}
{% endblock %} {% endblock %}
@ -174,58 +175,48 @@
</div> </div>
<div class='panel-heading'> <div class='panel-heading'>
<h4>{% trans "Language Settings" %}</h4> <div class='d-flex flex-wrap'>
</div> <h4>{% trans "Active Sessions" %}</h4>
{% include "spacer.html" %}
<div class="row"> <div class='btn-group' role='group'>
<div class="col"> {% if session_list.count > 1 %}
<form action="{% url 'set_language' %}" method="post"> <form method="post" action="{% url 'session_delete_other' %}">
{% csrf_token %} {% csrf_token %}
<input name="next" type="hidden" value="{% url 'settings' %}"> <button type="submit" class="btn btn-sm btn-default btn-danger" title='{% trans "Log out active sessions (except this one)" %}'>
<label for='language' class=' requiredField'> {% trans "Log Out Active Sessions" %}
{% trans "Select language" %} </button>
</label>
<div class='form-group input-group mb-3'>
<select name="language" class="select form-control w-25">
{% get_current_language as LANGUAGE_CODE %}
{% get_available_languages as LANGUAGES %}
{% get_language_info_list for LANGUAGES as languages %}
{% if 'alllang' in request.GET %}{% define True as ALL_LANG %}{% endif %}
{% for language in languages %}
{% define language.code as lang_code %}
{% define locale_stats|keyvalue:lang_code as lang_translated %}
{% if lang_translated > 10 or lang_code == 'en' or lang_code == LANGUAGE_CODE %}{% define True as use_lang %}{% else %}{% define False as use_lang %}{% endif %}
{% if ALL_LANG or use_lang %}
<option value="{{ lang_code }}"{% if lang_code == LANGUAGE_CODE %} selected{% endif %}>
{{ language.name_local }} ({{ lang_code }})
{% if lang_translated %}
{% blocktrans %}{{ lang_translated }}% translated{% endblocktrans %}
{% else %}
{% if lang_code == 'en' %}-{% else %}{% trans 'No translations available' %}{% endif %}
{% endif %}
</option>
{% endif %}
{% endfor %}
</select>
<div class='input-group-append'>
<input type="submit" value="{% trans 'Set Language' %}" class="btn btn btn-primary">
</div>
</div>
<p>{% trans "Some languages are not complete" %}
{% if ALL_LANG %}
. <a href="{% url 'settings' %}">{% trans "Show only sufficent" %}</a>
{% else %}
{% trans "and hidden." %} <a href="?alllang">{% trans "Show them too" %}</a>
{% endif %}
</p>
</form> </form>
{% endif %}
</div> </div>
<div class="col-sm-6">
<h4>{% trans "Help the translation efforts!" %}</h4>
<p>{% blocktrans with link="https://crowdin.com/project/inventree" %}Native language translation of the InvenTree web application is <a href="{{link}}">community contributed via crowdin</a>. Contributions are welcomed and encouraged.{% endblocktrans %}</p>
</div> </div>
</div> </div>
<div>
{% trans "<em>unknown on unknown</em>" as unknown_on_unknown %}
{% trans "<em>unknown</em>" as unknown %}
<table class="table table-striped table-condensed">
<thead>
<tr>
<th>{% trans "IP Address" %}</th>
<th>{% trans "Device" %}</th>
<th>{% trans "Last Activity" %}</th>
</tr>
</thead>
{% for object in session_list %}
<tr {% if object.session_key == session_key %}class="active"{% endif %}>
<td>{{ object.ip }}</td>
<td>{{ object.user_agent|device|default_if_none:unknown_on_unknown|safe }}</td>
<td>
{% if object.session_key == session_key %}
{% blocktrans with time=object.last_activity|timesince %}{{ time }} ago (this session){% endblocktrans %}
{% else %}
{% blocktrans with time=object.last_activity|timesince %}{{ time }} ago{% endblocktrans %}
{% endif %}
</td>
</tr>
{% endfor %}
</table>
</div>
{% endblock %} {% endblock %}
{% block js_ready %} {% block js_ready %}

View File

@ -50,4 +50,57 @@
</div> </div>
</div> </div>
<div class='panel-heading'>
<h4>{% trans "Language Settings" %}</h4>
</div>
<div class="row">
<div class="col">
<form action="{% url 'set_language' %}" method="post">
{% csrf_token %}
<input name="next" type="hidden" value="{% url 'settings' %}">
<label for='language' class=' requiredField'>
{% trans "Select language" %}
</label>
<div class='form-group input-group mb-3'>
<select name="language" class="select form-control w-25">
{% get_current_language as LANGUAGE_CODE %}
{% get_available_languages as LANGUAGES %}
{% get_language_info_list for LANGUAGES as languages %}
{% if 'alllang' in request.GET %}{% define True as ALL_LANG %}{% endif %}
{% for language in languages %}
{% define language.code as lang_code %}
{% define locale_stats|keyvalue:lang_code as lang_translated %}
{% if lang_translated > 10 or lang_code == 'en' or lang_code == LANGUAGE_CODE %}{% define True as use_lang %}{% else %}{% define False as use_lang %}{% endif %}
{% if ALL_LANG or use_lang %}
<option value="{{ lang_code }}"{% if lang_code == LANGUAGE_CODE %} selected{% endif %}>
{{ language.name_local }} ({{ lang_code }})
{% if lang_translated %}
{% blocktrans %}{{ lang_translated }}% translated{% endblocktrans %}
{% else %}
{% if lang_code == 'en' %}-{% else %}{% trans 'No translations available' %}{% endif %}
{% endif %}
</option>
{% endif %}
{% endfor %}
</select>
<div class='input-group-append'>
<input type="submit" value="{% trans 'Set Language' %}" class="btn btn btn-primary">
</div>
</div>
<p>{% trans "Some languages are not complete" %}
{% if ALL_LANG %}
. <a href="{% url 'settings' %}">{% trans "Show only sufficent" %}</a>
{% else %}
{% trans "and hidden." %} <a href="?alllang">{% trans "Show them too" %}</a>
{% endif %}
</p>
</form>
</div>
<div class="col-sm-6">
<h4>{% trans "Help the translation efforts!" %}</h4>
<p>{% blocktrans with link="https://crowdin.com/project/inventree" %}Native language translation of the InvenTree web application is <a href="{{link}}">community contributed via crowdin</a>. Contributions are welcomed and encouraged.{% endblocktrans %}</p>
</div>
</div>
{% endblock %} {% endblock %}

View File

@ -180,9 +180,9 @@
<script type='text/javascript' src="{% i18n_static 'tables.js' %}"></script> <script type='text/javascript' src="{% i18n_static 'tables.js' %}"></script>
<script type='text/javascript' src="{% i18n_static 'table_filters.js' %}"></script> <script type='text/javascript' src="{% i18n_static 'table_filters.js' %}"></script>
<script type='text/javascript' src="{% static 'fontawesome/js/solid.js' %}"></script> <script type='text/javascript' src="{% static 'fontawesome/js/solid.min.js' %}"></script>
<script type='text/javascript' src="{% static 'fontawesome/js/brands.js' %}"></script> <script type='text/javascript' src="{% static 'fontawesome/js/brands.min.js' %}"></script>
<script type='text/javascript' src="{% static 'fontawesome/js/fontawesome.js' %}"></script> <script type='text/javascript' src="{% static 'fontawesome/js/fontawesome.min.js' %}"></script>
{% block js_load %} {% block js_load %}
{% endblock %} {% endblock %}

View File

@ -701,6 +701,11 @@ function loadPurchaseOrderTable(table, options) {
switchable: true, switchable: true,
sortable: false, sortable: false,
formatter: function(value, row) { formatter: function(value, row) {
if (!row.responsible_detail) {
return '-';
}
var html = row.responsible_detail.name; var html = row.responsible_detail.name;
if (row.responsible_detail.label == 'group') { if (row.responsible_detail.label == 'group') {

View File

@ -44,6 +44,7 @@
editStockItem, editStockItem,
editStockLocation, editStockLocation,
exportStock, exportStock,
findStockItemBySerialNumber,
loadInstalledInTable, loadInstalledInTable,
loadStockLocationTable, loadStockLocationTable,
loadStockTable, loadStockTable,
@ -394,6 +395,87 @@ function createNewStockItem(options={}) {
constructForm(url, options); constructForm(url, options);
} }
/*
* Launch a modal form to find a particular stock item by serial number.
* Arguments:
* - part: ID (PK) of the part in question
*/
function findStockItemBySerialNumber(part_id) {
constructFormBody({}, {
title: '{% trans "Find Serial Number" %}',
fields: {
serial: {
label: '{% trans "Serial Number" %}',
help_text: '{% trans "Enter serial number" %}',
placeholder: '{% trans "Enter serial number" %}',
required: true,
type: 'string',
value: '',
}
},
onSubmit: function(fields, opts) {
var serial = getFormFieldValue('serial', fields['serial'], opts);
serial = serial.toString().trim();
if (!serial) {
handleFormErrors(
{
'serial': [
'{% trans "Enter a serial number" %}',
]
}, fields, opts
);
return;
}
inventreeGet(
'{% url "api-stock-list" %}',
{
part_tree: part_id,
serial: serial,
},
{
success: function(response) {
if (response.length == 0) {
// No results!
handleFormErrors(
{
'serial': [
'{% trans "No matching serial number" %}',
]
}, fields, opts
);
} else if (response.length > 1) {
// Too many results!
handleFormErrors(
{
'serial': [
'{% trans "More than one matching result found" %}',
]
}, fields, opts
);
} else {
$(opts.modal).modal('hide');
// Redirect
var pk = response[0].pk;
location.href = `/stock/item/${pk}/`;
}
},
error: function(xhr) {
showApiError(xhr, opts.url);
$(opts.modal).modal('hide');
}
}
);
}
});
}
/* Stock API functions /* Stock API functions
* Requires api.js to be loaded first * Requires api.js to be loaded first

View File

@ -24,30 +24,36 @@
{% block page_info %} {% block page_info %}
<div class='panel-content'> <div class='panel-content'>
{% block details_above %}
{% endblock details_above %}
<div class='container' style='max-width: 100%; padding: 5px;'>
<div class='row'> <div class='row'>
<div class='col-sm-6' id='detail-panel-left'> <div class='col' id='detail-panel-left'>
<div class='card'> <div class='card'>
{% block details_left %} {% block details_left %}
<div class='row g-0'> <div class='row'>
<div class='col-md-4'> <div class='col' style='max-width: 220px;'>
{% block thumbnail %} {% block thumbnail %}
{% endblock %} {% endblock thumbnail %}
</div> </div>
<div class='col-md-8'> <div class='col'>
{% block details %} {% block details %}
{% endblock %} {% endblock details %}
</div> </div>
</div> </div>
{% endblock %} {% endblock details_left %}
</div> </div>
</div> </div>
<div class='col-sm-6' id='detail-panel-right'> <div class='col' id='detail-panel-right'>
<div class='card'> <div class='card'>
{% block details_right %} {% block details_right %}
block details_right block details_right
{% endblock %} {% endblock details_right %}
</div> </div>
</div> </div>
{% block details_below %}
{% endblock details_below %}
</div>
</div> </div>
</div> </div>
{% endblock %} {% endblock %}

View File

@ -145,7 +145,6 @@ class RuleSet(models.Model):
# Core django models (not user configurable) # Core django models (not user configurable)
'admin_logentry', 'admin_logentry',
'contenttypes_contenttype', 'contenttypes_contenttype',
'sessions_session',
# Models which currently do not require permissions # Models which currently do not require permissions
'common_colortheme', 'common_colortheme',
@ -161,6 +160,7 @@ class RuleSet(models.Model):
'error_report_error', 'error_report_error',
'exchange_rate', 'exchange_rate',
'exchange_exchangebackend', 'exchange_exchangebackend',
'user_sessions_session',
# Django-q # Django-q
'django_q_ormq', 'django_q_ormq',

View File

@ -23,6 +23,7 @@ django-q==1.3.4 # Background task scheduling
django-sql-utils==0.5.0 # Advanced query annotation / aggregation django-sql-utils==0.5.0 # Advanced query annotation / aggregation
django-stdimage==5.1.1 # Advanced ImageField management django-stdimage==5.1.1 # Advanced ImageField management
django-test-migrations==1.1.0 # Unit testing for database migrations django-test-migrations==1.1.0 # Unit testing for database migrations
django-user-sessions==1.7.1 # user sessions in DB
django-weasyprint==1.0.1 # django weasyprint integration django-weasyprint==1.0.1 # django weasyprint integration
djangorestframework==3.12.4 # DRF framework djangorestframework==3.12.4 # DRF framework
flake8==3.8.3 # PEP checking flake8==3.8.3 # PEP checking

View File

@ -279,7 +279,6 @@ def content_excludes():
excludes = [ excludes = [
"contenttypes", "contenttypes",
"sessions.session",
"auth.permission", "auth.permission",
"authtoken.token", "authtoken.token",
"error_report.error", "error_report.error",
@ -291,6 +290,7 @@ def content_excludes():
"exchange.rate", "exchange.rate",
"exchange.exchangebackend", "exchange.exchangebackend",
"common.notificationentry", "common.notificationentry",
"user_sessions.session",
] ]
output = "" output = ""