From d535e4fa12f4274396f9dce7071c4cae9e40ba31 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Fri, 15 Apr 2022 22:38:31 +1000 Subject: [PATCH] Add 'available_variant_stock' to BomItem serializer - Note: This is definitely *not* the optimum solution here --- InvenTree/part/models.py | 55 +++++++++++++++++++++++++++++++++++ InvenTree/part/serializers.py | 7 +++++ 2 files changed, 62 insertions(+) diff --git a/InvenTree/part/models.py b/InvenTree/part/models.py index b7269f3e5e..3635f22587 100644 --- a/InvenTree/part/models.py +++ b/InvenTree/part/models.py @@ -2703,6 +2703,61 @@ class BomItem(models.Model, DataImportMixin): def get_api_url(): 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): """ Return a list of valid parts which can be allocated against this BomItem: diff --git a/InvenTree/part/serializers.py b/InvenTree/part/serializers.py index 8bf3d77501..0daac6e630 100644 --- a/InvenTree/part/serializers.py +++ b/InvenTree/part/serializers.py @@ -581,6 +581,12 @@ class BomItemSerializer(InvenTreeModelSerializer): available_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) + def __init__(self, *args, **kwargs): # part_detail and sub_part_detail serializers are only included if requested. # This saves a bunch of database requests @@ -790,6 +796,7 @@ class BomItemSerializer(InvenTreeModelSerializer): # Annotated fields describing available quantity 'available_stock', 'available_substitute_stock', + 'available_variant_stock', ]