Merge remote-tracking branch 'inventree/master'

This commit is contained in:
Oliver Walters 2019-05-25 22:11:21 +10:00
commit 026ffde807
9 changed files with 127 additions and 23 deletions

View File

@ -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)

View File

@ -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

View File

@ -85,6 +85,7 @@ class SupplierPartSerializer(serializers.ModelSerializer):
'supplier_logo', 'supplier_logo',
'SKU', 'SKU',
'manufacturer', 'manufacturer',
'description',
'MPN', 'MPN',
'URL', 'URL',
'pricing', 'pricing',

View File

@ -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:
# Test for simple recursion raise ValidationError({'sub_part': _('Part cannot be added to its own Bill of Materials')})
for item in self.sub_part.bom_items.all():
if self.part == item.sub_part: # Test for simple recursion
raise ValidationError({'sub_part': _("Part '{p1}' is used in BOM for '{p2}' (recursive)".format(p1=str(self.part), p2=str(self.sub_part)))}) for item in self.sub_part.bom_items.all():
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)))})
except Part.DoesNotExist:
# A blank Part will be caught elsewhere
pass
class Meta: class Meta:
verbose_name = "BOM Item" verbose_name = "BOM Item"

View File

@ -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>";
} }

View File

@ -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' %}",
@ -43,5 +63,39 @@ InvenTree | Search Results
allowInactive: true, allowInactive: true,
} }
); );
$("#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 %}

View 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 %}

View 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 %}

View File

@ -0,0 +1 @@
<span class='glyphicon glyphicon-refresh glyphicon-refresh-animate'></span> Searching...