diff --git a/InvenTree/common/models.py b/InvenTree/common/models.py
index 283a386c59..900ab57669 100644
--- a/InvenTree/common/models.py
+++ b/InvenTree/common/models.py
@@ -390,9 +390,12 @@ class BaseInvenTreeSetting(models.Model):
if create:
# Attempt to create a new settings object
+
+ default_value = cls.get_setting_default(key, **kwargs)
+
setting = cls(
key=key,
- value=cls.get_setting_default(key, **kwargs),
+ value=default_value,
**kwargs
)
@@ -1173,6 +1176,24 @@ class InvenTreeSetting(BaseInvenTreeSetting):
'validator': bool,
},
+ 'PRICING_USE_STOCK_PRICING': {
+ 'name': _('Use Stock Item Pricing'),
+ 'description': _('Use pricing from manually entered stock data for pricing calculations'),
+ 'default': True,
+ 'validator': bool,
+ },
+
+ 'PRICING_STOCK_ITEM_AGE_DAYS': {
+ 'name': _('Stock Item Pricing Age'),
+ 'description': _('Exclude stock items older than this number of days from pricing calculations'),
+ 'default': 0,
+ 'units': 'days',
+ 'validator': [
+ int,
+ MinValueValidator(0),
+ ]
+ },
+
'PRICING_USE_VARIANT_PRICING': {
'name': _('Use Variant Pricing'),
'description': _('Include variant pricing in overall pricing calculations'),
diff --git a/InvenTree/order/templates/order/order_base.html b/InvenTree/order/templates/order/order_base.html
index 057d53aad1..ff0a13f0d7 100644
--- a/InvenTree/order/templates/order/order_base.html
+++ b/InvenTree/order/templates/order/order_base.html
@@ -195,7 +195,7 @@ src="{% static 'img/blank_image.png' %}"
{% if tp == None %}
{% trans "Total cost could not be calculated" %}
{% else %}
- {{ tp }}
+ {% include "price_data.html" with price=tp %}
{% endif %}
{% endwith %}
diff --git a/InvenTree/order/templates/order/sales_order_base.html b/InvenTree/order/templates/order/sales_order_base.html
index cb7f998578..b0ab15be67 100644
--- a/InvenTree/order/templates/order/sales_order_base.html
+++ b/InvenTree/order/templates/order/sales_order_base.html
@@ -193,7 +193,7 @@ src="{% static 'img/blank_image.png' %}"
{% if tp == None %}
{% trans "Total cost could not be calculated" %}
{% else %}
- {{ tp }}
+ {% include "price_data.html" with price=tp %}
{% endif %}
{% endwith %}
diff --git a/InvenTree/part/models.py b/InvenTree/part/models.py
index 18e23b1ec6..96ffca62a4 100644
--- a/InvenTree/part/models.py
+++ b/InvenTree/part/models.py
@@ -6,7 +6,7 @@ import decimal
import hashlib
import logging
import os
-from datetime import datetime
+from datetime import datetime, timedelta
from decimal import Decimal, InvalidOperation
from django.contrib.auth.models import User
@@ -2510,6 +2510,31 @@ class PartPricing(common.models.MetaMixin):
if purchase_max is None or purchase_cost > purchase_max:
purchase_max = purchase_cost
+ # Also check if manual stock item pricing is included
+ if InvenTreeSetting.get_setting('PRICING_USE_STOCK_PRICING', True, cache=False):
+
+ items = self.part.stock_items.all()
+
+ # Limit to stock items updated within a certain window
+ days = int(InvenTreeSetting.get_setting('PRICING_STOCK_ITEM_AGE_DAYS', 0, cache=False))
+
+ if days > 0:
+ date_threshold = datetime.now().date() - timedelta(days=days)
+ items = items.filter(updated__gte=date_threshold)
+
+ for item in items:
+ cost = self.convert(item.purchase_price)
+
+ # Skip if the cost could not be converted (for some reason)
+ if cost is None:
+ continue
+
+ if purchase_min is None or cost < purchase_min:
+ purchase_min = cost
+
+ if purchase_max is None or cost > purchase_max:
+ purchase_max = cost
+
self.purchase_cost_min = purchase_min
self.purchase_cost_max = purchase_max
@@ -2651,6 +2676,7 @@ class PartPricing(common.models.MetaMixin):
max_costs.append(self.supplier_price_max)
if InvenTreeSetting.get_setting('PRICING_USE_VARIANT_PRICING', True, cache=False):
+ # Include variant pricing in overall calculations
min_costs.append(self.variant_cost_min)
max_costs.append(self.variant_cost_max)
diff --git a/InvenTree/part/templates/part/prices.html b/InvenTree/part/templates/part/prices.html
index e34b2d3f09..380720408b 100644
--- a/InvenTree/part/templates/part/prices.html
+++ b/InvenTree/part/templates/part/prices.html
@@ -45,9 +45,7 @@
{% endif %}
-
- {% trans "Internal Pricing" %}
- |
+ {% trans "Internal Pricing" %} |
{% include "price_data.html" with price=pricing.internal_cost_min %} |
{% include "price_data.html" with price=pricing.internal_cost_max %} |
@@ -60,9 +58,7 @@
{% endif %}
-
- {% trans "Purchase History" %}
- |
+ {% trans "Purchase History" %} |
{% include "price_data.html" with price=pricing.purchase_cost_min %} |
{% include "price_data.html" with price=pricing.purchase_cost_max %} |
@@ -74,9 +70,7 @@
{% endif %}
-
- {% trans "Supplier Pricing" %}
- |
+ {% trans "Supplier Pricing" %} |
{% include "price_data.html" with price=pricing.supplier_price_min %} |
{% include "price_data.html" with price=pricing.supplier_price_max %} |
@@ -90,9 +84,7 @@
{% endif %}
-
- {% trans "BOM Pricing" %}
- |
+ {% trans "BOM Pricing" %} |
{% include "price_data.html" with price=pricing.bom_cost_min %} |
{% include "price_data.html" with price=pricing.bom_cost_max %} |
@@ -107,9 +99,7 @@
{% endif %}
|
-
- {% trans "Overall Pricing" %}
- |
+ {% trans "Overall Pricing" %} |
{% include "price_data.html" with price=pricing.overall_min %} |
{% include "price_data.html" with price=pricing.overall_max %} |
@@ -135,15 +125,9 @@
-
- {% trans "Sale Price" %}
- |
-
- {% include "price_data.html" with price=pricing.sale_price_min %}
- |
-
- {% include "price_data.html" with price=pricing.sale_price_max %}
- |
+ {% trans "Sale Price" %} |
+ {% include "price_data.html" with price=pricing.sale_price_min %} |
+ {% include "price_data.html" with price=pricing.sale_price_max %} |
@@ -151,15 +135,9 @@
|
-
- {% trans "Sale History" %}
- |
-
- {% include "price_data.html" with price=pricing.sale_history_min %}
- |
-
- {% include "price_data.html" with price=pricing.sale_history_max %}
- |
+ {% trans "Sale History" %} |
+ {% include "price_data.html" with price=pricing.sale_history_min %} |
+ {% include "price_data.html" with price=pricing.sale_history_max %} |
diff --git a/InvenTree/part/test_pricing.py b/InvenTree/part/test_pricing.py
index b938e73073..8e715e4d7c 100644
--- a/InvenTree/part/test_pricing.py
+++ b/InvenTree/part/test_pricing.py
@@ -10,6 +10,7 @@ import common.settings
import company.models
import order.models
import part.models
+import stock.models
from InvenTree.helpers import InvenTreeTestCase
from InvenTree.status_codes import PurchaseOrderStatus
@@ -234,6 +235,54 @@ class PartPricingTests(InvenTreeTestCase):
self.assertEqual(pricing.internal_cost_max, Money(10, currency))
self.assertEqual(pricing.overall_max, Money(10, currency))
+ def test_stock_item_pricing(self):
+ """Test for stock item pricing data"""
+
+ # Create a part
+ p = part.models.Part.objects.create(
+ name='Test part for pricing',
+ description='hello world',
+ )
+
+ # Create some stock items
+ prices = [
+ (10, 'AUD'),
+ (5, 'USD'),
+ (2, 'CAD'),
+ ]
+
+ for price, currency in prices:
+
+ stock.models.StockItem.objects.create(
+ part=p,
+ quantity=10,
+ purchase_price=price,
+ purchase_price_currency=currency
+ )
+
+ # Ensure that initially, stock item pricing is disabled
+ common.models.InvenTreeSetting.set_setting('PRICING_USE_STOCK_PRICING', False, None)
+
+ pricing = p.pricing
+ pricing.update_pricing()
+
+ # Check that stock item pricing data is not used
+ self.assertIsNone(pricing.purchase_cost_min)
+ self.assertIsNone(pricing.purchase_cost_max)
+ self.assertIsNone(pricing.overall_min)
+ self.assertIsNone(pricing.overall_max)
+
+ # Turn on stock pricing
+ common.models.InvenTreeSetting.set_setting('PRICING_USE_STOCK_PRICING', True, None)
+
+ pricing.update_pricing()
+
+ self.assertIsNotNone(pricing.purchase_cost_min)
+ self.assertIsNotNone(pricing.purchase_cost_max)
+
+ self.assertEqual(pricing.overall_min, Money(1.176471, 'USD'))
+ self.assertEqual(pricing.overall_max, Money(6.666667, 'USD'))
+
def test_bom_pricing(self):
"""Unit test for BOM pricing calculations"""
diff --git a/InvenTree/stock/models.py b/InvenTree/stock/models.py
index 58c9065e49..2d0cfdb86d 100644
--- a/InvenTree/stock/models.py
+++ b/InvenTree/stock/models.py
@@ -1991,6 +1991,10 @@ def after_delete_stock_item(sender, instance: StockItem, **kwargs):
# Run this check in the background
InvenTree.tasks.offload_task(part_tasks.notify_low_stock_if_required, instance.part)
+ # Schedule an update on parent part pricing
+ if InvenTree.ready.canAppAccessDatabase():
+ instance.part.schedule_pricing_update()
+
@receiver(post_save, sender=StockItem, dispatch_uid='stock_item_post_save_log')
def after_save_stock_item(sender, instance: StockItem, created, **kwargs):
@@ -2001,6 +2005,9 @@ def after_save_stock_item(sender, instance: StockItem, created, **kwargs):
# Run this check in the background
InvenTree.tasks.offload_task(part_tasks.notify_low_stock_if_required, instance.part)
+ if InvenTree.ready.canAppAccessDatabase():
+ instance.part.schedule_pricing_update()
+
class StockItemAttachment(InvenTreeAttachment):
"""Model for storing file attachments against a StockItem object."""
diff --git a/InvenTree/stock/templates/stock/item_base.html b/InvenTree/stock/templates/stock/item_base.html
index c46f39ed39..16e023fac9 100644
--- a/InvenTree/stock/templates/stock/item_base.html
+++ b/InvenTree/stock/templates/stock/item_base.html
@@ -187,7 +187,7 @@
|
{% trans "Purchase Price" %} |
- {{ item.purchase_price }} |
+ {% include "price_data.html" with price=item.purchase_price %} |
{% endif %}
{% if item.parent %}
diff --git a/InvenTree/templates/InvenTree/settings/pricing.html b/InvenTree/templates/InvenTree/settings/pricing.html
index f05103e95e..fc9bb88f77 100644
--- a/InvenTree/templates/InvenTree/settings/pricing.html
+++ b/InvenTree/templates/InvenTree/settings/pricing.html
@@ -15,8 +15,11 @@
{% include "InvenTree/settings/setting.html" with key="PART_BOM_USE_INTERNAL_PRICE" %}
{% include "InvenTree/settings/setting.html" with key="PRICING_DECIMAL_PLACES" %}
{% include "InvenTree/settings/setting.html" with key="PRICING_UPDATE_DAYS" icon='fa-calendar-alt' %}
+ |
{% include "InvenTree/settings/setting.html" with key="PRICING_USE_SUPPLIER_PRICING" icon='fa-check-circle' %}
{% include "InvenTree/settings/setting.html" with key="PRICING_PURCHASE_HISTORY_OVERRIDES_SUPPLIER" icon='fa-shopping-cart' %}
+ {% include "InvenTree/settings/setting.html" with key="PRICING_USE_STOCK_PRICING" icon='fa-boxes' %}
+ {% include "InvenTree/settings/setting.html" with key="PRICING_STOCK_ITEM_AGE_DAYS" icon='fa-calendar-alt' %}
{% include "InvenTree/settings/setting.html" with key="PRICING_USE_VARIANT_PRICING" icon='fa-check-circle' %}
{% include "InvenTree/settings/setting.html" with key="PRICING_ACTIVE_VARIANTS" %}
@@ -57,6 +60,7 @@
|
{% trans "Base Currency" %} |
{{ base_currency }} |
+ |
|
@@ -70,7 +74,6 @@
|
{{ rate.currency }} |
{{ rate.value }} |
- |
{% endfor %}
diff --git a/InvenTree/templates/InvenTree/settings/setting.html b/InvenTree/templates/InvenTree/settings/setting.html
index 4a9192924a..1b9a020658 100644
--- a/InvenTree/templates/InvenTree/settings/setting.html
+++ b/InvenTree/templates/InvenTree/settings/setting.html
@@ -27,14 +27,14 @@
{% else %}
- {% if setting.value %}
+ {% if setting.value == '' %}
+ {% trans "No value set" %}
+ {% else %}
{% if setting.is_choice %}
{{ setting.as_choice }}
{% else %}
{{ setting.value }}
{% endif %}
- {% else %}
- {% trans "No value set" %}
{% endif %}
{{ setting.units }}