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:
Oliver Walters 2020-08-18 14:17:59 +10:00
parent ca4d3df287
commit 92ac93aac5
2 changed files with 47 additions and 17 deletions

View File

@ -271,6 +271,42 @@ class Part(MPTTModel):
def __str__(self):
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):
"""
Check if a serial number exists for this Part.
@ -1474,22 +1510,8 @@ class BomItem(models.Model):
except Part.DoesNotExist:
pass
# A part cannot refer to itself in its BOM
try:
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
# Check for circular BOM references
self.sub_part.checkAddToBOM(self.part)
class Meta:
verbose_name = _("BOM Item")

View File

@ -1325,8 +1325,16 @@ class BomUpload(FormView):
for row in self.bom_rows:
# 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')
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?
if row.get('quantity', None) is None: