2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-05-01 13:06:45 +00:00

Merge branch 'master' into scheduling

This commit is contained in:
Oliver 2022-03-03 09:15:08 +11:00
commit 954f0afb85
31 changed files with 24552 additions and 23313 deletions

View File

@ -383,9 +383,7 @@ class Build(MPTTModel, ReferenceIndexingMixin):
Returns the BOM items for the part referenced by this BuildOrder Returns the BOM items for the part referenced by this BuildOrder
""" """
return self.part.bom_items.all().prefetch_related( return self.part.get_bom_items()
'sub_part'
)
@property @property
def tracked_bom_items(self): def tracked_bom_items(self):

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1453,7 +1453,9 @@ class Part(MPTTModel):
By default, will include inherited BOM items By default, will include inherited BOM items
""" """
return BomItem.objects.filter(self.get_bom_item_filter(include_inherited=include_inherited)) queryset = BomItem.objects.filter(self.get_bom_item_filter(include_inherited=include_inherited))
return queryset.prefetch_related('sub_part')
def get_installed_part_options(self, include_inherited=True, include_variants=True): def get_installed_part_options(self, include_inherited=True, include_variants=True):
""" """
@ -1906,6 +1908,9 @@ class Part(MPTTModel):
include_inherited = kwargs.get('include_inherited', False) include_inherited = kwargs.get('include_inherited', False)
# Should substitute parts be duplicated?
copy_substitutes = kwargs.get('copy_substitutes', True)
# Copy existing BOM items from another part # Copy existing BOM items from another part
# Note: Inherited BOM Items will *not* be duplicated!! # Note: Inherited BOM Items will *not* be duplicated!!
for bom_item in other.get_bom_items(include_inherited=include_inherited).all(): for bom_item in other.get_bom_items(include_inherited=include_inherited).all():
@ -1928,11 +1933,22 @@ class Part(MPTTModel):
if not bom_item.sub_part.check_add_to_bom(self, raise_error=raise_error): if not bom_item.sub_part.check_add_to_bom(self, raise_error=raise_error):
continue continue
# Obtain a list of direct substitute parts against this BomItem
substitutes = BomItemSubstitute.objects.filter(bom_item=bom_item)
# Construct a new BOM item # Construct a new BOM item
bom_item.part = self bom_item.part = self
bom_item.pk = None bom_item.pk = None
bom_item.save() bom_item.save()
bom_item.refresh_from_db()
if copy_substitutes:
for sub in substitutes:
# Duplicate the substitute (and point to the *new* BomItem object)
sub.pk = None
sub.bom_item = bom_item
sub.save()
@transaction.atomic @transaction.atomic
def copy_parameters_from(self, other, **kwargs): def copy_parameters_from(self, other, **kwargs):

View File

@ -656,6 +656,9 @@ class PartCopyBOMSerializer(serializers.Serializer):
fields = [ fields = [
'part', 'part',
'remove_existing', 'remove_existing',
'copy_substitutes',
'include_inherited',
'skip_invalid',
] ]
part = serializers.PrimaryKeyRelatedField( part = serializers.PrimaryKeyRelatedField(
@ -692,6 +695,12 @@ class PartCopyBOMSerializer(serializers.Serializer):
default=False, default=False,
) )
copy_substitutes = serializers.BooleanField(
label=_('Copy Substitute Parts'),
help_text=_('Copy substitute parts when duplicate BOM items'),
default=True,
)
def save(self): def save(self):
""" """
Actually duplicate the BOM Actually duplicate the BOM
@ -706,6 +715,7 @@ class PartCopyBOMSerializer(serializers.Serializer):
clear=data.get('remove_existing', True), clear=data.get('remove_existing', True),
skip_invalid=data.get('skip_invalid', False), skip_invalid=data.get('skip_invalid', False),
include_inherited=data.get('include_inherited', False), include_inherited=data.get('include_inherited', False),
copy_substitutes=data.get('copy_substitutes', True),
) )

View File

@ -137,7 +137,13 @@
<h4>{% trans "Sales Order Allocations" %}</h4> <h4>{% trans "Sales Order Allocations" %}</h4>
</div> </div>
<div class='panel-content'> <div class='panel-content'>
<table class='table table-striped table-condensed' id='sales-order-allocation-table'></table>
<div id='sales-order-allocation-button-toolbar'>
<div class='btn-group' role='group'>
{% include "filter_list.html" with id="salesorderallocation" %}
</div>
</div>
<table class='table table-striped table-condensed' id='sales-order-allocation-table' data-toolbar='#sales-order-allocation-button-toolbar'></table>
</div> </div>
</div> </div>
@ -357,7 +363,12 @@
<h4>{% trans "Build Order Allocations" %}</h4> <h4>{% trans "Build Order Allocations" %}</h4>
</div> </div>
<div class='panel-content'> <div class='panel-content'>
<table class='table table-striped table-condensed' id='build-order-allocation-table'></table> <div id='build-allocation-button-toolbar'>
<div class='btn-group' role='group'>
{% include "filter_list.html" with id="buildorderallocation" %}
</div>
</div>
<table class='table table-striped table-condensed' id='build-order-allocation-table' data-toolbar='#build-allocation-button-toolbar'></table>
</div> </div>
</div> </div>
@ -742,6 +753,7 @@
}); });
// Load the BOM table data in the pricing view // Load the BOM table data in the pricing view
{% if part.has_bom and roles.sales_order.view %}
loadBomTable($("#bom-pricing-table"), { loadBomTable($("#bom-pricing-table"), {
editable: false, editable: false,
bom_url: "{% url 'api-bom-list' %}", bom_url: "{% url 'api-bom-list' %}",
@ -749,6 +761,7 @@
parent_id: {{ part.id }} , parent_id: {{ part.id }} ,
sub_part_detail: true, sub_part_detail: true,
}); });
{% endif %}
onPanelLoad("purchase-orders", function() { onPanelLoad("purchase-orders", function() {
loadPartPurchaseOrderTable( loadPartPurchaseOrderTable(

View File

@ -59,13 +59,13 @@
<ul class='dropdown-menu'> <ul class='dropdown-menu'>
<li> <li>
<a class='dropdown-item' href='#' id='part-count'> <a class='dropdown-item' href='#' id='part-count'>
<span class='fas fa-clipboard-list'></span> <span class='fas fa-check-circle icon-green'></span>
{% trans "Count part stock" %} {% trans "Count part stock" %}
</a> </a>
</li> </li>
<li> <li>
<a class='dropdown-item' href='#' id='part-move'> <a class='dropdown-item' href='#' id='part-move'>
<span class='fas fa-exchange-alt'></span> <span class='fas fa-exchange-alt icon-blue'></span>
{% trans "Transfer part stock" %} {% trans "Transfer part stock" %}
</a> </a>
</li> </li>

View File

@ -909,7 +909,6 @@ class StockItem(MPTTModel):
""" Can this stock item be deleted? It can NOT be deleted under the following circumstances: """ Can this stock item be deleted? It can NOT be deleted under the following circumstances:
- Has installed stock items - Has installed stock items
- Has a serial number and is tracked
- Is installed inside another StockItem - Is installed inside another StockItem
- It has been assigned to a SalesOrder - It has been assigned to a SalesOrder
- It has been assigned to a BuildOrder - It has been assigned to a BuildOrder
@ -918,9 +917,6 @@ class StockItem(MPTTModel):
if self.installed_item_count() > 0: if self.installed_item_count() > 0:
return False return False
if self.part.trackable and self.serial is not None:
return False
if self.sales_order is not None: if self.sales_order is not None:
return False return False

View File

@ -66,7 +66,7 @@
<ul class='dropdown-menu' role='menu'> <ul class='dropdown-menu' role='menu'>
{% if not item.serialized %} {% if not item.serialized %}
{% if item.in_stock %} {% if item.in_stock %}
<li><a class='dropdown-item' href='#' id='stock-count' title='{% trans "Count stock" %}'><span class='fas fa-clipboard-list'></span> {% trans "Count stock" %}</a></li> <li><a class='dropdown-item' href='#' id='stock-count' title='{% trans "Count stock" %}'><span class='fas fa-check-circle icon-green'></span> {% trans "Count stock" %}</a></li>
{% endif %} {% endif %}
{% if not item.customer %} {% if not item.customer %}
<li><a class='dropdown-item' href='#' id='stock-add' title='{% trans "Add stock" %}'><span class='fas fa-plus-circle icon-green'></span> {% trans "Add stock" %}</a></li> <li><a class='dropdown-item' href='#' id='stock-add' title='{% trans "Add stock" %}'><span class='fas fa-plus-circle icon-green'></span> {% trans "Add stock" %}</a></li>

View File

@ -671,9 +671,7 @@ function loadBomTable(table, options={}) {
// Do we show part pricing in the BOM table? // Do we show part pricing in the BOM table?
var show_pricing = global_settings.PART_SHOW_PRICE_IN_BOM; var show_pricing = global_settings.PART_SHOW_PRICE_IN_BOM;
if (!show_pricing) { params.include_pricing = show_pricing == true;
params.include_pricing = false;
}
if (options.part_detail) { if (options.part_detail) {
params.part_detail = true; params.part_detail = true;
@ -989,32 +987,40 @@ function loadBomTable(table, options={}) {
// Function to request BOM data for sub-items // Function to request BOM data for sub-items
// This function may be called recursively for multi-level BOMs // This function may be called recursively for multi-level BOMs
function requestSubItems(bom_pk, part_pk) { function requestSubItems(bom_pk, part_pk, depth=0) {
// TODO: 2022-02-03 Currently, multi-level BOMs are not actually displayed. // Prevent multi-level recursion
const MAX_BOM_DEPTH = 25;
// Re-enable this function once multi-level display has been re-deployed if (depth >= MAX_BOM_DEPTH) {
console.log(`Maximum BOM depth (${MAX_BOM_DEPTH}) reached!`);
return; return;
}
inventreeGet( inventreeGet(
options.bom_url, options.bom_url,
{ {
part: part_pk, part: part_pk,
sub_part_detail: true, sub_part_detail: true,
include_pricing: show_pricing == true,
}, },
{ {
success: function(response) { success: function(response) {
// Add the returned sub-items to the table
for (var idx = 0; idx < response.length; idx++) { for (var idx = 0; idx < response.length; idx++) {
response[idx].parentId = bom_pk; response[idx].parentId = bom_pk;
if (response[idx].sub_part_detail.assembly) {
requestSubItems(response[idx].pk, response[idx].sub_part);
}
} }
table.bootstrapTable('append', response); table.bootstrapTable('append', response);
// Next, re-iterate and check if the new items also have sub items
response.forEach(function(bom_item) {
if (bom_item.sub_part_detail.assembly) {
requestSubItems(bom_item.pk, bom_item.sub_part, depth + 1);
}
});
table.treegrid('collapseAll'); table.treegrid('collapseAll');
}, },
error: function(xhr) { error: function(xhr) {
@ -1026,7 +1032,7 @@ function loadBomTable(table, options={}) {
} }
table.inventreeTable({ table.inventreeTable({
treeEnable: !options.editable, treeEnable: true,
rootParentId: parent_id, rootParentId: parent_id,
idField: 'pk', idField: 'pk',
uniqueId: 'pk', uniqueId: 'pk',
@ -1066,19 +1072,19 @@ function loadBomTable(table, options={}) {
url: options.bom_url, url: options.bom_url,
onPostBody: function() { onPostBody: function() {
if (!options.editable) {
table.treegrid({ table.treegrid({
treeColumn: 0, treeColumn: 1,
onExpand: function() { onExpand: function() {
} }
}); });
}
table.treegrid('collapseAll');
}, },
onLoadSuccess: function() { onLoadSuccess: function() {
if (options.editable) { if (options.editable) {
table.bootstrapTable('uncheckAll'); table.bootstrapTable('uncheckAll');
} else { }
var data = table.bootstrapTable('getData'); var data = table.bootstrapTable('getData');
@ -1099,7 +1105,6 @@ function loadBomTable(table, options={}) {
requestSubItems(row.pk, row.sub_part); requestSubItems(row.pk, row.sub_part);
} }
} }
}
}, },
}); });

View File

@ -475,6 +475,7 @@ function duplicateBom(part_id, options={}) {
} }
}, },
include_inherited: {}, include_inherited: {},
copy_substitutes: {},
remove_existing: {}, remove_existing: {},
skip_invalid: {}, skip_invalid: {},
}, },

View File

@ -278,7 +278,7 @@ $.fn.inventreeTable = function(options) {
} }
}); });
} else { } else {
console.log(`Could not get list of visible columns for column '${tableName}'`); console.log(`Could not get list of visible columns for table '${tableName}'`);
} }
} }

View File

@ -46,16 +46,16 @@
<ul class="dropdown-menu"> <ul class="dropdown-menu">
{% if roles.stock.change %} {% if roles.stock.change %}
<li><a class='dropdown-item' href="#" id='multi-item-add' title='{% trans "Add to selected stock items" %}'><span class='fas fa-plus-circle icon-green'></span> {% trans "Add stock" %}</a></li> <li><a class='dropdown-item' href="#" id='multi-item-add' title='{% trans "Add to selected stock items" %}'><span class='fas fa-plus-circle icon-green'></span> {% trans "Add stock" %}</a></li>
<li><a class='dropdown-item' href="#" id='multi-item-remove' title='{% trans "Remove from selected stock items" %}'><span class='fas fa-minus-circle'></span> {% trans "Remove stock" %}</a></li> <li><a class='dropdown-item' href="#" id='multi-item-remove' title='{% trans "Remove from selected stock items" %}'><span class='fas fa-minus-circle icon-red'></span> {% trans "Remove stock" %}</a></li>
<li><a class='dropdown-item' href="#" id='multi-item-stocktake' title='{% trans "Stocktake selected stock items" %}'><span class='fas fa-check-circle'></span> {% trans "Count stock" %}</a></li> <li><a class='dropdown-item' href="#" id='multi-item-stocktake' title='{% trans "Stocktake selected stock items" %}'><span class='fas fa-check-circle icon-green'></span> {% trans "Count stock" %}</a></li>
<li><a class='dropdown-item' href='#' id='multi-item-move' title='{% trans "Move selected stock items" %}'><span class='fas fa-exchange-alt'></span> {% trans "Move stock" %}</a></li> <li><a class='dropdown-item' href='#' id='multi-item-move' title='{% trans "Move selected stock items" %}'><span class='fas fa-exchange-alt icon-blue'></span> {% trans "Transfer stock" %}</a></li>
<li><a class='dropdown-item' href='#' id='multi-item-merge' title='{% trans "Merge selected stock items" %}'><span class='fas fa-object-group'></span> {% trans "Merge stock" %}</a></li> <li><a class='dropdown-item' href='#' id='multi-item-merge' title='{% trans "Merge selected stock items" %}'><span class='fas fa-object-group'></span> {% trans "Merge stock" %}</a></li>
<li><a class='dropdown-item' href='#' id='multi-item-order' title='{% trans "Order selected items" %}'><span class='fas fa-shopping-cart'></span> {% trans "Order stock" %}</a></li> <li><a class='dropdown-item' href='#' id='multi-item-order' title='{% trans "Order selected items" %}'><span class='fas fa-shopping-cart'></span> {% trans "Order stock" %}</a></li>
<li><a class='dropdown-item' href='#' id='multi-item-assign' title='{% trans "Assign to customer" %}'><span class='fas fa-user-tie'></span> {% trans "Assign to customer" %}</a></li> <li><a class='dropdown-item' href='#' id='multi-item-assign' title='{% trans "Assign to customer" %}'><span class='fas fa-user-tie'></span> {% trans "Assign to customer" %}</a></li>
<li><a class='dropdown-item' href='#' id='multi-item-set-status' title='{% trans "Change status" %}'><span class='fas fa-exclamation-circle'></span> {% trans "Change stock status" %}</a></li> <li><a class='dropdown-item' href='#' id='multi-item-set-status' title='{% trans "Change status" %}'><span class='fas fa-exclamation-circle'></span> {% trans "Change stock status" %}</a></li>
{% endif %} {% endif %}
{% if roles.stock.delete %} {% if roles.stock.delete %}
<li><a class='dropdown-item' href='#' id='multi-item-delete' title='{% trans "Delete selected items" %}'><span class='fas fa-trash-alt icon-red'></span> {% trans "Delete Stock" %}</a></li> <li><a class='dropdown-item' href='#' id='multi-item-delete' title='{% trans "Delete selected items" %}'><span class='fas fa-trash-alt icon-red'></span> {% trans "Delete stock" %}</a></li>
{% endif %} {% endif %}
</ul> </ul>
</div> </div>