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
026ffde807
@ -17,7 +17,7 @@ from django.db.models import Sum
|
|||||||
from django.core.validators import MinValueValidator
|
from django.core.validators import MinValueValidator
|
||||||
|
|
||||||
from stock.models import StockItem
|
from stock.models import StockItem
|
||||||
from part.models import BomItem
|
from part.models import Part, BomItem
|
||||||
|
|
||||||
|
|
||||||
class Build(models.Model):
|
class Build(models.Model):
|
||||||
@ -368,15 +368,21 @@ class BuildItem(models.Model):
|
|||||||
|
|
||||||
errors = {}
|
errors = {}
|
||||||
|
|
||||||
if self.stock_item is not None and self.stock_item.part is not None:
|
try:
|
||||||
if self.stock_item.part not in self.build.part.required_parts():
|
if self.stock_item.part not in self.build.part.required_parts():
|
||||||
errors['stock_item'] = [_("Selected stock item not found in BOM for part '{p}'".format(p=self.build.part.full_name))]
|
errors['stock_item'] = [_("Selected stock item not found in BOM for part '{p}'".format(p=self.build.part.full_name))]
|
||||||
|
|
||||||
if self.stock_item is not None and self.quantity > self.stock_item.quantity:
|
if self.quantity > self.stock_item.quantity:
|
||||||
errors['quantity'] = [_("Allocated quantity ({n}) must not exceed available quantity ({q})".format(
|
errors['quantity'] = [_("Allocated quantity ({n}) must not exceed available quantity ({q})".format(
|
||||||
n=self.quantity,
|
n=self.quantity,
|
||||||
q=self.stock_item.quantity
|
q=self.stock_item.quantity
|
||||||
))]
|
))]
|
||||||
|
|
||||||
|
except StockItem.DoesNotExist:
|
||||||
|
pass
|
||||||
|
|
||||||
|
except Part.DoesNotExist:
|
||||||
|
pass
|
||||||
|
|
||||||
if len(errors) > 0:
|
if len(errors) > 0:
|
||||||
raise ValidationError(errors)
|
raise ValidationError(errors)
|
||||||
|
@ -113,6 +113,14 @@ class SupplierPartList(generics.ListCreateAPIView):
|
|||||||
'supplier'
|
'supplier'
|
||||||
]
|
]
|
||||||
|
|
||||||
|
search_fields = [
|
||||||
|
'SKU',
|
||||||
|
'supplier__name',
|
||||||
|
'manufacturer',
|
||||||
|
'description',
|
||||||
|
'MPN',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class SupplierPartDetail(generics.RetrieveUpdateDestroyAPIView):
|
class SupplierPartDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||||
""" API endpoint for detail view of SupplierPart object
|
""" API endpoint for detail view of SupplierPart object
|
||||||
|
@ -85,6 +85,7 @@ class SupplierPartSerializer(serializers.ModelSerializer):
|
|||||||
'supplier_logo',
|
'supplier_logo',
|
||||||
'SKU',
|
'SKU',
|
||||||
'manufacturer',
|
'manufacturer',
|
||||||
|
'description',
|
||||||
'MPN',
|
'MPN',
|
||||||
'URL',
|
'URL',
|
||||||
'pricing',
|
'pricing',
|
||||||
|
@ -858,13 +858,19 @@ class BomItem(models.Model):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
# A part cannot refer to itself in its BOM
|
# A part cannot refer to itself in its BOM
|
||||||
if self.part == self.sub_part:
|
try:
|
||||||
raise ValidationError({'sub_part': _('Part cannot be added to its own Bill of Materials')})
|
if self.sub_part is not None and self.part is not None:
|
||||||
|
if self.part == self.sub_part:
|
||||||
|
raise ValidationError({'sub_part': _('Part cannot be added to its own Bill of Materials')})
|
||||||
|
|
||||||
# Test for simple recursion
|
# Test for simple recursion
|
||||||
for item in self.sub_part.bom_items.all():
|
for item in self.sub_part.bom_items.all():
|
||||||
if self.part == item.sub_part:
|
if self.part == item.sub_part:
|
||||||
raise ValidationError({'sub_part': _("Part '{p1}' is used in BOM for '{p2}' (recursive)".format(p1=str(self.part), p2=str(self.sub_part)))})
|
raise ValidationError({'sub_part': _("Part '{p1}' is used in BOM for '{p2}' (recursive)".format(p1=str(self.part), p2=str(self.sub_part)))})
|
||||||
|
|
||||||
|
except Part.DoesNotExist:
|
||||||
|
# A blank Part will be caught elsewhere
|
||||||
|
pass
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = "BOM Item"
|
verbose_name = "BOM Item"
|
||||||
|
@ -137,14 +137,14 @@ function loadBomTable(table, options) {
|
|||||||
if (!options.editable) {
|
if (!options.editable) {
|
||||||
cols.push(
|
cols.push(
|
||||||
{
|
{
|
||||||
field: 'sub_part_detail.available_stock',
|
field: 'sub_part_detail.total_stock',
|
||||||
title: 'Available',
|
title: 'Available',
|
||||||
searchable: false,
|
searchable: false,
|
||||||
sortable: true,
|
sortable: true,
|
||||||
formatter: function(value, row, index, field) {
|
formatter: function(value, row, index, field) {
|
||||||
var text = "";
|
var text = "";
|
||||||
|
|
||||||
if (row.quantity < row.sub_part_detail.available_stock)
|
if (row.quantity < row.sub_part_detail.total_stock)
|
||||||
{
|
{
|
||||||
text = "<span class='label label-success'>" + value + "</span>";
|
text = "<span class='label label-success'>" + value + "</span>";
|
||||||
}
|
}
|
||||||
|
@ -15,9 +15,11 @@ InvenTree | Search Results
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<br><br>
|
<br><br>
|
||||||
<h3>Parts <span id='part-result-count'></span></h3>
|
<hr>
|
||||||
<table class='table table-striped table-condensed' data-toolbar="#button-toolbar" id='part-results-table'>
|
|
||||||
</table>
|
{% include "InvenTree/search_parts.html" with collapse_id='parts' %}
|
||||||
|
|
||||||
|
{% include "InvenTree/search_supplier_parts.html" with collapse_id='supplier_parts' %}
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
@ -29,10 +31,28 @@ InvenTree | Search Results
|
|||||||
{% block js_ready %}
|
{% block js_ready %}
|
||||||
{{ block.super }}
|
{{ block.super }}
|
||||||
|
|
||||||
$("#part-results-table").on('load-success.bs.table', function() {
|
function onSearchResults(table, output) {
|
||||||
var n = $("#part-results-table").bootstrapTable('getData').length;
|
$(table).on('load-success.bs.table', function() {
|
||||||
$("#part-result-count").html("(found " + n + " results)");
|
var n = $(table).bootstrapTable('getData').length;
|
||||||
});
|
|
||||||
|
var text = '';
|
||||||
|
if (n == 0) {
|
||||||
|
text = '<i>No results</i>'
|
||||||
|
} else {
|
||||||
|
text = n + ' result';
|
||||||
|
|
||||||
|
if (n > 1) {
|
||||||
|
text += 's';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$(output).html(text);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onSearchResults('#part-results-table', '#part-result-count');
|
||||||
|
|
||||||
|
onSearchResults('#supplier-part-results-table', '#supplier-part-result-count');
|
||||||
|
|
||||||
loadPartTable("#part-results-table",
|
loadPartTable("#part-results-table",
|
||||||
"{% url 'api-part-list' %}",
|
"{% url 'api-part-list' %}",
|
||||||
@ -44,4 +64,38 @@ InvenTree | Search Results
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
$("#supplier-part-results-table").bootstrapTable({
|
||||||
|
url: "{% url 'api-part-supplier-list' %}",
|
||||||
|
queryParams: {
|
||||||
|
search: "{{ query }}",
|
||||||
|
},
|
||||||
|
pagination: true,
|
||||||
|
pageSize: 25,
|
||||||
|
search: true,
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
field: 'supplier_name',
|
||||||
|
title: 'Supplier',
|
||||||
|
formatter: function(value, row, index, field) {
|
||||||
|
return imageHoverIcon(row.supplier_logo) + renderLink(value, '/company/' + row.supplier + '/');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'SKU',
|
||||||
|
title: 'SKU',
|
||||||
|
formatter: function(value, row, index, field) {
|
||||||
|
return renderLink(value, row.url);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'manufacturer',
|
||||||
|
title: 'Manufacturer',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'MPN',
|
||||||
|
title: 'MPN',
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
14
InvenTree/templates/InvenTree/search_parts.html
Normal file
14
InvenTree/templates/InvenTree/search_parts.html
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
{% extends "collapse.html" %}
|
||||||
|
|
||||||
|
{% block collapse_title %}
|
||||||
|
<h4>Parts</h4>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block collapse_heading %}
|
||||||
|
<h4><span id='part-result-count'>{% include "InvenTree/searching.html" %}</span></h4>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block collapse_content %}
|
||||||
|
<table class='table table-striped table-condensed' data-toolbar="#button-toolbar" id='part-results-table'>
|
||||||
|
</table>
|
||||||
|
{% endblock %}
|
14
InvenTree/templates/InvenTree/search_supplier_parts.html
Normal file
14
InvenTree/templates/InvenTree/search_supplier_parts.html
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
{% extends "collapse.html" %}
|
||||||
|
|
||||||
|
{% block collapse_title %}
|
||||||
|
<h4>Supplier Parts</h4>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block collapse_heading %}
|
||||||
|
<h4><span id='supplier-part-result-count'>{% include "InvenTree/searching.html" %}</span></h4>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block collapse_content %}
|
||||||
|
<table class='table table-striped table-condensed' data-toolbar="#button-toolbar" id='supplier-part-results-table'>
|
||||||
|
</table>
|
||||||
|
{% endblock %}
|
1
InvenTree/templates/InvenTree/searching.html
Normal file
1
InvenTree/templates/InvenTree/searching.html
Normal file
@ -0,0 +1 @@
|
|||||||
|
<span class='glyphicon glyphicon-refresh glyphicon-refresh-animate'></span> Searching...
|
Loading…
Reference in New Issue
Block a user