2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-06-17 12:35:46 +00:00

Queryset annotation refactor (#3117)

* Refactor out 'ordering' serializer annotation field

* Refactor BomItem serializer annotations

* Factor out MPTT OuterRef query

* Add 'available_stock' annotation to SalesOrderLineItem serializer

- Allows for better rendering of stock availability in sales order table

* Improve 'available quantity' rendering of salesorderlineitem table

* Bump API version

* Add docstring
This commit is contained in:
Oliver
2022-06-02 23:22:47 +10:00
committed by GitHub
parent 2074bf9156
commit 309ed595d7
7 changed files with 237 additions and 142 deletions

View File

@ -788,6 +788,8 @@ class SalesOrderLineItemList(generics.ListCreateAPIView):
'order__stock_items',
)
queryset = serializers.SalesOrderLineItemSerializer.annotate_queryset(queryset)
return queryset
filter_backends = [
@ -835,6 +837,14 @@ class SalesOrderLineItemDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = models.SalesOrderLineItem.objects.all()
serializer_class = serializers.SalesOrderLineItemSerializer
def get_queryset(self, *args, **kwargs):
"""Return annotated queryset for this endpoint"""
queryset = super().get_queryset(*args, **kwargs)
queryset = serializers.SalesOrderLineItemSerializer.annotate_queryset(queryset)
return queryset
class SalesOrderContextMixin:
"""Mixin to add sales order object as serializer context variable."""

View File

@ -887,14 +887,6 @@ class OrderLineItem(models.Model):
target_date: An (optional) date for expected shipment of this line item.
"""
"""
Query filter for determining if an individual line item is "overdue":
- Amount received is less than the required quantity
- Target date is not None
- Target date is in the past
"""
OVERDUE_FILTER = Q(received__lt=F('quantity')) & ~Q(target_date=None) & Q(target_date__lt=datetime.now().date())
class Meta:
"""Metaclass options. Abstract ensures no database table is created."""
@ -953,6 +945,9 @@ class PurchaseOrderLineItem(OrderLineItem):
order: Reference to a PurchaseOrder object
"""
# Filter for determining if a particular PurchaseOrderLineItem is overdue
OVERDUE_FILTER = Q(received__lt=F('quantity')) & ~Q(target_date=None) & Q(target_date__lt=datetime.now().date())
@staticmethod
def get_api_url():
"""Return the API URL associated with the PurchaseOrderLineItem model"""
@ -1076,6 +1071,9 @@ class SalesOrderLineItem(OrderLineItem):
shipped: The number of items which have actually shipped against this line item
"""
# Filter for determining if a particular SalesOrderLineItem is overdue
OVERDUE_FILTER = Q(shipped__lt=F('quantity')) & ~Q(target_date=None) & Q(target_date__lt=datetime.now().date())
@staticmethod
def get_api_url():
"""Return the API URL associated with the SalesOrderLineItem model"""

View File

@ -14,6 +14,7 @@ from rest_framework.serializers import ValidationError
from sql_util.utils import SubqueryCount
import order.models
import part.filters
import stock.models
import stock.serializers
from common.settings import currency_code_mappings
@ -248,7 +249,7 @@ class PurchaseOrderLineItemSerializer(InvenTreeModelSerializer):
queryset = queryset.annotate(
overdue=Case(
When(
Q(order__status__in=PurchaseOrderStatus.OPEN) & order.models.OrderLineItem.OVERDUE_FILTER, then=Value(True, output_field=BooleanField())
Q(order__status__in=PurchaseOrderStatus.OPEN) & order.models.PurchaseOrderLineItem.OVERDUE_FILTER, then=Value(True, output_field=BooleanField())
),
default=Value(False, output_field=BooleanField()),
)
@ -790,17 +791,36 @@ class SalesOrderLineItemSerializer(InvenTreeModelSerializer):
def annotate_queryset(queryset):
"""Add some extra annotations to this queryset:
- "Overdue" status (boolean field)
- "overdue" status (boolean field)
- "available_quantity"
"""
queryset = queryset.annotate(
overdue=Case(
When(
Q(order__status__in=SalesOrderStatus.OPEN) & order.models.OrderLineItem.OVERDUE_FILTER, then=Value(True, output_field=BooleanField()),
Q(order__status__in=SalesOrderStatus.OPEN) & order.models.SalesOrderLineItem.OVERDUE_FILTER, then=Value(True, output_field=BooleanField()),
),
default=Value(False, output_field=BooleanField()),
)
)
# Annotate each line with the available stock quantity
# To do this, we need to look at the total stock and any allocations
queryset = queryset.alias(
total_stock=part.filters.annotate_total_stock(reference='part__'),
allocated_to_sales_orders=part.filters.annotate_sales_order_allocations(reference='part__'),
allocated_to_build_orders=part.filters.annotate_build_order_allocations(reference='part__'),
)
queryset = queryset.annotate(
available_stock=ExpressionWrapper(
F('total_stock') - F('allocated_to_sales_orders') - F('allocated_to_build_orders'),
output_field=models.DecimalField()
)
)
return queryset
def __init__(self, *args, **kwargs):
"""Initializion routine for the serializer:
@ -825,7 +845,9 @@ class SalesOrderLineItemSerializer(InvenTreeModelSerializer):
part_detail = PartBriefSerializer(source='part', many=False, read_only=True)
allocations = SalesOrderAllocationSerializer(many=True, read_only=True, location_detail=True)
# Annotated fields
overdue = serializers.BooleanField(required=False, read_only=True)
available_stock = serializers.FloatField(read_only=True)
quantity = InvenTreeDecimalField()
@ -853,6 +875,7 @@ class SalesOrderLineItemSerializer(InvenTreeModelSerializer):
'pk',
'allocated',
'allocations',
'available_stock',
'quantity',
'reference',
'notes',