diff --git a/InvenTree/InvenTree/helpers.py b/InvenTree/InvenTree/helpers.py
index 3f65312691..9880662e63 100644
--- a/InvenTree/InvenTree/helpers.py
+++ b/InvenTree/InvenTree/helpers.py
@@ -22,7 +22,7 @@ from .settings import MEDIA_URL, STATIC_URL
def generateTestKey(test_name):
"""
Generate a test 'key' for a given test name.
- This must not have spaces as it will be used for dict lookup in a template.
+ This must not have illegal chars as it will be used for dict lookup in a template.
Tests must be named such that they will have unique keys.
"""
@@ -30,6 +30,9 @@ def generateTestKey(test_name):
key = test_name.strip().lower()
key = key.replace(" ", "")
+ # Remove any characters that cannot be used to represent a variable
+ key = re.sub(r'[^a-zA-Z0-9]', '', key)
+
return key
diff --git a/InvenTree/stock/templates/stock/item_tests.html b/InvenTree/stock/templates/stock/item_tests.html
index bd16ee8d29..77531714e0 100644
--- a/InvenTree/stock/templates/stock/item_tests.html
+++ b/InvenTree/stock/templates/stock/item_tests.html
@@ -7,7 +7,7 @@
{% include "stock/tabs.html" with tab='tests' %}
-
{% trans "Test Results" %}
+{% trans "Test Data" %}
@@ -20,8 +20,8 @@
-
-
+
+
{% endblock %}
@@ -30,11 +30,8 @@
loadStockTestResultsTable(
$("#test-result-table"), {
- params: {
- stock_item: {{ item.id }},
- user_detail: true,
- attachment_detail: true,
- },
+ part: {{ item.part.id }},
+ stock_item: {{ item.id }},
}
);
@@ -53,6 +50,21 @@ $("#add-test-result").click(function() {
);
});
+$("#test-result-table").on('click', '.button-test-add', function() {
+ var button = $(this);
+
+ var test_name = button.attr('pk');
+
+ launchModalForm(
+ "{% url 'stock-item-test-create' %}", {
+ data: {
+ stock_item: {{ item.id }},
+ test: test_name
+ },
+ }
+ );
+});
+
$("#test-result-table").on('click', '.button-test-edit', function() {
var button = $(this);
diff --git a/InvenTree/stock/views.py b/InvenTree/stock/views.py
index 11c2065c35..29a57f5926 100644
--- a/InvenTree/stock/views.py
+++ b/InvenTree/stock/views.py
@@ -254,6 +254,8 @@ class StockItemTestResultCreate(AjaxCreateView):
except (ValueError, StockItem.DoesNotExist):
pass
+ initials['test'] = self.request.GET.get('test', '')
+
return initials
def get_form(self):
diff --git a/InvenTree/templates/js/stock.html b/InvenTree/templates/js/stock.html
index af65782bef..963ccf525e 100644
--- a/InvenTree/templates/js/stock.html
+++ b/InvenTree/templates/js/stock.html
@@ -22,79 +22,98 @@ function removeStockRow(e) {
function passFailBadge(result) {
if (result) {
- return `{% trans "PASS" %}`;
+ return `{% trans "PASS" %}`;
} else {
- return `{% trans "FAIL" %}`;
+ return `{% trans "FAIL" %}`;
}
}
+function noResultBadge() {
+ return `{% trans "NO RESULT" %}`;
+}
+
+function testKey(test_name) {
+ // Convert test name to a unique key without any illegal chars
+
+ test_name = test_name.trim().toLowerCase();
+ test_name = test_name.replace(' ', '');
+
+ test_name = test_name.replace(/[^0-9a-z]/gi, '');
+
+ return test_name;
+}
+
function loadStockTestResultsTable(table, options) {
/*
* Load StockItemTestResult table
*/
- var params = options.params || {};
-
- // HTML element to setup the filtering
- var filterListElement = options.filterList || '#filter-list-stocktests';
+ function formatDate(row) {
+ // Function for formatting date field
+ var html = row.date;
- var filters = {};
+ if (row.user_detail) {
+ html += `${row.user_detail.username}`;
+ }
- filters = loadTableFilters("stocktests");
+ if (row.attachment_detail) {
+ html += ``;
+ }
- var original = {};
-
- for (var key in params) {
- original[key] = params[key];
+ return html;
}
- setupFilterList("stocktests", table, filterListElement);
+ function makeButtons(row, grouped) {
+ var html = ``;
- // Override the default values, or add new ones
- for (var key in params) {
- filters[key] = params[key];
+ html += makeIconButton('fa-plus icon-green', 'button-test-add', row.test_name, '{% trans "Add test result" %}');
+
+ if (!grouped) {
+ var pk = row.pk;
+ html += makeIconButton('fa-edit icon-blue', 'button-test-edit', pk, '{% trans "Edit test result" %}');
+ html += makeIconButton('fa-trash-alt icon-red', 'button-test-delete', pk, '{% trans "Delete test result" %}');
+ }
+
+ html += "
";
+
+ return html;
}
+ // First, load all the test templates
table.inventreeTable({
+ url: "{% url 'api-part-test-template-list' %}",
method: 'get',
formatNoMatches: function() {
- return '{% trans "No test results matching query" %}';
+ return "{% trans 'No test results found' %}";
+ },
+ queryParams: {
+ part: options.part,
},
- url: "{% url 'api-stock-test-result-list' %}",
- queryParams: filters,
- original: original,
columns: [
{
field: 'pk',
title: 'ID',
- visible: false
+ visible: false,
},
{
- field: 'test',
- title: '{% trans "Test" %}',
+ field: 'test_name',
+ title: "{% trans "Test Name" %}",
sortable: true,
formatter: function(value, row) {
var html = value;
- if (row.attachment_detail) {
- html += ``;
+ if (row.result == null) {
+ html += noResultBadge();
+ } else {
+ html += passFailBadge(row.result);
}
return html;
- },
- },
- {
- field: 'result',
- title: "{% trans "Result" %}",
- sortable: true,
- formatter: function(value) {
- return passFailBadge(value);
}
},
{
field: 'value',
- title: "{% trans "Value" %}",
- sortable: true,
+ title: '{% trans "Value" %}',
},
{
field: 'notes',
@@ -102,35 +121,85 @@ function loadStockTestResultsTable(table, options) {
},
{
field: 'date',
- title: '{% trans "Uploaded" %}',
- sortable: true,
+ title: '{% trans "Test Date" %}',
formatter: function(value, row) {
- var html = value;
-
- if (row.user_detail) {
- html += `${row.user_detail.username}`;
- }
-
- return html;
+ return formatDate(row);
}
},
{
field: 'buttons',
formatter: function(value, row) {
-
- var pk = row.pk;
-
- var html = ``;
-
- html += makeIconButton('fa-edit icon-blue', 'button-test-edit', pk, '{% trans "Edit test result" %}');
- html += makeIconButton('fa-trash-alt icon-red', 'button-test-delete', pk, '{% trans "Delete test result" %}');
-
- html += `
`;
-
- return html;
+ return makeButtons(row, false);
}
},
- ]
+ ],
+ groupBy: true,
+ groupByField: 'test_name',
+ groupByFormatter: function(field, id, data) {
+
+ // Extract the "latest" row (data are returned in date order from the server)
+ var latest = data[data.length-1];
+
+ switch (field) {
+ case 'test_name':
+ return latest.test_name + ` (${data.length})` + passFailBadge(latest.result);
+ case 'value':
+ return latest.value;
+ case 'notes':
+ return latest.notes;
+ case 'date':
+ return formatDate(latest);
+ case 'buttons':
+ // Buttons are done differently for grouped rows
+ return makeButtons(latest, true);
+ default:
+ return "---";
+ }
+ },
+ onLoadSuccess: function(tableData) {
+ // Once the test template data are loaded, query for results
+ inventreeGet(
+ "{% url 'api-stock-test-result-list' %}",
+ {
+ stock_item: options.stock_item,
+ user_detail: true,
+ attachment_detail: true,
+ },
+ {
+ success: function(data) {
+
+ // Iterate through the returned test result data, and group by test
+ data.forEach(function(item) {
+ var match = false;
+
+ var key = testKey(item.test);
+
+ // Try to associate this result with a test row
+ tableData.forEach(function(row) {
+ // The result matches the test template row
+ if (key == testKey(row.test_name)) {
+
+ // Force the names to be the same!
+ item.test_name = row.test_name;
+ match = true;
+ }
+ });
+
+ // No match could be found (this is a new test!)
+ if (!match) {
+
+ item.test_name = item.test;
+ }
+
+ tableData.push(item);
+ });
+
+ // Finally, push the data back into the table!
+ table.bootstrapTable("load", tableData);
+ }
+ },
+ );
+ }
});
}