mirror of
https://github.com/inventree/InvenTree.git
synced 2025-06-21 06:16:29 +00:00
Implement filtering which accommodates new inheritable BOM feature
- Can no longer filter bom_items by sub_part - Adds get_used_in_filter() and get_used_in() for part model (returns a query of other part objects)
This commit is contained in:
@ -465,6 +465,18 @@ class PartList(generics.ListCreateAPIView):
|
||||
|
||||
queryset = super().filter_queryset(queryset)
|
||||
|
||||
# Filter by "uses" query - Limit to parts which use the provided part
|
||||
uses = params.get('uses', None)
|
||||
|
||||
if uses:
|
||||
try:
|
||||
uses = Part.objects.get(pk=uses)
|
||||
|
||||
queryset = queryset.filter(uses.get_used_in_filter())
|
||||
|
||||
except (ValueError, Part.DoesNotExist):
|
||||
pass
|
||||
|
||||
# Filter by 'ancestor'?
|
||||
ancestor = params.get('ancestor', None)
|
||||
|
||||
@ -839,12 +851,6 @@ class BomList(generics.ListCreateAPIView):
|
||||
|
||||
except (ValueError, Part.DoesNotExist):
|
||||
pass
|
||||
|
||||
# Filter by sub-part?
|
||||
sub_part = params.get('sub_part', None)
|
||||
|
||||
if sub_part is not None:
|
||||
queryset = queryset.filter(sub_part=sub_part)
|
||||
|
||||
# Filter by "active" status of the part
|
||||
part_active = params.get('part_active', None)
|
||||
|
@ -643,7 +643,7 @@ class Part(MPTTModel):
|
||||
super().clean()
|
||||
|
||||
if self.trackable:
|
||||
for item in self.used_in.all():
|
||||
for item in self.get_used_in().all():
|
||||
parent_part = item.part
|
||||
if not parent_part.trackable:
|
||||
parent_part.trackable = True
|
||||
@ -891,10 +891,10 @@ class Part(MPTTModel):
|
||||
Return list of outstanding build orders which require this part
|
||||
"""
|
||||
|
||||
# List of BOM that this part is required for
|
||||
boms = BomItem.objects.filter(sub_part=self)
|
||||
# List parts that this part is required for
|
||||
parts = self.get_used_in().all()
|
||||
|
||||
part_ids = [bom.part.pk for bom in boms]
|
||||
part_ids = [part.pk for part in parts]
|
||||
|
||||
# Now, get a list of outstanding build orders which require this part
|
||||
builds = BuildModels.Build.objects.filter(
|
||||
@ -909,36 +909,24 @@ class Part(MPTTModel):
|
||||
Return the quantity of this part required for active build orders
|
||||
"""
|
||||
|
||||
# List of BOM that this part is required for
|
||||
boms = BomItem.objects.filter(sub_part=self)
|
||||
|
||||
part_ids = [bom.part.pk for bom in boms]
|
||||
|
||||
# Now, get a list of outstanding build orders which require this part
|
||||
builds = BuildModels.Build.objects.filter(
|
||||
part__in=part_ids,
|
||||
status__in=BuildStatus.ACTIVE_CODES
|
||||
)
|
||||
# List active build orders which reference this part
|
||||
builds = self.requiring_build_orders()
|
||||
|
||||
quantity = 0
|
||||
|
||||
for build in builds:
|
||||
|
||||
|
||||
bom_item = None
|
||||
|
||||
# List the bom lines required to make the build (including inherited ones!)
|
||||
bom_items = build.part.get_bom_items().filter(sub_part=self)
|
||||
|
||||
# Match BOM item to build
|
||||
for bom in boms:
|
||||
if bom.part == build.part:
|
||||
bom_item = bom
|
||||
break
|
||||
for bom_item in bom_items:
|
||||
|
||||
if bom_item is None:
|
||||
logger.warning("Found null BomItem when calculating required quantity")
|
||||
continue
|
||||
build_quantity = build.quantity * bom_item.quantity
|
||||
|
||||
build_quantity = build.quantity * bom_item.quantity
|
||||
|
||||
quantity += build_quantity
|
||||
quantity += build_quantity
|
||||
|
||||
return quantity
|
||||
|
||||
@ -1240,6 +1228,55 @@ class Part(MPTTModel):
|
||||
|
||||
return BomItem.objects.filter(self.get_bom_item_filter(include_inherited=include_inherited))
|
||||
|
||||
|
||||
def get_used_in_filter(self, include_inherited=True):
|
||||
"""
|
||||
Return a query filter for all parts that this part is used in.
|
||||
|
||||
There are some considerations:
|
||||
|
||||
a) This part may be directly specified against a BOM for a part
|
||||
b) This part may be specifed in a BOM which is then inherited by another part
|
||||
|
||||
Note: This function returns a Q object, not an actual queryset.
|
||||
The Q object is used to filter against a list of Part objects
|
||||
"""
|
||||
|
||||
# This is pretty expensive - we need to traverse multiple variant lists!
|
||||
# TODO - In the future, could this be improved somehow?
|
||||
|
||||
# Keep a set of Part ID values
|
||||
parts = set()
|
||||
|
||||
# First, grab a list of all BomItem objects which "require" this part
|
||||
bom_items = BomItem.objects.filter(sub_part=self)
|
||||
|
||||
for bom_item in bom_items:
|
||||
|
||||
# Add the directly referenced part
|
||||
parts.add(bom_item.part)
|
||||
|
||||
# Traverse down the variant tree?
|
||||
if include_inherited and bom_item.inherited:
|
||||
|
||||
part_variants = bom_item.part.get_descendants(include_self=False)
|
||||
|
||||
for variant in part_variants:
|
||||
parts.add(variant)
|
||||
|
||||
# Turn into a list of valid IDs (for matching against a Part query)
|
||||
part_ids = [part.pk for part in parts]
|
||||
|
||||
return Q(id__in=part_ids)
|
||||
|
||||
def get_used_in(self, include_inherited=True):
|
||||
"""
|
||||
Return a queryset containing all parts this part is used in.
|
||||
|
||||
Includes consideration of inherited BOMs
|
||||
"""
|
||||
return Part.objects.filter(self.get_used_in_filter(include_inherited=include_inherited))
|
||||
|
||||
@property
|
||||
def has_bom(self):
|
||||
return self.get_bom_items().count() > 0
|
||||
@ -1265,7 +1302,7 @@ class Part(MPTTModel):
|
||||
@property
|
||||
def used_in_count(self):
|
||||
""" Return the number of part BOMs that this part appears in """
|
||||
return self.used_in.count()
|
||||
return self.get_used_in().count()
|
||||
|
||||
def get_bom_hash(self):
|
||||
""" Return a checksum hash for the BOM for this part.
|
||||
@ -1364,7 +1401,7 @@ class Part(MPTTModel):
|
||||
parts = parts.exclude(id=self.id)
|
||||
|
||||
# Exclude any parts that this part is used *in* (to prevent recursive BOMs)
|
||||
used_in = self.used_in.all()
|
||||
used_in = self.get_used_in().all()
|
||||
|
||||
parts = parts.exclude(id__in=[item.part.id for item in used_in])
|
||||
|
||||
|
@ -133,11 +133,18 @@
|
||||
<td>{% decimal on_order %}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if required > 0 %}
|
||||
{% if required_build_order_quantity > 0 %}
|
||||
<tr>
|
||||
<td><span class='fas fa-clipboard-list'></span></td>
|
||||
<td>{% trans "Required for Orders" %}</td>
|
||||
<td>{% decimal required %}
|
||||
<td>{% trans "Required for Build Orders" %}</td>
|
||||
<td>{% decimal required_build_order_quantity %}
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if required_sales_order_quantity > 0 %}
|
||||
<tr>
|
||||
<td><span class='fas fa-clipboard-list'></span></td>
|
||||
<td>{% trans "Required for Sales Orders" %}</td>
|
||||
<td>{% decimal required_sales_order_quantity %}
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if allocated > 0 %}
|
||||
|
@ -22,10 +22,14 @@
|
||||
{% block js_ready %}
|
||||
{{ block.super }}
|
||||
|
||||
loadUsedInTable('#used-table', {
|
||||
part_detail: true,
|
||||
part_id: {{ part.pk }}
|
||||
});
|
||||
loadSimplePartTable('#used-table',
|
||||
'{% url "api-part-list" %}',
|
||||
{
|
||||
params: {
|
||||
uses: {{ part.pk }},
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
{% endblock %}
|
Reference in New Issue
Block a user