From 1f997d07b68fa862b38cbbe89f8623f60e55eed6 Mon Sep 17 00:00:00 2001 From: Oliver Date: Mon, 15 Nov 2021 22:16:13 +1100 Subject: [PATCH 01/14] Update BOM API to allow filtering by "uses" --- InvenTree/InvenTree/version.py | 7 +++- InvenTree/part/api.py | 60 +++++++++++++++++++++++++++------- 2 files changed, 54 insertions(+), 13 deletions(-) diff --git a/InvenTree/InvenTree/version.py b/InvenTree/InvenTree/version.py index 935a0bed37..ac6e268f78 100644 --- a/InvenTree/InvenTree/version.py +++ b/InvenTree/InvenTree/version.py @@ -12,11 +12,16 @@ import common.models INVENTREE_SW_VERSION = "0.6.0 dev" # InvenTree API version -INVENTREE_API_VERSION = 17 +INVENTREE_API_VERSION = 18 """ Increment this API version number whenever there is a significant change to the API that any clients need to know about +v18 -> 2021-11-15 + - Adds the ability to filter BomItem API by "uses" field + - This returns a list of all BomItems which "use" the specified part + - Includes inherited BomItem objects + v17 -> 2021-11-09 - Adds API endpoints for GLOBAL and USER settings objects - Ref: https://github.com/inventree/InvenTree/pull/2275 diff --git a/InvenTree/part/api.py b/InvenTree/part/api.py index b08834445c..052adfbec3 100644 --- a/InvenTree/part/api.py +++ b/InvenTree/part/api.py @@ -832,18 +832,6 @@ class PartList(generics.ListCreateAPIView): queryset = super().filter_queryset(queryset) - # Filter by "uses" query - Limit to parts which use the provided part - uses = params.get('uses', None) - - if uses: - try: - uses = Part.objects.get(pk=uses) - - queryset = queryset.filter(uses.get_used_in_filter()) - - except (ValueError, Part.DoesNotExist): - pass - # Exclude specific part ID values? exclude_id = [] @@ -1211,6 +1199,54 @@ class BomList(generics.ListCreateAPIView): except (ValueError, Part.DoesNotExist): pass + """ + Filter by 'uses'? + + Here we pass a part ID and return BOM items for any assemblies which "use" (or "require") that part. + + There are multiple ways that an assembly can "use" a sub-part: + + A) Directly specifying the sub_part in a BomItem field + B) Specifing a "template" part with inherited=True + C) Allowing variant parts to be substituted + D) Allowing direct substitute parts to be specified + + - BOM items which are "inherited" by parts which are variants of the master BomItem + """ + uses = params.get('uses', None) + + if uses is not None: + + try: + # Extract the part we are interested in + uses_part = Part.objects.get(pk=uses) + + # Construct the database query in multiple parts + + # A) Direct specification of sub_part + q_A = Q(sub_part=uses_part) + + # B) BomItem is inherited and points to a "parent" of this part + parents = uses_part.get_ancestors(include_self=False) + + q_B = Q( + inherited=True, + sub_part__in=parents + ) + + # C) Substitution of variant parts + # TODO + + # D) Specification of individual substitutes + # TODO + + q = q_A | q_B + + queryset = queryset.filter(q) + + except (ValueError, Part.DoesNotExist): + pass + if self.include_pricing(): queryset = self.annotate_pricing(queryset) From a9852355c454212dddbceb5ade43a8c384c65b79 Mon Sep 17 00:00:00 2001 From: Oliver Date: Mon, 15 Nov 2021 22:28:09 +1100 Subject: [PATCH 02/14] Add a unit test --- InvenTree/part/test_api.py | 53 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/InvenTree/part/test_api.py b/InvenTree/part/test_api.py index ec377bd513..53106b9848 100644 --- a/InvenTree/part/test_api.py +++ b/InvenTree/part/test_api.py @@ -1123,6 +1123,59 @@ class BomItemTest(InvenTreeAPITestCase): response = self.get(url, expected_code=200) self.assertEqual(len(response.data), 5) + def test_bom_item_uses(self): + """ + Tests for the 'uses' field + """ + + url = reverse('api-bom-list') + + # Test that the direct 'sub_part' association works + + assemblies = [] + + for i in range(5): + assy = Part.objects.create( + name=f"Assy_{i}", + description="An assembly made of other parts", + active=True, + assembly=True + ) + + assemblies.append(assy) + + components = [] + + # Create some sub-components + for i in range(5): + + cmp = Part.objects.create( + name=f"Component_{i}", + description="A sub component", + active=True, + component=True + ) + + for j in range(i): + # Create a BOM item + BomItem.objects.create( + quantity=10, + part=assemblies[j], + sub_part=cmp, + ) + + components.append(cmp) + + response = self.get( + url, + { + 'uses': cmp.pk, + }, + expected_code=200, + ) + + self.assertEqual(len(response.data), i) + class PartParameterTest(InvenTreeAPITestCase): """ From 00dc7dafe15652ff930b4f8850bf2578d2592807 Mon Sep 17 00:00:00 2001 From: Oliver Date: Mon, 15 Nov 2021 22:39:58 +1100 Subject: [PATCH 03/14] Construct "used in" table --- InvenTree/part/templates/part/detail.html | 12 +-- InvenTree/templates/js/translated/bom.js | 97 ++++++++++++++++++++++- 2 files changed, 99 insertions(+), 10 deletions(-) diff --git a/InvenTree/part/templates/part/detail.html b/InvenTree/part/templates/part/detail.html index 39ed011861..a306bc2767 100644 --- a/InvenTree/part/templates/part/detail.html +++ b/InvenTree/part/templates/part/detail.html @@ -649,14 +649,10 @@ // Load the "used in" tab onPanelLoad("used-in", function() { - loadPartTable('#used-table', - '{% url "api-part-list" %}', - { - params: { - uses: {{ part.pk }}, - }, - filterTarget: '#filter-list-usedin', - } + + loadUsedInTable( + '#used-table', + {{ part.pk }}, ); }); diff --git a/InvenTree/templates/js/translated/bom.js b/InvenTree/templates/js/translated/bom.js index ee04cb8660..de0d92fac8 100644 --- a/InvenTree/templates/js/translated/bom.js +++ b/InvenTree/templates/js/translated/bom.js @@ -16,6 +16,7 @@ /* exported newPartFromBomWizard, loadBomTable, + loadUsedInTable, removeRowFromBomWizard, removeColFromBomWizard, */ @@ -311,7 +312,7 @@ function bomSubstitutesDialog(bom_item_id, substitutes, options={}) { } -function loadBomTable(table, options) { +function loadBomTable(table, options={}) { /* Load a BOM table with some configurable options. * * Following options are available: @@ -395,7 +396,7 @@ function loadBomTable(table, options) { var sub_part = row.sub_part_detail; - html += makePartIcons(row.sub_part_detail); + html += makePartIcons(sub_part); if (row.substitutes && row.substitutes.length > 0) { html += makeIconBadge('fa-exchange-alt', '{% trans "Substitutes Available" %}'); @@ -835,3 +836,95 @@ function loadBomTable(table, options) { }); } } + + +/* + * Load a table which shows the assemblies which "require" a certain part. + * + * Arguments: + * - table: The ID string of the table element e.g. '#used-in-table' + * - part_id: The ID (PK) of the part we are interested in + * + * Options: + * - + * + * The following "options" are available. + */ +function loadUsedInTable(table, part_id, options={}) { + + var params = options.params || {}; + + params.uses = part_id; + params.part_detail = true; + params.sub_part_detail = true, + params.show_pricing = global_settings.PART_SHOW_PRICE_IN_BOM; + + var filters = {}; + + if (!options.disableFilters) { + filters = loadTableFilters('usedin'); + } + + for (var key in params) { + filters[key] = params[key]; + } + + setupFilterList('usedin', $(table), options.filterTarget || '#filter-list-usedin'); + + $(table).inventreeTable({ + url: options.url || '{% url "api-bom-list" %}', + name: options.table_name || 'usedin', + sortable: true, + search: true, + showColumns: true, + queryParams: filters, + original: params, + columns: [ + { + field: 'pk', + title: 'ID', + visible: false, + switchable: false, + }, + { + field: 'part', + title: '{% trans "Part" %}', + switchable: false, + sortable: true, + formatter: function(value, row) { + var url = `/part/${value}/`; + var html = ''; + + var part = row.part_detail; + + html += imageHoverIcon(part.thumbnail); + html += renderLink(part.full_name, url); + html += makePartIcons(part); + + return html; + } + }, + { + field: 'sub_part', + title: '{% trans "Sub Part" %}', + sortable: true, + formatter: function(value, row) { + var url = `/part/${value}/`; + var html = ''; + + var sub_part = row.sub_part_detail; + + html += imageHoverIcon(sub_part.thumbnail); + html += renderLink(sub_part.full_name, url); + html += makePartIcons(sub_part); + + return html; + } + }, + { + field: 'quantity', + title: '{% trans "Quantity" %}', + } + ] + }); +} \ No newline at end of file From 0b487c611153dafa5bc7c4248fb0ee0c88a92095 Mon Sep 17 00:00:00 2001 From: Oliver Date: Mon, 15 Nov 2021 23:00:05 +1100 Subject: [PATCH 04/14] "used in" table now accommodates "inherited" BOMs --- InvenTree/templates/js/translated/api.js | 6 ++- InvenTree/templates/js/translated/bom.js | 64 ++++++++++++++++++++++-- 2 files changed, 65 insertions(+), 5 deletions(-) diff --git a/InvenTree/templates/js/translated/api.js b/InvenTree/templates/js/translated/api.js index 15a74a9a71..735ce0a676 100644 --- a/InvenTree/templates/js/translated/api.js +++ b/InvenTree/templates/js/translated/api.js @@ -217,8 +217,10 @@ function showApiError(xhr, url) { break; } - message += '
'; - message += `URL: ${url}`; + if (url) { + message += '
'; + message += `URL: ${url}`; + } showMessage(title, { style: 'danger', diff --git a/InvenTree/templates/js/translated/bom.js b/InvenTree/templates/js/translated/bom.js index de0d92fac8..48255e80a2 100644 --- a/InvenTree/templates/js/translated/bom.js +++ b/InvenTree/templates/js/translated/bom.js @@ -673,8 +673,9 @@ function loadBomTable(table, options={}) { table.treegrid('collapseAll'); }, - error: function() { + error: function(xhr) { console.log('Error requesting BOM for part=' + part_pk); + showApiError(xhr); } } ); @@ -879,6 +880,63 @@ function loadUsedInTable(table, part_id, options={}) { showColumns: true, queryParams: filters, original: params, + rootParentId: 'top-level-item', + idField: 'pk', + uniqueId: 'pk', + parentIdField: 'parent', + treeShowField: 'part', + onLoadSuccess: function(tableData) { + // Once the initial data are loaded, check if there are any "inherited" BOM lines + for (var ii = 0; ii < tableData.length; ii++) { + var row = tableData[ii]; + + // This is a "top level" item in the table + row.parent = 'top-level-item'; + + // Ignore this row as it is not "inherited" by variant parts + if (!row.inherited) { + continue; + } + + var variants = inventreeGet( + '{% url "api-part-list" %}', + { + assembly: true, + ancestor: row.part, + }, + { + success: function(variantData) { + // Iterate through each variant item + for (var jj = 0; jj < variantData.length; jj++) { + variantData[jj].parent = row.pk; + + var variant = variantData[jj]; + + // Add this variant to the table, augmented + $(table).bootstrapTable('append', [{ + // Point the parent to the "master" assembly row + parent: row.pk, + part: variant.pk, + part_detail: variant, + sub_part: row.sub_part, + sub_part_detail: row.sub_part_detail, + quantity: row.quantity, + }]); + } + }, + error: function(xhr) { + showApiError(xhr); + } + } + ); + + } + }, + onPostBody: function() { + $(table).treegrid({ + treeColumn: 0, + }); + }, columns: [ { field: 'pk', @@ -892,7 +950,7 @@ function loadUsedInTable(table, part_id, options={}) { switchable: false, sortable: true, formatter: function(value, row) { - var url = `/part/${value}/`; + var url = `/part/${value}/?display=bom`; var html = ''; var part = row.part_detail; @@ -923,7 +981,7 @@ function loadUsedInTable(table, part_id, options={}) { }, { field: 'quantity', - title: '{% trans "Quantity" %}', + title: '{% trans "Required Quantity" %}', } ] }); From d5ebdd035e7e1d8981300db929044d423b50d2a1 Mon Sep 17 00:00:00 2001 From: Oliver Date: Mon, 15 Nov 2021 23:06:15 +1100 Subject: [PATCH 05/14] Search button fix --- InvenTree/templates/search_form.html | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/InvenTree/templates/search_form.html b/InvenTree/templates/search_form.html index f3928888b5..f77d1ccf7b 100644 --- a/InvenTree/templates/search_form.html +++ b/InvenTree/templates/search_form.html @@ -2,8 +2,10 @@
{% csrf_token %} - - +
+ + +
From f3782ae8bc94780c466f3713bb33c0538ff252c4 Mon Sep 17 00:00:00 2001 From: Oliver Date: Mon, 15 Nov 2021 23:13:58 +1100 Subject: [PATCH 06/14] visual improvements for table filter elements --- InvenTree/templates/js/translated/filters.js | 34 ++++++++++++-------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/InvenTree/templates/js/translated/filters.js b/InvenTree/templates/js/translated/filters.js index 4383f0a096..227fbb8009 100644 --- a/InvenTree/templates/js/translated/filters.js +++ b/InvenTree/templates/js/translated/filters.js @@ -281,23 +281,24 @@ function setupFilterList(tableKey, table, target) { // One blank slate, please element.empty(); - element.append(``); + var buttons = ''; - // Callback for reloading the table - element.find(`#reload-${tableKey}`).click(function() { - $(table).bootstrapTable('refresh'); - }); + buttons += ``; - // If there are no filters defined for this table, exit now - if (jQuery.isEmptyObject(getAvailableTableFilters(tableKey))) { - return; + // If there are filters defined for this table, add more buttons + if (!jQuery.isEmptyObject(getAvailableTableFilters(tableKey))) { + buttons += ``; + + if (Object.keys(filters).length > 0) { + buttons += ``; + } } - element.append(``); - - if (Object.keys(filters).length > 0) { - element.append(``); - } + element.html(` +
+ ${buttons} +
+ `); for (var key in filters) { var value = getFilterOptionValue(tableKey, key, filters[key]); @@ -307,6 +308,11 @@ function setupFilterList(tableKey, table, target) { element.append(`
${title} = ${value}x
`); } + // Callback for reloading the table + element.find(`#reload-${tableKey}`).click(function() { + $(table).bootstrapTable('refresh'); + }); + // Add a callback for adding a new filter element.find(`#${add}`).click(function clicked() { @@ -316,10 +322,12 @@ function setupFilterList(tableKey, table, target) { var html = ''; + html += `
`; html += generateAvailableFilterList(tableKey); html += generateFilterInput(tableKey); html += ``; + html += `
`; element.append(html); From 66032ea77af26b8dd70a6172d991615116e07979 Mon Sep 17 00:00:00 2001 From: Oliver Date: Mon, 15 Nov 2021 23:14:13 +1100 Subject: [PATCH 07/14] Indicate that a BOM item is inherited in the "uses" table --- InvenTree/templates/js/translated/bom.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/InvenTree/templates/js/translated/bom.js b/InvenTree/templates/js/translated/bom.js index 48255e80a2..a4a2007360 100644 --- a/InvenTree/templates/js/translated/bom.js +++ b/InvenTree/templates/js/translated/bom.js @@ -982,6 +982,15 @@ function loadUsedInTable(table, part_id, options={}) { { field: 'quantity', title: '{% trans "Required Quantity" %}', + formatter: function(value, row) { + var html = value; + + if (row.parent != 'top-level-item') { + html += ` ({% trans "Inherited from parent BOM" %})`; + } + + return html; + } } ] }); From 01da889c46b2a8bbf494a4db1b192bbf2f64aef1 Mon Sep 17 00:00:00 2001 From: Oliver Date: Mon, 15 Nov 2021 23:31:56 +1100 Subject: [PATCH 08/14] Add ability to search "partparametertemplate" API by "name" field --- InvenTree/part/api.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/InvenTree/part/api.py b/InvenTree/part/api.py index 052adfbec3..eeb8ec1255 100644 --- a/InvenTree/part/api.py +++ b/InvenTree/part/api.py @@ -1028,13 +1028,19 @@ class PartParameterTemplateList(generics.ListCreateAPIView): serializer_class = part_serializers.PartParameterTemplateSerializer filter_backends = [ + DjangoFilterBackend, filters.OrderingFilter, + filters.SearchFilter, ] filter_fields = [ 'name', ] + search_fields = [ + 'name', + ] + class PartParameterList(generics.ListCreateAPIView): """ API endpoint for accessing a list of PartParameter objects From c367fd794177bc5d19a2a96a6529d176bbf3e651 Mon Sep 17 00:00:00 2001 From: Oliver Date: Mon, 15 Nov 2021 23:51:02 +1100 Subject: [PATCH 09/14] Fixes multiple issues for "used in" table --- InvenTree/templates/js/translated/bom.js | 71 +++++++++++++----------- 1 file changed, 38 insertions(+), 33 deletions(-) diff --git a/InvenTree/templates/js/translated/bom.js b/InvenTree/templates/js/translated/bom.js index a4a2007360..2204d0661f 100644 --- a/InvenTree/templates/js/translated/bom.js +++ b/InvenTree/templates/js/translated/bom.js @@ -872,6 +872,42 @@ function loadUsedInTable(table, part_id, options={}) { setupFilterList('usedin', $(table), options.filterTarget || '#filter-list-usedin'); + function loadVariantData(row) { + // Load variants information for inherited BOM rows + + inventreeGet( + '{% url "api-part-list" %}', + { + assembly: true, + ancestor: row.part, + }, + { + success: function(variantData) { + // Iterate through each variant item + for (var jj = 0; jj < variantData.length; jj++) { + variantData[jj].parent = row.pk; + + var variant = variantData[jj]; + + // Add this variant to the table, augmented + $(table).bootstrapTable('append', [{ + // Point the parent to the "master" assembly row + parent: row.pk, + part: variant.pk, + part_detail: variant, + sub_part: row.sub_part, + sub_part_detail: row.sub_part_detail, + quantity: row.quantity, + }]); + } + }, + error: function(xhr) { + showApiError(xhr); + } + } + ); + } + $(table).inventreeTable({ url: options.url || '{% url "api-bom-list" %}', name: options.table_name || 'usedin', @@ -898,38 +934,7 @@ function loadUsedInTable(table, part_id, options={}) { continue; } - var variants = inventreeGet( - '{% url "api-part-list" %}', - { - assembly: true, - ancestor: row.part, - }, - { - success: function(variantData) { - // Iterate through each variant item - for (var jj = 0; jj < variantData.length; jj++) { - variantData[jj].parent = row.pk; - - var variant = variantData[jj]; - - // Add this variant to the table, augmented - $(table).bootstrapTable('append', [{ - // Point the parent to the "master" assembly row - parent: row.pk, - part: variant.pk, - part_detail: variant, - sub_part: row.sub_part, - sub_part_detail: row.sub_part_detail, - quantity: row.quantity, - }]); - } - }, - error: function(xhr) { - showApiError(xhr); - } - } - ); - + loadVariantData(row); } }, onPostBody: function() { @@ -985,7 +990,7 @@ function loadUsedInTable(table, part_id, options={}) { formatter: function(value, row) { var html = value; - if (row.parent != 'top-level-item') { + if (row.parent && row.parent != 'top-level-item') { html += ` ({% trans "Inherited from parent BOM" %})`; } From ca3c3685fe7cd669393ade2e979d38589433bffd Mon Sep 17 00:00:00 2001 From: Oliver Date: Mon, 15 Nov 2021 23:51:21 +1100 Subject: [PATCH 10/14] Tweaks --- InvenTree/part/templates/part/detail.html | 2 -- InvenTree/templates/js/translated/forms.js | 4 ++-- InvenTree/templates/js/translated/part.js | 1 + 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/InvenTree/part/templates/part/detail.html b/InvenTree/part/templates/part/detail.html index a306bc2767..0d05665f7d 100644 --- a/InvenTree/part/templates/part/detail.html +++ b/InvenTree/part/templates/part/detail.html @@ -388,9 +388,7 @@ {% if part.variant_of %}
  • {% trans "Copy BOM" %}
  • {% endif %} - {% if not part.is_bom_valid %}
  • {% trans "Validate BOM" %}
  • - {% endif %} diff --git a/InvenTree/templates/js/translated/forms.js b/InvenTree/templates/js/translated/forms.js index 2f25fef259..fd1668cc77 100644 --- a/InvenTree/templates/js/translated/forms.js +++ b/InvenTree/templates/js/translated/forms.js @@ -924,8 +924,8 @@ function handleFormSuccess(response, options) { var cache = (options.follow && response.url) || options.redirect || options.reload; // Display any messages - if (response && response.success) { - showAlertOrCache(response.success, cache, {style: 'success'}); + if (response && (response.success || options.successMessage)) { + showAlertOrCache(response.success || options.successMessage, cache, {style: 'success'}); } if (response && response.info) { diff --git a/InvenTree/templates/js/translated/part.js b/InvenTree/templates/js/translated/part.js index dc1adf8837..89e09a314e 100644 --- a/InvenTree/templates/js/translated/part.js +++ b/InvenTree/templates/js/translated/part.js @@ -331,6 +331,7 @@ function editPart(pk) { groups: groups, title: '{% trans "Edit Part" %}', reload: true, + successMessage: '{% trans "Part edited" %}', }); } From e36b4458bd676414656c4c0044803239bc6e35ce Mon Sep 17 00:00:00 2001 From: Oliver Date: Mon, 15 Nov 2021 23:58:34 +1100 Subject: [PATCH 11/14] PEP fixes --- InvenTree/part/test_api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/InvenTree/part/test_api.py b/InvenTree/part/test_api.py index 53106b9848..b16de1b9d7 100644 --- a/InvenTree/part/test_api.py +++ b/InvenTree/part/test_api.py @@ -1125,7 +1125,7 @@ class BomItemTest(InvenTreeAPITestCase): def test_bom_item_uses(self): """ - Tests for the 'uses' field + Tests for the 'uses' field """ url = reverse('api-bom-list') From 6367f1a9edacaebd725067e3fd8a6e7d2c104652 Mon Sep 17 00:00:00 2001 From: Oliver Date: Tue, 16 Nov 2021 00:00:52 +1100 Subject: [PATCH 12/14] Improve titles in table --- InvenTree/templates/js/translated/bom.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/InvenTree/templates/js/translated/bom.js b/InvenTree/templates/js/translated/bom.js index 2204d0661f..28374d9085 100644 --- a/InvenTree/templates/js/translated/bom.js +++ b/InvenTree/templates/js/translated/bom.js @@ -951,7 +951,7 @@ function loadUsedInTable(table, part_id, options={}) { }, { field: 'part', - title: '{% trans "Part" %}', + title: '{% trans "Assembly" %}', switchable: false, sortable: true, formatter: function(value, row) { @@ -969,7 +969,7 @@ function loadUsedInTable(table, part_id, options={}) { }, { field: 'sub_part', - title: '{% trans "Sub Part" %}', + title: '{% trans "Required Part" %}', sortable: true, formatter: function(value, row) { var url = `/part/${value}/`; From 5b0a2576f60ee013c426bd4921906e24a8b1cb95 Mon Sep 17 00:00:00 2001 From: Oliver Date: Tue, 16 Nov 2021 00:04:06 +1100 Subject: [PATCH 13/14] additional filters for "used-in" table --- InvenTree/templates/js/translated/table_filters.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/InvenTree/templates/js/translated/table_filters.js b/InvenTree/templates/js/translated/table_filters.js index 537adefee9..903774f8e5 100644 --- a/InvenTree/templates/js/translated/table_filters.js +++ b/InvenTree/templates/js/translated/table_filters.js @@ -77,10 +77,22 @@ function getAvailableTableFilters(tableKey) { // Filters for the "used in" table if (tableKey == 'usedin') { return { + 'inherited': { + type: 'bool', + title: '{% trans "Inherited" %}', + }, + 'optional': { + type: 'bool', + title: '{% trans "Optional" %}', + }, 'part_active': { type: 'bool', title: '{% trans "Active" %}', }, + 'part_trackable': { + type: 'bool', + title: '{% trans "Trackable" %}', + }, }; } From 20941ce00be82f00038686d7b299224eb2bb55d5 Mon Sep 17 00:00:00 2001 From: Oliver Date: Tue, 16 Nov 2021 00:15:18 +1100 Subject: [PATCH 14/14] JS linting --- InvenTree/templates/js/translated/bom.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/InvenTree/templates/js/translated/bom.js b/InvenTree/templates/js/translated/bom.js index 28374d9085..1885624dd8 100644 --- a/InvenTree/templates/js/translated/bom.js +++ b/InvenTree/templates/js/translated/bom.js @@ -999,4 +999,4 @@ function loadUsedInTable(table, part_id, options={}) { } ] }); -} \ No newline at end of file +}