diff --git a/.eslintrc.yml b/.eslintrc.yml new file mode 100644 index 0000000000..43363404b3 --- /dev/null +++ b/.eslintrc.yml @@ -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 diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000000..1a75b97af0 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -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" diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000000..ca9ff88a58 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -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). diff --git a/.github/workflows/html.yaml b/.github/workflows/html.yaml new file mode 100644 index 0000000000..069da7cbb4 --- /dev/null +++ b/.github/workflows/html.yaml @@ -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 + diff --git a/.github/workflows/javascript.yaml b/.github/workflows/javascript.yaml index 908a87e31c..a07b516ac6 100644 --- a/.github/workflows/javascript.yaml +++ b/.github/workflows/javascript.yaml @@ -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 - \ No newline at end of file + - name: Lint Javascript Files + run: | + npm install eslint eslint-config-google + invoke render-js-files + npx eslint js_tmp/*.js \ No newline at end of file diff --git a/.gitignore b/.gitignore index f3fa0ac8c1..420524d06f 100644 --- a/.gitignore +++ b/.gitignore @@ -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/ \ No newline at end of file diff --git a/InvenTree/InvenTree/ci_render_js.py b/InvenTree/InvenTree/ci_render_js.py new file mode 100644 index 0000000000..62e3fc4667 --- /dev/null +++ b/InvenTree/InvenTree/ci_render_js.py @@ -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.") diff --git a/InvenTree/InvenTree/filters.py b/InvenTree/InvenTree/filters.py new file mode 100644 index 0000000000..cd1b769646 --- /dev/null +++ b/InvenTree/InvenTree/filters.py @@ -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 diff --git a/InvenTree/InvenTree/serializers.py b/InvenTree/InvenTree/serializers.py index 4cea0a218c..0d21550f00 100644 --- a/InvenTree/InvenTree/serializers.py +++ b/InvenTree/InvenTree/serializers.py @@ -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 diff --git a/InvenTree/InvenTree/urls.py b/InvenTree/InvenTree/urls.py index 71f6388c68..7d51c6a4cf 100644 --- a/InvenTree/InvenTree/urls.py +++ b/InvenTree/InvenTree/urls.py @@ -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'), diff --git a/InvenTree/InvenTree/version.py b/InvenTree/InvenTree/version.py index 5e7453a80b..b53cacff79 100644 --- a/InvenTree/InvenTree/version.py +++ b/InvenTree/InvenTree/version.py @@ -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 diff --git a/InvenTree/build/templates/build/auto_allocate.html b/InvenTree/build/templates/build/auto_allocate.html index 48d1837ae0..2f2c7bbca7 100644 --- a/InvenTree/build/templates/build/auto_allocate.html +++ b/InvenTree/build/templates/build/auto_allocate.html @@ -6,7 +6,7 @@ {{ block.super }}
- {% trans "Automatically Allocate Stock" %}
+ {% trans "Automatically Allocate Stock" %}
{% trans "The following stock items will be allocated to the specified build output" %}
{% if allocations %} @@ -24,7 +24,7 @@ {{ item.stock_item.part.full_name }}
- {{ item.stock_item.part.description }} + {{ item.stock_item.part.description }} {% decimal item.quantity %} {{ item.stock_item.location }} diff --git a/InvenTree/build/templates/build/complete.html b/InvenTree/build/templates/build/complete.html index 527c0598d4..eeedc027dd 100644 --- a/InvenTree/build/templates/build/complete.html +++ b/InvenTree/build/templates/build/complete.html @@ -9,7 +9,7 @@ {% else %}
- {% trans "Build Order is incomplete" %}
+ {% trans "Build Order is incomplete" %}
diff --git a/InvenTree/build/templates/build/detail.html b/InvenTree/build/templates/build/detail.html index d6b59a060d..421cac059c 100644 --- a/InvenTree/build/templates/build/detail.html +++ b/InvenTree/build/templates/build/detail.html @@ -40,7 +40,7 @@ {% if build.take_from %} {{ build.take_from }}{% include "clip.html"%} {% else %} - {% trans "Stock can be taken from any available location." %} + {% trans "Stock can be taken from any available location." %} {% endif %} @@ -53,7 +53,7 @@ {{ build.destination }} {% include "clip.html"%} {% else %} - {% trans "Destination location not specified" %} + {% trans "Destination location not specified" %} {% endif %} @@ -127,7 +127,7 @@ {{ build.target_date }}{% if build.is_overdue %} {% endif %} {% else %} - {% trans "No target date set" %} + {% trans "No target date set" %} {% endif %} @@ -136,7 +136,7 @@ {% if build.completion_date %} {{ build.completion_date }}{% if build.completed_by %}{{ build.completed_by }}{% endif %} {% else %} - {% trans "Build not complete" %} + {% trans "Build not complete" %} {% endif %} @@ -222,7 +222,7 @@ {% else %}
- {% trans "Create a new build output" %}
+ {% trans "Create a new build output" %}
{% trans "No incomplete build outputs remain." %}
{% trans "Create a new build output using the button above" %}
diff --git a/InvenTree/common/templates/common/edit_setting.html b/InvenTree/common/templates/common/edit_setting.html index c74ed7d591..c479e268a5 100644 --- a/InvenTree/common/templates/common/edit_setting.html +++ b/InvenTree/common/templates/common/edit_setting.html @@ -6,9 +6,9 @@ {{ block.super }} {% endblock %} \ No newline at end of file diff --git a/InvenTree/company/templates/company/company_base.html b/InvenTree/company/templates/company/company_base.html index c50a9490f0..e4ca64b32e 100644 --- a/InvenTree/company/templates/company/company_base.html +++ b/InvenTree/company/templates/company/company_base.html @@ -78,7 +78,7 @@ {% if company.currency %} {{ company.currency }} {% else %} - {% trans "Uses default currency" %} + {% trans "Uses default currency" %} {% endif %} diff --git a/InvenTree/company/templates/company/manufacturer_part.html b/InvenTree/company/templates/company/manufacturer_part.html index cc2dd68840..13e81aaa90 100644 --- a/InvenTree/company/templates/company/manufacturer_part.html +++ b/InvenTree/company/templates/company/manufacturer_part.html @@ -225,7 +225,7 @@ $("#multi-parameter-delete").click(function() { + `; 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'); + } } - }); - }) + ); + }); } }); -} \ No newline at end of file +} diff --git a/InvenTree/templates/js/translated/filters.js b/InvenTree/templates/js/translated/filters.js index bc0dc1b958..d7e8f45ca5 100644 --- a/InvenTree/templates/js/translated/filters.js +++ b/InvenTree/templates/js/translated/filters.js @@ -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 = ``; for (var key in options) { - option = options[key]; + var option = options[key]; html += ``; } @@ -295,15 +306,11 @@ function setupFilterList(tableKey, table, target) { var html = ''; - //`
`; - html += generateAvailableFilterList(tableKey); html += generateFilterInput(tableKey); html += ``; - //html += '
'; - 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. */ diff --git a/InvenTree/templates/js/translated/forms.js b/InvenTree/templates/js/translated/forms.js index f61bc0e2e5..0e815f8c6d 100644 --- a/InvenTree/templates/js/translated/forms.js +++ b/InvenTree/templates/js/translated/forms.js @@ -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 = ` - + var error_html = ` + ${error_text} `; - 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) { `; } - html += ``; // input-group + html += ``; // input-group } if (parameters.help_text && !options.hideLabels) { @@ -1570,8 +1605,9 @@ function constructField(name, parameters, options) { // Div for error messages html += `
`; - html += ``; // controls - html += ``; // form-group + + html += ``; // controls + html += ``; // 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 = ``; @@ -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 = `
${parameters.help_text}
`; return html; -} \ No newline at end of file +} diff --git a/InvenTree/templates/js/translated/helpers.js b/InvenTree/templates/js/translated/helpers.js new file mode 100644 index 0000000000..020f098d19 --- /dev/null +++ b/InvenTree/templates/js/translated/helpers.js @@ -0,0 +1,207 @@ +{% load i18n %} + +/* exported + blankImage, + deleteButton, + editButton, + imageHoverIcon, + makeIconBadge, + makeIconButton, + makeProgressBar, + renderLink, + select2Thumbnail, + yesNoLabel, +*/ + +function yesNoLabel(value) { + if (value) { + return `{% trans "YES" %}`; + } else { + return `{% trans "NO" %}`; + } +} + + +function editButton(url, text='{% trans "Edit" %}') { + return ``; +} + + +function deleteButton(url, text='{% trans "Delete" %}') { + return ``; +} + + +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 = ` + + + + + `; + + return html; +} + + +/** + * Renders a simple thumbnail image + * @param {String} url is the image URL + * @returns html tag + */ +function thumbnailImage(url) { + + if (!url) { + url = '/static/img/blank_img.png'; + } + + // TODO: Support insertion of custom classes + + var html = ``; + + return html; + +} + + +// Render a select2 thumbnail image +function select2Thumbnail(image) { + if (!image) { + image = blankImage(); + } + + return ``; +} + + +function makeIconBadge(icon, title) { + // Construct an 'icon badge' which floats to the right of an object + + var html = ``; + + 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 += ``; + + 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 ` +
+
+
${text}
+
+ `; +} + + +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 `${text}`; +} diff --git a/InvenTree/templates/js/translated/label.js b/InvenTree/templates/js/translated/label.js index dc9e8fa935..1c843917e6 100644 --- a/InvenTree/templates/js/translated/label.js +++ b/InvenTree/templates/js/translated/label.js @@ -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 */ diff --git a/InvenTree/templates/js/translated/modals.js b/InvenTree/templates/js/translated/modals.js index 4bcc31fffa..5195c67b46 100644 --- a/InvenTree/templates/js/translated/modals.js +++ b/InvenTree/templates/js/translated/modals.js @@ -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 " {% trans 'Waiting for server...' %}"; + return ` {% 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 = ""; + var html = ``; - html += "
" + options.label + "
"; + html += ` id='btn-new-${options.field}'>${options.label}`; - html += "
"; + html += '
'; $(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 = ""; - html += ""; - html += ""; + var html = ` + + + `; $(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(); }); -} \ No newline at end of file +} diff --git a/InvenTree/templates/js/translated/model_renderers.js b/InvenTree/templates/js/translated/model_renderers.js index 12e00cab2c..389d5a650f 100644 --- a/InvenTree/templates/js/translated/model_renderers.js +++ b/InvenTree/templates/js/translated/model_renderers.js @@ -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 ``; -} +/* 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 = `${data.username}`; @@ -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 = `${data.name}`; switch (data.label) { - case 'user': - html += ``; - break; - case 'group': - html += ``; - break; - default: - break; + case 'user': + html += ``; + break; + case 'group': + html += ``; + 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 = `${data.name} - [${data.units}]`; @@ -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; diff --git a/InvenTree/templates/js/translated/order.js b/InvenTree/templates/js/translated/order.js index c6dd773373..7ebe000f64 100644 --- a/InvenTree/templates/js/translated/order.js +++ b/InvenTree/templates/js/translated/order.js @@ -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={}) { } ] }); -} \ No newline at end of file +} diff --git a/InvenTree/templates/js/translated/part.js b/InvenTree/templates/js/translated/part.js index 052a33ec5c..1ccf8157b6 100644 --- a/InvenTree/templates/js/translated/part.js +++ b/InvenTree/templates/js/translated/part.js @@ -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 `{% trans "YES" %}`; - } else { - return `{% trans "NO" %}`; - } -} - - -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 = '' + value + ''; + value = `${value}`; } 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 += "{% trans "Low stock" %}"; + value += `{% trans "Low stock" %}`; } } else if (row.on_order) { // There is no stock available, but stock is on order - value = "0{% trans "On Order" %}: " + row.on_order + ""; - link = "orders"; + value = `0{% trans "On Order" %}: ${row.on_order}`; + link = 'orders'; } else if (row.building) { // There is no stock available, but stock is being built - value = "0{% trans "Building" %}: " + row.building + ""; - link = "builds"; + value = `0{% trans "Building" %}: ${row.building}`; + link = 'builds'; } else { // There is no stock available - value = "0{% trans "No Stock" %}"; + value = `0{% trans "No Stock" %}`; } - 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 += `
` + html += `
`; 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" %}', diff --git a/InvenTree/templates/js/translated/report.js b/InvenTree/templates/js/translated/report.js index 370978be04..4f887f2275 100644 --- a/InvenTree/templates/js/translated/report.js +++ b/InvenTree/templates/js/translated/report.js @@ -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; } } - ) + ); } } - ) + ); } diff --git a/InvenTree/templates/js/translated/stock.js b/InvenTree/templates/js/translated/stock.js index 585cac1310..e35831624e 100644 --- a/InvenTree/templates/js/translated/stock.js +++ b/InvenTree/templates/js/translated/stock.js @@ -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 = `
`; @@ -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 += "
"; + html += '
'; 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 = "" + value + ""; + formatter: function(value, row) { + var html = '' + value + ''; if (row.notes) { - html += "
" + row.notes + ""; + html += '
' + row.notes + ''; } 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 = ``; 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 `{% trans "No user information" %}`; } } @@ -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'); } } - ) + ); }); } }); -} \ No newline at end of file +} diff --git a/InvenTree/templates/js/translated/table_filters.js b/InvenTree/templates/js/translated/table_filters.js index 9e173a7b37..b94bc324c7 100644 --- a/InvenTree/templates/js/translated/table_filters.js +++ b/InvenTree/templates/js/translated/table_filters.js @@ -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', diff --git a/InvenTree/templates/js/translated/tables.js b/InvenTree/templates/js/translated/tables.js index b9a05689d0..6e719563ae 100644 --- a/InvenTree/templates/js/translated/tables.js +++ b/InvenTree/templates/js/translated/tables.js @@ -1,44 +1,33 @@ {% load i18n %} +/* global + inventreeLoad, + inventreeSave, +*/ -function tdWrap(html, options={}) { - /* Wraps provided html in elements - */ - - var colspan = ''; - - if (options.colspan) { - colspan = ` colspan=${options.colspan}`; - } - - return `${html}`; -} - - -function trWrap(html) { - /* Wraps provided html in .. elements - */ - - return `${html}`; -} - - +/* exported + customGroupSorter, + reloadtable, + renderLink, + reloadTableFilters, +*/ +/** + * Reload a named table + * @param table + */ function reloadtable(table) { $(table).bootstrapTable('refresh'); } -function editButton(url, text='Edit') { - return ""; -} - - -function deleteButton(url, text='Delete') { - return ""; -} - - +/** + * 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); \ No newline at end of file +})(jQuery); diff --git a/InvenTree/templates/modals.html b/InvenTree/templates/modals.html index 11ddc40938..ac26a5fa9a 100644 --- a/InvenTree/templates/modals.html +++ b/InvenTree/templates/modals.html @@ -14,7 +14,7 @@ - + - + @@ -77,7 +77,7 @@ {% endif %} - + @@ -90,7 +90,7 @@ - + diff --git a/InvenTree/templates/status_codes.html b/InvenTree/templates/status_codes.html index e7bc2e951c..453a7914e2 100644 --- a/InvenTree/templates/status_codes.html +++ b/InvenTree/templates/status_codes.html @@ -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 %} }; /* diff --git a/README.md b/README.md index 7ace518175..e0b21f7e61 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/tasks.py b/tasks.py index 3de0241c07..1abbf23bc6 100644 --- a/tasks.py +++ b/tasks.py @@ -457,3 +457,12 @@ def server(c, address="127.0.0.1:8000"): """ manage(c, "runserver {address}".format(address=address), pty=True) + + +@task +def render_js_files(c): + """ + Render templated javascript files (used for static testing). + """ + + manage(c, "test InvenTree.ci_render_js")
..
{% trans "Server" %}{% trans "Server" %}
{% trans "Parts" %}{% trans "Parts" %}
{{ part_count }}
{% trans "Stock Items" %}{% trans "Stock Items" %}