From be5c5496b2ea4983c3869a8226e1cbb6ef35ebb8 Mon Sep 17 00:00:00 2001 From: Oliver Date: Wed, 13 Oct 2021 23:53:35 +1100 Subject: [PATCH] Improvements to allocation of stock items against build orders - Refactor functions for filtering stock using bom_item pk - Allow selection of substitute items when allocating against build order - Improvements for modal rendering - Don't display filter drop-down if there are no filters available --- InvenTree/build/models.py | 8 +-- InvenTree/build/templates/build/detail.html | 2 +- InvenTree/part/models.py | 51 ++++++++++++------- InvenTree/templates/js/translated/build.js | 19 ++++++- InvenTree/templates/js/translated/filters.js | 21 ++++---- .../js/translated/model_renderers.js | 45 ++++++++-------- InvenTree/templates/js/translated/part.js | 2 +- 7 files changed, 91 insertions(+), 57 deletions(-) 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,