From a2ffd06abf3c50b19337e343ca583350ee4131d8 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 13 Aug 2021 17:49:58 +0200 Subject: [PATCH 1/4] calculate purchase price for part --- InvenTree/part/models.py | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/InvenTree/part/models.py b/InvenTree/part/models.py index 50d3044771..1e45ea5f4c 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,11 @@ 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()] + return min(prices) * quantity, max(prices) * quantity + @transaction.atomic def copy_bom_from(self, other, clear=True, **kwargs): """ From f0325fe30fac874f5fb1f4888c6979605cc30f02 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 13 Aug 2021 17:50:41 +0200 Subject: [PATCH 2/4] view BOMpricing range Closes #1889 --- .../part/templates/part/part_pricing.html | 15 ++++++++++ InvenTree/part/templates/part/prices.html | 19 ++++++++++++ InvenTree/part/views.py | 29 ++++++++++++------- 3 files changed, 53 insertions(+), 10 deletions(-) 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 e498bc09ba..57967bf1d8 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..5d7a083e9b 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,27 @@ 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) From 00d4efb920568c7cb691cef6e788d5aaf00b05d3 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 13 Aug 2021 17:54:17 +0200 Subject: [PATCH 3/4] PEP fix --- InvenTree/part/views.py | 1 - 1 file changed, 1 deletion(-) diff --git a/InvenTree/part/views.py b/InvenTree/part/views.py index 5d7a083e9b..ee8699dfea 100644 --- a/InvenTree/part/views.py +++ b/InvenTree/part/views.py @@ -1380,7 +1380,6 @@ class PartPricing(AjaxView): 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) if internal_part_price is not None: From cb1e7a6cc5466d2b81d8e1e1426bcda8d994d93c Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 19 Aug 2021 23:22:58 +0200 Subject: [PATCH 4/4] only process purchase_price if prices present --- InvenTree/part/models.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/InvenTree/part/models.py b/InvenTree/part/models.py index 1e45ea5f4c..d7ad577081 100644 --- a/InvenTree/part/models.py +++ b/InvenTree/part/models.py @@ -1652,8 +1652,10 @@ class Part(MPTTModel): 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()] - return min(prices) * quantity, max(prices) * quantity + 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):