Update backend logic
This commit is contained in:
parent
4b21f9faa6
commit
99494ba9dc
|
|
@ -974,7 +974,9 @@ class Build(
|
||||||
items_to_delete = []
|
items_to_delete = []
|
||||||
|
|
||||||
lines = self.untracked_line_items.all()
|
lines = self.untracked_line_items.all()
|
||||||
lines = lines.exclude(bom_item__consumable=True)
|
lines = lines.exclude(
|
||||||
|
part.models.BomItem.consumable_filter(prefix='bom_item__')
|
||||||
|
)
|
||||||
lines = lines.annotate(allocated=annotate_allocated_quantity())
|
lines = lines.annotate(allocated=annotate_allocated_quantity())
|
||||||
|
|
||||||
for build_line in lines:
|
for build_line in lines:
|
||||||
|
|
@ -1254,13 +1256,16 @@ class Build(
|
||||||
return allocations
|
return allocations
|
||||||
|
|
||||||
tracked_line_items = self.tracked_line_items.filter(
|
tracked_line_items = self.tracked_line_items.filter(
|
||||||
bom_item__consumable=False, bom_item__sub_part__virtual=False
|
part.models.BomItem.consumable_filter(
|
||||||
|
consumable=False, prefix='bom_item__'
|
||||||
|
),
|
||||||
|
bom_item__sub_part__virtual=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
for line_item in tracked_line_items:
|
for line_item in tracked_line_items:
|
||||||
bom_item = line_item.bom_item
|
bom_item = line_item.bom_item
|
||||||
|
|
||||||
if bom_item.consumable:
|
if bom_item.is_consumable:
|
||||||
# Do not auto-allocate stock to consumable BOM items
|
# Do not auto-allocate stock to consumable BOM items
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
|
@ -1384,7 +1389,7 @@ class Build(
|
||||||
# Find the referenced BomItem
|
# Find the referenced BomItem
|
||||||
bom_item = line_item.bom_item
|
bom_item = line_item.bom_item
|
||||||
|
|
||||||
if bom_item.consumable:
|
if bom_item.is_consumable:
|
||||||
# Do not auto-allocate stock to consumable BOM items
|
# Do not auto-allocate stock to consumable BOM items
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
|
@ -1504,7 +1509,9 @@ class Build(
|
||||||
lines = self.build_lines.all()
|
lines = self.build_lines.all()
|
||||||
|
|
||||||
# Remove any 'consumable' line items
|
# Remove any 'consumable' line items
|
||||||
lines = lines.exclude(bom_item__consumable=True)
|
lines = lines.exclude(
|
||||||
|
part.models.BomItem.consumable_filter(prefix='bom_item__')
|
||||||
|
)
|
||||||
|
|
||||||
if tracked is True:
|
if tracked is True:
|
||||||
lines = lines.filter(bom_item__sub_part__trackable=True)
|
lines = lines.filter(bom_item__sub_part__trackable=True)
|
||||||
|
|
@ -1541,7 +1548,9 @@ class Build(
|
||||||
we need to test all "trackable" BuildLine objects
|
we need to test all "trackable" BuildLine objects
|
||||||
"""
|
"""
|
||||||
lines = self.build_lines.filter(bom_item__sub_part__trackable=True)
|
lines = self.build_lines.filter(bom_item__sub_part__trackable=True)
|
||||||
lines = lines.exclude(bom_item__consumable=True)
|
lines = lines.exclude(
|
||||||
|
part.models.BomItem.consumable_filter(prefix='bom_item__')
|
||||||
|
)
|
||||||
|
|
||||||
# Find any lines which have not been fully allocated
|
# Find any lines which have not been fully allocated
|
||||||
for line in lines:
|
for line in lines:
|
||||||
|
|
@ -1565,7 +1574,9 @@ class Build(
|
||||||
Returns:
|
Returns:
|
||||||
True if any BuildLine has been over-allocated.
|
True if any BuildLine has been over-allocated.
|
||||||
"""
|
"""
|
||||||
lines = self.build_lines.all().exclude(bom_item__consumable=True)
|
lines = self.build_lines.all().exclude(
|
||||||
|
part.models.BomItem.consumable_filter(prefix='bom_item__')
|
||||||
|
)
|
||||||
|
|
||||||
lines = lines.prefetch_related('allocations')
|
lines = lines.prefetch_related('allocations')
|
||||||
|
|
||||||
|
|
@ -1794,7 +1805,7 @@ class BuildLine(report.mixins.InvenTreeReportMixin, InvenTree.models.InvenTreeMo
|
||||||
|
|
||||||
def is_fully_allocated(self) -> bool:
|
def is_fully_allocated(self) -> bool:
|
||||||
"""Return True if this BuildLine is fully allocated."""
|
"""Return True if this BuildLine is fully allocated."""
|
||||||
if self.bom_item.consumable:
|
if self.bom_item.is_consumable:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
required = max(0, self.quantity - self.consumed)
|
required = max(0, self.quantity - self.consumed)
|
||||||
|
|
|
||||||
|
|
@ -975,7 +975,7 @@ class BuildAllocationSerializer(serializers.Serializer):
|
||||||
output = item.get('output', None)
|
output = item.get('output', None)
|
||||||
|
|
||||||
# Ignore allocation for consumable BOM items
|
# Ignore allocation for consumable BOM items
|
||||||
if build_line.bom_item.consumable:
|
if build_line.bom_item.is_consumable:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
params = {
|
params = {
|
||||||
|
|
@ -1368,7 +1368,7 @@ class BuildLineSerializer(
|
||||||
source='bom_item.reference', label=_('Reference'), read_only=True
|
source='bom_item.reference', label=_('Reference'), read_only=True
|
||||||
)
|
)
|
||||||
consumable = serializers.BooleanField(
|
consumable = serializers.BooleanField(
|
||||||
source='bom_item.consumable', label=_('Consumable'), read_only=True
|
source='bom_item.is_consumable', label=_('Consumable'), read_only=True
|
||||||
)
|
)
|
||||||
optional = serializers.BooleanField(
|
optional = serializers.BooleanField(
|
||||||
source='bom_item.optional', label=_('Optional'), read_only=True
|
source='bom_item.optional', label=_('Optional'), read_only=True
|
||||||
|
|
|
||||||
|
|
@ -1626,7 +1626,7 @@ class Part(
|
||||||
queryset = self.get_bom_items(include_virtual=False)
|
queryset = self.get_bom_items(include_virtual=False)
|
||||||
|
|
||||||
# Ignore 'consumable' BOM items for this calculation
|
# Ignore 'consumable' BOM items for this calculation
|
||||||
queryset = queryset.filter(consumable=False)
|
queryset = queryset.filter(BomItem.consumable_filter(consumable=False))
|
||||||
|
|
||||||
# Annotate the queryset with the 'can_build' quantity
|
# Annotate the queryset with the 'can_build' quantity
|
||||||
queryset = part.filters.annotate_bom_item_can_build(queryset)
|
queryset = part.filters.annotate_bom_item_can_build(queryset)
|
||||||
|
|
@ -4295,6 +4295,35 @@ class BomItem(InvenTree.models.MetadataMixin, InvenTree.models.InvenTreeModel):
|
||||||
|
|
||||||
return self.get_item_hash() == self.checksum
|
return self.get_item_hash() == self.checksum
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_consumable(self) -> bool:
|
||||||
|
"""Return True if this BOM line should be treated as consumable.
|
||||||
|
|
||||||
|
This is the case if either:
|
||||||
|
- The BOM line itself is marked as consumable
|
||||||
|
- The underlying part is marked as consumable
|
||||||
|
"""
|
||||||
|
return self.consumable or self.sub_part.consumable
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def consumable_filter(consumable: bool = True, prefix: str = '') -> Q:
|
||||||
|
"""Return a Q filter which selects BomItem objects based on "effective" consumable status.
|
||||||
|
|
||||||
|
A BomItem is considered "effectively consumable" if either the BOM line itself,
|
||||||
|
or the underlying part, is marked as consumable.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
consumable: If True, return a filter which matches consumable BOM items.
|
||||||
|
If False, return a filter which matches non-consumable BOM items.
|
||||||
|
prefix: Optional field lookup prefix, for use against querysets of a
|
||||||
|
related model (e.g. 'bom_item__' when filtering a BuildLine queryset).
|
||||||
|
"""
|
||||||
|
f = Q(**{f'{prefix}consumable': True}) | Q(**{
|
||||||
|
f'{prefix}sub_part__consumable': True
|
||||||
|
})
|
||||||
|
|
||||||
|
return f if consumable else ~f
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
"""Check validity of the BomItem model.
|
"""Check validity of the BomItem model.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
import structlog
|
import structlog
|
||||||
|
|
||||||
|
import part.models
|
||||||
from build.events import BuildEvents
|
from build.events import BuildEvents
|
||||||
from build.models import Build
|
from build.models import Build
|
||||||
from build.status_codes import BuildStatus
|
from build.status_codes import BuildStatus
|
||||||
|
|
@ -56,7 +57,8 @@ class AutoCreateBuildsPlugin(EventMixin, InvenTreePlugin):
|
||||||
"""Process a build order when it is issued."""
|
"""Process a build order when it is issued."""
|
||||||
# Iterate through all sub-assemblies for the build order
|
# Iterate through all sub-assemblies for the build order
|
||||||
for bom_item in build.part.get_bom_items().filter(
|
for bom_item in build.part.get_bom_items().filter(
|
||||||
consumable=False, sub_part__assembly=True
|
part.models.BomItem.consumable_filter(consumable=False),
|
||||||
|
sub_part__assembly=True,
|
||||||
):
|
):
|
||||||
subassembly = bom_item.sub_part
|
subassembly = bom_item.sub_part
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue