From 62362455b8dd60e43f2135bb3a9cde8f0ef999a9 Mon Sep 17 00:00:00 2001 From: Oliver Date: Wed, 9 Aug 2023 21:45:12 +1000 Subject: [PATCH] Sales order variant stock (#5415) * Annotate available variant stock to SalesOrderLine serializer * Filter variant stock by: - active = True - salable = True * Add 'salable' filter to StockList API * Filter available stock in sales order table: - Must be salable - Must be active * Update table display * Bump API version --- InvenTree/InvenTree/api_version.py | 5 ++++- InvenTree/order/serializers.py | 22 +++++++++++++++++++ InvenTree/stock/api.py | 1 + .../templates/js/translated/sales_order.js | 17 +++++++++----- 4 files changed, 38 insertions(+), 7 deletions(-) diff --git a/InvenTree/InvenTree/api_version.py b/InvenTree/InvenTree/api_version.py index e344b81d34..934e20526b 100644 --- a/InvenTree/InvenTree/api_version.py +++ b/InvenTree/InvenTree/api_version.py @@ -2,11 +2,14 @@ # InvenTree API version -INVENTREE_API_VERSION = 130 +INVENTREE_API_VERSION = 131 """ Increment this API version number whenever there is a significant change to the API that any clients need to know about +v131 -> 2023-08-09 : https://github.com/inventree/InvenTree/pull/5415 + - Annotate 'available_variant_stock' to the SalesOrderLine serializer + v130 -> 2023-07-14 : https://github.com/inventree/InvenTree/pull/5251 - Refactor label printing interface diff --git a/InvenTree/order/serializers.py b/InvenTree/order/serializers.py index fe27cd447e..36dc8a4516 100644 --- a/InvenTree/order/serializers.py +++ b/InvenTree/order/serializers.py @@ -862,6 +862,7 @@ class SalesOrderLineItemSerializer(InvenTreeModelSerializer): 'allocated', 'allocations', 'available_stock', + 'available_variant_stock', 'customer_detail', 'quantity', 'reference', @@ -934,6 +935,26 @@ class SalesOrderLineItemSerializer(InvenTreeModelSerializer): ) ) + # Filter for "variant" stock: Variant stock items must be salable and active + variant_stock_query = part.filters.variant_stock_query(reference='part__').filter( + part__salable=True, + part__active=True + ) + + # Also add in available "variant" stock + queryset = queryset.alias( + variant_stock_total=part.filters.annotate_variant_quantity(variant_stock_query, reference='quantity'), + variant_bo_allocations=part.filters.annotate_variant_quantity(variant_stock_query, reference='sales_order_allocations__quantity'), + variant_so_allocations=part.filters.annotate_variant_quantity(variant_stock_query, reference='allocations__quantity'), + ) + + queryset = queryset.annotate( + available_variant_stock=ExpressionWrapper( + F('variant_stock_total') - F('variant_bo_allocations') - F('variant_so_allocations'), + output_field=models.DecimalField(), + ) + ) + return queryset customer_detail = CompanyBriefSerializer(source='order.customer', many=False, read_only=True) @@ -944,6 +965,7 @@ class SalesOrderLineItemSerializer(InvenTreeModelSerializer): # Annotated fields overdue = serializers.BooleanField(required=False, read_only=True) available_stock = serializers.FloatField(read_only=True) + available_variant_stock = serializers.FloatField(read_only=True) quantity = InvenTreeDecimalField() diff --git a/InvenTree/stock/api.py b/InvenTree/stock/api.py index c465f6ed76..36e85577b2 100644 --- a/InvenTree/stock/api.py +++ b/InvenTree/stock/api.py @@ -387,6 +387,7 @@ class StockFilter(rest_filters.FilterSet): # Part attribute filters assembly = rest_filters.BooleanFilter(label="Assembly", field_name='part__assembly') active = rest_filters.BooleanFilter(label="Active", field_name='part__active') + salable = rest_filters.BooleanFilter(label="Salable", field_name='part__salable') min_stock = rest_filters.NumberFilter(label='Minimum stock', field_name='quantity', lookup_expr='gte') max_stock = rest_filters.NumberFilter(label='Maximum stock', field_name='quantity', lookup_expr='lte') diff --git a/InvenTree/templates/js/translated/sales_order.js b/InvenTree/templates/js/translated/sales_order.js index 7211dae488..b2e6b5d758 100644 --- a/InvenTree/templates/js/translated/sales_order.js +++ b/InvenTree/templates/js/translated/sales_order.js @@ -1308,6 +1308,8 @@ function allocateStockToSalesOrder(order_id, line_items, options={}) { part_detail: true, location_detail: true, available: true, + salable: true, + active: true, }, model: 'stockitem', required: true, @@ -1881,17 +1883,20 @@ function loadSalesOrderLineItemTable(table, options={}) { field: 'stock', title: '{% trans "Available Stock" %}', formatter: function(value, row) { - var available = row.available_stock; - var required = Math.max(row.quantity - row.allocated - row.shipped, 0); - var html = ''; + let available = row.available_stock + row.available_variant_stock; + let required = Math.max(row.quantity - row.allocated - row.shipped, 0); + + let html = ''; if (available > 0) { - var url = `/part/${row.part}/?display=part-stock`; + let url = `/part/${row.part}/?display=part-stock`; - var text = available; + html = renderLink(available, url); - html = renderLink(text, url); + if (row.available_variant_stock && row.available_variant_stock > 0) { + html += makeIconBadge('fa-info-circle icon-blue', '{% trans "Includes variant stock" %}'); + } } else { html += `{% trans "No Stock Available" %}`; }