mirror of
https://github.com/inventree/InvenTree.git
synced 2025-04-29 20:16:44 +00:00
Use a proper queryset annotation to calculate the "available_variant_stock"
This commit is contained in:
parent
1d73f7c066
commit
fa2510c42f
@ -2703,60 +2703,6 @@ class BomItem(models.Model, DataImportMixin):
|
|||||||
def get_api_url():
|
def get_api_url():
|
||||||
return reverse('api-bom-list')
|
return reverse('api-bom-list')
|
||||||
|
|
||||||
def available_variant_stock(self):
|
|
||||||
"""
|
|
||||||
Returns the total quantity of variant stock available for this BomItem.
|
|
||||||
|
|
||||||
Notes:
|
|
||||||
- If "allow_variants" is False, this will return zero
|
|
||||||
- This is used for the API serializer, and is very inefficient
|
|
||||||
- This logic needs to be converted to a queryset annotation
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Variant stock is not allowed for this BOM item
|
|
||||||
if not self.allow_variants:
|
|
||||||
return 0
|
|
||||||
|
|
||||||
# Extract a flattened list of part variants
|
|
||||||
variants = self.sub_part.get_descendants(include_self=False)
|
|
||||||
|
|
||||||
# Calculate 'in_stock' quantity - this is the total current stock count
|
|
||||||
query = StockModels.StockItem.objects.filter(StockModels.StockItem.IN_STOCK_FILTER)
|
|
||||||
|
|
||||||
query = query.filter(
|
|
||||||
part__in=variants,
|
|
||||||
)
|
|
||||||
|
|
||||||
query = query.aggregate(
|
|
||||||
in_stock=Coalesce(Sum('quantity'), Decimal(0))
|
|
||||||
)
|
|
||||||
|
|
||||||
in_stock = query['in_stock'] or 0
|
|
||||||
|
|
||||||
# Calculate the quantity allocated to sales orders
|
|
||||||
query = OrderModels.SalesOrderAllocation.objects.filter(
|
|
||||||
line__order__status__in=SalesOrderStatus.OPEN,
|
|
||||||
shipment__shipment_date=None,
|
|
||||||
item__part__in=variants,
|
|
||||||
).aggregate(
|
|
||||||
allocated=Coalesce(Sum('quantity'), Decimal(0)),
|
|
||||||
)
|
|
||||||
|
|
||||||
sales_order_allocations = query['allocated'] or 0
|
|
||||||
|
|
||||||
# Calculate the quantity allocated to build orders
|
|
||||||
query = BuildModels.BuildItem.objects.filter(
|
|
||||||
build__status__in=BuildStatus.ACTIVE_CODES,
|
|
||||||
stock_item__part__in=variants,
|
|
||||||
).aggregate(
|
|
||||||
allocated=Coalesce(Sum('quantity'), Decimal(0)),
|
|
||||||
)
|
|
||||||
|
|
||||||
build_order_allocations = query['allocated'] or 0
|
|
||||||
|
|
||||||
available = in_stock - sales_order_allocations - build_order_allocations
|
|
||||||
|
|
||||||
return max(available, 0)
|
|
||||||
|
|
||||||
def get_valid_parts_for_allocation(self, allow_variants=True, allow_substitutes=True):
|
def get_valid_parts_for_allocation(self, allow_variants=True, allow_substitutes=True):
|
||||||
"""
|
"""
|
||||||
|
@ -7,7 +7,9 @@ from decimal import Decimal
|
|||||||
|
|
||||||
from django.urls import reverse_lazy
|
from django.urls import reverse_lazy
|
||||||
from django.db import models, transaction
|
from django.db import models, transaction
|
||||||
from django.db.models import ExpressionWrapper, F, Q
|
from django.db.models import ExpressionWrapper, F, Q, Func
|
||||||
|
from django.db.models import Subquery, OuterRef, IntegerField, FloatField
|
||||||
|
|
||||||
from django.db.models.functions import Coalesce
|
from django.db.models.functions import Coalesce
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
@ -577,14 +579,9 @@ class BomItemSerializer(InvenTreeModelSerializer):
|
|||||||
|
|
||||||
purchase_price_range = serializers.SerializerMethodField()
|
purchase_price_range = serializers.SerializerMethodField()
|
||||||
|
|
||||||
# Annotated fields
|
# Annotated fields for available stock
|
||||||
available_stock = serializers.FloatField(read_only=True)
|
available_stock = serializers.FloatField(read_only=True)
|
||||||
available_substitute_stock = serializers.FloatField(read_only=True)
|
available_substitute_stock = serializers.FloatField(read_only=True)
|
||||||
|
|
||||||
# Note: 2022-04-15
|
|
||||||
# The 'available_variant_stock' field is calculated per-object,
|
|
||||||
# which means it is very inefficient!
|
|
||||||
# TODO: This needs to be converted into a query annotation, if possible!
|
|
||||||
available_variant_stock = serializers.FloatField(read_only=True)
|
available_variant_stock = serializers.FloatField(read_only=True)
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
@ -619,11 +616,18 @@ class BomItemSerializer(InvenTreeModelSerializer):
|
|||||||
|
|
||||||
queryset = queryset.prefetch_related('sub_part')
|
queryset = queryset.prefetch_related('sub_part')
|
||||||
queryset = queryset.prefetch_related('sub_part__category')
|
queryset = queryset.prefetch_related('sub_part__category')
|
||||||
|
|
||||||
queryset = queryset.prefetch_related(
|
queryset = queryset.prefetch_related(
|
||||||
'sub_part__stock_items',
|
'sub_part__stock_items',
|
||||||
'sub_part__stock_items__allocations',
|
'sub_part__stock_items__allocations',
|
||||||
'sub_part__stock_items__sales_order_allocations',
|
'sub_part__stock_items__sales_order_allocations',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
queryset = queryset.prefetch_related(
|
||||||
|
'substitutes',
|
||||||
|
'substitutes__part__stock_items',
|
||||||
|
)
|
||||||
|
|
||||||
queryset = queryset.prefetch_related('sub_part__supplier_parts__pricebreaks')
|
queryset = queryset.prefetch_related('sub_part__supplier_parts__pricebreaks')
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
@ -713,7 +717,7 @@ class BomItemSerializer(InvenTreeModelSerializer):
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
# Calculate 'available_variant_stock' field
|
# Calculate 'available_substitute_stock' field
|
||||||
queryset = queryset.annotate(
|
queryset = queryset.annotate(
|
||||||
available_substitute_stock=ExpressionWrapper(
|
available_substitute_stock=ExpressionWrapper(
|
||||||
F('substitute_stock') - F('substitute_build_allocations') - F('substitute_sales_allocations'),
|
F('substitute_stock') - F('substitute_build_allocations') - F('substitute_sales_allocations'),
|
||||||
@ -721,6 +725,47 @@ class BomItemSerializer(InvenTreeModelSerializer):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Annotate the queryset with 'available variant stock' information
|
||||||
|
variant_stock_query = StockItem.objects.filter(
|
||||||
|
part__tree_id=OuterRef('sub_part__tree_id'),
|
||||||
|
part__lft__gt=OuterRef('sub_part__lft'),
|
||||||
|
part__rght__lt=OuterRef('sub_part__rght'),
|
||||||
|
)
|
||||||
|
|
||||||
|
queryset = queryset.alias(
|
||||||
|
variant_stock_total=Coalesce(
|
||||||
|
Subquery(
|
||||||
|
variant_stock_query.annotate(
|
||||||
|
total=Func(F('quantity'), function='SUM', output_field=FloatField())
|
||||||
|
).values('total')),
|
||||||
|
0,
|
||||||
|
output_field=FloatField()
|
||||||
|
),
|
||||||
|
variant_stock_build_order_allocations=Coalesce(
|
||||||
|
Subquery(
|
||||||
|
variant_stock_query.annotate(
|
||||||
|
total=Func(F('sales_order_allocations__quantity'), function='SUM', output_field=FloatField()),
|
||||||
|
).values('total')),
|
||||||
|
0,
|
||||||
|
output_field=FloatField(),
|
||||||
|
),
|
||||||
|
variant_stock_sales_order_allocations=Coalesce(
|
||||||
|
Subquery(
|
||||||
|
variant_stock_query.annotate(
|
||||||
|
total=Func(F('allocations__quantity'), function='SUM', output_field=FloatField()),
|
||||||
|
).values('total')),
|
||||||
|
0,
|
||||||
|
output_field=FloatField(),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
queryset = queryset.annotate(
|
||||||
|
available_variant_stock=ExpressionWrapper(
|
||||||
|
F('variant_stock_total') - F('variant_stock_build_order_allocations') - F('variant_stock_sales_order_allocations'),
|
||||||
|
output_field=FloatField(),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
def get_purchase_price_range(self, obj):
|
def get_purchase_price_range(self, obj):
|
||||||
@ -797,6 +842,7 @@ class BomItemSerializer(InvenTreeModelSerializer):
|
|||||||
'available_stock',
|
'available_stock',
|
||||||
'available_substitute_stock',
|
'available_substitute_stock',
|
||||||
'available_variant_stock',
|
'available_variant_stock',
|
||||||
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user