diff --git a/InvenTree/part/models.py b/InvenTree/part/models.py index 50d3044771..d7ad577081 100644 --- a/InvenTree/part/models.py +++ b/InvenTree/part/models.py @@ -35,6 +35,8 @@ from stdimage.models import StdImageField from decimal import Decimal, InvalidOperation from datetime import datetime import hashlib +from djmoney.contrib.exchange.models import convert_money +from common.settings import currency_code_default from InvenTree import helpers from InvenTree import validators @@ -1514,7 +1516,7 @@ class Part(MPTTModel): return (min_price, max_price) - def get_bom_price_range(self, quantity=1, internal=False): + def get_bom_price_range(self, quantity=1, internal=False, purchase=False): """ Return the price range of the BOM for this part. Adds the minimum price for all components in the BOM. @@ -1531,7 +1533,7 @@ class Part(MPTTModel): print("Warning: Item contains itself in BOM") continue - prices = item.sub_part.get_price_range(quantity * item.quantity, internal=internal) + prices = item.sub_part.get_price_range(quantity * item.quantity, internal=internal, purchase=purchase) if prices is None: continue @@ -1555,16 +1557,17 @@ class Part(MPTTModel): return (min_price, max_price) - def get_price_range(self, quantity=1, buy=True, bom=True, internal=False): + def get_price_range(self, quantity=1, buy=True, bom=True, internal=False, purchase=False): """ Return the price range for this part. This price can be either: - Supplier price (if purchased from suppliers) - BOM price (if built from other parts) - Internal price (if set for the part) + - Purchase price (if set for the part) Returns: - Minimum of the supplier, BOM or internal price. If no pricing available, returns None + Minimum of the supplier, BOM, internal or purchase price. If no pricing available, returns None """ # only get internal price if set and should be used @@ -1572,6 +1575,12 @@ class Part(MPTTModel): internal_price = self.get_internal_price(quantity) return internal_price, internal_price + # only get purchase price if set and should be used + if purchase: + purchase_price = self.get_purchase_price(quantity) + if purchase_price: + return purchase_price + buy_price_range = self.get_supplier_price_range(quantity) if buy else None bom_price_range = self.get_bom_price_range(quantity, internal=internal) if bom else None @@ -1641,6 +1650,13 @@ class Part(MPTTModel): def internal_unit_pricing(self): return self.get_internal_price(1) + def get_purchase_price(self, quantity): + currency = currency_code_default() + prices = [convert_money(item.purchase_price, currency).amount for item in self.stock_items.all() if item.purchase_price] + if prices: + return min(prices) * quantity, max(prices) * quantity + return None + @transaction.atomic def copy_bom_from(self, other, clear=True, **kwargs): """ diff --git a/InvenTree/part/templates/part/part_pricing.html b/InvenTree/part/templates/part/part_pricing.html index ce55124bd9..be80efe1e7 100644 --- a/InvenTree/part/templates/part/part_pricing.html +++ b/InvenTree/part/templates/part/part_pricing.html @@ -60,6 +60,21 @@ Max: {% include "price.html" with price=max_total_bom_price %} {% endif %} + {% if min_total_bom_purchase_price %} + + {% trans 'Unit Purchase Price' %} + Min: {% include "price.html" with price=min_unit_bom_purchase_price %} + Max: {% include "price.html" with price=max_unit_bom_purchase_price %} + + {% if quantity > 1 %} + + {% trans 'Total Purchase Price' %} + Min: {% include "price.html" with price=min_total_bom_purchase_price %} + Max: {% include "price.html" with price=max_total_bom_purchase_price %} + + {% endif %} + {% endif %} + {% if part.has_complete_bom_pricing == False %} diff --git a/InvenTree/part/templates/part/prices.html b/InvenTree/part/templates/part/prices.html index bb62ffa880..3f62ea7c73 100644 --- a/InvenTree/part/templates/part/prices.html +++ b/InvenTree/part/templates/part/prices.html @@ -61,6 +61,25 @@ Max: {% include "price.html" with price=max_total_bom_price %} {% endif %} + + + {% if min_total_bom_purchase_price %} + + + {% trans 'Unit Purchase Price' %} + Min: {% include "price.html" with price=min_unit_bom_purchase_price %} + Max: {% include "price.html" with price=max_unit_bom_purchase_price %} + + {% if quantity > 1 %} + + + {% trans 'Total Purchase Price' %} + Min: {% include "price.html" with price=min_total_bom_purchase_price %} + Max: {% include "price.html" with price=max_total_bom_purchase_price %} + + {% endif %} + {% endif %} + {% if part.has_complete_bom_pricing == False %} diff --git a/InvenTree/part/views.py b/InvenTree/part/views.py index 0e06678694..ee8699dfea 100644 --- a/InvenTree/part/views.py +++ b/InvenTree/part/views.py @@ -1351,6 +1351,7 @@ class PartPricing(AjaxView): use_internal = InvenTreeSetting.get_setting('PART_BOM_USE_INTERNAL_PRICE', False) bom_price = part.get_bom_price_range(quantity, internal=use_internal) + purchase_price = part.get_bom_price_range(quantity, purchase=True) if bom_price is not None: min_bom_price, max_bom_price = bom_price @@ -1358,19 +1359,26 @@ class PartPricing(AjaxView): min_bom_price /= scaler max_bom_price /= scaler - min_unit_bom_price = round(min_bom_price / quantity, 3) - max_unit_bom_price = round(max_bom_price / quantity, 3) - - min_bom_price = round(min_bom_price, 3) - max_bom_price = round(max_bom_price, 3) - if min_bom_price: - ctx['min_total_bom_price'] = min_bom_price - ctx['min_unit_bom_price'] = min_unit_bom_price + ctx['min_total_bom_price'] = round(min_bom_price, 3) + ctx['min_unit_bom_price'] = round(min_bom_price / quantity, 3) if max_bom_price: - ctx['max_total_bom_price'] = max_bom_price - ctx['max_unit_bom_price'] = max_unit_bom_price + ctx['max_total_bom_price'] = round(max_bom_price, 3) + ctx['max_unit_bom_price'] = round(max_bom_price / quantity, 3) + + if purchase_price is not None: + min_bom_purchase_price, max_bom_purchase_price = purchase_price + + min_bom_purchase_price /= scaler + max_bom_purchase_price /= scaler + if min_bom_purchase_price: + ctx['min_total_bom_purchase_price'] = round(min_bom_purchase_price, 3) + ctx['min_unit_bom_purchase_price'] = round(min_bom_purchase_price / quantity, 3) + + if max_bom_purchase_price: + ctx['max_total_bom_purchase_price'] = round(max_bom_purchase_price, 3) + ctx['max_unit_bom_purchase_price'] = round(max_bom_purchase_price / quantity, 3) # internal part pricing information internal_part_price = part.get_internal_price(quantity)