2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-04-29 20:16:44 +00:00

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
This commit is contained in:
Oliver 2021-10-13 23:53:35 +11:00
parent c7cec13076
commit be5c5496b2
7 changed files with 91 additions and 57 deletions

View File

@ -1153,16 +1153,12 @@ class BuildItem(models.Model):
i) The sub_part points to the same part as the referenced StockItem 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 ii) The BomItem allows variants and the part referenced by the StockItem
is a variant of the sub_part referenced by the BomItem 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: 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) bom_item_valid = self.bom_item.is_stock_item_valid(self.stock_item)
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
# If the existing BomItem is *not* valid, try to find a match # If the existing BomItem is *not* valid, try to find a match
if not bom_item_valid: if not bom_item_valid:

View File

@ -197,7 +197,7 @@
<button id='allocate-selected-items' class='btn btn-success' title='{% trans "Allocate selected items" %}'> <button id='allocate-selected-items' class='btn btn-success' title='{% trans "Allocate selected items" %}'>
<span class='fas fa-sign-in-alt'></span> <span class='fas fa-sign-in-alt'></span>
</button> </button>
<div class='filter-list' id='filter-list-build-items'> <div class='filter-list' id='filter-list-builditems'>
<!-- Empty div for table filters--> <!-- Empty div for table filters-->
</div> </div>
</div> </div>

View File

@ -2333,6 +2333,38 @@ class BomItem(models.Model):
def get_api_url(): def get_api_url():
return reverse('api-bom-list') 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): def get_stock_filter(self):
""" """
Return a queryset filter for selecting StockItems which match this BomItem Return a queryset filter for selecting StockItems which match this BomItem
@ -2342,24 +2374,7 @@ class BomItem(models.Model):
""" """
# List of parts we allow return Q(part__in=[part.pk for part in self.get_valid_parts_for_allocation()])
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])
def save(self, *args, **kwargs): def save(self, *args, **kwargs):

View File

@ -348,6 +348,17 @@ function loadBuildOutputAllocationTable(buildInfo, output, options={}) {
table = `#allocation-table-${outputId}`; 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 // If an "output" is specified, then only "trackable" parts are allocated
// Otherwise, only "untrackable" parts are allowed // Otherwise, only "untrackable" parts are allowed
var trackable = ! !output; var trackable = ! !output;
@ -726,6 +737,10 @@ function loadBuildOutputAllocationTable(buildInfo, output, options={}) {
html += makePartIcons(row.sub_part_detail); html += makePartIcons(row.sub_part_detail);
if (row.substitutes && row.substitutes.length > 0) {
html += makeIconBadge('fa-exchange-alt', '{% trans "Substitutes Available" %}');
}
return html; return html;
} }
}, },
@ -1021,12 +1036,12 @@ function allocateStockToBuild(build_id, part_id, bom_items, options={}) {
filters: { filters: {
bom_item: bom_item.pk, bom_item: bom_item.pk,
in_stock: true, in_stock: true,
part_detail: false, part_detail: true,
location_detail: true, location_detail: true,
}, },
model: 'stockitem', model: 'stockitem',
required: true, required: true,
render_part_detail: false, render_part_detail: true,
render_location_detail: true, render_location_detail: true,
auto_fill: true, auto_fill: true,
adjustFilters: function(filters) { adjustFilters: function(filters) {

View File

@ -283,18 +283,21 @@ function setupFilterList(tableKey, table, target) {
element.append(`<button id='reload-${tableKey}' title='{% trans "Reload data" %}' class='btn btn-default filter-tag'><span class='fas fa-redo-alt'></span></button>`); element.append(`<button id='reload-${tableKey}' title='{% trans "Reload data" %}' class='btn btn-default filter-tag'><span class='fas fa-redo-alt'></span></button>`);
element.append(`<button id='${add}' title='{% trans "Add new filter" %}' class='btn btn-default filter-tag'><span class='fas fa-filter'></span></button>`); // If there are available filters, add them in!
if (filters.length > 0) {
element.append(`<button id='${add}' title='{% trans "Add new filter" %}' class='btn btn-default filter-tag'><span class='fas fa-filter'></span></button>`);
if (Object.keys(filters).length > 0) { if (Object.keys(filters).length > 0) {
element.append(`<button id='${clear}' title='{% trans "Clear all filters" %}' class='btn btn-default filter-tag'><span class='fas fa-trash-alt'></span></button>`); element.append(`<button id='${clear}' title='{% trans "Clear all filters" %}' class='btn btn-default filter-tag'><span class='fas fa-trash-alt'></span></button>`);
} }
for (var key in filters) { for (var key in filters) {
var value = getFilterOptionValue(tableKey, key, filters[key]); var value = getFilterOptionValue(tableKey, key, filters[key]);
var title = getFilterTitle(tableKey, key); var title = getFilterTitle(tableKey, key);
var description = getFilterDescription(tableKey, key); var description = getFilterDescription(tableKey, key);
element.append(`<div title='${description}' class='filter-tag'>${title} = ${value}<span ${tag}='${key}' class='close'>x</span></div>`); element.append(`<div title='${description}' class='filter-tag'>${title} = ${value}<span ${tag}='${key}' class='close'>x</span></div>`);
}
} }
// Callback for reloading the table // Callback for reloading the table

View File

@ -52,8 +52,6 @@ function renderStockItem(name, data, parameters, options) {
if (data.part_detail) { if (data.part_detail) {
image = data.part_detail.thumbnail || data.part_detail.image || blankImage(); image = data.part_detail.thumbnail || data.part_detail.image || blankImage();
} }
var html = '';
var render_part_detail = true; var render_part_detail = true;
@ -61,23 +59,10 @@ function renderStockItem(name, data, parameters, options) {
render_part_detail = parameters['render_part_detail']; render_part_detail = parameters['render_part_detail'];
} }
var part_detail = '';
if (render_part_detail) { if (render_part_detail) {
html += `<img src='${image}' class='select2-thumbnail'>`; part_detail = `<img src='${image}' class='select2-thumbnail'><span>${data.part_detail.full_name}</span> - `;
html += ` <span>${data.part_detail.full_name || data.part_detail.name}</span>`;
}
html += '<span>';
if (data.serial && data.quantity == 1) {
html += `{% trans "Serial Number" %}: ${data.serial}`;
} else {
html += `{% trans "Quantity" %}: ${data.quantity}`;
}
html += '</span>';
if (render_part_detail && data.part_detail.description) {
html += `<p><small>${data.part_detail.description}</small></p>`;
} }
var render_stock_id = true; var render_stock_id = true;
@ -86,8 +71,10 @@ function renderStockItem(name, data, parameters, options) {
render_stock_id = parameters['render_stock_id']; render_stock_id = parameters['render_stock_id'];
} }
var stock_id = '';
if (render_stock_id) { if (render_stock_id) {
html += `<span class='float-right'><small>{% trans "Stock ID" %}: ${data.pk}</small></span>`; stock_id = `<span class='float-right'><small>{% trans "Stock ID" %}: ${data.pk}</small></span>`;
} }
var render_location_detail = false; var render_location_detail = false;
@ -96,10 +83,28 @@ function renderStockItem(name, data, parameters, options) {
render_location_detail = parameters['render_location_detail']; render_location_detail = parameters['render_location_detail'];
} }
var location_detail = '';
if (render_location_detail && data.location_detail) { if (render_location_detail && data.location_detail) {
html += `<span> - ${data.location_detail.name}</span>`; location_detail = ` - (<em>${data.location_detail.name}</em>)`;
} }
var stock_detail = '';
if (data.serial && data.quantity == 1) {
stock_detail = `{% trans "Serial Number" %}: ${data.serial}`;
} else if (data.quantity == 0) {
stock_detail = `<span class='label-form label-red'>{% trans "No Stock"% }</span>`;
} else {
stock_detail = `{% trans "Quantity" %}: ${data.quantity}`;
}
var html = `
<span>
${part_detail}${stock_detail}${location_detail}${stock_id}
</span>
`;
return html; return html;
} }

View File

@ -592,7 +592,7 @@ function loadPartParameterTable(table, url, options) {
filters[key] = params[key]; filters[key] = params[key];
} }
// setupFilterLsit("#part-parameters", $(table)); // setupFilterList("#part-parameters", $(table));
$(table).inventreeTable({ $(table).inventreeTable({
url: url, url: url,