Refactor bom item filter

- Also updates a number of part functions to make use of inherited BOM items
This commit is contained in:
Oliver Walters 2021-02-17 22:53:56 +11:00
parent 1eb2456e3d
commit bb3440a8a4
5 changed files with 80 additions and 27 deletions

View File

@ -308,8 +308,7 @@
}
.rowinherited {
background-color: #efe;
opacity: 90%;
background-color: #dde;
}
.dropdown {

View File

@ -835,16 +835,7 @@ class BomList(generics.ListCreateAPIView):
try:
part = Part.objects.get(pk=part)
# Construct a filter for matching the provided part
local_part_filter = Q(part=part)
# Construct a filter for matching inherited items from parent parts
parent_parts = part.get_ancestors(include_self=False)
parent_ids = [p.pk for p in parent_parts]
parent_part_filter = Q(part__pk__in=parent_ids, inherited=True)
queryset = queryset.filter(local_part_filter | parent_part_filter)
queryset = queryset.filter(part.get_bom_item_filter())
except (ValueError, Part.DoesNotExist):
pass

View File

@ -14,7 +14,7 @@ from django.urls import reverse
from django.db import models, transaction
from django.db.utils import IntegrityError
from django.db.models import Sum, UniqueConstraint
from django.db.models import Q, Sum, UniqueConstraint
from django.db.models.functions import Coalesce
from django.core.validators import MinValueValidator
@ -418,8 +418,10 @@ class Part(MPTTModel):
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 self.bom_items.all():
for item in bom_items.all():
# Check for simple match
if item.sub_part == parent:
@ -1058,8 +1060,10 @@ class Part(MPTTModel):
total = None
bom_items = self.get_bom_items().prefetch_related('sub_part__stock_items')
# Calculate the minimum number of parts that can be built using each sub-part
for item in self.bom_items.all().prefetch_related('sub_part__stock_items'):
for item in bom_items.all():
stock = item.sub_part.available_stock
# If (by some chance) we get here but the BOM item quantity is invalid,
@ -1189,9 +1193,56 @@ class Part(MPTTModel):
return query['t']
def get_bom_item_filter(self, include_inherited=True):
"""
Returns a query filter for all BOM items associated with this Part.
There are some considerations:
a) BOM items can be defined against *this* part
b) BOM items can be inherited from a *parent* part
We will construct a filter to grab *all* the BOM items!
Note: This does *not* return a queryset, it returns a Q object,
which can be used by some other query operation!
Because we want to keep our code DRY!
"""
bom_filter = Q(part=self)
if include_inherited:
# We wish to include parent parts
parents = self.get_ancestors(include_self=False)
# There are parents available
if parents.count() > 0:
parent_ids = [p.pk for p in parents]
parent_filter = Q(
part__id__in=parent_ids,
inherited=True
)
# OR the filters together
bom_filter |= parent_filter
return bom_filter
def get_bom_items(self, include_inherited=True):
"""
Return a queryset containing all BOM items for this part
By default, will include inherited BOM items
"""
return BomItem.objects.filter(self.get_bom_item_filter(include_inherited=include_inherited))
@property
def has_bom(self):
return self.bom_count > 0
return self.get_bom_items().count() > 0
@property
def has_trackable_parts(self):
@ -1200,7 +1251,7 @@ class Part(MPTTModel):
This is important when building the part.
"""
for bom_item in self.bom_items.all():
for bom_item in self.get_bom_items().all():
if bom_item.sub_part.trackable:
return True
@ -1209,7 +1260,7 @@ class Part(MPTTModel):
@property
def bom_count(self):
""" Return the number of items contained in the BOM for this part """
return self.bom_items.count()
return self.get_bom_items().count()
@property
def used_in_count(self):
@ -1227,7 +1278,10 @@ class Part(MPTTModel):
hash = hashlib.md5(str(self.id).encode())
for item in self.bom_items.all().prefetch_related('sub_part'):
# List *all* BOM items (including inherited ones!)
bom_items = self.get_bom_items().all().prefetch_related('sub_part')
for item in bom_items:
hash.update(str(item.get_item_hash()).encode())
return str(hash.digest())
@ -1246,8 +1300,10 @@ class Part(MPTTModel):
- Saves the current date and the checking user
"""
# Validate each line item too
for item in self.bom_items.all():
# Validate each line item, ignoring inherited ones
bom_items = self.get_bom_items(include_inherited=False)
for item in bom_items.all():
item.validate_hash()
self.bom_checksum = self.get_bom_hash()
@ -1258,7 +1314,10 @@ class Part(MPTTModel):
@transaction.atomic
def clear_bom(self):
""" Clear the BOM items for the part (delete all BOM lines).
"""
Clear the BOM items for the part (delete all BOM lines).
Note: Does *NOT* delete inherited BOM items!
"""
self.bom_items.all().delete()
@ -1275,9 +1334,9 @@ class Part(MPTTModel):
if parts is None:
parts = set()
items = BomItem.objects.filter(part=self.pk)
bom_items = self.get_bom_items().all()
for bom_item in items:
for bom_item in bom_items:
sub_part = bom_item.sub_part
@ -1325,7 +1384,7 @@ class Part(MPTTModel):
def has_complete_bom_pricing(self):
""" Return true if there is pricing information for each item in the BOM. """
for item in self.bom_items.all().select_related('sub_part'):
for item in self.get_bom_items().all().select_related('sub_part'):
if not item.sub_part.has_pricing_info:
return False
@ -1392,7 +1451,7 @@ class Part(MPTTModel):
min_price = None
max_price = None
for item in self.bom_items.all().select_related('sub_part'):
for item in self.get_bom_items.all().select_related('sub_part'):
if item.sub_part.pk == self.pk:
print("Warning: Item contains itself in BOM")
@ -1460,8 +1519,11 @@ class Part(MPTTModel):
if clear:
# Remove existing BOM items
# Note: Inherited BOM items are *not* deleted!
self.bom_items.all().delete()
# Copy existing BOM items from another part
# Note: Inherited BOM Items will *not* be duplicated!!
for bom_item in other.bom_items.all():
# If this part already has a BomItem pointing to the same sub-part,
# delete that BomItem from this part first!

View File

@ -372,6 +372,7 @@ class BillOfMaterialsReport(ReportTemplateBase):
return {
'part': part,
'category': part.category,
'bom_items': part.get_bom_items(),
}

View File

@ -279,7 +279,7 @@ function loadBomTable(table, options) {
if (!row.inherited) {
return "-";
} else if (row.part == options.parent_id) {
return '{% trans "Inheritable" %}';
return '{% trans "Inherited" %}';
} else {
// If this BOM item is inherited from a parent part
return renderLink(