2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-04-29 20:16:44 +00:00

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: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
INVENTREE_DB_ENGINE: sqlite3
INVENTREE_DB_NAME: inventree
INVENTREE_MEDIA_ROOT: ./media
INVENTREE_STATIC_ROOT: ./static
steps: steps:
- name: Install node.js
uses: actions/setup-node@v2
- run: npm install
- name: Checkout Code - name: Checkout Code
uses: actions/checkout@v2 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: | run: |
cd ci cd ci
python check_js_templates.py 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 .coverage
htmlcov/ htmlcov/
# Temporary javascript files (used for testing)
js_tmp/
# Development files # Development files
dev/ dev/
# Locale stats file # Locale stats file
locale_stats.json 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 decimal import Decimal
from collections import OrderedDict
from django.conf import settings from django.conf import settings
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.core.exceptions import ValidationError as DjangoValidationError from django.core.exceptions import ValidationError as DjangoValidationError
@ -94,9 +96,14 @@ class InvenTreeModelSerializer(serializers.ModelSerializer):
# If instance is None, we are creating a new instance # If instance is None, we are creating a new instance
if instance is None and data is not empty: if instance is None and data is not empty:
# Required to side-step immutability of a QueryDict if data is None:
data = data.copy() data = OrderedDict()
else:
new_data = OrderedDict()
new_data.update(data)
data = new_data
# Add missing fields which have default values # Add missing fields which have default values
ModelClass = self.Meta.model 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'^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'^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'^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'^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'^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'), 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_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 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 v10 -> 2021-08-23
- Adds "purchase_price_currency" to StockItem serializer - Adds "purchase_price_currency" to StockItem serializer
- Adds "purchase_price_string" to StockItem serializer - Adds "purchase_price_string" to StockItem serializer

View File

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

View File

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

View File

@ -8,7 +8,7 @@
</p> </p>
{% if output %} {% if output %}
<p> <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> </p>
{% endif %} {% endif %}
</div> </div>

View File

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

View File

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

View File

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

View File

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

View File

@ -9,13 +9,14 @@ from django.utils.translation import ugettext_lazy as _
from django.conf.urls import url, include from django.conf.urls import url, include
from django.db import transaction 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 generics
from rest_framework import filters, status from rest_framework import filters, status
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.serializers import ValidationError from rest_framework.serializers import ValidationError
from InvenTree.filters import InvenTreeOrderingFilter
from InvenTree.helpers import str2bool from InvenTree.helpers import str2bool
from InvenTree.api import AttachmentMixin from InvenTree.api import AttachmentMixin
from InvenTree.status_codes import PurchaseOrderStatus, SalesOrderStatus from InvenTree.status_codes import PurchaseOrderStatus, SalesOrderStatus
@ -149,7 +150,7 @@ class POList(generics.ListCreateAPIView):
return queryset return queryset
filter_backends = [ filter_backends = [
DjangoFilterBackend, rest_filters.DjangoFilterBackend,
filters.SearchFilter, filters.SearchFilter,
filters.OrderingFilter, filters.OrderingFilter,
] ]
@ -323,6 +324,14 @@ class POLineItemList(generics.ListCreateAPIView):
queryset = PurchaseOrderLineItem.objects.all() queryset = PurchaseOrderLineItem.objects.all()
serializer_class = POLineItemSerializer 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): def get_serializer(self, *args, **kwargs):
try: try:
@ -335,18 +344,26 @@ class POLineItemList(generics.ListCreateAPIView):
return self.serializer_class(*args, **kwargs) return self.serializer_class(*args, **kwargs)
filter_backends = [ filter_backends = [
DjangoFilterBackend, rest_filters.DjangoFilterBackend,
filters.SearchFilter, filters.SearchFilter,
filters.OrderingFilter InvenTreeOrderingFilter
] ]
ordering_field_aliases = {
'MPN': 'part__manufacturer_part__MPN',
'SKU': 'part__SKU',
'part_name': 'part__part__name',
}
ordering_fields = [ ordering_fields = [
'part__part__name', 'MPN',
'part__MPN', 'part_name',
'part__SKU', 'purchase_price',
'reference',
'quantity', 'quantity',
'received', 'received',
'reference',
'SKU',
'total_price',
] ]
search_fields = [ search_fields = [
@ -371,6 +388,14 @@ class POLineItemDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = PurchaseOrderLineItem.objects.all() queryset = PurchaseOrderLineItem.objects.all()
serializer_class = POLineItemSerializer serializer_class = POLineItemSerializer
def get_queryset(self):
queryset = super().get_queryset()
queryset = POLineItemSerializer.annotate_queryset(queryset)
return queryset
class SOAttachmentList(generics.ListCreateAPIView, AttachmentMixin): class SOAttachmentList(generics.ListCreateAPIView, AttachmentMixin):
""" """
@ -381,7 +406,7 @@ class SOAttachmentList(generics.ListCreateAPIView, AttachmentMixin):
serializer_class = SOAttachmentSerializer serializer_class = SOAttachmentSerializer
filter_backends = [ filter_backends = [
DjangoFilterBackend, rest_filters.DjangoFilterBackend,
] ]
filter_fields = [ filter_fields = [
@ -505,7 +530,7 @@ class SOList(generics.ListCreateAPIView):
return queryset return queryset
filter_backends = [ filter_backends = [
DjangoFilterBackend, rest_filters.DjangoFilterBackend,
filters.SearchFilter, filters.SearchFilter,
filters.OrderingFilter, filters.OrderingFilter,
] ]
@ -604,7 +629,7 @@ class SOLineItemList(generics.ListCreateAPIView):
return queryset return queryset
filter_backends = [ filter_backends = [
DjangoFilterBackend, rest_filters.DjangoFilterBackend,
filters.SearchFilter, filters.SearchFilter,
filters.OrderingFilter filters.OrderingFilter
] ]
@ -689,7 +714,7 @@ class SOAllocationList(generics.ListCreateAPIView):
return queryset return queryset
filter_backends = [ filter_backends = [
DjangoFilterBackend, rest_filters.DjangoFilterBackend,
] ]
# Default filterable fields # Default filterable fields
@ -707,7 +732,7 @@ class POAttachmentList(generics.ListCreateAPIView, AttachmentMixin):
serializer_class = POAttachmentSerializer serializer_class = POAttachmentSerializer
filter_backends = [ filter_backends = [
DjangoFilterBackend, rest_filters.DjangoFilterBackend,
] ]
filter_fields = [ 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"), 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( purchase_price = InvenTreeModelMoneyField(
max_digits=19, max_digits=19,
@ -778,7 +784,7 @@ class PurchaseOrderLineItem(OrderLineItem):
) )
destination = TreeForeignKey( destination = TreeForeignKey(
'stock.StockLocation', on_delete=models.DO_NOTHING, 'stock.StockLocation', on_delete=models.SET_NULL,
verbose_name=_('Destination'), verbose_name=_('Destination'),
related_name='po_lines', related_name='po_lines',
blank=True, null=True, 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.utils.translation import ugettext_lazy as _
from django.db import models
from django.db.models import Case, When, Value 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 import serializers
from rest_framework.serializers import ValidationError from rest_framework.serializers import ValidationError
@ -116,6 +117,23 @@ class POSerializer(InvenTreeModelSerializer):
class POLineItemSerializer(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): def __init__(self, *args, **kwargs):
part_detail = kwargs.pop('part_detail', False) part_detail = kwargs.pop('part_detail', False)
@ -130,6 +148,8 @@ class POLineItemSerializer(InvenTreeModelSerializer):
quantity = serializers.FloatField(default=1) quantity = serializers.FloatField(default=1)
received = serializers.FloatField(default=0) received = serializers.FloatField(default=0)
total_price = serializers.FloatField(read_only=True)
part_detail = PartBriefSerializer(source='get_base_part', many=False, 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) supplier_part_detail = SupplierPartSerializer(source='part', many=False, read_only=True)
@ -165,6 +185,7 @@ class POLineItemSerializer(InvenTreeModelSerializer):
'purchase_price_string', 'purchase_price_string',
'destination', 'destination',
'destination_detail', 'destination_detail',
'total_price',
] ]

View File

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

View File

@ -38,7 +38,7 @@
<tr id='part_row_{{ part.id }}'> <tr id='part_row_{{ part.id }}'>
<td> <td>
{% include "hover_image.html" with image=part.image hover=False %} {% 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>
<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'> <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> </select>
</div> </div>
{% if not part.order_supplier %} {% 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 %} {% endif %}
</div> </div>
</td> </td>

View File

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

View File

@ -5,7 +5,7 @@
{% block form %} {% 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'> <form method='post' action='' class='js-modal-form' enctype='multipart/form-data'>
{% csrf_token %} {% csrf_token %}

View File

@ -22,7 +22,7 @@
{% endif %} {% endif %}
<div class='alert alert-block alert-info'> <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> <br>
{% trans "Shipping this order means that the order will no longer be editable." %} {% trans "Shipping this order means that the order will no longer be editable." %}
</div> </div>

View File

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

View File

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

View File

@ -11,13 +11,13 @@
<div class='alert alert-block alert-info'> <div class='alert alert-block alert-info'>
{% else %} {% else %}
<div class='alert alert-block alert-danger'> <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 %} {% 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> </div>
{% else %} {% else %}
<div class='alert alert-danger alert-block'> <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> </div>
{% endif %} {% endif %}

View File

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

View File

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

View File

@ -43,9 +43,9 @@
{% block form_alert %} {% block form_alert %}
<div class='alert alert-info alert-block'> <div class='alert alert-info alert-block'>
<b>{% trans "Requirements for BOM upload" %}:</b> <strong>{% trans "Requirements for BOM upload" %}:</strong>
<ul> <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> <li>{% trans "Each part must already exist in the database" %}</li>
</ul> </ul>
</div> </div>

View File

@ -3,7 +3,7 @@
{% load i18n %} {% load i18n %}
{% block pre_form_content %} {% 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'> <div class='alert alert-warning alert-block'>
{% trans 'This will validate each line in the BOM.' %} {% trans 'This will validate each line in the BOM.' %}

View File

@ -8,13 +8,13 @@
{% if matches %} {% if matches %}
<div class='alert alert-block alert-warning'> <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> <p>{% trans "The new part may be a duplicate of these existing parts" %}:</p>
<ul class='list-group'> <ul class='list-group'>
{% for match in matches %} {% for match in matches %}
<li class='list-group-item list-group-item-condensed'> <li class='list-group-item list-group-item-condensed'>
{% decimal match.ratio as match_per %} {% 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> </li>
{% endfor %} {% endfor %}
</ul> </ul>

View File

@ -18,7 +18,7 @@
<div class='panel-content'> <div class='panel-content'>
{% if part.is_template %} {% if part.is_template %}
<div class='alert alert-info alert-block'> <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> </div>
{% endif %} {% endif %}
{% include "stock_table.html" %} {% include "stock_table.html" %}

View File

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

View File

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

View File

@ -9,11 +9,11 @@
<table class='table table-striped table-condensed table-price-two'> <table class='table table-striped table-condensed table-price-two'>
<tr> <tr>
<td><b>{% trans 'Part' %}</b></td> <td><strong>{% trans 'Part' %}</strong></td>
<td>{{ part }}</td> <td>{{ part }}</td>
</tr> </tr>
<tr> <tr>
<td><b>{% trans 'Quantity' %}</b></td> <td><strong>{% trans 'Quantity' %}</strong></td>
<td>{{ quantity }}</td> <td>{{ quantity }}</td>
</tr> </tr>
</table> </table>
@ -23,13 +23,13 @@
<table class='table table-striped table-condensed table-price-three'> <table class='table table-striped table-condensed table-price-three'>
{% if min_total_buy_price %} {% if min_total_buy_price %}
<tr> <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>Min: {% include "price.html" with price=min_unit_buy_price %}</td>
<td>Max: {% include "price.html" with price=max_unit_buy_price %}</td> <td>Max: {% include "price.html" with price=max_unit_buy_price %}</td>
</tr> </tr>
{% if quantity > 1 %} {% if quantity > 1 %}
<tr> <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>Min: {% include "price.html" with price=min_total_buy_price %}</td>
<td>Max: {% include "price.html" with price=max_total_buy_price %}</td> <td>Max: {% include "price.html" with price=max_total_buy_price %}</td>
</tr> </tr>
@ -37,7 +37,7 @@
{% else %} {% else %}
<tr> <tr>
<td colspan='3'> <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> </td>
</tr> </tr>
{% endif %} {% endif %}
@ -49,26 +49,26 @@
<table class='table table-striped table-condensed table-price-three'> <table class='table table-striped table-condensed table-price-three'>
{% if min_total_bom_price %} {% if min_total_bom_price %}
<tr> <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>Min: {% include "price.html" with price=min_unit_bom_price %}</td>
<td>Max: {% include "price.html" with price=max_unit_bom_price %}</td> <td>Max: {% include "price.html" with price=max_unit_bom_price %}</td>
</tr> </tr>
{% if quantity > 1 %} {% if quantity > 1 %}
<tr> <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>Min: {% include "price.html" with price=min_total_bom_price %}</td>
<td>Max: {% include "price.html" with price=max_total_bom_price %}</td> <td>Max: {% include "price.html" with price=max_total_bom_price %}</td>
</tr> </tr>
{% endif %} {% endif %}
{% if min_total_bom_purchase_price %} {% if min_total_bom_purchase_price %}
<tr> <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>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> <td>Max: {% include "price.html" with price=max_unit_bom_purchase_price %}</td>
</tr> </tr>
{% if quantity > 1 %} {% if quantity > 1 %}
<tr> <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>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> <td>Max: {% include "price.html" with price=max_total_bom_purchase_price %}</td>
</tr> </tr>
@ -78,14 +78,14 @@
{% if part.has_complete_bom_pricing == False %} {% if part.has_complete_bom_pricing == False %}
<tr> <tr>
<td colspan='3'> <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> </td>
</tr> </tr>
{% endif %} {% endif %}
{% else %} {% else %}
<tr> <tr>
<td colspan='3'> <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> </td>
</tr> </tr>
{% endif %} {% endif %}
@ -97,11 +97,11 @@
<h4>{% trans 'Internal Price' %}</h4> <h4>{% trans 'Internal Price' %}</h4>
<table class='table table-striped table-condensed table-price-two'> <table class='table table-striped table-condensed table-price-two'>
<tr> <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> <td>{% include "price.html" with price=unit_internal_part_price %}</td>
</tr> </tr>
<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> <td>{% include "price.html" with price=total_internal_part_price %}</td>
</tr> </tr>
</table> </table>
@ -112,11 +112,11 @@
<h4>{% trans 'Sale Price' %}</h4> <h4>{% trans 'Sale Price' %}</h4>
<table class='table table-striped table-condensed table-price-two'> <table class='table table-striped table-condensed table-price-two'>
<tr> <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> <td>{% include "price.html" with price=unit_part_price %}</td>
</tr> </tr>
<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> <td>{% include "price.html" with price=total_part_price %}</td>
</tr> </tr>
</table> </table>

View File

@ -4,7 +4,7 @@
{% block pre_form_content %} {% block pre_form_content %}
<div class='alert alert-block alert-danger'> <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> </div>
{% if part.used_in_count %} {% if part.used_in_count %}

View File

@ -18,7 +18,7 @@
{% if part.supplier_count > 0 %} {% if part.supplier_count > 0 %}
{% if min_total_buy_price %} {% if min_total_buy_price %}
<tr> <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="#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> <a href="#purchase-price" title='{% trans "Show purchase price" %}'><span class="fas fa-chart-bar"></span></a>
</td> </td>
@ -37,7 +37,7 @@
{% else %} {% else %}
<tr> <tr>
<td colspan='4'> <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> </td>
</tr> </tr>
{% endif %} {% endif %}
@ -46,7 +46,7 @@
{% if part.bom_count > 0 %} {% if part.bom_count > 0 %}
{% if min_total_bom_price %} {% if min_total_bom_price %}
<tr> <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> <a href="#bom-cost" title='{% trans "Show BOM cost" %}'><span class="fas fa-search-dollar"></span></a>
</td> </td>
<td>{% trans 'Unit Cost' %}</td> <td>{% trans 'Unit Cost' %}</td>
@ -83,14 +83,14 @@
{% if part.has_complete_bom_pricing == False %} {% if part.has_complete_bom_pricing == False %}
<tr> <tr>
<td colspan='4'> <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> </td>
</tr> </tr>
{% endif %} {% endif %}
{% else %} {% else %}
<tr> <tr>
<td colspan='4'> <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> </td>
</tr> </tr>
{% endif %} {% endif %}
@ -99,7 +99,7 @@
{% if show_internal_price and roles.sales_order.view %} {% if show_internal_price and roles.sales_order.view %}
{% if total_internal_part_price %} {% if total_internal_part_price %}
<tr> <tr>
<td><b>{% trans 'Internal Price' %}</b></td> <td><strong>{% trans 'Internal Price' %}</strong></td>
<td>{% trans 'Unit Cost' %}</td> <td>{% trans 'Unit Cost' %}</td>
<td colspan='2'>{% include "price.html" with price=unit_internal_part_price %}</td> <td colspan='2'>{% include "price.html" with price=unit_internal_part_price %}</td>
</tr> </tr>
@ -113,7 +113,7 @@
{% if total_part_price %} {% if total_part_price %}
<tr> <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-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> <a href="#sale-price" title='{% trans "Show sale price" %}'><span class="fas fa-chart-bar"></span></a>
</td> </td>
@ -179,7 +179,7 @@
</div> </div>
<div class='panel-content'> <div class='panel-content'>
<h4>{% trans 'Stock Pricing' %} <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> </h4>
{% if price_history|length > 0 %} {% if price_history|length > 0 %}
<div style="max-width: 99%; min-height: 300px"> <div style="max-width: 99%; min-height: 300px">

View File

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

View File

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

View File

@ -68,8 +68,8 @@ content: "{% trans 'Stock Item Test Report' %}";
{{ part.full_name }} {{ part.full_name }}
</h2> </h2>
<p>{{ part.description }}</p> <p>{{ part.description }}</p>
<p><i>{{ stock_item.location }}</i></p> <p><em>{{ stock_item.location }}</em></p>
<p><i>Stock Item ID: {{ stock_item.pk }}</i></p> <p><em>Stock Item ID: {{ stock_item.pk }}</em></p>
</div> </div>
<div class='img-right'> <div class='img-right'>
<img class='part-img' src="{% part_image part %}"> <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.views import TreeSerializer
from InvenTree.helpers import str2bool, isNull from InvenTree.helpers import str2bool, isNull
from InvenTree.api import AttachmentMixin from InvenTree.api import AttachmentMixin
from InvenTree.filters import InvenTreeOrderingFilter
from decimal import Decimal, InvalidOperation from decimal import Decimal, InvalidOperation
@ -882,10 +883,16 @@ class StockList(generics.ListCreateAPIView):
filter_backends = [ filter_backends = [
DjangoFilterBackend, DjangoFilterBackend,
filters.SearchFilter, filters.SearchFilter,
filters.OrderingFilter, InvenTreeOrderingFilter,
] ]
ordering_field_aliases = {
'SKU': 'supplier_part__SKU',
}
ordering_fields = [ ordering_fields = [
'batch',
'location',
'part__name', 'part__name',
'part__IPN', 'part__IPN',
'updated', 'updated',
@ -893,10 +900,13 @@ class StockList(generics.ListCreateAPIView):
'expiry_date', 'expiry_date',
'quantity', 'quantity',
'status', 'status',
'SKU',
] ]
ordering = [ ordering = [
'part__name' 'part__name',
'quantity',
'location',
] ]
search_fields = [ search_fields = [

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 MiB

View File

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

View File

@ -9,7 +9,7 @@
{% trans "Are you sure you want to delete this stock item?" %} {% trans "Are you sure you want to delete this stock item?" %}
<br> <br>
{% decimal item.quantity as qty %} {% 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> </div>
{% endblock %} {% endblock %}

View File

@ -3,7 +3,7 @@
<div class="navigation"> <div class="navigation">
<nav aria-label="breadcrumb"> <nav aria-label="breadcrumb">
<ol class="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> <li class="breadcrumb-item{% if location is None %} active" aria-current="page{% endif %}"><a href="/stock/">{% trans "Stock" %}</a></li>
{% if location %} {% if location %}
{% for path_item in location.parentpath %} {% for path_item in location.parentpath %}

View File

@ -20,7 +20,7 @@ the top level 'Stock' location.
<ul class='list-group'> <ul class='list-group'>
{% for loc in location.children.all %} {% 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 %} {% endfor %}
</ul> </ul>
{% endif %} {% endif %}
@ -36,7 +36,7 @@ If this location is deleted, these items will be moved to the top level 'Stock'
<ul class='list-group'> <ul class='list-group'>
{% for item in location.stock_items.all %} {% 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 %} {% endfor %}
</ul> </ul>
{% endif %} {% endif %}

View File

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

View File

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

View File

@ -5,6 +5,8 @@ Unit testing for the Stock API
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals from __future__ import unicode_literals
import os
from datetime import datetime, timedelta from datetime import datetime, timedelta
from django.urls import reverse from django.urls import reverse
@ -666,3 +668,37 @@ class StockTestResultTest(StockAPITestCase):
test = response.data[0] test = response.data[0]
self.assertEqual(test['value'], '150kPa') self.assertEqual(test['value'], '150kPa')
self.assertEqual(test['user'], self.user.pk) 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) { function addHeaderTitle(title) {
// Add a header block to the action list // Add a header block to the action list
$("#action-item-list").append( $("#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 %} {% if query %}
{% else %} {% else %}
<div id='empty-search-query'> <div id='empty-search-query'>
<h4><i>{% trans "Enter a search query" %}</i></h4> <h4><em>{% trans "Enter a search query" %}</em></h4>
</div> </div>
{% endif %} {% endif %}
@ -47,7 +47,7 @@
function addItemTitle(title) { function addItemTitle(title) {
// Add header block to the results list // Add header block to the results list
$('#search-item-list').append( $('#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" %}"; var text = "{% trans "Shipped to customer" %}";
return renderLink(text, `/company/${row.customer}/assigned-stock/`); return renderLink(text, `/company/${row.customer}/assigned-stock/`);
} else { } 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 %} {% if rates_updated %}
{{ rates_updated }} {{ rates_updated }}
{% else %} {% else %}
<i>{% trans "Never" %}</i> <em>{% trans "Never" %}</em>
{% endif %} {% endif %}
<form action='{% url "settings-currencies-refresh" %}' method='post'> <form action='{% url "settings-currencies-refresh" %}' method='post'>
<div id='refresh-rates-form'> <div id='refresh-rates-form'>

View File

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

View File

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

View File

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

View File

@ -87,7 +87,7 @@
<td> <td>
<span style="display: none;" id="about-copy-text">{% include "version.html" %}</span> <span style="display: none;" id="about-copy-text">{% include "version.html" %}</span>
<span class="float-right"> <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> </span>
</td> </td>
</tr> </tr>

View File

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

View File

@ -1,5 +1,5 @@
{% load i18n %} {% load i18n %}
<span class="float-right"> <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> </span>

View File

@ -1,17 +1,26 @@
{% load i18n %} {% load i18n %}
/* globals
*/
/* exported
clearEvents,
endDate,
startDate,
*/
/** /**
* Helper functions for calendar display * Helper functions for calendar display
*/ */
function startDate(calendar) { function startDate(calendar) {
// Extract the first displayed date on the 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) { function endDate(calendar) {
// Extract the last display date on the 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) { function clearEvents(calendar) {
@ -21,5 +30,5 @@ function clearEvents(calendar) {
events.forEach(function(event) { events.forEach(function(event) {
event.remove(); event.remove();
}) });
} }

View File

@ -1,26 +1,44 @@
{% load inventree_extras %} {% load inventree_extras %}
/* globals
ClipboardJS,
inventreeFormDataUpload,
launchModalForm,
user_settings,
*/
/* exported
attachClipboard,
enableDragAndDrop,
inventreeDocReady,
inventreeLoad,
inventreeSave,
*/
function attachClipboard(selector, containerselector, textElement) { function attachClipboard(selector, containerselector, textElement) {
// set container // set container
if (containerselector){ if (containerselector) {
containerselector = document.getElementById(containerselector); containerselector = document.getElementById(containerselector);
} else { } else {
containerselector = document.body; containerselector = document.body;
} }
var text = null;
// set text-function // set text-function
if (textElement){ if (textElement) {
text = function() { text = function() {
return document.getElementById(textElement).textContent; return document.getElementById(textElement).textContent;
} };
} else { } else {
text = function(trigger) { text = function(trigger) {
var content = trigger.parentElement.parentElement.textContent; var content = trigger.parentElement.parentElement.textContent;
return content.trim(); return content.trim();
} };
} }
// create Clipboard // create Clipboard
// eslint-disable-next-line no-unused-vars
var cis = new ClipboardJS(selector, { var cis = new ClipboardJS(selector, {
text: text, text: text,
container: containerselector container: containerselector
@ -33,15 +51,15 @@ function inventreeDocReady() {
* This will be called for every page that extends "base.html" * 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 = e || event;
e.preventDefault(); e.preventDefault();
},false); }, false);
window.addEventListener("drop",function(e){ window.addEventListener('drop', function(e) {
e = e || event; e = e || event;
e.preventDefault(); e.preventDefault();
},false); }, false);
/* Add drag-n-drop functionality to any element /* Add drag-n-drop functionality to any element
* marked with the class 'dropzone' * 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 // TODO - Only indicate that a drop event will occur if a file is being dragged
var transfer = event.originalEvent.dataTransfer; var transfer = event.originalEvent.dataTransfer;
// eslint-disable-next-line no-constant-condition
if (true || isFileTransfer(transfer)) { if (true || isFileTransfer(transfer)) {
$(this).addClass('dragover'); $(this).addClass('dragover');
} }
}); });
$('.dropzone').on('dragleave drop', function(event) { $('.dropzone').on('dragleave drop', function() {
$(this).removeClass('dragover'); $(this).removeClass('dragover');
}); });
@ -74,19 +93,19 @@ function inventreeDocReady() {
// Callback to launch the 'Database Stats' window // Callback to launch the 'Database Stats' window
$('#launch-stats').click(function() { $('#launch-stats').click(function() {
launchModalForm("/stats/", { launchModalForm('/stats/', {
no_post: true, no_post: true,
}); });
}); });
// Initialize clipboard-buttons // Initialize clipboard-buttons
attachClipboard('.clip-btn'); attachClipboard('.clip-btn');
attachClipboard('.clip-btn', 'modal-about'); // modals attachClipboard('.clip-btn', 'modal-about');
attachClipboard('.clip-btn-version', 'modal-about', 'about-copy-text'); // version-text attachClipboard('.clip-btn-version', 'modal-about', 'about-copy-text');
// Add autocomplete to the search-bar // Add autocomplete to the search-bar
$("#search-bar" ).autocomplete({ $('#search-bar').autocomplete({
source: function (request, response) { source: function(request, response) {
$.ajax({ $.ajax({
url: '/api/part/', url: '/api/part/',
data: { data: {
@ -94,8 +113,8 @@ function inventreeDocReady() {
limit: user_settings.SEARCH_PREVIEW_RESULTS, limit: user_settings.SEARCH_PREVIEW_RESULTS,
offset: 0 offset: 0
}, },
success: function (data) { success: function(data) {
var transformed = $.map(data.results, function (el) { var transformed = $.map(data.results, function(el) {
return { return {
label: el.name, label: el.name,
id: el.pk, id: el.pk,
@ -104,13 +123,13 @@ function inventreeDocReady() {
}); });
response(transformed); response(transformed);
}, },
error: function () { error: function() {
response([]); response([]);
} }
}); });
}, },
create: function () { create: function() {
$(this).data('ui-autocomplete')._renderItem = function (ul, item) { $(this).data('ui-autocomplete')._renderItem = function(ul, item) {
var html = `<a href='/part/${item.id}/'><span>`; var html = `<a href='/part/${item.id}/'><span>`;
@ -128,7 +147,9 @@ function inventreeDocReady() {
window.location = '/part/' + ui.item.id + '/'; window.location = '/part/' + ui.item.id + '/';
}, },
minLength: 2, 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) { function enableDragAndDrop(element, url, options) {
/* Enable drag-and-drop file uploading for a given element. /* Enable drag-and-drop file uploading for a given element.
@ -272,7 +175,7 @@ function enableDragAndDrop(element, url, options) {
method - HTTP method method - HTTP method
*/ */
data = options.data || {}; var data = options.data || {};
$(element).on('drop', function(event) { $(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 */ * Save a key:value pair to local storage
* @param {String} name - settting key
if (!url) { * @param {String} value - setting value
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;
}
function inventreeSave(name, 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); 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); var value = localStorage.getItem(key);
@ -374,27 +249,3 @@ function inventreeLoad(name, defaultValue) {
return value; 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. * Attach callbacks to navigation bar elements.
@ -55,7 +62,7 @@ function activatePanel(panelName, options={}) {
// Iterate through the available 'select' elements until one matches // Iterate through the available 'select' elements until one matches
panelName = null; panelName = null;
$('.nav-toggle').each(function(item) { $('.nav-toggle').each(function() {
var panel_name = $(this).attr('id').replace('select-', ''); var panel_name = $(this).attr('id').replace('select-', '');
if ($(`#panel-${panel_name}`).length && (panelName == null)) { if ($(`#panel-${panel_name}`).length && (panelName == null)) {
@ -83,9 +90,9 @@ function activatePanel(panelName, options={}) {
$('.list-group-item').removeClass('active'); $('.list-group-item').removeClass('active');
// Find the associated selector // 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}`; var panelId = `#panel-${panel}`;
$(panelId).on('fadeInStarted', function(e) { $(panelId).on('fadeInStarted', function() {
// Trigger the callback // Trigger the callback
callback(); callback();
@ -105,4 +112,4 @@ function onPanelLoad(panel, callback) {
$(panelId).off('fadeInStarted'); $(panelId).off('fadeInStarted');
}); });
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -2,6 +2,63 @@
{% load inventree_extras %} {% load inventree_extras %}
{% load status_codes %} {% 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() { function locationFields() {
return { return {
@ -23,7 +80,7 @@ function stockStatusCodes() {
{% for code in StockStatus.list %} {% for code in StockStatus.list %}
{ {
key: {{ code.key }}, key: {{ code.key }},
text: "{{ code.value }}", text: '{{ code.value }}',
}, },
{% endfor %} {% endfor %}
]; ];
@ -45,11 +102,23 @@ function exportStock(params={}) {
type: 'choice', type: 'choice',
value: 'csv', value: 'csv',
choices: [ choices: [
{ value: 'csv', display_name: 'CSV' }, {
{ value: 'tsv', display_name: 'TSV' }, value: 'csv',
{ value: 'xls', display_name: 'XLS' }, display_name: 'CSV',
{ value: 'xlsx', display_name: 'XLSX' }, },
] {
value: 'tsv',
display_name: 'TSV',
},
{
value: 'xls',
display_name: 'XLS',
},
{
value: 'xlsx',
display_name: 'XLSX',
},
],
}, },
sublocations: { sublocations: {
label: '{% trans "Include Sublocations" %}', label: '{% trans "Include Sublocations" %}',
@ -94,34 +163,34 @@ function adjustStock(action, items, options={}) {
var allowSerializedStock = false; var allowSerializedStock = false;
switch (action) { switch (action) {
case 'move': case 'move':
formTitle = '{% trans "Transfer Stock" %}'; formTitle = '{% trans "Transfer Stock" %}';
actionTitle = '{% trans "Move" %}'; actionTitle = '{% trans "Move" %}';
specifyLocation = true; specifyLocation = true;
allowSerializedStock = true; allowSerializedStock = true;
url = '{% url "api-stock-transfer" %}'; url = '{% url "api-stock-transfer" %}';
break; break;
case 'count': case 'count':
formTitle = '{% trans "Count Stock" %}'; formTitle = '{% trans "Count Stock" %}';
actionTitle = '{% trans "Count" %}'; actionTitle = '{% trans "Count" %}';
url = '{% url "api-stock-count" %}'; url = '{% url "api-stock-count" %}';
break; break;
case 'take': case 'take':
formTitle = '{% trans "Remove Stock" %}'; formTitle = '{% trans "Remove Stock" %}';
actionTitle = '{% trans "Take" %}'; actionTitle = '{% trans "Take" %}';
url = '{% url "api-stock-remove" %}'; url = '{% url "api-stock-remove" %}';
break; break;
case 'add': case 'add':
formTitle = '{% trans "Add Stock" %}'; formTitle = '{% trans "Add Stock" %}';
actionTitle = '{% trans "Add" %}'; actionTitle = '{% trans "Add" %}';
url = '{% url "api-stock-add" %}'; url = '{% url "api-stock-add" %}';
break; break;
case 'delete': case 'delete':
formTitle = '{% trans "Delete Stock" %}'; formTitle = '{% trans "Delete Stock" %}';
allowSerializedStock = true; allowSerializedStock = true;
break; break;
default: default:
break; break;
} }
// Generate modal HTML content // Generate modal HTML content
@ -157,25 +226,25 @@ function adjustStock(action, items, options={}) {
var value = null; var value = null;
switch (action) { switch (action) {
case 'move': case 'move':
minValue = 0; minValue = 0;
maxValue = item.quantity; maxValue = item.quantity;
value = item.quantity; value = item.quantity;
break; break;
case 'add': case 'add':
minValue = 0; minValue = 0;
value = 0; value = 0;
break; break;
case 'take': case 'take':
minValue = 0; minValue = 0;
value = 0; value = 0;
break; break;
case 'count': case 'count':
minValue = 0; minValue = 0;
value = item.quantity; value = item.quantity;
break; break;
default: default:
break; break;
} }
var image = item.part_detail.thumbnail || item.part_detail.image || blankImage(); var image = item.part_detail.thumbnail || item.part_detail.image || blankImage();
@ -208,8 +277,8 @@ function adjustStock(action, items, options={}) {
read_only: readonly, read_only: readonly,
title: readonly ? '{% trans "Quantity cannot be adjusted for serialized stock" %}' : '{% trans "Specify stock quantity" %}', title: readonly ? '{% trans "Quantity cannot be adjusted for serialized stock" %}' : '{% trans "Specify stock quantity" %}',
} }
) );
}; }
var buttons = `<div class='btn-group float-right' role='group'>`; var buttons = `<div class='btn-group float-right' role='group'>`;
@ -283,7 +352,7 @@ function adjustStock(action, items, options={}) {
confirm: true, confirm: true,
confirmMessage: '{% trans "Confirm stock adjustment" %}', confirmMessage: '{% trans "Confirm stock adjustment" %}',
modal: modal, modal: modal,
onSubmit: function(fields, opts) { onSubmit: function(fields) {
// "Delete" action gets handled differently // "Delete" action gets handled differently
if (action == 'delete') { if (action == 'delete') {
@ -295,7 +364,7 @@ function adjustStock(action, items, options={}) {
inventreeDelete( inventreeDelete(
`/api/stock/${item.pk}/`, `/api/stock/${item.pk}/`,
) )
) );
}); });
// Wait for *all* the requests to complete // Wait for *all* the requests to complete
@ -327,7 +396,7 @@ function adjustStock(action, items, options={}) {
}); });
// Add in extra field data // Add in extra field data
for (field_name in extraFields) { for (var field_name in extraFields) {
data[field_name] = getFormFieldValue( data[field_name] = getFormFieldValue(
field_name, field_name,
fields[field_name], fields[field_name],
@ -342,7 +411,7 @@ function adjustStock(action, items, options={}) {
data, data,
{ {
method: 'POST', method: 'POST',
success: function(response, status) { success: function() {
// Destroy the modal window // Destroy the modal window
$(modal).modal('hide'); $(modal).modal('hide');
@ -353,22 +422,22 @@ function adjustStock(action, items, options={}) {
}, },
error: function(xhr) { error: function(xhr) {
switch (xhr.status) { switch (xhr.status) {
case 400: case 400:
// Handle errors for standard fields // Handle errors for standard fields
handleFormErrors( handleFormErrors(
xhr.responseJSON, xhr.responseJSON,
extraFields, extraFields,
{ {
modal: modal, modal: modal,
} }
) );
break; break;
default: default:
$(modal).modal('hide'); $(modal).modal('hide');
showApiError(xhr); showApiError(xhr);
break; 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 += makeIconButton('fa-trash-alt icon-red', 'button-test-delete', pk, '{% trans "Delete test result" %}');
} }
html += "</div>"; html += '</div>';
return html; return html;
} }
var parent_node = "parent node"; var parent_node = 'parent node';
table.inventreeTable({ table.inventreeTable({
url: "{% url 'api-part-test-template-list' %}", url: '{% url "api-part-test-template-list" %}',
method: 'get', method: 'get',
name: 'testresult', name: 'testresult',
treeEnable: true, treeEnable: true,
@ -473,7 +542,7 @@ function loadStockTestResultsTable(table, options) {
table.treegrid({ table.treegrid({
treeColumn: 0, treeColumn: 0,
}); });
table.treegrid("collapseAll"); table.treegrid('collapseAll');
}, },
columns: [ columns: [
{ {
@ -539,12 +608,12 @@ function loadStockTestResultsTable(table, options) {
stock_item: options.stock_item, stock_item: options.stock_item,
user_detail: true, user_detail: true,
attachment_detail: true, attachment_detail: true,
ordering: "-date", ordering: '-date',
}, },
{ {
success: function(data) { success: function(data) {
// Iterate through the returned test data // Iterate through the returned test data
data.forEach(function(item, index) { data.forEach(function(item) {
var match = false; var match = false;
var override = false; var override = false;
@ -589,13 +658,12 @@ function loadStockTestResultsTable(table, options) {
}); });
// Push data back into the table // 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 params = options.params || {};
var filterListElement = options.filterList || "#filter-list-stock"; var filterListElement = options.filterList || '#filter-list-stock';
var filters = {}; var filters = {};
var filterKey = options.filterKey || options.name || "stock"; var filterKey = options.filterKey || options.name || 'stock';
if (!options.disableFilters) { if (!options.disableFilters) {
filters = loadTableFilters(filterKey); filters = loadTableFilters(filterKey);
@ -683,8 +751,8 @@ function loadStockTable(table, options) {
var original = {}; var original = {};
for (var key in params) { for (var k in params) {
original[key] = params[key]; original[k] = params[k];
} }
setupFilterList(filterKey, table, filterListElement); setupFilterList(filterKey, table, filterListElement);
@ -700,10 +768,13 @@ function loadStockTable(table, options) {
grouping = options.grouping; grouping = options.grouping;
} }
var col = null;
// Explicitly disable part grouping functionality // Explicitly disable part grouping functionality
// Might be able to add this in later on, // Might be able to add this in later on,
// but there is a bug which makes this crash if paginating on the server side. // but there is a bug which makes this crash if paginating on the server side.
// Ref: https://github.com/wenzhixin/bootstrap-table/issues/3250 // Ref: https://github.com/wenzhixin/bootstrap-table/issues/3250
// eslint-disable-next-line no-unused-vars
grouping = false; grouping = false;
var columns = [ var columns = [
@ -727,22 +798,24 @@ function loadStockTable(table, options) {
sortName: 'part__name', sortName: 'part__name',
visible: params['part_detail'], visible: params['part_detail'],
switchable: params['part_detail'], switchable: params['part_detail'],
formatter: function(value, row, index, field) { formatter: function(value, row) {
var url = `/stock/item/${row.pk}/`; var url = `/stock/item/${row.pk}/`;
var thumb = row.part_detail.thumbnail; var thumb = row.part_detail.thumbnail;
var name = row.part_detail.full_name; 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); html += makePartIcons(row.part_detail);
return html; return html;
} }
}; };
if (!options.params.ordering) { if (!options.params.ordering) {
col['sortable'] = true; col['sortable'] = true;
}; }
columns.push(col); columns.push(col);
col = { col = {
@ -751,13 +824,15 @@ function loadStockTable(table, options) {
sortName: 'part__IPN', sortName: 'part__IPN',
visible: params['part_detail'], visible: params['part_detail'],
switchable: params['part_detail'], switchable: params['part_detail'],
formatter: function(value, row, index, field) { formatter: function(value, row) {
return row.part_detail.IPN; return row.part_detail.IPN;
}, },
}; };
if (!options.params.ordering) { if (!options.params.ordering) {
col['sortable'] = true; col['sortable'] = true;
}; }
columns.push(col); columns.push(col);
columns.push({ columns.push({
@ -765,7 +840,7 @@ function loadStockTable(table, options) {
title: '{% trans "Description" %}', title: '{% trans "Description" %}',
visible: params['part_detail'], visible: params['part_detail'],
switchable: params['part_detail'], switchable: params['part_detail'],
formatter: function(value, row, index, field) { formatter: function(value, row) {
return row.part_detail.description; return row.part_detail.description;
} }
}); });
@ -773,7 +848,7 @@ function loadStockTable(table, options) {
col = { col = {
field: 'quantity', field: 'quantity',
title: '{% trans "Stock" %}', title: '{% trans "Stock" %}',
formatter: function(value, row, index, field) { formatter: function(value, row) {
var val = parseFloat(value); var val = parseFloat(value);
@ -817,13 +892,9 @@ function loadStockTable(table, options) {
// REJECTED // REJECTED
if (row.status == {{ StockStatus.REJECTED }}) { if (row.status == {{ StockStatus.REJECTED }}) {
html += makeIconBadge('fa-times-circle icon-red', '{% trans "Stock item has been rejected" %}'); html += makeIconBadge('fa-times-circle icon-red', '{% trans "Stock item has been rejected" %}');
} } else if (row.status == {{ StockStatus.LOST }}) {
html += makeIconBadge('fa-question-circle', '{% trans "Stock item is lost" %}');
// 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" %}'); html += makeIconBadge('fa-skull-crossbones', '{% trans "Stock item is destroyed" %}');
} }
@ -834,51 +905,61 @@ function loadStockTable(table, options) {
return html; return html;
} }
}; };
if (!options.params.ordering) { if (!options.params.ordering) {
col['sortable'] = true; col['sortable'] = true;
}; }
columns.push(col); columns.push(col);
col = { col = {
field: 'status', field: 'status',
title: '{% trans "Status" %}', title: '{% trans "Status" %}',
formatter: function(value, row, index, field) { formatter: function(value) {
return stockStatusDisplay(value); return stockStatusDisplay(value);
}, },
}; };
if (!options.params.ordering) { if (!options.params.ordering) {
col['sortable'] = true; col['sortable'] = true;
}; }
columns.push(col); columns.push(col);
col = { col = {
field: 'batch', field: 'batch',
title: '{% trans "Batch" %}', title: '{% trans "Batch" %}',
}; };
if (!options.params.ordering) { if (!options.params.ordering) {
col['sortable'] = true; col['sortable'] = true;
}; }
columns.push(col); columns.push(col);
col = { col = {
field: 'location_detail.pathstring', field: 'location_detail.pathstring',
title: '{% trans "Location" %}', title: '{% trans "Location" %}',
formatter: function(value, row, index, field) { formatter: function(value, row) {
return locationDetail(row); return locationDetail(row);
} }
}; };
if (!options.params.ordering) { if (!options.params.ordering) {
col['sortable'] = true; col['sortable'] = true;
}; }
columns.push(col); columns.push(col);
col = { col = {
field: 'stocktake_date', field: 'stocktake_date',
title: '{% trans "Stocktake" %}', title: '{% trans "Stocktake" %}',
}; };
if (!options.params.ordering) { if (!options.params.ordering) {
col['sortable'] = true; col['sortable'] = true;
}; }
columns.push(col); columns.push(col);
col = { col = {
@ -887,18 +968,22 @@ function loadStockTable(table, options) {
visible: global_settings.STOCK_ENABLE_EXPIRY, visible: global_settings.STOCK_ENABLE_EXPIRY,
switchable: global_settings.STOCK_ENABLE_EXPIRY, switchable: global_settings.STOCK_ENABLE_EXPIRY,
}; };
if (!options.params.ordering) { if (!options.params.ordering) {
col['sortable'] = true; col['sortable'] = true;
}; }
columns.push(col); columns.push(col);
col = { col = {
field: 'updated', field: 'updated',
title: '{% trans "Last Updated" %}', title: '{% trans "Last Updated" %}',
}; };
if (!options.params.ordering) { if (!options.params.ordering) {
col['sortable'] = true; col['sortable'] = true;
}; }
columns.push(col); columns.push(col);
columns.push({ columns.push({
@ -921,8 +1006,10 @@ function loadStockTable(table, options) {
return renderLink(text, link); return renderLink(text, link);
} }
}, });
{
col = {
field: 'supplier_part', field: 'supplier_part',
title: '{% trans "Supplier Part" %}', title: '{% trans "Supplier Part" %}',
visible: params['supplier_part_detail'] || false, visible: params['supplier_part_detail'] || false,
@ -944,15 +1031,25 @@ function loadStockTable(table, options) {
return renderLink(text, link); return renderLink(text, link);
} }
}); };
if (!options.params.ordering) {
col.sortable = true;
col.sortName = 'SKU';
}
columns.push(col);
col = { col = {
field: 'purchase_price_string', field: 'purchase_price_string',
title: '{% trans "Purchase Price" %}', title: '{% trans "Purchase Price" %}',
}; };
if (!options.params.ordering) { if (!options.params.ordering) {
col['sortable'] = true; col.sortable = true;
}; col.sortName = 'purchase_price';
}
columns.push(col); columns.push(col);
columns.push({ columns.push({
@ -969,7 +1066,7 @@ function loadStockTable(table, options) {
formatNoMatches: function() { formatNoMatches: function() {
return '{% trans "No stock items matching query" %}'; return '{% trans "No stock items matching query" %}';
}, },
url: options.url || "{% url 'api-stock-list' %}", url: options.url || '{% url "api-stock-list" %}',
queryParams: filters, queryParams: filters,
sidePagination: 'server', sidePagination: 'server',
name: 'stock', name: 'stock',
@ -1036,7 +1133,7 @@ function loadStockTable(table, options) {
stock = +stock.toFixed(5); stock = +stock.toFixed(5);
return stock + " (" + items + " items)"; return `${stock} (${items} {% trans "items" %})`;
} else if (field == 'status') { } else if (field == 'status') {
var statii = []; var statii = [];
@ -1158,7 +1255,7 @@ function loadStockTable(table, options) {
function stockAdjustment(action) { function stockAdjustment(action) {
var items = $(table).bootstrapTable("getSelections"); var items = $(table).bootstrapTable('getSelections');
adjustStock(action, items, { adjustStock(action, items, {
onSuccess: function() { onSuccess: function() {
@ -1191,7 +1288,7 @@ function loadStockTable(table, options) {
}); });
printTestReports(items); printTestReports(items);
}) });
if (global_settings.BARCODE_ENABLE) { if (global_settings.BARCODE_ENABLE) {
$('#multi-item-barcode-scan-into-location').click(function() { $('#multi-item-barcode-scan-into-location').click(function() {
@ -1201,7 +1298,7 @@ function loadStockTable(table, options) {
selections.forEach(function(item) { selections.forEach(function(item) {
items.push(item.pk); items.push(item.pk);
}) });
scanItemsIntoLocation(items); scanItemsIntoLocation(items);
}); });
@ -1219,12 +1316,12 @@ function loadStockTable(table, options) {
stockAdjustment('add'); stockAdjustment('add');
}); });
$("#multi-item-move").click(function() { $('#multi-item-move').click(function() {
stockAdjustment('move'); stockAdjustment('move');
}); });
$("#multi-item-order").click(function() { $('#multi-item-order').click(function() {
var selections = $(table).bootstrapTable("getSelections"); var selections = $(table).bootstrapTable('getSelections');
var stock = []; var stock = [];
@ -1232,14 +1329,14 @@ function loadStockTable(table, options) {
stock.push(item.pk); stock.push(item.pk);
}); });
launchModalForm("/order/purchase-order/order-parts/", { launchModalForm('/order/purchase-order/order-parts/', {
data: { data: {
stock: stock, stock: stock,
}, },
}); });
}); });
$("#multi-item-set-status").click(function() { $('#multi-item-set-status').click(function() {
// Select and set the STATUS field for selected stock items // Select and set the STATUS field for selected stock items
var selections = $(table).bootstrapTable('getSelections'); var selections = $(table).bootstrapTable('getSelections');
@ -1251,7 +1348,7 @@ function loadStockTable(table, options) {
function(item) { function(item) {
return item.text; return item.text;
}, },
function (item) { function(item) {
return item.key; return item.key;
} }
); );
@ -1323,11 +1420,11 @@ function loadStockTable(table, options) {
$.when.apply($, requests).done(function() { $.when.apply($, requests).done(function() {
$(table).bootstrapTable('refresh'); $(table).bootstrapTable('refresh');
}); });
}) });
}); });
$("#multi-item-delete").click(function() { $('#multi-item-delete').click(function() {
var selections = $(table).bootstrapTable("getSelections"); var selections = $(table).bootstrapTable('getSelections');
var stock = []; var stock = [];
@ -1356,8 +1453,8 @@ function loadStockLocationTable(table, options) {
var original = {}; var original = {};
for (var key in params) { for (var k in params) {
original[key] = params[key]; original[k] = params[k];
} }
setupFilterList(filterKey, table, filterListElement); setupFilterList(filterKey, table, filterListElement);
@ -1425,7 +1522,7 @@ function loadStockTrackingTable(table, options) {
field: 'date', field: 'date',
title: '{% trans "Date" %}', title: '{% trans "Date" %}',
sortable: true, sortable: true,
formatter: function(value, row, index, field) { formatter: function(value) {
var m = moment(value); var m = moment(value);
if (m.isValid()) { if (m.isValid()) {
@ -1441,11 +1538,11 @@ function loadStockTrackingTable(table, options) {
cols.push({ cols.push({
field: 'label', field: 'label',
title: '{% trans "Description" %}', title: '{% trans "Description" %}',
formatter: function(value, row, index, field) { formatter: function(value, row) {
var html = "<b>" + value + "</b>"; var html = '<b>' + value + '</b>';
if (row.notes) { if (row.notes) {
html += "<br><i>" + row.notes + "</i>"; html += '<br><i>' + row.notes + '</i>';
} }
return html; return html;
@ -1456,7 +1553,7 @@ function loadStockTrackingTable(table, options) {
cols.push({ cols.push({
field: 'deltas', field: 'deltas',
title: '{% trans "Details" %}', 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}'>`; var html = `<table class='table table-condensed' id='tracking-table-${row.pk}'>`;
if (!details) { if (!details) {
@ -1591,14 +1688,11 @@ function loadStockTrackingTable(table, options) {
cols.push({ cols.push({
field: 'user', field: 'user',
title: '{% trans "User" %}', title: '{% trans "User" %}',
formatter: function(value, row, index, field) { formatter: function(value, row) {
if (value) if (value) {
{
// TODO - Format the user's first and last names // TODO - Format the user's first and last names
return row.user_detail.username; return row.user_detail.username;
} } else {
else
{
return `<i>{% trans "No user information" %}</i>`; return `<i>{% trans "No user information" %}</i>`;
} }
} }
@ -1680,7 +1774,7 @@ function createNewStockItem(options) {
reloadFieldOptions( reloadFieldOptions(
'supplier_part', 'supplier_part',
{ {
url: "{% url 'api-supplier-part-list' %}", url: '{% url "api-supplier-part-list" %}',
params: { params: {
part: value, part: value,
pretty: true, pretty: true,
@ -1711,7 +1805,7 @@ function createNewStockItem(options) {
} else { } else {
var expiry = moment().add(response.default_expiry, 'days'); 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. * 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({ table.inventreeTable({
url: "{% url 'api-stock-list' %}", url: '{% url "api-stock-list" %}',
queryParams: { queryParams: {
installed_in: options.stock_item, installed_in: options.stock_item,
part_detail: true, part_detail: true,
@ -1790,7 +1864,7 @@ function loadInstalledInTable(table, options) {
{ {
field: 'status', field: 'status',
title: '{% trans "Status" %}', title: '{% trans "Status" %}',
formatter: function(value, row) { formatter: function(value) {
return stockStatusDisplay(value); return stockStatusDisplay(value);
} }
}, },
@ -1829,8 +1903,8 @@ function loadInstalledInTable(table, options) {
table.bootstrapTable('refresh'); 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='purchaseOrder' options=PurchaseOrderStatus.list %}
{% include "status_codes.html" with label='salesOrder' options=SalesOrderStatus.list %} {% include "status_codes.html" with label='salesOrder' options=SalesOrderStatus.list %}
/* globals
global_settings
*/
/* exported
buildStatusDisplay,
getAvailableTableFilters,
purchaseOrderStatusDisplay,
salesOrderStatusDisplay,
stockHistoryStatusDisplay,
stockStatusDisplay,
*/
function getAvailableTableFilters(tableKey) { function getAvailableTableFilters(tableKey) {
tableKey = tableKey.toLowerCase(); tableKey = tableKey.toLowerCase();
// Filters for "variant" table // Filters for "variant" table
if (tableKey == "variants") { if (tableKey == 'variants') {
return { return {
active: { active: {
type: 'bool', type: 'bool',
@ -36,11 +49,11 @@ function getAvailableTableFilters(tableKey) {
} }
// Filters for Bill of Materials table // Filters for Bill of Materials table
if (tableKey == "bom") { if (tableKey == 'bom') {
return { return {
sub_part_trackable: { sub_part_trackable: {
type: 'bool', type: 'bool',
title: '{% trans "Trackable Part" %}' title: '{% trans "Trackable Part" %}',
}, },
sub_part_assembly: { sub_part_assembly: {
type: 'bool', type: 'bool',
@ -57,7 +70,7 @@ function getAvailableTableFilters(tableKey) {
allow_variants: { allow_variants: {
type: 'bool', type: 'bool',
title: '{% trans "Allow Variant Stock" %}', title: '{% trans "Allow Variant Stock" %}',
} },
}; };
} }
@ -72,29 +85,29 @@ function getAvailableTableFilters(tableKey) {
} }
// Filters for "stock location" table // Filters for "stock location" table
if (tableKey == "location") { if (tableKey == 'location') {
return { return {
cascade: { cascade: {
type: 'bool', type: 'bool',
title: '{% trans "Include sublocations" %}', title: '{% trans "Include sublocations" %}',
description: '{% trans "Include locations" %}', description: '{% trans "Include locations" %}',
} },
}; };
} }
// Filters for "part category" table // Filters for "part category" table
if (tableKey == "category") { if (tableKey == 'category') {
return { return {
cascade: { cascade: {
type: 'bool', type: 'bool',
title: '{% trans "Include subcategories" %}', title: '{% trans "Include subcategories" %}',
description: '{% trans "Include subcategories" %}', description: '{% trans "Include subcategories" %}',
} },
}; };
} }
// Filters for the "customer stock" table (really a subset of "stock") // Filters for the "customer stock" table (really a subset of "stock")
if (tableKey == "customerstock") { if (tableKey == 'customerstock') {
return { return {
serialized: { serialized: {
type: 'bool', type: 'bool',
@ -102,7 +115,7 @@ function getAvailableTableFilters(tableKey) {
}, },
serial_gte: { serial_gte: {
title: '{% trans "Serial number 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: { serial_lte: {
title: '{% trans "Serial number LTE" %}', title: '{% trans "Serial number LTE" %}',
@ -110,7 +123,7 @@ function getAvailableTableFilters(tableKey) {
}, },
serial: { serial: {
title: '{% trans "Serial number" %}', title: '{% trans "Serial number" %}',
description: '{% trans "Serial number" %}' description: '{% trans "Serial number" %}',
}, },
batch: { batch: {
title: '{% trans "Batch" %}', title: '{% trans "Batch" %}',
@ -179,11 +192,11 @@ function getAvailableTableFilters(tableKey) {
}, },
serial: { serial: {
title: '{% trans "Serial number" %}', title: '{% trans "Serial number" %}',
description: '{% trans "Serial number" %}' description: '{% trans "Serial number" %}',
}, },
serial_gte: { serial_gte: {
title: '{% trans "Serial number 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: { serial_lte: {
title: '{% trans "Serial number LTE" %}', title: '{% trans "Serial number LTE" %}',
@ -239,7 +252,7 @@ function getAvailableTableFilters(tableKey) {
required: { required: {
type: 'bool', type: 'bool',
title: '{% trans "Required" %}', title: '{% trans "Required" %}',
} },
}; };
} }
@ -262,7 +275,7 @@ function getAvailableTableFilters(tableKey) {
} }
// Filters for the "Order" table // Filters for the "Order" table
if (tableKey == "purchaseorder") { if (tableKey == 'purchaseorder') {
return { return {
status: { status: {
@ -280,7 +293,7 @@ function getAvailableTableFilters(tableKey) {
}; };
} }
if (tableKey == "salesorder") { if (tableKey == 'salesorder') {
return { return {
status: { status: {
title: '{% trans "Order status" %}', title: '{% trans "Order status" %}',
@ -302,12 +315,12 @@ function getAvailableTableFilters(tableKey) {
active: { active: {
type: 'bool', type: 'bool',
title: '{% trans "Active parts" %}', title: '{% trans "Active parts" %}',
} },
}; };
} }
// Filters for the "Parts" table // Filters for the "Parts" table
if (tableKey == "parts") { if (tableKey == 'parts') {
return { return {
cascade: { cascade: {
type: 'bool', type: 'bool',
@ -330,7 +343,7 @@ function getAvailableTableFilters(tableKey) {
}, },
has_stock: { has_stock: {
type: 'bool', type: 'bool',
title: '{% trans "Stock available" %}' title: '{% trans "Stock available" %}',
}, },
low_stock: { low_stock: {
type: 'bool', type: 'bool',

View File

@ -1,44 +1,33 @@
{% load i18n %} {% load i18n %}
/* global
inventreeLoad,
inventreeSave,
*/
function tdWrap(html, options={}) { /* exported
/* Wraps provided html in <td>..</td> elements customGroupSorter,
*/ reloadtable,
renderLink,
var colspan = ''; reloadTableFilters,
*/
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>`;
}
/**
* Reload a named table
* @param table
*/
function reloadtable(table) { function reloadtable(table) {
$(table).bootstrapTable('refresh'); $(table).bootstrapTable('refresh');
} }
function editButton(url, text='Edit') { /**
return "<button class='btn btn-success edit-button btn-sm' type='button' url='" + url + "'>" + text + "</button>"; * Render a URL for display
} * @param {String} text
* @param {String} url
* @param {object} options
function deleteButton(url, text='Delete') { * @returns link text
return "<button class='btn btn-danger delete-button btn-sm' type='button' url='" + url + "'>" + text + "</button>"; */
}
function renderLink(text, url, options={}) { function renderLink(text, url, options={}) {
if (url === null || url === undefined || url === '') { if (url === null || url === undefined || url === '') {
return text; return text;
@ -46,8 +35,6 @@ function renderLink(text, url, options={}) {
var max_length = options.max_length || -1; var max_length = options.max_length || -1;
var remove_http = options.remove_http || false;
// Shorten the displayed length if required // Shorten the displayed length if required
if ((max_length > 0) && (text.length > max_length)) { if ((max_length > 0) && (text.length > max_length)) {
var slice_length = (max_length - 3) / 2; var slice_length = (max_length - 3) / 2;
@ -82,12 +69,17 @@ function linkButtonsToSelection(table, buttons) {
enableButtons(buttons, table.bootstrapTable('getSelections').length > 0); enableButtons(buttons, table.bootstrapTable('getSelections').length > 0);
// Add a callback // 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); enableButtons(buttons, table.bootstrapTable('getSelections').length > 0);
}); });
} }
/**
* Returns true if the input looks like a valid number
* @param {String} n
* @returns
*/
function isNumeric(n) { function isNumeric(n) {
return !isNaN(parseFloat(n)) && isFinite(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 // Construct a new list of filters to use for the query
var params = {}; var params = {};
for (var key in filters) { for (var k in filters) {
params[key] = filters[key]; params[k] = filters[k];
} }
// Original query params will override // Original query params will override
@ -159,7 +151,6 @@ function convertQueryParameters(params, filters) {
var ordering = params['sort'] || null; var ordering = params['sort'] || null;
if (ordering) { if (ordering) {
if (order == 'desc') { if (order == 'desc') {
ordering = `-${ordering}`; ordering = `-${ordering}`;
} }
@ -243,7 +234,7 @@ $.fn.inventreeTable = function(options) {
}; };
// Callback when a column is changed // Callback when a column is changed
options.onColumnSwitch = function(field, checked) { options.onColumnSwitch = function() {
var columns = table.bootstrapTable('getVisibleColumns'); var columns = table.bootstrapTable('getVisibleColumns');
@ -262,7 +253,7 @@ $.fn.inventreeTable = function(options) {
// If a set of visible columns has been saved, load! // If a set of visible columns has been saved, load!
if (visibleColumns) { if (visibleColumns) {
var columns = visibleColumns.split(","); var columns = visibleColumns.split(',');
// Which columns are currently visible? // Which columns are currently visible?
var visible = table.bootstrapTable('getVisibleColumns'); var visible = table.bootstrapTable('getVisibleColumns');
@ -276,7 +267,7 @@ $.fn.inventreeTable = function(options) {
} }
}); });
} else { } 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) { if (options.buttons) {
linkButtonsToSelection(table, options.buttons); linkButtonsToSelection(table, options.buttons);
} }
} };
function customGroupSorter(sortName, sortOrder, sortData) { function customGroupSorter(sortName, sortOrder, sortData) {
@ -357,42 +349,42 @@ function customGroupSorter(sortName, sortOrder, sortData) {
} }
// Expose default bootstrap table string literals to translation layer // Expose default bootstrap table string literals to translation layer
(function ($) { (function($) {
'use strict'; 'use strict';
$.fn.bootstrapTable.locales['en-US-custom'] = { $.fn.bootstrapTable.locales['en-US-custom'] = {
formatLoadingMessage: function () { formatLoadingMessage: function() {
return '{% trans "Loading data" %}'; return '{% trans "Loading data" %}';
}, },
formatRecordsPerPage: function (pageNumber) { formatRecordsPerPage: function(pageNumber) {
return `${pageNumber} {% trans "rows per page" %}`; 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" %}`; return `{% trans "Showing" %} ${pageFrom} {% trans "to" %} ${pageTo} {% trans "of" %} ${totalRows} {% trans "rows" %}`;
}, },
formatSearch: function () { formatSearch: function() {
return '{% trans "Search" %}'; return '{% trans "Search" %}';
}, },
formatNoMatches: function () { formatNoMatches: function() {
return '{% trans "No matching results" %}'; return '{% trans "No matching results" %}';
}, },
formatPaginationSwitch: function () { formatPaginationSwitch: function() {
return '{% trans "Hide/Show pagination" %}'; return '{% trans "Hide/Show pagination" %}';
}, },
formatRefresh: function () { formatRefresh: function() {
return '{% trans "Refresh" %}'; return '{% trans "Refresh" %}';
}, },
formatToggle: function () { formatToggle: function() {
return '{% trans "Toggle" %}'; return '{% trans "Toggle" %}';
}, },
formatColumns: function () { formatColumns: function() {
return '{% trans "Columns" %}'; return '{% trans "Columns" %}';
}, },
formatAllRows: function () { formatAllRows: function() {
return '{% trans "All" %}'; return '{% trans "All" %}';
} },
}; };
$.extend($.fn.bootstrapTable.defaults, $.fn.bootstrapTable.locales['en-US-custom']); $.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"> <button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span> <span aria-hidden="true">&times;</span>
</button> </button>
<h3 id='modal-title'><i>Form Title Here</i></h3> <h3 id='modal-title'><em>Form Title Here</em></h3>
</div> </div>
<div class='modal-form-content-wrapper'> <div class='modal-form-content-wrapper'>
<div class='alert alert-block alert-danger' id='form-validation-warning' style='display: none;'> <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"> <button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span> <span aria-hidden="true">&times;</span>
</button> </button>
<h3 id='modal-title'><i>Form Title Here</i></h3> <h3 id='modal-title'><em>Form Title Here</em></h3>
</div> </div>
<div class='modal-form-content-wrapper'> <div class='modal-form-content-wrapper'>
<div class='alert alert-block alert-danger' id='form-validation-warning' style="display: none;"> <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> <span class='fas fa-info-circle icon-green'></span>
{% endif %} {% endif %}
{% 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'> <ul class='dropdown-menu'>
{% if user.is_authenticated %} {% if user.is_authenticated %}
{% if user.is_staff %} {% if user.is_staff %}

View File

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

View File

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

View File

@ -1,12 +1,13 @@
/* /*
* Status codes for the {{ label }} model. * Status codes for the {{ label }} model.
*/ */
var {{ label }}Codes = { const {{ label }}Codes = {
{% for opt in options %}'{{ opt.key }}': { {% for opt in options %}'{{ opt.key }}': {
key: '{{ opt.key }}', key: '{{ opt.key }}',
value: '{{ opt.value }}',{% if opt.color %} value: '{{ opt.value }}',{% if opt.color %}
label: 'label-{{ opt.color }}',{% endif %} 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. 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 # 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) 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")