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:
parent
c7cec13076
commit
be5c5496b2
@ -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:
|
||||||
|
@ -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>
|
||||||
|
@ -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):
|
||||||
|
|
||||||
|
@ -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) {
|
||||||
|
@ -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
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user