mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
commit
8537dc2a85
@ -1033,6 +1033,9 @@ class Part(MPTTModel):
|
|||||||
# Return the tests which are required by this part
|
# Return the tests which are required by this part
|
||||||
return self.getTestTemplates(required=True)
|
return self.getTestTemplates(required=True)
|
||||||
|
|
||||||
|
def requiredTestCount(self):
|
||||||
|
return self.getRequiredTests().count()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def attachment_count(self):
|
def attachment_count(self):
|
||||||
""" Count the number of attachments for this part.
|
""" Count the number of attachments for this part.
|
||||||
|
@ -105,9 +105,11 @@ class PartBriefSerializer(InvenTreeModelSerializer):
|
|||||||
'thumbnail',
|
'thumbnail',
|
||||||
'active',
|
'active',
|
||||||
'assembly',
|
'assembly',
|
||||||
|
'is_template',
|
||||||
'purchaseable',
|
'purchaseable',
|
||||||
'salable',
|
'salable',
|
||||||
'stock',
|
'stock',
|
||||||
|
'trackable',
|
||||||
'virtual',
|
'virtual',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -80,21 +80,10 @@ class StockDetail(generics.RetrieveUpdateDestroyAPIView):
|
|||||||
|
|
||||||
def get_serializer(self, *args, **kwargs):
|
def get_serializer(self, *args, **kwargs):
|
||||||
|
|
||||||
try:
|
kwargs['part_detail'] = True
|
||||||
kwargs['part_detail'] = str2bool(self.request.query_params.get('part_detail', False))
|
kwargs['location_detail'] = True
|
||||||
except AttributeError:
|
kwargs['supplier_part_detail'] = True
|
||||||
pass
|
kwargs['test_detail'] = True
|
||||||
|
|
||||||
try:
|
|
||||||
kwargs['location_detail'] = str2bool(self.request.query_params.get('location_detail', False))
|
|
||||||
except AttributeError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
try:
|
|
||||||
kwargs['supplier_part_detail'] = str2bool(self.request.query_params.get('supplier_detail', False))
|
|
||||||
except AttributeError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
kwargs['context'] = self.get_serializer_context()
|
kwargs['context'] = self.get_serializer_context()
|
||||||
|
|
||||||
return self.serializer_class(*args, **kwargs)
|
return self.serializer_class(*args, **kwargs)
|
||||||
@ -698,17 +687,14 @@ class StockItemTestResultList(generics.ListCreateAPIView):
|
|||||||
'value',
|
'value',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
ordering = 'date'
|
||||||
|
|
||||||
def get_serializer(self, *args, **kwargs):
|
def get_serializer(self, *args, **kwargs):
|
||||||
try:
|
try:
|
||||||
kwargs['user_detail'] = str2bool(self.request.query_params.get('user_detail', False))
|
kwargs['user_detail'] = str2bool(self.request.query_params.get('user_detail', False))
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
try:
|
|
||||||
kwargs['attachment_detail'] = str2bool(self.request.query_params.get('attachment_detail', False))
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
kwargs['context'] = self.get_serializer_context()
|
kwargs['context'] = self.get_serializer_context()
|
||||||
|
|
||||||
return self.serializer_class(*args, **kwargs)
|
return self.serializer_class(*args, **kwargs)
|
||||||
@ -724,23 +710,6 @@ class StockItemTestResultList(generics.ListCreateAPIView):
|
|||||||
# Capture the user information
|
# Capture the user information
|
||||||
test_result = serializer.save()
|
test_result = serializer.save()
|
||||||
test_result.user = self.request.user
|
test_result.user = self.request.user
|
||||||
|
|
||||||
# Check if a file has been attached to the request
|
|
||||||
attachment_file = self.request.FILES.get('attachment', None)
|
|
||||||
|
|
||||||
if attachment_file:
|
|
||||||
# Create a new attachment associated with the stock item
|
|
||||||
attachment = StockItemAttachment(
|
|
||||||
attachment=attachment_file,
|
|
||||||
stock_item=test_result.stock_item,
|
|
||||||
user=test_result.user
|
|
||||||
)
|
|
||||||
|
|
||||||
attachment.save()
|
|
||||||
|
|
||||||
# Link the attachment back to the test result
|
|
||||||
test_result.attachment = attachment
|
|
||||||
|
|
||||||
test_result.save()
|
test_result.save()
|
||||||
|
|
||||||
|
|
||||||
|
19
InvenTree/stock/migrations/0042_auto_20200523_0121.py
Normal file
19
InvenTree/stock/migrations/0042_auto_20200523_0121.py
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
# Generated by Django 3.0.5 on 2020-05-23 01:21
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import stock.models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('stock', '0041_stockitemtestresult_notes'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='stockitemtestresult',
|
||||||
|
name='attachment',
|
||||||
|
field=models.FileField(blank=True, help_text='Test result attachment', null=True, upload_to=stock.models.rename_stock_item_test_result_attachment, verbose_name='Attachment'),
|
||||||
|
),
|
||||||
|
]
|
@ -1007,6 +1007,10 @@ class StockItem(MPTTModel):
|
|||||||
'failed': failed,
|
'failed': failed,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@property
|
||||||
|
def required_test_count(self):
|
||||||
|
return self.part.getRequiredTests().count()
|
||||||
|
|
||||||
def hasRequiredTests(self):
|
def hasRequiredTests(self):
|
||||||
return self.part.getRequiredTests().count() > 0
|
return self.part.getRequiredTests().count() > 0
|
||||||
|
|
||||||
@ -1090,6 +1094,11 @@ class StockItemTracking(models.Model):
|
|||||||
# file = models.FileField()
|
# file = models.FileField()
|
||||||
|
|
||||||
|
|
||||||
|
def rename_stock_item_test_result_attachment(instance, filename):
|
||||||
|
|
||||||
|
return os.path.join('stock_files', str(instance.stock_item.pk), os.path.basename(filename))
|
||||||
|
|
||||||
|
|
||||||
class StockItemTestResult(models.Model):
|
class StockItemTestResult(models.Model):
|
||||||
"""
|
"""
|
||||||
A StockItemTestResult records results of custom tests against individual StockItem objects.
|
A StockItemTestResult records results of custom tests against individual StockItem objects.
|
||||||
@ -1119,13 +1128,11 @@ class StockItemTestResult(models.Model):
|
|||||||
|
|
||||||
super().clean()
|
super().clean()
|
||||||
|
|
||||||
"""
|
|
||||||
# If this test result corresponds to a template, check the requirements of the template
|
# If this test result corresponds to a template, check the requirements of the template
|
||||||
key = helpers.generateTestKey(self.test)
|
key = self.key
|
||||||
|
|
||||||
templates = self.stock_item.part.getTestTemplates()
|
templates = self.stock_item.part.getTestTemplates()
|
||||||
|
|
||||||
TODO: Re-introduce this at a later stage, it is buggy when uplaoding an attachment via the API
|
|
||||||
for template in templates:
|
for template in templates:
|
||||||
if key == template.key:
|
if key == template.key:
|
||||||
|
|
||||||
@ -1142,17 +1149,10 @@ class StockItemTestResult(models.Model):
|
|||||||
})
|
})
|
||||||
|
|
||||||
break
|
break
|
||||||
"""
|
|
||||||
|
|
||||||
# If an attachment is linked to this result, the attachment must also point to the item
|
@property
|
||||||
try:
|
def key(self):
|
||||||
if self.attachment:
|
return helpers.generateTestKey(self.test)
|
||||||
if not self.attachment.stock_item == self.stock_item:
|
|
||||||
raise ValidationError({
|
|
||||||
'attachment': _("Test result attachment must be linked to the same StockItem"),
|
|
||||||
})
|
|
||||||
except (StockItem.DoesNotExist, StockItemAttachment.DoesNotExist):
|
|
||||||
pass
|
|
||||||
|
|
||||||
stock_item = models.ForeignKey(
|
stock_item = models.ForeignKey(
|
||||||
StockItem,
|
StockItem,
|
||||||
@ -1178,10 +1178,9 @@ class StockItemTestResult(models.Model):
|
|||||||
help_text=_('Test output value')
|
help_text=_('Test output value')
|
||||||
)
|
)
|
||||||
|
|
||||||
attachment = models.ForeignKey(
|
attachment = models.FileField(
|
||||||
StockItemAttachment,
|
null=True, blank=True,
|
||||||
on_delete=models.SET_NULL,
|
upload_to=rename_stock_item_test_result_attachment,
|
||||||
blank=True, null=True,
|
|
||||||
verbose_name=_('Attachment'),
|
verbose_name=_('Attachment'),
|
||||||
help_text=_('Test result attachment'),
|
help_text=_('Test result attachment'),
|
||||||
)
|
)
|
||||||
|
@ -108,11 +108,14 @@ class StockItemSerializer(InvenTreeModelSerializer):
|
|||||||
quantity = serializers.FloatField()
|
quantity = serializers.FloatField()
|
||||||
allocated = serializers.FloatField()
|
allocated = serializers.FloatField()
|
||||||
|
|
||||||
|
required_tests = serializers.IntegerField(source='required_test_count', read_only=True)
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
|
|
||||||
part_detail = kwargs.pop('part_detail', False)
|
part_detail = kwargs.pop('part_detail', False)
|
||||||
location_detail = kwargs.pop('location_detail', False)
|
location_detail = kwargs.pop('location_detail', False)
|
||||||
supplier_part_detail = kwargs.pop('supplier_part_detail', False)
|
supplier_part_detail = kwargs.pop('supplier_part_detail', False)
|
||||||
|
test_detail = kwargs.pop('test_detail', False)
|
||||||
|
|
||||||
super(StockItemSerializer, self).__init__(*args, **kwargs)
|
super(StockItemSerializer, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
@ -125,6 +128,9 @@ class StockItemSerializer(InvenTreeModelSerializer):
|
|||||||
if supplier_part_detail is not True:
|
if supplier_part_detail is not True:
|
||||||
self.fields.pop('supplier_part_detail')
|
self.fields.pop('supplier_part_detail')
|
||||||
|
|
||||||
|
if test_detail is not True:
|
||||||
|
self.fields.pop('required_tests')
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = StockItem
|
model = StockItem
|
||||||
fields = [
|
fields = [
|
||||||
@ -141,6 +147,7 @@ class StockItemSerializer(InvenTreeModelSerializer):
|
|||||||
'part_detail',
|
'part_detail',
|
||||||
'pk',
|
'pk',
|
||||||
'quantity',
|
'quantity',
|
||||||
|
'required_tests',
|
||||||
'sales_order',
|
'sales_order',
|
||||||
'serial',
|
'serial',
|
||||||
'supplier_part',
|
'supplier_part',
|
||||||
@ -222,31 +229,28 @@ class StockItemTestResultSerializer(InvenTreeModelSerializer):
|
|||||||
""" Serializer for the StockItemTestResult model """
|
""" Serializer for the StockItemTestResult model """
|
||||||
|
|
||||||
user_detail = UserSerializerBrief(source='user', read_only=True)
|
user_detail = UserSerializerBrief(source='user', read_only=True)
|
||||||
attachment_detail = StockItemAttachmentSerializer(source='attachment', read_only=True)
|
|
||||||
|
key = serializers.CharField(read_only=True)
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
user_detail = kwargs.pop('user_detail', False)
|
user_detail = kwargs.pop('user_detail', False)
|
||||||
attachment_detail = kwargs.pop('attachment_detail', False)
|
|
||||||
|
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
if user_detail is not True:
|
if user_detail is not True:
|
||||||
self.fields.pop('user_detail')
|
self.fields.pop('user_detail')
|
||||||
|
|
||||||
if attachment_detail is not True:
|
|
||||||
self.fields.pop('attachment_detail')
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = StockItemTestResult
|
model = StockItemTestResult
|
||||||
|
|
||||||
fields = [
|
fields = [
|
||||||
'pk',
|
'pk',
|
||||||
'stock_item',
|
'stock_item',
|
||||||
|
'key',
|
||||||
'test',
|
'test',
|
||||||
'result',
|
'result',
|
||||||
'value',
|
'value',
|
||||||
'attachment',
|
'attachment',
|
||||||
'attachment_detail',
|
|
||||||
'notes',
|
'notes',
|
||||||
'user',
|
'user',
|
||||||
'user_detail',
|
'user_detail',
|
||||||
|
@ -13,10 +13,13 @@
|
|||||||
<div id='button-toolbar'>
|
<div id='button-toolbar'>
|
||||||
<div class='button-toolbar container-fluid' style="float: right;">
|
<div class='button-toolbar container-fluid' style="float: right;">
|
||||||
<div class='btn-group' role='group'>
|
<div class='btn-group' role='group'>
|
||||||
<button type='button' class='btn btn-success' id='add-test-result'>{% trans "Add Test Data" %}</button>
|
|
||||||
{% if user.is_staff %}
|
{% if user.is_staff %}
|
||||||
<button type='button' class='btn btn-danger' id='delete-test-results'>{% trans "Delete Test Data" %}</button>
|
<button type='button' class='btn btn-danger' id='delete-test-results'>{% trans "Delete Test Data" %}</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
<button type='button' class='btn btn-success' id='add-test-result'>{% trans "Add Test Data" %}</button>
|
||||||
|
{% if item.part.has_test_report_templates %}
|
||||||
|
<button type='button' class='btn btn-default' id='test-report'>{% trans "Test Report" %} <span class='fas fa-tasks'></span></button>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class='filter-list' id='filter-list-stocktests'>
|
<div class='filter-list' id='filter-list-stocktests'>
|
||||||
<!-- Empty div -->
|
<!-- Empty div -->
|
||||||
@ -43,6 +46,17 @@ function reloadTable() {
|
|||||||
//$("#test-result-table").bootstrapTable("refresh");
|
//$("#test-result-table").bootstrapTable("refresh");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{% if item.part.has_test_report_templates %}
|
||||||
|
$("#test-report").click(function() {
|
||||||
|
launchModalForm(
|
||||||
|
"{% url 'stock-item-test-report-select' item.id %}",
|
||||||
|
{
|
||||||
|
follow: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{% if user.is_staff %}
|
{% if user.is_staff %}
|
||||||
$("#delete-test-results").click(function() {
|
$("#delete-test-results").click(function() {
|
||||||
launchModalForm(
|
launchModalForm(
|
||||||
|
@ -292,17 +292,6 @@ class StockItemTestResultCreate(AjaxCreateView):
|
|||||||
form = super().get_form()
|
form = super().get_form()
|
||||||
form.fields['stock_item'].widget = HiddenInput()
|
form.fields['stock_item'].widget = HiddenInput()
|
||||||
|
|
||||||
# Extract the StockItem object
|
|
||||||
item_id = form['stock_item'].value()
|
|
||||||
|
|
||||||
# Limit the options for the file attachments
|
|
||||||
try:
|
|
||||||
stock_item = StockItem.objects.get(pk=item_id)
|
|
||||||
form.fields['attachment'].queryset = stock_item.attachments.all()
|
|
||||||
except (ValueError, StockItem.DoesNotExist):
|
|
||||||
# Hide the attachments field
|
|
||||||
form.fields['attachment'].widget = HiddenInput()
|
|
||||||
|
|
||||||
return form
|
return form
|
||||||
|
|
||||||
|
|
||||||
@ -321,8 +310,6 @@ class StockItemTestResultEdit(AjaxUpdateView):
|
|||||||
|
|
||||||
form.fields['stock_item'].widget = HiddenInput()
|
form.fields['stock_item'].widget = HiddenInput()
|
||||||
|
|
||||||
form.fields['attachment'].queryset = self.object.stock_item.attachments.all()
|
|
||||||
|
|
||||||
return form
|
return form
|
||||||
|
|
||||||
|
|
||||||
|
@ -32,17 +32,6 @@ function noResultBadge() {
|
|||||||
return `<span class='label label-blue float-right'>{% trans "NO RESULT" %}</span>`;
|
return `<span class='label label-blue float-right'>{% trans "NO RESULT" %}</span>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
function loadStockTestResultsTable(table, options) {
|
||||||
/*
|
/*
|
||||||
* Load StockItemTestResult table
|
* Load StockItemTestResult table
|
||||||
@ -56,8 +45,8 @@ function loadStockTestResultsTable(table, options) {
|
|||||||
html += `<span class='badge'>${row.user_detail.username}</span>`;
|
html += `<span class='badge'>${row.user_detail.username}</span>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (row.attachment_detail) {
|
if (row.attachment) {
|
||||||
html += `<a href='${row.attachment_detail.attachment}'><span class='fas fa-file-alt label-right'></span></a>`;
|
html += `<a href='${row.attachment}'><span class='fas fa-file-alt label-right'></span></a>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return html;
|
return html;
|
||||||
@ -177,14 +166,14 @@ function loadStockTestResultsTable(table, options) {
|
|||||||
var match = false;
|
var match = false;
|
||||||
var override = false;
|
var override = false;
|
||||||
|
|
||||||
var key = testKey(item.test);
|
var key = item.key;
|
||||||
|
|
||||||
// Try to associate this result with a test row
|
// Try to associate this result with a test row
|
||||||
tableData.forEach(function(row, index) {
|
tableData.forEach(function(row, index) {
|
||||||
|
|
||||||
|
|
||||||
// The result matches the test template row
|
// The result matches the test template row
|
||||||
if (key == testKey(row.test_name)) {
|
if (key == row.key) {
|
||||||
|
|
||||||
// Force the names to be the same!
|
// Force the names to be the same!
|
||||||
item.test_name = row.test_name;
|
item.test_name = row.test_name;
|
||||||
|
Loading…
Reference in New Issue
Block a user