Merge remote-tracking branch 'inventree/master' into match-fields

# Conflicts:
#	InvenTree/InvenTree/version.py
This commit is contained in:
Oliver 2022-02-17 22:55:16 +11:00
commit 580effab92
15 changed files with 367 additions and 333 deletions

2
.github/FUNDING.yml vendored Normal file
View File

@ -0,0 +1,2 @@
patreon: inventree
ko_fi: inventree

View File

@ -12,14 +12,17 @@ import common.models
INVENTREE_SW_VERSION = "0.6.0 dev" INVENTREE_SW_VERSION = "0.6.0 dev"
# InvenTree API version # InvenTree API version
INVENTREE_API_VERSION = 25 INVENTREE_API_VERSION = 26
""" """
Increment this API version number whenever there is a significant change to the API that any clients need to know about Increment this API version number whenever there is a significant change to the API that any clients need to know about
v25 -> 2022-02-16 v26 -> 2022-02-17
- Adds API endpoint for uploading a BOM file and extracting data - Adds API endpoint for uploading a BOM file and extracting data
v25 -> 2022-02-17
- Adds ability to filter "part" list endpoint by "in_bom_for" argument
v24 -> 2022-02-10 v24 -> 2022-02-10
- Adds API endpoint for deleting (cancelling) build order outputs - Adds API endpoint for deleting (cancelling) build order outputs

View File

@ -208,7 +208,7 @@ class BuildOutputCreateSerializer(serializers.Serializer):
raise ValidationError(_("Integer quantity required for trackable parts")) raise ValidationError(_("Integer quantity required for trackable parts"))
if part.has_trackable_parts(): if part.has_trackable_parts():
raise ValidationError(_("Integer quantity required, as the bill of materials contains tracakble parts")) raise ValidationError(_("Integer quantity required, as the bill of materials contains trackable parts"))
return quantity return quantity

View File

@ -995,6 +995,23 @@ class PartList(generics.ListCreateAPIView):
except (ValueError, Part.DoesNotExist): except (ValueError, Part.DoesNotExist):
pass pass
# Filter only parts which are in the "BOM" for a given part
in_bom_for = params.get('in_bom_for', None)
if in_bom_for is not None:
try:
in_bom_for = Part.objects.get(pk=in_bom_for)
# Extract a list of parts within the BOM
bom_parts = in_bom_for.get_parts_in_bom()
print("bom_parts:", bom_parts)
print([p.pk for p in bom_parts])
queryset = queryset.filter(pk__in=[p.pk for p in bom_parts])
except (ValueError, Part.DoesNotExist):
pass
# Filter by whether the BOM has been validated (or not) # Filter by whether the BOM has been validated (or not)
bom_valid = params.get('bom_valid', None) bom_valid = params.get('bom_valid', None)

View File

@ -483,6 +483,36 @@ class Part(MPTTModel):
def __str__(self): def __str__(self):
return f"{self.full_name} - {self.description}" return f"{self.full_name} - {self.description}"
def get_parts_in_bom(self):
"""
Return a list of all parts in the BOM for this part.
Takes into account substitutes, variant parts, and inherited BOM items
"""
parts = set()
for bom_item in self.get_bom_items():
for part in bom_item.get_valid_parts_for_allocation():
parts.add(part)
return parts
def check_if_part_in_bom(self, other_part):
"""
Check if the other_part is in the BOM for this part.
Note:
- Accounts for substitute parts
- Accounts for variant BOMs
"""
for bom_item in self.get_bom_items():
if other_part in bom_item.get_valid_parts_for_allocation():
return True
# No matches found
return False
def check_add_to_bom(self, parent, raise_error=False, recursive=True): def check_add_to_bom(self, parent, raise_error=False, recursive=True):
""" """
Check if this Part can be added to the BOM of another part. Check if this Part can be added to the BOM of another part.

View File

@ -109,6 +109,31 @@ class StockItemSerialize(generics.CreateAPIView):
return context return context
class StockItemInstall(generics.CreateAPIView):
"""
API endpoint for installing a particular stock item into this stock item.
- stock_item.part must be in the BOM for this part
- stock_item must currently be "in stock"
- stock_item must be serialized (and not belong to another item)
"""
queryset = StockItem.objects.none()
serializer_class = StockSerializers.InstallStockItemSerializer
def get_serializer_context(self):
context = super().get_serializer_context()
context['request'] = self.request
try:
context['item'] = StockItem.objects.get(pk=self.kwargs.get('pk', None))
except:
pass
return context
class StockAdjustView(generics.CreateAPIView): class StockAdjustView(generics.CreateAPIView):
""" """
A generic class for handling stocktake actions. A generic class for handling stocktake actions.
@ -503,11 +528,34 @@ class StockList(generics.ListCreateAPIView):
serial_numbers = data.get('serial_numbers', '') serial_numbers = data.get('serial_numbers', '')
# Assign serial numbers for a trackable part # Assign serial numbers for a trackable part
if serial_numbers and part.trackable: if serial_numbers:
if not part.trackable:
raise ValidationError({
'serial_numbers': [_("Serial numbers cannot be supplied for a non-trackable part")]
})
# If serial numbers are specified, check that they match! # If serial numbers are specified, check that they match!
try: try:
serials = extract_serial_numbers(serial_numbers, quantity, part.getLatestSerialNumberInt()) serials = extract_serial_numbers(serial_numbers, quantity, part.getLatestSerialNumberInt())
# Determine if any of the specified serial numbers already exist!
existing = []
for serial in serials:
if part.checkIfSerialNumberExists(serial):
existing.append(serial)
if len(existing) > 0:
msg = _("The following serial numbers already exist")
msg += " : "
msg += ",".join([str(e) for e in existing])
raise ValidationError({
'serial_numbers': [msg],
})
except DjangoValidationError as e: except DjangoValidationError as e:
raise ValidationError({ raise ValidationError({
'quantity': e.messages, 'quantity': e.messages,
@ -1256,6 +1304,7 @@ stock_api_urls = [
# Detail views for a single stock item # Detail views for a single stock item
url(r'^(?P<pk>\d+)/', include([ url(r'^(?P<pk>\d+)/', include([
url(r'^serialize/', StockItemSerialize.as_view(), name='api-stock-item-serialize'), url(r'^serialize/', StockItemSerialize.as_view(), name='api-stock-item-serialize'),
url(r'^install/', StockItemInstall.as_view(), name='api-stock-item-install'),
url(r'^.*$', StockDetail.as_view(), name='api-stock-detail'), url(r'^.*$', StockDetail.as_view(), name='api-stock-detail'),
])), ])),

View File

@ -8,7 +8,6 @@ from __future__ import unicode_literals
from django import forms from django import forms
from django.forms.utils import ErrorDict from django.forms.utils import ErrorDict
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.core.exceptions import ValidationError
from mptt.fields import TreeNodeChoiceField from mptt.fields import TreeNodeChoiceField
@ -16,8 +15,6 @@ from InvenTree.forms import HelperForm
from InvenTree.fields import RoundingDecimalFormField from InvenTree.fields import RoundingDecimalFormField
from InvenTree.fields import DatePickerFormField from InvenTree.fields import DatePickerFormField
from part.models import Part
from .models import StockLocation, StockItem, StockItemTracking from .models import StockLocation, StockItem, StockItemTracking
@ -162,56 +159,6 @@ class SerializeStockForm(HelperForm):
] ]
class InstallStockForm(HelperForm):
"""
Form for manually installing a stock item into another stock item
TODO: Migrate this form to the modern API forms interface
"""
part = forms.ModelChoiceField(
queryset=Part.objects.all(),
widget=forms.HiddenInput()
)
stock_item = forms.ModelChoiceField(
required=True,
queryset=StockItem.objects.filter(StockItem.IN_STOCK_FILTER),
help_text=_('Stock item to install')
)
to_install = forms.BooleanField(
widget=forms.HiddenInput(),
required=False,
)
notes = forms.CharField(
required=False,
help_text=_('Notes')
)
class Meta:
model = StockItem
fields = [
'part',
'stock_item',
# 'quantity_to_install',
'notes',
]
def clean(self):
data = super().clean()
stock_item = data.get('stock_item', None)
quantity = data.get('quantity_to_install', None)
if stock_item and quantity and quantity > stock_item.quantity:
raise ValidationError({'quantity_to_install': _('Must not exceed available quantity')})
return data
class UninstallStockForm(forms.ModelForm): class UninstallStockForm(forms.ModelForm):
""" """
Form for uninstalling a stock item which is installed in another item. Form for uninstalling a stock item which is installed in another item.

View File

@ -391,6 +391,63 @@ class SerializeStockItemSerializer(serializers.Serializer):
) )
class InstallStockItemSerializer(serializers.Serializer):
"""
Serializer for installing a stock item into a given part
"""
stock_item = serializers.PrimaryKeyRelatedField(
queryset=StockItem.objects.all(),
many=False,
required=True,
allow_null=False,
label=_('Stock Item'),
help_text=_('Select stock item to install'),
)
note = serializers.CharField(
label=_('Note'),
required=False,
allow_blank=True,
)
def validate_stock_item(self, stock_item):
"""
Validate the selected stock item
"""
if not stock_item.in_stock:
# StockItem must be in stock to be "installed"
raise ValidationError(_("Stock item is unavailable"))
# Extract the "parent" item - the item into which the stock item will be installed
parent_item = self.context['item']
parent_part = parent_item.part
if not parent_part.check_if_part_in_bom(stock_item.part):
raise ValidationError(_("Selected part is not in the Bill of Materials"))
return stock_item
def save(self):
""" Install the selected stock item into this one """
data = self.validated_data
stock_item = data['stock_item']
note = data.get('note', '')
parent_item = self.context['item']
request = self.context['request']
parent_item.installStockItem(
stock_item,
stock_item.quantity,
request.user,
note,
)
class LocationTreeSerializer(InvenTree.serializers.InvenTreeModelSerializer): class LocationTreeSerializer(InvenTree.serializers.InvenTreeModelSerializer):
""" """
Serializer for a simple tree view Serializer for a simple tree view

View File

@ -183,16 +183,11 @@
$('#stock-item-install').click(function() { $('#stock-item-install').click(function() {
launchModalForm( installStockItem({{ item.pk }}, {{ item.part.pk }}, {
"{% url 'stock-item-install' item.pk %}", onSuccess: function(response) {
{ $("#installed-table").bootstrapTable('refresh');
data: {
'part': {{ item.part.pk }},
'install_item': true,
},
reload: true,
} }
); });
}); });
loadInstalledInTable( loadInstalledInTable(
@ -311,65 +306,6 @@
}); });
}); });
$("#test-result-table").on('click', '.button-test-add', function() {
var button = $(this);
var test_name = button.attr('pk');
constructForm('{% url "api-stock-test-result-list" %}', {
method: 'POST',
fields: {
test: {
value: test_name,
},
result: {},
value: {},
attachment: {},
notes: {},
stock_item: {
value: {{ item.pk }},
hidden: true,
}
},
title: '{% trans "Add Test Result" %}',
onSuccess: reloadTable,
});
});
$("#test-result-table").on('click', '.button-test-edit', function() {
var button = $(this);
var pk = button.attr('pk');
var url = `/api/stock/test/${pk}/`;
constructForm(url, {
fields: {
test: {},
result: {},
value: {},
attachment: {},
notes: {},
},
title: '{% trans "Edit Test Result" %}',
onSuccess: reloadTable,
});
});
$("#test-result-table").on('click', '.button-test-delete', function() {
var button = $(this);
var pk = button.attr('pk');
var url = `/api/stock/test/${pk}/`;
constructForm(url, {
method: 'DELETE',
title: '{% trans "Delete Test Result" %}',
onSuccess: reloadTable,
});
});
{% if item.child_count > 0 %} {% if item.child_count > 0 %}
loadStockTable($("#childs-stock-table"), { loadStockTable($("#childs-stock-table"), {
params: { params: {

View File

@ -98,7 +98,9 @@
<li><a class='dropdown-item' href='#' id='stock-uninstall' title='{% trans "Uninstall stock item" %}'><span class='fas fa-unlink'></span> {% trans "Uninstall" %}</a></li> <li><a class='dropdown-item' href='#' id='stock-uninstall' title='{% trans "Uninstall stock item" %}'><span class='fas fa-unlink'></span> {% trans "Uninstall" %}</a></li>
{% else %} {% else %}
{% if item.part.get_used_in %} {% if item.part.get_used_in %}
<li><a class='dropdown-item' href='#' id='stock-install-in' title='{% trans "Install stock item" %}'><span class='fas fa-link'></span> {% trans "Install" %}</a></li> <!--
<li><a class='dropdown-item' href='#' id='stock-install-in' title='{% trans "Install stock item" %}'><span class='fas fa-link'></span> {% trans "Install" %}</a></li>
-->
{% endif %} {% endif %}
{% endif %} {% endif %}
</ul> </ul>
@ -442,16 +444,7 @@ $("#stock-serialize").click(function() {
$('#stock-install-in').click(function() { $('#stock-install-in').click(function() {
launchModalForm( // TODO - Launch dialog to install this item *into* another stock item
"{% url 'stock-item-install' item.pk %}",
{
data: {
'part': {{ item.part.pk }},
'install_in': true,
},
reload: true,
}
);
}); });
$('#stock-uninstall').click(function() { $('#stock-uninstall').click(function() {
@ -618,7 +611,7 @@ enableBreadcrumbTree({
{% endif %} {% endif %}
processNode: function(node) { processNode: function(node) {
node.text = node.name; node.text = node.name;
node.href = `/stock/item/${node.pk}/`; node.href = `/stock/location/${node.pk}/`;
return node; return node;
} }

View File

@ -1,33 +0,0 @@
{% extends "modal_form.html" %}
{% load i18n %}
{% block pre_form_content %}
{% if install_item %}
<p>
{% trans "Install another Stock Item into this item." %}
</p>
<p>
{% trans "Stock items can only be installed if they meet the following criteria" %}:
<ul>
<li>{% trans "The Stock Item links to a Part which is in the BOM for this Stock Item" %}</li>
<li>{% trans "The Stock Item is currently in stock" %}</li>
<li>{% trans "The Stock Item is serialized and does not belong to another item" %}</li>
</ul>
</p>
{% elif install_in %}
<p>
{% trans "Install this Stock Item in another stock item." %}
</p>
<p>
{% trans "Stock items can only be installed if they meet the following criteria" %}:
<ul>
<li>{% trans "The part associated to this Stock Item belongs to another part's BOM" %}</li>
<li>{% trans "This Stock Item is serialized and does not belong to another item" %}</li>
</ul>
</p>
{% endif %}
{% endblock %}

View File

@ -24,7 +24,6 @@ stock_item_detail_urls = [
url(r'^qr_code/', views.StockItemQRCode.as_view(), name='stock-item-qr'), url(r'^qr_code/', views.StockItemQRCode.as_view(), name='stock-item-qr'),
url(r'^delete_test_data/', views.StockItemDeleteTestData.as_view(), name='stock-item-delete-test-data'), url(r'^delete_test_data/', views.StockItemDeleteTestData.as_view(), name='stock-item-delete-test-data'),
url(r'^return/', views.StockItemReturnToStock.as_view(), name='stock-item-return'), url(r'^return/', views.StockItemReturnToStock.as_view(), name='stock-item-return'),
url(r'^install/', views.StockItemInstall.as_view(), name='stock-item-install'),
url(r'^add_tracking/', views.StockItemTrackingCreate.as_view(), name='stock-tracking-create'), url(r'^add_tracking/', views.StockItemTrackingCreate.as_view(), name='stock-tracking-create'),

View File

@ -465,155 +465,6 @@ class StockItemQRCode(QRCodeView):
return None return None
class StockItemInstall(AjaxUpdateView):
"""
View for manually installing stock items into
a particular stock item.
In contrast to the StockItemUninstall view,
only a single stock item can be installed at once.
The "part" to be installed must be provided in the GET query parameters.
"""
model = StockItem
form_class = StockForms.InstallStockForm
ajax_form_title = _('Install Stock Item')
ajax_template_name = "stock/item_install.html"
part = None
def get_params(self):
""" Retrieve GET parameters """
# Look at GET params
self.part_id = self.request.GET.get('part', None)
self.install_in = self.request.GET.get('install_in', False)
self.install_item = self.request.GET.get('install_item', False)
if self.part_id is None:
# Look at POST params
self.part_id = self.request.POST.get('part', None)
try:
self.part = Part.objects.get(pk=self.part_id)
except (ValueError, Part.DoesNotExist):
self.part = None
def get_stock_items(self):
"""
Return a list of stock items suitable for displaying to the user.
Requirements:
- Items must be in stock
- Items must be in BOM of stock item
- Items must be serialized
"""
# Filter items in stock
items = StockItem.objects.filter(StockItem.IN_STOCK_FILTER)
# Filter serialized stock items
items = items.exclude(serial__isnull=True).exclude(serial__exact='')
if self.part:
# Filter for parts to install this item in
if self.install_in:
# Get parts using this part
allowed_parts = self.part.get_used_in()
# Filter
items = items.filter(part__in=allowed_parts)
# Filter for parts to install in this item
if self.install_item:
# Get all parts which can be installed into this part
allowed_parts = self.part.get_installed_part_options()
# Filter
items = items.filter(part__in=allowed_parts)
return items
def get_context_data(self, **kwargs):
""" Retrieve parameters and update context """
ctx = super().get_context_data(**kwargs)
# Get request parameters
self.get_params()
ctx.update({
'part': self.part,
'install_in': self.install_in,
'install_item': self.install_item,
})
return ctx
def get_initial(self):
initials = super().get_initial()
items = self.get_stock_items()
# If there is a single stock item available, we can use it!
if items.count() == 1:
item = items.first()
initials['stock_item'] = item.pk
if self.part:
initials['part'] = self.part
try:
# Is this stock item being installed in the other stock item?
initials['to_install'] = self.install_in or not self.install_item
except AttributeError:
pass
return initials
def get_form(self):
form = super().get_form()
form.fields['stock_item'].queryset = self.get_stock_items()
return form
def post(self, request, *args, **kwargs):
self.get_params()
form = self.get_form()
valid = form.is_valid()
if valid:
# We assume by this point that we have a valid stock_item and quantity values
data = form.cleaned_data
other_stock_item = data['stock_item']
# Quantity will always be 1 for serialized item
quantity = 1
notes = data['notes']
# Get stock item
this_stock_item = self.get_object()
if data['to_install']:
# Install this stock item into the other stock item
other_stock_item.installStockItem(this_stock_item, quantity, request.user, notes)
else:
# Install the other stock item into this one
this_stock_item.installStockItem(other_stock_item, quantity, request.user, notes)
data = {
'form_valid': valid,
}
return self.renderJsonResponse(request, form, data=data)
class StockItemUninstall(AjaxView, FormMixin): class StockItemUninstall(AjaxView, FormMixin):
""" """
View for uninstalling one or more StockItems, View for uninstalling one or more StockItems,

View File

@ -46,6 +46,7 @@
editStockLocation, editStockLocation,
exportStock, exportStock,
findStockItemBySerialNumber, findStockItemBySerialNumber,
installStockItem,
loadInstalledInTable, loadInstalledInTable,
loadStockAllocationTable, loadStockAllocationTable,
loadStockLocationTable, loadStockLocationTable,
@ -1227,14 +1228,42 @@ function formatDate(row) {
return html; return html;
} }
/*
* Load StockItemTestResult table
*/
function loadStockTestResultsTable(table, options) { function loadStockTestResultsTable(table, options) {
/*
* Load StockItemTestResult table // Setup filters for the table
*/ var filterTarget = options.filterTarget || '#filter-list-stocktests';
var filterKey = options.filterKey || options.name || 'stocktests';
var filters = loadTableFilters(filterKey);
var params = {
part: options.part,
};
var original = {};
for (var k in params) {
original[k] = params[k];
filters[k] = params[k];
}
setupFilterList(filterKey, table, filterTarget);
function makeButtons(row, grouped) { function makeButtons(row, grouped) {
// Helper function for rendering buttons
var html = `<div class='btn-group float-right' role='group'>`; var html = `<div class='btn-group float-right' role='group'>`;
if (row.requires_attachment == false && row.requires_value == false && !row.result) {
// Enable a "quick tick" option for this test result
html += makeIconButton('fa-check-circle icon-green', 'button-test-tick', row.test_name, '{% trans "Pass test" %}');
}
html += makeIconButton('fa-plus icon-green', 'button-test-add', row.test_name, '{% trans "Add test result" %}'); html += makeIconButton('fa-plus icon-green', 'button-test-add', row.test_name, '{% trans "Add test result" %}');
if (!grouped && row.result != null) { if (!grouped && row.result != null) {
@ -1258,14 +1287,13 @@ function loadStockTestResultsTable(table, options) {
rootParentId: parent_node, rootParentId: parent_node,
parentIdField: 'parent', parentIdField: 'parent',
idField: 'pk', idField: 'pk',
uniqueId: 'key', uniqueId: 'pk',
treeShowField: 'test_name', treeShowField: 'test_name',
formatNoMatches: function() { formatNoMatches: function() {
return '{% trans "No test results found" %}'; return '{% trans "No test results found" %}';
}, },
queryParams: { queryParams: filters,
part: options.part, original: original,
},
onPostBody: function() { onPostBody: function() {
table.treegrid({ table.treegrid({
treeColumn: 0, treeColumn: 0,
@ -1401,6 +1429,102 @@ function loadStockTestResultsTable(table, options) {
); );
} }
}); });
/* Register button callbacks */
function reloadTestTable(response) {
$(table).bootstrapTable('refresh');
}
// "tick" a test result
$(table).on('click', '.button-test-tick', function() {
var button = $(this);
var test_name = button.attr('pk');
inventreePut(
'{% url "api-stock-test-result-list" %}',
{
test: test_name,
result: true,
stock_item: options.stock_item,
},
{
method: 'POST',
success: reloadTestTable,
}
);
});
// Add a test result
$(table).on('click', '.button-test-add', function() {
var button = $(this);
var test_name = button.attr('pk');
constructForm('{% url "api-stock-test-result-list" %}', {
method: 'POST',
fields: {
test: {
value: test_name,
},
result: {},
value: {},
attachment: {},
notes: {},
stock_item: {
value: options.stock_item,
hidden: true,
}
},
title: '{% trans "Add Test Result" %}',
onSuccess: reloadTestTable,
});
});
// Edit a test result
$(table).on('click', '.button-test-edit', function() {
var button = $(this);
var pk = button.attr('pk');
var url = `/api/stock/test/${pk}/`;
constructForm(url, {
fields: {
test: {},
result: {},
value: {},
attachment: {},
notes: {},
},
title: '{% trans "Edit Test Result" %}',
onSuccess: reloadTestTable,
});
});
// Delete a test result
$(table).on('click', '.button-test-delete', function() {
var button = $(this);
var pk = button.attr('pk');
var url = `/api/stock/test/${pk}/`;
var row = $(table).bootstrapTable('getRowByUniqueId', pk);
var html = `
<div class='alert alert-block alert-danger'>
<strong>{% trans "Delete test result" %}:</strong> ${row.test_name || row.test || row.key}
</div>`;
constructForm(url, {
method: 'DELETE',
title: '{% trans "Delete Test Result" %}',
onSuccess: reloadTestTable,
preFormContent: html,
});
});
} }
@ -2837,3 +2961,67 @@ function loadInstalledInTable(table, options) {
} }
}); });
} }
/*
* Launch a dialog to install a stock item into another stock item
*/
function installStockItem(stock_item_id, part_id, options={}) {
var html = `
<div class='alert alert-block alert-info'>
<strong>{% trans "Install another stock item into this item" %}</strong><br>
{% trans "Stock items can only be installed if they meet the following criteria" %}:<br>
<ul>
<li>{% trans "The Stock Item links to a Part which is the BOM for this Stock Item" %}</li>
<li>{% trans "The Stock Item is currently available in stock" %}</li>
<li>{% trans "The Stock Item is serialized and does not belong to another item" %}</li>
</ul>
</div>`;
constructForm(
`/api/stock/${stock_item_id}/install/`,
{
method: 'POST',
fields: {
part: {
type: 'related field',
required: 'true',
label: '{% trans "Part" %}',
help_text: '{% trans "Select part to install" %}',
model: 'part',
api_url: '{% url "api-part-list" %}',
auto_fill: true,
filters: {
trackable: true,
in_bom_for: part_id,
}
},
stock_item: {
filters: {
part_detail: true,
in_stock: true,
serialized: true,
},
adjustFilters: function(filters, opts) {
var part = getFormFieldValue('part', {}, opts);
if (part) {
filters.part = part;
}
return filters;
}
}
},
confirm: true,
title: '{% trans "Install Stock Item" %}',
preFormContent: html,
onSuccess: function(response) {
if (options.onSuccess) {
options.onSuccess(response);
}
}
}
);
}

View File

@ -265,12 +265,7 @@ function getAvailableTableFilters(tableKey) {
// Filters for the 'stock test' table // Filters for the 'stock test' table
if (tableKey == 'stocktests') { if (tableKey == 'stocktests') {
return { return {};
result: {
type: 'bool',
title: '{% trans "Test result" %}',
},
};
} }
// Filters for the 'part test template' table // Filters for the 'part test template' table