diff --git a/InvenTree/build/models.py b/InvenTree/build/models.py index 92f04bdcc6..b5f5aa10eb 100644 --- a/InvenTree/build/models.py +++ b/InvenTree/build/models.py @@ -17,7 +17,7 @@ from django.db.models import Sum from django.core.validators import MinValueValidator from stock.models import StockItem -from part.models import BomItem +from part.models import Part, BomItem class Build(models.Model): @@ -368,15 +368,21 @@ class BuildItem(models.Model): 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(): 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: - errors['quantity'] = [_("Allocated quantity ({n}) must not exceed available quantity ({q})".format( - n=self.quantity, - q=self.stock_item.quantity - ))] + if self.quantity > self.stock_item.quantity: + errors['quantity'] = [_("Allocated quantity ({n}) must not exceed available quantity ({q})".format( + n=self.quantity, + q=self.stock_item.quantity + ))] + + except StockItem.DoesNotExist: + pass + + except Part.DoesNotExist: + pass if len(errors) > 0: raise ValidationError(errors) diff --git a/InvenTree/company/api.py b/InvenTree/company/api.py index c9e1aa7cdb..6e1e5c162d 100644 --- a/InvenTree/company/api.py +++ b/InvenTree/company/api.py @@ -113,6 +113,14 @@ class SupplierPartList(generics.ListCreateAPIView): 'supplier' ] + search_fields = [ + 'SKU', + 'supplier__name', + 'manufacturer', + 'description', + 'MPN', + ] + class SupplierPartDetail(generics.RetrieveUpdateDestroyAPIView): """ API endpoint for detail view of SupplierPart object diff --git a/InvenTree/company/serializers.py b/InvenTree/company/serializers.py index 775a273248..773e033b7f 100644 --- a/InvenTree/company/serializers.py +++ b/InvenTree/company/serializers.py @@ -85,6 +85,7 @@ class SupplierPartSerializer(serializers.ModelSerializer): 'supplier_logo', 'SKU', 'manufacturer', + 'description', 'MPN', 'URL', 'pricing', diff --git a/InvenTree/part/models.py b/InvenTree/part/models.py index 8383431caa..385bae7bf4 100644 --- a/InvenTree/part/models.py +++ b/InvenTree/part/models.py @@ -858,13 +858,19 @@ class BomItem(models.Model): """ # A part cannot refer to itself in its BOM - if self.part == self.sub_part: - raise ValidationError({'sub_part': _('Part cannot be added to its own Bill of Materials')}) - - # Test for simple recursion - 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)))}) + try: + 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 + 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: verbose_name = "BOM Item" diff --git a/InvenTree/static/script/inventree/bom.js b/InvenTree/static/script/inventree/bom.js index d81bff569a..b6e0c63a70 100644 --- a/InvenTree/static/script/inventree/bom.js +++ b/InvenTree/static/script/inventree/bom.js @@ -137,14 +137,14 @@ function loadBomTable(table, options) { if (!options.editable) { cols.push( { - field: 'sub_part_detail.available_stock', + field: 'sub_part_detail.total_stock', title: 'Available', searchable: false, sortable: true, formatter: function(value, row, index, field) { var text = ""; - if (row.quantity < row.sub_part_detail.available_stock) + if (row.quantity < row.sub_part_detail.total_stock) { text = "" + value + ""; } diff --git a/InvenTree/templates/InvenTree/search.html b/InvenTree/templates/InvenTree/search.html index 77730f5ea5..f8631dfbd4 100644 --- a/InvenTree/templates/InvenTree/search.html +++ b/InvenTree/templates/InvenTree/search.html @@ -15,9 +15,11 @@ InvenTree | Search Results

-

Parts

- -
+
+ +{% include "InvenTree/search_parts.html" with collapse_id='parts' %} + +{% include "InvenTree/search_supplier_parts.html" with collapse_id='supplier_parts' %} {% endblock %} @@ -29,10 +31,28 @@ InvenTree | Search Results {% block js_ready %} {{ block.super }} - $("#part-results-table").on('load-success.bs.table', function() { - var n = $("#part-results-table").bootstrapTable('getData').length; - $("#part-result-count").html("(found " + n + " results)"); - }); + function onSearchResults(table, output) { + $(table).on('load-success.bs.table', function() { + var n = $(table).bootstrapTable('getData').length; + + var text = ''; + if (n == 0) { + text = 'No results' + } 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", "{% url 'api-part-list' %}", @@ -43,5 +63,39 @@ InvenTree | Search Results 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 %} \ No newline at end of file diff --git a/InvenTree/templates/InvenTree/search_parts.html b/InvenTree/templates/InvenTree/search_parts.html new file mode 100644 index 0000000000..69c5c29051 --- /dev/null +++ b/InvenTree/templates/InvenTree/search_parts.html @@ -0,0 +1,14 @@ +{% extends "collapse.html" %} + +{% block collapse_title %} +

Parts

+{% endblock %} + +{% block collapse_heading %} +

{% include "InvenTree/searching.html" %}

+{% endblock %} + +{% block collapse_content %} + +
+{% endblock %} \ No newline at end of file diff --git a/InvenTree/templates/InvenTree/search_supplier_parts.html b/InvenTree/templates/InvenTree/search_supplier_parts.html new file mode 100644 index 0000000000..04ee32cc5b --- /dev/null +++ b/InvenTree/templates/InvenTree/search_supplier_parts.html @@ -0,0 +1,14 @@ +{% extends "collapse.html" %} + +{% block collapse_title %} +

Supplier Parts

+{% endblock %} + +{% block collapse_heading %} +

{% include "InvenTree/searching.html" %}

+{% endblock %} + +{% block collapse_content %} + +
+{% endblock %} \ No newline at end of file diff --git a/InvenTree/templates/InvenTree/searching.html b/InvenTree/templates/InvenTree/searching.html new file mode 100644 index 0000000000..9d111fe257 --- /dev/null +++ b/InvenTree/templates/InvenTree/searching.html @@ -0,0 +1 @@ + Searching... \ No newline at end of file