mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Refactor bom item filter
- Also updates a number of part functions to make use of inherited BOM items
This commit is contained in:
parent
1eb2456e3d
commit
bb3440a8a4
@ -308,8 +308,7 @@
|
||||
}
|
||||
|
||||
.rowinherited {
|
||||
background-color: #efe;
|
||||
opacity: 90%;
|
||||
background-color: #dde;
|
||||
}
|
||||
|
||||
.dropdown {
|
||||
|
@ -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
|
||||
|
@ -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!
|
||||
|
@ -372,6 +372,7 @@ class BillOfMaterialsReport(ReportTemplateBase):
|
||||
return {
|
||||
'part': part,
|
||||
'category': part.category,
|
||||
'bom_items': part.get_bom_items(),
|
||||
}
|
||||
|
||||
|
||||
|
@ -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(
|
||||
|
Loading…
Reference in New Issue
Block a user