From 2edb7f2b5580e97203fd1b5d480eb064d7c1465a Mon Sep 17 00:00:00 2001 From: Oliver Date: Thu, 9 Mar 2023 00:18:39 +1100 Subject: [PATCH] Adds "stock value" calculation to stock table (#4471) * Add part pricing data to stockitem list serializer * Add "stock value" column to stock list table --- InvenTree/part/serializers.py | 6 ++ InvenTree/stock/serializers.py | 2 + InvenTree/templates/js/translated/pricing.js | 2 +- InvenTree/templates/js/translated/stock.js | 81 ++++++++++++++++++++ 4 files changed, 90 insertions(+), 1 deletion(-) diff --git a/InvenTree/part/serializers.py b/InvenTree/part/serializers.py index 55b0d0622c..c5600fb36c 100644 --- a/InvenTree/part/serializers.py +++ b/InvenTree/part/serializers.py @@ -281,6 +281,8 @@ class PartBriefSerializer(InvenTreeModelSerializer): 'trackable', 'virtual', 'units', + 'pricing_min', + 'pricing_max', ] read_only_fields = [ @@ -289,6 +291,10 @@ class PartBriefSerializer(InvenTreeModelSerializer): thumbnail = serializers.CharField(source='get_thumbnail_url', read_only=True) + # Pricing fields + pricing_min = InvenTreeMoneySerializer(source='pricing_data.overall_min', allow_null=True, read_only=True) + pricing_max = InvenTreeMoneySerializer(source='pricing_data.overall_max', allow_null=True, read_only=True) + class DuplicatePartSerializer(serializers.Serializer): """Serializer for specifying options when duplicating a Part. diff --git a/InvenTree/stock/serializers.py b/InvenTree/stock/serializers.py index 7336a1d15c..1d93f900bd 100644 --- a/InvenTree/stock/serializers.py +++ b/InvenTree/stock/serializers.py @@ -165,6 +165,8 @@ class StockItemSerializer(InvenTree.serializers.InvenTreeModelSerializer): queryset = queryset.prefetch_related( 'sales_order', 'purchase_order', + 'part', + 'part__pricing_data', ) # Annotate the queryset with the total allocated to sales orders diff --git a/InvenTree/templates/js/translated/pricing.js b/InvenTree/templates/js/translated/pricing.js index 590d5124c7..0e27aad229 100644 --- a/InvenTree/templates/js/translated/pricing.js +++ b/InvenTree/templates/js/translated/pricing.js @@ -136,7 +136,7 @@ function calculateTotalPrice(dataset, value_func, currency_func, options={}) { var currency = options.currency; - var rates = getCurrencyConversionRates(); + var rates = options.rates || getCurrencyConversionRates(); if (!rates) { console.error('Could not retrieve currency conversion information from the server'); diff --git a/InvenTree/templates/js/translated/stock.js b/InvenTree/templates/js/translated/stock.js index 36ca11a64a..f25efdee43 100644 --- a/InvenTree/templates/js/translated/stock.js +++ b/InvenTree/templates/js/translated/stock.js @@ -2065,6 +2065,86 @@ function loadStockTable(table, options) { } }); + // Total value of stock + // This is not sortable, and may default to the 'price range' for the parent part + columns.push({ + field: 'stock_value', + title: '{% trans "Stock Value" %}', + sortable: false, + switchable: true, + formatter: function(value, row) { + let min_price = row.purchase_price; + let max_price = row.purchase_price; + let currency = row.purchase_price_currency; + + if (min_price == null && max_price == null && row.part_detail) { + min_price = row.part_detail.pricing_min; + max_price = row.part_detail.pricing_max; + currency = baseCurrency(); + } + + return formatPriceRange( + min_price, + max_price, + { + quantity: row.quantity, + currency: currency + } + ); + }, + footerFormatter: function(data) { + // Display overall range of value for the selected items + let rates = getCurrencyConversionRates(); + let base = baseCurrency(); + + let min_price = calculateTotalPrice( + data, + function(row) { + return row.quantity * (row.purchase_price || row.part_detail.pricing_min); + }, + function(row) { + if (row.purchase_price) { + return row.purchase_price_currency; + } else { + return base; + } + }, + { + currency: base, + rates: rates, + raw: true, + } + ); + + let max_price = calculateTotalPrice( + data, + function(row) { + return row.quantity * (row.purchase_price || row.part_detail.pricing_max); + }, + function(row) { + if (row.purchase_price) { + return row.purchase_price_currency; + } else { + return base; + } + }, + { + currency: base, + rates: rates, + raw: true, + } + ); + + return formatPriceRange( + min_price, + max_price, + { + currency: base, + } + ); + } + }); + columns.push({ field: 'packaging', title: '{% trans "Packaging" %}', @@ -2085,6 +2165,7 @@ function loadStockTable(table, options) { name: 'stock', original: original, showColumns: true, + showFooter: true, columns: columns, });