mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Furher logic improvements to BOM copy
- Remove "self" part from list - Stop inherited BOM items from being copied incorrectly - Allow user to select whether "inherited" BOM items are copied
This commit is contained in:
parent
0c8a047bc2
commit
70f9a0fe13
@ -481,7 +481,7 @@ class Part(MPTTModel):
|
||||
def __str__(self):
|
||||
return f"{self.full_name} - {self.description}"
|
||||
|
||||
def checkAddToBOM(self, parent):
|
||||
def check_add_to_bom(self, parent, raise_error=False, recursive=True):
|
||||
"""
|
||||
Check if this Part can be added to the BOM of another part.
|
||||
|
||||
@ -491,33 +491,44 @@ class Part(MPTTModel):
|
||||
b) The parent part is used in the BOM for *this* part
|
||||
c) The parent part is used in the BOM for any child parts under this one
|
||||
|
||||
Failing this check raises a ValidationError!
|
||||
|
||||
"""
|
||||
|
||||
if parent is None:
|
||||
return
|
||||
result = True
|
||||
|
||||
if self.pk == parent.pk:
|
||||
raise ValidationError({'sub_part': _("Part '{p1}' is used in BOM for '{p2}' (recursive)").format(
|
||||
p1=str(self),
|
||||
p2=str(parent)
|
||||
)})
|
||||
|
||||
bom_items = self.get_bom_items()
|
||||
|
||||
# Ensure that the parent part does not appear under any child BOM item!
|
||||
for item in bom_items.all():
|
||||
|
||||
# Check for simple match
|
||||
if item.sub_part == parent:
|
||||
try:
|
||||
if self.pk == parent.pk:
|
||||
raise ValidationError({'sub_part': _("Part '{p1}' is used in BOM for '{p2}' (recursive)").format(
|
||||
p1=str(parent),
|
||||
p2=str(self)
|
||||
p1=str(self),
|
||||
p2=str(parent)
|
||||
)})
|
||||
|
||||
# And recursively check too
|
||||
item.sub_part.checkAddToBOM(parent)
|
||||
bom_items = self.get_bom_items()
|
||||
|
||||
# Ensure that the parent part does not appear under any child BOM item!
|
||||
for item in bom_items.all():
|
||||
|
||||
# Check for simple match
|
||||
if item.sub_part == parent:
|
||||
raise ValidationError({'sub_part': _("Part '{p1}' is used in BOM for '{p2}' (recursive)").format(
|
||||
p1=str(parent),
|
||||
p2=str(self)
|
||||
)})
|
||||
|
||||
# And recursively check too
|
||||
if recursive:
|
||||
result = result and item.sub_part.check_add_to_bom(
|
||||
parent,
|
||||
recursive=True,
|
||||
raise_error=raise_error
|
||||
)
|
||||
|
||||
except ValidationError as e:
|
||||
if raise_error:
|
||||
raise e
|
||||
else:
|
||||
return False
|
||||
|
||||
return result
|
||||
|
||||
def checkIfSerialNumberExists(self, sn, exclude_self=False):
|
||||
"""
|
||||
@ -1816,23 +1827,45 @@ class Part(MPTTModel):
|
||||
clear - Remove existing BOM items first (default=True)
|
||||
"""
|
||||
|
||||
# Ignore if the other part is actually this part?
|
||||
if other == self:
|
||||
return
|
||||
|
||||
if clear:
|
||||
# Remove existing BOM items
|
||||
# Note: Inherited BOM items are *not* deleted!
|
||||
self.bom_items.all().delete()
|
||||
|
||||
# List of "ancestor" parts above this one
|
||||
my_ancestors = self.get_ancestors(include_self=False)
|
||||
|
||||
raise_error = not kwargs.get('skip_invalid', True)
|
||||
|
||||
include_inherited = kwargs.get('include_inherited', False)
|
||||
|
||||
# Copy existing BOM items from another part
|
||||
# Note: Inherited BOM Items will *not* be duplicated!!
|
||||
for bom_item in other.get_bom_items(include_inherited=False).all():
|
||||
for bom_item in other.get_bom_items(include_inherited=include_inherited).all():
|
||||
# If this part already has a BomItem pointing to the same sub-part,
|
||||
# delete that BomItem from this part first!
|
||||
|
||||
try:
|
||||
existing = BomItem.objects.get(part=self, sub_part=bom_item.sub_part)
|
||||
existing.delete()
|
||||
except (BomItem.DoesNotExist):
|
||||
pass
|
||||
# Ignore invalid BomItem objects
|
||||
if not bom_item.part or not bom_item.sub_part:
|
||||
continue
|
||||
|
||||
# Ignore ancestor parts which are inherited
|
||||
if bom_item.part in my_ancestors and bom_item.inherited:
|
||||
continue
|
||||
|
||||
# Skip if already exists
|
||||
if BomItem.objects.filter(part=self, sub_part=bom_item.sub_part).exists():
|
||||
continue
|
||||
|
||||
# Skip (or throw error) if BomItem is not valid
|
||||
if not bom_item.sub_part.check_add_to_bom(self, raise_error=raise_error):
|
||||
continue
|
||||
|
||||
# Construct a new BOM item
|
||||
bom_item.part = self
|
||||
bom_item.pk = None
|
||||
|
||||
@ -2677,7 +2710,7 @@ class BomItem(models.Model):
|
||||
try:
|
||||
# Check for circular BOM references
|
||||
if self.sub_part:
|
||||
self.sub_part.checkAddToBOM(self.part)
|
||||
self.sub_part.check_add_to_bom(self.part, raise_error=True)
|
||||
|
||||
# If the sub_part is 'trackable' then the 'quantity' field must be an integer
|
||||
if self.sub_part.trackable:
|
||||
|
@ -664,14 +664,24 @@ class PartCopyBOMSerializer(serializers.Serializer):
|
||||
Check that a 'valid' part was selected
|
||||
"""
|
||||
|
||||
# Check if the BOM can be copied from the provided part
|
||||
base_part = self.context['part']
|
||||
|
||||
return part
|
||||
|
||||
remove_existing = serializers.BooleanField(
|
||||
label=_('Remove Existing Data'),
|
||||
help_text=_('Remove existing BOM items before copying')
|
||||
help_text=_('Remove existing BOM items before copying'),
|
||||
default=True,
|
||||
)
|
||||
|
||||
include_inherited = serializers.BooleanField(
|
||||
label=_('Include Inherited'),
|
||||
help_text=_('Include BOM items which are inherited from templated parts'),
|
||||
default=False,
|
||||
)
|
||||
|
||||
skip_invalid = serializers.BooleanField(
|
||||
label=_('Skip Invalid Rows'),
|
||||
help_text=_('Enable this option to skip invalid rows'),
|
||||
default=False,
|
||||
)
|
||||
|
||||
def save(self):
|
||||
@ -683,7 +693,9 @@ class PartCopyBOMSerializer(serializers.Serializer):
|
||||
|
||||
data = self.validated_data
|
||||
|
||||
part = data['part']
|
||||
clear = data.get('remove_existing', True)
|
||||
|
||||
base_part.copy_bom_from(part, clear=clear)
|
||||
base_part.copy_bom_from(
|
||||
data['part'],
|
||||
clear=data.get('remove_existing', True),
|
||||
skip_invalid=data.get('skip_invalid', False),
|
||||
include_inherited=data.get('include_inherited', False),
|
||||
)
|
||||
|
@ -582,7 +582,9 @@
|
||||
$('#bom-duplicate').click(function() {
|
||||
|
||||
duplicateBom({{ part.pk }}, {
|
||||
|
||||
success: function(response) {
|
||||
$('#bom-table').bootstrapTable('refresh');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -661,7 +661,7 @@ function loadBomTable(table, options={}) {
|
||||
if (!row.inherited) {
|
||||
return yesNoLabel(false);
|
||||
} else if (row.part == options.parent_id) {
|
||||
return '{% trans "Inherited" %}';
|
||||
return yesNoLabel(true);
|
||||
} else {
|
||||
// If this BOM item is inherited from a parent part
|
||||
return renderLink(
|
||||
|
@ -438,15 +438,20 @@ function duplicateBom(part_id, options={}) {
|
||||
icon: 'fa-shapes',
|
||||
filters: {
|
||||
assembly: true,
|
||||
ancestor: part_id,
|
||||
exclude_tree: part_id,
|
||||
}
|
||||
},
|
||||
remove_existing: {
|
||||
value: true,
|
||||
},
|
||||
include_inherited: {},
|
||||
remove_existing: {},
|
||||
skip_invalid: {},
|
||||
},
|
||||
confirm: true,
|
||||
title: '{% trans "Copy Bill of Materials" %}',
|
||||
onSuccess: function(response) {
|
||||
if (options.success) {
|
||||
options.success(response);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user