mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Merge remote-tracking branch 'inventree/master'
This commit is contained in:
commit
c00b19bc5b
@ -445,9 +445,9 @@ class IndexView(TemplateView):
|
|||||||
# TODO - Is there a less expensive way to get these from the database
|
# TODO - Is there a less expensive way to get these from the database
|
||||||
context['to_order'] = [part for part in Part.objects.filter(purchaseable=True) if part.need_to_restock()]
|
context['to_order'] = [part for part in Part.objects.filter(purchaseable=True) if part.need_to_restock()]
|
||||||
|
|
||||||
# Generate a list of buildable parts which have stock below their minimum values
|
# Generate a list of assembly parts which have stock below their minimum values
|
||||||
# TODO - Is there a less expensive way to get these from the database
|
# TODO - Is there a less expensive way to get these from the database
|
||||||
context['to_build'] = [part for part in Part.objects.filter(buildable=True) if part.need_to_restock()]
|
context['to_build'] = [part for part in Part.objects.filter(assembly=True) if part.need_to_restock()]
|
||||||
|
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
@ -51,7 +51,7 @@ class Build(models.Model):
|
|||||||
related_name='builds',
|
related_name='builds',
|
||||||
limit_choices_to={
|
limit_choices_to={
|
||||||
'is_template': False,
|
'is_template': False,
|
||||||
'buildable': True,
|
'assembly': True,
|
||||||
'active': True
|
'active': True
|
||||||
},
|
},
|
||||||
help_text='Select part to build',
|
help_text='Select part to build',
|
||||||
|
@ -51,7 +51,7 @@ class CategoryList(generics.ListCreateAPIView):
|
|||||||
|
|
||||||
filter_backends = [
|
filter_backends = [
|
||||||
DjangoFilterBackend,
|
DjangoFilterBackend,
|
||||||
# filters.SearchFilter,
|
filters.SearchFilter,
|
||||||
filters.OrderingFilter,
|
filters.OrderingFilter,
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -129,8 +129,8 @@ class PartList(generics.ListCreateAPIView):
|
|||||||
filter_fields = [
|
filter_fields = [
|
||||||
'is_template',
|
'is_template',
|
||||||
'variant_of',
|
'variant_of',
|
||||||
'buildable',
|
'assembly',
|
||||||
'consumable',
|
'component',
|
||||||
'trackable',
|
'trackable',
|
||||||
'purchaseable',
|
'purchaseable',
|
||||||
'salable',
|
'salable',
|
||||||
|
@ -57,5 +57,5 @@
|
|||||||
fields:
|
fields:
|
||||||
name: 'Bob'
|
name: 'Bob'
|
||||||
description: 'Can we build it?'
|
description: 'Can we build it?'
|
||||||
buildable: true
|
assembly: true
|
||||||
|
|
@ -92,17 +92,17 @@ class EditPartForm(HelperForm):
|
|||||||
'category',
|
'category',
|
||||||
'name',
|
'name',
|
||||||
'IPN',
|
'IPN',
|
||||||
'is_template',
|
|
||||||
'variant_of',
|
|
||||||
'description',
|
'description',
|
||||||
'keywords',
|
'keywords',
|
||||||
|
'variant_of',
|
||||||
|
'is_template',
|
||||||
'URL',
|
'URL',
|
||||||
'default_location',
|
'default_location',
|
||||||
'default_supplier',
|
'default_supplier',
|
||||||
'units',
|
'units',
|
||||||
'minimum_stock',
|
'minimum_stock',
|
||||||
'buildable',
|
'assembly',
|
||||||
'consumable',
|
'component',
|
||||||
'trackable',
|
'trackable',
|
||||||
'purchaseable',
|
'purchaseable',
|
||||||
'salable',
|
'salable',
|
||||||
|
42
InvenTree/part/migrations/0007_auto_20190602_1944.py
Normal file
42
InvenTree/part/migrations/0007_auto_20190602_1944.py
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
# Generated by Django 2.2 on 2019-06-02 09:44
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('part', '0006_auto_20190526_1215'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='part',
|
||||||
|
name='buildable',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='part',
|
||||||
|
name='consumable',
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='part',
|
||||||
|
name='assembly',
|
||||||
|
field=models.BooleanField(default=False, help_text='Can this part be built from other parts?', verbose_name='Assembly'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='part',
|
||||||
|
name='component',
|
||||||
|
field=models.BooleanField(default=True, help_text='Can this part be used to build other parts?', verbose_name='Component'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='bomitem',
|
||||||
|
name='part',
|
||||||
|
field=models.ForeignKey(help_text='Select parent part', limit_choices_to={'active': True, 'assembly': True}, on_delete=django.db.models.deletion.CASCADE, related_name='bom_items', to='part.Part'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='bomitem',
|
||||||
|
name='sub_part',
|
||||||
|
field=models.ForeignKey(help_text='Select part to be used in BOM', limit_choices_to={'active': True, 'component': True}, on_delete=django.db.models.deletion.CASCADE, related_name='used_in', to='part.Part'),
|
||||||
|
),
|
||||||
|
]
|
@ -201,8 +201,8 @@ class Part(models.Model):
|
|||||||
minimum_stock: Minimum preferred quantity to keep in stock
|
minimum_stock: Minimum preferred quantity to keep in stock
|
||||||
units: Units of measure for this part (default='pcs')
|
units: Units of measure for this part (default='pcs')
|
||||||
salable: Can this part be sold to customers?
|
salable: Can this part be sold to customers?
|
||||||
buildable: Can this part be build from other parts?
|
assembly: Can this part be build from other parts?
|
||||||
consumable: Can this part be used to make other parts?
|
component: Can this part be used to make other parts?
|
||||||
purchaseable: Can this part be purchased from suppliers?
|
purchaseable: Can this part be purchased from suppliers?
|
||||||
trackable: Trackable parts can have unique serial numbers assigned, etc, etc
|
trackable: Trackable parts can have unique serial numbers assigned, etc, etc
|
||||||
active: Is this part active? Parts are deactivated instead of being deleted
|
active: Is this part active? Parts are deactivated instead of being deleted
|
||||||
@ -248,6 +248,18 @@ class Part(models.Model):
|
|||||||
else:
|
else:
|
||||||
return static('/img/blank_image.png')
|
return static('/img/blank_image.png')
|
||||||
|
|
||||||
|
def validate_unique(self, exclude=None):
|
||||||
|
super().validate_unique(exclude)
|
||||||
|
|
||||||
|
# Part name uniqueness should be case insensitive
|
||||||
|
try:
|
||||||
|
if Part.objects.filter(name__iexact=self.name).exclude(id=self.id).exists():
|
||||||
|
raise ValidationError({
|
||||||
|
"name": _("A part with this name already exists")
|
||||||
|
})
|
||||||
|
except Part.DoesNotExist:
|
||||||
|
pass
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
""" Perform cleaning operations for the Part model """
|
""" Perform cleaning operations for the Part model """
|
||||||
|
|
||||||
@ -343,9 +355,9 @@ class Part(models.Model):
|
|||||||
|
|
||||||
units = models.CharField(max_length=20, default="pcs", blank=True, help_text='Stock keeping units for this part')
|
units = models.CharField(max_length=20, default="pcs", blank=True, help_text='Stock keeping units for this part')
|
||||||
|
|
||||||
buildable = models.BooleanField(default=False, help_text='Can this part be built from other parts?')
|
assembly = models.BooleanField(default=False, verbose_name='Assembly', help_text='Can this part be built from other parts?')
|
||||||
|
|
||||||
consumable = models.BooleanField(default=True, help_text='Can this part be used to build other parts?')
|
component = models.BooleanField(default=True, verbose_name='Component', help_text='Can this part be used to build other parts?')
|
||||||
|
|
||||||
trackable = models.BooleanField(default=False, help_text='Does this part have tracking for unique items?')
|
trackable = models.BooleanField(default=False, help_text='Does this part have tracking for unique items?')
|
||||||
|
|
||||||
@ -858,7 +870,7 @@ class BomItem(models.Model):
|
|||||||
part = models.ForeignKey(Part, on_delete=models.CASCADE, related_name='bom_items',
|
part = models.ForeignKey(Part, on_delete=models.CASCADE, related_name='bom_items',
|
||||||
help_text='Select parent part',
|
help_text='Select parent part',
|
||||||
limit_choices_to={
|
limit_choices_to={
|
||||||
'buildable': True,
|
'assembly': True,
|
||||||
'active': True,
|
'active': True,
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -867,7 +879,7 @@ class BomItem(models.Model):
|
|||||||
sub_part = models.ForeignKey(Part, on_delete=models.CASCADE, related_name='used_in',
|
sub_part = models.ForeignKey(Part, on_delete=models.CASCADE, related_name='used_in',
|
||||||
help_text='Select part to be used in BOM',
|
help_text='Select part to be used in BOM',
|
||||||
limit_choices_to={
|
limit_choices_to={
|
||||||
'consumable': True,
|
'component': True,
|
||||||
'active': True
|
'active': True
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -94,8 +94,8 @@ class PartSerializer(serializers.ModelSerializer):
|
|||||||
# 'available_stock',
|
# 'available_stock',
|
||||||
'units',
|
'units',
|
||||||
'trackable',
|
'trackable',
|
||||||
'buildable',
|
'assembly',
|
||||||
'consumable',
|
'component',
|
||||||
'trackable',
|
'trackable',
|
||||||
'salable',
|
'salable',
|
||||||
'active',
|
'active',
|
||||||
|
@ -156,6 +156,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
},
|
},
|
||||||
buttons: ['#part-options'],
|
buttons: ['#part-options'],
|
||||||
|
checkbox: true,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -90,30 +90,6 @@
|
|||||||
<td><b>Units</b></td>
|
<td><b>Units</b></td>
|
||||||
<td>{{ part.units }}</td>
|
<td>{{ part.units }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
<div class='col-sm-6'>
|
|
||||||
<table class='table table-striped'>
|
|
||||||
<tr>
|
|
||||||
<td><b>Buildable</b></td>
|
|
||||||
<td>{% include "yesnolabel.html" with value=part.buildable %}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><b>Consumable</b></td>
|
|
||||||
<td>{% include "yesnolabel.html" with value=part.consumable %}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><b>Trackable</b></td>
|
|
||||||
<td>{% include "yesnolabel.html" with value=part.trackable %}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><b>Purchaseable</b></td>
|
|
||||||
<td>{% include "yesnolabel.html" with value=part.purchaseable %}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><b>Salable</b></td>
|
|
||||||
<td>{% include "yesnolabel.html" with value=part.salable %}</td>
|
|
||||||
</tr>
|
|
||||||
{% if part.minimum_stock > 0 %}
|
{% if part.minimum_stock > 0 %}
|
||||||
<tr>
|
<tr>
|
||||||
<td><b>Minimum Stock</b></td>
|
<td><b>Minimum Stock</b></td>
|
||||||
@ -122,6 +98,40 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
<div class='col-sm-6'>
|
||||||
|
<table class='table table-striped'>
|
||||||
|
{% if part.assembly %}
|
||||||
|
<tr>
|
||||||
|
<td><b>Assembly</b></td>
|
||||||
|
<td><i>This part can be assembled from other parts</i></td>
|
||||||
|
</tr>
|
||||||
|
{% endif %}
|
||||||
|
{% if part.component %}
|
||||||
|
<tr>
|
||||||
|
<td><b>Component</b></td>
|
||||||
|
<td><i>This part can be used in assemblies</i></td>
|
||||||
|
</tr>
|
||||||
|
{% endif %}
|
||||||
|
{% if part.trackable %}
|
||||||
|
<tr>
|
||||||
|
<td><b>Trackable</b></td>
|
||||||
|
<td><i>Stock for this part will be tracked by (serial or batch)</i></td>
|
||||||
|
</tr>
|
||||||
|
{% endif %}
|
||||||
|
{% if part.purchaseable %}
|
||||||
|
<tr>
|
||||||
|
<td><b>Purchaseable</b></td>
|
||||||
|
<td><i>This part can be purchased from external suppliers</i></td>
|
||||||
|
</tr>
|
||||||
|
{% endif %}
|
||||||
|
{% if part.salable %}
|
||||||
|
<tr>
|
||||||
|
<td><b>Salable</b></td>
|
||||||
|
<td><i>This part can be sold to customers</i></td>
|
||||||
|
</tr>
|
||||||
|
{% endif %}
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if part.notes %}
|
{% if part.notes %}
|
||||||
|
@ -78,7 +78,7 @@
|
|||||||
<td>In Stock</td>
|
<td>In Stock</td>
|
||||||
<td>{{ part.total_stock }}</td>
|
<td>{{ part.total_stock }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% if part.buildable %}
|
{% if part.assembly %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>Can Build</td>
|
<td>Can Build</td>
|
||||||
<td>{{ part.can_build }}</td>
|
<td>{{ part.can_build }}</td>
|
||||||
|
@ -15,13 +15,13 @@
|
|||||||
<a href="{% url 'part-allocation' part.id %}">Allocated <span class="badge">{{ part.allocation_count }}</span></a>
|
<a href="{% url 'part-allocation' part.id %}">Allocated <span class="badge">{{ part.allocation_count }}</span></a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if part.buildable %}
|
{% if part.assembly %}
|
||||||
<li{% ifequal tab 'bom' %} class="active"{% endifequal %}>
|
<li{% ifequal tab 'bom' %} class="active"{% endifequal %}>
|
||||||
<a href="{% url 'part-bom' part.id %}">BOM<span class="badge{% if part.is_bom_valid == False %} badge-alert{% endif %}">{{ part.bom_count }}</span></a></li>
|
<a href="{% url 'part-bom' part.id %}">BOM<span class="badge{% if part.is_bom_valid == False %} badge-alert{% endif %}">{{ part.bom_count }}</span></a></li>
|
||||||
<li{% ifequal tab 'build' %} class="active"{% endifequal %}>
|
<li{% ifequal tab 'build' %} class="active"{% endifequal %}>
|
||||||
<a href="{% url 'part-build' part.id %}">Build<span class='badge'>{{ part.active_builds|length }}</span></a></li>
|
<a href="{% url 'part-build' part.id %}">Build<span class='badge'>{{ part.active_builds|length }}</span></a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if part.consumable or part.used_in_count > 0 %}
|
{% if part.component or part.used_in_count > 0 %}
|
||||||
<li{% ifequal tab 'used' %} class="active"{% endifequal %}>
|
<li{% ifequal tab 'used' %} class="active"{% endifequal %}>
|
||||||
<a href="{% url 'part-used-in' part.id %}">Used In{% if part.used_in_count > 0 %}<span class="badge">{{ part.used_in_count }}</span>{% endif %}</a></li>
|
<a href="{% url 'part-used-in' part.id %}">Used In{% if part.used_in_count > 0 %}<span class="badge">{{ part.used_in_count }}</span>{% endif %}</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -149,7 +149,7 @@ class PartAPITest(APITestCase):
|
|||||||
response = self.client.post(url, data, format='json')
|
response = self.client.post(url, data, format='json')
|
||||||
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||||
|
|
||||||
# Now try to create a BomItem which points to a non-buildable part (should fail)
|
# Now try to create a BomItem which points to a non-assembly part (should fail)
|
||||||
data['part'] = 3
|
data['part'] = 3
|
||||||
response = self.client.post(url, data, format='json')
|
response = self.client.post(url, data, format='json')
|
||||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||||
|
@ -124,6 +124,10 @@ function imageHoverIcon(url) {
|
|||||||
* On mouseover, display a full-size version of the image
|
* On mouseover, display a full-size version of the image
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
if (!url) {
|
||||||
|
url = '/static/img/blank_image.png';
|
||||||
|
}
|
||||||
|
|
||||||
var html = `
|
var html = `
|
||||||
<a class='hover-icon'>
|
<a class='hover-icon'>
|
||||||
<img class='hover-img-thumb' src='` + url + `'>
|
<img class='hover-img-thumb' src='` + url + `'>
|
||||||
|
@ -82,6 +82,7 @@ function loadPartTable(table, url, options={}) {
|
|||||||
* - url: Base URL for API query
|
* - url: Base URL for API query
|
||||||
* - options: object containing following (optional) fields
|
* - options: object containing following (optional) fields
|
||||||
* allowInactive: If true, allow display of inactive parts
|
* allowInactive: If true, allow display of inactive parts
|
||||||
|
* checkbox: Show the checkbox column
|
||||||
* query: extra query params for API request
|
* query: extra query params for API request
|
||||||
* buttons: If provided, link buttons to selection status of this table
|
* buttons: If provided, link buttons to selection status of this table
|
||||||
*/
|
*/
|
||||||
@ -94,6 +95,84 @@ function loadPartTable(table, url, options={}) {
|
|||||||
query.active = true;
|
query.active = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var columns = [
|
||||||
|
{
|
||||||
|
field: 'pk',
|
||||||
|
title: 'ID',
|
||||||
|
visible: false,
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
if (options.checkbox) {
|
||||||
|
columns.push({
|
||||||
|
checkbox: true,
|
||||||
|
title: 'Select',
|
||||||
|
searchable: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
columns.push({
|
||||||
|
field: 'full_name',
|
||||||
|
title: 'Part',
|
||||||
|
sortable: true,
|
||||||
|
formatter: function(value, row, index, field) {
|
||||||
|
|
||||||
|
if (row.is_template) {
|
||||||
|
value = '<i>' + value + '</i>';
|
||||||
|
}
|
||||||
|
|
||||||
|
var display = imageHoverIcon(row.image_url) + renderLink(value, row.url);
|
||||||
|
|
||||||
|
if (!row.active) {
|
||||||
|
display = display + "<span class='label label-warning' style='float: right;'>INACTIVE</span>";
|
||||||
|
}
|
||||||
|
return display;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
columns.push({
|
||||||
|
sortable: true,
|
||||||
|
field: 'description',
|
||||||
|
title: 'Description',
|
||||||
|
formatter: function(value, row, index, field) {
|
||||||
|
|
||||||
|
if (row.is_template) {
|
||||||
|
value = '<i>' + value + '</i>';
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
columns.push({
|
||||||
|
sortable: true,
|
||||||
|
field: 'category_name',
|
||||||
|
title: 'Category',
|
||||||
|
formatter: function(value, row, index, field) {
|
||||||
|
if (row.category) {
|
||||||
|
return renderLink(row.category_name, "/part/category/" + row.category + "/");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
columns.push({
|
||||||
|
field: 'total_stock',
|
||||||
|
title: 'Stock',
|
||||||
|
searchable: false,
|
||||||
|
sortable: true,
|
||||||
|
formatter: function(value, row, index, field) {
|
||||||
|
if (value) {
|
||||||
|
return renderLink(value, row.url + 'stock/');
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return "<span class='label label-warning'>No Stock</span>";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
$(table).bootstrapTable({
|
$(table).bootstrapTable({
|
||||||
url: url,
|
url: url,
|
||||||
sortable: true,
|
sortable: true,
|
||||||
@ -107,76 +186,7 @@ function loadPartTable(table, url, options={}) {
|
|||||||
queryParams: function(p) {
|
queryParams: function(p) {
|
||||||
return query;
|
return query;
|
||||||
},
|
},
|
||||||
columns: [
|
columns: columns,
|
||||||
{
|
|
||||||
checkbox: true,
|
|
||||||
title: 'Select',
|
|
||||||
searchable: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
field: 'pk',
|
|
||||||
title: 'ID',
|
|
||||||
visible: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
field: 'full_name',
|
|
||||||
title: 'Part',
|
|
||||||
sortable: true,
|
|
||||||
formatter: function(value, row, index, field) {
|
|
||||||
|
|
||||||
if (row.is_template) {
|
|
||||||
value = '<i>' + value + '</i>';
|
|
||||||
}
|
|
||||||
|
|
||||||
var display = imageHoverIcon(row.image_url) + renderLink(value, row.url);
|
|
||||||
|
|
||||||
if (!row.active) {
|
|
||||||
display = display + "<span class='label label-warning' style='float: right;'>INACTIVE</span>";
|
|
||||||
}
|
|
||||||
return display;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
sortable: true,
|
|
||||||
field: 'description',
|
|
||||||
title: 'Description',
|
|
||||||
formatter: function(value, row, index, field) {
|
|
||||||
|
|
||||||
if (row.is_template) {
|
|
||||||
value = '<i>' + value + '</i>';
|
|
||||||
}
|
|
||||||
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
sortable: true,
|
|
||||||
field: 'category_name',
|
|
||||||
title: 'Category',
|
|
||||||
formatter: function(value, row, index, field) {
|
|
||||||
if (row.category) {
|
|
||||||
return renderLink(row.category_name, "/part/category/" + row.category + "/");
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
field: 'total_stock',
|
|
||||||
title: 'Stock',
|
|
||||||
searchable: false,
|
|
||||||
sortable: true,
|
|
||||||
formatter: function(value, row, index, field) {
|
|
||||||
if (value) {
|
|
||||||
return renderLink(value, row.url + 'stock/');
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return "<span class='label label-warning'>No Stock</span>";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (options.buttons) {
|
if (options.buttons) {
|
||||||
|
@ -153,7 +153,10 @@ function loadStockTable(table, options) {
|
|||||||
|
|
||||||
var text = renderLink(val, '/stock/item/' + row.pk + '/');
|
var text = renderLink(val, '/stock/item/' + row.pk + '/');
|
||||||
|
|
||||||
text = text + "<span class='badge'>" + row.status_text + "</span>";
|
if (row.status_text != 'OK') {
|
||||||
|
text = text + "<span class='badge'>" + row.status_text + "</span>";
|
||||||
|
}
|
||||||
|
|
||||||
return text;
|
return text;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -236,6 +236,11 @@ class StockLocationList(generics.ListCreateAPIView):
|
|||||||
'parent',
|
'parent',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
search_fields = [
|
||||||
|
'name',
|
||||||
|
'description',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class StockList(generics.ListCreateAPIView):
|
class StockList(generics.ListCreateAPIView):
|
||||||
""" API endpoint for list view of Stock objects
|
""" API endpoint for list view of Stock objects
|
||||||
@ -306,6 +311,8 @@ class StockList(generics.ListCreateAPIView):
|
|||||||
else:
|
else:
|
||||||
item['location__path'] = None
|
item['location__path'] = None
|
||||||
|
|
||||||
|
item['status_text'] = StockItem.ITEM_STATUS_CODES[item['status']]
|
||||||
|
|
||||||
return Response(data)
|
return Response(data)
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
|
19
InvenTree/stock/migrations/0005_auto_20190602_1944.py
Normal file
19
InvenTree/stock/migrations/0005_auto_20190602_1944.py
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
# Generated by Django 2.2 on 2019-06-02 09:44
|
||||||
|
|
||||||
|
import django.core.validators
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('stock', '0004_auto_20190525_2356'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='stockitem',
|
||||||
|
name='status',
|
||||||
|
field=models.PositiveIntegerField(choices=[(10, 'OK'), (50, 'Attention needed'), (55, 'Damaged'), (60, 'Destroyed'), (70, 'Lost')], default=10, validators=[django.core.validators.MinValueValidator(0)]),
|
||||||
|
),
|
||||||
|
]
|
@ -260,12 +260,14 @@ class StockItem(models.Model):
|
|||||||
ITEM_ATTENTION = 50
|
ITEM_ATTENTION = 50
|
||||||
ITEM_DAMAGED = 55
|
ITEM_DAMAGED = 55
|
||||||
ITEM_DESTROYED = 60
|
ITEM_DESTROYED = 60
|
||||||
|
ITEM_LOST = 70
|
||||||
|
|
||||||
ITEM_STATUS_CODES = {
|
ITEM_STATUS_CODES = {
|
||||||
ITEM_OK: _("OK"),
|
ITEM_OK: _("OK"),
|
||||||
ITEM_ATTENTION: _("Attention needed"),
|
ITEM_ATTENTION: _("Attention needed"),
|
||||||
ITEM_DAMAGED: _("Damaged"),
|
ITEM_DAMAGED: _("Damaged"),
|
||||||
ITEM_DESTROYED: _("Destroyed")
|
ITEM_DESTROYED: _("Destroyed"),
|
||||||
|
ITEM_LOST: _("Lost")
|
||||||
}
|
}
|
||||||
|
|
||||||
status = models.PositiveIntegerField(
|
status = models.PositiveIntegerField(
|
||||||
|
@ -17,10 +17,18 @@ InvenTree | Search Results
|
|||||||
<br><br>
|
<br><br>
|
||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
|
<div id='no-search-results'>
|
||||||
|
<h4><i>No results found</i></h4>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% include "InvenTree/search_part_category.html" with collapse_id="categories" %}
|
||||||
|
|
||||||
{% include "InvenTree/search_parts.html" with collapse_id='parts' %}
|
{% include "InvenTree/search_parts.html" with collapse_id='parts' %}
|
||||||
|
|
||||||
{% include "InvenTree/search_supplier_parts.html" with collapse_id='supplier_parts' %}
|
{% include "InvenTree/search_supplier_parts.html" with collapse_id='supplier_parts' %}
|
||||||
|
|
||||||
|
{% include "InvenTree/search_stock_location.html" with collapse_id="locations" %}
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block js_load %}
|
{% block js_load %}
|
||||||
@ -31,29 +39,86 @@ InvenTree | Search Results
|
|||||||
{% block js_ready %}
|
{% block js_ready %}
|
||||||
{{ block.super }}
|
{{ block.super }}
|
||||||
|
|
||||||
|
$(".panel-group").hide();
|
||||||
|
|
||||||
function onSearchResults(table, output) {
|
function onSearchResults(table, output) {
|
||||||
$(table).on('load-success.bs.table', function() {
|
$(table).on('load-success.bs.table', function() {
|
||||||
|
|
||||||
|
var panel = $(output).closest('.panel-group');
|
||||||
|
|
||||||
var n = $(table).bootstrapTable('getData').length;
|
var n = $(table).bootstrapTable('getData').length;
|
||||||
|
|
||||||
var text = '';
|
var text = '';
|
||||||
if (n == 0) {
|
if (n == 0) {
|
||||||
text = '<i>No results</i>'
|
text = '<i>No results</i>'
|
||||||
|
|
||||||
|
$(panel).hide();
|
||||||
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
text = n + ' result';
|
text = n + ' result';
|
||||||
|
|
||||||
if (n > 1) {
|
if (n > 1) {
|
||||||
text += 's';
|
text += 's';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$(panel).show();
|
||||||
|
|
||||||
|
$("#no-search-results").hide();
|
||||||
}
|
}
|
||||||
|
|
||||||
$(output).html(text);
|
$(output).html(text);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onSearchResults("#category-results-table", "#category-results-count");
|
||||||
|
|
||||||
|
onSearchResults("#location-results-table", "#location-results-count");
|
||||||
|
|
||||||
onSearchResults('#part-results-table', '#part-result-count');
|
onSearchResults('#part-results-table', '#part-result-count');
|
||||||
|
|
||||||
onSearchResults('#supplier-part-results-table', '#supplier-part-result-count');
|
onSearchResults('#supplier-part-results-table', '#supplier-part-result-count');
|
||||||
|
|
||||||
|
$("#category-results-table").bootstrapTable({
|
||||||
|
url: "{% url 'api-part-category-list' %}",
|
||||||
|
queryParams: {
|
||||||
|
search: "{{ query }}",
|
||||||
|
},
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
field: 'name',
|
||||||
|
title: 'Name',
|
||||||
|
formatter: function(value, row, index, field) {
|
||||||
|
return renderLink(value, '/part/category/' + row.pk + '/');
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'description',
|
||||||
|
title: 'Description',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#location-results-table").bootstrapTable({
|
||||||
|
url: "{% url 'api-location-list' %}",
|
||||||
|
queryParams: {
|
||||||
|
search: "{{ query }}",
|
||||||
|
},
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
field: 'name',
|
||||||
|
title: 'Name',
|
||||||
|
formatter: function(value, row, index, field) {
|
||||||
|
return renderLink(value, '/stock/location/' + row.pk + '/');
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'description',
|
||||||
|
title: 'Description',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
loadPartTable("#part-results-table",
|
loadPartTable("#part-results-table",
|
||||||
"{% url 'api-part-list' %}",
|
"{% url 'api-part-list' %}",
|
||||||
{
|
{
|
||||||
@ -61,6 +126,7 @@ InvenTree | Search Results
|
|||||||
search: "{{ query }}",
|
search: "{{ query }}",
|
||||||
},
|
},
|
||||||
allowInactive: true,
|
allowInactive: true,
|
||||||
|
checkbox: false,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
14
InvenTree/templates/InvenTree/search_part_category.html
Normal file
14
InvenTree/templates/InvenTree/search_part_category.html
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
{% extends "collapse.html" %}
|
||||||
|
|
||||||
|
{% block collapse_title %}
|
||||||
|
<h4>Part Categories</h4>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block collapse_heading %}
|
||||||
|
<h4><span id='category-results-count'>{% include "InvenTree/searching.html" %}</span></h4>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block collapse_content %}
|
||||||
|
<table class='table table-striped table-condensed' data-toolbar="#button-toolbar" id='category-results-table'>
|
||||||
|
</table>
|
||||||
|
{% endblock %}
|
14
InvenTree/templates/InvenTree/search_stock_location.html
Normal file
14
InvenTree/templates/InvenTree/search_stock_location.html
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
{% extends "collapse.html" %}
|
||||||
|
|
||||||
|
{% block collapse_title %}
|
||||||
|
<h4>Stock Locations</h4>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block collapse_heading %}
|
||||||
|
<h4><span id='location-results-count'>{% include "InvenTree/searching.html" %}</span></h4>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block collapse_content %}
|
||||||
|
<table class='table table-striped table-condensed' data-toolbar="#button-toolbar" id='location-results-table'>
|
||||||
|
</table>
|
||||||
|
{% endblock %}
|
@ -1 +1 @@
|
|||||||
<span class='glyphicon glyphicon-refresh glyphicon-refresh-animate'></span> Searching...
|
<span class='glyphicon glyphicon-refresh glyphicon-refresh-animate'></span> Searching
|
@ -3,5 +3,5 @@
|
|||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<input type="text" name='search' class="form-control" placeholder="Search"{% if query_text %} value="{{ query }}"{% endif %}>
|
<input type="text" name='search' class="form-control" placeholder="Search"{% if query_text %} value="{{ query }}"{% endif %}>
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" id='search-submit' class="btn btn-default">Submit</button>
|
<button type="submit" id='search-submit' class="btn btn-default">Search</button>
|
||||||
</form>
|
</form>
|
||||||
|
Loading…
Reference in New Issue
Block a user