diff --git a/InvenTree/build/models.py b/InvenTree/build/models.py
index 9a7b40b52f..8f74ba1c06 100644
--- a/InvenTree/build/models.py
+++ b/InvenTree/build/models.py
@@ -1153,16 +1153,12 @@ class BuildItem(models.Model):
i) The sub_part points to the same part as the referenced StockItem
ii) The BomItem allows variants and the part referenced by the StockItem
is a variant of the sub_part referenced by the BomItem
+ iii) The Part referenced by the StockItem is a valid substitute for the BomItem
"""
if self.build and self.build.part == self.bom_item.part:
- # Check that the sub_part points to the stock_item (either directly or via a variant)
- if self.bom_item.sub_part == self.stock_item.part:
- bom_item_valid = True
-
- elif self.bom_item.allow_variants and self.stock_item.part in self.bom_item.sub_part.get_descendants(include_self=False):
- bom_item_valid = True
+ bom_item_valid = self.bom_item.is_stock_item_valid(self.stock_item)
# If the existing BomItem is *not* valid, try to find a match
if not bom_item_valid:
diff --git a/InvenTree/build/templates/build/detail.html b/InvenTree/build/templates/build/detail.html
index 8fb259f8a4..0b109d1890 100644
--- a/InvenTree/build/templates/build/detail.html
+++ b/InvenTree/build/templates/build/detail.html
@@ -197,7 +197,7 @@
-
+
diff --git a/InvenTree/part/models.py b/InvenTree/part/models.py
index 7d0c87201c..fe3e017b28 100644
--- a/InvenTree/part/models.py
+++ b/InvenTree/part/models.py
@@ -2333,6 +2333,38 @@ class BomItem(models.Model):
def get_api_url():
return reverse('api-bom-list')
+ def get_valid_parts_for_allocation(self):
+ """
+ Return a list of valid parts which can be allocated against this BomItem:
+
+ - Include the referenced sub_part
+ - Include any directly specvified substitute parts
+ - If allow_variants is True, allow all variants of sub_part
+ """
+
+ # Set of parts we will allow
+ parts = set()
+
+ parts.add(self.sub_part)
+
+ # Variant parts (if allowed)
+ if self.allow_variants:
+ for variant in self.sub_part.get_descendants(include_self=False):
+ parts.add(variant)
+
+ # Substitute parts
+ for sub in self.substitutes.all():
+ parts.add(sub.part)
+
+ return parts
+
+ def is_stock_item_valid(self, stock_item):
+ """
+ Check if the provided StockItem object is "valid" for assignment against this BomItem
+ """
+
+ return stock_item.part in self.get_valid_parts_for_allocation()
+
def get_stock_filter(self):
"""
Return a queryset filter for selecting StockItems which match this BomItem
@@ -2342,24 +2374,7 @@ class BomItem(models.Model):
"""
- # List of parts we allow
- part_ids = set()
-
- part_ids.add(self.sub_part.pk)
-
- # Variant parts (if allowed)
- if self.allow_variants:
- variants = self.sub_part.get_descendants(include_self=False)
-
- for v in variants:
- part_ids.add(v.pk)
-
- # Direct substitute parts
- for sub in self.substitutes.all():
- part_ids.add(sub.part.pk)
-
- # Return a list of Part ID values which can be filtered against
- return Q(part__in=[pk for pk in part_ids])
+ return Q(part__in=[part.pk for part in self.get_valid_parts_for_allocation()])
def save(self, *args, **kwargs):
diff --git a/InvenTree/templates/js/translated/build.js b/InvenTree/templates/js/translated/build.js
index d3be81e4e3..b1e517186a 100644
--- a/InvenTree/templates/js/translated/build.js
+++ b/InvenTree/templates/js/translated/build.js
@@ -348,6 +348,17 @@ function loadBuildOutputAllocationTable(buildInfo, output, options={}) {
table = `#allocation-table-${outputId}`;
}
+ // Filters
+ var filters = loadTableFilters('builditems');
+
+ var params = options.params || {};
+
+ for (var key in params) {
+ filters[key] = params[key];
+ }
+
+ setupFilterList('builditems', $(table), options.filterTarget || null);
+
// If an "output" is specified, then only "trackable" parts are allocated
// Otherwise, only "untrackable" parts are allowed
var trackable = ! !output;
@@ -726,6 +737,10 @@ function loadBuildOutputAllocationTable(buildInfo, output, options={}) {
html += makePartIcons(row.sub_part_detail);
+ if (row.substitutes && row.substitutes.length > 0) {
+ html += makeIconBadge('fa-exchange-alt', '{% trans "Substitutes Available" %}');
+ }
+
return html;
}
},
@@ -1021,12 +1036,12 @@ function allocateStockToBuild(build_id, part_id, bom_items, options={}) {
filters: {
bom_item: bom_item.pk,
in_stock: true,
- part_detail: false,
+ part_detail: true,
location_detail: true,
},
model: 'stockitem',
required: true,
- render_part_detail: false,
+ render_part_detail: true,
render_location_detail: true,
auto_fill: true,
adjustFilters: function(filters) {
diff --git a/InvenTree/templates/js/translated/filters.js b/InvenTree/templates/js/translated/filters.js
index 3e41003696..8845fc39e1 100644
--- a/InvenTree/templates/js/translated/filters.js
+++ b/InvenTree/templates/js/translated/filters.js
@@ -283,18 +283,21 @@ function setupFilterList(tableKey, table, target) {
element.append(``);
- element.append(``);
+ // If there are available filters, add them in!
+ if (filters.length > 0) {
+ element.append(``);
- if (Object.keys(filters).length > 0) {
- element.append(``);
- }
+ if (Object.keys(filters).length > 0) {
+ element.append(``);
+ }
- for (var key in filters) {
- var value = getFilterOptionValue(tableKey, key, filters[key]);
- var title = getFilterTitle(tableKey, key);
- var description = getFilterDescription(tableKey, key);
+ for (var key in filters) {
+ var value = getFilterOptionValue(tableKey, key, filters[key]);
+ var title = getFilterTitle(tableKey, key);
+ var description = getFilterDescription(tableKey, key);
- element.append(`
${title} = ${value}x
`);
+ element.append(`
${title} = ${value}x
`);
+ }
}
// Callback for reloading the table
diff --git a/InvenTree/templates/js/translated/model_renderers.js b/InvenTree/templates/js/translated/model_renderers.js
index cf675ff4f7..0bb0818a70 100644
--- a/InvenTree/templates/js/translated/model_renderers.js
+++ b/InvenTree/templates/js/translated/model_renderers.js
@@ -52,8 +52,6 @@ function renderStockItem(name, data, parameters, options) {
if (data.part_detail) {
image = data.part_detail.thumbnail || data.part_detail.image || blankImage();
}
-
- var html = '';
var render_part_detail = true;
@@ -61,23 +59,10 @@ function renderStockItem(name, data, parameters, options) {
render_part_detail = parameters['render_part_detail'];
}
+ var part_detail = '';
+
if (render_part_detail) {
- html += ``;
- html += ` ${data.part_detail.full_name || data.part_detail.name}`;
- }
-
- html += '';
-
- if (data.serial && data.quantity == 1) {
- html += `{% trans "Serial Number" %}: ${data.serial}`;
- } else {
- html += `{% trans "Quantity" %}: ${data.quantity}`;
- }
-
- html += '';
-
- if (render_part_detail && data.part_detail.description) {
- html += `
${data.part_detail.description}
`;
+ part_detail = `${data.part_detail.full_name} - `;
}
var render_stock_id = true;
@@ -86,8 +71,10 @@ function renderStockItem(name, data, parameters, options) {
render_stock_id = parameters['render_stock_id'];
}
+ var stock_id = '';
+
if (render_stock_id) {
- html += `{% trans "Stock ID" %}: ${data.pk}`;
+ stock_id = `{% trans "Stock ID" %}: ${data.pk}`;
}
var render_location_detail = false;
@@ -96,10 +83,28 @@ function renderStockItem(name, data, parameters, options) {
render_location_detail = parameters['render_location_detail'];
}
+ var location_detail = '';
+
if (render_location_detail && data.location_detail) {
- html += ` - ${data.location_detail.name}`;
+ location_detail = ` - (${data.location_detail.name})`;
}
+ var stock_detail = '';
+
+ if (data.serial && data.quantity == 1) {
+ stock_detail = `{% trans "Serial Number" %}: ${data.serial}`;
+ } else if (data.quantity == 0) {
+ stock_detail = `{% trans "No Stock"% }`;
+ } else {
+ stock_detail = `{% trans "Quantity" %}: ${data.quantity}`;
+ }
+
+ var html = `
+
+ ${part_detail}${stock_detail}${location_detail}${stock_id}
+
+ `;
+
return html;
}
diff --git a/InvenTree/templates/js/translated/part.js b/InvenTree/templates/js/translated/part.js
index aba3c46330..037df8d607 100644
--- a/InvenTree/templates/js/translated/part.js
+++ b/InvenTree/templates/js/translated/part.js
@@ -592,7 +592,7 @@ function loadPartParameterTable(table, url, options) {
filters[key] = params[key];
}
- // setupFilterLsit("#part-parameters", $(table));
+ // setupFilterList("#part-parameters", $(table));
$(table).inventreeTable({
url: url,