mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
More intelligent checking for circular BOM
- Check all the way down a BOM "tree" - Validate BOM tree before allowing BOM submission
This commit is contained in:
parent
ca4d3df287
commit
92ac93aac5
@ -271,6 +271,42 @@ class Part(MPTTModel):
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "{n} - {d}".format(n=self.full_name, d=self.description)
|
return "{n} - {d}".format(n=self.full_name, d=self.description)
|
||||||
|
|
||||||
|
def checkAddToBOM(self, parent):
|
||||||
|
"""
|
||||||
|
Check if this Part can be added to the BOM of another part.
|
||||||
|
|
||||||
|
This will fail if:
|
||||||
|
|
||||||
|
a) The parent part is the same as this one
|
||||||
|
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
|
||||||
|
|
||||||
|
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)
|
||||||
|
))})
|
||||||
|
|
||||||
|
# Ensure that the parent part does not appear under any child BOM item!
|
||||||
|
for item in self.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
|
||||||
|
item.sub_part.checkAddToBOM(parent)
|
||||||
|
|
||||||
def checkIfSerialNumberExists(self, sn):
|
def checkIfSerialNumberExists(self, sn):
|
||||||
"""
|
"""
|
||||||
Check if a serial number exists for this Part.
|
Check if a serial number exists for this Part.
|
||||||
@ -1474,22 +1510,8 @@ class BomItem(models.Model):
|
|||||||
except Part.DoesNotExist:
|
except Part.DoesNotExist:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# A part cannot refer to itself in its BOM
|
# Check for circular BOM references
|
||||||
try:
|
self.sub_part.checkAddToBOM(self.part)
|
||||||
if self.sub_part is not None and self.part is not None:
|
|
||||||
if self.part == self.sub_part:
|
|
||||||
raise ValidationError({'sub_part': _('Part cannot be added to its own Bill of Materials')})
|
|
||||||
|
|
||||||
# TODO - Make sure that there is no recusion
|
|
||||||
|
|
||||||
# Test for simple recursion
|
|
||||||
for item in self.sub_part.bom_items.all():
|
|
||||||
if self.part == item.sub_part:
|
|
||||||
raise ValidationError({'sub_part': _("Part '{p1}' is used in BOM for '{p2}' (recursive)".format(p1=str(self.part), p2=str(self.sub_part)))})
|
|
||||||
|
|
||||||
except Part.DoesNotExist:
|
|
||||||
# A blank Part will be caught elsewhere
|
|
||||||
pass
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _("BOM Item")
|
verbose_name = _("BOM Item")
|
||||||
|
@ -1325,8 +1325,16 @@ class BomUpload(FormView):
|
|||||||
|
|
||||||
for row in self.bom_rows:
|
for row in self.bom_rows:
|
||||||
# Has a part been selected for the given row?
|
# Has a part been selected for the given row?
|
||||||
if row.get('part', None) is None:
|
part = row.get('part', None)
|
||||||
|
|
||||||
|
if part is None:
|
||||||
row['errors']['part'] = _('Select a part')
|
row['errors']['part'] = _('Select a part')
|
||||||
|
else:
|
||||||
|
# Will the selected part result in a recursive BOM?
|
||||||
|
try:
|
||||||
|
part.checkAddToBOM(self.part)
|
||||||
|
except ValidationError:
|
||||||
|
row['errors']['part'] = _('Selected part creates a circular BOM')
|
||||||
|
|
||||||
# Has a quantity been specified?
|
# Has a quantity been specified?
|
||||||
if row.get('quantity', None) is None:
|
if row.get('quantity', None) is None:
|
||||||
|
Loading…
Reference in New Issue
Block a user