From f6664b2477f4c8a2fa882760ddaeaf34439f2754 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Mon, 4 Apr 2022 22:50:13 +1000 Subject: [PATCH] Add annotated fields to BomItem API: - total-stock / allocated_to_build_orders / allocated_to_sales_orders --- InvenTree/part/api.py | 3 +- InvenTree/part/serializers.py | 63 +++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 1 deletion(-) diff --git a/InvenTree/part/api.py b/InvenTree/part/api.py index f7bb81520d..911e5453a5 100644 --- a/InvenTree/part/api.py +++ b/InvenTree/part/api.py @@ -1601,9 +1601,10 @@ class BomList(generics.ListCreateAPIView): def get_queryset(self, *args, **kwargs): - queryset = BomItem.objects.all() + queryset = super().get_queryset(*args, **kwargs) queryset = self.get_serializer_class().setup_eager_loading(queryset) + queryset = self.get_serializer_class().annotate_queryset(queryset) return queryset diff --git a/InvenTree/part/serializers.py b/InvenTree/part/serializers.py index c352c59eab..09bb4799a0 100644 --- a/InvenTree/part/serializers.py +++ b/InvenTree/part/serializers.py @@ -577,6 +577,11 @@ class BomItemSerializer(InvenTreeModelSerializer): purchase_price_range = serializers.SerializerMethodField() + # Annotated fields + total_stock = serializers.FloatField(read_only=True) + allocated_to_sales_orders = serializers.FloatField(read_only=True) + allocated_to_build_orders = serializers.FloatField(read_only=True) + def __init__(self, *args, **kwargs): # part_detail and sub_part_detail serializers are only included if requested. # This saves a bunch of database requests @@ -613,6 +618,59 @@ class BomItemSerializer(InvenTreeModelSerializer): queryset = queryset.prefetch_related('sub_part__supplier_parts__pricebreaks') return queryset + @staticmethod + def annotate_queryset(queryset): + """ + Annotate the BomItem queryset with extra information: + + Annotations: + available_stock: The amount of stock available for the sub_part Part object + """ + + """ + Construct an "available stock" quantity: + + available_stock = total_stock - build_order_allocations - sales_order_allocations + """ + + # Calculate "total stock" for the referenced sub_part + # Calculate the "build_order_allocations" for the sub_part + queryset = queryset.annotate( + total_stock=Coalesce( + SubquerySum('sub_part__stock_items__quantity', filter=StockItem.IN_STOCK_FILTER), + Decimal(0), + output_field=models.DecimalField(), + ), + allocated_to_sales_orders=Coalesce( + SubquerySum( + 'sub_part__stock_items__sales_order_allocations__quantity', + filter=Q( + line__order__status__in=SalesOrderStatus.OPEN, + shipment__shipment_date=None, + ) + ), + Decimal(0), + output_field=models.DecimalField(), + ), + allocated_to_build_orders=Coalesce( + SubquerySum( + 'sub_part__stock_items__allocations__quantity', + filter=Q( + build__status__in=BuildStatus.ACTIVE_CODES, + ), + ), + Decimal(0), + output_field=models.DecimalField(), + ) + # build_order_allocations=Coalesce( + # SubquerySum('sub_part__stock_items__allocations__quantity'), + # ) + ) + + # queryset = querty + + return queryset + def get_purchase_price_range(self, obj): """ Return purchase price range """ @@ -682,6 +740,11 @@ class BomItemSerializer(InvenTreeModelSerializer): 'substitutes', 'price_range', 'validated', + + 'total_stock', + 'allocated_to_sales_orders', + 'allocated_to_build_orders', + ]