mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +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:
commit
125554c53f
25
.eslintrc.yml
Normal file
25
.eslintrc.yml
Normal 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
31
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal 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"
|
26
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
26
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal 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
54
.github/workflows/html.yaml
vendored
Normal 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
|
||||
|
26
.github/workflows/javascript.yaml
vendored
26
.github/workflows/javascript.yaml
vendored
@ -18,11 +18,33 @@ jobs:
|
||||
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
INVENTREE_DB_ENGINE: sqlite3
|
||||
INVENTREE_DB_NAME: inventree
|
||||
INVENTREE_MEDIA_ROOT: ./media
|
||||
INVENTREE_STATIC_ROOT: ./static
|
||||
steps:
|
||||
- name: Install node.js
|
||||
uses: actions/setup-node@v2
|
||||
- run: npm install
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v2
|
||||
- name: Check Files
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: 3.7
|
||||
- name: Install Dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install gettext
|
||||
pip3 install invoke
|
||||
invoke install
|
||||
invoke static
|
||||
- name: Check Templated Files
|
||||
run: |
|
||||
cd ci
|
||||
python check_js_templates.py
|
||||
|
||||
- name: Lint Javascript Files
|
||||
run: |
|
||||
npm install eslint eslint-config-google
|
||||
invoke render-js-files
|
||||
npx eslint js_tmp/*.js
|
8
.gitignore
vendored
8
.gitignore
vendored
@ -67,8 +67,16 @@ secret_key.txt
|
||||
.coverage
|
||||
htmlcov/
|
||||
|
||||
# Temporary javascript files (used for testing)
|
||||
js_tmp/
|
||||
|
||||
# Development files
|
||||
dev/
|
||||
|
||||
# Locale stats file
|
||||
locale_stats.json
|
||||
|
||||
# node.js
|
||||
package-lock.json
|
||||
package.json
|
||||
node_modules/
|
100
InvenTree/InvenTree/ci_render_js.py
Normal file
100
InvenTree/InvenTree/ci_render_js.py
Normal 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.")
|
51
InvenTree/InvenTree/filters.py
Normal file
51
InvenTree/InvenTree/filters.py
Normal 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
|
@ -10,6 +10,8 @@ import os
|
||||
|
||||
from decimal import Decimal
|
||||
|
||||
from collections import OrderedDict
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import User
|
||||
from django.core.exceptions import ValidationError as DjangoValidationError
|
||||
@ -94,9 +96,14 @@ class InvenTreeModelSerializer(serializers.ModelSerializer):
|
||||
|
||||
# If instance is None, we are creating a new instance
|
||||
if instance is None and data is not empty:
|
||||
|
||||
# Required to side-step immutability of a QueryDict
|
||||
data = data.copy()
|
||||
|
||||
if data is None:
|
||||
data = OrderedDict()
|
||||
else:
|
||||
new_data = OrderedDict()
|
||||
new_data.update(data)
|
||||
|
||||
data = new_data
|
||||
|
||||
# Add missing fields which have default values
|
||||
ModelClass = self.Meta.model
|
||||
|
@ -111,6 +111,7 @@ translated_javascript_urls = [
|
||||
url(r'^company.js', DynamicJsView.as_view(template_name='js/translated/company.js'), name='company.js'),
|
||||
url(r'^filters.js', DynamicJsView.as_view(template_name='js/translated/filters.js'), name='filters.js'),
|
||||
url(r'^forms.js', DynamicJsView.as_view(template_name='js/translated/forms.js'), name='forms.js'),
|
||||
url(r'^helpers.js', DynamicJsView.as_view(template_name='js/translated/helpers.js'), name='helpers.js'),
|
||||
url(r'^label.js', DynamicJsView.as_view(template_name='js/translated/label.js'), name='label.js'),
|
||||
url(r'^model_renderers.js', DynamicJsView.as_view(template_name='js/translated/model_renderers.js'), name='model_renderers.js'),
|
||||
url(r'^modals.js', DynamicJsView.as_view(template_name='js/translated/modals.js'), name='modals.js'),
|
||||
|
@ -10,11 +10,15 @@ import common.models
|
||||
|
||||
INVENTREE_SW_VERSION = "0.5.0 pre"
|
||||
|
||||
INVENTREE_API_VERSION = 10
|
||||
INVENTREE_API_VERSION = 11
|
||||
|
||||
"""
|
||||
Increment this API version number whenever there is a significant change to the API that any clients need to know about
|
||||
|
||||
v11 -> 2021-08-26
|
||||
- Adds "units" field to PartBriefSerializer
|
||||
- This allows units to be introspected from the "part_detail" field in the StockItem serializer
|
||||
|
||||
v10 -> 2021-08-23
|
||||
- Adds "purchase_price_currency" to StockItem serializer
|
||||
- Adds "purchase_price_string" to StockItem serializer
|
||||
|
@ -6,7 +6,7 @@
|
||||
{{ block.super }}
|
||||
|
||||
<div class='alert alert-block alert-info'>
|
||||
<b>{% trans "Automatically Allocate Stock" %}</b><br>
|
||||
<strong>{% trans "Automatically Allocate Stock" %}</strong><br>
|
||||
{% trans "The following stock items will be allocated to the specified build output" %}
|
||||
</div>
|
||||
{% if allocations %}
|
||||
@ -24,7 +24,7 @@
|
||||
</td>
|
||||
<td>
|
||||
{{ item.stock_item.part.full_name }}<br>
|
||||
<i>{{ item.stock_item.part.description }}</i>
|
||||
<em>{{ item.stock_item.part.description }}</em>
|
||||
</td>
|
||||
<td>{% decimal item.quantity %}</td>
|
||||
<td>{{ item.stock_item.location }}</td>
|
||||
|
@ -9,7 +9,7 @@
|
||||
</div>
|
||||
{% else %}
|
||||
<div class='alert alert-block alert-danger'>
|
||||
<b>{% trans "Build Order is incomplete" %}</b><br>
|
||||
<strong>{% trans "Build Order is incomplete" %}</strong><br>
|
||||
<ul>
|
||||
{% if build.incomplete_count > 0 %}
|
||||
<li>{% trans "Incompleted build outputs remain" %}</li>
|
||||
|
@ -8,7 +8,7 @@
|
||||
</p>
|
||||
{% if output %}
|
||||
<p>
|
||||
{% blocktrans %}The allocated stock will be installed into the following build output:<br><i>{{output}}</i>{% endblocktrans %}
|
||||
{% blocktrans %}The allocated stock will be installed into the following build output:<br><em>{{output}}</em>{% endblocktrans %}
|
||||
</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
@ -40,7 +40,7 @@
|
||||
{% if build.take_from %}
|
||||
<a href="{% url 'stock-location-detail' build.take_from.id %}">{{ build.take_from }}</a>{% include "clip.html"%}
|
||||
{% else %}
|
||||
<i>{% trans "Stock can be taken from any available location." %}</i>
|
||||
<em>{% trans "Stock can be taken from any available location." %}</em>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
@ -53,7 +53,7 @@
|
||||
{{ build.destination }}
|
||||
</a>{% include "clip.html"%}
|
||||
{% else %}
|
||||
<i>{% trans "Destination location not specified" %}</i>
|
||||
<em>{% trans "Destination location not specified" %}</em>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
@ -127,7 +127,7 @@
|
||||
{{ build.target_date }}{% if build.is_overdue %} <span class='fas fa-calendar-times icon-red'></span>{% endif %}
|
||||
</td>
|
||||
{% else %}
|
||||
<td><i>{% trans "No target date set" %}</i></td>
|
||||
<td><em>{% trans "No target date set" %}</em></td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
<tr>
|
||||
@ -136,7 +136,7 @@
|
||||
{% if build.completion_date %}
|
||||
<td>{{ build.completion_date }}{% if build.completed_by %}<span class='badge'>{{ build.completed_by }}</span>{% endif %}</td>
|
||||
{% else %}
|
||||
<td><i>{% trans "Build not complete" %}</i></td>
|
||||
<td><em>{% trans "Build not complete" %}</em></td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
</table>
|
||||
@ -222,7 +222,7 @@
|
||||
</div>
|
||||
{% else %}
|
||||
<div class='alert alert-block alert-info'>
|
||||
<b>{% trans "Create a new build output" %}</b><br>
|
||||
<strong>{% trans "Create a new build output" %}</strong><br>
|
||||
{% trans "No incomplete build outputs remain." %}<br>
|
||||
{% trans "Create a new build output using the button above" %}
|
||||
</div>
|
||||
|
@ -6,9 +6,9 @@
|
||||
{{ block.super }}
|
||||
<!--
|
||||
<p>
|
||||
<b>{{ name }}</b><br>
|
||||
<strong>{{ name }}</strong><br>
|
||||
{{ description }}<br>
|
||||
<i>{% trans "Current value" %}: {{ value }}</i>
|
||||
<em>{% trans "Current value" %}: {{ value }}</em>
|
||||
</p>
|
||||
-->
|
||||
{% endblock %}
|
@ -78,7 +78,7 @@
|
||||
{% if company.currency %}
|
||||
{{ company.currency }}
|
||||
{% else %}
|
||||
<i>{% trans "Uses default currency" %}</i>
|
||||
<em>{% trans "Uses default currency" %}</em>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
|
@ -225,7 +225,7 @@ $("#multi-parameter-delete").click(function() {
|
||||
<ul>`;
|
||||
|
||||
selections.forEach(function(item) {
|
||||
text += `<li>${item.name} - <i>${item.value}</i></li>`;
|
||||
text += `<li>${item.name} - <em>${item.value}</em></li>`;
|
||||
});
|
||||
|
||||
text += `
|
||||
|
@ -9,13 +9,14 @@ from django.utils.translation import ugettext_lazy as _
|
||||
from django.conf.urls import url, include
|
||||
from django.db import transaction
|
||||
|
||||
from django_filters.rest_framework import DjangoFilterBackend
|
||||
from django_filters import rest_framework as rest_filters
|
||||
from rest_framework import generics
|
||||
from rest_framework import filters, status
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.serializers import ValidationError
|
||||
|
||||
|
||||
from InvenTree.filters import InvenTreeOrderingFilter
|
||||
from InvenTree.helpers import str2bool
|
||||
from InvenTree.api import AttachmentMixin
|
||||
from InvenTree.status_codes import PurchaseOrderStatus, SalesOrderStatus
|
||||
@ -149,7 +150,7 @@ class POList(generics.ListCreateAPIView):
|
||||
return queryset
|
||||
|
||||
filter_backends = [
|
||||
DjangoFilterBackend,
|
||||
rest_filters.DjangoFilterBackend,
|
||||
filters.SearchFilter,
|
||||
filters.OrderingFilter,
|
||||
]
|
||||
@ -323,6 +324,14 @@ class POLineItemList(generics.ListCreateAPIView):
|
||||
queryset = PurchaseOrderLineItem.objects.all()
|
||||
serializer_class = POLineItemSerializer
|
||||
|
||||
def get_queryset(self, *args, **kwargs):
|
||||
|
||||
queryset = super().get_queryset(*args, **kwargs)
|
||||
|
||||
queryset = POLineItemSerializer.annotate_queryset(queryset)
|
||||
|
||||
return queryset
|
||||
|
||||
def get_serializer(self, *args, **kwargs):
|
||||
|
||||
try:
|
||||
@ -335,18 +344,26 @@ class POLineItemList(generics.ListCreateAPIView):
|
||||
return self.serializer_class(*args, **kwargs)
|
||||
|
||||
filter_backends = [
|
||||
DjangoFilterBackend,
|
||||
rest_filters.DjangoFilterBackend,
|
||||
filters.SearchFilter,
|
||||
filters.OrderingFilter
|
||||
InvenTreeOrderingFilter
|
||||
]
|
||||
|
||||
ordering_field_aliases = {
|
||||
'MPN': 'part__manufacturer_part__MPN',
|
||||
'SKU': 'part__SKU',
|
||||
'part_name': 'part__part__name',
|
||||
}
|
||||
|
||||
ordering_fields = [
|
||||
'part__part__name',
|
||||
'part__MPN',
|
||||
'part__SKU',
|
||||
'reference',
|
||||
'MPN',
|
||||
'part_name',
|
||||
'purchase_price',
|
||||
'quantity',
|
||||
'received',
|
||||
'reference',
|
||||
'SKU',
|
||||
'total_price',
|
||||
]
|
||||
|
||||
search_fields = [
|
||||
@ -371,6 +388,14 @@ class POLineItemDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||
queryset = PurchaseOrderLineItem.objects.all()
|
||||
serializer_class = POLineItemSerializer
|
||||
|
||||
def get_queryset(self):
|
||||
|
||||
queryset = super().get_queryset()
|
||||
|
||||
queryset = POLineItemSerializer.annotate_queryset(queryset)
|
||||
|
||||
return queryset
|
||||
|
||||
|
||||
class SOAttachmentList(generics.ListCreateAPIView, AttachmentMixin):
|
||||
"""
|
||||
@ -381,7 +406,7 @@ class SOAttachmentList(generics.ListCreateAPIView, AttachmentMixin):
|
||||
serializer_class = SOAttachmentSerializer
|
||||
|
||||
filter_backends = [
|
||||
DjangoFilterBackend,
|
||||
rest_filters.DjangoFilterBackend,
|
||||
]
|
||||
|
||||
filter_fields = [
|
||||
@ -505,7 +530,7 @@ class SOList(generics.ListCreateAPIView):
|
||||
return queryset
|
||||
|
||||
filter_backends = [
|
||||
DjangoFilterBackend,
|
||||
rest_filters.DjangoFilterBackend,
|
||||
filters.SearchFilter,
|
||||
filters.OrderingFilter,
|
||||
]
|
||||
@ -604,7 +629,7 @@ class SOLineItemList(generics.ListCreateAPIView):
|
||||
return queryset
|
||||
|
||||
filter_backends = [
|
||||
DjangoFilterBackend,
|
||||
rest_filters.DjangoFilterBackend,
|
||||
filters.SearchFilter,
|
||||
filters.OrderingFilter
|
||||
]
|
||||
@ -689,7 +714,7 @@ class SOAllocationList(generics.ListCreateAPIView):
|
||||
return queryset
|
||||
|
||||
filter_backends = [
|
||||
DjangoFilterBackend,
|
||||
rest_filters.DjangoFilterBackend,
|
||||
]
|
||||
|
||||
# Default filterable fields
|
||||
@ -707,7 +732,7 @@ class POAttachmentList(generics.ListCreateAPIView, AttachmentMixin):
|
||||
serializer_class = POAttachmentSerializer
|
||||
|
||||
filter_backends = [
|
||||
DjangoFilterBackend,
|
||||
rest_filters.DjangoFilterBackend,
|
||||
]
|
||||
|
||||
filter_fields = [
|
||||
|
@ -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'),
|
||||
),
|
||||
]
|
@ -767,7 +767,13 @@ class PurchaseOrderLineItem(OrderLineItem):
|
||||
help_text=_("Supplier part"),
|
||||
)
|
||||
|
||||
received = models.DecimalField(decimal_places=5, max_digits=15, default=0, verbose_name=_('Received'), help_text=_('Number of items received'))
|
||||
received = models.DecimalField(
|
||||
decimal_places=5,
|
||||
max_digits=15,
|
||||
default=0,
|
||||
verbose_name=_('Received'),
|
||||
help_text=_('Number of items received')
|
||||
)
|
||||
|
||||
purchase_price = InvenTreeModelMoneyField(
|
||||
max_digits=19,
|
||||
@ -778,7 +784,7 @@ class PurchaseOrderLineItem(OrderLineItem):
|
||||
)
|
||||
|
||||
destination = TreeForeignKey(
|
||||
'stock.StockLocation', on_delete=models.DO_NOTHING,
|
||||
'stock.StockLocation', on_delete=models.SET_NULL,
|
||||
verbose_name=_('Destination'),
|
||||
related_name='po_lines',
|
||||
blank=True, null=True,
|
||||
|
@ -7,8 +7,9 @@ from __future__ import unicode_literals
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from django.db import models
|
||||
from django.db.models import Case, When, Value
|
||||
from django.db.models import BooleanField
|
||||
from django.db.models import BooleanField, ExpressionWrapper, F
|
||||
|
||||
from rest_framework import serializers
|
||||
from rest_framework.serializers import ValidationError
|
||||
@ -116,6 +117,23 @@ class POSerializer(InvenTreeModelSerializer):
|
||||
|
||||
class POLineItemSerializer(InvenTreeModelSerializer):
|
||||
|
||||
@staticmethod
|
||||
def annotate_queryset(queryset):
|
||||
"""
|
||||
Add some extra annotations to this queryset:
|
||||
|
||||
- Total price = purchase_price * quantity
|
||||
"""
|
||||
|
||||
queryset = queryset.annotate(
|
||||
total_price=ExpressionWrapper(
|
||||
F('purchase_price') * F('quantity'),
|
||||
output_field=models.DecimalField()
|
||||
)
|
||||
)
|
||||
|
||||
return queryset
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
||||
part_detail = kwargs.pop('part_detail', False)
|
||||
@ -130,6 +148,8 @@ class POLineItemSerializer(InvenTreeModelSerializer):
|
||||
quantity = serializers.FloatField(default=1)
|
||||
received = serializers.FloatField(default=0)
|
||||
|
||||
total_price = serializers.FloatField(read_only=True)
|
||||
|
||||
part_detail = PartBriefSerializer(source='get_base_part', many=False, read_only=True)
|
||||
supplier_part_detail = SupplierPartSerializer(source='part', many=False, read_only=True)
|
||||
|
||||
@ -165,6 +185,7 @@ class POLineItemSerializer(InvenTreeModelSerializer):
|
||||
'purchase_price_string',
|
||||
'destination',
|
||||
'destination_detail',
|
||||
'total_price',
|
||||
]
|
||||
|
||||
|
||||
|
@ -57,7 +57,7 @@
|
||||
{% for duplicate in duplicates %}
|
||||
{% if duplicate == col.value %}
|
||||
<div class='alert alert-danger alert-block text-center' role='alert' style='padding:2px; margin-top:6px; margin-bottom:2px'>
|
||||
<b>{% trans "Duplicate selection" %}</b>
|
||||
<strong>{% trans "Duplicate selection" %}</strong>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
@ -38,7 +38,7 @@
|
||||
<tr id='part_row_{{ part.id }}'>
|
||||
<td>
|
||||
{% include "hover_image.html" with image=part.image hover=False %}
|
||||
{{ part.full_name }} <small><i>{{ part.description }}</i></small>
|
||||
{{ part.full_name }} <small><em>{{ part.description }}</em></small>
|
||||
</td>
|
||||
<td>
|
||||
<button class='btn btn-default btn-create' onClick='newSupplierPartFromOrderWizard()' id='new_supplier_part_{{ part.id }}' part='{{ part.pk }}' title='{% trans "Create new supplier part" %}' type='button'>
|
||||
@ -62,7 +62,7 @@
|
||||
</select>
|
||||
</div>
|
||||
{% if not part.order_supplier %}
|
||||
<span class='help-inline'>{% blocktrans with name=part.name %}Select a supplier for <i>{{name}}</i>{% endblocktrans %}</span>
|
||||
<span class='help-inline'>{% blocktrans with name=part.name %}Select a supplier for <em>{{name}}</em>{% endblocktrans %}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</td>
|
||||
|
@ -28,7 +28,7 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<table class='table table-striped table-condensed' id='po-table' data-toolbar='#order-toolbar-buttons'>
|
||||
<table class='table table-striped table-condensed' id='po-line-table' data-toolbar='#order-toolbar-buttons'>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
@ -208,13 +208,13 @@ $('#new-po-line').click(function() {
|
||||
{% endif %}
|
||||
|
||||
function reloadTable() {
|
||||
$("#po-table").bootstrapTable("refresh");
|
||||
$("#po-line-table").bootstrapTable("refresh");
|
||||
}
|
||||
|
||||
function setupCallbacks() {
|
||||
// Setup callbacks for the line buttons
|
||||
|
||||
var table = $("#po-table");
|
||||
var table = $("#po-line-table");
|
||||
|
||||
{% if order.status == PurchaseOrderStatus.PENDING %}
|
||||
table.find(".button-line-edit").click(function() {
|
||||
@ -273,9 +273,9 @@ function setupCallbacks() {
|
||||
|
||||
}
|
||||
|
||||
$("#po-table").inventreeTable({
|
||||
$("#po-line-table").inventreeTable({
|
||||
onPostBody: setupCallbacks,
|
||||
name: 'purchaseorder',
|
||||
name: 'purchaseorderlines',
|
||||
sidePagination: 'server',
|
||||
formatNoMatches: function() { return "{% trans 'No line items found' %}"; },
|
||||
queryParams: {
|
||||
@ -294,7 +294,7 @@ $("#po-table").inventreeTable({
|
||||
{
|
||||
field: 'part',
|
||||
sortable: true,
|
||||
sortName: 'part__part__name',
|
||||
sortName: 'part_name',
|
||||
title: '{% trans "Part" %}',
|
||||
switchable: false,
|
||||
formatter: function(value, row, index, field) {
|
||||
@ -314,7 +314,7 @@ $("#po-table").inventreeTable({
|
||||
},
|
||||
{
|
||||
sortable: true,
|
||||
sortName: 'part__SKU',
|
||||
sortName: 'SKU',
|
||||
field: 'supplier_part_detail.SKU',
|
||||
title: '{% trans "SKU" %}',
|
||||
formatter: function(value, row, index, field) {
|
||||
@ -327,7 +327,7 @@ $("#po-table").inventreeTable({
|
||||
},
|
||||
{
|
||||
sortable: true,
|
||||
sortName: 'part__MPN',
|
||||
sortName: 'MPN',
|
||||
field: 'supplier_part_detail.manufacturer_part_detail.MPN',
|
||||
title: '{% trans "MPN" %}',
|
||||
formatter: function(value, row, index, field) {
|
||||
@ -364,7 +364,9 @@ $("#po-table").inventreeTable({
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'total_price',
|
||||
sortable: true,
|
||||
field: 'total_price',
|
||||
title: '{% trans "Total price" %}',
|
||||
formatter: function(value, row) {
|
||||
var total = row.purchase_price * row.quantity;
|
||||
@ -383,7 +385,7 @@ $("#po-table").inventreeTable({
|
||||
}
|
||||
},
|
||||
{
|
||||
sortable: true,
|
||||
sortable: false,
|
||||
field: 'received',
|
||||
switchable: false,
|
||||
title: '{% trans "Received" %}',
|
||||
|
@ -5,7 +5,7 @@
|
||||
|
||||
{% block form %}
|
||||
|
||||
{% blocktrans with desc=order.description %}Receive outstanding parts for <b>{{order}}</b> - <i>{{desc}}</i>{% endblocktrans %}
|
||||
{% blocktrans with desc=order.description %}Receive outstanding parts for <strong>{{order}}</strong> - <em>{{desc}}</em>{% endblocktrans %}
|
||||
|
||||
<form method='post' action='' class='js-modal-form' enctype='multipart/form-data'>
|
||||
{% csrf_token %}
|
||||
|
@ -22,7 +22,7 @@
|
||||
{% endif %}
|
||||
|
||||
<div class='alert alert-block alert-info'>
|
||||
<b>{% trans "Sales Order" %} {{ order.reference }} - {{ order.customer.name }}</b>
|
||||
<strong>{% trans "Sales Order" %} {{ order.reference }} - {{ order.customer.name }}</strong>
|
||||
<br>
|
||||
{% trans "Shipping this order means that the order will no longer be editable." %}
|
||||
</div>
|
||||
|
@ -6,9 +6,9 @@
|
||||
<div class='alert alert-block alert-warning'>
|
||||
{% trans "This action will unallocate the following stock from the Sales Order" %}:
|
||||
<br>
|
||||
<b>
|
||||
<strong>
|
||||
{% decimal allocation.get_allocated %} x {{ allocation.line.part.full_name }}
|
||||
{% if allocation.item.location %} ({{ allocation.get_location }}){% endif %}
|
||||
</b>
|
||||
</strong>
|
||||
</div>
|
||||
{% endblock %}
|
@ -206,6 +206,7 @@ class PartBriefSerializer(InvenTreeModelSerializer):
|
||||
'stock',
|
||||
'trackable',
|
||||
'virtual',
|
||||
'units',
|
||||
]
|
||||
|
||||
|
||||
|
@ -11,13 +11,13 @@
|
||||
<div class='alert alert-block alert-info'>
|
||||
{% else %}
|
||||
<div class='alert alert-block alert-danger'>
|
||||
{% blocktrans with part=part.full_name %}The BOM for <i>{{ part }}</i> has changed, and must be validated.<br>{% endblocktrans %}
|
||||
{% blocktrans with part=part.full_name %}The BOM for <em>{{ part }}</em> has changed, and must be validated.<br>{% endblocktrans %}
|
||||
{% endif %}
|
||||
{% blocktrans with part=part.full_name checker=part.bom_checked_by check_date=part.bom_checked_date %}The BOM for <i>{{ part }}</i> was last checked by {{ checker }} on {{ check_date }}{% endblocktrans %}
|
||||
{% blocktrans with part=part.full_name checker=part.bom_checked_by check_date=part.bom_checked_date %}The BOM for <em>{{ part }}</em> was last checked by {{ checker }} on {{ check_date }}{% endblocktrans %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class='alert alert-danger alert-block'>
|
||||
<b>{% blocktrans with part=part.full_name %}The BOM for <i>{{ part }}</i> has not been validated.{% endblocktrans %}</b>
|
||||
<strong>{% blocktrans with part=part.full_name %}The BOM for <em>{{ part }}</em> has not been validated.{% endblocktrans %}</strong>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
|
@ -9,7 +9,7 @@
|
||||
|
||||
{% if part.has_bom %}
|
||||
<div class='alert alert-block alert-danger'>
|
||||
<b>{% trans "Warning" %}</b><br>
|
||||
<strong>{% trans "Warning" %}</strong><br>
|
||||
{% trans "This part already has a Bill of Materials" %}<br>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
@ -57,7 +57,7 @@
|
||||
{% for duplicate in duplicates %}
|
||||
{% if duplicate == col.value %}
|
||||
<div class='alert alert-danger alert-block text-center' role='alert' style='padding:2px; margin-top:6px; margin-bottom:2px'>
|
||||
<b>{% trans "Duplicate selection" %}</b>
|
||||
<strong>{% trans "Duplicate selection" %}</strong>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
@ -43,9 +43,9 @@
|
||||
|
||||
{% block form_alert %}
|
||||
<div class='alert alert-info alert-block'>
|
||||
<b>{% trans "Requirements for BOM upload" %}:</b>
|
||||
<strong>{% trans "Requirements for BOM upload" %}:</strong>
|
||||
<ul>
|
||||
<li>{% trans "The BOM file must contain the required named columns as provided in the " %} <b><a href="/part/bom_template/">{% trans "BOM Upload Template" %}</a></b></li>
|
||||
<li>{% trans "The BOM file must contain the required named columns as provided in the " %} <strong><a href="/part/bom_template/">{% trans "BOM Upload Template" %}</a></strong></li>
|
||||
<li>{% trans "Each part must already exist in the database" %}</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
@ -3,7 +3,7 @@
|
||||
{% load i18n %}
|
||||
|
||||
{% block pre_form_content %}
|
||||
{% blocktrans with part.full_name as part %}Confirm that the Bill of Materials (BOM) is valid for:<br><i>{{ part }}</i>{% endblocktrans %}
|
||||
{% blocktrans with part.full_name as part %}Confirm that the Bill of Materials (BOM) is valid for:<br><em>{{ part }}</em>{% endblocktrans %}
|
||||
|
||||
<div class='alert alert-warning alert-block'>
|
||||
{% trans 'This will validate each line in the BOM.' %}
|
||||
|
@ -8,13 +8,13 @@
|
||||
|
||||
{% if matches %}
|
||||
<div class='alert alert-block alert-warning'>
|
||||
<b>{% trans "Possible Matching Parts" %}</b>
|
||||
<strong>{% trans "Possible Matching Parts" %}</strong>
|
||||
<p>{% trans "The new part may be a duplicate of these existing parts" %}:</p>
|
||||
<ul class='list-group'>
|
||||
{% for match in matches %}
|
||||
<li class='list-group-item list-group-item-condensed'>
|
||||
{% decimal match.ratio as match_per %}
|
||||
{% blocktrans with full_name=match.part.full_name desc=match.part.description %}{{full_name}} - <i>{{desc}}</i> ({{match_per}}% match){% endblocktrans %}
|
||||
{% blocktrans with full_name=match.part.full_name desc=match.part.description %}{{full_name}} - <em>{{desc}}</em> ({{match_per}}% match){% endblocktrans %}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
@ -18,7 +18,7 @@
|
||||
<div class='panel-content'>
|
||||
{% if part.is_template %}
|
||||
<div class='alert alert-info alert-block'>
|
||||
{% blocktrans with full_name=part.full_name%}Showing stock for all variants of <i>{{full_name}}</i>{% endblocktrans %}
|
||||
{% blocktrans with full_name=part.full_name%}Showing stock for all variants of <em>{{full_name}}</em>{% endblocktrans %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% include "stock_table.html" %}
|
||||
|
@ -50,7 +50,7 @@
|
||||
{% for duplicate in duplicates %}
|
||||
{% if duplicate == col.value %}
|
||||
<div class='alert alert-danger alert-block text-center' role='alert' style='padding:2px; margin-top:6px; margin-bottom:2px'>
|
||||
<b>{% trans "Duplicate selection" %}</b>
|
||||
<strong>{% trans "Duplicate selection" %}</strong>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
@ -57,7 +57,7 @@
|
||||
{% for duplicate in duplicates %}
|
||||
{% if duplicate == col.value %}
|
||||
<div class='alert alert-danger alert-block text-center' role='alert' style='padding:2px; margin-top:6px; margin-bottom:2px'>
|
||||
<b>{% trans "Duplicate selection" %}</b>
|
||||
<strong>{% trans "Duplicate selection" %}</strong>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
@ -9,11 +9,11 @@
|
||||
|
||||
<table class='table table-striped table-condensed table-price-two'>
|
||||
<tr>
|
||||
<td><b>{% trans 'Part' %}</b></td>
|
||||
<td><strong>{% trans 'Part' %}</strong></td>
|
||||
<td>{{ part }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>{% trans 'Quantity' %}</b></td>
|
||||
<td><strong>{% trans 'Quantity' %}</strong></td>
|
||||
<td>{{ quantity }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
@ -23,13 +23,13 @@
|
||||
<table class='table table-striped table-condensed table-price-three'>
|
||||
{% if min_total_buy_price %}
|
||||
<tr>
|
||||
<td><b>{% trans 'Unit Cost' %}</b></td>
|
||||
<td><strong>{% trans 'Unit Cost' %}</strong></td>
|
||||
<td>Min: {% include "price.html" with price=min_unit_buy_price %}</td>
|
||||
<td>Max: {% include "price.html" with price=max_unit_buy_price %}</td>
|
||||
</tr>
|
||||
{% if quantity > 1 %}
|
||||
<tr>
|
||||
<td><b>{% trans 'Total Cost' %}</b></td>
|
||||
<td><strong>{% trans 'Total Cost' %}</strong></td>
|
||||
<td>Min: {% include "price.html" with price=min_total_buy_price %}</td>
|
||||
<td>Max: {% include "price.html" with price=max_total_buy_price %}</td>
|
||||
</tr>
|
||||
@ -37,7 +37,7 @@
|
||||
{% else %}
|
||||
<tr>
|
||||
<td colspan='3'>
|
||||
<span class='warning-msg'><i>{% trans 'No supplier pricing available' %}</i></span>
|
||||
<span class='warning-msg'><em>{% trans 'No supplier pricing available' %}</em></span>
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
@ -49,26 +49,26 @@
|
||||
<table class='table table-striped table-condensed table-price-three'>
|
||||
{% if min_total_bom_price %}
|
||||
<tr>
|
||||
<td><b>{% trans 'Unit Cost' %}</b></td>
|
||||
<td><strong>{% trans 'Unit Cost' %}</strong></td>
|
||||
<td>Min: {% include "price.html" with price=min_unit_bom_price %}</td>
|
||||
<td>Max: {% include "price.html" with price=max_unit_bom_price %}</td>
|
||||
</tr>
|
||||
{% if quantity > 1 %}
|
||||
<tr>
|
||||
<td><b>{% trans 'Total Cost' %}</b></td>
|
||||
<td><strong>{% trans 'Total Cost' %}</strong></td>
|
||||
<td>Min: {% include "price.html" with price=min_total_bom_price %}</td>
|
||||
<td>Max: {% include "price.html" with price=max_total_bom_price %}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if min_total_bom_purchase_price %}
|
||||
<tr>
|
||||
<td><b>{% trans 'Unit Purchase Price' %}</b></td>
|
||||
<td><strong>{% trans 'Unit Purchase Price' %}</strong></td>
|
||||
<td>Min: {% include "price.html" with price=min_unit_bom_purchase_price %}</td>
|
||||
<td>Max: {% include "price.html" with price=max_unit_bom_purchase_price %}</td>
|
||||
</tr>
|
||||
{% if quantity > 1 %}
|
||||
<tr>
|
||||
<td><b>{% trans 'Total Purchase Price' %}</b></td>
|
||||
<td><strong>{% trans 'Total Purchase Price' %}</strong></td>
|
||||
<td>Min: {% include "price.html" with price=min_total_bom_purchase_price %}</td>
|
||||
<td>Max: {% include "price.html" with price=max_total_bom_purchase_price %}</td>
|
||||
</tr>
|
||||
@ -78,14 +78,14 @@
|
||||
{% if part.has_complete_bom_pricing == False %}
|
||||
<tr>
|
||||
<td colspan='3'>
|
||||
<span class='warning-msg'><i>{% trans 'Note: BOM pricing is incomplete for this part' %}</i></span>
|
||||
<span class='warning-msg'><em>{% trans 'Note: BOM pricing is incomplete for this part' %}</em></span>
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<tr>
|
||||
<td colspan='3'>
|
||||
<span class='warning-msg'><i>{% trans 'No BOM pricing available' %}</i></span>
|
||||
<span class='warning-msg'><em>{% trans 'No BOM pricing available' %}</em></span>
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
@ -97,11 +97,11 @@
|
||||
<h4>{% trans 'Internal Price' %}</h4>
|
||||
<table class='table table-striped table-condensed table-price-two'>
|
||||
<tr>
|
||||
<td><b>{% trans 'Unit Cost' %}</b></td>
|
||||
<td><strong>{% trans 'Unit Cost' %}</strong></td>
|
||||
<td>{% include "price.html" with price=unit_internal_part_price %}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>{% trans 'Total Cost' %}</b></td>
|
||||
<td><strong>{% trans 'Total Cost' %}</strong></td>
|
||||
<td>{% include "price.html" with price=total_internal_part_price %}</td>
|
||||
</tr>
|
||||
</table>
|
||||
@ -112,11 +112,11 @@
|
||||
<h4>{% trans 'Sale Price' %}</h4>
|
||||
<table class='table table-striped table-condensed table-price-two'>
|
||||
<tr>
|
||||
<td><b>{% trans 'Unit Cost' %}</b></td>
|
||||
<td><strong>{% trans 'Unit Cost' %}</strong></td>
|
||||
<td>{% include "price.html" with price=unit_part_price %}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>{% trans 'Total Cost' %}</b></td>
|
||||
<td><strong>{% trans 'Total Cost' %}</strong></td>
|
||||
<td>{% include "price.html" with price=total_part_price %}</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
@ -4,7 +4,7 @@
|
||||
{% block pre_form_content %}
|
||||
|
||||
<div class='alert alert-block alert-danger'>
|
||||
{% blocktrans with full_name=part.full_name %}Are you sure you want to delete part '<b>{{full_name}}</b>'?{% endblocktrans %}
|
||||
{% blocktrans with full_name=part.full_name %}Are you sure you want to delete part '<strong>{{full_name}}</strong>'?{% endblocktrans %}
|
||||
</div>
|
||||
|
||||
{% if part.used_in_count %}
|
||||
|
@ -18,7 +18,7 @@
|
||||
{% if part.supplier_count > 0 %}
|
||||
{% if min_total_buy_price %}
|
||||
<tr>
|
||||
<td><b>{% trans 'Supplier Pricing' %}</b>
|
||||
<td><strong>{% trans 'Supplier Pricing' %}</strong>
|
||||
<a href="#supplier-cost" title='{% trans "Show supplier cost" %}'><span class="fas fa-search-dollar"></span></a>
|
||||
<a href="#purchase-price" title='{% trans "Show purchase price" %}'><span class="fas fa-chart-bar"></span></a>
|
||||
</td>
|
||||
@ -37,7 +37,7 @@
|
||||
{% else %}
|
||||
<tr>
|
||||
<td colspan='4'>
|
||||
<span class='warning-msg'><i>{% trans 'No supplier pricing available' %}</i></span>
|
||||
<span class='warning-msg'><em>{% trans 'No supplier pricing available' %}</em></span>
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
@ -46,7 +46,7 @@
|
||||
{% if part.bom_count > 0 %}
|
||||
{% if min_total_bom_price %}
|
||||
<tr>
|
||||
<td><b>{% trans 'BOM Pricing' %}</b>
|
||||
<td><strong>{% trans 'BOM Pricing' %}</strong>
|
||||
<a href="#bom-cost" title='{% trans "Show BOM cost" %}'><span class="fas fa-search-dollar"></span></a>
|
||||
</td>
|
||||
<td>{% trans 'Unit Cost' %}</td>
|
||||
@ -83,14 +83,14 @@
|
||||
{% if part.has_complete_bom_pricing == False %}
|
||||
<tr>
|
||||
<td colspan='4'>
|
||||
<span class='warning-msg'><i>{% trans 'Note: BOM pricing is incomplete for this part' %}</i></span>
|
||||
<span class='warning-msg'><em>{% trans 'Note: BOM pricing is incomplete for this part' %}</em></span>
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<tr>
|
||||
<td colspan='4'>
|
||||
<span class='warning-msg'><i>{% trans 'No BOM pricing available' %}</i></span>
|
||||
<span class='warning-msg'><em>{% trans 'No BOM pricing available' %}</em></span>
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
@ -99,7 +99,7 @@
|
||||
{% if show_internal_price and roles.sales_order.view %}
|
||||
{% if total_internal_part_price %}
|
||||
<tr>
|
||||
<td><b>{% trans 'Internal Price' %}</b></td>
|
||||
<td><strong>{% trans 'Internal Price' %}</strong></td>
|
||||
<td>{% trans 'Unit Cost' %}</td>
|
||||
<td colspan='2'>{% include "price.html" with price=unit_internal_part_price %}</td>
|
||||
</tr>
|
||||
@ -113,7 +113,7 @@
|
||||
|
||||
{% if total_part_price %}
|
||||
<tr>
|
||||
<td><b>{% trans 'Sale Price' %}</b>
|
||||
<td><strong>{% trans 'Sale Price' %}</strong>
|
||||
<a href="#sale-cost" title='{% trans "Show sale cost" %}'><span class="fas fa-search-dollar"></span></a>
|
||||
<a href="#sale-price" title='{% trans "Show sale price" %}'><span class="fas fa-chart-bar"></span></a>
|
||||
</td>
|
||||
@ -179,7 +179,7 @@
|
||||
</div>
|
||||
<div class='panel-content'>
|
||||
<h4>{% trans 'Stock Pricing' %}
|
||||
<i class="fas fa-info-circle" title="Shows the purchase prices of stock for this part. 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. The Supplier Unit Cost is the current purchase price for that supplier part."></em>
|
||||
</h4>
|
||||
{% if price_history|length > 0 %}
|
||||
<div style="max-width: 99%; min-height: 300px">
|
||||
|
@ -6,8 +6,8 @@
|
||||
{{ block.super }}
|
||||
|
||||
<div class='alert alert-info alert-block'>
|
||||
<b>{% trans "Create new part variant" %}</b><br>
|
||||
{% blocktrans with full_name=part.full_name %}Create a new variant of template <i>'{{full_name}}'</i>.{% endblocktrans %}
|
||||
<strong>{% trans "Create new part variant" %}</strong><br>
|
||||
{% blocktrans with full_name=part.full_name %}Create a new variant of template <em>'{{full_name}}'</em>.{% endblocktrans %}
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
@ -128,7 +128,7 @@ content: "v{{report_revision}} - {{ date.isoformat }}";
|
||||
{% if build.target_date %}
|
||||
{{ build.target_date }}
|
||||
{% else %}
|
||||
<i>Not specified</i>
|
||||
<em>Not specified</em>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
@ -138,7 +138,7 @@ content: "v{{report_revision}} - {{ date.isoformat }}";
|
||||
{% if build.sales_order %}
|
||||
{% internal_link build.sales_order.get_absolute_url build.sales_order %}
|
||||
{% else %}
|
||||
<i>Not specified</i>
|
||||
<em>Not specified</em>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
|
@ -68,8 +68,8 @@ content: "{% trans 'Stock Item Test Report' %}";
|
||||
{{ part.full_name }}
|
||||
</h2>
|
||||
<p>{{ part.description }}</p>
|
||||
<p><i>{{ stock_item.location }}</i></p>
|
||||
<p><i>Stock Item ID: {{ stock_item.pk }}</i></p>
|
||||
<p><em>{{ stock_item.location }}</em></p>
|
||||
<p><em>Stock Item ID: {{ stock_item.pk }}</em></p>
|
||||
</div>
|
||||
<div class='img-right'>
|
||||
<img class='part-img' src="{% part_image part %}">
|
||||
|
@ -43,6 +43,7 @@ from .serializers import StockItemTestResultSerializer
|
||||
from InvenTree.views import TreeSerializer
|
||||
from InvenTree.helpers import str2bool, isNull
|
||||
from InvenTree.api import AttachmentMixin
|
||||
from InvenTree.filters import InvenTreeOrderingFilter
|
||||
|
||||
from decimal import Decimal, InvalidOperation
|
||||
|
||||
@ -882,10 +883,16 @@ class StockList(generics.ListCreateAPIView):
|
||||
filter_backends = [
|
||||
DjangoFilterBackend,
|
||||
filters.SearchFilter,
|
||||
filters.OrderingFilter,
|
||||
InvenTreeOrderingFilter,
|
||||
]
|
||||
|
||||
ordering_field_aliases = {
|
||||
'SKU': 'supplier_part__SKU',
|
||||
}
|
||||
|
||||
ordering_fields = [
|
||||
'batch',
|
||||
'location',
|
||||
'part__name',
|
||||
'part__IPN',
|
||||
'updated',
|
||||
@ -893,10 +900,13 @@ class StockList(generics.ListCreateAPIView):
|
||||
'expiry_date',
|
||||
'quantity',
|
||||
'status',
|
||||
'SKU',
|
||||
]
|
||||
|
||||
ordering = [
|
||||
'part__name'
|
||||
'part__name',
|
||||
'quantity',
|
||||
'location',
|
||||
]
|
||||
|
||||
search_fields = [
|
||||
|
BIN
InvenTree/stock/fixtures/test_image.bmp
Normal file
BIN
InvenTree/stock/fixtures/test_image.bmp
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.4 MiB |
@ -182,7 +182,7 @@
|
||||
|
||||
{% if item.build %}
|
||||
<a href="{% url 'build-detail' item.build.id %}">
|
||||
<b>{{ item.build }}</b>
|
||||
<strong>{{ item.build }}</strong>
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
@ -300,7 +300,7 @@
|
||||
{% if item.location %}
|
||||
<td><a href="{% url 'stock-location-detail' item.location.id %}">{{ item.location.name }}</a></td>
|
||||
{% else %}
|
||||
<td><i>{% trans "No location set" %}</i></td>
|
||||
<td><em>{% trans "No location set" %}</em></td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
{% endif %}
|
||||
@ -367,7 +367,7 @@
|
||||
{% if item.supplier_part.manufacturer_part.manufacturer %}
|
||||
<td><a href="{% url 'company-detail' item.supplier_part.manufacturer_part.manufacturer.id %}">{{ item.supplier_part.manufacturer_part.manufacturer.name }}</a></td>
|
||||
{% else %}
|
||||
<td><i>{% trans "No manufacturer set" %}</i></td>
|
||||
<td><em>{% trans "No manufacturer set" %}</em></td>
|
||||
{% endif %}
|
||||
|
||||
</tr>
|
||||
@ -414,7 +414,7 @@
|
||||
{% if item.stocktake_date %}
|
||||
<td>{{ item.stocktake_date }} <span class='badge'>{{ item.stocktake_user }}</span></td>
|
||||
{% else %}
|
||||
<td><i>{% trans "No stocktake performed" %}</i></td>
|
||||
<td><em>{% trans "No stocktake performed" %}</em></td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
<tr>
|
||||
|
@ -9,7 +9,7 @@
|
||||
{% trans "Are you sure you want to delete this stock item?" %}
|
||||
<br>
|
||||
{% decimal item.quantity as qty %}
|
||||
{% blocktrans with full_name=item.part.full_name %}This will remove <b>{{qty}}</b> units of <b>{{full_name}}</b> from stock.{% endblocktrans %}
|
||||
{% blocktrans with full_name=item.part.full_name %}This will remove <strong>{{qty}}</strong> units of <strong>{{full_name}}</strong> from stock.{% endblocktrans %}
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
@ -3,7 +3,7 @@
|
||||
<div class="navigation">
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb">
|
||||
<li><a href='#' title='Toggle Stock Tree' id='toggle-stock-tree'><b><span class='fas fa-stream'></span></b></a></li>
|
||||
<li><a href='#' title='Toggle Stock Tree' id='toggle-stock-tree'><strong><span class='fas fa-stream'></span></strong></a></li>
|
||||
<li class="breadcrumb-item{% if location is None %} active" aria-current="page{% endif %}"><a href="/stock/">{% trans "Stock" %}</a></li>
|
||||
{% if location %}
|
||||
{% for path_item in location.parentpath %}
|
||||
|
@ -20,7 +20,7 @@ the top level 'Stock' location.
|
||||
|
||||
<ul class='list-group'>
|
||||
{% for loc in location.children.all %}
|
||||
<li class='list-group-item'><b>{{ loc.name }}</b> - <i>{{ loc.description}}</i></li>
|
||||
<li class='list-group-item'><strong>{{ loc.name }}</strong> - <em>{{ loc.description}}</em></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
@ -36,7 +36,7 @@ If this location is deleted, these items will be moved to the top level 'Stock'
|
||||
|
||||
<ul class='list-group'>
|
||||
{% for item in location.stock_items.all %}
|
||||
<li class='list-group-item'><b>{{ item.part.full_name }}</b> - <i>{{ item.part.description }}</i><span class='badge'>{% decimal item.quantity %}</span></li>
|
||||
<li class='list-group-item'><strong>{{ item.part.full_name }}</strong> - <em>{{ item.part.description }}</em><span class='badge'>{% decimal item.quantity %}</span></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
|
@ -24,7 +24,7 @@
|
||||
{% for item in stock_items %}
|
||||
<tr id='stock-row-{{ item.id }}' class='error'>
|
||||
<td>{% include "hover_image.html" with image=item.part.image hover=True %}
|
||||
{{ item.part.full_name }} <small><i>{{ item.part.description }}</i></small></td>
|
||||
{{ item.part.full_name }} <small><em>{{ item.part.description }}</em></small></td>
|
||||
<td>{{ item.location.pathstring }}</td>
|
||||
<td>{% decimal item.quantity %}</td>
|
||||
<td>
|
||||
|
@ -4,13 +4,13 @@
|
||||
{% block pre_form_content %}
|
||||
|
||||
<div class='alert alert-block alert-info'>
|
||||
<b>{% trans "Convert Stock Item" %}</b><br>
|
||||
{% blocktrans with part=item.part %}This stock item is current an instance of <i>{{part}}</i>{% endblocktrans %}<br>
|
||||
<strong>{% trans "Convert Stock Item" %}</strong><br>
|
||||
{% blocktrans with part=item.part %}This stock item is current an instance of <em>{{part}}</em>{% endblocktrans %}<br>
|
||||
{% trans "It can be converted to one of the part variants listed below." %}
|
||||
</div>
|
||||
|
||||
<div class='alert alert-block alert-warning'>
|
||||
<b>{% trans "Warning" %}</b>
|
||||
<strong>{% trans "Warning" %}</strong>
|
||||
{% trans "This action cannot be easily undone" %}
|
||||
</div>
|
||||
|
||||
|
@ -5,6 +5,8 @@ Unit testing for the Stock API
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import os
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from django.urls import reverse
|
||||
@ -666,3 +668,37 @@ class StockTestResultTest(StockAPITestCase):
|
||||
test = response.data[0]
|
||||
self.assertEqual(test['value'], '150kPa')
|
||||
self.assertEqual(test['user'], self.user.pk)
|
||||
|
||||
def test_post_bitmap(self):
|
||||
"""
|
||||
2021-08-25
|
||||
|
||||
For some (unknown) reason, prior to fix https://github.com/inventree/InvenTree/pull/2018
|
||||
uploading a bitmap image would result in a failure.
|
||||
|
||||
This test has been added to ensure that there is no regression.
|
||||
|
||||
As a bonus this also tests the file-upload component
|
||||
"""
|
||||
|
||||
here = os.path.dirname(__file__)
|
||||
|
||||
image_file = os.path.join(here, 'fixtures', 'test_image.bmp')
|
||||
|
||||
with open(image_file, 'rb') as bitmap:
|
||||
|
||||
data = {
|
||||
'stock_item': 105,
|
||||
'test': 'Checked Steam Valve',
|
||||
'result': False,
|
||||
'value': '150kPa',
|
||||
'notes': 'I guess there was just too much pressure?',
|
||||
"attachment": bitmap,
|
||||
}
|
||||
|
||||
response = self.client.post(self.get_url(), data)
|
||||
|
||||
self.assertEqual(response.status_code, 201)
|
||||
|
||||
# Check that an attachment has been uploaded
|
||||
self.assertIsNotNone(response.data['attachment'])
|
||||
|
@ -34,7 +34,7 @@
|
||||
function addHeaderTitle(title) {
|
||||
// Add a header block to the action list
|
||||
$("#action-item-list").append(
|
||||
`<li class='list-group-item'><b>${title}</b></li>`
|
||||
`<li class='list-group-item'><strong>${title}</strong></li>`
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -21,7 +21,7 @@
|
||||
{% if query %}
|
||||
{% else %}
|
||||
<div id='empty-search-query'>
|
||||
<h4><i>{% trans "Enter a search query" %}</i></h4>
|
||||
<h4><em>{% trans "Enter a search query" %}</em></h4>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
@ -47,7 +47,7 @@
|
||||
function addItemTitle(title) {
|
||||
// Add header block to the results list
|
||||
$('#search-item-list').append(
|
||||
`<li class='list-group-item'><b>${title}</b></li>`
|
||||
`<li class='list-group-item'><strong>${title}</strong></li>`
|
||||
);
|
||||
}
|
||||
|
||||
@ -268,7 +268,7 @@
|
||||
var text = "{% trans "Shipped to customer" %}";
|
||||
return renderLink(text, `/company/${row.customer}/assigned-stock/`);
|
||||
} else {
|
||||
return '<i>{% trans "No stock location set" %}</i>';
|
||||
return '<em>{% trans "No stock location set" %}</em>';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -40,7 +40,7 @@
|
||||
{% if rates_updated %}
|
||||
{{ rates_updated }}
|
||||
{% else %}
|
||||
<i>{% trans "Never" %}</i>
|
||||
<em>{% trans "Never" %}</em>
|
||||
{% endif %}
|
||||
<form action='{% url "settings-currencies-refresh" %}' method='post'>
|
||||
<div id='refresh-rates-form'>
|
||||
|
@ -9,7 +9,7 @@
|
||||
</li>
|
||||
|
||||
<li class='list-group-item'>
|
||||
<b>{% trans "User Settings" %}</b>
|
||||
<strong>{% trans "User Settings" %}</strong>
|
||||
</li>
|
||||
|
||||
<li class='list-group-item' title='{% trans "Account" %}'>
|
||||
@ -53,7 +53,7 @@
|
||||
{% if user.is_staff %}
|
||||
|
||||
<li class='list-group-item'>
|
||||
<b>{% trans "InvenTree Settings" %}</b>
|
||||
<strong>{% trans "InvenTree Settings" %}</strong>
|
||||
</li>
|
||||
|
||||
<li class='list-group-item' title='{% trans "Server" %}'>
|
||||
|
@ -13,7 +13,7 @@
|
||||
<span class='fas {{ icon }}'></span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td><b>{% trans setting.name %}</b></td>
|
||||
<td><strong>{% trans setting.name %}</strong></td>
|
||||
<td>
|
||||
{% if setting.is_bool %}
|
||||
<div>
|
||||
@ -21,15 +21,15 @@
|
||||
</div>
|
||||
{% else %}
|
||||
<div id='setting-{{ setting.pk }}'>
|
||||
<b>
|
||||
<strong>
|
||||
<span id='setting-value-{{ setting.key.upper }}' fieldname='{{ setting.key.upper }}'>
|
||||
{% if setting.value %}
|
||||
{{ setting.value }}
|
||||
{% else %}
|
||||
<i>{% trans "No value set" %}</i>
|
||||
<em>{% trans "No value set" %}</em>
|
||||
{% endif %}
|
||||
</span>
|
||||
</b>
|
||||
</strong>
|
||||
{{ setting.units }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
@ -185,8 +185,6 @@ $('#cat-param-table').inventreeTable({
|
||||
|
||||
function loadTemplateTable(pk) {
|
||||
|
||||
console.log('refresh:', pk);
|
||||
|
||||
// Enable the buttons
|
||||
$('#new-cat-param').removeAttr('disabled');
|
||||
|
||||
@ -210,7 +208,11 @@ $("#new-cat-param").click(function() {
|
||||
|
||||
launchModalForm(`/part/category/${pk}/parameters/new/`, {
|
||||
success: function() {
|
||||
$("#cat-param-table").bootstrapTable('refresh');
|
||||
$("#cat-param-table").bootstrapTable('refresh', {
|
||||
query: {
|
||||
category: pk,
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
});
|
||||
|
@ -87,7 +87,7 @@
|
||||
<td>
|
||||
<span style="display: none;" id="about-copy-text">{% include "version.html" %}</span>
|
||||
<span class="float-right">
|
||||
<button class="btn clip-btn-version" type="button" data-toggle='tooltip' title='{% trans "copy to clipboard" %}'><i class="fas fa-copy"></i> {% trans "copy version information" %}</button>
|
||||
<button class="btn clip-btn-version" type="button" data-toggle='tooltip' title='{% trans "copy to clipboard" %}'><em class="fas fa-copy"></em> {% trans "copy version information" %}</button>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
|
@ -160,6 +160,7 @@
|
||||
<script type='text/javascript' src="{% i18n_static 'company.js' %}"></script>
|
||||
<script type='text/javascript' src="{% i18n_static 'filters.js' %}"></script>
|
||||
<script type='text/javascript' src="{% i18n_static 'forms.js' %}"></script>
|
||||
<script type='text/javascript' src="{% i18n_static 'helpers.js' %}"></script>
|
||||
<script type='text/javascript' src="{% i18n_static 'label.js' %}"></script>
|
||||
<script type='text/javascript' src="{% i18n_static 'modals.js' %}"></script>
|
||||
<script type='text/javascript' src="{% i18n_static 'model_renderers.js' %}"></script>
|
||||
|
@ -1,5 +1,5 @@
|
||||
{% load i18n %}
|
||||
|
||||
<span class="float-right">
|
||||
<button class="btn clip-btn" type="button" data-toggle='tooltip' title='{% trans "copy to clipboard" %}'><i class="fas fa-copy"></i></button>
|
||||
<button class="btn clip-btn" type="button" data-toggle='tooltip' title='{% trans "copy to clipboard" %}'><em class="fas fa-copy"></em></button>
|
||||
</span>
|
@ -1,17 +1,26 @@
|
||||
{% load i18n %}
|
||||
|
||||
/* globals
|
||||
*/
|
||||
|
||||
/* exported
|
||||
clearEvents,
|
||||
endDate,
|
||||
startDate,
|
||||
*/
|
||||
|
||||
/**
|
||||
* Helper functions for calendar display
|
||||
*/
|
||||
|
||||
function startDate(calendar) {
|
||||
// Extract the first displayed date on the calendar
|
||||
return calendar.currentData.dateProfile.activeRange.start.toISOString().split("T")[0];
|
||||
return calendar.currentData.dateProfile.activeRange.start.toISOString().split('T')[0];
|
||||
}
|
||||
|
||||
function endDate(calendar) {
|
||||
// Extract the last display date on the calendar
|
||||
return calendar.currentData.dateProfile.activeRange.end.toISOString().split("T")[0];
|
||||
return calendar.currentData.dateProfile.activeRange.end.toISOString().split('T')[0];
|
||||
}
|
||||
|
||||
function clearEvents(calendar) {
|
||||
@ -21,5 +30,5 @@ function clearEvents(calendar) {
|
||||
|
||||
events.forEach(function(event) {
|
||||
event.remove();
|
||||
})
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -1,26 +1,44 @@
|
||||
{% load inventree_extras %}
|
||||
|
||||
/* globals
|
||||
ClipboardJS,
|
||||
inventreeFormDataUpload,
|
||||
launchModalForm,
|
||||
user_settings,
|
||||
*/
|
||||
|
||||
/* exported
|
||||
attachClipboard,
|
||||
enableDragAndDrop,
|
||||
inventreeDocReady,
|
||||
inventreeLoad,
|
||||
inventreeSave,
|
||||
*/
|
||||
|
||||
function attachClipboard(selector, containerselector, textElement) {
|
||||
// set container
|
||||
if (containerselector){
|
||||
if (containerselector) {
|
||||
containerselector = document.getElementById(containerselector);
|
||||
} else {
|
||||
containerselector = document.body;
|
||||
}
|
||||
|
||||
var text = null;
|
||||
|
||||
// set text-function
|
||||
if (textElement){
|
||||
if (textElement) {
|
||||
text = function() {
|
||||
return document.getElementById(textElement).textContent;
|
||||
}
|
||||
};
|
||||
} else {
|
||||
text = function(trigger) {
|
||||
var content = trigger.parentElement.parentElement.textContent;
|
||||
return content.trim();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// create Clipboard
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
var cis = new ClipboardJS(selector, {
|
||||
text: text,
|
||||
container: containerselector
|
||||
@ -33,15 +51,15 @@ function inventreeDocReady() {
|
||||
* This will be called for every page that extends "base.html"
|
||||
*/
|
||||
|
||||
window.addEventListener("dragover",function(e){
|
||||
window.addEventListener('dragover', function(e) {
|
||||
e = e || event;
|
||||
e.preventDefault();
|
||||
},false);
|
||||
}, false);
|
||||
|
||||
window.addEventListener("drop",function(e){
|
||||
window.addEventListener('drop', function(e) {
|
||||
e = e || event;
|
||||
e.preventDefault();
|
||||
},false);
|
||||
}, false);
|
||||
|
||||
/* Add drag-n-drop functionality to any element
|
||||
* marked with the class 'dropzone'
|
||||
@ -51,12 +69,13 @@ function inventreeDocReady() {
|
||||
// TODO - Only indicate that a drop event will occur if a file is being dragged
|
||||
var transfer = event.originalEvent.dataTransfer;
|
||||
|
||||
// eslint-disable-next-line no-constant-condition
|
||||
if (true || isFileTransfer(transfer)) {
|
||||
$(this).addClass('dragover');
|
||||
}
|
||||
});
|
||||
|
||||
$('.dropzone').on('dragleave drop', function(event) {
|
||||
$('.dropzone').on('dragleave drop', function() {
|
||||
$(this).removeClass('dragover');
|
||||
});
|
||||
|
||||
@ -74,19 +93,19 @@ function inventreeDocReady() {
|
||||
|
||||
// Callback to launch the 'Database Stats' window
|
||||
$('#launch-stats').click(function() {
|
||||
launchModalForm("/stats/", {
|
||||
launchModalForm('/stats/', {
|
||||
no_post: true,
|
||||
});
|
||||
});
|
||||
|
||||
// Initialize clipboard-buttons
|
||||
attachClipboard('.clip-btn');
|
||||
attachClipboard('.clip-btn', 'modal-about'); // modals
|
||||
attachClipboard('.clip-btn-version', 'modal-about', 'about-copy-text'); // version-text
|
||||
attachClipboard('.clip-btn', 'modal-about');
|
||||
attachClipboard('.clip-btn-version', 'modal-about', 'about-copy-text');
|
||||
|
||||
// Add autocomplete to the search-bar
|
||||
$("#search-bar" ).autocomplete({
|
||||
source: function (request, response) {
|
||||
$('#search-bar').autocomplete({
|
||||
source: function(request, response) {
|
||||
$.ajax({
|
||||
url: '/api/part/',
|
||||
data: {
|
||||
@ -94,8 +113,8 @@ function inventreeDocReady() {
|
||||
limit: user_settings.SEARCH_PREVIEW_RESULTS,
|
||||
offset: 0
|
||||
},
|
||||
success: function (data) {
|
||||
var transformed = $.map(data.results, function (el) {
|
||||
success: function(data) {
|
||||
var transformed = $.map(data.results, function(el) {
|
||||
return {
|
||||
label: el.name,
|
||||
id: el.pk,
|
||||
@ -104,13 +123,13 @@ function inventreeDocReady() {
|
||||
});
|
||||
response(transformed);
|
||||
},
|
||||
error: function () {
|
||||
error: function() {
|
||||
response([]);
|
||||
}
|
||||
});
|
||||
},
|
||||
create: function () {
|
||||
$(this).data('ui-autocomplete')._renderItem = function (ul, item) {
|
||||
create: function() {
|
||||
$(this).data('ui-autocomplete')._renderItem = function(ul, item) {
|
||||
|
||||
var html = `<a href='/part/${item.id}/'><span>`;
|
||||
|
||||
@ -128,7 +147,9 @@ function inventreeDocReady() {
|
||||
window.location = '/part/' + ui.item.id + '/';
|
||||
},
|
||||
minLength: 2,
|
||||
classes: {'ui-autocomplete': 'dropdown-menu search-menu'},
|
||||
classes: {
|
||||
'ui-autocomplete': 'dropdown-menu search-menu',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@ -140,124 +161,6 @@ function isFileTransfer(transfer) {
|
||||
}
|
||||
|
||||
|
||||
function isOnlineTransfer(transfer) {
|
||||
/* Determine if a drag-and-drop transfer is from another website.
|
||||
* e.g. dragged from another browser window
|
||||
*/
|
||||
|
||||
return transfer.items.length > 0;
|
||||
}
|
||||
|
||||
|
||||
function getImageUrlFromTransfer(transfer) {
|
||||
/* Extract external image URL from a drag-and-dropped image
|
||||
*/
|
||||
|
||||
var url = transfer.getData('text/html').match(/src\s*=\s*"(.+?)"/)[1];
|
||||
|
||||
console.log('Image URL: ' + url);
|
||||
|
||||
return url;
|
||||
}
|
||||
|
||||
function makeIconBadge(icon, title) {
|
||||
// Construct an 'icon badge' which floats to the right of an object
|
||||
|
||||
var html = `<span class='fas ${icon} label-right' title='${title}'></span>`;
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
function makeIconButton(icon, cls, pk, title, options={}) {
|
||||
// Construct an 'icon button' using the fontawesome set
|
||||
|
||||
var classes = `btn btn-default btn-glyph ${cls}`;
|
||||
|
||||
var id = `${cls}-${pk}`;
|
||||
|
||||
var html = '';
|
||||
|
||||
var extraProps = '';
|
||||
|
||||
if (options.disabled) {
|
||||
extraProps += "disabled='true' ";
|
||||
}
|
||||
|
||||
html += `<button pk='${pk}' id='${id}' class='${classes}' title='${title}' ${extraProps}>`;
|
||||
html += `<span class='fas ${icon}'></span>`;
|
||||
html += `</button>`;
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
function makeProgressBar(value, maximum, opts={}) {
|
||||
/*
|
||||
* Render a progessbar!
|
||||
*
|
||||
* @param value is the current value of the progress bar
|
||||
* @param maximum is the maximum value of the progress bar
|
||||
*/
|
||||
|
||||
var options = opts || {};
|
||||
|
||||
value = parseFloat(value);
|
||||
|
||||
var percent = 100;
|
||||
|
||||
// Prevent div-by-zero or null value
|
||||
if (maximum && maximum > 0) {
|
||||
maximum = parseFloat(maximum);
|
||||
percent = parseInt(value / maximum * 100);
|
||||
}
|
||||
|
||||
if (percent > 100) {
|
||||
percent = 100;
|
||||
}
|
||||
|
||||
var extraclass = '';
|
||||
|
||||
if (value > maximum) {
|
||||
extraclass='progress-bar-over';
|
||||
} else if (value < maximum) {
|
||||
extraclass = 'progress-bar-under';
|
||||
}
|
||||
|
||||
var style = options.style || '';
|
||||
|
||||
var text = '';
|
||||
|
||||
if (style == 'percent') {
|
||||
// Display e.g. "50%"
|
||||
|
||||
text = `${percent}%`;
|
||||
} else if (style == 'max') {
|
||||
// Display just the maximum value
|
||||
text = `${maximum}`;
|
||||
} else if (style == 'value') {
|
||||
// Display just the current value
|
||||
text = `${value}`;
|
||||
} else if (style == 'blank') {
|
||||
// No display!
|
||||
text = '';
|
||||
} else {
|
||||
/* Default style
|
||||
* Display e.g. "5 / 10"
|
||||
*/
|
||||
|
||||
text = `${value} / ${maximum}`;
|
||||
}
|
||||
|
||||
var id = options.id || 'progress-bar';
|
||||
|
||||
return `
|
||||
<div id='${id}' class='progress'>
|
||||
<div class='progress-bar ${extraclass}' role='progressbar' aria-valuenow='${percent}' aria-valuemin='0' aria-valuemax='100' style='width:${percent}%'></div>
|
||||
<div class='progress-value'>${text}</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
|
||||
function enableDragAndDrop(element, url, options) {
|
||||
/* Enable drag-and-drop file uploading for a given element.
|
||||
|
||||
@ -272,7 +175,7 @@ function enableDragAndDrop(element, url, options) {
|
||||
method - HTTP method
|
||||
*/
|
||||
|
||||
data = options.data || {};
|
||||
var data = options.data || {};
|
||||
|
||||
$(element).on('drop', function(event) {
|
||||
|
||||
@ -316,55 +219,27 @@ function enableDragAndDrop(element, url, options) {
|
||||
}
|
||||
|
||||
|
||||
function thumbnailImage(url, options={}) {
|
||||
/* Render a simple thumbnail image from the provided URL */
|
||||
|
||||
if (!url) {
|
||||
url = '/static/img/blank_img.png';
|
||||
}
|
||||
|
||||
// TODO: Support insertion of custom classes
|
||||
|
||||
var html = `<img class='hover-img-thumb' src='${url}'>`;
|
||||
|
||||
return html;
|
||||
|
||||
}
|
||||
|
||||
function imageHoverIcon(url) {
|
||||
/* Render a small thumbnail icon for an image.
|
||||
* On mouseover, display a full-size version of the image
|
||||
*/
|
||||
|
||||
if (!url) {
|
||||
url = '/static/img/blank_image.png';
|
||||
}
|
||||
|
||||
var html = `
|
||||
<a class='hover-icon'>
|
||||
<img class='hover-img-thumb' src='` + url + `'>
|
||||
<img class='hover-img-large' src='` + url + `'>
|
||||
</a>
|
||||
`;
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save a key:value pair to local storage
|
||||
* @param {String} name - settting key
|
||||
* @param {String} value - setting value
|
||||
*/
|
||||
function inventreeSave(name, value) {
|
||||
/*
|
||||
* Save a key:value pair to local storage
|
||||
*/
|
||||
|
||||
var key = "inventree-" + name;
|
||||
var key = `inventree-${name}`;
|
||||
localStorage.setItem(key, value);
|
||||
}
|
||||
|
||||
function inventreeLoad(name, defaultValue) {
|
||||
/*
|
||||
* Retrieve a key:value pair from local storage
|
||||
*/
|
||||
|
||||
var key = "inventree-" + name;
|
||||
/**
|
||||
* Retrieve a key:value pair from local storage
|
||||
* @param {*} name - setting key
|
||||
* @param {*} defaultValue - default value (returned if no matching key:value pair is found)
|
||||
* @returns
|
||||
*/
|
||||
function inventreeLoad(name, defaultValue) {
|
||||
|
||||
var key = `inventree-${name}`;
|
||||
|
||||
var value = localStorage.getItem(key);
|
||||
|
||||
@ -374,27 +249,3 @@ function inventreeLoad(name, defaultValue) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
function inventreeLoadInt(name) {
|
||||
/*
|
||||
* Retrieve a value from local storage, and attempt to cast to integer
|
||||
*/
|
||||
|
||||
var data = inventreeLoad(name);
|
||||
|
||||
return parseInt(data, 10);
|
||||
}
|
||||
|
||||
function inventreeLoadFloat(name) {
|
||||
|
||||
var data = inventreeLoad(name);
|
||||
|
||||
return parseFloat(data);
|
||||
}
|
||||
|
||||
function inventreeDel(name) {
|
||||
|
||||
var key = 'inventree-' + name;
|
||||
|
||||
localStorage.removeItem(key);
|
||||
}
|
@ -1,3 +1,10 @@
|
||||
/* globals
|
||||
*/
|
||||
|
||||
/* exported
|
||||
attachNavCallbacks,
|
||||
onPanelLoad,
|
||||
*/
|
||||
|
||||
/*
|
||||
* Attach callbacks to navigation bar elements.
|
||||
@ -55,7 +62,7 @@ function activatePanel(panelName, options={}) {
|
||||
// Iterate through the available 'select' elements until one matches
|
||||
panelName = null;
|
||||
|
||||
$('.nav-toggle').each(function(item) {
|
||||
$('.nav-toggle').each(function() {
|
||||
var panel_name = $(this).attr('id').replace('select-', '');
|
||||
|
||||
if ($(`#panel-${panel_name}`).length && (panelName == null)) {
|
||||
@ -83,9 +90,9 @@ function activatePanel(panelName, options={}) {
|
||||
$('.list-group-item').removeClass('active');
|
||||
|
||||
// Find the associated selector
|
||||
var select = `#select-${panelName}`;
|
||||
var selectElement = `#select-${panelName}`;
|
||||
|
||||
$(select).parent('.list-group-item').addClass('active');
|
||||
$(selectElement).parent('.list-group-item').addClass('active');
|
||||
}
|
||||
|
||||
|
||||
@ -96,7 +103,7 @@ function onPanelLoad(panel, callback) {
|
||||
|
||||
var panelId = `#panel-${panel}`;
|
||||
|
||||
$(panelId).on('fadeInStarted', function(e) {
|
||||
$(panelId).on('fadeInStarted', function() {
|
||||
|
||||
// Trigger the callback
|
||||
callback();
|
||||
@ -105,4 +112,4 @@ function onPanelLoad(panel, callback) {
|
||||
$(panelId).off('fadeInStarted');
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1,18 +1,20 @@
|
||||
{% load inventree_extras %}
|
||||
// InvenTree settings
|
||||
|
||||
/* exported
|
||||
user_settings,
|
||||
global_settings,
|
||||
*/
|
||||
|
||||
{% user_settings request.user as USER_SETTINGS %}
|
||||
|
||||
var user_settings = {
|
||||
const user_settings = {
|
||||
{% for key, value in USER_SETTINGS.items %}
|
||||
{{ key }}: {% primitive_to_javascript value %},
|
||||
{% endfor %}
|
||||
};
|
||||
|
||||
{% global_settings as GLOBAL_SETTINGS %}
|
||||
|
||||
var global_settings = {
|
||||
const global_settings = {
|
||||
{% for key, value in GLOBAL_SETTINGS.items %}
|
||||
{{ key }}: {% primitive_to_javascript value %},
|
||||
{% endfor %}
|
||||
};
|
||||
};
|
||||
|
@ -1,15 +1,29 @@
|
||||
{% load i18n %}
|
||||
{% load inventree_extras %}
|
||||
|
||||
var jQuery = window.$;
|
||||
/* globals
|
||||
renderErrorMessage,
|
||||
showAlertDialog,
|
||||
*/
|
||||
|
||||
$.urlParam = function(name){
|
||||
/* exported
|
||||
inventreeGet,
|
||||
inventreeDelete,
|
||||
inventreeFormDataUpload,
|
||||
showApiError,
|
||||
*/
|
||||
|
||||
$.urlParam = function(name) {
|
||||
// eslint-disable-next-line no-useless-escape
|
||||
var results = new RegExp('[\?&]' + name + '=([^&#]*)').exec(window.location.href);
|
||||
if (results==null) {
|
||||
return null;
|
||||
|
||||
if (results == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return decodeURI(results[1]) || 0;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// using jQuery
|
||||
function getCookie(name) {
|
||||
@ -31,11 +45,10 @@ function getCookie(name) {
|
||||
function inventreeGet(url, filters={}, options={}) {
|
||||
|
||||
// Middleware token required for data update
|
||||
//var csrftoken = jQuery("[name=csrfmiddlewaretoken]").val();
|
||||
var csrftoken = getCookie('csrftoken');
|
||||
|
||||
return $.ajax({
|
||||
beforeSend: function(xhr, settings) {
|
||||
beforeSend: function(xhr) {
|
||||
xhr.setRequestHeader('X-CSRFToken', csrftoken);
|
||||
},
|
||||
url: url,
|
||||
@ -73,7 +86,7 @@ function inventreeFormDataUpload(url, data, options={}) {
|
||||
var csrftoken = getCookie('csrftoken');
|
||||
|
||||
return $.ajax({
|
||||
beforeSend: function(xhr, settings) {
|
||||
beforeSend: function(xhr) {
|
||||
xhr.setRequestHeader('X-CSRFToken', csrftoken);
|
||||
},
|
||||
url: url,
|
||||
@ -101,11 +114,10 @@ function inventreePut(url, data={}, options={}) {
|
||||
var method = options.method || 'PUT';
|
||||
|
||||
// Middleware token required for data update
|
||||
//var csrftoken = jQuery("[name=csrfmiddlewaretoken]").val();
|
||||
var csrftoken = getCookie('csrftoken');
|
||||
|
||||
return $.ajax({
|
||||
beforeSend: function(xhr, settings) {
|
||||
beforeSend: function(xhr) {
|
||||
xhr.setRequestHeader('X-CSRFToken', csrftoken);
|
||||
},
|
||||
url: url,
|
||||
@ -157,29 +169,35 @@ function showApiError(xhr) {
|
||||
var message = null;
|
||||
|
||||
switch (xhr.status) {
|
||||
case 0: // No response
|
||||
// No response
|
||||
case 0:
|
||||
title = '{% trans "No Response" %}';
|
||||
message = '{% trans "No response from the InvenTree server" %}';
|
||||
break;
|
||||
case 400: // Bad request
|
||||
// Bad request
|
||||
case 400:
|
||||
// Note: Normally error code 400 is handled separately,
|
||||
// and should now be shown here!
|
||||
title = '{% trans "Error 400: Bad request" %}';
|
||||
message = '{% trans "API request returned error code 400" %}';
|
||||
break;
|
||||
case 401: // Not authenticated
|
||||
// Not authenticated
|
||||
case 401:
|
||||
title = '{% trans "Error 401: Not Authenticated" %}';
|
||||
message = '{% trans "Authentication credentials not supplied" %}';
|
||||
break;
|
||||
case 403: // Permission denied
|
||||
// Permission denied
|
||||
case 403:
|
||||
title = '{% trans "Error 403: Permission Denied" %}';
|
||||
message = '{% trans "You do not have the required permissions to access this function" %}';
|
||||
break;
|
||||
case 404: // Resource not found
|
||||
// Resource not found
|
||||
case 404:
|
||||
title = '{% trans "Error 404: Resource Not Found" %}';
|
||||
message = '{% trans "The requested resource could not be located on the server" %}';
|
||||
break;
|
||||
case 408: // Timeout
|
||||
// Timeout
|
||||
case 408:
|
||||
title = '{% trans "Error 408: Timeout" %}';
|
||||
message = '{% trans "Connection timeout while requesting data from server" %}';
|
||||
break;
|
||||
@ -189,8 +207,8 @@ function showApiError(xhr) {
|
||||
break;
|
||||
}
|
||||
|
||||
message += "<hr>";
|
||||
message += '<hr>';
|
||||
message += renderErrorMessage(xhr);
|
||||
|
||||
showAlertDialog(title, message);
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,18 @@
|
||||
{% load i18n %}
|
||||
|
||||
/* globals
|
||||
makeIconButton,
|
||||
renderLink,
|
||||
*/
|
||||
|
||||
/* exported
|
||||
loadAttachmentTable,
|
||||
reloadAttachmentTable,
|
||||
*/
|
||||
|
||||
function reloadAttachmentTable() {
|
||||
|
||||
$('#attachment-table').bootstrapTable("refresh");
|
||||
$('#attachment-table').bootstrapTable('refresh');
|
||||
}
|
||||
|
||||
|
||||
@ -13,7 +23,9 @@ function loadAttachmentTable(url, options) {
|
||||
$(table).inventreeTable({
|
||||
url: url,
|
||||
name: options.name || 'attachments',
|
||||
formatNoMatches: function() { return '{% trans "No attachments found" %}'},
|
||||
formatNoMatches: function() {
|
||||
return '{% trans "No attachments found" %}';
|
||||
},
|
||||
sortable: true,
|
||||
search: false,
|
||||
queryParams: options.filters || {},
|
||||
@ -40,7 +52,7 @@ function loadAttachmentTable(url, options) {
|
||||
{
|
||||
field: 'attachment',
|
||||
title: '{% trans "File" %}',
|
||||
formatter: function(value, row) {
|
||||
formatter: function(value) {
|
||||
|
||||
var icon = 'fa-file-alt';
|
||||
|
||||
@ -55,7 +67,7 @@ function loadAttachmentTable(url, options) {
|
||||
} else {
|
||||
var images = ['.png', '.jpg', '.bmp', '.gif', '.svg', '.tif'];
|
||||
|
||||
images.forEach(function (suffix) {
|
||||
images.forEach(function(suffix) {
|
||||
if (fn.endsWith(suffix)) {
|
||||
icon = 'fa-file-image';
|
||||
}
|
||||
@ -106,4 +118,4 @@ function loadAttachmentTable(url, options) {
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,27 @@
|
||||
{% load i18n %}
|
||||
|
||||
/* globals
|
||||
imageHoverIcon,
|
||||
inventreePut,
|
||||
makeIconButton,
|
||||
modalEnable,
|
||||
modalSetContent,
|
||||
modalSetTitle,
|
||||
modalSetSubmitText,
|
||||
modalShowSubmitButton,
|
||||
modalSubmit,
|
||||
showAlertOrCache,
|
||||
showQuestionDialog,
|
||||
*/
|
||||
|
||||
/* exported
|
||||
barcodeCheckIn,
|
||||
barcodeScanDialog,
|
||||
linkBarcodeDialog,
|
||||
scanItemsIntoLocation,
|
||||
unlinkBarcode,
|
||||
*/
|
||||
|
||||
function makeBarcodeInput(placeholderText='', hintText='') {
|
||||
/*
|
||||
* Generate HTML for a barcode input
|
||||
@ -99,7 +121,7 @@ function postBarcodeData(barcode_data, options={}) {
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -109,7 +131,7 @@ function showBarcodeMessage(modal, message, style='danger') {
|
||||
|
||||
html += message;
|
||||
|
||||
html += "</div>";
|
||||
html += '</div>';
|
||||
|
||||
$(modal + ' #barcode-error-message').html(html);
|
||||
}
|
||||
@ -256,7 +278,7 @@ function barcodeScanDialog() {
|
||||
var modal = '#modal-form';
|
||||
|
||||
barcodeDialog(
|
||||
"Scan Barcode",
|
||||
'{% trans "Scan Barcode" %}',
|
||||
{
|
||||
onScan: function(response) {
|
||||
if ('url' in response) {
|
||||
@ -280,18 +302,18 @@ function barcodeScanDialog() {
|
||||
/*
|
||||
* Dialog for linking a particular barcode to a stock item.
|
||||
*/
|
||||
function linkBarcodeDialog(stockitem, options={}) {
|
||||
function linkBarcodeDialog(stockitem) {
|
||||
|
||||
var modal = '#modal-form';
|
||||
|
||||
barcodeDialog(
|
||||
"{% trans 'Link Barcode to Stock Item' %}",
|
||||
'{% trans "Link Barcode to Stock Item" %}',
|
||||
{
|
||||
url: '/api/barcode/link/',
|
||||
data: {
|
||||
stockitem: stockitem,
|
||||
},
|
||||
onScan: function(response) {
|
||||
onScan: function() {
|
||||
|
||||
$(modal).modal('hide');
|
||||
location.reload();
|
||||
@ -308,13 +330,13 @@ function unlinkBarcode(stockitem) {
|
||||
|
||||
var html = `<b>{% trans "Unlink Barcode" %}</b><br>`;
|
||||
|
||||
html += "{% trans 'This will remove the association between this stock item and the barcode' %}";
|
||||
html += '{% trans "This will remove the association between this stock item and the barcode" %}';
|
||||
|
||||
showQuestionDialog(
|
||||
"{% trans 'Unlink Barcode' %}",
|
||||
'{% trans "Unlink Barcode" %}',
|
||||
html,
|
||||
{
|
||||
accept_text: "{% trans 'Unlink' %}",
|
||||
accept_text: '{% trans "Unlink" %}',
|
||||
accept: function() {
|
||||
inventreePut(
|
||||
`/api/stock/${stockitem}/`,
|
||||
@ -324,7 +346,7 @@ function unlinkBarcode(stockitem) {
|
||||
},
|
||||
{
|
||||
method: 'PATCH',
|
||||
success: function(response, status) {
|
||||
success: function() {
|
||||
location.reload();
|
||||
},
|
||||
},
|
||||
@ -338,7 +360,7 @@ function unlinkBarcode(stockitem) {
|
||||
/*
|
||||
* Display dialog to check multiple stock items in to a stock location.
|
||||
*/
|
||||
function barcodeCheckIn(location_id, options={}) {
|
||||
function barcodeCheckIn(location_id) {
|
||||
|
||||
var modal = '#modal-form';
|
||||
|
||||
@ -430,7 +452,9 @@ function barcodeCheckIn(location_id, options={}) {
|
||||
|
||||
// Called when the 'check-in' button is pressed
|
||||
|
||||
var data = {location: location_id};
|
||||
var data = {
|
||||
location: location_id
|
||||
};
|
||||
|
||||
// Extract 'notes' field
|
||||
data.notes = $(modal + ' #notes').val();
|
||||
@ -447,7 +471,7 @@ function barcodeCheckIn(location_id, options={}) {
|
||||
data.items = entries;
|
||||
|
||||
inventreePut(
|
||||
"{% url 'api-stock-transfer' %}",
|
||||
'{% url "api-stock-transfer" %}',
|
||||
data,
|
||||
{
|
||||
method: 'POST',
|
||||
@ -467,7 +491,7 @@ function barcodeCheckIn(location_id, options={}) {
|
||||
},
|
||||
onScan: function(response) {
|
||||
if ('stockitem' in response) {
|
||||
stockitem = response.stockitem;
|
||||
var stockitem = response.stockitem;
|
||||
|
||||
var duplicate = false;
|
||||
|
||||
@ -478,7 +502,7 @@ function barcodeCheckIn(location_id, options={}) {
|
||||
});
|
||||
|
||||
if (duplicate) {
|
||||
showBarcodeMessage(modal, '{% trans "Stock Item already scanned" %}', "warning");
|
||||
showBarcodeMessage(modal, '{% trans "Stock Item already scanned" %}', 'warning');
|
||||
} else {
|
||||
|
||||
if (stockitem.location == location_id) {
|
||||
@ -489,14 +513,14 @@ function barcodeCheckIn(location_id, options={}) {
|
||||
// Add this stock item to the list
|
||||
items.push(stockitem);
|
||||
|
||||
showBarcodeMessage(modal, '{% trans "Added stock item" %}', "success");
|
||||
showBarcodeMessage(modal, '{% trans "Added stock item" %}', 'success');
|
||||
|
||||
reloadTable();
|
||||
}
|
||||
|
||||
} else {
|
||||
// Barcode does not match a stock item
|
||||
showBarcodeMessage(modal, '{% trans "Barcode does not match Stock Item" %}', "warning");
|
||||
showBarcodeMessage(modal, '{% trans "Barcode does not match Stock Item" %}', 'warning');
|
||||
}
|
||||
},
|
||||
}
|
||||
@ -525,12 +549,12 @@ function scanItemsIntoLocation(item_id_list, options={}) {
|
||||
function updateLocationInfo(location) {
|
||||
var div = $(modal + ' #header-div');
|
||||
|
||||
if (stock_location && stock_location.pk) {
|
||||
if (location && location.pk) {
|
||||
div.html(`
|
||||
<div class='alert alert-block alert-info'>
|
||||
<b>{% trans "Location" %}</b></br>
|
||||
${stock_location.name}<br>
|
||||
<i>${stock_location.description}</i>
|
||||
${location.name}<br>
|
||||
<i>${location.description}</i>
|
||||
</div>
|
||||
`);
|
||||
} else {
|
||||
@ -561,7 +585,7 @@ function scanItemsIntoLocation(item_id_list, options={}) {
|
||||
items.push({
|
||||
pk: pk,
|
||||
});
|
||||
})
|
||||
});
|
||||
|
||||
var data = {
|
||||
location: stock_location.pk,
|
||||
@ -587,7 +611,7 @@ function scanItemsIntoLocation(item_id_list, options={}) {
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
},
|
||||
onScan: function(response) {
|
||||
updateLocationInfo(null);
|
||||
@ -603,10 +627,10 @@ function scanItemsIntoLocation(item_id_list, options={}) {
|
||||
showBarcodeMessage(
|
||||
modal,
|
||||
'{% trans "Barcode does not match a valid location" %}',
|
||||
"warning",
|
||||
'warning',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
);
|
||||
}
|
||||
|
@ -1,5 +1,25 @@
|
||||
{% load i18n %}
|
||||
|
||||
/* globals
|
||||
constructForm,
|
||||
imageHoverIcon,
|
||||
inventreeGet,
|
||||
inventreePut,
|
||||
launchModalForm,
|
||||
loadTableFilters,
|
||||
makePartIcons,
|
||||
renderLink,
|
||||
setupFilterList,
|
||||
yesNoLabel,
|
||||
*/
|
||||
|
||||
/* exported
|
||||
newPartFromBomWizard,
|
||||
loadBomTable,
|
||||
removeRowFromBomWizard,
|
||||
removeColFromBomWizard,
|
||||
*/
|
||||
|
||||
/* BOM management functions.
|
||||
* Requires follwing files to be loaded first:
|
||||
* - api.js
|
||||
@ -28,7 +48,7 @@ function bomItemFields() {
|
||||
}
|
||||
|
||||
|
||||
function reloadBomTable(table, options) {
|
||||
function reloadBomTable(table) {
|
||||
|
||||
table.bootstrapTable('refresh');
|
||||
}
|
||||
@ -126,7 +146,7 @@ function loadBomTable(table, options) {
|
||||
var params = {
|
||||
part: options.parent_id,
|
||||
ordering: 'name',
|
||||
}
|
||||
};
|
||||
|
||||
if (options.part_detail) {
|
||||
params.part_detail = true;
|
||||
@ -157,7 +177,7 @@ function loadBomTable(table, options) {
|
||||
checkbox: true,
|
||||
visible: true,
|
||||
switchable: false,
|
||||
formatter: function(value, row, index, field) {
|
||||
formatter: function(value, row) {
|
||||
// Disable checkbox if the row is defined for a *different* part!
|
||||
if (row.part != options.parent_id) {
|
||||
return {
|
||||
@ -182,7 +202,7 @@ function loadBomTable(table, options) {
|
||||
field: 'sub_part',
|
||||
title: '{% trans "Part" %}',
|
||||
sortable: true,
|
||||
formatter: function(value, row, index, field) {
|
||||
formatter: function(value, row) {
|
||||
var url = `/part/${row.sub_part}/`;
|
||||
var html = imageHoverIcon(row.sub_part_detail.thumbnail) + renderLink(row.sub_part_detail.full_name, url);
|
||||
|
||||
@ -225,7 +245,7 @@ function loadBomTable(table, options) {
|
||||
title: '{% trans "Quantity" %}',
|
||||
searchable: false,
|
||||
sortable: true,
|
||||
formatter: function(value, row, index, field) {
|
||||
formatter: function(value, row) {
|
||||
var text = value;
|
||||
|
||||
// The 'value' is a text string with (potentially) multiple trailing zeros
|
||||
@ -244,13 +264,12 @@ function loadBomTable(table, options) {
|
||||
},
|
||||
});
|
||||
|
||||
cols.push(
|
||||
{
|
||||
cols.push({
|
||||
field: 'sub_part_detail.stock',
|
||||
title: '{% trans "Available" %}',
|
||||
searchable: false,
|
||||
sortable: true,
|
||||
formatter: function(value, row, index, field) {
|
||||
formatter: function(value, row) {
|
||||
|
||||
var url = `/part/${row.sub_part_detail.pk}/?display=stock`;
|
||||
var text = value;
|
||||
@ -263,32 +282,29 @@ function loadBomTable(table, options) {
|
||||
}
|
||||
});
|
||||
|
||||
cols.push(
|
||||
{
|
||||
cols.push({
|
||||
field: 'purchase_price_range',
|
||||
title: '{% trans "Purchase Price Range" %}',
|
||||
searchable: false,
|
||||
sortable: true,
|
||||
});
|
||||
|
||||
cols.push(
|
||||
{
|
||||
cols.push({
|
||||
field: 'purchase_price_avg',
|
||||
title: '{% trans "Purchase Price Average" %}',
|
||||
searchable: false,
|
||||
sortable: true,
|
||||
});
|
||||
|
||||
cols.push(
|
||||
{
|
||||
cols.push({
|
||||
field: 'price_range',
|
||||
title: '{% trans "Supplier Cost" %}',
|
||||
sortable: true,
|
||||
formatter: function(value, row, index, field) {
|
||||
formatter: function(value) {
|
||||
if (value) {
|
||||
return value;
|
||||
} else {
|
||||
return "<span class='warning-msg'>{% trans 'No supplier pricing available' %}</span>";
|
||||
return `<span class='warning-msg'>{% trans 'No supplier pricing available' %}</span>`;
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -308,13 +324,13 @@ function loadBomTable(table, options) {
|
||||
formatter: function(value) {
|
||||
return yesNoLabel(value);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
cols.push({
|
||||
field: 'inherited',
|
||||
title: '{% trans "Inherited" %}',
|
||||
searchable: false,
|
||||
formatter: function(value, row, index, field) {
|
||||
formatter: function(value, row) {
|
||||
// This BOM item *is* inheritable, but is defined for this BOM
|
||||
if (!row.inherited) {
|
||||
return yesNoLabel(false);
|
||||
@ -332,9 +348,9 @@ function loadBomTable(table, options) {
|
||||
|
||||
cols.push(
|
||||
{
|
||||
'field': 'can_build',
|
||||
'title': '{% trans "Can Build" %}',
|
||||
formatter: function(value, row, index, field) {
|
||||
field: 'can_build',
|
||||
title: '{% trans "Can Build" %}',
|
||||
formatter: function(value, row) {
|
||||
var can_build = 0;
|
||||
|
||||
if (row.quantity > 0) {
|
||||
@ -360,7 +376,7 @@ function loadBomTable(table, options) {
|
||||
},
|
||||
sortable: true,
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
// Part notes
|
||||
cols.push(
|
||||
@ -379,7 +395,7 @@ function loadBomTable(table, options) {
|
||||
switchable: false,
|
||||
field: 'pk',
|
||||
visible: true,
|
||||
formatter: function(value, row, index, field) {
|
||||
formatter: function(value, row) {
|
||||
|
||||
if (row.part == options.parent_id) {
|
||||
|
||||
@ -391,7 +407,7 @@ function loadBomTable(table, options) {
|
||||
|
||||
var bDelt = `<button title='{% trans "Delete BOM Item" %}' class='bom-delete-button btn btn-default btn-glyph' type='button' pk='${row.pk}'><span class='fas fa-trash-alt icon-red'></span></button>`;
|
||||
|
||||
var html = "<div class='btn-group' role='group'>";
|
||||
var html = `<div class='btn-group' role='group'>`;
|
||||
|
||||
html += bEdit;
|
||||
html += bDelt;
|
||||
@ -402,7 +418,7 @@ function loadBomTable(table, options) {
|
||||
html += bValid;
|
||||
}
|
||||
|
||||
html += "</div>";
|
||||
html += `</div>`;
|
||||
|
||||
return html;
|
||||
} else {
|
||||
@ -434,7 +450,7 @@ function loadBomTable(table, options) {
|
||||
response[idx].parentId = bom_pk;
|
||||
|
||||
if (response[idx].sub_part_detail.assembly) {
|
||||
requestSubItems(response[idx].pk, response[idx].sub_part)
|
||||
requestSubItems(response[idx].pk, response[idx].sub_part);
|
||||
}
|
||||
}
|
||||
|
||||
@ -446,7 +462,7 @@ function loadBomTable(table, options) {
|
||||
console.log('Error requesting BOM for part=' + part_pk);
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
table.inventreeTable({
|
||||
@ -459,7 +475,7 @@ function loadBomTable(table, options) {
|
||||
name: 'bom',
|
||||
sortable: true,
|
||||
search: true,
|
||||
rowStyle: function(row, index) {
|
||||
rowStyle: function(row) {
|
||||
|
||||
var classes = [];
|
||||
|
||||
@ -532,7 +548,6 @@ function loadBomTable(table, options) {
|
||||
table.on('click', '.bom-delete-button', function() {
|
||||
|
||||
var pk = $(this).attr('pk');
|
||||
var url = `/part/bom/${pk}/delete/`;
|
||||
|
||||
constructForm(`/api/bom/${pk}/`, {
|
||||
method: 'DELETE',
|
||||
@ -546,7 +561,6 @@ function loadBomTable(table, options) {
|
||||
table.on('click', '.bom-edit-button', function() {
|
||||
|
||||
var pk = $(this).attr('pk');
|
||||
var url = `/part/bom/${pk}/edit/`;
|
||||
|
||||
var fields = bomItemFields();
|
||||
|
||||
|
@ -1,6 +1,33 @@
|
||||
{% load i18n %}
|
||||
{% load inventree_extras %}
|
||||
|
||||
/* globals
|
||||
buildStatusDisplay,
|
||||
constructForm,
|
||||
getFieldByName,
|
||||
global_settings,
|
||||
imageHoverIcon,
|
||||
inventreeGet,
|
||||
launchModalForm,
|
||||
linkButtonsToSelection,
|
||||
loadTableFilters,
|
||||
makeIconBadge,
|
||||
makeIconButton,
|
||||
makePartIcons,
|
||||
makeProgressBar,
|
||||
renderLink,
|
||||
setupFilterList,
|
||||
*/
|
||||
|
||||
/* exported
|
||||
editBuildOrder,
|
||||
loadAllocationTable,
|
||||
loadBuildOrderAllocationTable,
|
||||
loadBuildOutputAllocationTable,
|
||||
loadBuildPartsTable,
|
||||
loadBuildTable,
|
||||
*/
|
||||
|
||||
|
||||
function buildFormFields() {
|
||||
return {
|
||||
@ -32,7 +59,7 @@ function buildFormFields() {
|
||||
}
|
||||
|
||||
|
||||
function editBuildOrder(pk, options={}) {
|
||||
function editBuildOrder(pk) {
|
||||
|
||||
var fields = buildFormFields();
|
||||
|
||||
@ -76,10 +103,10 @@ function makeBuildOutputActionButtons(output, buildInfo, lines) {
|
||||
|
||||
var buildId = buildInfo.pk;
|
||||
|
||||
var outputId = 'untracked';
|
||||
|
||||
if (output) {
|
||||
outputId = output.pk;
|
||||
} else {
|
||||
outputId = 'untracked';
|
||||
}
|
||||
|
||||
var panel = `#allocation-panel-${outputId}`;
|
||||
@ -98,7 +125,7 @@ function makeBuildOutputActionButtons(output, buildInfo, lines) {
|
||||
html += makeIconButton(
|
||||
'fa-magic icon-blue', 'button-output-auto', outputId,
|
||||
'{% trans "Auto-allocate stock items to this output" %}',
|
||||
);
|
||||
);
|
||||
}
|
||||
|
||||
if (lines > 0) {
|
||||
@ -106,7 +133,7 @@ function makeBuildOutputActionButtons(output, buildInfo, lines) {
|
||||
html += makeIconButton(
|
||||
'fa-minus-circle icon-red', 'button-output-unallocate', outputId,
|
||||
'{% trans "Unallocate stock from build output" %}',
|
||||
);
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -117,7 +144,7 @@ function makeBuildOutputActionButtons(output, buildInfo, lines) {
|
||||
'fa-check icon-green', 'button-output-complete', outputId,
|
||||
'{% trans "Complete build output" %}',
|
||||
{
|
||||
//disabled: true
|
||||
// disabled: true
|
||||
}
|
||||
);
|
||||
|
||||
@ -125,7 +152,7 @@ function makeBuildOutputActionButtons(output, buildInfo, lines) {
|
||||
html += makeIconButton(
|
||||
'fa-trash-alt icon-red', 'button-output-delete', outputId,
|
||||
'{% trans "Delete build output" %}',
|
||||
);
|
||||
);
|
||||
|
||||
// TODO - Add a button to "destroy" the particular build output (mark as damaged, scrap)
|
||||
}
|
||||
@ -202,13 +229,13 @@ function loadBuildOrderAllocationTable(table, options={}) {
|
||||
options.params['build_detail'] = true;
|
||||
options.params['location_detail'] = true;
|
||||
|
||||
var filters = loadTableFilters("buildorderallocation");
|
||||
var filters = loadTableFilters('buildorderallocation');
|
||||
|
||||
for (var key in options.params) {
|
||||
filters[key] = options.params[key];
|
||||
}
|
||||
|
||||
setupFilterList("buildorderallocation", $(table));
|
||||
setupFilterList('buildorderallocation', $(table));
|
||||
|
||||
$(table).inventreeTable({
|
||||
url: '{% url "api-build-item-list" %}',
|
||||
@ -219,7 +246,7 @@ function loadBuildOrderAllocationTable(table, options={}) {
|
||||
paginationVAlign: 'bottom',
|
||||
original: options.params,
|
||||
formatNoMatches: function() {
|
||||
return '{% trans "No build order allocations found" %}'
|
||||
return '{% trans "No build order allocations found" %}';
|
||||
},
|
||||
columns: [
|
||||
{
|
||||
@ -345,13 +372,13 @@ function loadBuildOutputAllocationTable(buildInfo, output, options={}) {
|
||||
// Register button callbacks once table data are loaded
|
||||
|
||||
// Callback for 'allocate' button
|
||||
$(table).find(".button-add").click(function() {
|
||||
$(table).find('.button-add').click(function() {
|
||||
|
||||
// Primary key of the 'sub_part'
|
||||
var pk = $(this).attr('pk');
|
||||
|
||||
// Launch form to allocate new stock against this output
|
||||
launchModalForm("{% url 'build-item-create' %}", {
|
||||
launchModalForm('{% url "build-item-create" %}', {
|
||||
success: reloadTable,
|
||||
data: {
|
||||
part: pk,
|
||||
@ -391,7 +418,7 @@ function loadBuildOutputAllocationTable(buildInfo, output, options={}) {
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
]
|
||||
@ -400,10 +427,8 @@ function loadBuildOutputAllocationTable(buildInfo, output, options={}) {
|
||||
|
||||
// Callback for 'buy' button
|
||||
$(table).find('.button-buy').click(function() {
|
||||
var pk = $(this).attr('pk');
|
||||
|
||||
var idx = $(this).closest('tr').attr('data-index');
|
||||
var row = $(table).bootstrapTable('getData')[idx];
|
||||
var pk = $(this).attr('pk');
|
||||
|
||||
launchModalForm('{% url "order-parts" %}', {
|
||||
data: {
|
||||
@ -447,7 +472,7 @@ function loadBuildOutputAllocationTable(buildInfo, output, options={}) {
|
||||
|
||||
// Load table of BOM items
|
||||
$(table).inventreeTable({
|
||||
url: "{% url 'api-bom-list' %}",
|
||||
url: '{% url "api-bom-list" %}',
|
||||
queryParams: {
|
||||
part: partId,
|
||||
sub_part_detail: true,
|
||||
@ -467,7 +492,7 @@ function loadBuildOutputAllocationTable(buildInfo, output, options={}) {
|
||||
build: buildId,
|
||||
part_detail: true,
|
||||
location_detail: true,
|
||||
}
|
||||
};
|
||||
|
||||
if (output) {
|
||||
params.sub_part_trackable = true;
|
||||
@ -496,7 +521,7 @@ function loadBuildOutputAllocationTable(buildInfo, output, options={}) {
|
||||
var key = parseInt(part);
|
||||
|
||||
if (!(key in allocations)) {
|
||||
allocations[key] = new Array();
|
||||
allocations[key] = [];
|
||||
}
|
||||
|
||||
allocations[key].push(item);
|
||||
@ -573,8 +598,6 @@ function loadBuildOutputAllocationTable(buildInfo, output, options={}) {
|
||||
|
||||
element.html(html);
|
||||
|
||||
var lineItem = row;
|
||||
|
||||
var subTable = $(`#${subTableId}`);
|
||||
|
||||
subTable.bootstrapTable({
|
||||
@ -595,7 +618,7 @@ function loadBuildOutputAllocationTable(buildInfo, output, options={}) {
|
||||
width: '50%',
|
||||
field: 'quantity',
|
||||
title: '{% trans "Assigned Stock" %}',
|
||||
formatter: function(value, row, index, field) {
|
||||
formatter: function(value, row) {
|
||||
var text = '';
|
||||
|
||||
var url = '';
|
||||
@ -618,7 +641,7 @@ function loadBuildOutputAllocationTable(buildInfo, output, options={}) {
|
||||
{
|
||||
field: 'location',
|
||||
title: '{% trans "Location" %}',
|
||||
formatter: function(value, row, index, field) {
|
||||
formatter: function(value, row) {
|
||||
if (row.stock_item_detail.location) {
|
||||
var text = row.stock_item_detail.location_name;
|
||||
var url = `/stock/location/${row.stock_item_detail.location}/`;
|
||||
@ -631,7 +654,7 @@ function loadBuildOutputAllocationTable(buildInfo, output, options={}) {
|
||||
},
|
||||
{
|
||||
field: 'actions',
|
||||
formatter: function(value, row, index, field) {
|
||||
formatter: function(value, row) {
|
||||
/* Actions available for a particular stock item allocation:
|
||||
*
|
||||
* - Edit the allocation quantity
|
||||
@ -678,7 +701,7 @@ function loadBuildOutputAllocationTable(buildInfo, output, options={}) {
|
||||
field: 'sub_part_detail.full_name',
|
||||
title: '{% trans "Required Part" %}',
|
||||
sortable: true,
|
||||
formatter: function(value, row, index, field) {
|
||||
formatter: function(value, row) {
|
||||
var url = `/part/${row.sub_part}/`;
|
||||
var thumb = row.sub_part_detail.thumbnail;
|
||||
var name = row.sub_part_detail.full_name;
|
||||
@ -709,7 +732,7 @@ function loadBuildOutputAllocationTable(buildInfo, output, options={}) {
|
||||
field: 'allocated',
|
||||
title: '{% trans "Allocated" %}',
|
||||
sortable: true,
|
||||
formatter: function(value, row, index, field) {
|
||||
formatter: function(value, row) {
|
||||
var allocated = 0;
|
||||
|
||||
if (row.allocations) {
|
||||
@ -757,7 +780,7 @@ function loadBuildOutputAllocationTable(buildInfo, output, options={}) {
|
||||
{
|
||||
field: 'actions',
|
||||
title: '{% trans "Actions" %}',
|
||||
formatter: function(value, row, index, field) {
|
||||
formatter: function(value, row) {
|
||||
// Generate action buttons for this build output
|
||||
var html = `<div class='btn-group float-right' role='group'>`;
|
||||
|
||||
@ -804,7 +827,7 @@ function loadBuildTable(table, options) {
|
||||
params['part_detail'] = true;
|
||||
|
||||
if (!options.disableFilters) {
|
||||
filters = loadTableFilters("build");
|
||||
filters = loadTableFilters('build');
|
||||
}
|
||||
|
||||
for (var key in params) {
|
||||
@ -815,7 +838,7 @@ function loadBuildTable(table, options) {
|
||||
|
||||
var filterTarget = options.filterTarget || null;
|
||||
|
||||
setupFilterList("build", table, filterTarget);
|
||||
setupFilterList('build', table, filterTarget);
|
||||
|
||||
$(table).inventreeTable({
|
||||
method: 'get',
|
||||
@ -846,7 +869,7 @@ function loadBuildTable(table, options) {
|
||||
title: '{% trans "Build" %}',
|
||||
sortable: true,
|
||||
switchable: true,
|
||||
formatter: function(value, row, index, field) {
|
||||
formatter: function(value, row) {
|
||||
|
||||
var prefix = global_settings.BUILDORDER_REFERENCE_PREFIX;
|
||||
|
||||
@ -873,7 +896,7 @@ function loadBuildTable(table, options) {
|
||||
title: '{% trans "Part" %}',
|
||||
sortable: true,
|
||||
sortName: 'part__name',
|
||||
formatter: function(value, row, index, field) {
|
||||
formatter: function(value, row) {
|
||||
|
||||
var html = imageHoverIcon(row.part_detail.thumbnail);
|
||||
|
||||
@ -887,12 +910,12 @@ function loadBuildTable(table, options) {
|
||||
field: 'quantity',
|
||||
title: '{% trans "Completed" %}',
|
||||
sortable: true,
|
||||
formatter: function(value, row, index, field) {
|
||||
formatter: function(value, row) {
|
||||
return makeProgressBar(
|
||||
row.completed,
|
||||
row.quantity,
|
||||
{
|
||||
//style: 'max',
|
||||
// style: 'max',
|
||||
}
|
||||
);
|
||||
}
|
||||
@ -901,7 +924,7 @@ function loadBuildTable(table, options) {
|
||||
field: 'status',
|
||||
title: '{% trans "Status" %}',
|
||||
sortable: true,
|
||||
formatter: function(value, row, index, field) {
|
||||
formatter: function(value) {
|
||||
return buildStatusDisplay(value);
|
||||
},
|
||||
},
|
||||
@ -914,13 +937,10 @@ function loadBuildTable(table, options) {
|
||||
field: 'issued_by',
|
||||
title: '{% trans "Issued by" %}',
|
||||
sortable: true,
|
||||
formatter: function(value, row, index, field) {
|
||||
if (value)
|
||||
{
|
||||
formatter: function(value, row) {
|
||||
if (value) {
|
||||
return row.issued_by_detail.username;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
return `<i>{% trans "No user information" %}</i>`;
|
||||
}
|
||||
}
|
||||
@ -929,13 +949,10 @@ function loadBuildTable(table, options) {
|
||||
field: 'responsible',
|
||||
title: '{% trans "Responsible" %}',
|
||||
sortable: true,
|
||||
formatter: function(value, row, index, field) {
|
||||
if (value)
|
||||
{
|
||||
formatter: function(value, row) {
|
||||
if (value) {
|
||||
return row.responsible_detail.name;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
return '{% trans "No information" %}';
|
||||
}
|
||||
}
|
||||
@ -968,7 +985,7 @@ function updateAllocationTotal(id, count, required) {
|
||||
|
||||
$('#allocation-total-'+id).html(count);
|
||||
|
||||
var el = $("#allocation-panel-" + id);
|
||||
var el = $('#allocation-panel-' + id);
|
||||
el.removeClass('part-allocation-pass part-allocation-underallocated part-allocation-overallocated');
|
||||
|
||||
if (count < required) {
|
||||
@ -986,32 +1003,39 @@ function loadAllocationTable(table, part_id, part, url, required, button) {
|
||||
table.bootstrapTable({
|
||||
url: url,
|
||||
sortable: false,
|
||||
formatNoMatches: function() { return '{% trans "No parts allocated for" %} ' + part; },
|
||||
formatNoMatches: function() {
|
||||
return '{% trans "No parts allocated for" %} ' + part;
|
||||
},
|
||||
columns: [
|
||||
{
|
||||
field: 'stock_item_detail',
|
||||
title: '{% trans "Stock Item" %}',
|
||||
formatter: function(value, row, index, field) {
|
||||
formatter: function(value) {
|
||||
return '' + parseFloat(value.quantity) + ' x ' + value.part_name + ' @ ' + value.location_name;
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'stock_item_detail.quantity',
|
||||
title: '{% trans "Available" %}',
|
||||
formatter: function(value, row, index, field) {
|
||||
formatter: function(value) {
|
||||
return parseFloat(value);
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'quantity',
|
||||
title: '{% trans "Allocated" %}',
|
||||
formatter: function(value, row, index, field) {
|
||||
formatter: function(value, row) {
|
||||
var html = parseFloat(value);
|
||||
|
||||
var bEdit = "<button class='btn item-edit-button btn-sm' type='button' title='{% trans "Edit stock allocation" %}' url='/build/item/" + row.pk + "/edit/'><span class='fas fa-edit'></span></button>";
|
||||
var bDel = "<button class='btn item-del-button btn-sm' type='button' title='{% trans "Delete stock allocation" %}' url='/build/item/" + row.pk + "/delete/'><span class='fas fa-trash-alt icon-red'></span></button>";
|
||||
var bEdit = `<button class='btn item-edit-button btn-sm' type='button' title='{% trans "Edit stock allocation" %}' url='/build/item/${row.pk}/edit/'><span class='fas fa-edit'></span></button>`;
|
||||
var bDel = `<button class='btn item-del-button btn-sm' type='button' title='{% trans "Delete stock allocation" %}' url='/build/item/${row.pk}/delete/'><span class='fas fa-trash-alt icon-red'></span></button>`;
|
||||
|
||||
html += "<div class='btn-group' style='float: right;'>" + bEdit + bDel + "</div>";
|
||||
html += `
|
||||
<div class='btn-group' style='float: right;'>
|
||||
${bEdit}
|
||||
${bDel}
|
||||
</div>
|
||||
`;
|
||||
|
||||
return html;
|
||||
}
|
||||
@ -1028,7 +1052,7 @@ function loadAllocationTable(table, part_id, part, url, required, button) {
|
||||
});
|
||||
});
|
||||
|
||||
table.on('load-success.bs.table', function(data) {
|
||||
table.on('load-success.bs.table', function() {
|
||||
// Extract table data
|
||||
var results = table.bootstrapTable('getData');
|
||||
|
||||
@ -1106,9 +1130,6 @@ function loadBuildPartsTable(table, options={}) {
|
||||
$(table).find('.button-buy').click(function() {
|
||||
var pk = $(this).attr('pk');
|
||||
|
||||
var idx = $(this).closest('tr').attr('data-index');
|
||||
var row = $(table).bootstrapTable('getData')[idx];
|
||||
|
||||
launchModalForm('{% url "order-parts" %}', {
|
||||
data: {
|
||||
parts: [
|
||||
@ -1122,10 +1143,6 @@ function loadBuildPartsTable(table, options={}) {
|
||||
$(table).find('.button-build').click(function() {
|
||||
var pk = $(this).attr('pk');
|
||||
|
||||
// Extract row data from the table
|
||||
var idx = $(this).closest('tr').attr('data-index');
|
||||
var row = $(table).bootstrapTable('getData')[idx];
|
||||
|
||||
newBuildOrder({
|
||||
part: pk,
|
||||
parent: options.build,
|
||||
@ -1139,7 +1156,7 @@ function loadBuildPartsTable(table, options={}) {
|
||||
title: '{% trans "Part" %}',
|
||||
switchable: false,
|
||||
sortable: true,
|
||||
formatter: function(value, row, index, field) {
|
||||
formatter: function(value, row) {
|
||||
var url = `/part/${row.sub_part}/`;
|
||||
var html = imageHoverIcon(row.sub_part_detail.thumbnail) + renderLink(row.sub_part_detail.full_name, url);
|
||||
|
||||
@ -1177,7 +1194,7 @@ function loadBuildPartsTable(table, options={}) {
|
||||
switchable: false,
|
||||
field: 'sub_part_detail.stock',
|
||||
title: '{% trans "Available" %}',
|
||||
formatter: function(value, row, index, field) {
|
||||
formatter: function(value, row) {
|
||||
return makeProgressBar(
|
||||
value,
|
||||
row.quantity * options.build_remaining,
|
||||
@ -1201,7 +1218,7 @@ function loadBuildPartsTable(table, options={}) {
|
||||
field: 'actions',
|
||||
title: '{% trans "Actions" %}',
|
||||
switchable: false,
|
||||
formatter: function(value, row, index, field) {
|
||||
formatter: function(value, row) {
|
||||
|
||||
// Generate action buttons against the part
|
||||
var html = `<div class='btn-group float-right' role='group'>`;
|
||||
@ -1228,7 +1245,7 @@ function loadBuildPartsTable(table, options={}) {
|
||||
sortable: true,
|
||||
search: true,
|
||||
onPostBody: setupTableCallbacks,
|
||||
rowStyle: function(row, index) {
|
||||
rowStyle: function(row) {
|
||||
var classes = [];
|
||||
|
||||
// Shade rows differently if they are for different parent parts
|
||||
@ -1254,4 +1271,4 @@ function loadBuildPartsTable(table, options={}) {
|
||||
original: params,
|
||||
columns: columns,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,33 @@
|
||||
{% load i18n %}
|
||||
|
||||
/* globals
|
||||
constructForm,
|
||||
imageHoverIcon,
|
||||
inventreeDelete,
|
||||
loadTableFilters,
|
||||
makeIconButton,
|
||||
renderLink,
|
||||
setupFilterList,
|
||||
showQuestionDialog,
|
||||
*/
|
||||
|
||||
/* exported
|
||||
createCompany,
|
||||
createManufacturerPart,
|
||||
createSupplierPart,
|
||||
deleteManufacturerParts,
|
||||
editCompany,
|
||||
loadCompanyTable,
|
||||
loadManufacturerPartTable,
|
||||
loadManufacturerPartParameterTable,
|
||||
loadSupplierPartTable,
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Construct a set of form fields for creating / editing a ManufacturerPart
|
||||
* @returns
|
||||
*/
|
||||
function manufacturerPartFields() {
|
||||
|
||||
return {
|
||||
@ -17,6 +44,10 @@ function manufacturerPartFields() {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Launches a form to create a new ManufacturerPart
|
||||
* @param {object} options
|
||||
*/
|
||||
function createManufacturerPart(options={}) {
|
||||
|
||||
var fields = manufacturerPartFields();
|
||||
@ -32,14 +63,14 @@ function createManufacturerPart(options={}) {
|
||||
|
||||
fields.manufacturer.secondary = {
|
||||
title: '{% trans "Add Manufacturer" %}',
|
||||
fields: function(data) {
|
||||
fields: function() {
|
||||
var company_fields = companyFormFields();
|
||||
|
||||
company_fields.is_manufacturer.value = true;
|
||||
|
||||
return company_fields;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
constructForm('{% url "api-manufacturer-part-list" %}', {
|
||||
fields: fields,
|
||||
@ -50,6 +81,11 @@ function createManufacturerPart(options={}) {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Launches a form to edit a ManufacturerPart
|
||||
* @param {integer} part - ID of a ManufacturerPart
|
||||
* @param {object} options
|
||||
*/
|
||||
function editManufacturerPart(part, options={}) {
|
||||
|
||||
var url = `/api/company/part/manufacturer/${part}/`;
|
||||
@ -126,7 +162,7 @@ function createSupplierPart(options={}) {
|
||||
// Add a secondary modal for the supplier
|
||||
fields.supplier.secondary = {
|
||||
title: '{% trans "Add Supplier" %}',
|
||||
fields: function(data) {
|
||||
fields: function() {
|
||||
var company_fields = companyFormFields();
|
||||
|
||||
company_fields.is_supplier.value = true;
|
||||
@ -185,7 +221,7 @@ function deleteSupplierPart(part, options={}) {
|
||||
|
||||
|
||||
// Returns a default form-set for creating / editing a Company object
|
||||
function companyFormFields(options={}) {
|
||||
function companyFormFields() {
|
||||
|
||||
return {
|
||||
name: {},
|
||||
@ -228,7 +264,7 @@ function editCompany(pk, options={}) {
|
||||
title: '{% trans "Edit Company" %}',
|
||||
}
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
/*
|
||||
* Launches a form to create a new company.
|
||||
@ -265,13 +301,13 @@ function loadCompanyTable(table, url, options={}) {
|
||||
// Query parameters
|
||||
var params = options.params || {};
|
||||
|
||||
var filters = loadTableFilters("company");
|
||||
var filters = loadTableFilters('company');
|
||||
|
||||
for (var key in params) {
|
||||
filters[key] = params[key];
|
||||
}
|
||||
|
||||
setupFilterList("company", $(table));
|
||||
setupFilterList('company', $(table));
|
||||
|
||||
var columns = [
|
||||
{
|
||||
@ -285,7 +321,7 @@ function loadCompanyTable(table, url, options={}) {
|
||||
title: '{% trans "Company" %}',
|
||||
sortable: true,
|
||||
switchable: false,
|
||||
formatter: function(value, row, index, field) {
|
||||
formatter: function(value, row) {
|
||||
var html = imageHoverIcon(row.image) + renderLink(value, row.url);
|
||||
|
||||
if (row.is_customer) {
|
||||
@ -310,7 +346,7 @@ function loadCompanyTable(table, url, options={}) {
|
||||
{
|
||||
field: 'website',
|
||||
title: '{% trans "Website" %}',
|
||||
formatter: function(value, row, index, field) {
|
||||
formatter: function(value) {
|
||||
if (value) {
|
||||
return renderLink(value, value);
|
||||
}
|
||||
@ -345,7 +381,9 @@ function loadCompanyTable(table, url, options={}) {
|
||||
queryParams: filters,
|
||||
groupBy: false,
|
||||
sidePagination: 'server',
|
||||
formatNoMatches: function() { return "{% trans "No company information found" %}"; },
|
||||
formatNoMatches: function() {
|
||||
return '{% trans "No company information found" %}';
|
||||
},
|
||||
showColumns: true,
|
||||
name: options.pagetype || 'company',
|
||||
columns: columns,
|
||||
@ -366,18 +404,18 @@ function deleteManufacturerParts(selections, options={}) {
|
||||
<p>{% trans "The following manufacturer parts will be deleted" %}:</p>
|
||||
<ul>`;
|
||||
|
||||
selections.forEach(function(item) {
|
||||
parts.push(item.pk);
|
||||
selections.forEach(function(item) {
|
||||
parts.push(item.pk);
|
||||
|
||||
text += `
|
||||
<li>
|
||||
<p>${item.MPN} - ${item.part_detail.full_name}</p>
|
||||
</li>`;
|
||||
});
|
||||
|
||||
text += `
|
||||
</ul>
|
||||
</div>`;
|
||||
<li>
|
||||
<p>${item.MPN} - ${item.part_detail.full_name}</p>
|
||||
</li>`;
|
||||
});
|
||||
|
||||
text += `
|
||||
</ul>
|
||||
</div>`;
|
||||
|
||||
showQuestionDialog(
|
||||
'{% trans "Delete Manufacturer Parts" %}',
|
||||
@ -401,7 +439,7 @@ function deleteManufacturerParts(selections, options={}) {
|
||||
if (options.onSuccess) {
|
||||
options.onSuccess();
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
@ -418,13 +456,13 @@ function loadManufacturerPartTable(table, url, options) {
|
||||
var params = options.params || {};
|
||||
|
||||
// Load filters
|
||||
var filters = loadTableFilters("manufacturer-part");
|
||||
var filters = loadTableFilters('manufacturer-part');
|
||||
|
||||
for (var key in params) {
|
||||
filters[key] = params[key];
|
||||
}
|
||||
|
||||
setupFilterList("manufacturer-part", $(table));
|
||||
setupFilterList('manufacturer-part', $(table));
|
||||
|
||||
$(table).inventreeTable({
|
||||
url: url,
|
||||
@ -433,7 +471,9 @@ function loadManufacturerPartTable(table, url, options) {
|
||||
queryParams: filters,
|
||||
name: 'manufacturerparts',
|
||||
groupBy: false,
|
||||
formatNoMatches: function() { return '{% trans "No manufacturer parts found" %}'; },
|
||||
formatNoMatches: function() {
|
||||
return '{% trans "No manufacturer parts found" %}';
|
||||
},
|
||||
columns: [
|
||||
{
|
||||
checkbox: true,
|
||||
@ -445,7 +485,7 @@ function loadManufacturerPartTable(table, url, options) {
|
||||
sortable: true,
|
||||
field: 'part_detail.full_name',
|
||||
title: '{% trans "Part" %}',
|
||||
formatter: function(value, row, index, field) {
|
||||
formatter: function(value, row) {
|
||||
|
||||
var url = `/part/${row.part}/`;
|
||||
|
||||
@ -470,7 +510,7 @@ function loadManufacturerPartTable(table, url, options) {
|
||||
sortable: true,
|
||||
field: 'manufacturer',
|
||||
title: '{% trans "Manufacturer" %}',
|
||||
formatter: function(value, row, index, field) {
|
||||
formatter: function(value, row) {
|
||||
if (value && row.manufacturer_detail) {
|
||||
var name = row.manufacturer_detail.name;
|
||||
var url = `/company/${value}/`;
|
||||
@ -478,7 +518,7 @@ function loadManufacturerPartTable(table, url, options) {
|
||||
|
||||
return html;
|
||||
} else {
|
||||
return "-";
|
||||
return '-';
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -486,14 +526,14 @@ function loadManufacturerPartTable(table, url, options) {
|
||||
sortable: true,
|
||||
field: 'MPN',
|
||||
title: '{% trans "MPN" %}',
|
||||
formatter: function(value, row, index, field) {
|
||||
formatter: function(value, row) {
|
||||
return renderLink(value, `/manufacturer-part/${row.pk}/`);
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'link',
|
||||
title: '{% trans "Link" %}',
|
||||
formatter: function(value, row, index, field) {
|
||||
formatter: function(value) {
|
||||
if (value) {
|
||||
return renderLink(value, value);
|
||||
} else {
|
||||
@ -536,8 +576,9 @@ function loadManufacturerPartTable(table, url, options) {
|
||||
{
|
||||
onSuccess: function() {
|
||||
$(table).bootstrapTable('refresh');
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
});
|
||||
|
||||
$(table).find('.button-manufacturer-part-delete').click(function() {
|
||||
@ -548,9 +589,10 @@ function loadManufacturerPartTable(table, url, options) {
|
||||
{
|
||||
onSuccess: function() {
|
||||
$(table).bootstrapTable('refresh');
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -564,7 +606,7 @@ function loadManufacturerPartParameterTable(table, url, options) {
|
||||
var params = options.params || {};
|
||||
|
||||
// Load filters
|
||||
var filters = loadTableFilters("manufacturer-part-parameters");
|
||||
var filters = loadTableFilters('manufacturer-part-parameters');
|
||||
|
||||
// Overwrite explicit parameters
|
||||
for (var key in params) {
|
||||
@ -580,7 +622,9 @@ function loadManufacturerPartParameterTable(table, url, options) {
|
||||
queryParams: filters,
|
||||
name: 'manufacturerpartparameters',
|
||||
groupBy: false,
|
||||
formatNoMatches: function() { return '{% trans "No parameters found" %}'; },
|
||||
formatNoMatches: function() {
|
||||
return '{% trans "No parameters found" %}';
|
||||
},
|
||||
columns: [
|
||||
{
|
||||
checkbox: true,
|
||||
@ -668,13 +712,13 @@ function loadSupplierPartTable(table, url, options) {
|
||||
var params = options.params || {};
|
||||
|
||||
// Load filters
|
||||
var filters = loadTableFilters("supplier-part");
|
||||
var filters = loadTableFilters('supplier-part');
|
||||
|
||||
for (var key in params) {
|
||||
filters[key] = params[key];
|
||||
}
|
||||
|
||||
setupFilterList("supplier-part", $(table));
|
||||
setupFilterList('supplier-part', $(table));
|
||||
|
||||
$(table).inventreeTable({
|
||||
url: url,
|
||||
@ -683,7 +727,9 @@ function loadSupplierPartTable(table, url, options) {
|
||||
queryParams: filters,
|
||||
name: 'supplierparts',
|
||||
groupBy: false,
|
||||
formatNoMatches: function() { return '{% trans "No supplier parts found" %}'; },
|
||||
formatNoMatches: function() {
|
||||
return '{% trans "No supplier parts found" %}';
|
||||
},
|
||||
columns: [
|
||||
{
|
||||
checkbox: true,
|
||||
@ -695,7 +741,7 @@ function loadSupplierPartTable(table, url, options) {
|
||||
sortable: true,
|
||||
field: 'part_detail.full_name',
|
||||
title: '{% trans "Part" %}',
|
||||
formatter: function(value, row, index, field) {
|
||||
formatter: function(value, row) {
|
||||
|
||||
var url = `/part/${row.part}/`;
|
||||
|
||||
@ -720,7 +766,7 @@ function loadSupplierPartTable(table, url, options) {
|
||||
sortable: true,
|
||||
field: 'supplier',
|
||||
title: '{% trans "Supplier" %}',
|
||||
formatter: function(value, row, index, field) {
|
||||
formatter: function(value, row) {
|
||||
if (value) {
|
||||
var name = row.supplier_detail.name;
|
||||
var url = `/company/${value}/`;
|
||||
@ -728,7 +774,7 @@ function loadSupplierPartTable(table, url, options) {
|
||||
|
||||
return html;
|
||||
} else {
|
||||
return "-";
|
||||
return '-';
|
||||
}
|
||||
},
|
||||
},
|
||||
@ -736,7 +782,7 @@ function loadSupplierPartTable(table, url, options) {
|
||||
sortable: true,
|
||||
field: 'SKU',
|
||||
title: '{% trans "Supplier Part" %}',
|
||||
formatter: function(value, row, index, field) {
|
||||
formatter: function(value, row) {
|
||||
return renderLink(value, `/supplier-part/${row.pk}/`);
|
||||
}
|
||||
},
|
||||
@ -746,7 +792,7 @@ function loadSupplierPartTable(table, url, options) {
|
||||
sortable: true,
|
||||
field: 'manufacturer_detail.name',
|
||||
title: '{% trans "Manufacturer" %}',
|
||||
formatter: function(value, row, index, field) {
|
||||
formatter: function(value, row) {
|
||||
if (value && row.manufacturer_detail) {
|
||||
var name = value;
|
||||
var url = `/company/${row.manufacturer_detail.pk}/`;
|
||||
@ -754,7 +800,7 @@ function loadSupplierPartTable(table, url, options) {
|
||||
|
||||
return html;
|
||||
} else {
|
||||
return "-";
|
||||
return '-';
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -764,18 +810,18 @@ function loadSupplierPartTable(table, url, options) {
|
||||
sortable: true,
|
||||
field: 'manufacturer_part_detail.MPN',
|
||||
title: '{% trans "MPN" %}',
|
||||
formatter: function(value, row, index, field) {
|
||||
formatter: function(value, row) {
|
||||
if (value && row.manufacturer_part) {
|
||||
return renderLink(value, `/manufacturer-part/${row.manufacturer_part}/`);
|
||||
} else {
|
||||
return "-";
|
||||
return '-';
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'link',
|
||||
title: '{% trans "Link" %}',
|
||||
formatter: function(value, row, index, field) {
|
||||
formatter: function(value) {
|
||||
if (value) {
|
||||
return renderLink(value, value);
|
||||
} else {
|
||||
@ -827,8 +873,9 @@ function loadSupplierPartTable(table, url, options) {
|
||||
{
|
||||
onSuccess: function() {
|
||||
$(table).bootstrapTable('refresh');
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
});
|
||||
|
||||
$(table).find('.button-supplier-part-delete').click(function() {
|
||||
@ -839,9 +886,10 @@ function loadSupplierPartTable(table, url, options) {
|
||||
{
|
||||
onSuccess: function() {
|
||||
$(table).bootstrapTable('refresh');
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,16 @@
|
||||
{% load i18n %}
|
||||
|
||||
/* globals
|
||||
getAvailableTableFilters,
|
||||
inventreeLoad,
|
||||
inventreeSave,
|
||||
reloadTableFilters,
|
||||
*/
|
||||
|
||||
/* exported
|
||||
setupFilterList,
|
||||
*/
|
||||
|
||||
/**
|
||||
* Code for managing query filters / table options.
|
||||
*
|
||||
@ -16,12 +27,12 @@
|
||||
|
||||
function defaultFilters() {
|
||||
return {
|
||||
stock: "cascade=1&in_stock=1",
|
||||
build: "",
|
||||
parts: "cascade=1",
|
||||
company: "",
|
||||
salesorder: "",
|
||||
purchaseorder: "",
|
||||
stock: 'cascade=1&in_stock=1',
|
||||
build: '',
|
||||
parts: 'cascade=1',
|
||||
company: '',
|
||||
salesorder: '',
|
||||
purchaseorder: '',
|
||||
};
|
||||
}
|
||||
|
||||
@ -34,7 +45,7 @@ function defaultFilters() {
|
||||
*/
|
||||
function loadTableFilters(tableKey) {
|
||||
|
||||
var lookup = "table-filters-" + tableKey.toLowerCase();
|
||||
var lookup = 'table-filters-' + tableKey.toLowerCase();
|
||||
|
||||
var defaults = defaultFilters()[tableKey] || '';
|
||||
|
||||
@ -42,7 +53,7 @@ function loadTableFilters(tableKey) {
|
||||
|
||||
var filters = {};
|
||||
|
||||
filterstring.split("&").forEach(function(item, index) {
|
||||
filterstring.split('&').forEach(function(item) {
|
||||
item = item.trim();
|
||||
|
||||
if (item.length > 0) {
|
||||
@ -67,7 +78,7 @@ function loadTableFilters(tableKey) {
|
||||
* @param {*} filters - object of string:string pairs
|
||||
*/
|
||||
function saveTableFilters(tableKey, filters) {
|
||||
var lookup = "table-filters-" + tableKey.toLowerCase();
|
||||
var lookup = 'table-filters-' + tableKey.toLowerCase();
|
||||
|
||||
var strings = [];
|
||||
|
||||
@ -190,7 +201,7 @@ function generateAvailableFilterList(tableKey) {
|
||||
|
||||
var html = `<select class='form-control filter-input' id='${id}' name='tag'>`;
|
||||
|
||||
html += "<option value=''>{% trans 'Select filter' %}</option>";
|
||||
html += `<option value=''>{% trans 'Select filter' %}</option>`;
|
||||
|
||||
for (var opt in remaining) {
|
||||
var title = getFilterTitle(tableKey, opt);
|
||||
@ -227,7 +238,7 @@ function generateFilterInput(tableKey, filterKey) {
|
||||
html = `<select class='form-control filter-input' id='${id}' name='value'>`;
|
||||
|
||||
for (var key in options) {
|
||||
option = options[key];
|
||||
var option = options[key];
|
||||
html += `<option value='${key}'>${option.value}</option>`;
|
||||
}
|
||||
|
||||
@ -295,15 +306,11 @@ function setupFilterList(tableKey, table, target) {
|
||||
|
||||
var html = '';
|
||||
|
||||
//`<div class='filter-input'>`;
|
||||
|
||||
html += generateAvailableFilterList(tableKey);
|
||||
html += generateFilterInput(tableKey);
|
||||
|
||||
html += `<button title='{% trans "Create filter" %}' class='btn btn-default filter-tag' id='${make}'><span class='fas fa-plus'></span></button>`;
|
||||
|
||||
//html += '</div>';
|
||||
|
||||
element.append(html);
|
||||
|
||||
// Add a callback for when the filter tag selection is changed
|
||||
@ -346,7 +353,7 @@ function setupFilterList(tableKey, table, target) {
|
||||
});
|
||||
|
||||
// Add callback for deleting each filter
|
||||
element.find(".close").click(function(event) {
|
||||
element.find('.close').click(function() {
|
||||
var me = $(this);
|
||||
|
||||
var filter = me.attr(`filter-tag-${tableKey}`);
|
||||
@ -372,15 +379,6 @@ function getFilterTitle(tableKey, filterKey) {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the pretty description for the given table and filter selection
|
||||
*/
|
||||
function getFilterDescription(tableKey, filterKey) {
|
||||
var settings = getFilterSettings(tableKey, filterKey);
|
||||
|
||||
return settings.title;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return a description for the given table and filter selection.
|
||||
*/
|
||||
|
@ -1,6 +1,34 @@
|
||||
{% load i18n %}
|
||||
{% load inventree_extras %}
|
||||
|
||||
/* globals
|
||||
attachToggle,
|
||||
createNewModal,
|
||||
inventreeFormDataUpload,
|
||||
inventreeGet,
|
||||
inventreePut,
|
||||
modalEnable,
|
||||
modalShowSubmitButton,
|
||||
renderBuild,
|
||||
renderCompany,
|
||||
renderManufacturerPart,
|
||||
renderOwner,
|
||||
renderPart,
|
||||
renderPartCategory,
|
||||
renderPartParameterTemplate,
|
||||
renderStockItem,
|
||||
renderStockLocation,
|
||||
renderSupplierPart,
|
||||
renderUser,
|
||||
showAlertDialog,
|
||||
showAlertOrCache,
|
||||
showApiError,
|
||||
*/
|
||||
|
||||
/* exported
|
||||
setFormGroupVisibility
|
||||
*/
|
||||
|
||||
/**
|
||||
*
|
||||
* This file contains code for rendering (and managing) HTML forms
|
||||
@ -81,7 +109,7 @@ function canDelete(OPTIONS) {
|
||||
* Get the API endpoint options at the provided URL,
|
||||
* using a HTTP options request.
|
||||
*/
|
||||
function getApiEndpointOptions(url, callback, options) {
|
||||
function getApiEndpointOptions(url, callback) {
|
||||
|
||||
// Return the ajax request object
|
||||
$.ajax({
|
||||
@ -93,7 +121,7 @@ function getApiEndpointOptions(url, callback, options) {
|
||||
json: 'application/json',
|
||||
},
|
||||
success: callback,
|
||||
error: function(request, status, error) {
|
||||
error: function() {
|
||||
// TODO: Handle error
|
||||
console.log(`ERROR in getApiEndpointOptions at '${url}'`);
|
||||
}
|
||||
@ -172,7 +200,7 @@ function constructChangeForm(fields, options) {
|
||||
|
||||
constructFormBody(fields, options);
|
||||
},
|
||||
error: function(request, status, error) {
|
||||
error: function() {
|
||||
// TODO: Handle error here
|
||||
console.log(`ERROR in constructChangeForm at '${options.url}'`);
|
||||
}
|
||||
@ -211,7 +239,7 @@ function constructDeleteForm(fields, options) {
|
||||
|
||||
constructFormBody(fields, options);
|
||||
},
|
||||
error: function(request, status, error) {
|
||||
error: function() {
|
||||
// TODO: Handle error here
|
||||
console.log(`ERROR in constructDeleteForm at '${options.url}`);
|
||||
}
|
||||
@ -286,58 +314,58 @@ function constructForm(url, options) {
|
||||
*/
|
||||
|
||||
switch (options.method) {
|
||||
case 'POST':
|
||||
if (canCreate(OPTIONS)) {
|
||||
constructCreateForm(OPTIONS.actions.POST, options);
|
||||
} else {
|
||||
// User does not have permission to POST to the endpoint
|
||||
showAlertDialog(
|
||||
'{% trans "Action Prohibited" %}',
|
||||
'{% trans "Create operation not allowed" %}'
|
||||
);
|
||||
console.log(`'POST action unavailable at ${url}`);
|
||||
}
|
||||
break;
|
||||
case 'PUT':
|
||||
case 'PATCH':
|
||||
if (canChange(OPTIONS)) {
|
||||
constructChangeForm(OPTIONS.actions.PUT, options);
|
||||
} else {
|
||||
// User does not have permission to PUT/PATCH to the endpoint
|
||||
showAlertDialog(
|
||||
'{% trans "Action Prohibited" %}',
|
||||
'{% trans "Update operation not allowed" %}'
|
||||
);
|
||||
console.log(`${options.method} action unavailable at ${url}`);
|
||||
}
|
||||
break;
|
||||
case 'DELETE':
|
||||
if (canDelete(OPTIONS)) {
|
||||
constructDeleteForm(OPTIONS.actions.DELETE, options);
|
||||
} else {
|
||||
// User does not have permission to DELETE to the endpoint
|
||||
showAlertDialog(
|
||||
'{% trans "Action Prohibited" %}',
|
||||
'{% trans "Delete operation not allowed" %}'
|
||||
);
|
||||
console.log(`DELETE action unavailable at ${url}`);
|
||||
}
|
||||
break;
|
||||
case 'GET':
|
||||
if (canView(OPTIONS)) {
|
||||
// TODO?
|
||||
} else {
|
||||
// User does not have permission to GET to the endpoint
|
||||
showAlertDialog(
|
||||
'{% trans "Action Prohibited" %}',
|
||||
'{% trans "View operation not allowed" %}'
|
||||
);
|
||||
console.log(`GET action unavailable at ${url}`);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
console.log(`constructForm() called with invalid method '${options.method}'`);
|
||||
break;
|
||||
case 'POST':
|
||||
if (canCreate(OPTIONS)) {
|
||||
constructCreateForm(OPTIONS.actions.POST, options);
|
||||
} else {
|
||||
// User does not have permission to POST to the endpoint
|
||||
showAlertDialog(
|
||||
'{% trans "Action Prohibited" %}',
|
||||
'{% trans "Create operation not allowed" %}'
|
||||
);
|
||||
console.log(`'POST action unavailable at ${url}`);
|
||||
}
|
||||
break;
|
||||
case 'PUT':
|
||||
case 'PATCH':
|
||||
if (canChange(OPTIONS)) {
|
||||
constructChangeForm(OPTIONS.actions.PUT, options);
|
||||
} else {
|
||||
// User does not have permission to PUT/PATCH to the endpoint
|
||||
showAlertDialog(
|
||||
'{% trans "Action Prohibited" %}',
|
||||
'{% trans "Update operation not allowed" %}'
|
||||
);
|
||||
console.log(`${options.method} action unavailable at ${url}`);
|
||||
}
|
||||
break;
|
||||
case 'DELETE':
|
||||
if (canDelete(OPTIONS)) {
|
||||
constructDeleteForm(OPTIONS.actions.DELETE, options);
|
||||
} else {
|
||||
// User does not have permission to DELETE to the endpoint
|
||||
showAlertDialog(
|
||||
'{% trans "Action Prohibited" %}',
|
||||
'{% trans "Delete operation not allowed" %}'
|
||||
);
|
||||
console.log(`DELETE action unavailable at ${url}`);
|
||||
}
|
||||
break;
|
||||
case 'GET':
|
||||
if (canView(OPTIONS)) {
|
||||
// TODO?
|
||||
} else {
|
||||
// User does not have permission to GET to the endpoint
|
||||
showAlertDialog(
|
||||
'{% trans "Action Prohibited" %}',
|
||||
'{% trans "View operation not allowed" %}'
|
||||
);
|
||||
console.log(`GET action unavailable at ${url}`);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
console.log(`constructForm() called with invalid method '${options.method}'`);
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -376,7 +404,7 @@ function constructFormBody(fields, options) {
|
||||
}
|
||||
|
||||
// Provide each field object with its own name
|
||||
for(field in fields) {
|
||||
for (field in fields) {
|
||||
fields[field].name = field;
|
||||
|
||||
// If any "instance_filters" are defined for the endpoint, copy them across (overwrite)
|
||||
@ -429,19 +457,19 @@ function constructFormBody(fields, options) {
|
||||
|
||||
for (var idx = 0; idx < field_names.length; idx++) {
|
||||
|
||||
var name = field_names[idx];
|
||||
var field_name = field_names[idx];
|
||||
|
||||
var field = fields[name];
|
||||
var field = fields[field_name];
|
||||
|
||||
switch (field.type) {
|
||||
// Skip field types which are simply not supported
|
||||
case 'nested object':
|
||||
continue;
|
||||
default:
|
||||
break;
|
||||
// Skip field types which are simply not supported
|
||||
case 'nested object':
|
||||
continue;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
html += constructField(name, field, options);
|
||||
html += constructField(field_name, field, options);
|
||||
}
|
||||
|
||||
if (options.current_group) {
|
||||
@ -647,19 +675,19 @@ function submitFormData(fields, options) {
|
||||
data,
|
||||
{
|
||||
method: options.method,
|
||||
success: function(response, status) {
|
||||
success: function(response) {
|
||||
handleFormSuccess(response, options);
|
||||
},
|
||||
error: function(xhr, status, thrownError) {
|
||||
error: function(xhr) {
|
||||
|
||||
switch (xhr.status) {
|
||||
case 400: // Bad request
|
||||
handleFormErrors(xhr.responseJSON, fields, options);
|
||||
break;
|
||||
default:
|
||||
$(options.modal).modal('hide');
|
||||
showApiError(xhr);
|
||||
break;
|
||||
case 400:
|
||||
handleFormErrors(xhr.responseJSON, fields, options);
|
||||
break;
|
||||
default:
|
||||
$(options.modal).modal('hide');
|
||||
showApiError(xhr);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -682,7 +710,9 @@ function updateFieldValues(fields, options) {
|
||||
|
||||
var field = fields[name] || null;
|
||||
|
||||
if (field == null) { continue; }
|
||||
if (field == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var value = field.value;
|
||||
|
||||
@ -690,7 +720,9 @@ function updateFieldValues(fields, options) {
|
||||
value = field.default;
|
||||
}
|
||||
|
||||
if (value == null) { continue; }
|
||||
if (value == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
updateFieldValue(name, value, field, options);
|
||||
}
|
||||
@ -701,22 +733,22 @@ function updateFieldValue(name, value, field, options) {
|
||||
var el = $(options.modal).find(`#id_${name}`);
|
||||
|
||||
switch (field.type) {
|
||||
case 'boolean':
|
||||
el.prop('checked', value);
|
||||
break;
|
||||
case 'related field':
|
||||
// Clear?
|
||||
if (value == null && !field.required) {
|
||||
el.val(null).trigger('change');
|
||||
}
|
||||
// TODO - Specify an actual value!
|
||||
break;
|
||||
case 'file upload':
|
||||
case 'image upload':
|
||||
break;
|
||||
default:
|
||||
el.val(value);
|
||||
break;
|
||||
case 'boolean':
|
||||
el.prop('checked', value);
|
||||
break;
|
||||
case 'related field':
|
||||
// Clear?
|
||||
if (value == null && !field.required) {
|
||||
el.val(null).trigger('change');
|
||||
}
|
||||
// TODO - Specify an actual value!
|
||||
break;
|
||||
case 'file upload':
|
||||
case 'image upload':
|
||||
break;
|
||||
default:
|
||||
el.val(value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@ -741,21 +773,21 @@ function getFormFieldValue(name, field, options) {
|
||||
var value = null;
|
||||
|
||||
switch (field.type) {
|
||||
case 'boolean':
|
||||
value = el.is(":checked");
|
||||
break;
|
||||
case 'date':
|
||||
case 'datetime':
|
||||
value = el.val();
|
||||
case 'boolean':
|
||||
value = el.is(':checked');
|
||||
break;
|
||||
case 'date':
|
||||
case 'datetime':
|
||||
value = el.val();
|
||||
|
||||
// Ensure empty values are sent as nulls
|
||||
if (!value || value.length == 0) {
|
||||
value = null;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
value = el.val();
|
||||
break;
|
||||
// Ensure empty values are sent as nulls
|
||||
if (!value || value.length == 0) {
|
||||
value = null;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
value = el.val();
|
||||
break;
|
||||
}
|
||||
|
||||
return value;
|
||||
@ -783,19 +815,19 @@ function handleFormSuccess(response, options) {
|
||||
|
||||
// Display any messages
|
||||
if (response && response.success) {
|
||||
showAlertOrCache("alert-success", response.success, cache);
|
||||
showAlertOrCache('alert-success', response.success, cache);
|
||||
}
|
||||
|
||||
if (response && response.info) {
|
||||
showAlertOrCache("alert-info", response.info, cache);
|
||||
showAlertOrCache('alert-info', response.info, cache);
|
||||
}
|
||||
|
||||
if (response && response.warning) {
|
||||
showAlertOrCache("alert-warning", response.warning, cache);
|
||||
showAlertOrCache('alert-warning', response.warning, cache);
|
||||
}
|
||||
|
||||
if (response && response.danger) {
|
||||
showAlertOrCache("alert-danger", response.danger, cache);
|
||||
showAlertOrCache('alert-danger', response.danger, cache);
|
||||
}
|
||||
|
||||
if (options.onSuccess) {
|
||||
@ -879,7 +911,7 @@ function handleFormErrors(errors, fields, options) {
|
||||
|
||||
var first_error_field = null;
|
||||
|
||||
for (field_name in errors) {
|
||||
for (var field_name in errors) {
|
||||
|
||||
// Add the 'has-error' class
|
||||
$(options.modal).find(`#div_id_${field_name}`).addClass('has-error');
|
||||
@ -893,16 +925,16 @@ function handleFormErrors(errors, fields, options) {
|
||||
}
|
||||
|
||||
// Add an entry for each returned error message
|
||||
for (var idx = field_errors.length-1; idx >= 0; idx--) {
|
||||
for (var ii = field_errors.length-1; ii >= 0; ii--) {
|
||||
|
||||
var error_text = field_errors[idx];
|
||||
var error_text = field_errors[ii];
|
||||
|
||||
var html = `
|
||||
<span id='error_${idx+1}_id_${field_name}' class='help-block form-error-message'>
|
||||
var error_html = `
|
||||
<span id='error_${ii+1}_id_${field_name}' class='help-block form-error-message'>
|
||||
<strong>${error_text}</strong>
|
||||
</span>`;
|
||||
|
||||
field_dom.append(html);
|
||||
field_dom.append(error_html);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1016,9 +1048,9 @@ function initializeGroups(fields, options) {
|
||||
var group_options = options.groups[group];
|
||||
|
||||
if (group_options.collapsed) {
|
||||
$(modal).find(`#form-panel-content-${group}`).collapse("hide");
|
||||
$(modal).find(`#form-panel-content-${group}`).collapse('hide');
|
||||
} else {
|
||||
$(modal).find(`#form-panel-content-${group}`).collapse("show");
|
||||
$(modal).find(`#form-panel-content-${group}`).collapse('show');
|
||||
}
|
||||
|
||||
if (group_options.hidden) {
|
||||
@ -1059,12 +1091,14 @@ function initializeRelatedFields(fields, options) {
|
||||
if (!field || field.hidden) continue;
|
||||
|
||||
switch (field.type) {
|
||||
case 'related field':
|
||||
initializeRelatedField(field, fields, options);
|
||||
break;
|
||||
case 'choice':
|
||||
initializeChoiceField(field, fields, options);
|
||||
break;
|
||||
case 'related field':
|
||||
initializeRelatedField(field, fields, options);
|
||||
break;
|
||||
case 'choice':
|
||||
initializeChoiceField(field, fields, options);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1103,14 +1137,14 @@ function addSecondaryModal(field, fields, options) {
|
||||
if (secondary.fields instanceof Function) {
|
||||
|
||||
// Extract form values at time of button press
|
||||
var data = extractFormData(fields, options)
|
||||
var data = extractFormData(fields, options);
|
||||
|
||||
secondary.fields = secondary.fields(data);
|
||||
}
|
||||
|
||||
// If no onSuccess function is defined, provide a default one
|
||||
if (!secondary.onSuccess) {
|
||||
secondary.onSuccess = function(data, opts) {
|
||||
secondary.onSuccess = function(data) {
|
||||
|
||||
// Force refresh from the API, to get full detail
|
||||
inventreeGet(`${url}${data.pk}/`, {}, {
|
||||
@ -1176,6 +1210,8 @@ function initializeRelatedField(field, fields, options) {
|
||||
cache: true,
|
||||
data: function(params) {
|
||||
|
||||
var offset = 0;
|
||||
|
||||
if (!params.page) {
|
||||
offset = 0;
|
||||
} else {
|
||||
@ -1229,7 +1265,7 @@ function initializeRelatedField(field, fields, options) {
|
||||
return results;
|
||||
},
|
||||
},
|
||||
templateResult: function(item, container) {
|
||||
templateResult: function(item) {
|
||||
|
||||
// Extract 'instance' data passed through from an initial value
|
||||
// Or, use the raw 'item' data as a backup
|
||||
@ -1254,7 +1290,7 @@ function initializeRelatedField(field, fields, options) {
|
||||
return `${name} - ${item.id}`;
|
||||
}
|
||||
},
|
||||
templateSelection: function(item, container) {
|
||||
templateSelection: function(item) {
|
||||
|
||||
// Extract 'instance' data passed through from an initial value
|
||||
// Or, use the raw 'item' data as a backup
|
||||
@ -1266,7 +1302,6 @@ function initializeRelatedField(field, fields, options) {
|
||||
|
||||
if (!data.pk) {
|
||||
return field.placeholder || '';
|
||||
return $(searching());
|
||||
}
|
||||
|
||||
// Custom formatting for selected item
|
||||
@ -1369,41 +1404,41 @@ function renderModelData(name, model, data, parameters, options) {
|
||||
|
||||
// Find a custom renderer
|
||||
switch (model) {
|
||||
case 'company':
|
||||
renderer = renderCompany;
|
||||
break;
|
||||
case 'stockitem':
|
||||
renderer = renderStockItem;
|
||||
break;
|
||||
case 'stocklocation':
|
||||
renderer = renderStockLocation;
|
||||
break;
|
||||
case 'part':
|
||||
renderer = renderPart;
|
||||
break;
|
||||
case 'partcategory':
|
||||
renderer = renderPartCategory;
|
||||
break;
|
||||
case 'partparametertemplate':
|
||||
renderer = renderPartParameterTemplate;
|
||||
break;
|
||||
case 'manufacturerpart':
|
||||
renderer = renderManufacturerPart;
|
||||
break;
|
||||
case 'supplierpart':
|
||||
renderer = renderSupplierPart;
|
||||
break;
|
||||
case 'build':
|
||||
renderer = renderBuild;
|
||||
break;
|
||||
case 'owner':
|
||||
renderer = renderOwner;
|
||||
break;
|
||||
case 'user':
|
||||
renderer = renderUser;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
case 'company':
|
||||
renderer = renderCompany;
|
||||
break;
|
||||
case 'stockitem':
|
||||
renderer = renderStockItem;
|
||||
break;
|
||||
case 'stocklocation':
|
||||
renderer = renderStockLocation;
|
||||
break;
|
||||
case 'part':
|
||||
renderer = renderPart;
|
||||
break;
|
||||
case 'partcategory':
|
||||
renderer = renderPartCategory;
|
||||
break;
|
||||
case 'partparametertemplate':
|
||||
renderer = renderPartParameterTemplate;
|
||||
break;
|
||||
case 'manufacturerpart':
|
||||
renderer = renderManufacturerPart;
|
||||
break;
|
||||
case 'supplierpart':
|
||||
renderer = renderSupplierPart;
|
||||
break;
|
||||
case 'build':
|
||||
renderer = renderBuild;
|
||||
break;
|
||||
case 'owner':
|
||||
renderer = renderOwner;
|
||||
break;
|
||||
case 'user':
|
||||
renderer = renderUser;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (renderer != null) {
|
||||
@ -1526,18 +1561,18 @@ function constructField(name, parameters, options) {
|
||||
// Some fields can have 'clear' inputs associated with them
|
||||
if (!parameters.required && !parameters.read_only) {
|
||||
switch (parameters.type) {
|
||||
case 'string':
|
||||
case 'url':
|
||||
case 'email':
|
||||
case 'integer':
|
||||
case 'float':
|
||||
case 'decimal':
|
||||
case 'related field':
|
||||
case 'date':
|
||||
extra = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
case 'string':
|
||||
case 'url':
|
||||
case 'email':
|
||||
case 'integer':
|
||||
case 'float':
|
||||
case 'decimal':
|
||||
case 'related field':
|
||||
case 'date':
|
||||
extra = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1560,7 +1595,7 @@ function constructField(name, parameters, options) {
|
||||
</span>`;
|
||||
}
|
||||
|
||||
html += `</div>`; // input-group
|
||||
html += `</div>`; // input-group
|
||||
}
|
||||
|
||||
if (parameters.help_text && !options.hideLabels) {
|
||||
@ -1570,8 +1605,9 @@ function constructField(name, parameters, options) {
|
||||
// Div for error messages
|
||||
html += `<div id='errors-${name}'></div>`;
|
||||
|
||||
html += `</div>`; // controls
|
||||
html += `</div>`; // form-group
|
||||
|
||||
html += `</div>`; // controls
|
||||
html += `</div>`; // form-group
|
||||
|
||||
if (parameters.after) {
|
||||
html += parameters.after;
|
||||
@ -1629,39 +1665,39 @@ function constructInput(name, parameters, options) {
|
||||
var func = null;
|
||||
|
||||
switch (parameters.type) {
|
||||
case 'boolean':
|
||||
func = constructCheckboxInput;
|
||||
break;
|
||||
case 'string':
|
||||
case 'url':
|
||||
case 'email':
|
||||
func = constructTextInput;
|
||||
break;
|
||||
case 'integer':
|
||||
case 'float':
|
||||
case 'decimal':
|
||||
func = constructNumberInput;
|
||||
break;
|
||||
case 'choice':
|
||||
func = constructChoiceInput;
|
||||
break;
|
||||
case 'related field':
|
||||
func = constructRelatedFieldInput;
|
||||
break;
|
||||
case 'image upload':
|
||||
case 'file upload':
|
||||
func = constructFileUploadInput;
|
||||
break;
|
||||
case 'date':
|
||||
func = constructDateInput;
|
||||
break;
|
||||
case 'candy':
|
||||
func = constructCandyInput;
|
||||
break;
|
||||
default:
|
||||
// Unsupported field type!
|
||||
break;
|
||||
}
|
||||
case 'boolean':
|
||||
func = constructCheckboxInput;
|
||||
break;
|
||||
case 'string':
|
||||
case 'url':
|
||||
case 'email':
|
||||
func = constructTextInput;
|
||||
break;
|
||||
case 'integer':
|
||||
case 'float':
|
||||
case 'decimal':
|
||||
func = constructNumberInput;
|
||||
break;
|
||||
case 'choice':
|
||||
func = constructChoiceInput;
|
||||
break;
|
||||
case 'related field':
|
||||
func = constructRelatedFieldInput;
|
||||
break;
|
||||
case 'image upload':
|
||||
case 'file upload':
|
||||
func = constructFileUploadInput;
|
||||
break;
|
||||
case 'date':
|
||||
func = constructDateInput;
|
||||
break;
|
||||
case 'candy':
|
||||
func = constructCandyInput;
|
||||
break;
|
||||
default:
|
||||
// Unsupported field type!
|
||||
break;
|
||||
}
|
||||
|
||||
if (func != null) {
|
||||
html = func(name, parameters, options);
|
||||
@ -1747,7 +1783,7 @@ function constructInputOptions(name, classes, type, parameters) {
|
||||
|
||||
|
||||
// Construct a "hidden" input
|
||||
function constructHiddenInput(name, parameters, options) {
|
||||
function constructHiddenInput(name, parameters) {
|
||||
|
||||
return constructInputOptions(
|
||||
name,
|
||||
@ -1759,7 +1795,7 @@ function constructHiddenInput(name, parameters, options) {
|
||||
|
||||
|
||||
// Construct a "checkbox" input
|
||||
function constructCheckboxInput(name, parameters, options) {
|
||||
function constructCheckboxInput(name, parameters) {
|
||||
|
||||
return constructInputOptions(
|
||||
name,
|
||||
@ -1771,24 +1807,24 @@ function constructCheckboxInput(name, parameters, options) {
|
||||
|
||||
|
||||
// Construct a "text" input
|
||||
function constructTextInput(name, parameters, options) {
|
||||
function constructTextInput(name, parameters) {
|
||||
|
||||
var classes = '';
|
||||
var type = '';
|
||||
|
||||
switch (parameters.type) {
|
||||
default:
|
||||
classes = 'textinput textInput form-control';
|
||||
type = 'text';
|
||||
break;
|
||||
case 'url':
|
||||
classes = 'urlinput form-control';
|
||||
type = 'url';
|
||||
break;
|
||||
case 'email':
|
||||
classes = 'emailinput form-control';
|
||||
type = 'email';
|
||||
break;
|
||||
default:
|
||||
classes = 'textinput textInput form-control';
|
||||
type = 'text';
|
||||
break;
|
||||
case 'url':
|
||||
classes = 'urlinput form-control';
|
||||
type = 'url';
|
||||
break;
|
||||
case 'email':
|
||||
classes = 'emailinput form-control';
|
||||
type = 'email';
|
||||
break;
|
||||
}
|
||||
|
||||
return constructInputOptions(
|
||||
@ -1801,7 +1837,7 @@ function constructTextInput(name, parameters, options) {
|
||||
|
||||
|
||||
// Construct a "number" field
|
||||
function constructNumberInput(name, parameters, options) {
|
||||
function constructNumberInput(name, parameters) {
|
||||
|
||||
return constructInputOptions(
|
||||
name,
|
||||
@ -1813,7 +1849,7 @@ function constructNumberInput(name, parameters, options) {
|
||||
|
||||
|
||||
// Construct a "choice" input
|
||||
function constructChoiceInput(name, parameters, options) {
|
||||
function constructChoiceInput(name, parameters) {
|
||||
|
||||
var html = `<select id='id_${name}' class='select form-control' name='${name}'>`;
|
||||
|
||||
@ -1846,7 +1882,7 @@ function constructChoiceInput(name, parameters, options) {
|
||||
* be converted into a select2 input.
|
||||
* This will then be served custom data from the API (as required)...
|
||||
*/
|
||||
function constructRelatedFieldInput(name, parameters, options) {
|
||||
function constructRelatedFieldInput(name) {
|
||||
|
||||
var html = `<select id='id_${name}' class='select form-control' name='${name}'></select>`;
|
||||
|
||||
@ -1859,7 +1895,7 @@ function constructRelatedFieldInput(name, parameters, options) {
|
||||
/*
|
||||
* Construct a field for file upload
|
||||
*/
|
||||
function constructFileUploadInput(name, parameters, options) {
|
||||
function constructFileUploadInput(name, parameters) {
|
||||
|
||||
var cls = 'clearablefileinput';
|
||||
|
||||
@ -1879,7 +1915,7 @@ function constructFileUploadInput(name, parameters, options) {
|
||||
/*
|
||||
* Construct a field for a date input
|
||||
*/
|
||||
function constructDateInput(name, parameters, options) {
|
||||
function constructDateInput(name, parameters) {
|
||||
|
||||
return constructInputOptions(
|
||||
name,
|
||||
@ -1894,7 +1930,7 @@ function constructDateInput(name, parameters, options) {
|
||||
* Construct a "candy" field input
|
||||
* No actual field data!
|
||||
*/
|
||||
function constructCandyInput(name, parameters, options) {
|
||||
function constructCandyInput(name, parameters) {
|
||||
|
||||
return parameters.html;
|
||||
|
||||
@ -1909,7 +1945,7 @@ function constructCandyInput(name, parameters, options) {
|
||||
* - parameters: Field parameters returned by the OPTIONS method
|
||||
*
|
||||
*/
|
||||
function constructHelpText(name, parameters, options) {
|
||||
function constructHelpText(name, parameters) {
|
||||
|
||||
var style = '';
|
||||
|
||||
@ -1920,4 +1956,4 @@ function constructHelpText(name, parameters, options) {
|
||||
var html = `<div id='hint_id_${name}' ${style}class='help-block'><i>${parameters.help_text}</i></div>`;
|
||||
|
||||
return html;
|
||||
}
|
||||
}
|
||||
|
207
InvenTree/templates/js/translated/helpers.js
Normal file
207
InvenTree/templates/js/translated/helpers.js
Normal 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>`;
|
||||
}
|
@ -1,6 +1,25 @@
|
||||
{% load i18n %}
|
||||
|
||||
function printStockItemLabels(items, options={}) {
|
||||
/* globals
|
||||
attachSelect,
|
||||
closeModal,
|
||||
inventreeGet,
|
||||
makeOptionsList,
|
||||
modalEnable,
|
||||
modalSetContent,
|
||||
modalSetTitle,
|
||||
modalSubmit,
|
||||
openModal,
|
||||
showAlertDialog,
|
||||
*/
|
||||
|
||||
/* exported
|
||||
printPartLabels,
|
||||
printStockItemLabels,
|
||||
printStockLocationLabels,
|
||||
*/
|
||||
|
||||
function printStockItemLabels(items) {
|
||||
/**
|
||||
* Print stock item labels for the given stock items
|
||||
*/
|
||||
@ -54,7 +73,7 @@ function printStockItemLabels(items, options={}) {
|
||||
);
|
||||
}
|
||||
|
||||
function printStockLocationLabels(locations, options={}) {
|
||||
function printStockLocationLabels(locations) {
|
||||
|
||||
if (locations.length == 0) {
|
||||
showAlertDialog(
|
||||
@ -101,11 +120,11 @@ function printStockLocationLabels(locations, options={}) {
|
||||
);
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
function printPartLabels(parts, options={}) {
|
||||
function printPartLabels(parts) {
|
||||
/**
|
||||
* Print labels for the provided parts
|
||||
*/
|
||||
|
@ -1,5 +1,22 @@
|
||||
{% load i18n %}
|
||||
|
||||
/* globals
|
||||
inventreeGet,
|
||||
showAlertOrCache,
|
||||
*/
|
||||
|
||||
/* exported
|
||||
attachSecondaryModal,
|
||||
clearField,
|
||||
clearFieldOptions,
|
||||
closeModal,
|
||||
enableField,
|
||||
getFieldValue,
|
||||
reloadFieldOptions,
|
||||
showModalImage,
|
||||
removeRowFromModalForm,
|
||||
showQuestionDialog,
|
||||
*/
|
||||
|
||||
/*
|
||||
* Create and display a new modal dialog
|
||||
@ -77,7 +94,7 @@ function createNewModal(options={}) {
|
||||
});
|
||||
|
||||
// Automatically remove the modal when it is deleted!
|
||||
$(modal_name).on('hidden.bs.modal', function(e) {
|
||||
$(modal_name).on('hidden.bs.modal', function() {
|
||||
$(modal_name).remove();
|
||||
});
|
||||
|
||||
@ -86,7 +103,7 @@ function createNewModal(options={}) {
|
||||
if (event.keyCode == 13) {
|
||||
event.preventDefault();
|
||||
// Simulate a click on the 'Submit' button
|
||||
$(modal_name).find("#modal-form-submit").click();
|
||||
$(modal_name).find('#modal-form-submit').click();
|
||||
|
||||
return false;
|
||||
}
|
||||
@ -253,8 +270,8 @@ function reloadFieldOptions(fieldName, options) {
|
||||
// Update the target field with the new options
|
||||
setFieldOptions(fieldName, opts);
|
||||
},
|
||||
error: function(response) {
|
||||
console.log("Error GETting field options");
|
||||
error: function() {
|
||||
console.log('Error GETting field options');
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -273,7 +290,7 @@ function enableField(fieldName, enabled, options={}) {
|
||||
|
||||
var field = getFieldByName(modal, fieldName);
|
||||
|
||||
field.prop("disabled", !enabled);
|
||||
field.prop('disabled', !enabled);
|
||||
}
|
||||
|
||||
function clearField(fieldName, options={}) {
|
||||
@ -344,7 +361,7 @@ function attachToggle(modal) {
|
||||
* and also larger toggle style buttons are easier to press!
|
||||
*/
|
||||
|
||||
$(modal).find("input[type='checkbox']").each(function(x) {
|
||||
$(modal).find(`input[type='checkbox']`).each(function() {
|
||||
$(this).bootstrapToggle({
|
||||
size: 'small',
|
||||
onstyle: 'success',
|
||||
@ -359,7 +376,7 @@ function attachSelect(modal) {
|
||||
* Provides search filtering for dropdown items
|
||||
*/
|
||||
|
||||
$(modal + ' .select').select2({
|
||||
$(modal + ' .select').select2({
|
||||
dropdownParent: $(modal),
|
||||
// dropdownAutoWidth parameter is required to work properly with modal forms
|
||||
dropdownAutoWidth: false,
|
||||
@ -377,7 +394,7 @@ function loadingMessageContent() {
|
||||
*/
|
||||
|
||||
// TODO - This can be made a lot better
|
||||
return "<span class='glyphicon glyphicon-refresh glyphicon-refresh-animate'></span> {% trans 'Waiting for server...' %}";
|
||||
return `<span class='glyphicon glyphicon-refresh glyphicon-refresh-animate'></span> {% trans 'Waiting for server...' %}`;
|
||||
}
|
||||
|
||||
|
||||
@ -392,36 +409,36 @@ function afterForm(response, options) {
|
||||
* - Reload the page
|
||||
*/
|
||||
|
||||
// Should we show alerts immediately or cache them?
|
||||
// Should we show alerts immediately or cache them?
|
||||
var cache = (options.follow && response.url) ||
|
||||
options.redirect ||
|
||||
options.reload;
|
||||
|
||||
// Display any messages
|
||||
if (response.success) {
|
||||
showAlertOrCache("alert-success", response.success, cache);
|
||||
showAlertOrCache('alert-success', response.success, cache);
|
||||
}
|
||||
|
||||
if (response.info) {
|
||||
showAlertOrCache("alert-info", response.info, cache);
|
||||
showAlertOrCache('alert-info', response.info, cache);
|
||||
}
|
||||
|
||||
if (response.warning) {
|
||||
showAlertOrCache("alert-warning", response.warning, cache);
|
||||
showAlertOrCache('alert-warning', response.warning, cache);
|
||||
}
|
||||
|
||||
if (response.danger) {
|
||||
showAlertOrCache("alert-danger", response.danger, cache);
|
||||
showAlertOrCache('alert-danger', response.danger, cache);
|
||||
}
|
||||
|
||||
// Was a callback provided?
|
||||
if (options.success) {
|
||||
options.success(response);
|
||||
}
|
||||
else if (options.follow && response.url) {
|
||||
} else if (options.follow && response.url) {
|
||||
window.location.href = response.url;
|
||||
}
|
||||
else if (options.redirect) {
|
||||
} else if (options.redirect) {
|
||||
window.location.href = options.redirect;
|
||||
}
|
||||
else if (options.reload) {
|
||||
} else if (options.reload) {
|
||||
location.reload();
|
||||
}
|
||||
}
|
||||
@ -554,7 +571,7 @@ function renderErrorMessage(xhr) {
|
||||
}
|
||||
|
||||
|
||||
function showAlertDialog(title, content, options={}) {
|
||||
function showAlertDialog(title, content) {
|
||||
/* Display a modal dialog message box.
|
||||
*
|
||||
* title - Title text
|
||||
@ -595,7 +612,7 @@ function showQuestionDialog(title, content, options={}) {
|
||||
|
||||
modalSetContent(modal, content);
|
||||
|
||||
$(modal).on('click', "#modal-form-submit", function() {
|
||||
$(modal).on('click', '#modal-form-submit', function() {
|
||||
$(modal).modal('hide');
|
||||
|
||||
if (options.accept) {
|
||||
@ -636,7 +653,7 @@ function openModal(options) {
|
||||
event.preventDefault();
|
||||
|
||||
// Simulate a click on the 'Submit' button
|
||||
$(modal).find("#modal-form-submit").click();
|
||||
$(modal).find('#modal-form-submit').click();
|
||||
|
||||
return false;
|
||||
}
|
||||
@ -698,17 +715,17 @@ function insertNewItemButton(modal, options) {
|
||||
* Inserts a button at the end of this lael element.
|
||||
*/
|
||||
|
||||
var html = "<span style='float: right;'>";
|
||||
var html = `<span style='float: right;'>`;
|
||||
|
||||
html += "<div type='button' class='btn btn-primary btn-secondary'";
|
||||
html += `<div type='button' class='btn btn-primary btn-secondary'`;
|
||||
|
||||
if (options.title) {
|
||||
html += " title='" + options.title + "'";
|
||||
html += ` title='${ options.title}'`;
|
||||
}
|
||||
|
||||
html += " id='btn-new-" + options.field + "'>" + options.label + "</div>";
|
||||
html += ` id='btn-new-${options.field}'>${options.label}</div>`;
|
||||
|
||||
html += "</span>";
|
||||
html += '</span>';
|
||||
|
||||
$(modal).find('label[for="id_'+ options.field + '"]').append(html);
|
||||
}
|
||||
@ -733,7 +750,7 @@ function attachSecondaryModal(modal, options) {
|
||||
var data = options.data || {};
|
||||
|
||||
// Add a callback to the button
|
||||
$(modal).find("#btn-new-" + options.field).on('click', function() {
|
||||
$(modal).find('#btn-new-' + options.field).on('click', function() {
|
||||
|
||||
// Launch the secondary modal
|
||||
launchModalForm(
|
||||
@ -762,24 +779,26 @@ function attachSecondaryModal(modal, options) {
|
||||
}
|
||||
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
function attachSecondaries(modal, secondaries) {
|
||||
/* Attach a provided list of secondary modals */
|
||||
|
||||
// 2021-07-18 - Secondary modals will be disabled for now, until they are re-implemented in the "API forms" architecture
|
||||
return;
|
||||
|
||||
for (var i = 0; i < secondaries.length; i++) {
|
||||
attachSecondaryModal(modal, secondaries[i]);
|
||||
}
|
||||
// for (var i = 0; i < secondaries.length; i++) {
|
||||
// attachSecondaryModal(modal, secondaries[i]);
|
||||
// }
|
||||
}
|
||||
|
||||
function insertActionButton(modal, options) {
|
||||
/* Insert a custom submition button */
|
||||
/* Insert a custom submission button */
|
||||
|
||||
var html = "<span style='float: right;'>";
|
||||
html += "<button name='" + options.name + "' type='submit' class='btn btn-default modal-form-button'";
|
||||
html += " value='" + options.name + "'>" + options.title + "</button>";
|
||||
html += "</span>";
|
||||
var html = `
|
||||
<span style='float: right;'>
|
||||
<button name='${options.name}' type='submit' class='btn btn-default modal-form-button' value='${options.name}'>
|
||||
${options.title}
|
||||
</button>
|
||||
</span>`;
|
||||
|
||||
$(modal).find('#modal-footer-buttons').append(html);
|
||||
}
|
||||
@ -802,8 +821,8 @@ function attachFieldCallback(modal, callback) {
|
||||
* - action: A function to perform
|
||||
*/
|
||||
|
||||
// Find the field input in the form
|
||||
var field = getFieldByName(modal, callback.field);
|
||||
// Find the field input in the form
|
||||
var field = getFieldByName(modal, callback.field);
|
||||
|
||||
field.change(function() {
|
||||
|
||||
@ -838,8 +857,6 @@ function handleModalForm(url, options) {
|
||||
|
||||
var form = $(modal).find('.js-modal-form');
|
||||
|
||||
var _form = $(modal).find(".js-modal-form");
|
||||
|
||||
form.ajaxForm({
|
||||
url: url,
|
||||
dataType: 'json',
|
||||
@ -860,7 +877,7 @@ function handleModalForm(url, options) {
|
||||
modalEnable(modal, false);
|
||||
},
|
||||
// POST was successful
|
||||
success: function(response, status, xhr, f) {
|
||||
success: function(response) {
|
||||
// Re-enable the modal
|
||||
modalEnable(modal, true);
|
||||
if ('form_valid' in response) {
|
||||
@ -868,9 +885,8 @@ function handleModalForm(url, options) {
|
||||
if (response.form_valid) {
|
||||
$(modal).modal('hide');
|
||||
afterForm(response, options);
|
||||
}
|
||||
// Form was returned, invalid!
|
||||
else {
|
||||
} else {
|
||||
// Form was returned, invalid!
|
||||
|
||||
// Disable error message with option or response
|
||||
if (!options.hideErrorMessage && !response.hideErrorMessage) {
|
||||
@ -901,26 +917,24 @@ function handleModalForm(url, options) {
|
||||
if (response.buttons) {
|
||||
attachButtons(modal, response.buttons);
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
$(modal).modal('hide');
|
||||
showAlertDialog('{% trans "Invalid response from server" %}', '{% trans "Form data missing from server response" %}');
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
$(modal).modal('hide');
|
||||
afterForm(response, options);
|
||||
}
|
||||
},
|
||||
error: function(xhr, ajaxOptions, thrownError) {
|
||||
error: function(xhr) {
|
||||
// There was an error submitting form data via POST
|
||||
|
||||
$(modal).modal('hide');
|
||||
showAlertDialog('{% trans "Error posting form data" %}', renderErrorMessage(xhr));
|
||||
},
|
||||
complete: function(xhr) {
|
||||
//TODO
|
||||
complete: function() {
|
||||
// TODO
|
||||
}
|
||||
});
|
||||
});
|
||||
@ -960,11 +974,11 @@ function launchModalForm(url, options = {}) {
|
||||
$(modal).find('#modal-footer-buttons').html('');
|
||||
|
||||
// Form the ajax request to retrieve the django form data
|
||||
ajax_data = {
|
||||
var ajax_data = {
|
||||
url: url,
|
||||
type: 'get',
|
||||
dataType: 'json',
|
||||
beforeSend: function () {
|
||||
beforeSend: function() {
|
||||
openModal({
|
||||
modal: modal,
|
||||
submit_text: submit_text,
|
||||
@ -1017,7 +1031,7 @@ function launchModalForm(url, options = {}) {
|
||||
showAlertDialog('{% trans "Invalid server response" %}', '{% trans "JSON response missing form data" %}');
|
||||
}
|
||||
},
|
||||
error: function (xhr, ajaxOptions, thrownError) {
|
||||
error: function(xhr) {
|
||||
|
||||
$(modal).modal('hide');
|
||||
|
||||
@ -1056,8 +1070,8 @@ function launchModalForm(url, options = {}) {
|
||||
showAlertDialog('{% trans "Error requesting form data" %}', renderErrorMessage(xhr));
|
||||
}
|
||||
|
||||
console.log("Modal form error: " + xhr.status);
|
||||
console.log("Message: " + xhr.responseText);
|
||||
console.log('Modal form error: ' + xhr.status);
|
||||
console.log('Message: ' + xhr.responseText);
|
||||
}
|
||||
};
|
||||
|
||||
@ -1106,4 +1120,4 @@ function showModalImage(image_url) {
|
||||
modal.click(function() {
|
||||
hideModalImage();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1,18 +1,19 @@
|
||||
{% load i18n %}
|
||||
|
||||
/* globals
|
||||
blankImage,
|
||||
select2Thumbnail
|
||||
*/
|
||||
|
||||
function blankImage() {
|
||||
return `/static/img/blank_image.png`;
|
||||
}
|
||||
|
||||
// Render a select2 thumbnail image
|
||||
function select2Thumbnail(image) {
|
||||
if (!image) {
|
||||
image = blankImage();
|
||||
}
|
||||
|
||||
return `<img src='${image}' class='select2-thumbnail'>`;
|
||||
}
|
||||
/* exported
|
||||
renderBuild,
|
||||
renderCompany,
|
||||
renderManufacturerPart,
|
||||
renderOwner,
|
||||
renderPartCategory,
|
||||
renderStockLocation,
|
||||
renderSupplierPart,
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
@ -29,6 +30,7 @@ function select2Thumbnail(image) {
|
||||
|
||||
|
||||
// Renderer for "Company" model
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
function renderCompany(name, data, parameters, options) {
|
||||
|
||||
var html = select2Thumbnail(data.image);
|
||||
@ -42,6 +44,7 @@ function renderCompany(name, data, parameters, options) {
|
||||
|
||||
|
||||
// Renderer for "StockItem" model
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
function renderStockItem(name, data, parameters, options) {
|
||||
|
||||
var image = data.part_detail.thumbnail || data.part_detail.image || blankImage();
|
||||
@ -65,6 +68,7 @@ function renderStockItem(name, data, parameters, options) {
|
||||
|
||||
|
||||
// Renderer for "StockLocation" model
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
function renderStockLocation(name, data, parameters, options) {
|
||||
|
||||
var level = '- '.repeat(data.level);
|
||||
@ -80,7 +84,7 @@ function renderStockLocation(name, data, parameters, options) {
|
||||
return html;
|
||||
}
|
||||
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
function renderBuild(name, data, parameters, options) {
|
||||
|
||||
var image = null;
|
||||
@ -101,6 +105,7 @@ function renderBuild(name, data, parameters, options) {
|
||||
|
||||
|
||||
// Renderer for "Part" model
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
function renderPart(name, data, parameters, options) {
|
||||
|
||||
var html = select2Thumbnail(data.image);
|
||||
@ -117,6 +122,7 @@ function renderPart(name, data, parameters, options) {
|
||||
}
|
||||
|
||||
// Renderer for "User" model
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
function renderUser(name, data, parameters, options) {
|
||||
|
||||
var html = `<span>${data.username}</span>`;
|
||||
@ -130,19 +136,20 @@ function renderUser(name, data, parameters, options) {
|
||||
|
||||
|
||||
// Renderer for "Owner" model
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
function renderOwner(name, data, parameters, options) {
|
||||
|
||||
var html = `<span>${data.name}</span>`;
|
||||
|
||||
switch (data.label) {
|
||||
case 'user':
|
||||
html += `<span class='float-right fas fa-user'></span>`;
|
||||
break;
|
||||
case 'group':
|
||||
html += `<span class='float-right fas fa-users'></span>`;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
case 'user':
|
||||
html += `<span class='float-right fas fa-user'></span>`;
|
||||
break;
|
||||
case 'group':
|
||||
html += `<span class='float-right fas fa-users'></span>`;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return html;
|
||||
@ -150,6 +157,7 @@ function renderOwner(name, data, parameters, options) {
|
||||
|
||||
|
||||
// Renderer for "PartCategory" model
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
function renderPartCategory(name, data, parameters, options) {
|
||||
|
||||
var level = '- '.repeat(data.level);
|
||||
@ -165,7 +173,7 @@ function renderPartCategory(name, data, parameters, options) {
|
||||
return html;
|
||||
}
|
||||
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
function renderPartParameterTemplate(name, data, parameters, options) {
|
||||
|
||||
var html = `<span>${data.name} - [${data.units}]</span>`;
|
||||
@ -175,6 +183,7 @@ function renderPartParameterTemplate(name, data, parameters, options) {
|
||||
|
||||
|
||||
// Renderer for "ManufacturerPart" model
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
function renderManufacturerPart(name, data, parameters, options) {
|
||||
|
||||
var manufacturer_image = null;
|
||||
@ -203,6 +212,7 @@ function renderManufacturerPart(name, data, parameters, options) {
|
||||
|
||||
|
||||
// Renderer for "SupplierPart" model
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
function renderSupplierPart(name, data, parameters, options) {
|
||||
|
||||
var supplier_image = null;
|
||||
|
@ -1,6 +1,33 @@
|
||||
{% load i18n %}
|
||||
{% load inventree_extras %}
|
||||
|
||||
/* globals
|
||||
companyFormFields,
|
||||
constructForm,
|
||||
createSupplierPart,
|
||||
global_settings,
|
||||
imageHoverIcon,
|
||||
inventreeGet,
|
||||
launchModalForm,
|
||||
loadTableFilters,
|
||||
makeIconBadge,
|
||||
purchaseOrderStatusDisplay,
|
||||
renderLink,
|
||||
salesOrderStatusDisplay,
|
||||
setupFilterList,
|
||||
*/
|
||||
|
||||
/* exported
|
||||
createSalesOrder,
|
||||
editPurchaseOrderLineItem,
|
||||
loadPurchaseOrderTable,
|
||||
loadSalesOrderAllocationTable,
|
||||
loadSalesOrderTable,
|
||||
newPurchaseOrderFromOrderWizard,
|
||||
newSupplierPartFromOrderWizard,
|
||||
removeOrderRowFromOrderWizard,
|
||||
removePurchaseOrderLineItem,
|
||||
*/
|
||||
|
||||
// Create a new SalesOrder
|
||||
function createSalesOrder(options={}) {
|
||||
@ -15,7 +42,7 @@ function createSalesOrder(options={}) {
|
||||
value: options.customer,
|
||||
secondary: {
|
||||
title: '{% trans "Add Customer" %}',
|
||||
fields: function(data) {
|
||||
fields: function() {
|
||||
var fields = companyFormFields();
|
||||
|
||||
fields.is_customer.value = true;
|
||||
@ -56,7 +83,7 @@ function createPurchaseOrder(options={}) {
|
||||
value: options.supplier,
|
||||
secondary: {
|
||||
title: '{% trans "Add Supplier" %}',
|
||||
fields: function(data) {
|
||||
fields: function() {
|
||||
var fields = companyFormFields();
|
||||
|
||||
fields.is_supplier.value = true;
|
||||
@ -143,7 +170,7 @@ function newSupplierPartFromOrderWizard(e) {
|
||||
|
||||
if (response.supplier_detail) {
|
||||
text += response.supplier_detail.name;
|
||||
text += " | ";
|
||||
text += ' | ';
|
||||
}
|
||||
|
||||
text += response.SKU;
|
||||
@ -153,8 +180,7 @@ function newSupplierPartFromOrderWizard(e) {
|
||||
$('#modal-form').find(dropdown).append(option).trigger('change');
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -203,7 +229,7 @@ function newPurchaseOrderFromOrderWizard(e) {
|
||||
$('#modal-form').find(dropdown).append(option).trigger('change');
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -248,7 +274,7 @@ function loadPurchaseOrderTable(table, options) {
|
||||
|
||||
options.params['supplier_detail'] = true;
|
||||
|
||||
var filters = loadTableFilters("purchaseorder");
|
||||
var filters = loadTableFilters('purchaseorder');
|
||||
|
||||
for (var key in options.params) {
|
||||
filters[key] = options.params[key];
|
||||
@ -256,7 +282,7 @@ function loadPurchaseOrderTable(table, options) {
|
||||
|
||||
options.url = options.url || '{% url "api-po-list" %}';
|
||||
|
||||
setupFilterList("purchaseorder", $(table));
|
||||
setupFilterList('purchaseorder', $(table));
|
||||
|
||||
$(table).inventreeTable({
|
||||
url: options.url,
|
||||
@ -265,7 +291,9 @@ function loadPurchaseOrderTable(table, options) {
|
||||
groupBy: false,
|
||||
sidePagination: 'server',
|
||||
original: options.params,
|
||||
formatNoMatches: function() { return '{% trans "No purchase orders found" %}'; },
|
||||
formatNoMatches: function() {
|
||||
return '{% trans "No purchase orders found" %}';
|
||||
},
|
||||
columns: [
|
||||
{
|
||||
title: '',
|
||||
@ -278,7 +306,7 @@ function loadPurchaseOrderTable(table, options) {
|
||||
title: '{% trans "Purchase Order" %}',
|
||||
sortable: true,
|
||||
switchable: false,
|
||||
formatter: function(value, row, index, field) {
|
||||
formatter: function(value, row) {
|
||||
|
||||
var prefix = global_settings.PURCHASEORDER_REFERENCE_PREFIX;
|
||||
|
||||
@ -300,7 +328,7 @@ function loadPurchaseOrderTable(table, options) {
|
||||
title: '{% trans "Supplier" %}',
|
||||
sortable: true,
|
||||
sortName: 'supplier__name',
|
||||
formatter: function(value, row, index, field) {
|
||||
formatter: function(value, row) {
|
||||
return imageHoverIcon(row.supplier_detail.image) + renderLink(row.supplier_detail.name, `/company/${row.supplier}/purchase-orders/`);
|
||||
}
|
||||
},
|
||||
@ -316,7 +344,7 @@ function loadPurchaseOrderTable(table, options) {
|
||||
field: 'status',
|
||||
title: '{% trans "Status" %}',
|
||||
sortable: true,
|
||||
formatter: function(value, row, index, field) {
|
||||
formatter: function(value, row) {
|
||||
return purchaseOrderStatusDisplay(row.status, row.status_text);
|
||||
}
|
||||
},
|
||||
@ -344,7 +372,7 @@ function loadSalesOrderTable(table, options) {
|
||||
options.params = options.params || {};
|
||||
options.params['customer_detail'] = true;
|
||||
|
||||
var filters = loadTableFilters("salesorder");
|
||||
var filters = loadTableFilters('salesorder');
|
||||
|
||||
for (var key in options.params) {
|
||||
filters[key] = options.params[key];
|
||||
@ -352,7 +380,7 @@ function loadSalesOrderTable(table, options) {
|
||||
|
||||
options.url = options.url || '{% url "api-so-list" %}';
|
||||
|
||||
setupFilterList("salesorder", $(table));
|
||||
setupFilterList('salesorder', $(table));
|
||||
|
||||
$(table).inventreeTable({
|
||||
url: options.url,
|
||||
@ -361,7 +389,9 @@ function loadSalesOrderTable(table, options) {
|
||||
groupBy: false,
|
||||
sidePagination: 'server',
|
||||
original: options.params,
|
||||
formatNoMatches: function() { return '{% trans "No sales orders found" %}'; },
|
||||
formatNoMatches: function() {
|
||||
return '{% trans "No sales orders found" %}';
|
||||
},
|
||||
columns: [
|
||||
{
|
||||
title: '',
|
||||
@ -373,7 +403,7 @@ function loadSalesOrderTable(table, options) {
|
||||
sortable: true,
|
||||
field: 'reference',
|
||||
title: '{% trans "Sales Order" %}',
|
||||
formatter: function(value, row, index, field) {
|
||||
formatter: function(value, row) {
|
||||
|
||||
var prefix = global_settings.SALESORDER_REFERENCE_PREFIX;
|
||||
|
||||
@ -395,7 +425,7 @@ function loadSalesOrderTable(table, options) {
|
||||
sortName: 'customer__name',
|
||||
field: 'customer_detail',
|
||||
title: '{% trans "Customer" %}',
|
||||
formatter: function(value, row, index, field) {
|
||||
formatter: function(value, row) {
|
||||
|
||||
if (!row.customer_detail) {
|
||||
return '{% trans "Invalid Customer" %}';
|
||||
@ -418,7 +448,7 @@ function loadSalesOrderTable(table, options) {
|
||||
sortable: true,
|
||||
field: 'status',
|
||||
title: '{% trans "Status" %}',
|
||||
formatter: function(value, row, index, field) {
|
||||
formatter: function(value, row) {
|
||||
return salesOrderStatusDisplay(row.status, row.status_text);
|
||||
}
|
||||
},
|
||||
@ -459,13 +489,13 @@ function loadSalesOrderAllocationTable(table, options={}) {
|
||||
options.params['item_detail'] = true;
|
||||
options.params['order_detail'] = true;
|
||||
|
||||
var filters = loadTableFilters("salesorderallocation");
|
||||
var filters = loadTableFilters('salesorderallocation');
|
||||
|
||||
for (var key in options.params) {
|
||||
filters[key] = options.params[key];
|
||||
}
|
||||
|
||||
setupFilterList("salesorderallocation", $(table));
|
||||
setupFilterList('salesorderallocation', $(table));
|
||||
|
||||
$(table).inventreeTable({
|
||||
url: '{% url "api-so-allocation-list" %}',
|
||||
@ -475,7 +505,9 @@ function loadSalesOrderAllocationTable(table, options={}) {
|
||||
search: false,
|
||||
paginationVAlign: 'bottom',
|
||||
original: options.params,
|
||||
formatNoMatches: function() { return '{% trans "No sales order allocations found" %}'; },
|
||||
formatNoMatches: function() {
|
||||
return '{% trans "No sales order allocations found" %}';
|
||||
},
|
||||
columns: [
|
||||
{
|
||||
field: 'pk',
|
||||
@ -486,7 +518,6 @@ function loadSalesOrderAllocationTable(table, options={}) {
|
||||
field: 'order',
|
||||
switchable: false,
|
||||
title: '{% trans "Order" %}',
|
||||
switchable: false,
|
||||
formatter: function(value, row) {
|
||||
|
||||
var prefix = global_settings.SALESORDER_REFERENCE_PREFIX;
|
||||
@ -530,4 +561,4 @@ function loadSalesOrderAllocationTable(table, options={}) {
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1,20 +1,48 @@
|
||||
{% load i18n %}
|
||||
{% load inventree_extras %}
|
||||
|
||||
/* globals
|
||||
Chart,
|
||||
constructForm,
|
||||
global_settings,
|
||||
imageHoverIcon,
|
||||
inventreeGet,
|
||||
inventreePut,
|
||||
launchModalForm,
|
||||
linkButtonsToSelection,
|
||||
loadTableFilters,
|
||||
makeIconBadge,
|
||||
makeIconButton,
|
||||
printPartLabels,
|
||||
renderLink,
|
||||
setFormGroupVisibility,
|
||||
setupFilterList,
|
||||
yesNoLabel,
|
||||
*/
|
||||
|
||||
/* exported
|
||||
duplicatePart,
|
||||
editCategory,
|
||||
editPart,
|
||||
initPriceBreakSet,
|
||||
loadBomChart,
|
||||
loadParametricPartTable,
|
||||
loadPartCategoryTable,
|
||||
loadPartParameterTable,
|
||||
loadPartTable,
|
||||
loadPartTestTemplateTable,
|
||||
loadPartVariantTable,
|
||||
loadSellPricingChart,
|
||||
loadSimplePartTable,
|
||||
loadStockPricingChart,
|
||||
toggleStar,
|
||||
*/
|
||||
|
||||
/* Part API functions
|
||||
* Requires api.js to be loaded first
|
||||
*/
|
||||
|
||||
function yesNoLabel(value) {
|
||||
if (value) {
|
||||
return `<span class='label label-green'>{% trans "YES" %}</span>`;
|
||||
} else {
|
||||
return `<span class='label label-yellow'>{% trans "NO" %}</span>`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function partGroups(options={}) {
|
||||
function partGroups() {
|
||||
|
||||
return {
|
||||
attributes: {
|
||||
@ -34,10 +62,10 @@ function partGroups(options={}) {
|
||||
collapsible: true,
|
||||
hidden: !global_settings.PART_PURCHASEABLE,
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
// Construct fieldset for part forms
|
||||
function partFields(options={}) {
|
||||
|
||||
@ -45,7 +73,7 @@ function partFields(options={}) {
|
||||
category: {
|
||||
secondary: {
|
||||
title: '{% trans "Add Part Category" %}',
|
||||
fields: function(data) {
|
||||
fields: function() {
|
||||
var fields = categoryFields();
|
||||
|
||||
return fields;
|
||||
@ -115,14 +143,14 @@ function partFields(options={}) {
|
||||
|
||||
// Pop expiry field
|
||||
if (!global_settings.STOCK_ENABLE_EXPIRY) {
|
||||
delete fields["default_expiry"];
|
||||
delete fields['default_expiry'];
|
||||
}
|
||||
|
||||
// Additional fields when "creating" a new part
|
||||
if (options.create) {
|
||||
|
||||
// No supplier parts available yet
|
||||
delete fields["default_supplier"];
|
||||
delete fields['default_supplier'];
|
||||
|
||||
if (global_settings.PART_CREATE_INITIAL) {
|
||||
|
||||
@ -264,7 +292,7 @@ function categoryFields() {
|
||||
|
||||
|
||||
// Edit a PartCategory via the API
|
||||
function editCategory(pk, options={}) {
|
||||
function editCategory(pk) {
|
||||
|
||||
var url = `/api/part/category/${pk}/`;
|
||||
|
||||
@ -279,7 +307,7 @@ function editCategory(pk, options={}) {
|
||||
}
|
||||
|
||||
|
||||
function editPart(pk, options={}) {
|
||||
function editPart(pk) {
|
||||
|
||||
var url = `/api/part/${pk}/`;
|
||||
|
||||
@ -291,7 +319,7 @@ function editPart(pk, options={}) {
|
||||
|
||||
constructForm(url, {
|
||||
fields: fields,
|
||||
groups: partGroups(),
|
||||
groups: groups,
|
||||
title: '{% trans "Edit Part" %}',
|
||||
reload: true,
|
||||
});
|
||||
@ -370,7 +398,7 @@ function toggleStar(options) {
|
||||
}
|
||||
|
||||
|
||||
function makePartIcons(part, options={}) {
|
||||
function makePartIcons(part) {
|
||||
/* Render a set of icons for the given part.
|
||||
*/
|
||||
|
||||
@ -397,7 +425,7 @@ function makePartIcons(part, options={}) {
|
||||
}
|
||||
|
||||
if (part.salable) {
|
||||
html += makeIconBadge('fa-dollar-sign', title='{% trans "Salable part" %}');
|
||||
html += makeIconBadge('fa-dollar-sign', '{% trans "Salable part" %}');
|
||||
}
|
||||
|
||||
if (!part.active) {
|
||||
@ -418,13 +446,13 @@ function loadPartVariantTable(table, partId, options={}) {
|
||||
params.ancestor = partId;
|
||||
|
||||
// Load filters
|
||||
var filters = loadTableFilters("variants");
|
||||
var filters = loadTableFilters('variants');
|
||||
|
||||
for (var key in params) {
|
||||
filters[key] = params[key];
|
||||
}
|
||||
|
||||
setupFilterList("variants", $(table));
|
||||
setupFilterList('variants', $(table));
|
||||
|
||||
var cols = [
|
||||
{
|
||||
@ -437,7 +465,7 @@ function loadPartVariantTable(table, partId, options={}) {
|
||||
field: 'name',
|
||||
title: '{% trans "Name" %}',
|
||||
switchable: false,
|
||||
formatter: function(value, row, index, field) {
|
||||
formatter: function(value, row) {
|
||||
var html = '';
|
||||
|
||||
var name = '';
|
||||
@ -506,12 +534,14 @@ function loadPartVariantTable(table, partId, options={}) {
|
||||
];
|
||||
|
||||
table.inventreeTable({
|
||||
url: "{% url 'api-part-list' %}",
|
||||
url: '{% url "api-part-list" %}',
|
||||
name: 'partvariants',
|
||||
showColumns: true,
|
||||
original: params,
|
||||
queryParams: filters,
|
||||
formatNoMatches: function() { return '{% trans "No variants found" %}'; },
|
||||
formatNoMatches: function() {
|
||||
return '{% trans "No variants found" %}';
|
||||
},
|
||||
columns: cols,
|
||||
treeEnable: true,
|
||||
rootParentId: partId,
|
||||
@ -545,7 +575,7 @@ function loadPartParameterTable(table, url, options) {
|
||||
var params = options.params || {};
|
||||
|
||||
// Load filters
|
||||
var filters = loadTableFilters("part-parameters");
|
||||
var filters = loadTableFilters('part-parameters');
|
||||
|
||||
for (var key in params) {
|
||||
filters[key] = params[key];
|
||||
@ -559,7 +589,9 @@ function loadPartParameterTable(table, url, options) {
|
||||
queryParams: filters,
|
||||
name: 'partparameters',
|
||||
groupBy: false,
|
||||
formatNoMatches: function() { return '{% trans "No parameters found" %}'; },
|
||||
formatNoMatches: function() {
|
||||
return '{% trans "No parameters found" %}';
|
||||
},
|
||||
columns: [
|
||||
{
|
||||
checkbox: true,
|
||||
@ -650,19 +682,19 @@ function loadParametricPartTable(table, options={}) {
|
||||
* - table_data: Parameters data
|
||||
*/
|
||||
|
||||
var table_headers = options.headers
|
||||
var table_data = options.data
|
||||
var table_headers = options.headers;
|
||||
var table_data = options.data;
|
||||
|
||||
var columns = [];
|
||||
|
||||
for (header of table_headers) {
|
||||
for (var header of table_headers) {
|
||||
if (header === 'part') {
|
||||
columns.push({
|
||||
field: header,
|
||||
title: '{% trans "Part" %}',
|
||||
sortable: true,
|
||||
sortName: 'name',
|
||||
formatter: function(value, row, index, field) {
|
||||
formatter: function(value, row) {
|
||||
|
||||
var name = '';
|
||||
|
||||
@ -687,8 +719,6 @@ function loadParametricPartTable(table, options={}) {
|
||||
title: header,
|
||||
sortable: true,
|
||||
filterControl: 'input',
|
||||
/* TODO: Search icons are not displayed */
|
||||
/*clear: 'fa-times icon-red',*/
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -698,7 +728,9 @@ function loadParametricPartTable(table, options={}) {
|
||||
queryParams: table_headers,
|
||||
groupBy: false,
|
||||
name: options.name || 'parametric',
|
||||
formatNoMatches: function() { return '{% trans "No parts found" %}'; },
|
||||
formatNoMatches: function() {
|
||||
return '{% trans "No parts found" %}';
|
||||
},
|
||||
columns: columns,
|
||||
showColumns: true,
|
||||
data: table_data,
|
||||
@ -785,15 +817,17 @@ function loadPartTable(table, url, options={}) {
|
||||
|
||||
var filters = {};
|
||||
|
||||
var col = null;
|
||||
|
||||
if (!options.disableFilters) {
|
||||
filters = loadTableFilters("parts");
|
||||
filters = loadTableFilters('parts');
|
||||
}
|
||||
|
||||
for (var key in params) {
|
||||
filters[key] = params[key];
|
||||
}
|
||||
|
||||
setupFilterList("parts", $(table), options.filterTarget || null);
|
||||
setupFilterList('parts', $(table), options.filterTarget || null);
|
||||
|
||||
var columns = [
|
||||
{
|
||||
@ -818,16 +852,18 @@ function loadPartTable(table, url, options={}) {
|
||||
field: 'IPN',
|
||||
title: 'IPN',
|
||||
};
|
||||
|
||||
if (!options.params.ordering) {
|
||||
col['sortable'] = true;
|
||||
};
|
||||
}
|
||||
|
||||
columns.push(col);
|
||||
|
||||
col = {
|
||||
field: 'name',
|
||||
title: '{% trans "Part" %}',
|
||||
switchable: false,
|
||||
formatter: function(value, row, index, field) {
|
||||
formatter: function(value, row) {
|
||||
|
||||
var name = '';
|
||||
|
||||
@ -854,18 +890,20 @@ function loadPartTable(table, url, options={}) {
|
||||
return display;
|
||||
}
|
||||
};
|
||||
|
||||
if (!options.params.ordering) {
|
||||
col['sortable'] = true;
|
||||
};
|
||||
}
|
||||
|
||||
columns.push(col);
|
||||
|
||||
columns.push({
|
||||
field: 'description',
|
||||
title: '{% trans "Description" %}',
|
||||
formatter: function(value, row, index, field) {
|
||||
formatter: function(value, row) {
|
||||
|
||||
if (row.is_template) {
|
||||
value = '<i>' + value + '</i>';
|
||||
value = `<i>${value}</i>`;
|
||||
}
|
||||
|
||||
return value;
|
||||
@ -876,60 +914,63 @@ function loadPartTable(table, url, options={}) {
|
||||
sortName: 'category',
|
||||
field: 'category_detail',
|
||||
title: '{% trans "Category" %}',
|
||||
formatter: function(value, row, index, field) {
|
||||
formatter: function(value, row) {
|
||||
if (row.category) {
|
||||
return renderLink(value.pathstring, "/part/category/" + row.category + "/");
|
||||
}
|
||||
else {
|
||||
return renderLink(value.pathstring, `/part/category/${row.category}/`);
|
||||
} else {
|
||||
return '{% trans "No category" %}';
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (!options.params.ordering) {
|
||||
col['sortable'] = true;
|
||||
};
|
||||
}
|
||||
|
||||
columns.push(col);
|
||||
|
||||
col = {
|
||||
field: 'in_stock',
|
||||
title: '{% trans "Stock" %}',
|
||||
searchable: false,
|
||||
formatter: function(value, row, index, field) {
|
||||
var link = "stock";
|
||||
formatter: function(value, row) {
|
||||
var link = 'stock';
|
||||
|
||||
if (value) {
|
||||
// There IS stock available for this part
|
||||
|
||||
// Is stock "low" (below the 'minimum_stock' quantity)?
|
||||
if (row.minimum_stock && row.minimum_stock > value) {
|
||||
value += "<span class='label label-right label-warning'>{% trans "Low stock" %}</span>";
|
||||
value += `<span class='label label-right label-warning'>{% trans "Low stock" %}</span>`;
|
||||
}
|
||||
|
||||
} else if (row.on_order) {
|
||||
// There is no stock available, but stock is on order
|
||||
value = "0<span class='label label-right label-primary'>{% trans "On Order" %}: " + row.on_order + "</span>";
|
||||
link = "orders";
|
||||
value = `0<span class='label label-right label-primary'>{% trans "On Order" %}: ${row.on_order}</span>`;
|
||||
link = 'orders';
|
||||
} else if (row.building) {
|
||||
// There is no stock available, but stock is being built
|
||||
value = "0<span class='label label-right label-info'>{% trans "Building" %}: " + row.building + "</span>";
|
||||
link = "builds";
|
||||
value = `0<span class='label label-right label-info'>{% trans "Building" %}: ${row.building}</span>`;
|
||||
link = 'builds';
|
||||
} else {
|
||||
// There is no stock available
|
||||
value = "0<span class='label label-right label-danger'>{% trans "No Stock" %}</span>";
|
||||
value = `0<span class='label label-right label-danger'>{% trans "No Stock" %}</span>`;
|
||||
}
|
||||
|
||||
return renderLink(value, '/part/' + row.pk + "/" + link + "/");
|
||||
return renderLink(value, `/part/${row.pk}/${link}/`);
|
||||
}
|
||||
};
|
||||
|
||||
if (!options.params.ordering) {
|
||||
col['sortable'] = true;
|
||||
};
|
||||
}
|
||||
|
||||
columns.push(col);
|
||||
|
||||
columns.push({
|
||||
field: 'link',
|
||||
title: '{% trans "Link" %}',
|
||||
formatter: function(value, row, index, field) {
|
||||
formatter: function(value) {
|
||||
return renderLink(
|
||||
value, value,
|
||||
{
|
||||
@ -949,7 +990,9 @@ function loadPartTable(table, url, options={}) {
|
||||
original: params,
|
||||
sidePagination: 'server',
|
||||
pagination: 'true',
|
||||
formatNoMatches: function() { return '{% trans "No parts found" %}'; },
|
||||
formatNoMatches: function() {
|
||||
return '{% trans "No parts found" %}';
|
||||
},
|
||||
columns: columns,
|
||||
showColumns: true,
|
||||
showCustomView: false,
|
||||
@ -982,8 +1025,8 @@ function loadPartTable(table, url, options={}) {
|
||||
|
||||
/* Button callbacks for part table buttons */
|
||||
|
||||
$("#multi-part-order").click(function() {
|
||||
var selections = $(table).bootstrapTable("getSelections");
|
||||
$('#multi-part-order').click(function() {
|
||||
var selections = $(table).bootstrapTable('getSelections');
|
||||
|
||||
var parts = [];
|
||||
|
||||
@ -991,15 +1034,15 @@ function loadPartTable(table, url, options={}) {
|
||||
parts.push(item.pk);
|
||||
});
|
||||
|
||||
launchModalForm("/order/purchase-order/order-parts/", {
|
||||
launchModalForm('/order/purchase-order/order-parts/', {
|
||||
data: {
|
||||
parts: parts,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
$("#multi-part-category").click(function() {
|
||||
var selections = $(table).bootstrapTable("getSelections");
|
||||
$('#multi-part-category').click(function() {
|
||||
var selections = $(table).bootstrapTable('getSelections');
|
||||
|
||||
var parts = [];
|
||||
|
||||
@ -1007,7 +1050,7 @@ function loadPartTable(table, url, options={}) {
|
||||
parts.push(item.pk);
|
||||
});
|
||||
|
||||
launchModalForm("/part/set-category/", {
|
||||
launchModalForm('/part/set-category/', {
|
||||
data: {
|
||||
parts: parts,
|
||||
},
|
||||
@ -1028,7 +1071,7 @@ function loadPartTable(table, url, options={}) {
|
||||
});
|
||||
|
||||
$('#multi-part-export').click(function() {
|
||||
var selections = $(table).bootstrapTable("getSelections");
|
||||
var selections = $(table).bootstrapTable('getSelections');
|
||||
|
||||
var parts = '';
|
||||
|
||||
@ -1127,15 +1170,15 @@ function loadPartTestTemplateTable(table, options) {
|
||||
|
||||
var filterListElement = options.filterList || '#filter-list-parttests';
|
||||
|
||||
var filters = loadTableFilters("parttests");
|
||||
var filters = loadTableFilters('parttests');
|
||||
|
||||
var original = {};
|
||||
|
||||
for (var key in params) {
|
||||
original[key] = params[key];
|
||||
for (var k in params) {
|
||||
original[k] = params[k];
|
||||
}
|
||||
|
||||
setupFilterList("parttests", table, filterListElement);
|
||||
setupFilterList('parttests', table, filterListElement);
|
||||
|
||||
// Override the default values, or add new ones
|
||||
for (var key in params) {
|
||||
@ -1147,7 +1190,7 @@ function loadPartTestTemplateTable(table, options) {
|
||||
formatNoMatches: function() {
|
||||
return '{% trans "No test templates matching query" %}';
|
||||
},
|
||||
url: "{% url 'api-part-test-template-list' %}",
|
||||
url: '{% url "api-part-test-template-list" %}',
|
||||
queryParams: filters,
|
||||
name: 'testtemplate',
|
||||
original: original,
|
||||
@ -1168,7 +1211,7 @@ function loadPartTestTemplateTable(table, options) {
|
||||
},
|
||||
{
|
||||
field: 'required',
|
||||
title: "{% trans 'Required' %}",
|
||||
title: '{% trans "Required" %}',
|
||||
sortable: true,
|
||||
formatter: function(value) {
|
||||
return yesNoLabel(value);
|
||||
@ -1235,28 +1278,29 @@ function loadPriceBreakTable(table, options) {
|
||||
onLoadSuccess: function(tableData) {
|
||||
if (linkedGraph) {
|
||||
// sort array
|
||||
tableData = tableData.sort((a,b)=>a.quantity-b.quantity);
|
||||
tableData = tableData.sort((a, b) => (a.quantity - b.quantity));
|
||||
|
||||
// split up for graph definition
|
||||
var graphLabels = Array.from(tableData, x => x.quantity);
|
||||
var graphData = Array.from(tableData, x => x.price);
|
||||
var graphLabels = Array.from(tableData, (x) => (x.quantity));
|
||||
var graphData = Array.from(tableData, (x) => (x.price));
|
||||
|
||||
// destroy chart if exists
|
||||
if (chart){
|
||||
if (chart) {
|
||||
chart.destroy();
|
||||
}
|
||||
chart = loadLineChart(linkedGraph,
|
||||
{
|
||||
labels: graphLabels,
|
||||
datasets: [
|
||||
{
|
||||
label: '{% trans "Unit Price" %}',
|
||||
data: graphData,
|
||||
backgroundColor: 'rgba(255, 206, 86, 0.2)',
|
||||
borderColor: 'rgb(255, 206, 86)',
|
||||
stepped: true,
|
||||
fill: true,
|
||||
},]
|
||||
datasets: [
|
||||
{
|
||||
label: '{% trans "Unit Price" %}',
|
||||
data: graphData,
|
||||
backgroundColor: 'rgba(255, 206, 86, 0.2)',
|
||||
borderColor: 'rgb(255, 206, 86)',
|
||||
stepped: true,
|
||||
fill: true,
|
||||
},
|
||||
],
|
||||
}
|
||||
);
|
||||
}
|
||||
@ -1277,10 +1321,10 @@ function loadPriceBreakTable(table, options) {
|
||||
field: 'price',
|
||||
title: '{% trans "Price" %}',
|
||||
sortable: true,
|
||||
formatter: function(value, row, index) {
|
||||
formatter: function(value, row) {
|
||||
var html = value;
|
||||
|
||||
html += `<div class='btn-group float-right' role='group'>`
|
||||
html += `<div class='btn-group float-right' role='group'>`;
|
||||
|
||||
html += makeIconButton('fa-edit icon-blue', `button-${name}-edit`, row.pk, `{% trans "Edit ${human_name}" %}`);
|
||||
html += makeIconButton('fa-trash-alt icon-red', `button-${name}-delete`, row.pk, `{% trans "Delete ${human_name}" %}`);
|
||||
@ -1330,8 +1374,8 @@ function initPriceBreakSet(table, options) {
|
||||
}
|
||||
);
|
||||
|
||||
function reloadPriceBreakTable(){
|
||||
table.bootstrapTable("refresh");
|
||||
function reloadPriceBreakTable() {
|
||||
table.bootstrapTable('refresh');
|
||||
}
|
||||
|
||||
pb_new_btn.click(function() {
|
||||
@ -1419,12 +1463,26 @@ function loadBomChart(context, data) {
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {legend: {position: 'bottom'},
|
||||
scales: {xAxes: [{beginAtZero: true, ticks: {autoSkip: false}}]}}
|
||||
plugins: {
|
||||
legend: {
|
||||
position: 'bottom',
|
||||
},
|
||||
scales: {
|
||||
xAxes: [
|
||||
{
|
||||
beginAtZero: true,
|
||||
ticks: {
|
||||
autoSkip: false,
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function loadSellPricingChart(context, data) {
|
||||
return new Chart(context, {
|
||||
type: 'line',
|
||||
@ -1432,21 +1490,29 @@ function loadSellPricingChart(context, data) {
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {legend: {position: 'bottom'}},
|
||||
plugins: {
|
||||
legend: {
|
||||
position: 'bottom'
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
y: {
|
||||
type: 'linear',
|
||||
position: 'left',
|
||||
grid: {display: false},
|
||||
grid: {
|
||||
display: false
|
||||
},
|
||||
title: {
|
||||
display: true,
|
||||
text: '{% trans "Unit Price" %}'
|
||||
text: '{% trans "Unit Price" %}',
|
||||
}
|
||||
},
|
||||
y1: {
|
||||
type: 'linear',
|
||||
position: 'right',
|
||||
grid: {display: false},
|
||||
grid: {
|
||||
display: false
|
||||
},
|
||||
titel: {
|
||||
display: true,
|
||||
text: '{% trans "Quantity" %}',
|
||||
|
@ -1,5 +1,25 @@
|
||||
{% load i18n %}
|
||||
|
||||
/* globals
|
||||
attachSelect,
|
||||
closeModal,
|
||||
inventreeGet,
|
||||
openModal,
|
||||
makeOptionsList,
|
||||
modalEnable,
|
||||
modalSetContent,
|
||||
modalSetTitle,
|
||||
modalSubmit,
|
||||
showAlertDialog,
|
||||
*/
|
||||
|
||||
/* exported
|
||||
printBomReports,
|
||||
printBuildReports,
|
||||
printPurchaseOrderReports,
|
||||
printSalesOrderReports,
|
||||
printTestReports,
|
||||
*/
|
||||
|
||||
function selectReport(reports, items, options={}) {
|
||||
/**
|
||||
@ -88,7 +108,7 @@ function selectReport(reports, items, options={}) {
|
||||
}
|
||||
|
||||
|
||||
function printTestReports(items, options={}) {
|
||||
function printTestReports(items) {
|
||||
/**
|
||||
* Print test reports for the provided stock item(s)
|
||||
*/
|
||||
@ -142,7 +162,7 @@ function printTestReports(items, options={}) {
|
||||
}
|
||||
|
||||
|
||||
function printBuildReports(builds, options={}) {
|
||||
function printBuildReports(builds) {
|
||||
/**
|
||||
* Print Build report for the provided build(s)
|
||||
*/
|
||||
@ -188,14 +208,14 @@ function printBuildReports(builds, options={}) {
|
||||
window.location.href = href;
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
function printBomReports(parts, options={}) {
|
||||
function printBomReports(parts) {
|
||||
/**
|
||||
* Print BOM reports for the provided part(s)
|
||||
*/
|
||||
@ -245,11 +265,11 @@ function printBomReports(parts, options={}) {
|
||||
);
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
function printPurchaseOrderReports(orders, options={}) {
|
||||
function printPurchaseOrderReports(orders) {
|
||||
/**
|
||||
* Print PO reports for the provided purchase order(s)
|
||||
*/
|
||||
@ -296,14 +316,14 @@ function printPurchaseOrderReports(orders, options={}) {
|
||||
window.location.href = href;
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
function printSalesOrderReports(orders, options={}) {
|
||||
function printSalesOrderReports(orders) {
|
||||
/**
|
||||
* Print SO reports for the provided purchase order(s)
|
||||
*/
|
||||
@ -350,8 +370,8 @@ function printSalesOrderReports(orders, options={}) {
|
||||
window.location.href = href;
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
@ -2,6 +2,63 @@
|
||||
{% load inventree_extras %}
|
||||
{% load status_codes %}
|
||||
|
||||
/* globals
|
||||
attachSelect,
|
||||
attachToggle,
|
||||
blankImage,
|
||||
enableField,
|
||||
clearField,
|
||||
clearFieldOptions,
|
||||
closeModal,
|
||||
constructFormBody,
|
||||
constructNumberInput,
|
||||
createNewModal,
|
||||
getFormFieldValue,
|
||||
global_settings,
|
||||
handleFormErrors,
|
||||
imageHoverIcon,
|
||||
inventreeDelete,
|
||||
inventreeGet,
|
||||
inventreePut,
|
||||
launchModalForm,
|
||||
linkButtonsToSelection,
|
||||
loadTableFilters,
|
||||
makeIconBadge,
|
||||
makeIconButton,
|
||||
makeOptionsList,
|
||||
makePartIcons,
|
||||
modalEnable,
|
||||
modalSetContent,
|
||||
modalSetTitle,
|
||||
modalSubmit,
|
||||
moment,
|
||||
openModal,
|
||||
printStockItemLabels,
|
||||
printTestReports,
|
||||
renderLink,
|
||||
reloadFieldOptions,
|
||||
scanItemsIntoLocation,
|
||||
showAlertDialog,
|
||||
setFieldValue,
|
||||
setupFilterList,
|
||||
showApiError,
|
||||
stockStatusDisplay,
|
||||
*/
|
||||
|
||||
/* exported
|
||||
createNewStockItem,
|
||||
exportStock,
|
||||
loadInstalledInTable,
|
||||
loadStockLocationTable,
|
||||
loadStockTable,
|
||||
loadStockTestResultsTable,
|
||||
loadStockTrackingTable,
|
||||
loadTableFilters,
|
||||
locationFields,
|
||||
removeStockRow,
|
||||
stockStatusCodes,
|
||||
*/
|
||||
|
||||
|
||||
function locationFields() {
|
||||
return {
|
||||
@ -23,7 +80,7 @@ function stockStatusCodes() {
|
||||
{% for code in StockStatus.list %}
|
||||
{
|
||||
key: {{ code.key }},
|
||||
text: "{{ code.value }}",
|
||||
text: '{{ code.value }}',
|
||||
},
|
||||
{% endfor %}
|
||||
];
|
||||
@ -45,11 +102,23 @@ function exportStock(params={}) {
|
||||
type: 'choice',
|
||||
value: 'csv',
|
||||
choices: [
|
||||
{ value: 'csv', display_name: 'CSV' },
|
||||
{ value: 'tsv', display_name: 'TSV' },
|
||||
{ value: 'xls', display_name: 'XLS' },
|
||||
{ value: 'xlsx', display_name: 'XLSX' },
|
||||
]
|
||||
{
|
||||
value: 'csv',
|
||||
display_name: 'CSV',
|
||||
},
|
||||
{
|
||||
value: 'tsv',
|
||||
display_name: 'TSV',
|
||||
},
|
||||
{
|
||||
value: 'xls',
|
||||
display_name: 'XLS',
|
||||
},
|
||||
{
|
||||
value: 'xlsx',
|
||||
display_name: 'XLSX',
|
||||
},
|
||||
],
|
||||
},
|
||||
sublocations: {
|
||||
label: '{% trans "Include Sublocations" %}',
|
||||
@ -94,34 +163,34 @@ function adjustStock(action, items, options={}) {
|
||||
var allowSerializedStock = false;
|
||||
|
||||
switch (action) {
|
||||
case 'move':
|
||||
formTitle = '{% trans "Transfer Stock" %}';
|
||||
actionTitle = '{% trans "Move" %}';
|
||||
specifyLocation = true;
|
||||
allowSerializedStock = true;
|
||||
url = '{% url "api-stock-transfer" %}';
|
||||
break;
|
||||
case 'count':
|
||||
formTitle = '{% trans "Count Stock" %}';
|
||||
actionTitle = '{% trans "Count" %}';
|
||||
url = '{% url "api-stock-count" %}';
|
||||
break;
|
||||
case 'take':
|
||||
formTitle = '{% trans "Remove Stock" %}';
|
||||
actionTitle = '{% trans "Take" %}';
|
||||
url = '{% url "api-stock-remove" %}';
|
||||
break;
|
||||
case 'add':
|
||||
formTitle = '{% trans "Add Stock" %}';
|
||||
actionTitle = '{% trans "Add" %}';
|
||||
url = '{% url "api-stock-add" %}';
|
||||
break;
|
||||
case 'delete':
|
||||
formTitle = '{% trans "Delete Stock" %}';
|
||||
allowSerializedStock = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
case 'move':
|
||||
formTitle = '{% trans "Transfer Stock" %}';
|
||||
actionTitle = '{% trans "Move" %}';
|
||||
specifyLocation = true;
|
||||
allowSerializedStock = true;
|
||||
url = '{% url "api-stock-transfer" %}';
|
||||
break;
|
||||
case 'count':
|
||||
formTitle = '{% trans "Count Stock" %}';
|
||||
actionTitle = '{% trans "Count" %}';
|
||||
url = '{% url "api-stock-count" %}';
|
||||
break;
|
||||
case 'take':
|
||||
formTitle = '{% trans "Remove Stock" %}';
|
||||
actionTitle = '{% trans "Take" %}';
|
||||
url = '{% url "api-stock-remove" %}';
|
||||
break;
|
||||
case 'add':
|
||||
formTitle = '{% trans "Add Stock" %}';
|
||||
actionTitle = '{% trans "Add" %}';
|
||||
url = '{% url "api-stock-add" %}';
|
||||
break;
|
||||
case 'delete':
|
||||
formTitle = '{% trans "Delete Stock" %}';
|
||||
allowSerializedStock = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Generate modal HTML content
|
||||
@ -157,25 +226,25 @@ function adjustStock(action, items, options={}) {
|
||||
var value = null;
|
||||
|
||||
switch (action) {
|
||||
case 'move':
|
||||
minValue = 0;
|
||||
maxValue = item.quantity;
|
||||
value = item.quantity;
|
||||
break;
|
||||
case 'add':
|
||||
minValue = 0;
|
||||
value = 0;
|
||||
break;
|
||||
case 'take':
|
||||
minValue = 0;
|
||||
value = 0;
|
||||
break;
|
||||
case 'count':
|
||||
minValue = 0;
|
||||
value = item.quantity;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
case 'move':
|
||||
minValue = 0;
|
||||
maxValue = item.quantity;
|
||||
value = item.quantity;
|
||||
break;
|
||||
case 'add':
|
||||
minValue = 0;
|
||||
value = 0;
|
||||
break;
|
||||
case 'take':
|
||||
minValue = 0;
|
||||
value = 0;
|
||||
break;
|
||||
case 'count':
|
||||
minValue = 0;
|
||||
value = item.quantity;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
var image = item.part_detail.thumbnail || item.part_detail.image || blankImage();
|
||||
@ -208,8 +277,8 @@ function adjustStock(action, items, options={}) {
|
||||
read_only: readonly,
|
||||
title: readonly ? '{% trans "Quantity cannot be adjusted for serialized stock" %}' : '{% trans "Specify stock quantity" %}',
|
||||
}
|
||||
)
|
||||
};
|
||||
);
|
||||
}
|
||||
|
||||
var buttons = `<div class='btn-group float-right' role='group'>`;
|
||||
|
||||
@ -283,7 +352,7 @@ function adjustStock(action, items, options={}) {
|
||||
confirm: true,
|
||||
confirmMessage: '{% trans "Confirm stock adjustment" %}',
|
||||
modal: modal,
|
||||
onSubmit: function(fields, opts) {
|
||||
onSubmit: function(fields) {
|
||||
|
||||
// "Delete" action gets handled differently
|
||||
if (action == 'delete') {
|
||||
@ -295,7 +364,7 @@ function adjustStock(action, items, options={}) {
|
||||
inventreeDelete(
|
||||
`/api/stock/${item.pk}/`,
|
||||
)
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
// Wait for *all* the requests to complete
|
||||
@ -327,7 +396,7 @@ function adjustStock(action, items, options={}) {
|
||||
});
|
||||
|
||||
// Add in extra field data
|
||||
for (field_name in extraFields) {
|
||||
for (var field_name in extraFields) {
|
||||
data[field_name] = getFormFieldValue(
|
||||
field_name,
|
||||
fields[field_name],
|
||||
@ -342,7 +411,7 @@ function adjustStock(action, items, options={}) {
|
||||
data,
|
||||
{
|
||||
method: 'POST',
|
||||
success: function(response, status) {
|
||||
success: function() {
|
||||
|
||||
// Destroy the modal window
|
||||
$(modal).modal('hide');
|
||||
@ -353,22 +422,22 @@ function adjustStock(action, items, options={}) {
|
||||
},
|
||||
error: function(xhr) {
|
||||
switch (xhr.status) {
|
||||
case 400:
|
||||
case 400:
|
||||
|
||||
// Handle errors for standard fields
|
||||
handleFormErrors(
|
||||
xhr.responseJSON,
|
||||
extraFields,
|
||||
{
|
||||
modal: modal,
|
||||
}
|
||||
)
|
||||
// Handle errors for standard fields
|
||||
handleFormErrors(
|
||||
xhr.responseJSON,
|
||||
extraFields,
|
||||
{
|
||||
modal: modal,
|
||||
}
|
||||
);
|
||||
|
||||
break;
|
||||
default:
|
||||
$(modal).modal('hide');
|
||||
showApiError(xhr);
|
||||
break;
|
||||
break;
|
||||
default:
|
||||
$(modal).modal('hide');
|
||||
showApiError(xhr);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -446,15 +515,15 @@ function loadStockTestResultsTable(table, options) {
|
||||
html += makeIconButton('fa-trash-alt icon-red', 'button-test-delete', pk, '{% trans "Delete test result" %}');
|
||||
}
|
||||
|
||||
html += "</div>";
|
||||
html += '</div>';
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
var parent_node = "parent node";
|
||||
var parent_node = 'parent node';
|
||||
|
||||
table.inventreeTable({
|
||||
url: "{% url 'api-part-test-template-list' %}",
|
||||
url: '{% url "api-part-test-template-list" %}',
|
||||
method: 'get',
|
||||
name: 'testresult',
|
||||
treeEnable: true,
|
||||
@ -473,7 +542,7 @@ function loadStockTestResultsTable(table, options) {
|
||||
table.treegrid({
|
||||
treeColumn: 0,
|
||||
});
|
||||
table.treegrid("collapseAll");
|
||||
table.treegrid('collapseAll');
|
||||
},
|
||||
columns: [
|
||||
{
|
||||
@ -539,12 +608,12 @@ function loadStockTestResultsTable(table, options) {
|
||||
stock_item: options.stock_item,
|
||||
user_detail: true,
|
||||
attachment_detail: true,
|
||||
ordering: "-date",
|
||||
ordering: '-date',
|
||||
},
|
||||
{
|
||||
success: function(data) {
|
||||
// Iterate through the returned test data
|
||||
data.forEach(function(item, index) {
|
||||
data.forEach(function(item) {
|
||||
|
||||
var match = false;
|
||||
var override = false;
|
||||
@ -589,13 +658,12 @@ function loadStockTestResultsTable(table, options) {
|
||||
});
|
||||
|
||||
// Push data back into the table
|
||||
table.bootstrapTable("load", tableData);
|
||||
table.bootstrapTable('load', tableData);
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -671,11 +739,11 @@ function loadStockTable(table, options) {
|
||||
|
||||
var params = options.params || {};
|
||||
|
||||
var filterListElement = options.filterList || "#filter-list-stock";
|
||||
var filterListElement = options.filterList || '#filter-list-stock';
|
||||
|
||||
var filters = {};
|
||||
|
||||
var filterKey = options.filterKey || options.name || "stock";
|
||||
var filterKey = options.filterKey || options.name || 'stock';
|
||||
|
||||
if (!options.disableFilters) {
|
||||
filters = loadTableFilters(filterKey);
|
||||
@ -683,8 +751,8 @@ function loadStockTable(table, options) {
|
||||
|
||||
var original = {};
|
||||
|
||||
for (var key in params) {
|
||||
original[key] = params[key];
|
||||
for (var k in params) {
|
||||
original[k] = params[k];
|
||||
}
|
||||
|
||||
setupFilterList(filterKey, table, filterListElement);
|
||||
@ -700,10 +768,13 @@ function loadStockTable(table, options) {
|
||||
grouping = options.grouping;
|
||||
}
|
||||
|
||||
var col = null;
|
||||
|
||||
// Explicitly disable part grouping functionality
|
||||
// Might be able to add this in later on,
|
||||
// but there is a bug which makes this crash if paginating on the server side.
|
||||
// Ref: https://github.com/wenzhixin/bootstrap-table/issues/3250
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
grouping = false;
|
||||
|
||||
var columns = [
|
||||
@ -727,22 +798,24 @@ function loadStockTable(table, options) {
|
||||
sortName: 'part__name',
|
||||
visible: params['part_detail'],
|
||||
switchable: params['part_detail'],
|
||||
formatter: function(value, row, index, field) {
|
||||
formatter: function(value, row) {
|
||||
|
||||
var url = `/stock/item/${row.pk}/`;
|
||||
var thumb = row.part_detail.thumbnail;
|
||||
var name = row.part_detail.full_name;
|
||||
|
||||
html = imageHoverIcon(thumb) + renderLink(name, url);
|
||||
var html = imageHoverIcon(thumb) + renderLink(name, url);
|
||||
|
||||
html += makePartIcons(row.part_detail);
|
||||
|
||||
return html;
|
||||
}
|
||||
};
|
||||
|
||||
if (!options.params.ordering) {
|
||||
col['sortable'] = true;
|
||||
};
|
||||
}
|
||||
|
||||
columns.push(col);
|
||||
|
||||
col = {
|
||||
@ -751,13 +824,15 @@ function loadStockTable(table, options) {
|
||||
sortName: 'part__IPN',
|
||||
visible: params['part_detail'],
|
||||
switchable: params['part_detail'],
|
||||
formatter: function(value, row, index, field) {
|
||||
formatter: function(value, row) {
|
||||
return row.part_detail.IPN;
|
||||
},
|
||||
};
|
||||
|
||||
if (!options.params.ordering) {
|
||||
col['sortable'] = true;
|
||||
};
|
||||
}
|
||||
|
||||
columns.push(col);
|
||||
|
||||
columns.push({
|
||||
@ -765,7 +840,7 @@ function loadStockTable(table, options) {
|
||||
title: '{% trans "Description" %}',
|
||||
visible: params['part_detail'],
|
||||
switchable: params['part_detail'],
|
||||
formatter: function(value, row, index, field) {
|
||||
formatter: function(value, row) {
|
||||
return row.part_detail.description;
|
||||
}
|
||||
});
|
||||
@ -773,7 +848,7 @@ function loadStockTable(table, options) {
|
||||
col = {
|
||||
field: 'quantity',
|
||||
title: '{% trans "Stock" %}',
|
||||
formatter: function(value, row, index, field) {
|
||||
formatter: function(value, row) {
|
||||
|
||||
var val = parseFloat(value);
|
||||
|
||||
@ -817,13 +892,9 @@ function loadStockTable(table, options) {
|
||||
// REJECTED
|
||||
if (row.status == {{ StockStatus.REJECTED }}) {
|
||||
html += makeIconBadge('fa-times-circle icon-red', '{% trans "Stock item has been rejected" %}');
|
||||
}
|
||||
|
||||
// LOST
|
||||
else if (row.status == {{ StockStatus.LOST }}) {
|
||||
html += makeIconBadge('fa-question-circle','{% trans "Stock item is lost" %}');
|
||||
}
|
||||
else if (row.status == {{ StockStatus.DESTROYED }}) {
|
||||
} else if (row.status == {{ StockStatus.LOST }}) {
|
||||
html += makeIconBadge('fa-question-circle', '{% trans "Stock item is lost" %}');
|
||||
} else if (row.status == {{ StockStatus.DESTROYED }}) {
|
||||
html += makeIconBadge('fa-skull-crossbones', '{% trans "Stock item is destroyed" %}');
|
||||
}
|
||||
|
||||
@ -834,51 +905,61 @@ function loadStockTable(table, options) {
|
||||
return html;
|
||||
}
|
||||
};
|
||||
|
||||
if (!options.params.ordering) {
|
||||
col['sortable'] = true;
|
||||
};
|
||||
}
|
||||
|
||||
columns.push(col);
|
||||
|
||||
col = {
|
||||
field: 'status',
|
||||
title: '{% trans "Status" %}',
|
||||
formatter: function(value, row, index, field) {
|
||||
formatter: function(value) {
|
||||
return stockStatusDisplay(value);
|
||||
},
|
||||
};
|
||||
|
||||
if (!options.params.ordering) {
|
||||
col['sortable'] = true;
|
||||
};
|
||||
}
|
||||
|
||||
columns.push(col);
|
||||
|
||||
col = {
|
||||
field: 'batch',
|
||||
title: '{% trans "Batch" %}',
|
||||
};
|
||||
|
||||
if (!options.params.ordering) {
|
||||
col['sortable'] = true;
|
||||
};
|
||||
}
|
||||
|
||||
columns.push(col);
|
||||
|
||||
col = {
|
||||
field: 'location_detail.pathstring',
|
||||
title: '{% trans "Location" %}',
|
||||
formatter: function(value, row, index, field) {
|
||||
formatter: function(value, row) {
|
||||
return locationDetail(row);
|
||||
}
|
||||
};
|
||||
|
||||
if (!options.params.ordering) {
|
||||
col['sortable'] = true;
|
||||
};
|
||||
}
|
||||
|
||||
columns.push(col);
|
||||
|
||||
col = {
|
||||
field: 'stocktake_date',
|
||||
title: '{% trans "Stocktake" %}',
|
||||
};
|
||||
|
||||
if (!options.params.ordering) {
|
||||
col['sortable'] = true;
|
||||
};
|
||||
}
|
||||
|
||||
columns.push(col);
|
||||
|
||||
col = {
|
||||
@ -887,18 +968,22 @@ function loadStockTable(table, options) {
|
||||
visible: global_settings.STOCK_ENABLE_EXPIRY,
|
||||
switchable: global_settings.STOCK_ENABLE_EXPIRY,
|
||||
};
|
||||
|
||||
if (!options.params.ordering) {
|
||||
col['sortable'] = true;
|
||||
};
|
||||
}
|
||||
|
||||
columns.push(col);
|
||||
|
||||
col = {
|
||||
field: 'updated',
|
||||
title: '{% trans "Last Updated" %}',
|
||||
};
|
||||
|
||||
if (!options.params.ordering) {
|
||||
col['sortable'] = true;
|
||||
};
|
||||
}
|
||||
|
||||
columns.push(col);
|
||||
|
||||
columns.push({
|
||||
@ -921,8 +1006,10 @@ function loadStockTable(table, options) {
|
||||
|
||||
return renderLink(text, link);
|
||||
}
|
||||
},
|
||||
{
|
||||
});
|
||||
|
||||
col = {
|
||||
|
||||
field: 'supplier_part',
|
||||
title: '{% trans "Supplier Part" %}',
|
||||
visible: params['supplier_part_detail'] || false,
|
||||
@ -944,15 +1031,25 @@ function loadStockTable(table, options) {
|
||||
|
||||
return renderLink(text, link);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
if (!options.params.ordering) {
|
||||
col.sortable = true;
|
||||
col.sortName = 'SKU';
|
||||
}
|
||||
|
||||
columns.push(col);
|
||||
|
||||
col = {
|
||||
field: 'purchase_price_string',
|
||||
title: '{% trans "Purchase Price" %}',
|
||||
};
|
||||
|
||||
if (!options.params.ordering) {
|
||||
col['sortable'] = true;
|
||||
};
|
||||
col.sortable = true;
|
||||
col.sortName = 'purchase_price';
|
||||
}
|
||||
|
||||
columns.push(col);
|
||||
|
||||
columns.push({
|
||||
@ -969,7 +1066,7 @@ function loadStockTable(table, options) {
|
||||
formatNoMatches: function() {
|
||||
return '{% trans "No stock items matching query" %}';
|
||||
},
|
||||
url: options.url || "{% url 'api-stock-list' %}",
|
||||
url: options.url || '{% url "api-stock-list" %}',
|
||||
queryParams: filters,
|
||||
sidePagination: 'server',
|
||||
name: 'stock',
|
||||
@ -1036,7 +1133,7 @@ function loadStockTable(table, options) {
|
||||
|
||||
stock = +stock.toFixed(5);
|
||||
|
||||
return stock + " (" + items + " items)";
|
||||
return `${stock} (${items} {% trans "items" %})`;
|
||||
} else if (field == 'status') {
|
||||
var statii = [];
|
||||
|
||||
@ -1158,7 +1255,7 @@ function loadStockTable(table, options) {
|
||||
|
||||
|
||||
function stockAdjustment(action) {
|
||||
var items = $(table).bootstrapTable("getSelections");
|
||||
var items = $(table).bootstrapTable('getSelections');
|
||||
|
||||
adjustStock(action, items, {
|
||||
onSuccess: function() {
|
||||
@ -1191,7 +1288,7 @@ function loadStockTable(table, options) {
|
||||
});
|
||||
|
||||
printTestReports(items);
|
||||
})
|
||||
});
|
||||
|
||||
if (global_settings.BARCODE_ENABLE) {
|
||||
$('#multi-item-barcode-scan-into-location').click(function() {
|
||||
@ -1201,7 +1298,7 @@ function loadStockTable(table, options) {
|
||||
|
||||
selections.forEach(function(item) {
|
||||
items.push(item.pk);
|
||||
})
|
||||
});
|
||||
|
||||
scanItemsIntoLocation(items);
|
||||
});
|
||||
@ -1219,12 +1316,12 @@ function loadStockTable(table, options) {
|
||||
stockAdjustment('add');
|
||||
});
|
||||
|
||||
$("#multi-item-move").click(function() {
|
||||
$('#multi-item-move').click(function() {
|
||||
stockAdjustment('move');
|
||||
});
|
||||
|
||||
$("#multi-item-order").click(function() {
|
||||
var selections = $(table).bootstrapTable("getSelections");
|
||||
$('#multi-item-order').click(function() {
|
||||
var selections = $(table).bootstrapTable('getSelections');
|
||||
|
||||
var stock = [];
|
||||
|
||||
@ -1232,14 +1329,14 @@ function loadStockTable(table, options) {
|
||||
stock.push(item.pk);
|
||||
});
|
||||
|
||||
launchModalForm("/order/purchase-order/order-parts/", {
|
||||
launchModalForm('/order/purchase-order/order-parts/', {
|
||||
data: {
|
||||
stock: stock,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
$("#multi-item-set-status").click(function() {
|
||||
$('#multi-item-set-status').click(function() {
|
||||
// Select and set the STATUS field for selected stock items
|
||||
var selections = $(table).bootstrapTable('getSelections');
|
||||
|
||||
@ -1251,7 +1348,7 @@ function loadStockTable(table, options) {
|
||||
function(item) {
|
||||
return item.text;
|
||||
},
|
||||
function (item) {
|
||||
function(item) {
|
||||
return item.key;
|
||||
}
|
||||
);
|
||||
@ -1323,11 +1420,11 @@ function loadStockTable(table, options) {
|
||||
$.when.apply($, requests).done(function() {
|
||||
$(table).bootstrapTable('refresh');
|
||||
});
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
$("#multi-item-delete").click(function() {
|
||||
var selections = $(table).bootstrapTable("getSelections");
|
||||
$('#multi-item-delete').click(function() {
|
||||
var selections = $(table).bootstrapTable('getSelections');
|
||||
|
||||
var stock = [];
|
||||
|
||||
@ -1356,8 +1453,8 @@ function loadStockLocationTable(table, options) {
|
||||
|
||||
var original = {};
|
||||
|
||||
for (var key in params) {
|
||||
original[key] = params[key];
|
||||
for (var k in params) {
|
||||
original[k] = params[k];
|
||||
}
|
||||
|
||||
setupFilterList(filterKey, table, filterListElement);
|
||||
@ -1425,7 +1522,7 @@ function loadStockTrackingTable(table, options) {
|
||||
field: 'date',
|
||||
title: '{% trans "Date" %}',
|
||||
sortable: true,
|
||||
formatter: function(value, row, index, field) {
|
||||
formatter: function(value) {
|
||||
var m = moment(value);
|
||||
|
||||
if (m.isValid()) {
|
||||
@ -1441,11 +1538,11 @@ function loadStockTrackingTable(table, options) {
|
||||
cols.push({
|
||||
field: 'label',
|
||||
title: '{% trans "Description" %}',
|
||||
formatter: function(value, row, index, field) {
|
||||
var html = "<b>" + value + "</b>";
|
||||
formatter: function(value, row) {
|
||||
var html = '<b>' + value + '</b>';
|
||||
|
||||
if (row.notes) {
|
||||
html += "<br><i>" + row.notes + "</i>";
|
||||
html += '<br><i>' + row.notes + '</i>';
|
||||
}
|
||||
|
||||
return html;
|
||||
@ -1456,7 +1553,7 @@ function loadStockTrackingTable(table, options) {
|
||||
cols.push({
|
||||
field: 'deltas',
|
||||
title: '{% trans "Details" %}',
|
||||
formatter: function(details, row, index, field) {
|
||||
formatter: function(details, row) {
|
||||
var html = `<table class='table table-condensed' id='tracking-table-${row.pk}'>`;
|
||||
|
||||
if (!details) {
|
||||
@ -1591,14 +1688,11 @@ function loadStockTrackingTable(table, options) {
|
||||
cols.push({
|
||||
field: 'user',
|
||||
title: '{% trans "User" %}',
|
||||
formatter: function(value, row, index, field) {
|
||||
if (value)
|
||||
{
|
||||
formatter: function(value, row) {
|
||||
if (value) {
|
||||
// TODO - Format the user's first and last names
|
||||
return row.user_detail.username;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
return `<i>{% trans "No user information" %}</i>`;
|
||||
}
|
||||
}
|
||||
@ -1680,7 +1774,7 @@ function createNewStockItem(options) {
|
||||
reloadFieldOptions(
|
||||
'supplier_part',
|
||||
{
|
||||
url: "{% url 'api-supplier-part-list' %}",
|
||||
url: '{% url "api-supplier-part-list" %}',
|
||||
params: {
|
||||
part: value,
|
||||
pretty: true,
|
||||
@ -1711,7 +1805,7 @@ function createNewStockItem(options) {
|
||||
} else {
|
||||
var expiry = moment().add(response.default_expiry, 'days');
|
||||
|
||||
setFieldValue('expiry_date', expiry.format("YYYY-MM-DD"));
|
||||
setFieldValue('expiry_date', expiry.format('YYYY-MM-DD'));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1720,7 +1814,7 @@ function createNewStockItem(options) {
|
||||
},
|
||||
];
|
||||
|
||||
launchModalForm("{% url 'stock-item-create' %}", options);
|
||||
launchModalForm('{% url "stock-item-create" %}', options);
|
||||
}
|
||||
|
||||
|
||||
@ -1729,28 +1823,8 @@ function loadInstalledInTable(table, options) {
|
||||
* Display a table showing the stock items which are installed in this stock item.
|
||||
*/
|
||||
|
||||
function updateCallbacks() {
|
||||
// Setup callback functions when buttons are pressed
|
||||
table.find('.button-install').click(function() {
|
||||
var pk = $(this).attr('pk');
|
||||
|
||||
launchModalForm(
|
||||
`/stock/item/${options.stock_item}/install/`,
|
||||
{
|
||||
data: {
|
||||
part: pk,
|
||||
},
|
||||
success: function() {
|
||||
// Refresh entire table!
|
||||
table.bootstrapTable('refresh');
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
table.inventreeTable({
|
||||
url: "{% url 'api-stock-list' %}",
|
||||
url: '{% url "api-stock-list" %}',
|
||||
queryParams: {
|
||||
installed_in: options.stock_item,
|
||||
part_detail: true,
|
||||
@ -1790,7 +1864,7 @@ function loadInstalledInTable(table, options) {
|
||||
{
|
||||
field: 'status',
|
||||
title: '{% trans "Status" %}',
|
||||
formatter: function(value, row) {
|
||||
formatter: function(value) {
|
||||
return stockStatusDisplay(value);
|
||||
}
|
||||
},
|
||||
@ -1829,8 +1903,8 @@ function loadInstalledInTable(table, options) {
|
||||
table.bootstrapTable('refresh');
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -8,13 +8,26 @@
|
||||
{% include "status_codes.html" with label='purchaseOrder' options=PurchaseOrderStatus.list %}
|
||||
{% include "status_codes.html" with label='salesOrder' options=SalesOrderStatus.list %}
|
||||
|
||||
/* globals
|
||||
global_settings
|
||||
*/
|
||||
|
||||
/* exported
|
||||
buildStatusDisplay,
|
||||
getAvailableTableFilters,
|
||||
purchaseOrderStatusDisplay,
|
||||
salesOrderStatusDisplay,
|
||||
stockHistoryStatusDisplay,
|
||||
stockStatusDisplay,
|
||||
*/
|
||||
|
||||
|
||||
function getAvailableTableFilters(tableKey) {
|
||||
|
||||
tableKey = tableKey.toLowerCase();
|
||||
|
||||
// Filters for "variant" table
|
||||
if (tableKey == "variants") {
|
||||
if (tableKey == 'variants') {
|
||||
return {
|
||||
active: {
|
||||
type: 'bool',
|
||||
@ -36,11 +49,11 @@ function getAvailableTableFilters(tableKey) {
|
||||
}
|
||||
|
||||
// Filters for Bill of Materials table
|
||||
if (tableKey == "bom") {
|
||||
if (tableKey == 'bom') {
|
||||
return {
|
||||
sub_part_trackable: {
|
||||
type: 'bool',
|
||||
title: '{% trans "Trackable Part" %}'
|
||||
title: '{% trans "Trackable Part" %}',
|
||||
},
|
||||
sub_part_assembly: {
|
||||
type: 'bool',
|
||||
@ -57,7 +70,7 @@ function getAvailableTableFilters(tableKey) {
|
||||
allow_variants: {
|
||||
type: 'bool',
|
||||
title: '{% trans "Allow Variant Stock" %}',
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@ -72,29 +85,29 @@ function getAvailableTableFilters(tableKey) {
|
||||
}
|
||||
|
||||
// Filters for "stock location" table
|
||||
if (tableKey == "location") {
|
||||
if (tableKey == 'location') {
|
||||
return {
|
||||
cascade: {
|
||||
type: 'bool',
|
||||
title: '{% trans "Include sublocations" %}',
|
||||
description: '{% trans "Include locations" %}',
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// Filters for "part category" table
|
||||
if (tableKey == "category") {
|
||||
if (tableKey == 'category') {
|
||||
return {
|
||||
cascade: {
|
||||
type: 'bool',
|
||||
title: '{% trans "Include subcategories" %}',
|
||||
description: '{% trans "Include subcategories" %}',
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// Filters for the "customer stock" table (really a subset of "stock")
|
||||
if (tableKey == "customerstock") {
|
||||
if (tableKey == 'customerstock') {
|
||||
return {
|
||||
serialized: {
|
||||
type: 'bool',
|
||||
@ -102,7 +115,7 @@ function getAvailableTableFilters(tableKey) {
|
||||
},
|
||||
serial_gte: {
|
||||
title: '{% trans "Serial number GTE" %}',
|
||||
description: '{% trans "Serial number greater than or equal to" %}'
|
||||
description: '{% trans "Serial number greater than or equal to" %}',
|
||||
},
|
||||
serial_lte: {
|
||||
title: '{% trans "Serial number LTE" %}',
|
||||
@ -110,7 +123,7 @@ function getAvailableTableFilters(tableKey) {
|
||||
},
|
||||
serial: {
|
||||
title: '{% trans "Serial number" %}',
|
||||
description: '{% trans "Serial number" %}'
|
||||
description: '{% trans "Serial number" %}',
|
||||
},
|
||||
batch: {
|
||||
title: '{% trans "Batch" %}',
|
||||
@ -179,11 +192,11 @@ function getAvailableTableFilters(tableKey) {
|
||||
},
|
||||
serial: {
|
||||
title: '{% trans "Serial number" %}',
|
||||
description: '{% trans "Serial number" %}'
|
||||
description: '{% trans "Serial number" %}',
|
||||
},
|
||||
serial_gte: {
|
||||
title: '{% trans "Serial number GTE" %}',
|
||||
description: '{% trans "Serial number greater than or equal to" %}'
|
||||
description: '{% trans "Serial number greater than or equal to" %}',
|
||||
},
|
||||
serial_lte: {
|
||||
title: '{% trans "Serial number LTE" %}',
|
||||
@ -239,7 +252,7 @@ function getAvailableTableFilters(tableKey) {
|
||||
required: {
|
||||
type: 'bool',
|
||||
title: '{% trans "Required" %}',
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@ -262,7 +275,7 @@ function getAvailableTableFilters(tableKey) {
|
||||
}
|
||||
|
||||
// Filters for the "Order" table
|
||||
if (tableKey == "purchaseorder") {
|
||||
if (tableKey == 'purchaseorder') {
|
||||
|
||||
return {
|
||||
status: {
|
||||
@ -280,7 +293,7 @@ function getAvailableTableFilters(tableKey) {
|
||||
};
|
||||
}
|
||||
|
||||
if (tableKey == "salesorder") {
|
||||
if (tableKey == 'salesorder') {
|
||||
return {
|
||||
status: {
|
||||
title: '{% trans "Order status" %}',
|
||||
@ -302,12 +315,12 @@ function getAvailableTableFilters(tableKey) {
|
||||
active: {
|
||||
type: 'bool',
|
||||
title: '{% trans "Active parts" %}',
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// Filters for the "Parts" table
|
||||
if (tableKey == "parts") {
|
||||
if (tableKey == 'parts') {
|
||||
return {
|
||||
cascade: {
|
||||
type: 'bool',
|
||||
@ -330,7 +343,7 @@ function getAvailableTableFilters(tableKey) {
|
||||
},
|
||||
has_stock: {
|
||||
type: 'bool',
|
||||
title: '{% trans "Stock available" %}'
|
||||
title: '{% trans "Stock available" %}',
|
||||
},
|
||||
low_stock: {
|
||||
type: 'bool',
|
||||
|
@ -1,44 +1,33 @@
|
||||
{% load i18n %}
|
||||
|
||||
/* global
|
||||
inventreeLoad,
|
||||
inventreeSave,
|
||||
*/
|
||||
|
||||
function tdWrap(html, options={}) {
|
||||
/* Wraps provided html in <td>..</td> elements
|
||||
*/
|
||||
|
||||
var colspan = '';
|
||||
|
||||
if (options.colspan) {
|
||||
colspan = ` colspan=${options.colspan}`;
|
||||
}
|
||||
|
||||
return `<td${colspan}>${html}</td>`;
|
||||
}
|
||||
|
||||
|
||||
function trWrap(html) {
|
||||
/* Wraps provided html in <tr>..</tr> elements
|
||||
*/
|
||||
|
||||
return `<tr>${html}</tr>`;
|
||||
}
|
||||
|
||||
|
||||
/* exported
|
||||
customGroupSorter,
|
||||
reloadtable,
|
||||
renderLink,
|
||||
reloadTableFilters,
|
||||
*/
|
||||
|
||||
/**
|
||||
* Reload a named table
|
||||
* @param table
|
||||
*/
|
||||
function reloadtable(table) {
|
||||
$(table).bootstrapTable('refresh');
|
||||
}
|
||||
|
||||
|
||||
function editButton(url, text='Edit') {
|
||||
return "<button class='btn btn-success edit-button btn-sm' type='button' url='" + url + "'>" + text + "</button>";
|
||||
}
|
||||
|
||||
|
||||
function deleteButton(url, text='Delete') {
|
||||
return "<button class='btn btn-danger delete-button btn-sm' type='button' url='" + url + "'>" + text + "</button>";
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Render a URL for display
|
||||
* @param {String} text
|
||||
* @param {String} url
|
||||
* @param {object} options
|
||||
* @returns link text
|
||||
*/
|
||||
function renderLink(text, url, options={}) {
|
||||
if (url === null || url === undefined || url === '') {
|
||||
return text;
|
||||
@ -46,8 +35,6 @@ function renderLink(text, url, options={}) {
|
||||
|
||||
var max_length = options.max_length || -1;
|
||||
|
||||
var remove_http = options.remove_http || false;
|
||||
|
||||
// Shorten the displayed length if required
|
||||
if ((max_length > 0) && (text.length > max_length)) {
|
||||
var slice_length = (max_length - 3) / 2;
|
||||
@ -82,12 +69,17 @@ function linkButtonsToSelection(table, buttons) {
|
||||
enableButtons(buttons, table.bootstrapTable('getSelections').length > 0);
|
||||
|
||||
// Add a callback
|
||||
table.on('check.bs.table uncheck.bs.table check-some.bs.table uncheck-some.bs.table check-all.bs.table uncheck-all.bs.table', function(row) {
|
||||
table.on('check.bs.table uncheck.bs.table check-some.bs.table uncheck-some.bs.table check-all.bs.table uncheck-all.bs.table', function() {
|
||||
enableButtons(buttons, table.bootstrapTable('getSelections').length > 0);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns true if the input looks like a valid number
|
||||
* @param {String} n
|
||||
* @returns
|
||||
*/
|
||||
function isNumeric(n) {
|
||||
return !isNaN(parseFloat(n)) && isFinite(n);
|
||||
}
|
||||
@ -111,8 +103,8 @@ function reloadTableFilters(table, filters) {
|
||||
// Construct a new list of filters to use for the query
|
||||
var params = {};
|
||||
|
||||
for (var key in filters) {
|
||||
params[key] = filters[key];
|
||||
for (var k in filters) {
|
||||
params[k] = filters[k];
|
||||
}
|
||||
|
||||
// Original query params will override
|
||||
@ -159,7 +151,6 @@ function convertQueryParameters(params, filters) {
|
||||
var ordering = params['sort'] || null;
|
||||
|
||||
if (ordering) {
|
||||
|
||||
if (order == 'desc') {
|
||||
ordering = `-${ordering}`;
|
||||
}
|
||||
@ -243,7 +234,7 @@ $.fn.inventreeTable = function(options) {
|
||||
};
|
||||
|
||||
// Callback when a column is changed
|
||||
options.onColumnSwitch = function(field, checked) {
|
||||
options.onColumnSwitch = function() {
|
||||
|
||||
var columns = table.bootstrapTable('getVisibleColumns');
|
||||
|
||||
@ -262,7 +253,7 @@ $.fn.inventreeTable = function(options) {
|
||||
|
||||
// If a set of visible columns has been saved, load!
|
||||
if (visibleColumns) {
|
||||
var columns = visibleColumns.split(",");
|
||||
var columns = visibleColumns.split(',');
|
||||
|
||||
// Which columns are currently visible?
|
||||
var visible = table.bootstrapTable('getVisibleColumns');
|
||||
@ -276,7 +267,7 @@ $.fn.inventreeTable = function(options) {
|
||||
}
|
||||
});
|
||||
} else {
|
||||
console.log('Could not get list of visible columns!');
|
||||
console.log(`Could not get list of visible columns for column '${tableName}'`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -284,7 +275,8 @@ $.fn.inventreeTable = function(options) {
|
||||
if (options.buttons) {
|
||||
linkButtonsToSelection(table, options.buttons);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
function customGroupSorter(sortName, sortOrder, sortData) {
|
||||
|
||||
@ -357,42 +349,42 @@ function customGroupSorter(sortName, sortOrder, sortData) {
|
||||
}
|
||||
|
||||
// Expose default bootstrap table string literals to translation layer
|
||||
(function ($) {
|
||||
(function($) {
|
||||
'use strict';
|
||||
|
||||
$.fn.bootstrapTable.locales['en-US-custom'] = {
|
||||
formatLoadingMessage: function () {
|
||||
formatLoadingMessage: function() {
|
||||
return '{% trans "Loading data" %}';
|
||||
},
|
||||
formatRecordsPerPage: function (pageNumber) {
|
||||
formatRecordsPerPage: function(pageNumber) {
|
||||
return `${pageNumber} {% trans "rows per page" %}`;
|
||||
},
|
||||
formatShowingRows: function (pageFrom, pageTo, totalRows) {
|
||||
formatShowingRows: function(pageFrom, pageTo, totalRows) {
|
||||
return `{% trans "Showing" %} ${pageFrom} {% trans "to" %} ${pageTo} {% trans "of" %} ${totalRows} {% trans "rows" %}`;
|
||||
},
|
||||
formatSearch: function () {
|
||||
formatSearch: function() {
|
||||
return '{% trans "Search" %}';
|
||||
},
|
||||
formatNoMatches: function () {
|
||||
formatNoMatches: function() {
|
||||
return '{% trans "No matching results" %}';
|
||||
},
|
||||
formatPaginationSwitch: function () {
|
||||
formatPaginationSwitch: function() {
|
||||
return '{% trans "Hide/Show pagination" %}';
|
||||
},
|
||||
formatRefresh: function () {
|
||||
formatRefresh: function() {
|
||||
return '{% trans "Refresh" %}';
|
||||
},
|
||||
formatToggle: function () {
|
||||
formatToggle: function() {
|
||||
return '{% trans "Toggle" %}';
|
||||
},
|
||||
formatColumns: function () {
|
||||
formatColumns: function() {
|
||||
return '{% trans "Columns" %}';
|
||||
},
|
||||
formatAllRows: function () {
|
||||
formatAllRows: function() {
|
||||
return '{% trans "All" %}';
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
$.extend($.fn.bootstrapTable.defaults, $.fn.bootstrapTable.locales['en-US-custom']);
|
||||
|
||||
})(jQuery);
|
||||
})(jQuery);
|
||||
|
@ -14,7 +14,7 @@
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
<h3 id='modal-title'><i>Form Title Here</i></h3>
|
||||
<h3 id='modal-title'><em>Form Title Here</em></h3>
|
||||
</div>
|
||||
<div class='modal-form-content-wrapper'>
|
||||
<div class='alert alert-block alert-danger' id='form-validation-warning' style='display: none;'>
|
||||
@ -40,7 +40,7 @@
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
<h3 id='modal-title'><i>Form Title Here</i></h3>
|
||||
<h3 id='modal-title'><em>Form Title Here</em></h3>
|
||||
</div>
|
||||
<div class='modal-form-content-wrapper'>
|
||||
<div class='alert alert-block alert-danger' id='form-validation-warning' style="display: none;">
|
||||
|
@ -80,7 +80,7 @@
|
||||
<span class='fas fa-info-circle icon-green'></span>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
<span class="fas fa-user"></span> <b>{{ user.get_username }}</b></a>
|
||||
<span class="fas fa-user"></span> <strong>{{ user.get_username }}</strong></a>
|
||||
<ul class='dropdown-menu'>
|
||||
{% if user.is_authenticated %}
|
||||
{% if user.is_staff %}
|
||||
|
@ -80,7 +80,7 @@
|
||||
|
||||
{% if form.errors %}
|
||||
<div class='login-error'>
|
||||
<b>{% trans "Username / password combination is incorrect" %}</b>
|
||||
<strong>{% trans "Username / password combination is incorrect" %}</strong>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
|
@ -6,7 +6,7 @@
|
||||
<col width='25'>
|
||||
|
||||
<tr>
|
||||
<td colspan="3"><b>{% trans "Server" %}</b></td>
|
||||
<td colspan="3"><strong>{% trans "Server" %}</strong></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class='fas fa-server'></span></td>
|
||||
@ -77,7 +77,7 @@
|
||||
{% endif %}
|
||||
|
||||
<tr>
|
||||
<td colspan='3'><b>{% trans "Parts" %}</b></td>
|
||||
<td colspan='3'><strong>{% trans "Parts" %}</strong></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class='fas fa-sitemap'></span></td>
|
||||
@ -90,7 +90,7 @@
|
||||
<td>{{ part_count }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="3"><b>{% trans "Stock Items" %}</b></td>
|
||||
<td colspan="3"><strong>{% trans "Stock Items" %}</strong></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class='fas fa-map-marker-alt'></span></td>
|
||||
|
@ -1,12 +1,13 @@
|
||||
/*
|
||||
* Status codes for the {{ label }} model.
|
||||
*/
|
||||
var {{ label }}Codes = {
|
||||
const {{ label }}Codes = {
|
||||
{% for opt in options %}'{{ opt.key }}': {
|
||||
key: '{{ opt.key }}',
|
||||
value: '{{ opt.value }}',{% if opt.color %}
|
||||
label: 'label-{{ opt.color }}',{% endif %}
|
||||
},{% endfor %}
|
||||
},
|
||||
{% endfor %}
|
||||
};
|
||||
|
||||
/*
|
||||
|
@ -28,9 +28,9 @@ InvenTree is [available via Docker](https://hub.docker.com/r/inventree/inventree
|
||||
|
||||
InvenTree is supported by a [companion mobile app](https://inventree.readthedocs.io/en/latest/app/app/) which allows users access to stock control information and functionality.
|
||||
|
||||
[**Download InvenTree from the Android Play Store**](https://play.google.com/store/apps/details?id=inventree.inventree_app)
|
||||
- [**Download InvenTree from the Android Play Store**](https://play.google.com/store/apps/details?id=inventree.inventree_app)
|
||||
|
||||
*Currently the mobile app is only availble for Android*
|
||||
- [**Download InvenTree from the Apple App Store**](https://apps.apple.com/au/app/inventree/id1581731101#?platform=iphone)
|
||||
|
||||
# Translation
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user