mirror of
https://github.com/inventree/InvenTree.git
synced 2026-04-30 23:04:34 +00:00
Merge branch 'master' into scheduling
This commit is contained in:
@@ -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):
|
||||||
|
|||||||
+1148
-1088
File diff suppressed because it is too large
Load Diff
+1145
-1085
File diff suppressed because it is too large
Load Diff
+1148
-1088
File diff suppressed because it is too large
Load Diff
+1146
-1086
File diff suppressed because it is too large
Load Diff
+1145
-1085
File diff suppressed because it is too large
Load Diff
+2680
-2620
File diff suppressed because it is too large
Load Diff
+1145
-1085
File diff suppressed because it is too large
Load Diff
+1156
-1096
File diff suppressed because it is too large
Load Diff
+1145
-1085
File diff suppressed because it is too large
Load Diff
+1145
-1085
File diff suppressed because it is too large
Load Diff
+1146
-1086
File diff suppressed because it is too large
Load Diff
+1146
-1086
File diff suppressed because it is too large
Load Diff
+1147
-1087
File diff suppressed because it is too large
Load Diff
+1145
-1085
File diff suppressed because it is too large
Load Diff
+1146
-1086
File diff suppressed because it is too large
Load Diff
+1145
-1085
File diff suppressed because it is too large
Load Diff
+1145
-1085
File diff suppressed because it is too large
Load Diff
+1146
-1086
File diff suppressed because it is too large
Load Diff
+1145
-1085
File diff suppressed because it is too large
Load Diff
+1146
-1086
File diff suppressed because it is too large
Load Diff
@@ -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):
|
||||||
|
|||||||
@@ -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),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -475,6 +475,7 @@ function duplicateBom(part_id, options={}) {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
include_inherited: {},
|
include_inherited: {},
|
||||||
|
copy_substitutes: {},
|
||||||
remove_existing: {},
|
remove_existing: {},
|
||||||
skip_invalid: {},
|
skip_invalid: {},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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}'`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user