Merge remote-tracking branch 'upstream/master' into receive-via-api

# Conflicts:
#	InvenTree/templates/js/dynamic/inventree.js
#	InvenTree/templates/js/translated/forms.js
#	InvenTree/templates/js/translated/tables.js
This commit is contained in:
Oliver Walters 2021-09-07 22:34:00 +10:00
commit 125554c53f
91 changed files with 2212 additions and 1266 deletions

25
.eslintrc.yml Normal file
View File

@ -0,0 +1,25 @@
env:
commonjs: false
browser: true
es2021: true
jquery: true
extends:
- google
parserOptions:
ecmaVersion: 12
rules:
no-var: off
guard-for-in: off
no-trailing-spaces: off
camelcase: off
padded-blocks: off
prefer-const: off
max-len: off
require-jsdoc: off
valid-jsdoc: off
no-multiple-empty-lines: off
comma-dangle: off
prefer-spread: off
indent:
- error
- 4

31
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@ -0,0 +1,31 @@
---
name: Bug report
about: Create a bug report to help us improve InvenTree
title: "[BUG] Enter bug description"
labels: bug, question
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Deployment Method**
Docker
Bare Metal
**Version Information**
You can get this by going to the "About InvenTree" section in the upper right corner and cicking on to the "copy version information"

View File

@ -0,0 +1,26 @@
---
name: Feature request
about: Suggest an idea for this project
title: "[FR]"
labels: enhancement
assignees: ''
---
**Is your feature request the result of a bug?**
Please link it here.
**Problem**
A clear and concise description of what the problem is. e.g. I'm always frustrated when [...]
**Suggested solution**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Examples of other systems**
Show how other software handles your FR if you have examples.
**Do you want to develop this?**
If so please describe briefly how you would like to implement it (so we can give advice) and if you have experience in the needed technology (you do not need to be a pro - this is just as a information for us).

54
.github/workflows/html.yaml vendored Normal file
View File

@ -0,0 +1,54 @@
# Check javascript template files
name: HTML Templates
on:
push:
branches:
- master
pull_request:
branches-ignore:
- l10*
jobs:
html:
runs-on: ubuntu-latest
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
INVENTREE_DB_ENGINE: sqlite3
INVENTREE_DB_NAME: inventree
INVENTREE_MEDIA_ROOT: ./media
INVENTREE_STATIC_ROOT: ./static
steps:
- name: Install node.js
uses: actions/setup-node@v2
- run: npm install
- name: Checkout Code
uses: actions/checkout@v2
- name: Setup Python
uses: actions/setup-python@v2
with:
python-version: 3.7
- name: Install Dependencies
run: |
sudo apt-get update
sudo apt-get install gettext
pip3 install invoke
invoke install
invoke static
- name: Check HTML Files
run: |
npm install markuplint
npx markuplint InvenTree/build/templates/build/*.html
npx markuplint InvenTree/common/templates/common/*.html
npx markuplint InvenTree/company/templates/company/*.html
npx markuplint InvenTree/order/templates/order/*.html
npx markuplint InvenTree/part/templates/part/*.html
npx markuplint InvenTree/stock/templates/stock/*.html
npx markuplint InvenTree/templates/*.html
npx markuplint InvenTree/templates/InvenTree/*.html
npx markuplint InvenTree/templates/InvenTree/settings/*.html

View File

@ -18,11 +18,33 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
INVENTREE_DB_ENGINE: sqlite3
INVENTREE_DB_NAME: inventree
INVENTREE_MEDIA_ROOT: ./media
INVENTREE_STATIC_ROOT: ./static
steps:
- name: Install node.js
uses: actions/setup-node@v2
- run: npm install
- name: Checkout Code
uses: actions/checkout@v2
- name: Check Files
- name: Setup Python
uses: actions/setup-python@v2
with:
python-version: 3.7
- name: Install Dependencies
run: |
sudo apt-get update
sudo apt-get install gettext
pip3 install invoke
invoke install
invoke static
- name: Check Templated Files
run: |
cd ci
python check_js_templates.py
- name: Lint Javascript Files
run: |
npm install eslint eslint-config-google
invoke render-js-files
npx eslint js_tmp/*.js

8
.gitignore vendored
View File

@ -67,8 +67,16 @@ secret_key.txt
.coverage
htmlcov/
# Temporary javascript files (used for testing)
js_tmp/
# Development files
dev/
# Locale stats file
locale_stats.json
# node.js
package-lock.json
package.json
node_modules/

View File

@ -0,0 +1,100 @@
"""
Pull rendered copies of the templated
"""
from django.http import response
from django.test import TestCase, testcases
from django.contrib.auth import get_user_model
import os
import pathlib
class RenderJavascriptFiles(TestCase):
"""
A unit test to "render" javascript files.
The server renders templated javascript files,
we need the fully-rendered files for linting and static tests.
"""
def setUp(self):
user = get_user_model()
self.user = user.objects.create_user(
username='testuser',
password='testpassword',
email='user@gmail.com',
)
self.client.login(username='testuser', password='testpassword')
def download_file(self, filename, prefix):
url = os.path.join(prefix, filename)
response = self.client.get(url)
here = os.path.abspath(os.path.dirname(__file__))
output_dir = os.path.join(
here,
'..',
'..',
'js_tmp',
)
output_dir = os.path.abspath(output_dir)
if not os.path.exists(output_dir):
os.mkdir(output_dir)
output_file = os.path.join(
output_dir,
filename,
)
with open(output_file, 'wb') as output:
output.write(response.content)
def download_files(self, subdir, prefix):
here = os.path.abspath(os.path.dirname(__file__))
js_template_dir = os.path.join(
here,
'..',
'templates',
'js',
)
directory = os.path.join(js_template_dir, subdir)
directory = os.path.abspath(directory)
js_files = pathlib.Path(directory).rglob('*.js')
n = 0
for f in js_files:
js = os.path.basename(f)
self.download_file(js, prefix)
n += 1
return n
def test_render_files(self):
"""
Look for all javascript files
"""
n = 0
print("Rendering javascript files...")
n += self.download_files('translated', '/js/i18n')
n += self.download_files('dynamic', '/js/dynamic')
print(f"Rendered {n} javascript files.")

View File

@ -0,0 +1,51 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from rest_framework.filters import OrderingFilter
class InvenTreeOrderingFilter(OrderingFilter):
"""
Custom OrderingFilter class which allows aliased filtering of related fields.
To use, simply specify this filter in the "filter_backends" section.
filter_backends = [
InvenTreeOrderingFilter,
]
Then, specify a ordering_field_aliases attribute:
ordering_field_alises = {
'name': 'part__part__name',
'SKU': 'part__SKU',
}
"""
def get_ordering(self, request, queryset, view):
ordering = super().get_ordering(request, queryset, view)
aliases = getattr(view, 'ordering_field_aliases', None)
# Attempt to map ordering fields based on provided aliases
if ordering is not None and aliases is not None:
"""
Ordering fields should be mapped to separate fields
"""
for idx, field in enumerate(ordering):
reverse = False
if field.startswith('-'):
field = field[1:]
reverse = True
if field in aliases:
ordering[idx] = aliases[field]
if reverse:
ordering[idx] = '-' + ordering[idx]
return ordering

View File

@ -10,6 +10,8 @@ import os
from decimal import Decimal
from collections import OrderedDict
from django.conf import settings
from django.contrib.auth.models import User
from django.core.exceptions import ValidationError as DjangoValidationError
@ -94,9 +96,14 @@ class InvenTreeModelSerializer(serializers.ModelSerializer):
# If instance is None, we are creating a new instance
if instance is None and data is not empty:
# Required to side-step immutability of a QueryDict
data = data.copy()
if data is None:
data = OrderedDict()
else:
new_data = OrderedDict()
new_data.update(data)
data = new_data
# Add missing fields which have default values
ModelClass = self.Meta.model

View File

@ -111,6 +111,7 @@ translated_javascript_urls = [
url(r'^company.js', DynamicJsView.as_view(template_name='js/translated/company.js'), name='company.js'),
url(r'^filters.js', DynamicJsView.as_view(template_name='js/translated/filters.js'), name='filters.js'),
url(r'^forms.js', DynamicJsView.as_view(template_name='js/translated/forms.js'), name='forms.js'),
url(r'^helpers.js', DynamicJsView.as_view(template_name='js/translated/helpers.js'), name='helpers.js'),
url(r'^label.js', DynamicJsView.as_view(template_name='js/translated/label.js'), name='label.js'),
url(r'^model_renderers.js', DynamicJsView.as_view(template_name='js/translated/model_renderers.js'), name='model_renderers.js'),
url(r'^modals.js', DynamicJsView.as_view(template_name='js/translated/modals.js'), name='modals.js'),

View File

@ -10,11 +10,15 @@ import common.models
INVENTREE_SW_VERSION = "0.5.0 pre"
INVENTREE_API_VERSION = 10
INVENTREE_API_VERSION = 11
"""
Increment this API version number whenever there is a significant change to the API that any clients need to know about
v11 -> 2021-08-26
- Adds "units" field to PartBriefSerializer
- This allows units to be introspected from the "part_detail" field in the StockItem serializer
v10 -> 2021-08-23
- Adds "purchase_price_currency" to StockItem serializer
- Adds "purchase_price_string" to StockItem serializer

View File

@ -6,7 +6,7 @@
{{ block.super }}
<div class='alert alert-block alert-info'>
<b>{% trans "Automatically Allocate Stock" %}</b><br>
<strong>{% trans "Automatically Allocate Stock" %}</strong><br>
{% trans "The following stock items will be allocated to the specified build output" %}
</div>
{% if allocations %}
@ -24,7 +24,7 @@
</td>
<td>
{{ item.stock_item.part.full_name }}<br>
<i>{{ item.stock_item.part.description }}</i>
<em>{{ item.stock_item.part.description }}</em>
</td>
<td>{% decimal item.quantity %}</td>
<td>{{ item.stock_item.location }}</td>

View File

@ -9,7 +9,7 @@
</div>
{% else %}
<div class='alert alert-block alert-danger'>
<b>{% trans "Build Order is incomplete" %}</b><br>
<strong>{% trans "Build Order is incomplete" %}</strong><br>
<ul>
{% if build.incomplete_count > 0 %}
<li>{% trans "Incompleted build outputs remain" %}</li>

View File

@ -8,7 +8,7 @@
</p>
{% if output %}
<p>
{% blocktrans %}The allocated stock will be installed into the following build output:<br><i>{{output}}</i>{% endblocktrans %}
{% blocktrans %}The allocated stock will be installed into the following build output:<br><em>{{output}}</em>{% endblocktrans %}
</p>
{% endif %}
</div>

View File

@ -40,7 +40,7 @@
{% if build.take_from %}
<a href="{% url 'stock-location-detail' build.take_from.id %}">{{ build.take_from }}</a>{% include "clip.html"%}
{% else %}
<i>{% trans "Stock can be taken from any available location." %}</i>
<em>{% trans "Stock can be taken from any available location." %}</em>
{% endif %}
</td>
</tr>
@ -53,7 +53,7 @@
{{ build.destination }}
</a>{% include "clip.html"%}
{% else %}
<i>{% trans "Destination location not specified" %}</i>
<em>{% trans "Destination location not specified" %}</em>
{% endif %}
</td>
</tr>
@ -127,7 +127,7 @@
{{ build.target_date }}{% if build.is_overdue %} <span class='fas fa-calendar-times icon-red'></span>{% endif %}
</td>
{% else %}
<td><i>{% trans "No target date set" %}</i></td>
<td><em>{% trans "No target date set" %}</em></td>
{% endif %}
</tr>
<tr>
@ -136,7 +136,7 @@
{% if build.completion_date %}
<td>{{ build.completion_date }}{% if build.completed_by %}<span class='badge'>{{ build.completed_by }}</span>{% endif %}</td>
{% else %}
<td><i>{% trans "Build not complete" %}</i></td>
<td><em>{% trans "Build not complete" %}</em></td>
{% endif %}
</tr>
</table>
@ -222,7 +222,7 @@
</div>
{% else %}
<div class='alert alert-block alert-info'>
<b>{% trans "Create a new build output" %}</b><br>
<strong>{% trans "Create a new build output" %}</strong><br>
{% trans "No incomplete build outputs remain." %}<br>
{% trans "Create a new build output using the button above" %}
</div>

View File

@ -6,9 +6,9 @@
{{ block.super }}
<!--
<p>
<b>{{ name }}</b><br>
<strong>{{ name }}</strong><br>
{{ description }}<br>
<i>{% trans "Current value" %}: {{ value }}</i>
<em>{% trans "Current value" %}: {{ value }}</em>
</p>
-->
{% endblock %}

View File

@ -78,7 +78,7 @@
{% if company.currency %}
{{ company.currency }}
{% else %}
<i>{% trans "Uses default currency" %}</i>
<em>{% trans "Uses default currency" %}</em>
{% endif %}
</td>
</tr>

View File

@ -225,7 +225,7 @@ $("#multi-parameter-delete").click(function() {
<ul>`;
selections.forEach(function(item) {
text += `<li>${item.name} - <i>${item.value}</i></li>`;
text += `<li>${item.name} - <em>${item.value}</em></li>`;
});
text += `

View File

@ -9,13 +9,14 @@ from django.utils.translation import ugettext_lazy as _
from django.conf.urls import url, include
from django.db import transaction
from django_filters.rest_framework import DjangoFilterBackend
from django_filters import rest_framework as rest_filters
from rest_framework import generics
from rest_framework import filters, status
from rest_framework.response import Response
from rest_framework.serializers import ValidationError
from InvenTree.filters import InvenTreeOrderingFilter
from InvenTree.helpers import str2bool
from InvenTree.api import AttachmentMixin
from InvenTree.status_codes import PurchaseOrderStatus, SalesOrderStatus
@ -149,7 +150,7 @@ class POList(generics.ListCreateAPIView):
return queryset
filter_backends = [
DjangoFilterBackend,
rest_filters.DjangoFilterBackend,
filters.SearchFilter,
filters.OrderingFilter,
]
@ -323,6 +324,14 @@ class POLineItemList(generics.ListCreateAPIView):
queryset = PurchaseOrderLineItem.objects.all()
serializer_class = POLineItemSerializer
def get_queryset(self, *args, **kwargs):
queryset = super().get_queryset(*args, **kwargs)
queryset = POLineItemSerializer.annotate_queryset(queryset)
return queryset
def get_serializer(self, *args, **kwargs):
try:
@ -335,18 +344,26 @@ class POLineItemList(generics.ListCreateAPIView):
return self.serializer_class(*args, **kwargs)
filter_backends = [
DjangoFilterBackend,
rest_filters.DjangoFilterBackend,
filters.SearchFilter,
filters.OrderingFilter
InvenTreeOrderingFilter
]
ordering_field_aliases = {
'MPN': 'part__manufacturer_part__MPN',
'SKU': 'part__SKU',
'part_name': 'part__part__name',
}
ordering_fields = [
'part__part__name',
'part__MPN',
'part__SKU',
'reference',
'MPN',
'part_name',
'purchase_price',
'quantity',
'received',
'reference',
'SKU',
'total_price',
]
search_fields = [
@ -371,6 +388,14 @@ class POLineItemDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = PurchaseOrderLineItem.objects.all()
serializer_class = POLineItemSerializer
def get_queryset(self):
queryset = super().get_queryset()
queryset = POLineItemSerializer.annotate_queryset(queryset)
return queryset
class SOAttachmentList(generics.ListCreateAPIView, AttachmentMixin):
"""
@ -381,7 +406,7 @@ class SOAttachmentList(generics.ListCreateAPIView, AttachmentMixin):
serializer_class = SOAttachmentSerializer
filter_backends = [
DjangoFilterBackend,
rest_filters.DjangoFilterBackend,
]
filter_fields = [
@ -505,7 +530,7 @@ class SOList(generics.ListCreateAPIView):
return queryset
filter_backends = [
DjangoFilterBackend,
rest_filters.DjangoFilterBackend,
filters.SearchFilter,
filters.OrderingFilter,
]
@ -604,7 +629,7 @@ class SOLineItemList(generics.ListCreateAPIView):
return queryset
filter_backends = [
DjangoFilterBackend,
rest_filters.DjangoFilterBackend,
filters.SearchFilter,
filters.OrderingFilter
]
@ -689,7 +714,7 @@ class SOAllocationList(generics.ListCreateAPIView):
return queryset
filter_backends = [
DjangoFilterBackend,
rest_filters.DjangoFilterBackend,
]
# Default filterable fields
@ -707,7 +732,7 @@ class POAttachmentList(generics.ListCreateAPIView, AttachmentMixin):
serializer_class = POAttachmentSerializer
filter_backends = [
DjangoFilterBackend,
rest_filters.DjangoFilterBackend,
]
filter_fields = [

View File

@ -0,0 +1,21 @@
# Generated by Django 3.2.4 on 2021-09-02 00:42
from django.db import migrations
import django.db.models.deletion
import mptt.fields
class Migration(migrations.Migration):
dependencies = [
('stock', '0065_auto_20210701_0509'),
('order', '0049_alter_purchaseorderlineitem_unique_together'),
]
operations = [
migrations.AlterField(
model_name='purchaseorderlineitem',
name='destination',
field=mptt.fields.TreeForeignKey(blank=True, help_text='Where does the Purchaser want this item to be stored?', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='po_lines', to='stock.stocklocation', verbose_name='Destination'),
),
]

View File

@ -767,7 +767,13 @@ class PurchaseOrderLineItem(OrderLineItem):
help_text=_("Supplier part"),
)
received = models.DecimalField(decimal_places=5, max_digits=15, default=0, verbose_name=_('Received'), help_text=_('Number of items received'))
received = models.DecimalField(
decimal_places=5,
max_digits=15,
default=0,
verbose_name=_('Received'),
help_text=_('Number of items received')
)
purchase_price = InvenTreeModelMoneyField(
max_digits=19,
@ -778,7 +784,7 @@ class PurchaseOrderLineItem(OrderLineItem):
)
destination = TreeForeignKey(
'stock.StockLocation', on_delete=models.DO_NOTHING,
'stock.StockLocation', on_delete=models.SET_NULL,
verbose_name=_('Destination'),
related_name='po_lines',
blank=True, null=True,

View File

@ -7,8 +7,9 @@ from __future__ import unicode_literals
from django.utils.translation import ugettext_lazy as _
from django.db import models
from django.db.models import Case, When, Value
from django.db.models import BooleanField
from django.db.models import BooleanField, ExpressionWrapper, F
from rest_framework import serializers
from rest_framework.serializers import ValidationError
@ -116,6 +117,23 @@ class POSerializer(InvenTreeModelSerializer):
class POLineItemSerializer(InvenTreeModelSerializer):
@staticmethod
def annotate_queryset(queryset):
"""
Add some extra annotations to this queryset:
- Total price = purchase_price * quantity
"""
queryset = queryset.annotate(
total_price=ExpressionWrapper(
F('purchase_price') * F('quantity'),
output_field=models.DecimalField()
)
)
return queryset
def __init__(self, *args, **kwargs):
part_detail = kwargs.pop('part_detail', False)
@ -130,6 +148,8 @@ class POLineItemSerializer(InvenTreeModelSerializer):
quantity = serializers.FloatField(default=1)
received = serializers.FloatField(default=0)
total_price = serializers.FloatField(read_only=True)
part_detail = PartBriefSerializer(source='get_base_part', many=False, read_only=True)
supplier_part_detail = SupplierPartSerializer(source='part', many=False, read_only=True)
@ -165,6 +185,7 @@ class POLineItemSerializer(InvenTreeModelSerializer):
'purchase_price_string',
'destination',
'destination_detail',
'total_price',
]

View File

@ -57,7 +57,7 @@
{% for duplicate in duplicates %}
{% if duplicate == col.value %}
<div class='alert alert-danger alert-block text-center' role='alert' style='padding:2px; margin-top:6px; margin-bottom:2px'>
<b>{% trans "Duplicate selection" %}</b>
<strong>{% trans "Duplicate selection" %}</strong>
</div>
{% endif %}
{% endfor %}

View File

@ -38,7 +38,7 @@
<tr id='part_row_{{ part.id }}'>
<td>
{% include "hover_image.html" with image=part.image hover=False %}
{{ part.full_name }} <small><i>{{ part.description }}</i></small>
{{ part.full_name }} <small><em>{{ part.description }}</em></small>
</td>
<td>
<button class='btn btn-default btn-create' onClick='newSupplierPartFromOrderWizard()' id='new_supplier_part_{{ part.id }}' part='{{ part.pk }}' title='{% trans "Create new supplier part" %}' type='button'>
@ -62,7 +62,7 @@
</select>
</div>
{% if not part.order_supplier %}
<span class='help-inline'>{% blocktrans with name=part.name %}Select a supplier for <i>{{name}}</i>{% endblocktrans %}</span>
<span class='help-inline'>{% blocktrans with name=part.name %}Select a supplier for <em>{{name}}</em>{% endblocktrans %}</span>
{% endif %}
</div>
</td>

View File

@ -28,7 +28,7 @@
{% endif %}
</div>
<table class='table table-striped table-condensed' id='po-table' data-toolbar='#order-toolbar-buttons'>
<table class='table table-striped table-condensed' id='po-line-table' data-toolbar='#order-toolbar-buttons'>
</table>
</div>
</div>
@ -208,13 +208,13 @@ $('#new-po-line').click(function() {
{% endif %}
function reloadTable() {
$("#po-table").bootstrapTable("refresh");
$("#po-line-table").bootstrapTable("refresh");
}
function setupCallbacks() {
// Setup callbacks for the line buttons
var table = $("#po-table");
var table = $("#po-line-table");
{% if order.status == PurchaseOrderStatus.PENDING %}
table.find(".button-line-edit").click(function() {
@ -273,9 +273,9 @@ function setupCallbacks() {
}
$("#po-table").inventreeTable({
$("#po-line-table").inventreeTable({
onPostBody: setupCallbacks,
name: 'purchaseorder',
name: 'purchaseorderlines',
sidePagination: 'server',
formatNoMatches: function() { return "{% trans 'No line items found' %}"; },
queryParams: {
@ -294,7 +294,7 @@ $("#po-table").inventreeTable({
{
field: 'part',
sortable: true,
sortName: 'part__part__name',
sortName: 'part_name',
title: '{% trans "Part" %}',
switchable: false,
formatter: function(value, row, index, field) {
@ -314,7 +314,7 @@ $("#po-table").inventreeTable({
},
{
sortable: true,
sortName: 'part__SKU',
sortName: 'SKU',
field: 'supplier_part_detail.SKU',
title: '{% trans "SKU" %}',
formatter: function(value, row, index, field) {
@ -327,7 +327,7 @@ $("#po-table").inventreeTable({
},
{
sortable: true,
sortName: 'part__MPN',
sortName: 'MPN',
field: 'supplier_part_detail.manufacturer_part_detail.MPN',
title: '{% trans "MPN" %}',
formatter: function(value, row, index, field) {
@ -364,7 +364,9 @@ $("#po-table").inventreeTable({
}
},
{
field: 'total_price',
sortable: true,
field: 'total_price',
title: '{% trans "Total price" %}',
formatter: function(value, row) {
var total = row.purchase_price * row.quantity;
@ -383,7 +385,7 @@ $("#po-table").inventreeTable({
}
},
{
sortable: true,
sortable: false,
field: 'received',
switchable: false,
title: '{% trans "Received" %}',

View File

@ -5,7 +5,7 @@
{% block form %}
{% blocktrans with desc=order.description %}Receive outstanding parts for <b>{{order}}</b> - <i>{{desc}}</i>{% endblocktrans %}
{% blocktrans with desc=order.description %}Receive outstanding parts for <strong>{{order}}</strong> - <em>{{desc}}</em>{% endblocktrans %}
<form method='post' action='' class='js-modal-form' enctype='multipart/form-data'>
{% csrf_token %}

View File

@ -22,7 +22,7 @@
{% endif %}
<div class='alert alert-block alert-info'>
<b>{% trans "Sales Order" %} {{ order.reference }} - {{ order.customer.name }}</b>
<strong>{% trans "Sales Order" %} {{ order.reference }} - {{ order.customer.name }}</strong>
<br>
{% trans "Shipping this order means that the order will no longer be editable." %}
</div>

View File

@ -6,9 +6,9 @@
<div class='alert alert-block alert-warning'>
{% trans "This action will unallocate the following stock from the Sales Order" %}:
<br>
<b>
<strong>
{% decimal allocation.get_allocated %} x {{ allocation.line.part.full_name }}
{% if allocation.item.location %} ({{ allocation.get_location }}){% endif %}
</b>
</strong>
</div>
{% endblock %}

View File

@ -206,6 +206,7 @@ class PartBriefSerializer(InvenTreeModelSerializer):
'stock',
'trackable',
'virtual',
'units',
]

View File

@ -11,13 +11,13 @@
<div class='alert alert-block alert-info'>
{% else %}
<div class='alert alert-block alert-danger'>
{% blocktrans with part=part.full_name %}The BOM for <i>{{ part }}</i> has changed, and must be validated.<br>{% endblocktrans %}
{% blocktrans with part=part.full_name %}The BOM for <em>{{ part }}</em> has changed, and must be validated.<br>{% endblocktrans %}
{% endif %}
{% blocktrans with part=part.full_name checker=part.bom_checked_by check_date=part.bom_checked_date %}The BOM for <i>{{ part }}</i> was last checked by {{ checker }} on {{ check_date }}{% endblocktrans %}
{% blocktrans with part=part.full_name checker=part.bom_checked_by check_date=part.bom_checked_date %}The BOM for <em>{{ part }}</em> was last checked by {{ checker }} on {{ check_date }}{% endblocktrans %}
</div>
{% else %}
<div class='alert alert-danger alert-block'>
<b>{% blocktrans with part=part.full_name %}The BOM for <i>{{ part }}</i> has not been validated.{% endblocktrans %}</b>
<strong>{% blocktrans with part=part.full_name %}The BOM for <em>{{ part }}</em> has not been validated.{% endblocktrans %}</strong>
</div>
{% endif %}

View File

@ -9,7 +9,7 @@
{% if part.has_bom %}
<div class='alert alert-block alert-danger'>
<b>{% trans "Warning" %}</b><br>
<strong>{% trans "Warning" %}</strong><br>
{% trans "This part already has a Bill of Materials" %}<br>
</div>
{% endif %}

View File

@ -57,7 +57,7 @@
{% for duplicate in duplicates %}
{% if duplicate == col.value %}
<div class='alert alert-danger alert-block text-center' role='alert' style='padding:2px; margin-top:6px; margin-bottom:2px'>
<b>{% trans "Duplicate selection" %}</b>
<strong>{% trans "Duplicate selection" %}</strong>
</div>
{% endif %}
{% endfor %}

View File

@ -43,9 +43,9 @@
{% block form_alert %}
<div class='alert alert-info alert-block'>
<b>{% trans "Requirements for BOM upload" %}:</b>
<strong>{% trans "Requirements for BOM upload" %}:</strong>
<ul>
<li>{% trans "The BOM file must contain the required named columns as provided in the " %} <b><a href="/part/bom_template/">{% trans "BOM Upload Template" %}</a></b></li>
<li>{% trans "The BOM file must contain the required named columns as provided in the " %} <strong><a href="/part/bom_template/">{% trans "BOM Upload Template" %}</a></strong></li>
<li>{% trans "Each part must already exist in the database" %}</li>
</ul>
</div>

View File

@ -3,7 +3,7 @@
{% load i18n %}
{% block pre_form_content %}
{% blocktrans with part.full_name as part %}Confirm that the Bill of Materials (BOM) is valid for:<br><i>{{ part }}</i>{% endblocktrans %}
{% blocktrans with part.full_name as part %}Confirm that the Bill of Materials (BOM) is valid for:<br><em>{{ part }}</em>{% endblocktrans %}
<div class='alert alert-warning alert-block'>
{% trans 'This will validate each line in the BOM.' %}

View File

@ -8,13 +8,13 @@
{% if matches %}
<div class='alert alert-block alert-warning'>
<b>{% trans "Possible Matching Parts" %}</b>
<strong>{% trans "Possible Matching Parts" %}</strong>
<p>{% trans "The new part may be a duplicate of these existing parts" %}:</p>
<ul class='list-group'>
{% for match in matches %}
<li class='list-group-item list-group-item-condensed'>
{% decimal match.ratio as match_per %}
{% blocktrans with full_name=match.part.full_name desc=match.part.description %}{{full_name}} - <i>{{desc}}</i> ({{match_per}}% match){% endblocktrans %}
{% blocktrans with full_name=match.part.full_name desc=match.part.description %}{{full_name}} - <em>{{desc}}</em> ({{match_per}}% match){% endblocktrans %}
</li>
{% endfor %}
</ul>

View File

@ -18,7 +18,7 @@
<div class='panel-content'>
{% if part.is_template %}
<div class='alert alert-info alert-block'>
{% blocktrans with full_name=part.full_name%}Showing stock for all variants of <i>{{full_name}}</i>{% endblocktrans %}
{% blocktrans with full_name=part.full_name%}Showing stock for all variants of <em>{{full_name}}</em>{% endblocktrans %}
</div>
{% endif %}
{% include "stock_table.html" %}

View File

@ -50,7 +50,7 @@
{% for duplicate in duplicates %}
{% if duplicate == col.value %}
<div class='alert alert-danger alert-block text-center' role='alert' style='padding:2px; margin-top:6px; margin-bottom:2px'>
<b>{% trans "Duplicate selection" %}</b>
<strong>{% trans "Duplicate selection" %}</strong>
</div>
{% endif %}
{% endfor %}

View File

@ -57,7 +57,7 @@
{% for duplicate in duplicates %}
{% if duplicate == col.value %}
<div class='alert alert-danger alert-block text-center' role='alert' style='padding:2px; margin-top:6px; margin-bottom:2px'>
<b>{% trans "Duplicate selection" %}</b>
<strong>{% trans "Duplicate selection" %}</strong>
</div>
{% endif %}
{% endfor %}

View File

@ -9,11 +9,11 @@
<table class='table table-striped table-condensed table-price-two'>
<tr>
<td><b>{% trans 'Part' %}</b></td>
<td><strong>{% trans 'Part' %}</strong></td>
<td>{{ part }}</td>
</tr>
<tr>
<td><b>{% trans 'Quantity' %}</b></td>
<td><strong>{% trans 'Quantity' %}</strong></td>
<td>{{ quantity }}</td>
</tr>
</table>
@ -23,13 +23,13 @@
<table class='table table-striped table-condensed table-price-three'>
{% if min_total_buy_price %}
<tr>
<td><b>{% trans 'Unit Cost' %}</b></td>
<td><strong>{% trans 'Unit Cost' %}</strong></td>
<td>Min: {% include "price.html" with price=min_unit_buy_price %}</td>
<td>Max: {% include "price.html" with price=max_unit_buy_price %}</td>
</tr>
{% if quantity > 1 %}
<tr>
<td><b>{% trans 'Total Cost' %}</b></td>
<td><strong>{% trans 'Total Cost' %}</strong></td>
<td>Min: {% include "price.html" with price=min_total_buy_price %}</td>
<td>Max: {% include "price.html" with price=max_total_buy_price %}</td>
</tr>
@ -37,7 +37,7 @@
{% else %}
<tr>
<td colspan='3'>
<span class='warning-msg'><i>{% trans 'No supplier pricing available' %}</i></span>
<span class='warning-msg'><em>{% trans 'No supplier pricing available' %}</em></span>
</td>
</tr>
{% endif %}
@ -49,26 +49,26 @@
<table class='table table-striped table-condensed table-price-three'>
{% if min_total_bom_price %}
<tr>
<td><b>{% trans 'Unit Cost' %}</b></td>
<td><strong>{% trans 'Unit Cost' %}</strong></td>
<td>Min: {% include "price.html" with price=min_unit_bom_price %}</td>
<td>Max: {% include "price.html" with price=max_unit_bom_price %}</td>
</tr>
{% if quantity > 1 %}
<tr>
<td><b>{% trans 'Total Cost' %}</b></td>
<td><strong>{% trans 'Total Cost' %}</strong></td>
<td>Min: {% include "price.html" with price=min_total_bom_price %}</td>
<td>Max: {% include "price.html" with price=max_total_bom_price %}</td>
</tr>
{% endif %}
{% if min_total_bom_purchase_price %}
<tr>
<td><b>{% trans 'Unit Purchase Price' %}</b></td>
<td><strong>{% trans 'Unit Purchase Price' %}</strong></td>
<td>Min: {% include "price.html" with price=min_unit_bom_purchase_price %}</td>
<td>Max: {% include "price.html" with price=max_unit_bom_purchase_price %}</td>
</tr>
{% if quantity > 1 %}
<tr>
<td><b>{% trans 'Total Purchase Price' %}</b></td>
<td><strong>{% trans 'Total Purchase Price' %}</strong></td>
<td>Min: {% include "price.html" with price=min_total_bom_purchase_price %}</td>
<td>Max: {% include "price.html" with price=max_total_bom_purchase_price %}</td>
</tr>
@ -78,14 +78,14 @@
{% if part.has_complete_bom_pricing == False %}
<tr>
<td colspan='3'>
<span class='warning-msg'><i>{% trans 'Note: BOM pricing is incomplete for this part' %}</i></span>
<span class='warning-msg'><em>{% trans 'Note: BOM pricing is incomplete for this part' %}</em></span>
</td>
</tr>
{% endif %}
{% else %}
<tr>
<td colspan='3'>
<span class='warning-msg'><i>{% trans 'No BOM pricing available' %}</i></span>
<span class='warning-msg'><em>{% trans 'No BOM pricing available' %}</em></span>
</td>
</tr>
{% endif %}
@ -97,11 +97,11 @@
<h4>{% trans 'Internal Price' %}</h4>
<table class='table table-striped table-condensed table-price-two'>
<tr>
<td><b>{% trans 'Unit Cost' %}</b></td>
<td><strong>{% trans 'Unit Cost' %}</strong></td>
<td>{% include "price.html" with price=unit_internal_part_price %}</td>
</tr>
<tr>
<td><b>{% trans 'Total Cost' %}</b></td>
<td><strong>{% trans 'Total Cost' %}</strong></td>
<td>{% include "price.html" with price=total_internal_part_price %}</td>
</tr>
</table>
@ -112,11 +112,11 @@
<h4>{% trans 'Sale Price' %}</h4>
<table class='table table-striped table-condensed table-price-two'>
<tr>
<td><b>{% trans 'Unit Cost' %}</b></td>
<td><strong>{% trans 'Unit Cost' %}</strong></td>
<td>{% include "price.html" with price=unit_part_price %}</td>
</tr>
<tr>
<td><b>{% trans 'Total Cost' %}</b></td>
<td><strong>{% trans 'Total Cost' %}</strong></td>
<td>{% include "price.html" with price=total_part_price %}</td>
</tr>
</table>

View File

@ -4,7 +4,7 @@
{% block pre_form_content %}
<div class='alert alert-block alert-danger'>
{% blocktrans with full_name=part.full_name %}Are you sure you want to delete part '<b>{{full_name}}</b>'?{% endblocktrans %}
{% blocktrans with full_name=part.full_name %}Are you sure you want to delete part '<strong>{{full_name}}</strong>'?{% endblocktrans %}
</div>
{% if part.used_in_count %}

View File

@ -18,7 +18,7 @@
{% if part.supplier_count > 0 %}
{% if min_total_buy_price %}
<tr>
<td><b>{% trans 'Supplier Pricing' %}</b>
<td><strong>{% trans 'Supplier Pricing' %}</strong>
<a href="#supplier-cost" title='{% trans "Show supplier cost" %}'><span class="fas fa-search-dollar"></span></a>
<a href="#purchase-price" title='{% trans "Show purchase price" %}'><span class="fas fa-chart-bar"></span></a>
</td>
@ -37,7 +37,7 @@
{% else %}
<tr>
<td colspan='4'>
<span class='warning-msg'><i>{% trans 'No supplier pricing available' %}</i></span>
<span class='warning-msg'><em>{% trans 'No supplier pricing available' %}</em></span>
</td>
</tr>
{% endif %}
@ -46,7 +46,7 @@
{% if part.bom_count > 0 %}
{% if min_total_bom_price %}
<tr>
<td><b>{% trans 'BOM Pricing' %}</b>
<td><strong>{% trans 'BOM Pricing' %}</strong>
<a href="#bom-cost" title='{% trans "Show BOM cost" %}'><span class="fas fa-search-dollar"></span></a>
</td>
<td>{% trans 'Unit Cost' %}</td>
@ -83,14 +83,14 @@
{% if part.has_complete_bom_pricing == False %}
<tr>
<td colspan='4'>
<span class='warning-msg'><i>{% trans 'Note: BOM pricing is incomplete for this part' %}</i></span>
<span class='warning-msg'><em>{% trans 'Note: BOM pricing is incomplete for this part' %}</em></span>
</td>
</tr>
{% endif %}
{% else %}
<tr>
<td colspan='4'>
<span class='warning-msg'><i>{% trans 'No BOM pricing available' %}</i></span>
<span class='warning-msg'><em>{% trans 'No BOM pricing available' %}</em></span>
</td>
</tr>
{% endif %}
@ -99,7 +99,7 @@
{% if show_internal_price and roles.sales_order.view %}
{% if total_internal_part_price %}
<tr>
<td><b>{% trans 'Internal Price' %}</b></td>
<td><strong>{% trans 'Internal Price' %}</strong></td>
<td>{% trans 'Unit Cost' %}</td>
<td colspan='2'>{% include "price.html" with price=unit_internal_part_price %}</td>
</tr>
@ -113,7 +113,7 @@
{% if total_part_price %}
<tr>
<td><b>{% trans 'Sale Price' %}</b>
<td><strong>{% trans 'Sale Price' %}</strong>
<a href="#sale-cost" title='{% trans "Show sale cost" %}'><span class="fas fa-search-dollar"></span></a>
<a href="#sale-price" title='{% trans "Show sale price" %}'><span class="fas fa-chart-bar"></span></a>
</td>
@ -179,7 +179,7 @@
</div>
<div class='panel-content'>
<h4>{% trans 'Stock Pricing' %}
<i class="fas fa-info-circle" title="Shows the purchase prices of stock for this part.&#10;The Supplier Unit Cost is the current purchase price for that supplier part."></i>
<em class="fas fa-info-circle" title="Shows the purchase prices of stock for this part.&#10;The Supplier Unit Cost is the current purchase price for that supplier part."></em>
</h4>
{% if price_history|length > 0 %}
<div style="max-width: 99%; min-height: 300px">

View File

@ -6,8 +6,8 @@
{{ block.super }}
<div class='alert alert-info alert-block'>
<b>{% trans "Create new part variant" %}</b><br>
{% blocktrans with full_name=part.full_name %}Create a new variant of template <i>'{{full_name}}'</i>.{% endblocktrans %}
<strong>{% trans "Create new part variant" %}</strong><br>
{% blocktrans with full_name=part.full_name %}Create a new variant of template <em>'{{full_name}}'</em>.{% endblocktrans %}
</div>
{% endblock %}

View File

@ -128,7 +128,7 @@ content: "v{{report_revision}} - {{ date.isoformat }}";
{% if build.target_date %}
{{ build.target_date }}
{% else %}
<i>Not specified</i>
<em>Not specified</em>
{% endif %}
</td>
</tr>
@ -138,7 +138,7 @@ content: "v{{report_revision}} - {{ date.isoformat }}";
{% if build.sales_order %}
{% internal_link build.sales_order.get_absolute_url build.sales_order %}
{% else %}
<i>Not specified</i>
<em>Not specified</em>
{% endif %}
</td>
</tr>

View File

@ -68,8 +68,8 @@ content: "{% trans 'Stock Item Test Report' %}";
{{ part.full_name }}
</h2>
<p>{{ part.description }}</p>
<p><i>{{ stock_item.location }}</i></p>
<p><i>Stock Item ID: {{ stock_item.pk }}</i></p>
<p><em>{{ stock_item.location }}</em></p>
<p><em>Stock Item ID: {{ stock_item.pk }}</em></p>
</div>
<div class='img-right'>
<img class='part-img' src="{% part_image part %}">

View File

@ -43,6 +43,7 @@ from .serializers import StockItemTestResultSerializer
from InvenTree.views import TreeSerializer
from InvenTree.helpers import str2bool, isNull
from InvenTree.api import AttachmentMixin
from InvenTree.filters import InvenTreeOrderingFilter
from decimal import Decimal, InvalidOperation
@ -882,10 +883,16 @@ class StockList(generics.ListCreateAPIView):
filter_backends = [
DjangoFilterBackend,
filters.SearchFilter,
filters.OrderingFilter,
InvenTreeOrderingFilter,
]
ordering_field_aliases = {
'SKU': 'supplier_part__SKU',
}
ordering_fields = [
'batch',
'location',
'part__name',
'part__IPN',
'updated',
@ -893,10 +900,13 @@ class StockList(generics.ListCreateAPIView):
'expiry_date',
'quantity',
'status',
'SKU',
]
ordering = [
'part__name'
'part__name',
'quantity',
'location',
]
search_fields = [

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 MiB

View File

@ -182,7 +182,7 @@
{% if item.build %}
<a href="{% url 'build-detail' item.build.id %}">
<b>{{ item.build }}</b>
<strong>{{ item.build }}</strong>
</a>
{% endif %}
@ -300,7 +300,7 @@
{% if item.location %}
<td><a href="{% url 'stock-location-detail' item.location.id %}">{{ item.location.name }}</a></td>
{% else %}
<td><i>{% trans "No location set" %}</i></td>
<td><em>{% trans "No location set" %}</em></td>
{% endif %}
</tr>
{% endif %}
@ -367,7 +367,7 @@
{% if item.supplier_part.manufacturer_part.manufacturer %}
<td><a href="{% url 'company-detail' item.supplier_part.manufacturer_part.manufacturer.id %}">{{ item.supplier_part.manufacturer_part.manufacturer.name }}</a></td>
{% else %}
<td><i>{% trans "No manufacturer set" %}</i></td>
<td><em>{% trans "No manufacturer set" %}</em></td>
{% endif %}
</tr>
@ -414,7 +414,7 @@
{% if item.stocktake_date %}
<td>{{ item.stocktake_date }} <span class='badge'>{{ item.stocktake_user }}</span></td>
{% else %}
<td><i>{% trans "No stocktake performed" %}</i></td>
<td><em>{% trans "No stocktake performed" %}</em></td>
{% endif %}
</tr>
<tr>

View File

@ -9,7 +9,7 @@
{% trans "Are you sure you want to delete this stock item?" %}
<br>
{% decimal item.quantity as qty %}
{% blocktrans with full_name=item.part.full_name %}This will remove <b>{{qty}}</b> units of <b>{{full_name}}</b> from stock.{% endblocktrans %}
{% blocktrans with full_name=item.part.full_name %}This will remove <strong>{{qty}}</strong> units of <strong>{{full_name}}</strong> from stock.{% endblocktrans %}
</div>
{% endblock %}

View File

@ -3,7 +3,7 @@
<div class="navigation">
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li><a href='#' title='Toggle Stock Tree' id='toggle-stock-tree'><b><span class='fas fa-stream'></span></b></a></li>
<li><a href='#' title='Toggle Stock Tree' id='toggle-stock-tree'><strong><span class='fas fa-stream'></span></strong></a></li>
<li class="breadcrumb-item{% if location is None %} active" aria-current="page{% endif %}"><a href="/stock/">{% trans "Stock" %}</a></li>
{% if location %}
{% for path_item in location.parentpath %}

View File

@ -20,7 +20,7 @@ the top level 'Stock' location.
<ul class='list-group'>
{% for loc in location.children.all %}
<li class='list-group-item'><b>{{ loc.name }}</b> - <i>{{ loc.description}}</i></li>
<li class='list-group-item'><strong>{{ loc.name }}</strong> - <em>{{ loc.description}}</em></li>
{% endfor %}
</ul>
{% endif %}
@ -36,7 +36,7 @@ If this location is deleted, these items will be moved to the top level 'Stock'
<ul class='list-group'>
{% for item in location.stock_items.all %}
<li class='list-group-item'><b>{{ item.part.full_name }}</b> - <i>{{ item.part.description }}</i><span class='badge'>{% decimal item.quantity %}</span></li>
<li class='list-group-item'><strong>{{ item.part.full_name }}</strong> - <em>{{ item.part.description }}</em><span class='badge'>{% decimal item.quantity %}</span></li>
{% endfor %}
</ul>
{% endif %}

View File

@ -24,7 +24,7 @@
{% for item in stock_items %}
<tr id='stock-row-{{ item.id }}' class='error'>
<td>{% include "hover_image.html" with image=item.part.image hover=True %}
{{ item.part.full_name }} <small><i>{{ item.part.description }}</i></small></td>
{{ item.part.full_name }} <small><em>{{ item.part.description }}</em></small></td>
<td>{{ item.location.pathstring }}</td>
<td>{% decimal item.quantity %}</td>
<td>

View File

@ -4,13 +4,13 @@
{% block pre_form_content %}
<div class='alert alert-block alert-info'>
<b>{% trans "Convert Stock Item" %}</b><br>
{% blocktrans with part=item.part %}This stock item is current an instance of <i>{{part}}</i>{% endblocktrans %}<br>
<strong>{% trans "Convert Stock Item" %}</strong><br>
{% blocktrans with part=item.part %}This stock item is current an instance of <em>{{part}}</em>{% endblocktrans %}<br>
{% trans "It can be converted to one of the part variants listed below." %}
</div>
<div class='alert alert-block alert-warning'>
<b>{% trans "Warning" %}</b>
<strong>{% trans "Warning" %}</strong>
{% trans "This action cannot be easily undone" %}
</div>

View File

@ -5,6 +5,8 @@ Unit testing for the Stock API
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import os
from datetime import datetime, timedelta
from django.urls import reverse
@ -666,3 +668,37 @@ class StockTestResultTest(StockAPITestCase):
test = response.data[0]
self.assertEqual(test['value'], '150kPa')
self.assertEqual(test['user'], self.user.pk)
def test_post_bitmap(self):
"""
2021-08-25
For some (unknown) reason, prior to fix https://github.com/inventree/InvenTree/pull/2018
uploading a bitmap image would result in a failure.
This test has been added to ensure that there is no regression.
As a bonus this also tests the file-upload component
"""
here = os.path.dirname(__file__)
image_file = os.path.join(here, 'fixtures', 'test_image.bmp')
with open(image_file, 'rb') as bitmap:
data = {
'stock_item': 105,
'test': 'Checked Steam Valve',
'result': False,
'value': '150kPa',
'notes': 'I guess there was just too much pressure?',
"attachment": bitmap,
}
response = self.client.post(self.get_url(), data)
self.assertEqual(response.status_code, 201)
# Check that an attachment has been uploaded
self.assertIsNotNone(response.data['attachment'])

View File

@ -34,7 +34,7 @@
function addHeaderTitle(title) {
// Add a header block to the action list
$("#action-item-list").append(
`<li class='list-group-item'><b>${title}</b></li>`
`<li class='list-group-item'><strong>${title}</strong></li>`
);
}

View File

@ -21,7 +21,7 @@
{% if query %}
{% else %}
<div id='empty-search-query'>
<h4><i>{% trans "Enter a search query" %}</i></h4>
<h4><em>{% trans "Enter a search query" %}</em></h4>
</div>
{% endif %}
@ -47,7 +47,7 @@
function addItemTitle(title) {
// Add header block to the results list
$('#search-item-list').append(
`<li class='list-group-item'><b>${title}</b></li>`
`<li class='list-group-item'><strong>${title}</strong></li>`
);
}
@ -268,7 +268,7 @@
var text = "{% trans "Shipped to customer" %}";
return renderLink(text, `/company/${row.customer}/assigned-stock/`);
} else {
return '<i>{% trans "No stock location set" %}</i>';
return '<em>{% trans "No stock location set" %}</em>';
}
}
}

View File

@ -40,7 +40,7 @@
{% if rates_updated %}
{{ rates_updated }}
{% else %}
<i>{% trans "Never" %}</i>
<em>{% trans "Never" %}</em>
{% endif %}
<form action='{% url "settings-currencies-refresh" %}' method='post'>
<div id='refresh-rates-form'>

View File

@ -9,7 +9,7 @@
</li>
<li class='list-group-item'>
<b>{% trans "User Settings" %}</b>
<strong>{% trans "User Settings" %}</strong>
</li>
<li class='list-group-item' title='{% trans "Account" %}'>
@ -53,7 +53,7 @@
{% if user.is_staff %}
<li class='list-group-item'>
<b>{% trans "InvenTree Settings" %}</b>
<strong>{% trans "InvenTree Settings" %}</strong>
</li>
<li class='list-group-item' title='{% trans "Server" %}'>

View File

@ -13,7 +13,7 @@
<span class='fas {{ icon }}'></span>
{% endif %}
</td>
<td><b>{% trans setting.name %}</b></td>
<td><strong>{% trans setting.name %}</strong></td>
<td>
{% if setting.is_bool %}
<div>
@ -21,15 +21,15 @@
</div>
{% else %}
<div id='setting-{{ setting.pk }}'>
<b>
<strong>
<span id='setting-value-{{ setting.key.upper }}' fieldname='{{ setting.key.upper }}'>
{% if setting.value %}
{{ setting.value }}
{% else %}
<i>{% trans "No value set" %}</i>
<em>{% trans "No value set" %}</em>
{% endif %}
</span>
</b>
</strong>
{{ setting.units }}
</div>
{% endif %}

View File

@ -185,8 +185,6 @@ $('#cat-param-table').inventreeTable({
function loadTemplateTable(pk) {
console.log('refresh:', pk);
// Enable the buttons
$('#new-cat-param').removeAttr('disabled');
@ -210,7 +208,11 @@ $("#new-cat-param").click(function() {
launchModalForm(`/part/category/${pk}/parameters/new/`, {
success: function() {
$("#cat-param-table").bootstrapTable('refresh');
$("#cat-param-table").bootstrapTable('refresh', {
query: {
category: pk,
}
});
},
});
});

View File

@ -87,7 +87,7 @@
<td>
<span style="display: none;" id="about-copy-text">{% include "version.html" %}</span>
<span class="float-right">
<button class="btn clip-btn-version" type="button" data-toggle='tooltip' title='{% trans "copy to clipboard" %}'><i class="fas fa-copy"></i> {% trans "copy version information" %}</button>
<button class="btn clip-btn-version" type="button" data-toggle='tooltip' title='{% trans "copy to clipboard" %}'><em class="fas fa-copy"></em> {% trans "copy version information" %}</button>
</span>
</td>
</tr>

View File

@ -160,6 +160,7 @@
<script type='text/javascript' src="{% i18n_static 'company.js' %}"></script>
<script type='text/javascript' src="{% i18n_static 'filters.js' %}"></script>
<script type='text/javascript' src="{% i18n_static 'forms.js' %}"></script>
<script type='text/javascript' src="{% i18n_static 'helpers.js' %}"></script>
<script type='text/javascript' src="{% i18n_static 'label.js' %}"></script>
<script type='text/javascript' src="{% i18n_static 'modals.js' %}"></script>
<script type='text/javascript' src="{% i18n_static 'model_renderers.js' %}"></script>

View File

@ -1,5 +1,5 @@
{% load i18n %}
<span class="float-right">
<button class="btn clip-btn" type="button" data-toggle='tooltip' title='{% trans "copy to clipboard" %}'><i class="fas fa-copy"></i></button>
<button class="btn clip-btn" type="button" data-toggle='tooltip' title='{% trans "copy to clipboard" %}'><em class="fas fa-copy"></em></button>
</span>

View File

@ -1,17 +1,26 @@
{% load i18n %}
/* globals
*/
/* exported
clearEvents,
endDate,
startDate,
*/
/**
* Helper functions for calendar display
*/
function startDate(calendar) {
// Extract the first displayed date on the calendar
return calendar.currentData.dateProfile.activeRange.start.toISOString().split("T")[0];
return calendar.currentData.dateProfile.activeRange.start.toISOString().split('T')[0];
}
function endDate(calendar) {
// Extract the last display date on the calendar
return calendar.currentData.dateProfile.activeRange.end.toISOString().split("T")[0];
return calendar.currentData.dateProfile.activeRange.end.toISOString().split('T')[0];
}
function clearEvents(calendar) {
@ -21,5 +30,5 @@ function clearEvents(calendar) {
events.forEach(function(event) {
event.remove();
})
}
});
}

View File

@ -1,26 +1,44 @@
{% load inventree_extras %}
/* globals
ClipboardJS,
inventreeFormDataUpload,
launchModalForm,
user_settings,
*/
/* exported
attachClipboard,
enableDragAndDrop,
inventreeDocReady,
inventreeLoad,
inventreeSave,
*/
function attachClipboard(selector, containerselector, textElement) {
// set container
if (containerselector){
if (containerselector) {
containerselector = document.getElementById(containerselector);
} else {
containerselector = document.body;
}
var text = null;
// set text-function
if (textElement){
if (textElement) {
text = function() {
return document.getElementById(textElement).textContent;
}
};
} else {
text = function(trigger) {
var content = trigger.parentElement.parentElement.textContent;
return content.trim();
}
};
}
// create Clipboard
// eslint-disable-next-line no-unused-vars
var cis = new ClipboardJS(selector, {
text: text,
container: containerselector
@ -33,15 +51,15 @@ function inventreeDocReady() {
* This will be called for every page that extends "base.html"
*/
window.addEventListener("dragover",function(e){
window.addEventListener('dragover', function(e) {
e = e || event;
e.preventDefault();
},false);
}, false);
window.addEventListener("drop",function(e){
window.addEventListener('drop', function(e) {
e = e || event;
e.preventDefault();
},false);
}, false);
/* Add drag-n-drop functionality to any element
* marked with the class 'dropzone'
@ -51,12 +69,13 @@ function inventreeDocReady() {
// TODO - Only indicate that a drop event will occur if a file is being dragged
var transfer = event.originalEvent.dataTransfer;
// eslint-disable-next-line no-constant-condition
if (true || isFileTransfer(transfer)) {
$(this).addClass('dragover');
}
});
$('.dropzone').on('dragleave drop', function(event) {
$('.dropzone').on('dragleave drop', function() {
$(this).removeClass('dragover');
});
@ -74,19 +93,19 @@ function inventreeDocReady() {
// Callback to launch the 'Database Stats' window
$('#launch-stats').click(function() {
launchModalForm("/stats/", {
launchModalForm('/stats/', {
no_post: true,
});
});
// Initialize clipboard-buttons
attachClipboard('.clip-btn');
attachClipboard('.clip-btn', 'modal-about'); // modals
attachClipboard('.clip-btn-version', 'modal-about', 'about-copy-text'); // version-text
attachClipboard('.clip-btn', 'modal-about');
attachClipboard('.clip-btn-version', 'modal-about', 'about-copy-text');
// Add autocomplete to the search-bar
$("#search-bar" ).autocomplete({
source: function (request, response) {
$('#search-bar').autocomplete({
source: function(request, response) {
$.ajax({
url: '/api/part/',
data: {
@ -94,8 +113,8 @@ function inventreeDocReady() {
limit: user_settings.SEARCH_PREVIEW_RESULTS,
offset: 0
},
success: function (data) {
var transformed = $.map(data.results, function (el) {
success: function(data) {
var transformed = $.map(data.results, function(el) {
return {
label: el.name,
id: el.pk,
@ -104,13 +123,13 @@ function inventreeDocReady() {
});
response(transformed);
},
error: function () {
error: function() {
response([]);
}
});
},
create: function () {
$(this).data('ui-autocomplete')._renderItem = function (ul, item) {
create: function() {
$(this).data('ui-autocomplete')._renderItem = function(ul, item) {
var html = `<a href='/part/${item.id}/'><span>`;
@ -128,7 +147,9 @@ function inventreeDocReady() {
window.location = '/part/' + ui.item.id + '/';
},
minLength: 2,
classes: {'ui-autocomplete': 'dropdown-menu search-menu'},
classes: {
'ui-autocomplete': 'dropdown-menu search-menu',
},
});
}
@ -140,124 +161,6 @@ function isFileTransfer(transfer) {
}
function isOnlineTransfer(transfer) {
/* Determine if a drag-and-drop transfer is from another website.
* e.g. dragged from another browser window
*/
return transfer.items.length > 0;
}
function getImageUrlFromTransfer(transfer) {
/* Extract external image URL from a drag-and-dropped image
*/
var url = transfer.getData('text/html').match(/src\s*=\s*"(.+?)"/)[1];
console.log('Image URL: ' + url);
return url;
}
function makeIconBadge(icon, title) {
// Construct an 'icon badge' which floats to the right of an object
var html = `<span class='fas ${icon} label-right' title='${title}'></span>`;
return html;
}
function makeIconButton(icon, cls, pk, title, options={}) {
// Construct an 'icon button' using the fontawesome set
var classes = `btn btn-default btn-glyph ${cls}`;
var id = `${cls}-${pk}`;
var html = '';
var extraProps = '';
if (options.disabled) {
extraProps += "disabled='true' ";
}
html += `<button pk='${pk}' id='${id}' class='${classes}' title='${title}' ${extraProps}>`;
html += `<span class='fas ${icon}'></span>`;
html += `</button>`;
return html;
}
function makeProgressBar(value, maximum, opts={}) {
/*
* Render a progessbar!
*
* @param value is the current value of the progress bar
* @param maximum is the maximum value of the progress bar
*/
var options = opts || {};
value = parseFloat(value);
var percent = 100;
// Prevent div-by-zero or null value
if (maximum && maximum > 0) {
maximum = parseFloat(maximum);
percent = parseInt(value / maximum * 100);
}
if (percent > 100) {
percent = 100;
}
var extraclass = '';
if (value > maximum) {
extraclass='progress-bar-over';
} else if (value < maximum) {
extraclass = 'progress-bar-under';
}
var style = options.style || '';
var text = '';
if (style == 'percent') {
// Display e.g. "50%"
text = `${percent}%`;
} else if (style == 'max') {
// Display just the maximum value
text = `${maximum}`;
} else if (style == 'value') {
// Display just the current value
text = `${value}`;
} else if (style == 'blank') {
// No display!
text = '';
} else {
/* Default style
* Display e.g. "5 / 10"
*/
text = `${value} / ${maximum}`;
}
var id = options.id || 'progress-bar';
return `
<div id='${id}' class='progress'>
<div class='progress-bar ${extraclass}' role='progressbar' aria-valuenow='${percent}' aria-valuemin='0' aria-valuemax='100' style='width:${percent}%'></div>
<div class='progress-value'>${text}</div>
</div>
`;
}
function enableDragAndDrop(element, url, options) {
/* Enable drag-and-drop file uploading for a given element.
@ -272,7 +175,7 @@ function enableDragAndDrop(element, url, options) {
method - HTTP method
*/
data = options.data || {};
var data = options.data || {};
$(element).on('drop', function(event) {
@ -316,55 +219,27 @@ function enableDragAndDrop(element, url, options) {
}
function thumbnailImage(url, options={}) {
/* Render a simple thumbnail image from the provided URL */
if (!url) {
url = '/static/img/blank_img.png';
}
// TODO: Support insertion of custom classes
var html = `<img class='hover-img-thumb' src='${url}'>`;
return html;
}
function imageHoverIcon(url) {
/* Render a small thumbnail icon for an image.
* On mouseover, display a full-size version of the image
*/
if (!url) {
url = '/static/img/blank_image.png';
}
var html = `
<a class='hover-icon'>
<img class='hover-img-thumb' src='` + url + `'>
<img class='hover-img-large' src='` + url + `'>
</a>
`;
return html;
}
/**
* Save a key:value pair to local storage
* @param {String} name - settting key
* @param {String} value - setting value
*/
function inventreeSave(name, value) {
/*
* Save a key:value pair to local storage
*/
var key = "inventree-" + name;
var key = `inventree-${name}`;
localStorage.setItem(key, value);
}
function inventreeLoad(name, defaultValue) {
/*
* Retrieve a key:value pair from local storage
*/
var key = "inventree-" + name;
/**
* Retrieve a key:value pair from local storage
* @param {*} name - setting key
* @param {*} defaultValue - default value (returned if no matching key:value pair is found)
* @returns
*/
function inventreeLoad(name, defaultValue) {
var key = `inventree-${name}`;
var value = localStorage.getItem(key);
@ -374,27 +249,3 @@ function inventreeLoad(name, defaultValue) {
return value;
}
}
function inventreeLoadInt(name) {
/*
* Retrieve a value from local storage, and attempt to cast to integer
*/
var data = inventreeLoad(name);
return parseInt(data, 10);
}
function inventreeLoadFloat(name) {
var data = inventreeLoad(name);
return parseFloat(data);
}
function inventreeDel(name) {
var key = 'inventree-' + name;
localStorage.removeItem(key);
}

View File

@ -1,3 +1,10 @@
/* globals
*/
/* exported
attachNavCallbacks,
onPanelLoad,
*/
/*
* Attach callbacks to navigation bar elements.
@ -55,7 +62,7 @@ function activatePanel(panelName, options={}) {
// Iterate through the available 'select' elements until one matches
panelName = null;
$('.nav-toggle').each(function(item) {
$('.nav-toggle').each(function() {
var panel_name = $(this).attr('id').replace('select-', '');
if ($(`#panel-${panel_name}`).length && (panelName == null)) {
@ -83,9 +90,9 @@ function activatePanel(panelName, options={}) {
$('.list-group-item').removeClass('active');
// Find the associated selector
var select = `#select-${panelName}`;
var selectElement = `#select-${panelName}`;
$(select).parent('.list-group-item').addClass('active');
$(selectElement).parent('.list-group-item').addClass('active');
}
@ -96,7 +103,7 @@ function onPanelLoad(panel, callback) {
var panelId = `#panel-${panel}`;
$(panelId).on('fadeInStarted', function(e) {
$(panelId).on('fadeInStarted', function() {
// Trigger the callback
callback();
@ -105,4 +112,4 @@ function onPanelLoad(panel, callback) {
$(panelId).off('fadeInStarted');
});
}
}

View File

@ -1,18 +1,20 @@
{% load inventree_extras %}
// InvenTree settings
/* exported
user_settings,
global_settings,
*/
{% user_settings request.user as USER_SETTINGS %}
var user_settings = {
const user_settings = {
{% for key, value in USER_SETTINGS.items %}
{{ key }}: {% primitive_to_javascript value %},
{% endfor %}
};
{% global_settings as GLOBAL_SETTINGS %}
var global_settings = {
const global_settings = {
{% for key, value in GLOBAL_SETTINGS.items %}
{{ key }}: {% primitive_to_javascript value %},
{% endfor %}
};
};

View File

@ -1,15 +1,29 @@
{% load i18n %}
{% load inventree_extras %}
var jQuery = window.$;
/* globals
renderErrorMessage,
showAlertDialog,
*/
$.urlParam = function(name){
/* exported
inventreeGet,
inventreeDelete,
inventreeFormDataUpload,
showApiError,
*/
$.urlParam = function(name) {
// eslint-disable-next-line no-useless-escape
var results = new RegExp('[\?&]' + name + '=([^&#]*)').exec(window.location.href);
if (results==null) {
return null;
if (results == null) {
return null;
}
return decodeURI(results[1]) || 0;
}
};
// using jQuery
function getCookie(name) {
@ -31,11 +45,10 @@ function getCookie(name) {
function inventreeGet(url, filters={}, options={}) {
// Middleware token required for data update
//var csrftoken = jQuery("[name=csrfmiddlewaretoken]").val();
var csrftoken = getCookie('csrftoken');
return $.ajax({
beforeSend: function(xhr, settings) {
beforeSend: function(xhr) {
xhr.setRequestHeader('X-CSRFToken', csrftoken);
},
url: url,
@ -73,7 +86,7 @@ function inventreeFormDataUpload(url, data, options={}) {
var csrftoken = getCookie('csrftoken');
return $.ajax({
beforeSend: function(xhr, settings) {
beforeSend: function(xhr) {
xhr.setRequestHeader('X-CSRFToken', csrftoken);
},
url: url,
@ -101,11 +114,10 @@ function inventreePut(url, data={}, options={}) {
var method = options.method || 'PUT';
// Middleware token required for data update
//var csrftoken = jQuery("[name=csrfmiddlewaretoken]").val();
var csrftoken = getCookie('csrftoken');
return $.ajax({
beforeSend: function(xhr, settings) {
beforeSend: function(xhr) {
xhr.setRequestHeader('X-CSRFToken', csrftoken);
},
url: url,
@ -157,29 +169,35 @@ function showApiError(xhr) {
var message = null;
switch (xhr.status) {
case 0: // No response
// No response
case 0:
title = '{% trans "No Response" %}';
message = '{% trans "No response from the InvenTree server" %}';
break;
case 400: // Bad request
// Bad request
case 400:
// Note: Normally error code 400 is handled separately,
// and should now be shown here!
title = '{% trans "Error 400: Bad request" %}';
message = '{% trans "API request returned error code 400" %}';
break;
case 401: // Not authenticated
// Not authenticated
case 401:
title = '{% trans "Error 401: Not Authenticated" %}';
message = '{% trans "Authentication credentials not supplied" %}';
break;
case 403: // Permission denied
// Permission denied
case 403:
title = '{% trans "Error 403: Permission Denied" %}';
message = '{% trans "You do not have the required permissions to access this function" %}';
break;
case 404: // Resource not found
// Resource not found
case 404:
title = '{% trans "Error 404: Resource Not Found" %}';
message = '{% trans "The requested resource could not be located on the server" %}';
break;
case 408: // Timeout
// Timeout
case 408:
title = '{% trans "Error 408: Timeout" %}';
message = '{% trans "Connection timeout while requesting data from server" %}';
break;
@ -189,8 +207,8 @@ function showApiError(xhr) {
break;
}
message += "<hr>";
message += '<hr>';
message += renderErrorMessage(xhr);
showAlertDialog(title, message);
}
}

View File

@ -1,8 +1,18 @@
{% load i18n %}
/* globals
makeIconButton,
renderLink,
*/
/* exported
loadAttachmentTable,
reloadAttachmentTable,
*/
function reloadAttachmentTable() {
$('#attachment-table').bootstrapTable("refresh");
$('#attachment-table').bootstrapTable('refresh');
}
@ -13,7 +23,9 @@ function loadAttachmentTable(url, options) {
$(table).inventreeTable({
url: url,
name: options.name || 'attachments',
formatNoMatches: function() { return '{% trans "No attachments found" %}'},
formatNoMatches: function() {
return '{% trans "No attachments found" %}';
},
sortable: true,
search: false,
queryParams: options.filters || {},
@ -40,7 +52,7 @@ function loadAttachmentTable(url, options) {
{
field: 'attachment',
title: '{% trans "File" %}',
formatter: function(value, row) {
formatter: function(value) {
var icon = 'fa-file-alt';
@ -55,7 +67,7 @@ function loadAttachmentTable(url, options) {
} else {
var images = ['.png', '.jpg', '.bmp', '.gif', '.svg', '.tif'];
images.forEach(function (suffix) {
images.forEach(function(suffix) {
if (fn.endsWith(suffix)) {
icon = 'fa-file-image';
}
@ -106,4 +118,4 @@ function loadAttachmentTable(url, options) {
}
]
});
}
}

View File

@ -1,5 +1,27 @@
{% load i18n %}
/* globals
imageHoverIcon,
inventreePut,
makeIconButton,
modalEnable,
modalSetContent,
modalSetTitle,
modalSetSubmitText,
modalShowSubmitButton,
modalSubmit,
showAlertOrCache,
showQuestionDialog,
*/
/* exported
barcodeCheckIn,
barcodeScanDialog,
linkBarcodeDialog,
scanItemsIntoLocation,
unlinkBarcode,
*/
function makeBarcodeInput(placeholderText='', hintText='') {
/*
* Generate HTML for a barcode input
@ -99,7 +121,7 @@ function postBarcodeData(barcode_data, options={}) {
}
}
}
)
);
}
@ -109,7 +131,7 @@ function showBarcodeMessage(modal, message, style='danger') {
html += message;
html += "</div>";
html += '</div>';
$(modal + ' #barcode-error-message').html(html);
}
@ -256,7 +278,7 @@ function barcodeScanDialog() {
var modal = '#modal-form';
barcodeDialog(
"Scan Barcode",
'{% trans "Scan Barcode" %}',
{
onScan: function(response) {
if ('url' in response) {
@ -280,18 +302,18 @@ function barcodeScanDialog() {
/*
* Dialog for linking a particular barcode to a stock item.
*/
function linkBarcodeDialog(stockitem, options={}) {
function linkBarcodeDialog(stockitem) {
var modal = '#modal-form';
barcodeDialog(
"{% trans 'Link Barcode to Stock Item' %}",
'{% trans "Link Barcode to Stock Item" %}',
{
url: '/api/barcode/link/',
data: {
stockitem: stockitem,
},
onScan: function(response) {
onScan: function() {
$(modal).modal('hide');
location.reload();
@ -308,13 +330,13 @@ function unlinkBarcode(stockitem) {
var html = `<b>{% trans "Unlink Barcode" %}</b><br>`;
html += "{% trans 'This will remove the association between this stock item and the barcode' %}";
html += '{% trans "This will remove the association between this stock item and the barcode" %}';
showQuestionDialog(
"{% trans 'Unlink Barcode' %}",
'{% trans "Unlink Barcode" %}',
html,
{
accept_text: "{% trans 'Unlink' %}",
accept_text: '{% trans "Unlink" %}',
accept: function() {
inventreePut(
`/api/stock/${stockitem}/`,
@ -324,7 +346,7 @@ function unlinkBarcode(stockitem) {
},
{
method: 'PATCH',
success: function(response, status) {
success: function() {
location.reload();
},
},
@ -338,7 +360,7 @@ function unlinkBarcode(stockitem) {
/*
* Display dialog to check multiple stock items in to a stock location.
*/
function barcodeCheckIn(location_id, options={}) {
function barcodeCheckIn(location_id) {
var modal = '#modal-form';
@ -430,7 +452,9 @@ function barcodeCheckIn(location_id, options={}) {
// Called when the 'check-in' button is pressed
var data = {location: location_id};
var data = {
location: location_id
};
// Extract 'notes' field
data.notes = $(modal + ' #notes').val();
@ -447,7 +471,7 @@ function barcodeCheckIn(location_id, options={}) {
data.items = entries;
inventreePut(
"{% url 'api-stock-transfer' %}",
'{% url "api-stock-transfer" %}',
data,
{
method: 'POST',
@ -467,7 +491,7 @@ function barcodeCheckIn(location_id, options={}) {
},
onScan: function(response) {
if ('stockitem' in response) {
stockitem = response.stockitem;
var stockitem = response.stockitem;
var duplicate = false;
@ -478,7 +502,7 @@ function barcodeCheckIn(location_id, options={}) {
});
if (duplicate) {
showBarcodeMessage(modal, '{% trans "Stock Item already scanned" %}', "warning");
showBarcodeMessage(modal, '{% trans "Stock Item already scanned" %}', 'warning');
} else {
if (stockitem.location == location_id) {
@ -489,14 +513,14 @@ function barcodeCheckIn(location_id, options={}) {
// Add this stock item to the list
items.push(stockitem);
showBarcodeMessage(modal, '{% trans "Added stock item" %}', "success");
showBarcodeMessage(modal, '{% trans "Added stock item" %}', 'success');
reloadTable();
}
} else {
// Barcode does not match a stock item
showBarcodeMessage(modal, '{% trans "Barcode does not match Stock Item" %}', "warning");
showBarcodeMessage(modal, '{% trans "Barcode does not match Stock Item" %}', 'warning');
}
},
}
@ -525,12 +549,12 @@ function scanItemsIntoLocation(item_id_list, options={}) {
function updateLocationInfo(location) {
var div = $(modal + ' #header-div');
if (stock_location && stock_location.pk) {
if (location && location.pk) {
div.html(`
<div class='alert alert-block alert-info'>
<b>{% trans "Location" %}</b></br>
${stock_location.name}<br>
<i>${stock_location.description}</i>
${location.name}<br>
<i>${location.description}</i>
</div>
`);
} else {
@ -561,7 +585,7 @@ function scanItemsIntoLocation(item_id_list, options={}) {
items.push({
pk: pk,
});
})
});
var data = {
location: stock_location.pk,
@ -587,7 +611,7 @@ function scanItemsIntoLocation(item_id_list, options={}) {
}
}
}
)
);
},
onScan: function(response) {
updateLocationInfo(null);
@ -603,10 +627,10 @@ function scanItemsIntoLocation(item_id_list, options={}) {
showBarcodeMessage(
modal,
'{% trans "Barcode does not match a valid location" %}',
"warning",
'warning',
);
}
}
}
)
}
);
}

View File

@ -1,5 +1,25 @@
{% load i18n %}
/* globals
constructForm,
imageHoverIcon,
inventreeGet,
inventreePut,
launchModalForm,
loadTableFilters,
makePartIcons,
renderLink,
setupFilterList,
yesNoLabel,
*/
/* exported
newPartFromBomWizard,
loadBomTable,
removeRowFromBomWizard,
removeColFromBomWizard,
*/
/* BOM management functions.
* Requires follwing files to be loaded first:
* - api.js
@ -28,7 +48,7 @@ function bomItemFields() {
}
function reloadBomTable(table, options) {
function reloadBomTable(table) {
table.bootstrapTable('refresh');
}
@ -126,7 +146,7 @@ function loadBomTable(table, options) {
var params = {
part: options.parent_id,
ordering: 'name',
}
};
if (options.part_detail) {
params.part_detail = true;
@ -157,7 +177,7 @@ function loadBomTable(table, options) {
checkbox: true,
visible: true,
switchable: false,
formatter: function(value, row, index, field) {
formatter: function(value, row) {
// Disable checkbox if the row is defined for a *different* part!
if (row.part != options.parent_id) {
return {
@ -182,7 +202,7 @@ function loadBomTable(table, options) {
field: 'sub_part',
title: '{% trans "Part" %}',
sortable: true,
formatter: function(value, row, index, field) {
formatter: function(value, row) {
var url = `/part/${row.sub_part}/`;
var html = imageHoverIcon(row.sub_part_detail.thumbnail) + renderLink(row.sub_part_detail.full_name, url);
@ -225,7 +245,7 @@ function loadBomTable(table, options) {
title: '{% trans "Quantity" %}',
searchable: false,
sortable: true,
formatter: function(value, row, index, field) {
formatter: function(value, row) {
var text = value;
// The 'value' is a text string with (potentially) multiple trailing zeros
@ -244,13 +264,12 @@ function loadBomTable(table, options) {
},
});
cols.push(
{
cols.push({
field: 'sub_part_detail.stock',
title: '{% trans "Available" %}',
searchable: false,
sortable: true,
formatter: function(value, row, index, field) {
formatter: function(value, row) {
var url = `/part/${row.sub_part_detail.pk}/?display=stock`;
var text = value;
@ -263,32 +282,29 @@ function loadBomTable(table, options) {
}
});
cols.push(
{
cols.push({
field: 'purchase_price_range',
title: '{% trans "Purchase Price Range" %}',
searchable: false,
sortable: true,
});
cols.push(
{
cols.push({
field: 'purchase_price_avg',
title: '{% trans "Purchase Price Average" %}',
searchable: false,
sortable: true,
});
cols.push(
{
cols.push({
field: 'price_range',
title: '{% trans "Supplier Cost" %}',
sortable: true,
formatter: function(value, row, index, field) {
formatter: function(value) {
if (value) {
return value;
} else {
return "<span class='warning-msg'>{% trans 'No supplier pricing available' %}</span>";
return `<span class='warning-msg'>{% trans 'No supplier pricing available' %}</span>`;
}
}
});
@ -308,13 +324,13 @@ function loadBomTable(table, options) {
formatter: function(value) {
return yesNoLabel(value);
}
})
});
cols.push({
field: 'inherited',
title: '{% trans "Inherited" %}',
searchable: false,
formatter: function(value, row, index, field) {
formatter: function(value, row) {
// This BOM item *is* inheritable, but is defined for this BOM
if (!row.inherited) {
return yesNoLabel(false);
@ -332,9 +348,9 @@ function loadBomTable(table, options) {
cols.push(
{
'field': 'can_build',
'title': '{% trans "Can Build" %}',
formatter: function(value, row, index, field) {
field: 'can_build',
title: '{% trans "Can Build" %}',
formatter: function(value, row) {
var can_build = 0;
if (row.quantity > 0) {
@ -360,7 +376,7 @@ function loadBomTable(table, options) {
},
sortable: true,
}
)
);
// Part notes
cols.push(
@ -379,7 +395,7 @@ function loadBomTable(table, options) {
switchable: false,
field: 'pk',
visible: true,
formatter: function(value, row, index, field) {
formatter: function(value, row) {
if (row.part == options.parent_id) {
@ -391,7 +407,7 @@ function loadBomTable(table, options) {
var bDelt = `<button title='{% trans "Delete BOM Item" %}' class='bom-delete-button btn btn-default btn-glyph' type='button' pk='${row.pk}'><span class='fas fa-trash-alt icon-red'></span></button>`;
var html = "<div class='btn-group' role='group'>";
var html = `<div class='btn-group' role='group'>`;
html += bEdit;
html += bDelt;
@ -402,7 +418,7 @@ function loadBomTable(table, options) {
html += bValid;
}
html += "</div>";
html += `</div>`;
return html;
} else {
@ -434,7 +450,7 @@ function loadBomTable(table, options) {
response[idx].parentId = bom_pk;
if (response[idx].sub_part_detail.assembly) {
requestSubItems(response[idx].pk, response[idx].sub_part)
requestSubItems(response[idx].pk, response[idx].sub_part);
}
}
@ -446,7 +462,7 @@ function loadBomTable(table, options) {
console.log('Error requesting BOM for part=' + part_pk);
}
}
)
);
}
table.inventreeTable({
@ -459,7 +475,7 @@ function loadBomTable(table, options) {
name: 'bom',
sortable: true,
search: true,
rowStyle: function(row, index) {
rowStyle: function(row) {
var classes = [];
@ -532,7 +548,6 @@ function loadBomTable(table, options) {
table.on('click', '.bom-delete-button', function() {
var pk = $(this).attr('pk');
var url = `/part/bom/${pk}/delete/`;
constructForm(`/api/bom/${pk}/`, {
method: 'DELETE',
@ -546,7 +561,6 @@ function loadBomTable(table, options) {
table.on('click', '.bom-edit-button', function() {
var pk = $(this).attr('pk');
var url = `/part/bom/${pk}/edit/`;
var fields = bomItemFields();

View File

@ -1,6 +1,33 @@
{% load i18n %}
{% load inventree_extras %}
/* globals
buildStatusDisplay,
constructForm,
getFieldByName,
global_settings,
imageHoverIcon,
inventreeGet,
launchModalForm,
linkButtonsToSelection,
loadTableFilters,
makeIconBadge,
makeIconButton,
makePartIcons,
makeProgressBar,
renderLink,
setupFilterList,
*/
/* exported
editBuildOrder,
loadAllocationTable,
loadBuildOrderAllocationTable,
loadBuildOutputAllocationTable,
loadBuildPartsTable,
loadBuildTable,
*/
function buildFormFields() {
return {
@ -32,7 +59,7 @@ function buildFormFields() {
}
function editBuildOrder(pk, options={}) {
function editBuildOrder(pk) {
var fields = buildFormFields();
@ -76,10 +103,10 @@ function makeBuildOutputActionButtons(output, buildInfo, lines) {
var buildId = buildInfo.pk;
var outputId = 'untracked';
if (output) {
outputId = output.pk;
} else {
outputId = 'untracked';
}
var panel = `#allocation-panel-${outputId}`;
@ -98,7 +125,7 @@ function makeBuildOutputActionButtons(output, buildInfo, lines) {
html += makeIconButton(
'fa-magic icon-blue', 'button-output-auto', outputId,
'{% trans "Auto-allocate stock items to this output" %}',
);
);
}
if (lines > 0) {
@ -106,7 +133,7 @@ function makeBuildOutputActionButtons(output, buildInfo, lines) {
html += makeIconButton(
'fa-minus-circle icon-red', 'button-output-unallocate', outputId,
'{% trans "Unallocate stock from build output" %}',
);
);
}
@ -117,7 +144,7 @@ function makeBuildOutputActionButtons(output, buildInfo, lines) {
'fa-check icon-green', 'button-output-complete', outputId,
'{% trans "Complete build output" %}',
{
//disabled: true
// disabled: true
}
);
@ -125,7 +152,7 @@ function makeBuildOutputActionButtons(output, buildInfo, lines) {
html += makeIconButton(
'fa-trash-alt icon-red', 'button-output-delete', outputId,
'{% trans "Delete build output" %}',
);
);
// TODO - Add a button to "destroy" the particular build output (mark as damaged, scrap)
}
@ -202,13 +229,13 @@ function loadBuildOrderAllocationTable(table, options={}) {
options.params['build_detail'] = true;
options.params['location_detail'] = true;
var filters = loadTableFilters("buildorderallocation");
var filters = loadTableFilters('buildorderallocation');
for (var key in options.params) {
filters[key] = options.params[key];
}
setupFilterList("buildorderallocation", $(table));
setupFilterList('buildorderallocation', $(table));
$(table).inventreeTable({
url: '{% url "api-build-item-list" %}',
@ -219,7 +246,7 @@ function loadBuildOrderAllocationTable(table, options={}) {
paginationVAlign: 'bottom',
original: options.params,
formatNoMatches: function() {
return '{% trans "No build order allocations found" %}'
return '{% trans "No build order allocations found" %}';
},
columns: [
{
@ -345,13 +372,13 @@ function loadBuildOutputAllocationTable(buildInfo, output, options={}) {
// Register button callbacks once table data are loaded
// Callback for 'allocate' button
$(table).find(".button-add").click(function() {
$(table).find('.button-add').click(function() {
// Primary key of the 'sub_part'
var pk = $(this).attr('pk');
// Launch form to allocate new stock against this output
launchModalForm("{% url 'build-item-create' %}", {
launchModalForm('{% url "build-item-create" %}', {
success: reloadTable,
data: {
part: pk,
@ -391,7 +418,7 @@ function loadBuildOutputAllocationTable(buildInfo, output, options={}) {
}
}
}
)
);
}
}
]
@ -400,10 +427,8 @@ function loadBuildOutputAllocationTable(buildInfo, output, options={}) {
// Callback for 'buy' button
$(table).find('.button-buy').click(function() {
var pk = $(this).attr('pk');
var idx = $(this).closest('tr').attr('data-index');
var row = $(table).bootstrapTable('getData')[idx];
var pk = $(this).attr('pk');
launchModalForm('{% url "order-parts" %}', {
data: {
@ -447,7 +472,7 @@ function loadBuildOutputAllocationTable(buildInfo, output, options={}) {
// Load table of BOM items
$(table).inventreeTable({
url: "{% url 'api-bom-list' %}",
url: '{% url "api-bom-list" %}',
queryParams: {
part: partId,
sub_part_detail: true,
@ -467,7 +492,7 @@ function loadBuildOutputAllocationTable(buildInfo, output, options={}) {
build: buildId,
part_detail: true,
location_detail: true,
}
};
if (output) {
params.sub_part_trackable = true;
@ -496,7 +521,7 @@ function loadBuildOutputAllocationTable(buildInfo, output, options={}) {
var key = parseInt(part);
if (!(key in allocations)) {
allocations[key] = new Array();
allocations[key] = [];
}
allocations[key].push(item);
@ -573,8 +598,6 @@ function loadBuildOutputAllocationTable(buildInfo, output, options={}) {
element.html(html);
var lineItem = row;
var subTable = $(`#${subTableId}`);
subTable.bootstrapTable({
@ -595,7 +618,7 @@ function loadBuildOutputAllocationTable(buildInfo, output, options={}) {
width: '50%',
field: 'quantity',
title: '{% trans "Assigned Stock" %}',
formatter: function(value, row, index, field) {
formatter: function(value, row) {
var text = '';
var url = '';
@ -618,7 +641,7 @@ function loadBuildOutputAllocationTable(buildInfo, output, options={}) {
{
field: 'location',
title: '{% trans "Location" %}',
formatter: function(value, row, index, field) {
formatter: function(value, row) {
if (row.stock_item_detail.location) {
var text = row.stock_item_detail.location_name;
var url = `/stock/location/${row.stock_item_detail.location}/`;
@ -631,7 +654,7 @@ function loadBuildOutputAllocationTable(buildInfo, output, options={}) {
},
{
field: 'actions',
formatter: function(value, row, index, field) {
formatter: function(value, row) {
/* Actions available for a particular stock item allocation:
*
* - Edit the allocation quantity
@ -678,7 +701,7 @@ function loadBuildOutputAllocationTable(buildInfo, output, options={}) {
field: 'sub_part_detail.full_name',
title: '{% trans "Required Part" %}',
sortable: true,
formatter: function(value, row, index, field) {
formatter: function(value, row) {
var url = `/part/${row.sub_part}/`;
var thumb = row.sub_part_detail.thumbnail;
var name = row.sub_part_detail.full_name;
@ -709,7 +732,7 @@ function loadBuildOutputAllocationTable(buildInfo, output, options={}) {
field: 'allocated',
title: '{% trans "Allocated" %}',
sortable: true,
formatter: function(value, row, index, field) {
formatter: function(value, row) {
var allocated = 0;
if (row.allocations) {
@ -757,7 +780,7 @@ function loadBuildOutputAllocationTable(buildInfo, output, options={}) {
{
field: 'actions',
title: '{% trans "Actions" %}',
formatter: function(value, row, index, field) {
formatter: function(value, row) {
// Generate action buttons for this build output
var html = `<div class='btn-group float-right' role='group'>`;
@ -804,7 +827,7 @@ function loadBuildTable(table, options) {
params['part_detail'] = true;
if (!options.disableFilters) {
filters = loadTableFilters("build");
filters = loadTableFilters('build');
}
for (var key in params) {
@ -815,7 +838,7 @@ function loadBuildTable(table, options) {
var filterTarget = options.filterTarget || null;
setupFilterList("build", table, filterTarget);
setupFilterList('build', table, filterTarget);
$(table).inventreeTable({
method: 'get',
@ -846,7 +869,7 @@ function loadBuildTable(table, options) {
title: '{% trans "Build" %}',
sortable: true,
switchable: true,
formatter: function(value, row, index, field) {
formatter: function(value, row) {
var prefix = global_settings.BUILDORDER_REFERENCE_PREFIX;
@ -873,7 +896,7 @@ function loadBuildTable(table, options) {
title: '{% trans "Part" %}',
sortable: true,
sortName: 'part__name',
formatter: function(value, row, index, field) {
formatter: function(value, row) {
var html = imageHoverIcon(row.part_detail.thumbnail);
@ -887,12 +910,12 @@ function loadBuildTable(table, options) {
field: 'quantity',
title: '{% trans "Completed" %}',
sortable: true,
formatter: function(value, row, index, field) {
formatter: function(value, row) {
return makeProgressBar(
row.completed,
row.quantity,
{
//style: 'max',
// style: 'max',
}
);
}
@ -901,7 +924,7 @@ function loadBuildTable(table, options) {
field: 'status',
title: '{% trans "Status" %}',
sortable: true,
formatter: function(value, row, index, field) {
formatter: function(value) {
return buildStatusDisplay(value);
},
},
@ -914,13 +937,10 @@ function loadBuildTable(table, options) {
field: 'issued_by',
title: '{% trans "Issued by" %}',
sortable: true,
formatter: function(value, row, index, field) {
if (value)
{
formatter: function(value, row) {
if (value) {
return row.issued_by_detail.username;
}
else
{
} else {
return `<i>{% trans "No user information" %}</i>`;
}
}
@ -929,13 +949,10 @@ function loadBuildTable(table, options) {
field: 'responsible',
title: '{% trans "Responsible" %}',
sortable: true,
formatter: function(value, row, index, field) {
if (value)
{
formatter: function(value, row) {
if (value) {
return row.responsible_detail.name;
}
else
{
} else {
return '{% trans "No information" %}';
}
}
@ -968,7 +985,7 @@ function updateAllocationTotal(id, count, required) {
$('#allocation-total-'+id).html(count);
var el = $("#allocation-panel-" + id);
var el = $('#allocation-panel-' + id);
el.removeClass('part-allocation-pass part-allocation-underallocated part-allocation-overallocated');
if (count < required) {
@ -986,32 +1003,39 @@ function loadAllocationTable(table, part_id, part, url, required, button) {
table.bootstrapTable({
url: url,
sortable: false,
formatNoMatches: function() { return '{% trans "No parts allocated for" %} ' + part; },
formatNoMatches: function() {
return '{% trans "No parts allocated for" %} ' + part;
},
columns: [
{
field: 'stock_item_detail',
title: '{% trans "Stock Item" %}',
formatter: function(value, row, index, field) {
formatter: function(value) {
return '' + parseFloat(value.quantity) + ' x ' + value.part_name + ' @ ' + value.location_name;
}
},
{
field: 'stock_item_detail.quantity',
title: '{% trans "Available" %}',
formatter: function(value, row, index, field) {
formatter: function(value) {
return parseFloat(value);
}
},
{
field: 'quantity',
title: '{% trans "Allocated" %}',
formatter: function(value, row, index, field) {
formatter: function(value, row) {
var html = parseFloat(value);
var bEdit = "<button class='btn item-edit-button btn-sm' type='button' title='{% trans "Edit stock allocation" %}' url='/build/item/" + row.pk + "/edit/'><span class='fas fa-edit'></span></button>";
var bDel = "<button class='btn item-del-button btn-sm' type='button' title='{% trans "Delete stock allocation" %}' url='/build/item/" + row.pk + "/delete/'><span class='fas fa-trash-alt icon-red'></span></button>";
var bEdit = `<button class='btn item-edit-button btn-sm' type='button' title='{% trans "Edit stock allocation" %}' url='/build/item/${row.pk}/edit/'><span class='fas fa-edit'></span></button>`;
var bDel = `<button class='btn item-del-button btn-sm' type='button' title='{% trans "Delete stock allocation" %}' url='/build/item/${row.pk}/delete/'><span class='fas fa-trash-alt icon-red'></span></button>`;
html += "<div class='btn-group' style='float: right;'>" + bEdit + bDel + "</div>";
html += `
<div class='btn-group' style='float: right;'>
${bEdit}
${bDel}
</div>
`;
return html;
}
@ -1028,7 +1052,7 @@ function loadAllocationTable(table, part_id, part, url, required, button) {
});
});
table.on('load-success.bs.table', function(data) {
table.on('load-success.bs.table', function() {
// Extract table data
var results = table.bootstrapTable('getData');
@ -1106,9 +1130,6 @@ function loadBuildPartsTable(table, options={}) {
$(table).find('.button-buy').click(function() {
var pk = $(this).attr('pk');
var idx = $(this).closest('tr').attr('data-index');
var row = $(table).bootstrapTable('getData')[idx];
launchModalForm('{% url "order-parts" %}', {
data: {
parts: [
@ -1122,10 +1143,6 @@ function loadBuildPartsTable(table, options={}) {
$(table).find('.button-build').click(function() {
var pk = $(this).attr('pk');
// Extract row data from the table
var idx = $(this).closest('tr').attr('data-index');
var row = $(table).bootstrapTable('getData')[idx];
newBuildOrder({
part: pk,
parent: options.build,
@ -1139,7 +1156,7 @@ function loadBuildPartsTable(table, options={}) {
title: '{% trans "Part" %}',
switchable: false,
sortable: true,
formatter: function(value, row, index, field) {
formatter: function(value, row) {
var url = `/part/${row.sub_part}/`;
var html = imageHoverIcon(row.sub_part_detail.thumbnail) + renderLink(row.sub_part_detail.full_name, url);
@ -1177,7 +1194,7 @@ function loadBuildPartsTable(table, options={}) {
switchable: false,
field: 'sub_part_detail.stock',
title: '{% trans "Available" %}',
formatter: function(value, row, index, field) {
formatter: function(value, row) {
return makeProgressBar(
value,
row.quantity * options.build_remaining,
@ -1201,7 +1218,7 @@ function loadBuildPartsTable(table, options={}) {
field: 'actions',
title: '{% trans "Actions" %}',
switchable: false,
formatter: function(value, row, index, field) {
formatter: function(value, row) {
// Generate action buttons against the part
var html = `<div class='btn-group float-right' role='group'>`;
@ -1228,7 +1245,7 @@ function loadBuildPartsTable(table, options={}) {
sortable: true,
search: true,
onPostBody: setupTableCallbacks,
rowStyle: function(row, index) {
rowStyle: function(row) {
var classes = [];
// Shade rows differently if they are for different parent parts
@ -1254,4 +1271,4 @@ function loadBuildPartsTable(table, options={}) {
original: params,
columns: columns,
});
}
}

View File

@ -1,6 +1,33 @@
{% load i18n %}
/* globals
constructForm,
imageHoverIcon,
inventreeDelete,
loadTableFilters,
makeIconButton,
renderLink,
setupFilterList,
showQuestionDialog,
*/
/* exported
createCompany,
createManufacturerPart,
createSupplierPart,
deleteManufacturerParts,
editCompany,
loadCompanyTable,
loadManufacturerPartTable,
loadManufacturerPartParameterTable,
loadSupplierPartTable,
*/
/**
* Construct a set of form fields for creating / editing a ManufacturerPart
* @returns
*/
function manufacturerPartFields() {
return {
@ -17,6 +44,10 @@ function manufacturerPartFields() {
}
/**
* Launches a form to create a new ManufacturerPart
* @param {object} options
*/
function createManufacturerPart(options={}) {
var fields = manufacturerPartFields();
@ -32,14 +63,14 @@ function createManufacturerPart(options={}) {
fields.manufacturer.secondary = {
title: '{% trans "Add Manufacturer" %}',
fields: function(data) {
fields: function() {
var company_fields = companyFormFields();
company_fields.is_manufacturer.value = true;
return company_fields;
}
}
};
constructForm('{% url "api-manufacturer-part-list" %}', {
fields: fields,
@ -50,6 +81,11 @@ function createManufacturerPart(options={}) {
}
/**
* Launches a form to edit a ManufacturerPart
* @param {integer} part - ID of a ManufacturerPart
* @param {object} options
*/
function editManufacturerPart(part, options={}) {
var url = `/api/company/part/manufacturer/${part}/`;
@ -126,7 +162,7 @@ function createSupplierPart(options={}) {
// Add a secondary modal for the supplier
fields.supplier.secondary = {
title: '{% trans "Add Supplier" %}',
fields: function(data) {
fields: function() {
var company_fields = companyFormFields();
company_fields.is_supplier.value = true;
@ -185,7 +221,7 @@ function deleteSupplierPart(part, options={}) {
// Returns a default form-set for creating / editing a Company object
function companyFormFields(options={}) {
function companyFormFields() {
return {
name: {},
@ -228,7 +264,7 @@ function editCompany(pk, options={}) {
title: '{% trans "Edit Company" %}',
}
);
};
}
/*
* Launches a form to create a new company.
@ -265,13 +301,13 @@ function loadCompanyTable(table, url, options={}) {
// Query parameters
var params = options.params || {};
var filters = loadTableFilters("company");
var filters = loadTableFilters('company');
for (var key in params) {
filters[key] = params[key];
}
setupFilterList("company", $(table));
setupFilterList('company', $(table));
var columns = [
{
@ -285,7 +321,7 @@ function loadCompanyTable(table, url, options={}) {
title: '{% trans "Company" %}',
sortable: true,
switchable: false,
formatter: function(value, row, index, field) {
formatter: function(value, row) {
var html = imageHoverIcon(row.image) + renderLink(value, row.url);
if (row.is_customer) {
@ -310,7 +346,7 @@ function loadCompanyTable(table, url, options={}) {
{
field: 'website',
title: '{% trans "Website" %}',
formatter: function(value, row, index, field) {
formatter: function(value) {
if (value) {
return renderLink(value, value);
}
@ -345,7 +381,9 @@ function loadCompanyTable(table, url, options={}) {
queryParams: filters,
groupBy: false,
sidePagination: 'server',
formatNoMatches: function() { return "{% trans "No company information found" %}"; },
formatNoMatches: function() {
return '{% trans "No company information found" %}';
},
showColumns: true,
name: options.pagetype || 'company',
columns: columns,
@ -366,18 +404,18 @@ function deleteManufacturerParts(selections, options={}) {
<p>{% trans "The following manufacturer parts will be deleted" %}:</p>
<ul>`;
selections.forEach(function(item) {
parts.push(item.pk);
selections.forEach(function(item) {
parts.push(item.pk);
text += `
<li>
<p>${item.MPN} - ${item.part_detail.full_name}</p>
</li>`;
});
text += `
</ul>
</div>`;
<li>
<p>${item.MPN} - ${item.part_detail.full_name}</p>
</li>`;
});
text += `
</ul>
</div>`;
showQuestionDialog(
'{% trans "Delete Manufacturer Parts" %}',
@ -401,7 +439,7 @@ function deleteManufacturerParts(selections, options={}) {
if (options.onSuccess) {
options.onSuccess();
}
})
});
}
}
);
@ -418,13 +456,13 @@ function loadManufacturerPartTable(table, url, options) {
var params = options.params || {};
// Load filters
var filters = loadTableFilters("manufacturer-part");
var filters = loadTableFilters('manufacturer-part');
for (var key in params) {
filters[key] = params[key];
}
setupFilterList("manufacturer-part", $(table));
setupFilterList('manufacturer-part', $(table));
$(table).inventreeTable({
url: url,
@ -433,7 +471,9 @@ function loadManufacturerPartTable(table, url, options) {
queryParams: filters,
name: 'manufacturerparts',
groupBy: false,
formatNoMatches: function() { return '{% trans "No manufacturer parts found" %}'; },
formatNoMatches: function() {
return '{% trans "No manufacturer parts found" %}';
},
columns: [
{
checkbox: true,
@ -445,7 +485,7 @@ function loadManufacturerPartTable(table, url, options) {
sortable: true,
field: 'part_detail.full_name',
title: '{% trans "Part" %}',
formatter: function(value, row, index, field) {
formatter: function(value, row) {
var url = `/part/${row.part}/`;
@ -470,7 +510,7 @@ function loadManufacturerPartTable(table, url, options) {
sortable: true,
field: 'manufacturer',
title: '{% trans "Manufacturer" %}',
formatter: function(value, row, index, field) {
formatter: function(value, row) {
if (value && row.manufacturer_detail) {
var name = row.manufacturer_detail.name;
var url = `/company/${value}/`;
@ -478,7 +518,7 @@ function loadManufacturerPartTable(table, url, options) {
return html;
} else {
return "-";
return '-';
}
}
},
@ -486,14 +526,14 @@ function loadManufacturerPartTable(table, url, options) {
sortable: true,
field: 'MPN',
title: '{% trans "MPN" %}',
formatter: function(value, row, index, field) {
formatter: function(value, row) {
return renderLink(value, `/manufacturer-part/${row.pk}/`);
}
},
{
field: 'link',
title: '{% trans "Link" %}',
formatter: function(value, row, index, field) {
formatter: function(value) {
if (value) {
return renderLink(value, value);
} else {
@ -536,8 +576,9 @@ function loadManufacturerPartTable(table, url, options) {
{
onSuccess: function() {
$(table).bootstrapTable('refresh');
}
}
});
);
});
$(table).find('.button-manufacturer-part-delete').click(function() {
@ -548,9 +589,10 @@ function loadManufacturerPartTable(table, url, options) {
{
onSuccess: function() {
$(table).bootstrapTable('refresh');
}
}
});
})
);
});
}
});
}
@ -564,7 +606,7 @@ function loadManufacturerPartParameterTable(table, url, options) {
var params = options.params || {};
// Load filters
var filters = loadTableFilters("manufacturer-part-parameters");
var filters = loadTableFilters('manufacturer-part-parameters');
// Overwrite explicit parameters
for (var key in params) {
@ -580,7 +622,9 @@ function loadManufacturerPartParameterTable(table, url, options) {
queryParams: filters,
name: 'manufacturerpartparameters',
groupBy: false,
formatNoMatches: function() { return '{% trans "No parameters found" %}'; },
formatNoMatches: function() {
return '{% trans "No parameters found" %}';
},
columns: [
{
checkbox: true,
@ -668,13 +712,13 @@ function loadSupplierPartTable(table, url, options) {
var params = options.params || {};
// Load filters
var filters = loadTableFilters("supplier-part");
var filters = loadTableFilters('supplier-part');
for (var key in params) {
filters[key] = params[key];
}
setupFilterList("supplier-part", $(table));
setupFilterList('supplier-part', $(table));
$(table).inventreeTable({
url: url,
@ -683,7 +727,9 @@ function loadSupplierPartTable(table, url, options) {
queryParams: filters,
name: 'supplierparts',
groupBy: false,
formatNoMatches: function() { return '{% trans "No supplier parts found" %}'; },
formatNoMatches: function() {
return '{% trans "No supplier parts found" %}';
},
columns: [
{
checkbox: true,
@ -695,7 +741,7 @@ function loadSupplierPartTable(table, url, options) {
sortable: true,
field: 'part_detail.full_name',
title: '{% trans "Part" %}',
formatter: function(value, row, index, field) {
formatter: function(value, row) {
var url = `/part/${row.part}/`;
@ -720,7 +766,7 @@ function loadSupplierPartTable(table, url, options) {
sortable: true,
field: 'supplier',
title: '{% trans "Supplier" %}',
formatter: function(value, row, index, field) {
formatter: function(value, row) {
if (value) {
var name = row.supplier_detail.name;
var url = `/company/${value}/`;
@ -728,7 +774,7 @@ function loadSupplierPartTable(table, url, options) {
return html;
} else {
return "-";
return '-';
}
},
},
@ -736,7 +782,7 @@ function loadSupplierPartTable(table, url, options) {
sortable: true,
field: 'SKU',
title: '{% trans "Supplier Part" %}',
formatter: function(value, row, index, field) {
formatter: function(value, row) {
return renderLink(value, `/supplier-part/${row.pk}/`);
}
},
@ -746,7 +792,7 @@ function loadSupplierPartTable(table, url, options) {
sortable: true,
field: 'manufacturer_detail.name',
title: '{% trans "Manufacturer" %}',
formatter: function(value, row, index, field) {
formatter: function(value, row) {
if (value && row.manufacturer_detail) {
var name = value;
var url = `/company/${row.manufacturer_detail.pk}/`;
@ -754,7 +800,7 @@ function loadSupplierPartTable(table, url, options) {
return html;
} else {
return "-";
return '-';
}
}
},
@ -764,18 +810,18 @@ function loadSupplierPartTable(table, url, options) {
sortable: true,
field: 'manufacturer_part_detail.MPN',
title: '{% trans "MPN" %}',
formatter: function(value, row, index, field) {
formatter: function(value, row) {
if (value && row.manufacturer_part) {
return renderLink(value, `/manufacturer-part/${row.manufacturer_part}/`);
} else {
return "-";
return '-';
}
}
},
{
field: 'link',
title: '{% trans "Link" %}',
formatter: function(value, row, index, field) {
formatter: function(value) {
if (value) {
return renderLink(value, value);
} else {
@ -827,8 +873,9 @@ function loadSupplierPartTable(table, url, options) {
{
onSuccess: function() {
$(table).bootstrapTable('refresh');
}
}
});
);
});
$(table).find('.button-supplier-part-delete').click(function() {
@ -839,9 +886,10 @@ function loadSupplierPartTable(table, url, options) {
{
onSuccess: function() {
$(table).bootstrapTable('refresh');
}
}
});
})
);
});
}
});
}
}

View File

@ -1,5 +1,16 @@
{% load i18n %}
/* globals
getAvailableTableFilters,
inventreeLoad,
inventreeSave,
reloadTableFilters,
*/
/* exported
setupFilterList,
*/
/**
* Code for managing query filters / table options.
*
@ -16,12 +27,12 @@
function defaultFilters() {
return {
stock: "cascade=1&in_stock=1",
build: "",
parts: "cascade=1",
company: "",
salesorder: "",
purchaseorder: "",
stock: 'cascade=1&in_stock=1',
build: '',
parts: 'cascade=1',
company: '',
salesorder: '',
purchaseorder: '',
};
}
@ -34,7 +45,7 @@ function defaultFilters() {
*/
function loadTableFilters(tableKey) {
var lookup = "table-filters-" + tableKey.toLowerCase();
var lookup = 'table-filters-' + tableKey.toLowerCase();
var defaults = defaultFilters()[tableKey] || '';
@ -42,7 +53,7 @@ function loadTableFilters(tableKey) {
var filters = {};
filterstring.split("&").forEach(function(item, index) {
filterstring.split('&').forEach(function(item) {
item = item.trim();
if (item.length > 0) {
@ -67,7 +78,7 @@ function loadTableFilters(tableKey) {
* @param {*} filters - object of string:string pairs
*/
function saveTableFilters(tableKey, filters) {
var lookup = "table-filters-" + tableKey.toLowerCase();
var lookup = 'table-filters-' + tableKey.toLowerCase();
var strings = [];
@ -190,7 +201,7 @@ function generateAvailableFilterList(tableKey) {
var html = `<select class='form-control filter-input' id='${id}' name='tag'>`;
html += "<option value=''>{% trans 'Select filter' %}</option>";
html += `<option value=''>{% trans 'Select filter' %}</option>`;
for (var opt in remaining) {
var title = getFilterTitle(tableKey, opt);
@ -227,7 +238,7 @@ function generateFilterInput(tableKey, filterKey) {
html = `<select class='form-control filter-input' id='${id}' name='value'>`;
for (var key in options) {
option = options[key];
var option = options[key];
html += `<option value='${key}'>${option.value}</option>`;
}
@ -295,15 +306,11 @@ function setupFilterList(tableKey, table, target) {
var html = '';
//`<div class='filter-input'>`;
html += generateAvailableFilterList(tableKey);
html += generateFilterInput(tableKey);
html += `<button title='{% trans "Create filter" %}' class='btn btn-default filter-tag' id='${make}'><span class='fas fa-plus'></span></button>`;
//html += '</div>';
element.append(html);
// Add a callback for when the filter tag selection is changed
@ -346,7 +353,7 @@ function setupFilterList(tableKey, table, target) {
});
// Add callback for deleting each filter
element.find(".close").click(function(event) {
element.find('.close').click(function() {
var me = $(this);
var filter = me.attr(`filter-tag-${tableKey}`);
@ -372,15 +379,6 @@ function getFilterTitle(tableKey, filterKey) {
}
/**
* Return the pretty description for the given table and filter selection
*/
function getFilterDescription(tableKey, filterKey) {
var settings = getFilterSettings(tableKey, filterKey);
return settings.title;
}
/*
* Return a description for the given table and filter selection.
*/

View File

@ -1,6 +1,34 @@
{% load i18n %}
{% load inventree_extras %}
/* globals
attachToggle,
createNewModal,
inventreeFormDataUpload,
inventreeGet,
inventreePut,
modalEnable,
modalShowSubmitButton,
renderBuild,
renderCompany,
renderManufacturerPart,
renderOwner,
renderPart,
renderPartCategory,
renderPartParameterTemplate,
renderStockItem,
renderStockLocation,
renderSupplierPart,
renderUser,
showAlertDialog,
showAlertOrCache,
showApiError,
*/
/* exported
setFormGroupVisibility
*/
/**
*
* This file contains code for rendering (and managing) HTML forms
@ -81,7 +109,7 @@ function canDelete(OPTIONS) {
* Get the API endpoint options at the provided URL,
* using a HTTP options request.
*/
function getApiEndpointOptions(url, callback, options) {
function getApiEndpointOptions(url, callback) {
// Return the ajax request object
$.ajax({
@ -93,7 +121,7 @@ function getApiEndpointOptions(url, callback, options) {
json: 'application/json',
},
success: callback,
error: function(request, status, error) {
error: function() {
// TODO: Handle error
console.log(`ERROR in getApiEndpointOptions at '${url}'`);
}
@ -172,7 +200,7 @@ function constructChangeForm(fields, options) {
constructFormBody(fields, options);
},
error: function(request, status, error) {
error: function() {
// TODO: Handle error here
console.log(`ERROR in constructChangeForm at '${options.url}'`);
}
@ -211,7 +239,7 @@ function constructDeleteForm(fields, options) {
constructFormBody(fields, options);
},
error: function(request, status, error) {
error: function() {
// TODO: Handle error here
console.log(`ERROR in constructDeleteForm at '${options.url}`);
}
@ -286,58 +314,58 @@ function constructForm(url, options) {
*/
switch (options.method) {
case 'POST':
if (canCreate(OPTIONS)) {
constructCreateForm(OPTIONS.actions.POST, options);
} else {
// User does not have permission to POST to the endpoint
showAlertDialog(
'{% trans "Action Prohibited" %}',
'{% trans "Create operation not allowed" %}'
);
console.log(`'POST action unavailable at ${url}`);
}
break;
case 'PUT':
case 'PATCH':
if (canChange(OPTIONS)) {
constructChangeForm(OPTIONS.actions.PUT, options);
} else {
// User does not have permission to PUT/PATCH to the endpoint
showAlertDialog(
'{% trans "Action Prohibited" %}',
'{% trans "Update operation not allowed" %}'
);
console.log(`${options.method} action unavailable at ${url}`);
}
break;
case 'DELETE':
if (canDelete(OPTIONS)) {
constructDeleteForm(OPTIONS.actions.DELETE, options);
} else {
// User does not have permission to DELETE to the endpoint
showAlertDialog(
'{% trans "Action Prohibited" %}',
'{% trans "Delete operation not allowed" %}'
);
console.log(`DELETE action unavailable at ${url}`);
}
break;
case 'GET':
if (canView(OPTIONS)) {
// TODO?
} else {
// User does not have permission to GET to the endpoint
showAlertDialog(
'{% trans "Action Prohibited" %}',
'{% trans "View operation not allowed" %}'
);
console.log(`GET action unavailable at ${url}`);
}
break;
default:
console.log(`constructForm() called with invalid method '${options.method}'`);
break;
case 'POST':
if (canCreate(OPTIONS)) {
constructCreateForm(OPTIONS.actions.POST, options);
} else {
// User does not have permission to POST to the endpoint
showAlertDialog(
'{% trans "Action Prohibited" %}',
'{% trans "Create operation not allowed" %}'
);
console.log(`'POST action unavailable at ${url}`);
}
break;
case 'PUT':
case 'PATCH':
if (canChange(OPTIONS)) {
constructChangeForm(OPTIONS.actions.PUT, options);
} else {
// User does not have permission to PUT/PATCH to the endpoint
showAlertDialog(
'{% trans "Action Prohibited" %}',
'{% trans "Update operation not allowed" %}'
);
console.log(`${options.method} action unavailable at ${url}`);
}
break;
case 'DELETE':
if (canDelete(OPTIONS)) {
constructDeleteForm(OPTIONS.actions.DELETE, options);
} else {
// User does not have permission to DELETE to the endpoint
showAlertDialog(
'{% trans "Action Prohibited" %}',
'{% trans "Delete operation not allowed" %}'
);
console.log(`DELETE action unavailable at ${url}`);
}
break;
case 'GET':
if (canView(OPTIONS)) {
// TODO?
} else {
// User does not have permission to GET to the endpoint
showAlertDialog(
'{% trans "Action Prohibited" %}',
'{% trans "View operation not allowed" %}'
);
console.log(`GET action unavailable at ${url}`);
}
break;
default:
console.log(`constructForm() called with invalid method '${options.method}'`);
break;
}
});
}
@ -376,7 +404,7 @@ function constructFormBody(fields, options) {
}
// Provide each field object with its own name
for(field in fields) {
for (field in fields) {
fields[field].name = field;
// If any "instance_filters" are defined for the endpoint, copy them across (overwrite)
@ -429,19 +457,19 @@ function constructFormBody(fields, options) {
for (var idx = 0; idx < field_names.length; idx++) {
var name = field_names[idx];
var field_name = field_names[idx];
var field = fields[name];
var field = fields[field_name];
switch (field.type) {
// Skip field types which are simply not supported
case 'nested object':
continue;
default:
break;
// Skip field types which are simply not supported
case 'nested object':
continue;
default:
break;
}
html += constructField(name, field, options);
html += constructField(field_name, field, options);
}
if (options.current_group) {
@ -647,19 +675,19 @@ function submitFormData(fields, options) {
data,
{
method: options.method,
success: function(response, status) {
success: function(response) {
handleFormSuccess(response, options);
},
error: function(xhr, status, thrownError) {
error: function(xhr) {
switch (xhr.status) {
case 400: // Bad request
handleFormErrors(xhr.responseJSON, fields, options);
break;
default:
$(options.modal).modal('hide');
showApiError(xhr);
break;
case 400:
handleFormErrors(xhr.responseJSON, fields, options);
break;
default:
$(options.modal).modal('hide');
showApiError(xhr);
break;
}
}
}
@ -682,7 +710,9 @@ function updateFieldValues(fields, options) {
var field = fields[name] || null;
if (field == null) { continue; }
if (field == null) {
continue;
}
var value = field.value;
@ -690,7 +720,9 @@ function updateFieldValues(fields, options) {
value = field.default;
}
if (value == null) { continue; }
if (value == null) {
continue;
}
updateFieldValue(name, value, field, options);
}
@ -701,22 +733,22 @@ function updateFieldValue(name, value, field, options) {
var el = $(options.modal).find(`#id_${name}`);
switch (field.type) {
case 'boolean':
el.prop('checked', value);
break;
case 'related field':
// Clear?
if (value == null && !field.required) {
el.val(null).trigger('change');
}
// TODO - Specify an actual value!
break;
case 'file upload':
case 'image upload':
break;
default:
el.val(value);
break;
case 'boolean':
el.prop('checked', value);
break;
case 'related field':
// Clear?
if (value == null && !field.required) {
el.val(null).trigger('change');
}
// TODO - Specify an actual value!
break;
case 'file upload':
case 'image upload':
break;
default:
el.val(value);
break;
}
}
@ -741,21 +773,21 @@ function getFormFieldValue(name, field, options) {
var value = null;
switch (field.type) {
case 'boolean':
value = el.is(":checked");
break;
case 'date':
case 'datetime':
value = el.val();
case 'boolean':
value = el.is(':checked');
break;
case 'date':
case 'datetime':
value = el.val();
// Ensure empty values are sent as nulls
if (!value || value.length == 0) {
value = null;
}
break;
default:
value = el.val();
break;
// Ensure empty values are sent as nulls
if (!value || value.length == 0) {
value = null;
}
break;
default:
value = el.val();
break;
}
return value;
@ -783,19 +815,19 @@ function handleFormSuccess(response, options) {
// Display any messages
if (response && response.success) {
showAlertOrCache("alert-success", response.success, cache);
showAlertOrCache('alert-success', response.success, cache);
}
if (response && response.info) {
showAlertOrCache("alert-info", response.info, cache);
showAlertOrCache('alert-info', response.info, cache);
}
if (response && response.warning) {
showAlertOrCache("alert-warning", response.warning, cache);
showAlertOrCache('alert-warning', response.warning, cache);
}
if (response && response.danger) {
showAlertOrCache("alert-danger", response.danger, cache);
showAlertOrCache('alert-danger', response.danger, cache);
}
if (options.onSuccess) {
@ -879,7 +911,7 @@ function handleFormErrors(errors, fields, options) {
var first_error_field = null;
for (field_name in errors) {
for (var field_name in errors) {
// Add the 'has-error' class
$(options.modal).find(`#div_id_${field_name}`).addClass('has-error');
@ -893,16 +925,16 @@ function handleFormErrors(errors, fields, options) {
}
// Add an entry for each returned error message
for (var idx = field_errors.length-1; idx >= 0; idx--) {
for (var ii = field_errors.length-1; ii >= 0; ii--) {
var error_text = field_errors[idx];
var error_text = field_errors[ii];
var html = `
<span id='error_${idx+1}_id_${field_name}' class='help-block form-error-message'>
var error_html = `
<span id='error_${ii+1}_id_${field_name}' class='help-block form-error-message'>
<strong>${error_text}</strong>
</span>`;
field_dom.append(html);
field_dom.append(error_html);
}
}
@ -1016,9 +1048,9 @@ function initializeGroups(fields, options) {
var group_options = options.groups[group];
if (group_options.collapsed) {
$(modal).find(`#form-panel-content-${group}`).collapse("hide");
$(modal).find(`#form-panel-content-${group}`).collapse('hide');
} else {
$(modal).find(`#form-panel-content-${group}`).collapse("show");
$(modal).find(`#form-panel-content-${group}`).collapse('show');
}
if (group_options.hidden) {
@ -1059,12 +1091,14 @@ function initializeRelatedFields(fields, options) {
if (!field || field.hidden) continue;
switch (field.type) {
case 'related field':
initializeRelatedField(field, fields, options);
break;
case 'choice':
initializeChoiceField(field, fields, options);
break;
case 'related field':
initializeRelatedField(field, fields, options);
break;
case 'choice':
initializeChoiceField(field, fields, options);
break;
default:
break;
}
}
}
@ -1103,14 +1137,14 @@ function addSecondaryModal(field, fields, options) {
if (secondary.fields instanceof Function) {
// Extract form values at time of button press
var data = extractFormData(fields, options)
var data = extractFormData(fields, options);
secondary.fields = secondary.fields(data);
}
// If no onSuccess function is defined, provide a default one
if (!secondary.onSuccess) {
secondary.onSuccess = function(data, opts) {
secondary.onSuccess = function(data) {
// Force refresh from the API, to get full detail
inventreeGet(`${url}${data.pk}/`, {}, {
@ -1176,6 +1210,8 @@ function initializeRelatedField(field, fields, options) {
cache: true,
data: function(params) {
var offset = 0;
if (!params.page) {
offset = 0;
} else {
@ -1229,7 +1265,7 @@ function initializeRelatedField(field, fields, options) {
return results;
},
},
templateResult: function(item, container) {
templateResult: function(item) {
// Extract 'instance' data passed through from an initial value
// Or, use the raw 'item' data as a backup
@ -1254,7 +1290,7 @@ function initializeRelatedField(field, fields, options) {
return `${name} - ${item.id}`;
}
},
templateSelection: function(item, container) {
templateSelection: function(item) {
// Extract 'instance' data passed through from an initial value
// Or, use the raw 'item' data as a backup
@ -1266,7 +1302,6 @@ function initializeRelatedField(field, fields, options) {
if (!data.pk) {
return field.placeholder || '';
return $(searching());
}
// Custom formatting for selected item
@ -1369,41 +1404,41 @@ function renderModelData(name, model, data, parameters, options) {
// Find a custom renderer
switch (model) {
case 'company':
renderer = renderCompany;
break;
case 'stockitem':
renderer = renderStockItem;
break;
case 'stocklocation':
renderer = renderStockLocation;
break;
case 'part':
renderer = renderPart;
break;
case 'partcategory':
renderer = renderPartCategory;
break;
case 'partparametertemplate':
renderer = renderPartParameterTemplate;
break;
case 'manufacturerpart':
renderer = renderManufacturerPart;
break;
case 'supplierpart':
renderer = renderSupplierPart;
break;
case 'build':
renderer = renderBuild;
break;
case 'owner':
renderer = renderOwner;
break;
case 'user':
renderer = renderUser;
break;
default:
break;
case 'company':
renderer = renderCompany;
break;
case 'stockitem':
renderer = renderStockItem;
break;
case 'stocklocation':
renderer = renderStockLocation;
break;
case 'part':
renderer = renderPart;
break;
case 'partcategory':
renderer = renderPartCategory;
break;
case 'partparametertemplate':
renderer = renderPartParameterTemplate;
break;
case 'manufacturerpart':
renderer = renderManufacturerPart;
break;
case 'supplierpart':
renderer = renderSupplierPart;
break;
case 'build':
renderer = renderBuild;
break;
case 'owner':
renderer = renderOwner;
break;
case 'user':
renderer = renderUser;
break;
default:
break;
}
if (renderer != null) {
@ -1526,18 +1561,18 @@ function constructField(name, parameters, options) {
// Some fields can have 'clear' inputs associated with them
if (!parameters.required && !parameters.read_only) {
switch (parameters.type) {
case 'string':
case 'url':
case 'email':
case 'integer':
case 'float':
case 'decimal':
case 'related field':
case 'date':
extra = true;
break;
default:
break;
case 'string':
case 'url':
case 'email':
case 'integer':
case 'float':
case 'decimal':
case 'related field':
case 'date':
extra = true;
break;
default:
break;
}
}
@ -1560,7 +1595,7 @@ function constructField(name, parameters, options) {
</span>`;
}
html += `</div>`; // input-group
html += `</div>`; // input-group
}
if (parameters.help_text && !options.hideLabels) {
@ -1570,8 +1605,9 @@ function constructField(name, parameters, options) {
// Div for error messages
html += `<div id='errors-${name}'></div>`;
html += `</div>`; // controls
html += `</div>`; // form-group
html += `</div>`; // controls
html += `</div>`; // form-group
if (parameters.after) {
html += parameters.after;
@ -1629,39 +1665,39 @@ function constructInput(name, parameters, options) {
var func = null;
switch (parameters.type) {
case 'boolean':
func = constructCheckboxInput;
break;
case 'string':
case 'url':
case 'email':
func = constructTextInput;
break;
case 'integer':
case 'float':
case 'decimal':
func = constructNumberInput;
break;
case 'choice':
func = constructChoiceInput;
break;
case 'related field':
func = constructRelatedFieldInput;
break;
case 'image upload':
case 'file upload':
func = constructFileUploadInput;
break;
case 'date':
func = constructDateInput;
break;
case 'candy':
func = constructCandyInput;
break;
default:
// Unsupported field type!
break;
}
case 'boolean':
func = constructCheckboxInput;
break;
case 'string':
case 'url':
case 'email':
func = constructTextInput;
break;
case 'integer':
case 'float':
case 'decimal':
func = constructNumberInput;
break;
case 'choice':
func = constructChoiceInput;
break;
case 'related field':
func = constructRelatedFieldInput;
break;
case 'image upload':
case 'file upload':
func = constructFileUploadInput;
break;
case 'date':
func = constructDateInput;
break;
case 'candy':
func = constructCandyInput;
break;
default:
// Unsupported field type!
break;
}
if (func != null) {
html = func(name, parameters, options);
@ -1747,7 +1783,7 @@ function constructInputOptions(name, classes, type, parameters) {
// Construct a "hidden" input
function constructHiddenInput(name, parameters, options) {
function constructHiddenInput(name, parameters) {
return constructInputOptions(
name,
@ -1759,7 +1795,7 @@ function constructHiddenInput(name, parameters, options) {
// Construct a "checkbox" input
function constructCheckboxInput(name, parameters, options) {
function constructCheckboxInput(name, parameters) {
return constructInputOptions(
name,
@ -1771,24 +1807,24 @@ function constructCheckboxInput(name, parameters, options) {
// Construct a "text" input
function constructTextInput(name, parameters, options) {
function constructTextInput(name, parameters) {
var classes = '';
var type = '';
switch (parameters.type) {
default:
classes = 'textinput textInput form-control';
type = 'text';
break;
case 'url':
classes = 'urlinput form-control';
type = 'url';
break;
case 'email':
classes = 'emailinput form-control';
type = 'email';
break;
default:
classes = 'textinput textInput form-control';
type = 'text';
break;
case 'url':
classes = 'urlinput form-control';
type = 'url';
break;
case 'email':
classes = 'emailinput form-control';
type = 'email';
break;
}
return constructInputOptions(
@ -1801,7 +1837,7 @@ function constructTextInput(name, parameters, options) {
// Construct a "number" field
function constructNumberInput(name, parameters, options) {
function constructNumberInput(name, parameters) {
return constructInputOptions(
name,
@ -1813,7 +1849,7 @@ function constructNumberInput(name, parameters, options) {
// Construct a "choice" input
function constructChoiceInput(name, parameters, options) {
function constructChoiceInput(name, parameters) {
var html = `<select id='id_${name}' class='select form-control' name='${name}'>`;
@ -1846,7 +1882,7 @@ function constructChoiceInput(name, parameters, options) {
* be converted into a select2 input.
* This will then be served custom data from the API (as required)...
*/
function constructRelatedFieldInput(name, parameters, options) {
function constructRelatedFieldInput(name) {
var html = `<select id='id_${name}' class='select form-control' name='${name}'></select>`;
@ -1859,7 +1895,7 @@ function constructRelatedFieldInput(name, parameters, options) {
/*
* Construct a field for file upload
*/
function constructFileUploadInput(name, parameters, options) {
function constructFileUploadInput(name, parameters) {
var cls = 'clearablefileinput';
@ -1879,7 +1915,7 @@ function constructFileUploadInput(name, parameters, options) {
/*
* Construct a field for a date input
*/
function constructDateInput(name, parameters, options) {
function constructDateInput(name, parameters) {
return constructInputOptions(
name,
@ -1894,7 +1930,7 @@ function constructDateInput(name, parameters, options) {
* Construct a "candy" field input
* No actual field data!
*/
function constructCandyInput(name, parameters, options) {
function constructCandyInput(name, parameters) {
return parameters.html;
@ -1909,7 +1945,7 @@ function constructCandyInput(name, parameters, options) {
* - parameters: Field parameters returned by the OPTIONS method
*
*/
function constructHelpText(name, parameters, options) {
function constructHelpText(name, parameters) {
var style = '';
@ -1920,4 +1956,4 @@ function constructHelpText(name, parameters, options) {
var html = `<div id='hint_id_${name}' ${style}class='help-block'><i>${parameters.help_text}</i></div>`;
return html;
}
}

View File

@ -0,0 +1,207 @@
{% load i18n %}
/* exported
blankImage,
deleteButton,
editButton,
imageHoverIcon,
makeIconBadge,
makeIconButton,
makeProgressBar,
renderLink,
select2Thumbnail,
yesNoLabel,
*/
function yesNoLabel(value) {
if (value) {
return `<span class='label label-green'>{% trans "YES" %}</span>`;
} else {
return `<span class='label label-yellow'>{% trans "NO" %}</span>`;
}
}
function editButton(url, text='{% trans "Edit" %}') {
return `<button class='btn btn-success edit-button btn-sm' type='button' url='${url}'>${text}</button>`;
}
function deleteButton(url, text='{% trans "Delete" %}') {
return `<button class='btn btn-danger delete-button btn-sm' type='button' url='${url}'>${text}</button>`;
}
function blankImage() {
return `/static/img/blank_image.png`;
}
/* Render a small thumbnail icon for an image.
* On mouseover, display a full-size version of the image
*/
function imageHoverIcon(url) {
if (!url) {
url = blankImage();
}
var html = `
<a class='hover-icon'>
<img class='hover-img-thumb' src='${url}'>
<img class='hover-img-large' src='${url}'>
</a>
`;
return html;
}
/**
* Renders a simple thumbnail image
* @param {String} url is the image URL
* @returns html <img> tag
*/
function thumbnailImage(url) {
if (!url) {
url = '/static/img/blank_img.png';
}
// TODO: Support insertion of custom classes
var html = `<img class='hover-img-thumb' src='${url}'>`;
return html;
}
// Render a select2 thumbnail image
function select2Thumbnail(image) {
if (!image) {
image = blankImage();
}
return `<img src='${image}' class='select2-thumbnail'>`;
}
function makeIconBadge(icon, title) {
// Construct an 'icon badge' which floats to the right of an object
var html = `<span class='fas ${icon} label-right' title='${title}'></span>`;
return html;
}
function makeIconButton(icon, cls, pk, title, options={}) {
// Construct an 'icon button' using the fontawesome set
var classes = `btn btn-default btn-glyph ${cls}`;
var id = `${cls}-${pk}`;
var html = '';
var extraProps = '';
if (options.disabled) {
extraProps += `disabled='true' `;
}
html += `<button pk='${pk}' id='${id}' class='${classes}' title='${title}' ${extraProps}>`;
html += `<span class='fas ${icon}'></span>`;
html += `</button>`;
return html;
}
/*
* Render a progessbar!
*
* @param value is the current value of the progress bar
* @param maximum is the maximum value of the progress bar
*/
function makeProgressBar(value, maximum, opts={}) {
var options = opts || {};
value = parseFloat(value);
var percent = 100;
// Prevent div-by-zero or null value
if (maximum && maximum > 0) {
maximum = parseFloat(maximum);
percent = parseInt(value / maximum * 100);
}
if (percent > 100) {
percent = 100;
}
var extraclass = '';
if (value > maximum) {
extraclass='progress-bar-over';
} else if (value < maximum) {
extraclass = 'progress-bar-under';
}
var style = options.style || '';
var text = '';
if (style == 'percent') {
// Display e.g. "50%"
text = `${percent}%`;
} else if (style == 'max') {
// Display just the maximum value
text = `${maximum}`;
} else if (style == 'value') {
// Display just the current value
text = `${value}`;
} else if (style == 'blank') {
// No display!
text = '';
} else {
/* Default style
* Display e.g. "5 / 10"
*/
text = `${value} / ${maximum}`;
}
var id = options.id || 'progress-bar';
return `
<div id='${id}' class='progress'>
<div class='progress-bar ${extraclass}' role='progressbar' aria-valuenow='${percent}' aria-valuemin='0' aria-valuemax='100' style='width:${percent}%'></div>
<div class='progress-value'>${text}</div>
</div>
`;
}
function renderLink(text, url, options={}) {
if (url === null || url === undefined || url === '') {
return text;
}
var max_length = options.max_length || -1;
// Shorten the displayed length if required
if ((max_length > 0) && (text.length > max_length)) {
var slice_length = (max_length - 3) / 2;
var text_start = text.slice(0, slice_length);
var text_end = text.slice(-slice_length);
text = `${text_start}...${text_end}`;
}
return `<a href="${url}">${text}</a>`;
}

View File

@ -1,6 +1,25 @@
{% load i18n %}
function printStockItemLabels(items, options={}) {
/* globals
attachSelect,
closeModal,
inventreeGet,
makeOptionsList,
modalEnable,
modalSetContent,
modalSetTitle,
modalSubmit,
openModal,
showAlertDialog,
*/
/* exported
printPartLabels,
printStockItemLabels,
printStockLocationLabels,
*/
function printStockItemLabels(items) {
/**
* Print stock item labels for the given stock items
*/
@ -54,7 +73,7 @@ function printStockItemLabels(items, options={}) {
);
}
function printStockLocationLabels(locations, options={}) {
function printStockLocationLabels(locations) {
if (locations.length == 0) {
showAlertDialog(
@ -101,11 +120,11 @@ function printStockLocationLabels(locations, options={}) {
);
}
}
)
);
}
function printPartLabels(parts, options={}) {
function printPartLabels(parts) {
/**
* Print labels for the provided parts
*/

View File

@ -1,5 +1,22 @@
{% load i18n %}
/* globals
inventreeGet,
showAlertOrCache,
*/
/* exported
attachSecondaryModal,
clearField,
clearFieldOptions,
closeModal,
enableField,
getFieldValue,
reloadFieldOptions,
showModalImage,
removeRowFromModalForm,
showQuestionDialog,
*/
/*
* Create and display a new modal dialog
@ -77,7 +94,7 @@ function createNewModal(options={}) {
});
// Automatically remove the modal when it is deleted!
$(modal_name).on('hidden.bs.modal', function(e) {
$(modal_name).on('hidden.bs.modal', function() {
$(modal_name).remove();
});
@ -86,7 +103,7 @@ function createNewModal(options={}) {
if (event.keyCode == 13) {
event.preventDefault();
// Simulate a click on the 'Submit' button
$(modal_name).find("#modal-form-submit").click();
$(modal_name).find('#modal-form-submit').click();
return false;
}
@ -253,8 +270,8 @@ function reloadFieldOptions(fieldName, options) {
// Update the target field with the new options
setFieldOptions(fieldName, opts);
},
error: function(response) {
console.log("Error GETting field options");
error: function() {
console.log('Error GETting field options');
}
});
}
@ -273,7 +290,7 @@ function enableField(fieldName, enabled, options={}) {
var field = getFieldByName(modal, fieldName);
field.prop("disabled", !enabled);
field.prop('disabled', !enabled);
}
function clearField(fieldName, options={}) {
@ -344,7 +361,7 @@ function attachToggle(modal) {
* and also larger toggle style buttons are easier to press!
*/
$(modal).find("input[type='checkbox']").each(function(x) {
$(modal).find(`input[type='checkbox']`).each(function() {
$(this).bootstrapToggle({
size: 'small',
onstyle: 'success',
@ -359,7 +376,7 @@ function attachSelect(modal) {
* Provides search filtering for dropdown items
*/
$(modal + ' .select').select2({
$(modal + ' .select').select2({
dropdownParent: $(modal),
// dropdownAutoWidth parameter is required to work properly with modal forms
dropdownAutoWidth: false,
@ -377,7 +394,7 @@ function loadingMessageContent() {
*/
// TODO - This can be made a lot better
return "<span class='glyphicon glyphicon-refresh glyphicon-refresh-animate'></span> {% trans 'Waiting for server...' %}";
return `<span class='glyphicon glyphicon-refresh glyphicon-refresh-animate'></span> {% trans 'Waiting for server...' %}`;
}
@ -392,36 +409,36 @@ function afterForm(response, options) {
* - Reload the page
*/
// Should we show alerts immediately or cache them?
// Should we show alerts immediately or cache them?
var cache = (options.follow && response.url) ||
options.redirect ||
options.reload;
// Display any messages
if (response.success) {
showAlertOrCache("alert-success", response.success, cache);
showAlertOrCache('alert-success', response.success, cache);
}
if (response.info) {
showAlertOrCache("alert-info", response.info, cache);
showAlertOrCache('alert-info', response.info, cache);
}
if (response.warning) {
showAlertOrCache("alert-warning", response.warning, cache);
showAlertOrCache('alert-warning', response.warning, cache);
}
if (response.danger) {
showAlertOrCache("alert-danger", response.danger, cache);
showAlertOrCache('alert-danger', response.danger, cache);
}
// Was a callback provided?
if (options.success) {
options.success(response);
}
else if (options.follow && response.url) {
} else if (options.follow && response.url) {
window.location.href = response.url;
}
else if (options.redirect) {
} else if (options.redirect) {
window.location.href = options.redirect;
}
else if (options.reload) {
} else if (options.reload) {
location.reload();
}
}
@ -554,7 +571,7 @@ function renderErrorMessage(xhr) {
}
function showAlertDialog(title, content, options={}) {
function showAlertDialog(title, content) {
/* Display a modal dialog message box.
*
* title - Title text
@ -595,7 +612,7 @@ function showQuestionDialog(title, content, options={}) {
modalSetContent(modal, content);
$(modal).on('click', "#modal-form-submit", function() {
$(modal).on('click', '#modal-form-submit', function() {
$(modal).modal('hide');
if (options.accept) {
@ -636,7 +653,7 @@ function openModal(options) {
event.preventDefault();
// Simulate a click on the 'Submit' button
$(modal).find("#modal-form-submit").click();
$(modal).find('#modal-form-submit').click();
return false;
}
@ -698,17 +715,17 @@ function insertNewItemButton(modal, options) {
* Inserts a button at the end of this lael element.
*/
var html = "<span style='float: right;'>";
var html = `<span style='float: right;'>`;
html += "<div type='button' class='btn btn-primary btn-secondary'";
html += `<div type='button' class='btn btn-primary btn-secondary'`;
if (options.title) {
html += " title='" + options.title + "'";
html += ` title='${ options.title}'`;
}
html += " id='btn-new-" + options.field + "'>" + options.label + "</div>";
html += ` id='btn-new-${options.field}'>${options.label}</div>`;
html += "</span>";
html += '</span>';
$(modal).find('label[for="id_'+ options.field + '"]').append(html);
}
@ -733,7 +750,7 @@ function attachSecondaryModal(modal, options) {
var data = options.data || {};
// Add a callback to the button
$(modal).find("#btn-new-" + options.field).on('click', function() {
$(modal).find('#btn-new-' + options.field).on('click', function() {
// Launch the secondary modal
launchModalForm(
@ -762,24 +779,26 @@ function attachSecondaryModal(modal, options) {
}
// eslint-disable-next-line no-unused-vars
function attachSecondaries(modal, secondaries) {
/* Attach a provided list of secondary modals */
// 2021-07-18 - Secondary modals will be disabled for now, until they are re-implemented in the "API forms" architecture
return;
for (var i = 0; i < secondaries.length; i++) {
attachSecondaryModal(modal, secondaries[i]);
}
// for (var i = 0; i < secondaries.length; i++) {
// attachSecondaryModal(modal, secondaries[i]);
// }
}
function insertActionButton(modal, options) {
/* Insert a custom submition button */
/* Insert a custom submission button */
var html = "<span style='float: right;'>";
html += "<button name='" + options.name + "' type='submit' class='btn btn-default modal-form-button'";
html += " value='" + options.name + "'>" + options.title + "</button>";
html += "</span>";
var html = `
<span style='float: right;'>
<button name='${options.name}' type='submit' class='btn btn-default modal-form-button' value='${options.name}'>
${options.title}
</button>
</span>`;
$(modal).find('#modal-footer-buttons').append(html);
}
@ -802,8 +821,8 @@ function attachFieldCallback(modal, callback) {
* - action: A function to perform
*/
// Find the field input in the form
var field = getFieldByName(modal, callback.field);
// Find the field input in the form
var field = getFieldByName(modal, callback.field);
field.change(function() {
@ -838,8 +857,6 @@ function handleModalForm(url, options) {
var form = $(modal).find('.js-modal-form');
var _form = $(modal).find(".js-modal-form");
form.ajaxForm({
url: url,
dataType: 'json',
@ -860,7 +877,7 @@ function handleModalForm(url, options) {
modalEnable(modal, false);
},
// POST was successful
success: function(response, status, xhr, f) {
success: function(response) {
// Re-enable the modal
modalEnable(modal, true);
if ('form_valid' in response) {
@ -868,9 +885,8 @@ function handleModalForm(url, options) {
if (response.form_valid) {
$(modal).modal('hide');
afterForm(response, options);
}
// Form was returned, invalid!
else {
} else {
// Form was returned, invalid!
// Disable error message with option or response
if (!options.hideErrorMessage && !response.hideErrorMessage) {
@ -901,26 +917,24 @@ function handleModalForm(url, options) {
if (response.buttons) {
attachButtons(modal, response.buttons);
}
}
else {
} else {
$(modal).modal('hide');
showAlertDialog('{% trans "Invalid response from server" %}', '{% trans "Form data missing from server response" %}');
}
}
}
else {
} else {
$(modal).modal('hide');
afterForm(response, options);
}
},
error: function(xhr, ajaxOptions, thrownError) {
error: function(xhr) {
// There was an error submitting form data via POST
$(modal).modal('hide');
showAlertDialog('{% trans "Error posting form data" %}', renderErrorMessage(xhr));
},
complete: function(xhr) {
//TODO
complete: function() {
// TODO
}
});
});
@ -960,11 +974,11 @@ function launchModalForm(url, options = {}) {
$(modal).find('#modal-footer-buttons').html('');
// Form the ajax request to retrieve the django form data
ajax_data = {
var ajax_data = {
url: url,
type: 'get',
dataType: 'json',
beforeSend: function () {
beforeSend: function() {
openModal({
modal: modal,
submit_text: submit_text,
@ -1017,7 +1031,7 @@ function launchModalForm(url, options = {}) {
showAlertDialog('{% trans "Invalid server response" %}', '{% trans "JSON response missing form data" %}');
}
},
error: function (xhr, ajaxOptions, thrownError) {
error: function(xhr) {
$(modal).modal('hide');
@ -1056,8 +1070,8 @@ function launchModalForm(url, options = {}) {
showAlertDialog('{% trans "Error requesting form data" %}', renderErrorMessage(xhr));
}
console.log("Modal form error: " + xhr.status);
console.log("Message: " + xhr.responseText);
console.log('Modal form error: ' + xhr.status);
console.log('Message: ' + xhr.responseText);
}
};
@ -1106,4 +1120,4 @@ function showModalImage(image_url) {
modal.click(function() {
hideModalImage();
});
}
}

View File

@ -1,18 +1,19 @@
{% load i18n %}
/* globals
blankImage,
select2Thumbnail
*/
function blankImage() {
return `/static/img/blank_image.png`;
}
// Render a select2 thumbnail image
function select2Thumbnail(image) {
if (!image) {
image = blankImage();
}
return `<img src='${image}' class='select2-thumbnail'>`;
}
/* exported
renderBuild,
renderCompany,
renderManufacturerPart,
renderOwner,
renderPartCategory,
renderStockLocation,
renderSupplierPart,
*/
/*
@ -29,6 +30,7 @@ function select2Thumbnail(image) {
// Renderer for "Company" model
// eslint-disable-next-line no-unused-vars
function renderCompany(name, data, parameters, options) {
var html = select2Thumbnail(data.image);
@ -42,6 +44,7 @@ function renderCompany(name, data, parameters, options) {
// Renderer for "StockItem" model
// eslint-disable-next-line no-unused-vars
function renderStockItem(name, data, parameters, options) {
var image = data.part_detail.thumbnail || data.part_detail.image || blankImage();
@ -65,6 +68,7 @@ function renderStockItem(name, data, parameters, options) {
// Renderer for "StockLocation" model
// eslint-disable-next-line no-unused-vars
function renderStockLocation(name, data, parameters, options) {
var level = '- '.repeat(data.level);
@ -80,7 +84,7 @@ function renderStockLocation(name, data, parameters, options) {
return html;
}
// eslint-disable-next-line no-unused-vars
function renderBuild(name, data, parameters, options) {
var image = null;
@ -101,6 +105,7 @@ function renderBuild(name, data, parameters, options) {
// Renderer for "Part" model
// eslint-disable-next-line no-unused-vars
function renderPart(name, data, parameters, options) {
var html = select2Thumbnail(data.image);
@ -117,6 +122,7 @@ function renderPart(name, data, parameters, options) {
}
// Renderer for "User" model
// eslint-disable-next-line no-unused-vars
function renderUser(name, data, parameters, options) {
var html = `<span>${data.username}</span>`;
@ -130,19 +136,20 @@ function renderUser(name, data, parameters, options) {
// Renderer for "Owner" model
// eslint-disable-next-line no-unused-vars
function renderOwner(name, data, parameters, options) {
var html = `<span>${data.name}</span>`;
switch (data.label) {
case 'user':
html += `<span class='float-right fas fa-user'></span>`;
break;
case 'group':
html += `<span class='float-right fas fa-users'></span>`;
break;
default:
break;
case 'user':
html += `<span class='float-right fas fa-user'></span>`;
break;
case 'group':
html += `<span class='float-right fas fa-users'></span>`;
break;
default:
break;
}
return html;
@ -150,6 +157,7 @@ function renderOwner(name, data, parameters, options) {
// Renderer for "PartCategory" model
// eslint-disable-next-line no-unused-vars
function renderPartCategory(name, data, parameters, options) {
var level = '- '.repeat(data.level);
@ -165,7 +173,7 @@ function renderPartCategory(name, data, parameters, options) {
return html;
}
// eslint-disable-next-line no-unused-vars
function renderPartParameterTemplate(name, data, parameters, options) {
var html = `<span>${data.name} - [${data.units}]</span>`;
@ -175,6 +183,7 @@ function renderPartParameterTemplate(name, data, parameters, options) {
// Renderer for "ManufacturerPart" model
// eslint-disable-next-line no-unused-vars
function renderManufacturerPart(name, data, parameters, options) {
var manufacturer_image = null;
@ -203,6 +212,7 @@ function renderManufacturerPart(name, data, parameters, options) {
// Renderer for "SupplierPart" model
// eslint-disable-next-line no-unused-vars
function renderSupplierPart(name, data, parameters, options) {
var supplier_image = null;

View File

@ -1,6 +1,33 @@
{% load i18n %}
{% load inventree_extras %}
/* globals
companyFormFields,
constructForm,
createSupplierPart,
global_settings,
imageHoverIcon,
inventreeGet,
launchModalForm,
loadTableFilters,
makeIconBadge,
purchaseOrderStatusDisplay,
renderLink,
salesOrderStatusDisplay,
setupFilterList,
*/
/* exported
createSalesOrder,
editPurchaseOrderLineItem,
loadPurchaseOrderTable,
loadSalesOrderAllocationTable,
loadSalesOrderTable,
newPurchaseOrderFromOrderWizard,
newSupplierPartFromOrderWizard,
removeOrderRowFromOrderWizard,
removePurchaseOrderLineItem,
*/
// Create a new SalesOrder
function createSalesOrder(options={}) {
@ -15,7 +42,7 @@ function createSalesOrder(options={}) {
value: options.customer,
secondary: {
title: '{% trans "Add Customer" %}',
fields: function(data) {
fields: function() {
var fields = companyFormFields();
fields.is_customer.value = true;
@ -56,7 +83,7 @@ function createPurchaseOrder(options={}) {
value: options.supplier,
secondary: {
title: '{% trans "Add Supplier" %}',
fields: function(data) {
fields: function() {
var fields = companyFormFields();
fields.is_supplier.value = true;
@ -143,7 +170,7 @@ function newSupplierPartFromOrderWizard(e) {
if (response.supplier_detail) {
text += response.supplier_detail.name;
text += " | ";
text += ' | ';
}
text += response.SKU;
@ -153,8 +180,7 @@ function newSupplierPartFromOrderWizard(e) {
$('#modal-form').find(dropdown).append(option).trigger('change');
}
}
)
);
}
});
}
@ -203,7 +229,7 @@ function newPurchaseOrderFromOrderWizard(e) {
$('#modal-form').find(dropdown).append(option).trigger('change');
}
}
)
);
}
});
}
@ -248,7 +274,7 @@ function loadPurchaseOrderTable(table, options) {
options.params['supplier_detail'] = true;
var filters = loadTableFilters("purchaseorder");
var filters = loadTableFilters('purchaseorder');
for (var key in options.params) {
filters[key] = options.params[key];
@ -256,7 +282,7 @@ function loadPurchaseOrderTable(table, options) {
options.url = options.url || '{% url "api-po-list" %}';
setupFilterList("purchaseorder", $(table));
setupFilterList('purchaseorder', $(table));
$(table).inventreeTable({
url: options.url,
@ -265,7 +291,9 @@ function loadPurchaseOrderTable(table, options) {
groupBy: false,
sidePagination: 'server',
original: options.params,
formatNoMatches: function() { return '{% trans "No purchase orders found" %}'; },
formatNoMatches: function() {
return '{% trans "No purchase orders found" %}';
},
columns: [
{
title: '',
@ -278,7 +306,7 @@ function loadPurchaseOrderTable(table, options) {
title: '{% trans "Purchase Order" %}',
sortable: true,
switchable: false,
formatter: function(value, row, index, field) {
formatter: function(value, row) {
var prefix = global_settings.PURCHASEORDER_REFERENCE_PREFIX;
@ -300,7 +328,7 @@ function loadPurchaseOrderTable(table, options) {
title: '{% trans "Supplier" %}',
sortable: true,
sortName: 'supplier__name',
formatter: function(value, row, index, field) {
formatter: function(value, row) {
return imageHoverIcon(row.supplier_detail.image) + renderLink(row.supplier_detail.name, `/company/${row.supplier}/purchase-orders/`);
}
},
@ -316,7 +344,7 @@ function loadPurchaseOrderTable(table, options) {
field: 'status',
title: '{% trans "Status" %}',
sortable: true,
formatter: function(value, row, index, field) {
formatter: function(value, row) {
return purchaseOrderStatusDisplay(row.status, row.status_text);
}
},
@ -344,7 +372,7 @@ function loadSalesOrderTable(table, options) {
options.params = options.params || {};
options.params['customer_detail'] = true;
var filters = loadTableFilters("salesorder");
var filters = loadTableFilters('salesorder');
for (var key in options.params) {
filters[key] = options.params[key];
@ -352,7 +380,7 @@ function loadSalesOrderTable(table, options) {
options.url = options.url || '{% url "api-so-list" %}';
setupFilterList("salesorder", $(table));
setupFilterList('salesorder', $(table));
$(table).inventreeTable({
url: options.url,
@ -361,7 +389,9 @@ function loadSalesOrderTable(table, options) {
groupBy: false,
sidePagination: 'server',
original: options.params,
formatNoMatches: function() { return '{% trans "No sales orders found" %}'; },
formatNoMatches: function() {
return '{% trans "No sales orders found" %}';
},
columns: [
{
title: '',
@ -373,7 +403,7 @@ function loadSalesOrderTable(table, options) {
sortable: true,
field: 'reference',
title: '{% trans "Sales Order" %}',
formatter: function(value, row, index, field) {
formatter: function(value, row) {
var prefix = global_settings.SALESORDER_REFERENCE_PREFIX;
@ -395,7 +425,7 @@ function loadSalesOrderTable(table, options) {
sortName: 'customer__name',
field: 'customer_detail',
title: '{% trans "Customer" %}',
formatter: function(value, row, index, field) {
formatter: function(value, row) {
if (!row.customer_detail) {
return '{% trans "Invalid Customer" %}';
@ -418,7 +448,7 @@ function loadSalesOrderTable(table, options) {
sortable: true,
field: 'status',
title: '{% trans "Status" %}',
formatter: function(value, row, index, field) {
formatter: function(value, row) {
return salesOrderStatusDisplay(row.status, row.status_text);
}
},
@ -459,13 +489,13 @@ function loadSalesOrderAllocationTable(table, options={}) {
options.params['item_detail'] = true;
options.params['order_detail'] = true;
var filters = loadTableFilters("salesorderallocation");
var filters = loadTableFilters('salesorderallocation');
for (var key in options.params) {
filters[key] = options.params[key];
}
setupFilterList("salesorderallocation", $(table));
setupFilterList('salesorderallocation', $(table));
$(table).inventreeTable({
url: '{% url "api-so-allocation-list" %}',
@ -475,7 +505,9 @@ function loadSalesOrderAllocationTable(table, options={}) {
search: false,
paginationVAlign: 'bottom',
original: options.params,
formatNoMatches: function() { return '{% trans "No sales order allocations found" %}'; },
formatNoMatches: function() {
return '{% trans "No sales order allocations found" %}';
},
columns: [
{
field: 'pk',
@ -486,7 +518,6 @@ function loadSalesOrderAllocationTable(table, options={}) {
field: 'order',
switchable: false,
title: '{% trans "Order" %}',
switchable: false,
formatter: function(value, row) {
var prefix = global_settings.SALESORDER_REFERENCE_PREFIX;
@ -530,4 +561,4 @@ function loadSalesOrderAllocationTable(table, options={}) {
}
]
});
}
}

View File

@ -1,20 +1,48 @@
{% load i18n %}
{% load inventree_extras %}
/* globals
Chart,
constructForm,
global_settings,
imageHoverIcon,
inventreeGet,
inventreePut,
launchModalForm,
linkButtonsToSelection,
loadTableFilters,
makeIconBadge,
makeIconButton,
printPartLabels,
renderLink,
setFormGroupVisibility,
setupFilterList,
yesNoLabel,
*/
/* exported
duplicatePart,
editCategory,
editPart,
initPriceBreakSet,
loadBomChart,
loadParametricPartTable,
loadPartCategoryTable,
loadPartParameterTable,
loadPartTable,
loadPartTestTemplateTable,
loadPartVariantTable,
loadSellPricingChart,
loadSimplePartTable,
loadStockPricingChart,
toggleStar,
*/
/* Part API functions
* Requires api.js to be loaded first
*/
function yesNoLabel(value) {
if (value) {
return `<span class='label label-green'>{% trans "YES" %}</span>`;
} else {
return `<span class='label label-yellow'>{% trans "NO" %}</span>`;
}
}
function partGroups(options={}) {
function partGroups() {
return {
attributes: {
@ -34,10 +62,10 @@ function partGroups(options={}) {
collapsible: true,
hidden: !global_settings.PART_PURCHASEABLE,
}
}
};
}
// Construct fieldset for part forms
function partFields(options={}) {
@ -45,7 +73,7 @@ function partFields(options={}) {
category: {
secondary: {
title: '{% trans "Add Part Category" %}',
fields: function(data) {
fields: function() {
var fields = categoryFields();
return fields;
@ -115,14 +143,14 @@ function partFields(options={}) {
// Pop expiry field
if (!global_settings.STOCK_ENABLE_EXPIRY) {
delete fields["default_expiry"];
delete fields['default_expiry'];
}
// Additional fields when "creating" a new part
if (options.create) {
// No supplier parts available yet
delete fields["default_supplier"];
delete fields['default_supplier'];
if (global_settings.PART_CREATE_INITIAL) {
@ -264,7 +292,7 @@ function categoryFields() {
// Edit a PartCategory via the API
function editCategory(pk, options={}) {
function editCategory(pk) {
var url = `/api/part/category/${pk}/`;
@ -279,7 +307,7 @@ function editCategory(pk, options={}) {
}
function editPart(pk, options={}) {
function editPart(pk) {
var url = `/api/part/${pk}/`;
@ -291,7 +319,7 @@ function editPart(pk, options={}) {
constructForm(url, {
fields: fields,
groups: partGroups(),
groups: groups,
title: '{% trans "Edit Part" %}',
reload: true,
});
@ -370,7 +398,7 @@ function toggleStar(options) {
}
function makePartIcons(part, options={}) {
function makePartIcons(part) {
/* Render a set of icons for the given part.
*/
@ -397,7 +425,7 @@ function makePartIcons(part, options={}) {
}
if (part.salable) {
html += makeIconBadge('fa-dollar-sign', title='{% trans "Salable part" %}');
html += makeIconBadge('fa-dollar-sign', '{% trans "Salable part" %}');
}
if (!part.active) {
@ -418,13 +446,13 @@ function loadPartVariantTable(table, partId, options={}) {
params.ancestor = partId;
// Load filters
var filters = loadTableFilters("variants");
var filters = loadTableFilters('variants');
for (var key in params) {
filters[key] = params[key];
}
setupFilterList("variants", $(table));
setupFilterList('variants', $(table));
var cols = [
{
@ -437,7 +465,7 @@ function loadPartVariantTable(table, partId, options={}) {
field: 'name',
title: '{% trans "Name" %}',
switchable: false,
formatter: function(value, row, index, field) {
formatter: function(value, row) {
var html = '';
var name = '';
@ -506,12 +534,14 @@ function loadPartVariantTable(table, partId, options={}) {
];
table.inventreeTable({
url: "{% url 'api-part-list' %}",
url: '{% url "api-part-list" %}',
name: 'partvariants',
showColumns: true,
original: params,
queryParams: filters,
formatNoMatches: function() { return '{% trans "No variants found" %}'; },
formatNoMatches: function() {
return '{% trans "No variants found" %}';
},
columns: cols,
treeEnable: true,
rootParentId: partId,
@ -545,7 +575,7 @@ function loadPartParameterTable(table, url, options) {
var params = options.params || {};
// Load filters
var filters = loadTableFilters("part-parameters");
var filters = loadTableFilters('part-parameters');
for (var key in params) {
filters[key] = params[key];
@ -559,7 +589,9 @@ function loadPartParameterTable(table, url, options) {
queryParams: filters,
name: 'partparameters',
groupBy: false,
formatNoMatches: function() { return '{% trans "No parameters found" %}'; },
formatNoMatches: function() {
return '{% trans "No parameters found" %}';
},
columns: [
{
checkbox: true,
@ -650,19 +682,19 @@ function loadParametricPartTable(table, options={}) {
* - table_data: Parameters data
*/
var table_headers = options.headers
var table_data = options.data
var table_headers = options.headers;
var table_data = options.data;
var columns = [];
for (header of table_headers) {
for (var header of table_headers) {
if (header === 'part') {
columns.push({
field: header,
title: '{% trans "Part" %}',
sortable: true,
sortName: 'name',
formatter: function(value, row, index, field) {
formatter: function(value, row) {
var name = '';
@ -687,8 +719,6 @@ function loadParametricPartTable(table, options={}) {
title: header,
sortable: true,
filterControl: 'input',
/* TODO: Search icons are not displayed */
/*clear: 'fa-times icon-red',*/
});
}
}
@ -698,7 +728,9 @@ function loadParametricPartTable(table, options={}) {
queryParams: table_headers,
groupBy: false,
name: options.name || 'parametric',
formatNoMatches: function() { return '{% trans "No parts found" %}'; },
formatNoMatches: function() {
return '{% trans "No parts found" %}';
},
columns: columns,
showColumns: true,
data: table_data,
@ -785,15 +817,17 @@ function loadPartTable(table, url, options={}) {
var filters = {};
var col = null;
if (!options.disableFilters) {
filters = loadTableFilters("parts");
filters = loadTableFilters('parts');
}
for (var key in params) {
filters[key] = params[key];
}
setupFilterList("parts", $(table), options.filterTarget || null);
setupFilterList('parts', $(table), options.filterTarget || null);
var columns = [
{
@ -818,16 +852,18 @@ function loadPartTable(table, url, options={}) {
field: 'IPN',
title: 'IPN',
};
if (!options.params.ordering) {
col['sortable'] = true;
};
}
columns.push(col);
col = {
field: 'name',
title: '{% trans "Part" %}',
switchable: false,
formatter: function(value, row, index, field) {
formatter: function(value, row) {
var name = '';
@ -854,18 +890,20 @@ function loadPartTable(table, url, options={}) {
return display;
}
};
if (!options.params.ordering) {
col['sortable'] = true;
};
}
columns.push(col);
columns.push({
field: 'description',
title: '{% trans "Description" %}',
formatter: function(value, row, index, field) {
formatter: function(value, row) {
if (row.is_template) {
value = '<i>' + value + '</i>';
value = `<i>${value}</i>`;
}
return value;
@ -876,60 +914,63 @@ function loadPartTable(table, url, options={}) {
sortName: 'category',
field: 'category_detail',
title: '{% trans "Category" %}',
formatter: function(value, row, index, field) {
formatter: function(value, row) {
if (row.category) {
return renderLink(value.pathstring, "/part/category/" + row.category + "/");
}
else {
return renderLink(value.pathstring, `/part/category/${row.category}/`);
} else {
return '{% trans "No category" %}';
}
}
};
if (!options.params.ordering) {
col['sortable'] = true;
};
}
columns.push(col);
col = {
field: 'in_stock',
title: '{% trans "Stock" %}',
searchable: false,
formatter: function(value, row, index, field) {
var link = "stock";
formatter: function(value, row) {
var link = 'stock';
if (value) {
// There IS stock available for this part
// Is stock "low" (below the 'minimum_stock' quantity)?
if (row.minimum_stock && row.minimum_stock > value) {
value += "<span class='label label-right label-warning'>{% trans "Low stock" %}</span>";
value += `<span class='label label-right label-warning'>{% trans "Low stock" %}</span>`;
}
} else if (row.on_order) {
// There is no stock available, but stock is on order
value = "0<span class='label label-right label-primary'>{% trans "On Order" %}: " + row.on_order + "</span>";
link = "orders";
value = `0<span class='label label-right label-primary'>{% trans "On Order" %}: ${row.on_order}</span>`;
link = 'orders';
} else if (row.building) {
// There is no stock available, but stock is being built
value = "0<span class='label label-right label-info'>{% trans "Building" %}: " + row.building + "</span>";
link = "builds";
value = `0<span class='label label-right label-info'>{% trans "Building" %}: ${row.building}</span>`;
link = 'builds';
} else {
// There is no stock available
value = "0<span class='label label-right label-danger'>{% trans "No Stock" %}</span>";
value = `0<span class='label label-right label-danger'>{% trans "No Stock" %}</span>`;
}
return renderLink(value, '/part/' + row.pk + "/" + link + "/");
return renderLink(value, `/part/${row.pk}/${link}/`);
}
};
if (!options.params.ordering) {
col['sortable'] = true;
};
}
columns.push(col);
columns.push({
field: 'link',
title: '{% trans "Link" %}',
formatter: function(value, row, index, field) {
formatter: function(value) {
return renderLink(
value, value,
{
@ -949,7 +990,9 @@ function loadPartTable(table, url, options={}) {
original: params,
sidePagination: 'server',
pagination: 'true',
formatNoMatches: function() { return '{% trans "No parts found" %}'; },
formatNoMatches: function() {
return '{% trans "No parts found" %}';
},
columns: columns,
showColumns: true,
showCustomView: false,
@ -982,8 +1025,8 @@ function loadPartTable(table, url, options={}) {
/* Button callbacks for part table buttons */
$("#multi-part-order").click(function() {
var selections = $(table).bootstrapTable("getSelections");
$('#multi-part-order').click(function() {
var selections = $(table).bootstrapTable('getSelections');
var parts = [];
@ -991,15 +1034,15 @@ function loadPartTable(table, url, options={}) {
parts.push(item.pk);
});
launchModalForm("/order/purchase-order/order-parts/", {
launchModalForm('/order/purchase-order/order-parts/', {
data: {
parts: parts,
},
});
});
$("#multi-part-category").click(function() {
var selections = $(table).bootstrapTable("getSelections");
$('#multi-part-category').click(function() {
var selections = $(table).bootstrapTable('getSelections');
var parts = [];
@ -1007,7 +1050,7 @@ function loadPartTable(table, url, options={}) {
parts.push(item.pk);
});
launchModalForm("/part/set-category/", {
launchModalForm('/part/set-category/', {
data: {
parts: parts,
},
@ -1028,7 +1071,7 @@ function loadPartTable(table, url, options={}) {
});
$('#multi-part-export').click(function() {
var selections = $(table).bootstrapTable("getSelections");
var selections = $(table).bootstrapTable('getSelections');
var parts = '';
@ -1127,15 +1170,15 @@ function loadPartTestTemplateTable(table, options) {
var filterListElement = options.filterList || '#filter-list-parttests';
var filters = loadTableFilters("parttests");
var filters = loadTableFilters('parttests');
var original = {};
for (var key in params) {
original[key] = params[key];
for (var k in params) {
original[k] = params[k];
}
setupFilterList("parttests", table, filterListElement);
setupFilterList('parttests', table, filterListElement);
// Override the default values, or add new ones
for (var key in params) {
@ -1147,7 +1190,7 @@ function loadPartTestTemplateTable(table, options) {
formatNoMatches: function() {
return '{% trans "No test templates matching query" %}';
},
url: "{% url 'api-part-test-template-list' %}",
url: '{% url "api-part-test-template-list" %}',
queryParams: filters,
name: 'testtemplate',
original: original,
@ -1168,7 +1211,7 @@ function loadPartTestTemplateTable(table, options) {
},
{
field: 'required',
title: "{% trans 'Required' %}",
title: '{% trans "Required" %}',
sortable: true,
formatter: function(value) {
return yesNoLabel(value);
@ -1235,28 +1278,29 @@ function loadPriceBreakTable(table, options) {
onLoadSuccess: function(tableData) {
if (linkedGraph) {
// sort array
tableData = tableData.sort((a,b)=>a.quantity-b.quantity);
tableData = tableData.sort((a, b) => (a.quantity - b.quantity));
// split up for graph definition
var graphLabels = Array.from(tableData, x => x.quantity);
var graphData = Array.from(tableData, x => x.price);
var graphLabels = Array.from(tableData, (x) => (x.quantity));
var graphData = Array.from(tableData, (x) => (x.price));
// destroy chart if exists
if (chart){
if (chart) {
chart.destroy();
}
chart = loadLineChart(linkedGraph,
{
labels: graphLabels,
datasets: [
{
label: '{% trans "Unit Price" %}',
data: graphData,
backgroundColor: 'rgba(255, 206, 86, 0.2)',
borderColor: 'rgb(255, 206, 86)',
stepped: true,
fill: true,
},]
datasets: [
{
label: '{% trans "Unit Price" %}',
data: graphData,
backgroundColor: 'rgba(255, 206, 86, 0.2)',
borderColor: 'rgb(255, 206, 86)',
stepped: true,
fill: true,
},
],
}
);
}
@ -1277,10 +1321,10 @@ function loadPriceBreakTable(table, options) {
field: 'price',
title: '{% trans "Price" %}',
sortable: true,
formatter: function(value, row, index) {
formatter: function(value, row) {
var html = value;
html += `<div class='btn-group float-right' role='group'>`
html += `<div class='btn-group float-right' role='group'>`;
html += makeIconButton('fa-edit icon-blue', `button-${name}-edit`, row.pk, `{% trans "Edit ${human_name}" %}`);
html += makeIconButton('fa-trash-alt icon-red', `button-${name}-delete`, row.pk, `{% trans "Delete ${human_name}" %}`);
@ -1330,8 +1374,8 @@ function initPriceBreakSet(table, options) {
}
);
function reloadPriceBreakTable(){
table.bootstrapTable("refresh");
function reloadPriceBreakTable() {
table.bootstrapTable('refresh');
}
pb_new_btn.click(function() {
@ -1419,12 +1463,26 @@ function loadBomChart(context, data) {
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {legend: {position: 'bottom'},
scales: {xAxes: [{beginAtZero: true, ticks: {autoSkip: false}}]}}
plugins: {
legend: {
position: 'bottom',
},
scales: {
xAxes: [
{
beginAtZero: true,
ticks: {
autoSkip: false,
}
}
]
}
}
}
});
}
function loadSellPricingChart(context, data) {
return new Chart(context, {
type: 'line',
@ -1432,21 +1490,29 @@ function loadSellPricingChart(context, data) {
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {legend: {position: 'bottom'}},
plugins: {
legend: {
position: 'bottom'
}
},
scales: {
y: {
type: 'linear',
position: 'left',
grid: {display: false},
grid: {
display: false
},
title: {
display: true,
text: '{% trans "Unit Price" %}'
text: '{% trans "Unit Price" %}',
}
},
y1: {
type: 'linear',
position: 'right',
grid: {display: false},
grid: {
display: false
},
titel: {
display: true,
text: '{% trans "Quantity" %}',

View File

@ -1,5 +1,25 @@
{% load i18n %}
/* globals
attachSelect,
closeModal,
inventreeGet,
openModal,
makeOptionsList,
modalEnable,
modalSetContent,
modalSetTitle,
modalSubmit,
showAlertDialog,
*/
/* exported
printBomReports,
printBuildReports,
printPurchaseOrderReports,
printSalesOrderReports,
printTestReports,
*/
function selectReport(reports, items, options={}) {
/**
@ -88,7 +108,7 @@ function selectReport(reports, items, options={}) {
}
function printTestReports(items, options={}) {
function printTestReports(items) {
/**
* Print test reports for the provided stock item(s)
*/
@ -142,7 +162,7 @@ function printTestReports(items, options={}) {
}
function printBuildReports(builds, options={}) {
function printBuildReports(builds) {
/**
* Print Build report for the provided build(s)
*/
@ -188,14 +208,14 @@ function printBuildReports(builds, options={}) {
window.location.href = href;
}
}
)
);
}
}
)
);
}
function printBomReports(parts, options={}) {
function printBomReports(parts) {
/**
* Print BOM reports for the provided part(s)
*/
@ -245,11 +265,11 @@ function printBomReports(parts, options={}) {
);
}
}
)
);
}
function printPurchaseOrderReports(orders, options={}) {
function printPurchaseOrderReports(orders) {
/**
* Print PO reports for the provided purchase order(s)
*/
@ -296,14 +316,14 @@ function printPurchaseOrderReports(orders, options={}) {
window.location.href = href;
}
}
)
);
}
}
)
);
}
function printSalesOrderReports(orders, options={}) {
function printSalesOrderReports(orders) {
/**
* Print SO reports for the provided purchase order(s)
*/
@ -350,8 +370,8 @@ function printSalesOrderReports(orders, options={}) {
window.location.href = href;
}
}
)
);
}
}
)
);
}

View File

@ -2,6 +2,63 @@
{% load inventree_extras %}
{% load status_codes %}
/* globals
attachSelect,
attachToggle,
blankImage,
enableField,
clearField,
clearFieldOptions,
closeModal,
constructFormBody,
constructNumberInput,
createNewModal,
getFormFieldValue,
global_settings,
handleFormErrors,
imageHoverIcon,
inventreeDelete,
inventreeGet,
inventreePut,
launchModalForm,
linkButtonsToSelection,
loadTableFilters,
makeIconBadge,
makeIconButton,
makeOptionsList,
makePartIcons,
modalEnable,
modalSetContent,
modalSetTitle,
modalSubmit,
moment,
openModal,
printStockItemLabels,
printTestReports,
renderLink,
reloadFieldOptions,
scanItemsIntoLocation,
showAlertDialog,
setFieldValue,
setupFilterList,
showApiError,
stockStatusDisplay,
*/
/* exported
createNewStockItem,
exportStock,
loadInstalledInTable,
loadStockLocationTable,
loadStockTable,
loadStockTestResultsTable,
loadStockTrackingTable,
loadTableFilters,
locationFields,
removeStockRow,
stockStatusCodes,
*/
function locationFields() {
return {
@ -23,7 +80,7 @@ function stockStatusCodes() {
{% for code in StockStatus.list %}
{
key: {{ code.key }},
text: "{{ code.value }}",
text: '{{ code.value }}',
},
{% endfor %}
];
@ -45,11 +102,23 @@ function exportStock(params={}) {
type: 'choice',
value: 'csv',
choices: [
{ value: 'csv', display_name: 'CSV' },
{ value: 'tsv', display_name: 'TSV' },
{ value: 'xls', display_name: 'XLS' },
{ value: 'xlsx', display_name: 'XLSX' },
]
{
value: 'csv',
display_name: 'CSV',
},
{
value: 'tsv',
display_name: 'TSV',
},
{
value: 'xls',
display_name: 'XLS',
},
{
value: 'xlsx',
display_name: 'XLSX',
},
],
},
sublocations: {
label: '{% trans "Include Sublocations" %}',
@ -94,34 +163,34 @@ function adjustStock(action, items, options={}) {
var allowSerializedStock = false;
switch (action) {
case 'move':
formTitle = '{% trans "Transfer Stock" %}';
actionTitle = '{% trans "Move" %}';
specifyLocation = true;
allowSerializedStock = true;
url = '{% url "api-stock-transfer" %}';
break;
case 'count':
formTitle = '{% trans "Count Stock" %}';
actionTitle = '{% trans "Count" %}';
url = '{% url "api-stock-count" %}';
break;
case 'take':
formTitle = '{% trans "Remove Stock" %}';
actionTitle = '{% trans "Take" %}';
url = '{% url "api-stock-remove" %}';
break;
case 'add':
formTitle = '{% trans "Add Stock" %}';
actionTitle = '{% trans "Add" %}';
url = '{% url "api-stock-add" %}';
break;
case 'delete':
formTitle = '{% trans "Delete Stock" %}';
allowSerializedStock = true;
break;
default:
break;
case 'move':
formTitle = '{% trans "Transfer Stock" %}';
actionTitle = '{% trans "Move" %}';
specifyLocation = true;
allowSerializedStock = true;
url = '{% url "api-stock-transfer" %}';
break;
case 'count':
formTitle = '{% trans "Count Stock" %}';
actionTitle = '{% trans "Count" %}';
url = '{% url "api-stock-count" %}';
break;
case 'take':
formTitle = '{% trans "Remove Stock" %}';
actionTitle = '{% trans "Take" %}';
url = '{% url "api-stock-remove" %}';
break;
case 'add':
formTitle = '{% trans "Add Stock" %}';
actionTitle = '{% trans "Add" %}';
url = '{% url "api-stock-add" %}';
break;
case 'delete':
formTitle = '{% trans "Delete Stock" %}';
allowSerializedStock = true;
break;
default:
break;
}
// Generate modal HTML content
@ -157,25 +226,25 @@ function adjustStock(action, items, options={}) {
var value = null;
switch (action) {
case 'move':
minValue = 0;
maxValue = item.quantity;
value = item.quantity;
break;
case 'add':
minValue = 0;
value = 0;
break;
case 'take':
minValue = 0;
value = 0;
break;
case 'count':
minValue = 0;
value = item.quantity;
break;
default:
break;
case 'move':
minValue = 0;
maxValue = item.quantity;
value = item.quantity;
break;
case 'add':
minValue = 0;
value = 0;
break;
case 'take':
minValue = 0;
value = 0;
break;
case 'count':
minValue = 0;
value = item.quantity;
break;
default:
break;
}
var image = item.part_detail.thumbnail || item.part_detail.image || blankImage();
@ -208,8 +277,8 @@ function adjustStock(action, items, options={}) {
read_only: readonly,
title: readonly ? '{% trans "Quantity cannot be adjusted for serialized stock" %}' : '{% trans "Specify stock quantity" %}',
}
)
};
);
}
var buttons = `<div class='btn-group float-right' role='group'>`;
@ -283,7 +352,7 @@ function adjustStock(action, items, options={}) {
confirm: true,
confirmMessage: '{% trans "Confirm stock adjustment" %}',
modal: modal,
onSubmit: function(fields, opts) {
onSubmit: function(fields) {
// "Delete" action gets handled differently
if (action == 'delete') {
@ -295,7 +364,7 @@ function adjustStock(action, items, options={}) {
inventreeDelete(
`/api/stock/${item.pk}/`,
)
)
);
});
// Wait for *all* the requests to complete
@ -327,7 +396,7 @@ function adjustStock(action, items, options={}) {
});
// Add in extra field data
for (field_name in extraFields) {
for (var field_name in extraFields) {
data[field_name] = getFormFieldValue(
field_name,
fields[field_name],
@ -342,7 +411,7 @@ function adjustStock(action, items, options={}) {
data,
{
method: 'POST',
success: function(response, status) {
success: function() {
// Destroy the modal window
$(modal).modal('hide');
@ -353,22 +422,22 @@ function adjustStock(action, items, options={}) {
},
error: function(xhr) {
switch (xhr.status) {
case 400:
case 400:
// Handle errors for standard fields
handleFormErrors(
xhr.responseJSON,
extraFields,
{
modal: modal,
}
)
// Handle errors for standard fields
handleFormErrors(
xhr.responseJSON,
extraFields,
{
modal: modal,
}
);
break;
default:
$(modal).modal('hide');
showApiError(xhr);
break;
break;
default:
$(modal).modal('hide');
showApiError(xhr);
break;
}
}
}
@ -446,15 +515,15 @@ function loadStockTestResultsTable(table, options) {
html += makeIconButton('fa-trash-alt icon-red', 'button-test-delete', pk, '{% trans "Delete test result" %}');
}
html += "</div>";
html += '</div>';
return html;
}
var parent_node = "parent node";
var parent_node = 'parent node';
table.inventreeTable({
url: "{% url 'api-part-test-template-list' %}",
url: '{% url "api-part-test-template-list" %}',
method: 'get',
name: 'testresult',
treeEnable: true,
@ -473,7 +542,7 @@ function loadStockTestResultsTable(table, options) {
table.treegrid({
treeColumn: 0,
});
table.treegrid("collapseAll");
table.treegrid('collapseAll');
},
columns: [
{
@ -539,12 +608,12 @@ function loadStockTestResultsTable(table, options) {
stock_item: options.stock_item,
user_detail: true,
attachment_detail: true,
ordering: "-date",
ordering: '-date',
},
{
success: function(data) {
// Iterate through the returned test data
data.forEach(function(item, index) {
data.forEach(function(item) {
var match = false;
var override = false;
@ -589,13 +658,12 @@ function loadStockTestResultsTable(table, options) {
});
// Push data back into the table
table.bootstrapTable("load", tableData);
table.bootstrapTable('load', tableData);
}
}
)
);
}
});
}
@ -671,11 +739,11 @@ function loadStockTable(table, options) {
var params = options.params || {};
var filterListElement = options.filterList || "#filter-list-stock";
var filterListElement = options.filterList || '#filter-list-stock';
var filters = {};
var filterKey = options.filterKey || options.name || "stock";
var filterKey = options.filterKey || options.name || 'stock';
if (!options.disableFilters) {
filters = loadTableFilters(filterKey);
@ -683,8 +751,8 @@ function loadStockTable(table, options) {
var original = {};
for (var key in params) {
original[key] = params[key];
for (var k in params) {
original[k] = params[k];
}
setupFilterList(filterKey, table, filterListElement);
@ -700,10 +768,13 @@ function loadStockTable(table, options) {
grouping = options.grouping;
}
var col = null;
// Explicitly disable part grouping functionality
// Might be able to add this in later on,
// but there is a bug which makes this crash if paginating on the server side.
// Ref: https://github.com/wenzhixin/bootstrap-table/issues/3250
// eslint-disable-next-line no-unused-vars
grouping = false;
var columns = [
@ -727,22 +798,24 @@ function loadStockTable(table, options) {
sortName: 'part__name',
visible: params['part_detail'],
switchable: params['part_detail'],
formatter: function(value, row, index, field) {
formatter: function(value, row) {
var url = `/stock/item/${row.pk}/`;
var thumb = row.part_detail.thumbnail;
var name = row.part_detail.full_name;
html = imageHoverIcon(thumb) + renderLink(name, url);
var html = imageHoverIcon(thumb) + renderLink(name, url);
html += makePartIcons(row.part_detail);
return html;
}
};
if (!options.params.ordering) {
col['sortable'] = true;
};
}
columns.push(col);
col = {
@ -751,13 +824,15 @@ function loadStockTable(table, options) {
sortName: 'part__IPN',
visible: params['part_detail'],
switchable: params['part_detail'],
formatter: function(value, row, index, field) {
formatter: function(value, row) {
return row.part_detail.IPN;
},
};
if (!options.params.ordering) {
col['sortable'] = true;
};
}
columns.push(col);
columns.push({
@ -765,7 +840,7 @@ function loadStockTable(table, options) {
title: '{% trans "Description" %}',
visible: params['part_detail'],
switchable: params['part_detail'],
formatter: function(value, row, index, field) {
formatter: function(value, row) {
return row.part_detail.description;
}
});
@ -773,7 +848,7 @@ function loadStockTable(table, options) {
col = {
field: 'quantity',
title: '{% trans "Stock" %}',
formatter: function(value, row, index, field) {
formatter: function(value, row) {
var val = parseFloat(value);
@ -817,13 +892,9 @@ function loadStockTable(table, options) {
// REJECTED
if (row.status == {{ StockStatus.REJECTED }}) {
html += makeIconBadge('fa-times-circle icon-red', '{% trans "Stock item has been rejected" %}');
}
// LOST
else if (row.status == {{ StockStatus.LOST }}) {
html += makeIconBadge('fa-question-circle','{% trans "Stock item is lost" %}');
}
else if (row.status == {{ StockStatus.DESTROYED }}) {
} else if (row.status == {{ StockStatus.LOST }}) {
html += makeIconBadge('fa-question-circle', '{% trans "Stock item is lost" %}');
} else if (row.status == {{ StockStatus.DESTROYED }}) {
html += makeIconBadge('fa-skull-crossbones', '{% trans "Stock item is destroyed" %}');
}
@ -834,51 +905,61 @@ function loadStockTable(table, options) {
return html;
}
};
if (!options.params.ordering) {
col['sortable'] = true;
};
}
columns.push(col);
col = {
field: 'status',
title: '{% trans "Status" %}',
formatter: function(value, row, index, field) {
formatter: function(value) {
return stockStatusDisplay(value);
},
};
if (!options.params.ordering) {
col['sortable'] = true;
};
}
columns.push(col);
col = {
field: 'batch',
title: '{% trans "Batch" %}',
};
if (!options.params.ordering) {
col['sortable'] = true;
};
}
columns.push(col);
col = {
field: 'location_detail.pathstring',
title: '{% trans "Location" %}',
formatter: function(value, row, index, field) {
formatter: function(value, row) {
return locationDetail(row);
}
};
if (!options.params.ordering) {
col['sortable'] = true;
};
}
columns.push(col);
col = {
field: 'stocktake_date',
title: '{% trans "Stocktake" %}',
};
if (!options.params.ordering) {
col['sortable'] = true;
};
}
columns.push(col);
col = {
@ -887,18 +968,22 @@ function loadStockTable(table, options) {
visible: global_settings.STOCK_ENABLE_EXPIRY,
switchable: global_settings.STOCK_ENABLE_EXPIRY,
};
if (!options.params.ordering) {
col['sortable'] = true;
};
}
columns.push(col);
col = {
field: 'updated',
title: '{% trans "Last Updated" %}',
};
if (!options.params.ordering) {
col['sortable'] = true;
};
}
columns.push(col);
columns.push({
@ -921,8 +1006,10 @@ function loadStockTable(table, options) {
return renderLink(text, link);
}
},
{
});
col = {
field: 'supplier_part',
title: '{% trans "Supplier Part" %}',
visible: params['supplier_part_detail'] || false,
@ -944,15 +1031,25 @@ function loadStockTable(table, options) {
return renderLink(text, link);
}
});
};
if (!options.params.ordering) {
col.sortable = true;
col.sortName = 'SKU';
}
columns.push(col);
col = {
field: 'purchase_price_string',
title: '{% trans "Purchase Price" %}',
};
if (!options.params.ordering) {
col['sortable'] = true;
};
col.sortable = true;
col.sortName = 'purchase_price';
}
columns.push(col);
columns.push({
@ -969,7 +1066,7 @@ function loadStockTable(table, options) {
formatNoMatches: function() {
return '{% trans "No stock items matching query" %}';
},
url: options.url || "{% url 'api-stock-list' %}",
url: options.url || '{% url "api-stock-list" %}',
queryParams: filters,
sidePagination: 'server',
name: 'stock',
@ -1036,7 +1133,7 @@ function loadStockTable(table, options) {
stock = +stock.toFixed(5);
return stock + " (" + items + " items)";
return `${stock} (${items} {% trans "items" %})`;
} else if (field == 'status') {
var statii = [];
@ -1158,7 +1255,7 @@ function loadStockTable(table, options) {
function stockAdjustment(action) {
var items = $(table).bootstrapTable("getSelections");
var items = $(table).bootstrapTable('getSelections');
adjustStock(action, items, {
onSuccess: function() {
@ -1191,7 +1288,7 @@ function loadStockTable(table, options) {
});
printTestReports(items);
})
});
if (global_settings.BARCODE_ENABLE) {
$('#multi-item-barcode-scan-into-location').click(function() {
@ -1201,7 +1298,7 @@ function loadStockTable(table, options) {
selections.forEach(function(item) {
items.push(item.pk);
})
});
scanItemsIntoLocation(items);
});
@ -1219,12 +1316,12 @@ function loadStockTable(table, options) {
stockAdjustment('add');
});
$("#multi-item-move").click(function() {
$('#multi-item-move').click(function() {
stockAdjustment('move');
});
$("#multi-item-order").click(function() {
var selections = $(table).bootstrapTable("getSelections");
$('#multi-item-order').click(function() {
var selections = $(table).bootstrapTable('getSelections');
var stock = [];
@ -1232,14 +1329,14 @@ function loadStockTable(table, options) {
stock.push(item.pk);
});
launchModalForm("/order/purchase-order/order-parts/", {
launchModalForm('/order/purchase-order/order-parts/', {
data: {
stock: stock,
},
});
});
$("#multi-item-set-status").click(function() {
$('#multi-item-set-status').click(function() {
// Select and set the STATUS field for selected stock items
var selections = $(table).bootstrapTable('getSelections');
@ -1251,7 +1348,7 @@ function loadStockTable(table, options) {
function(item) {
return item.text;
},
function (item) {
function(item) {
return item.key;
}
);
@ -1323,11 +1420,11 @@ function loadStockTable(table, options) {
$.when.apply($, requests).done(function() {
$(table).bootstrapTable('refresh');
});
})
});
});
$("#multi-item-delete").click(function() {
var selections = $(table).bootstrapTable("getSelections");
$('#multi-item-delete').click(function() {
var selections = $(table).bootstrapTable('getSelections');
var stock = [];
@ -1356,8 +1453,8 @@ function loadStockLocationTable(table, options) {
var original = {};
for (var key in params) {
original[key] = params[key];
for (var k in params) {
original[k] = params[k];
}
setupFilterList(filterKey, table, filterListElement);
@ -1425,7 +1522,7 @@ function loadStockTrackingTable(table, options) {
field: 'date',
title: '{% trans "Date" %}',
sortable: true,
formatter: function(value, row, index, field) {
formatter: function(value) {
var m = moment(value);
if (m.isValid()) {
@ -1441,11 +1538,11 @@ function loadStockTrackingTable(table, options) {
cols.push({
field: 'label',
title: '{% trans "Description" %}',
formatter: function(value, row, index, field) {
var html = "<b>" + value + "</b>";
formatter: function(value, row) {
var html = '<b>' + value + '</b>';
if (row.notes) {
html += "<br><i>" + row.notes + "</i>";
html += '<br><i>' + row.notes + '</i>';
}
return html;
@ -1456,7 +1553,7 @@ function loadStockTrackingTable(table, options) {
cols.push({
field: 'deltas',
title: '{% trans "Details" %}',
formatter: function(details, row, index, field) {
formatter: function(details, row) {
var html = `<table class='table table-condensed' id='tracking-table-${row.pk}'>`;
if (!details) {
@ -1591,14 +1688,11 @@ function loadStockTrackingTable(table, options) {
cols.push({
field: 'user',
title: '{% trans "User" %}',
formatter: function(value, row, index, field) {
if (value)
{
formatter: function(value, row) {
if (value) {
// TODO - Format the user's first and last names
return row.user_detail.username;
}
else
{
} else {
return `<i>{% trans "No user information" %}</i>`;
}
}
@ -1680,7 +1774,7 @@ function createNewStockItem(options) {
reloadFieldOptions(
'supplier_part',
{
url: "{% url 'api-supplier-part-list' %}",
url: '{% url "api-supplier-part-list" %}',
params: {
part: value,
pretty: true,
@ -1711,7 +1805,7 @@ function createNewStockItem(options) {
} else {
var expiry = moment().add(response.default_expiry, 'days');
setFieldValue('expiry_date', expiry.format("YYYY-MM-DD"));
setFieldValue('expiry_date', expiry.format('YYYY-MM-DD'));
}
}
}
@ -1720,7 +1814,7 @@ function createNewStockItem(options) {
},
];
launchModalForm("{% url 'stock-item-create' %}", options);
launchModalForm('{% url "stock-item-create" %}', options);
}
@ -1729,28 +1823,8 @@ function loadInstalledInTable(table, options) {
* Display a table showing the stock items which are installed in this stock item.
*/
function updateCallbacks() {
// Setup callback functions when buttons are pressed
table.find('.button-install').click(function() {
var pk = $(this).attr('pk');
launchModalForm(
`/stock/item/${options.stock_item}/install/`,
{
data: {
part: pk,
},
success: function() {
// Refresh entire table!
table.bootstrapTable('refresh');
}
}
);
});
}
table.inventreeTable({
url: "{% url 'api-stock-list' %}",
url: '{% url "api-stock-list" %}',
queryParams: {
installed_in: options.stock_item,
part_detail: true,
@ -1790,7 +1864,7 @@ function loadInstalledInTable(table, options) {
{
field: 'status',
title: '{% trans "Status" %}',
formatter: function(value, row) {
formatter: function(value) {
return stockStatusDisplay(value);
}
},
@ -1829,8 +1903,8 @@ function loadInstalledInTable(table, options) {
table.bootstrapTable('refresh');
}
}
)
);
});
}
});
}
}

View File

@ -8,13 +8,26 @@
{% include "status_codes.html" with label='purchaseOrder' options=PurchaseOrderStatus.list %}
{% include "status_codes.html" with label='salesOrder' options=SalesOrderStatus.list %}
/* globals
global_settings
*/
/* exported
buildStatusDisplay,
getAvailableTableFilters,
purchaseOrderStatusDisplay,
salesOrderStatusDisplay,
stockHistoryStatusDisplay,
stockStatusDisplay,
*/
function getAvailableTableFilters(tableKey) {
tableKey = tableKey.toLowerCase();
// Filters for "variant" table
if (tableKey == "variants") {
if (tableKey == 'variants') {
return {
active: {
type: 'bool',
@ -36,11 +49,11 @@ function getAvailableTableFilters(tableKey) {
}
// Filters for Bill of Materials table
if (tableKey == "bom") {
if (tableKey == 'bom') {
return {
sub_part_trackable: {
type: 'bool',
title: '{% trans "Trackable Part" %}'
title: '{% trans "Trackable Part" %}',
},
sub_part_assembly: {
type: 'bool',
@ -57,7 +70,7 @@ function getAvailableTableFilters(tableKey) {
allow_variants: {
type: 'bool',
title: '{% trans "Allow Variant Stock" %}',
}
},
};
}
@ -72,29 +85,29 @@ function getAvailableTableFilters(tableKey) {
}
// Filters for "stock location" table
if (tableKey == "location") {
if (tableKey == 'location') {
return {
cascade: {
type: 'bool',
title: '{% trans "Include sublocations" %}',
description: '{% trans "Include locations" %}',
}
},
};
}
// Filters for "part category" table
if (tableKey == "category") {
if (tableKey == 'category') {
return {
cascade: {
type: 'bool',
title: '{% trans "Include subcategories" %}',
description: '{% trans "Include subcategories" %}',
}
},
};
}
// Filters for the "customer stock" table (really a subset of "stock")
if (tableKey == "customerstock") {
if (tableKey == 'customerstock') {
return {
serialized: {
type: 'bool',
@ -102,7 +115,7 @@ function getAvailableTableFilters(tableKey) {
},
serial_gte: {
title: '{% trans "Serial number GTE" %}',
description: '{% trans "Serial number greater than or equal to" %}'
description: '{% trans "Serial number greater than or equal to" %}',
},
serial_lte: {
title: '{% trans "Serial number LTE" %}',
@ -110,7 +123,7 @@ function getAvailableTableFilters(tableKey) {
},
serial: {
title: '{% trans "Serial number" %}',
description: '{% trans "Serial number" %}'
description: '{% trans "Serial number" %}',
},
batch: {
title: '{% trans "Batch" %}',
@ -179,11 +192,11 @@ function getAvailableTableFilters(tableKey) {
},
serial: {
title: '{% trans "Serial number" %}',
description: '{% trans "Serial number" %}'
description: '{% trans "Serial number" %}',
},
serial_gte: {
title: '{% trans "Serial number GTE" %}',
description: '{% trans "Serial number greater than or equal to" %}'
description: '{% trans "Serial number greater than or equal to" %}',
},
serial_lte: {
title: '{% trans "Serial number LTE" %}',
@ -239,7 +252,7 @@ function getAvailableTableFilters(tableKey) {
required: {
type: 'bool',
title: '{% trans "Required" %}',
}
},
};
}
@ -262,7 +275,7 @@ function getAvailableTableFilters(tableKey) {
}
// Filters for the "Order" table
if (tableKey == "purchaseorder") {
if (tableKey == 'purchaseorder') {
return {
status: {
@ -280,7 +293,7 @@ function getAvailableTableFilters(tableKey) {
};
}
if (tableKey == "salesorder") {
if (tableKey == 'salesorder') {
return {
status: {
title: '{% trans "Order status" %}',
@ -302,12 +315,12 @@ function getAvailableTableFilters(tableKey) {
active: {
type: 'bool',
title: '{% trans "Active parts" %}',
}
},
};
}
// Filters for the "Parts" table
if (tableKey == "parts") {
if (tableKey == 'parts') {
return {
cascade: {
type: 'bool',
@ -330,7 +343,7 @@ function getAvailableTableFilters(tableKey) {
},
has_stock: {
type: 'bool',
title: '{% trans "Stock available" %}'
title: '{% trans "Stock available" %}',
},
low_stock: {
type: 'bool',

View File

@ -1,44 +1,33 @@
{% load i18n %}
/* global
inventreeLoad,
inventreeSave,
*/
function tdWrap(html, options={}) {
/* Wraps provided html in <td>..</td> elements
*/
var colspan = '';
if (options.colspan) {
colspan = ` colspan=${options.colspan}`;
}
return `<td${colspan}>${html}</td>`;
}
function trWrap(html) {
/* Wraps provided html in <tr>..</tr> elements
*/
return `<tr>${html}</tr>`;
}
/* exported
customGroupSorter,
reloadtable,
renderLink,
reloadTableFilters,
*/
/**
* Reload a named table
* @param table
*/
function reloadtable(table) {
$(table).bootstrapTable('refresh');
}
function editButton(url, text='Edit') {
return "<button class='btn btn-success edit-button btn-sm' type='button' url='" + url + "'>" + text + "</button>";
}
function deleteButton(url, text='Delete') {
return "<button class='btn btn-danger delete-button btn-sm' type='button' url='" + url + "'>" + text + "</button>";
}
/**
* Render a URL for display
* @param {String} text
* @param {String} url
* @param {object} options
* @returns link text
*/
function renderLink(text, url, options={}) {
if (url === null || url === undefined || url === '') {
return text;
@ -46,8 +35,6 @@ function renderLink(text, url, options={}) {
var max_length = options.max_length || -1;
var remove_http = options.remove_http || false;
// Shorten the displayed length if required
if ((max_length > 0) && (text.length > max_length)) {
var slice_length = (max_length - 3) / 2;
@ -82,12 +69,17 @@ function linkButtonsToSelection(table, buttons) {
enableButtons(buttons, table.bootstrapTable('getSelections').length > 0);
// Add a callback
table.on('check.bs.table uncheck.bs.table check-some.bs.table uncheck-some.bs.table check-all.bs.table uncheck-all.bs.table', function(row) {
table.on('check.bs.table uncheck.bs.table check-some.bs.table uncheck-some.bs.table check-all.bs.table uncheck-all.bs.table', function() {
enableButtons(buttons, table.bootstrapTable('getSelections').length > 0);
});
}
/**
* Returns true if the input looks like a valid number
* @param {String} n
* @returns
*/
function isNumeric(n) {
return !isNaN(parseFloat(n)) && isFinite(n);
}
@ -111,8 +103,8 @@ function reloadTableFilters(table, filters) {
// Construct a new list of filters to use for the query
var params = {};
for (var key in filters) {
params[key] = filters[key];
for (var k in filters) {
params[k] = filters[k];
}
// Original query params will override
@ -159,7 +151,6 @@ function convertQueryParameters(params, filters) {
var ordering = params['sort'] || null;
if (ordering) {
if (order == 'desc') {
ordering = `-${ordering}`;
}
@ -243,7 +234,7 @@ $.fn.inventreeTable = function(options) {
};
// Callback when a column is changed
options.onColumnSwitch = function(field, checked) {
options.onColumnSwitch = function() {
var columns = table.bootstrapTable('getVisibleColumns');
@ -262,7 +253,7 @@ $.fn.inventreeTable = function(options) {
// If a set of visible columns has been saved, load!
if (visibleColumns) {
var columns = visibleColumns.split(",");
var columns = visibleColumns.split(',');
// Which columns are currently visible?
var visible = table.bootstrapTable('getVisibleColumns');
@ -276,7 +267,7 @@ $.fn.inventreeTable = function(options) {
}
});
} else {
console.log('Could not get list of visible columns!');
console.log(`Could not get list of visible columns for column '${tableName}'`);
}
}
@ -284,7 +275,8 @@ $.fn.inventreeTable = function(options) {
if (options.buttons) {
linkButtonsToSelection(table, options.buttons);
}
}
};
function customGroupSorter(sortName, sortOrder, sortData) {
@ -357,42 +349,42 @@ function customGroupSorter(sortName, sortOrder, sortData) {
}
// Expose default bootstrap table string literals to translation layer
(function ($) {
(function($) {
'use strict';
$.fn.bootstrapTable.locales['en-US-custom'] = {
formatLoadingMessage: function () {
formatLoadingMessage: function() {
return '{% trans "Loading data" %}';
},
formatRecordsPerPage: function (pageNumber) {
formatRecordsPerPage: function(pageNumber) {
return `${pageNumber} {% trans "rows per page" %}`;
},
formatShowingRows: function (pageFrom, pageTo, totalRows) {
formatShowingRows: function(pageFrom, pageTo, totalRows) {
return `{% trans "Showing" %} ${pageFrom} {% trans "to" %} ${pageTo} {% trans "of" %} ${totalRows} {% trans "rows" %}`;
},
formatSearch: function () {
formatSearch: function() {
return '{% trans "Search" %}';
},
formatNoMatches: function () {
formatNoMatches: function() {
return '{% trans "No matching results" %}';
},
formatPaginationSwitch: function () {
formatPaginationSwitch: function() {
return '{% trans "Hide/Show pagination" %}';
},
formatRefresh: function () {
formatRefresh: function() {
return '{% trans "Refresh" %}';
},
formatToggle: function () {
formatToggle: function() {
return '{% trans "Toggle" %}';
},
formatColumns: function () {
formatColumns: function() {
return '{% trans "Columns" %}';
},
formatAllRows: function () {
formatAllRows: function() {
return '{% trans "All" %}';
}
},
};
$.extend($.fn.bootstrapTable.defaults, $.fn.bootstrapTable.locales['en-US-custom']);
})(jQuery);
})(jQuery);

View File

@ -14,7 +14,7 @@
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
<h3 id='modal-title'><i>Form Title Here</i></h3>
<h3 id='modal-title'><em>Form Title Here</em></h3>
</div>
<div class='modal-form-content-wrapper'>
<div class='alert alert-block alert-danger' id='form-validation-warning' style='display: none;'>
@ -40,7 +40,7 @@
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
<h3 id='modal-title'><i>Form Title Here</i></h3>
<h3 id='modal-title'><em>Form Title Here</em></h3>
</div>
<div class='modal-form-content-wrapper'>
<div class='alert alert-block alert-danger' id='form-validation-warning' style="display: none;">

View File

@ -80,7 +80,7 @@
<span class='fas fa-info-circle icon-green'></span>
{% endif %}
{% endif %}
<span class="fas fa-user"></span> <b>{{ user.get_username }}</b></a>
<span class="fas fa-user"></span> <strong>{{ user.get_username }}</strong></a>
<ul class='dropdown-menu'>
{% if user.is_authenticated %}
{% if user.is_staff %}

View File

@ -80,7 +80,7 @@
{% if form.errors %}
<div class='login-error'>
<b>{% trans "Username / password combination is incorrect" %}</b>
<strong>{% trans "Username / password combination is incorrect" %}</strong>
</div>
{% endif %}

View File

@ -6,7 +6,7 @@
<col width='25'>
<tr>
<td colspan="3"><b>{% trans "Server" %}</b></td>
<td colspan="3"><strong>{% trans "Server" %}</strong></td>
</tr>
<tr>
<td><span class='fas fa-server'></span></td>
@ -77,7 +77,7 @@
{% endif %}
<tr>
<td colspan='3'><b>{% trans "Parts" %}</b></td>
<td colspan='3'><strong>{% trans "Parts" %}</strong></td>
</tr>
<tr>
<td><span class='fas fa-sitemap'></span></td>
@ -90,7 +90,7 @@
<td>{{ part_count }}</td>
</tr>
<tr>
<td colspan="3"><b>{% trans "Stock Items" %}</b></td>
<td colspan="3"><strong>{% trans "Stock Items" %}</strong></td>
</tr>
<tr>
<td><span class='fas fa-map-marker-alt'></span></td>

View File

@ -1,12 +1,13 @@
/*
* Status codes for the {{ label }} model.
*/
var {{ label }}Codes = {
const {{ label }}Codes = {
{% for opt in options %}'{{ opt.key }}': {
key: '{{ opt.key }}',
value: '{{ opt.value }}',{% if opt.color %}
label: 'label-{{ opt.color }}',{% endif %}
},{% endfor %}
},
{% endfor %}
};
/*

View File

@ -28,9 +28,9 @@ InvenTree is [available via Docker](https://hub.docker.com/r/inventree/inventree
InvenTree is supported by a [companion mobile app](https://inventree.readthedocs.io/en/latest/app/app/) which allows users access to stock control information and functionality.
[**Download InvenTree from the Android Play Store**](https://play.google.com/store/apps/details?id=inventree.inventree_app)
- [**Download InvenTree from the Android Play Store**](https://play.google.com/store/apps/details?id=inventree.inventree_app)
*Currently the mobile app is only availble for Android*
- [**Download InvenTree from the Apple App Store**](https://apps.apple.com/au/app/inventree/id1581731101#?platform=iphone)
# Translation

View File

@ -457,3 +457,12 @@ def server(c, address="127.0.0.1:8000"):
"""
manage(c, "runserver {address}".format(address=address), pty=True)
@task
def render_js_files(c):
"""
Render templated javascript files (used for static testing).
"""
manage(c, "test InvenTree.ci_render_js")