Remove dead pricing code on Part model (#12357)

This commit is contained in:
Oliver
2026-07-11 17:49:28 +10:00
committed by GitHub
parent 89d11e9e37
commit 4bff83108f
4 changed files with 0 additions and 262 deletions
-19
View File
@@ -1,7 +1,6 @@
"""Unit tests for the models in the 'company' app.""" """Unit tests for the models in the 'company' app."""
import os import os
from decimal import Decimal
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.test import TestCase from django.test import TestCase
@@ -99,24 +98,6 @@ class CompanySimpleTest(TestCase):
self.assertEqual(p(45), 315) self.assertEqual(p(45), 315)
self.assertEqual(p(55), 68.75) self.assertEqual(p(55), 68.75)
def test_part_pricing(self):
"""Unit tests for supplier part pricing."""
m2x4 = Part.objects.get(name='M2x4 LPHS')
self.assertEqual(m2x4.get_price_info(5.5), '38.5 - 41.25')
self.assertEqual(m2x4.get_price_info(10), '70 - 75')
self.assertEqual(m2x4.get_price_info(100), '125 - 350')
pmin, pmax = m2x4.get_price_range(5)
self.assertEqual(pmin, 35)
self.assertEqual(pmax, 37.5)
m3x12 = Part.objects.get(name='M3x12 SHCS')
self.assertEqual(m3x12.get_price_info(0.3), Decimal('2.4'))
self.assertEqual(m3x12.get_price_info(3), Decimal('24'))
self.assertIsNotNone(m3x12.get_price_info(50))
def test_currency_validation(self): def test_currency_validation(self):
"""Test validation for currency selection.""" """Test validation for currency selection."""
# Create a company with a valid currency code (should pass) # Create a company with a valid currency code (should pass)
-211
View File
@@ -2214,150 +2214,6 @@ class Part(
# which can cause issues down the track # which can cause issues down the track
pass pass
def get_price_info(self, quantity=1, buy=True, bom=True, internal=False):
"""Return a simplified pricing string for this part.
Args:
quantity: Number of units to calculate price for
buy: Include supplier pricing (default = True)
bom: Include BOM pricing (default = True)
internal: Include internal pricing (default = False)
"""
price_range = self.get_price_range(quantity, buy, bom, internal)
if price_range is None:
return None
min_price, max_price = price_range
if min_price == max_price:
return min_price
min_price = normalize(min_price)
max_price = normalize(max_price)
return f'{min_price} - {max_price}'
def get_supplier_price_range(self, quantity=1):
"""Return the supplier price range of this part.
Actions:
- Checks if there is any supplier pricing information associated with this Part
- Iterate through available supplier pricing and select (min, max)
- Returns tuple of (min, max)
Arguments:
quantity: Quantity at which to calculate price (default=1)
Returns: (min, max) tuple or (None, None) if no supplier pricing available
"""
min_price = None
max_price = None
for supplier in self.supplier_parts.all():
price = supplier.get_price(quantity)
if price is None:
continue
if min_price is None or price < min_price:
min_price = price
if max_price is None or price > max_price:
max_price = price
if min_price is None or max_price is None:
return None
min_price = normalize(min_price)
max_price = normalize(max_price)
return (min_price, max_price)
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.
Note: If the BOM contains items without pricing information,
these items cannot be included in the BOM!
"""
min_price = None
max_price = None
for item in self.get_bom_items().select_related('sub_part'):
if item.sub_part.pk == self.pk:
logger.warning('WARNING: BomItem ID %s contains itself in BOM', item.pk)
continue
q = Decimal(quantity)
i = Decimal(item.quantity)
prices = item.sub_part.get_price_range(
q * i, internal=internal, purchase=purchase
)
if prices is None:
continue
low, high = prices
if min_price is None:
min_price = 0
if max_price is None:
max_price = 0
min_price += low
max_price += high
if min_price is None or max_price is None:
return None
min_price = normalize(min_price)
max_price = normalize(max_price)
return (min_price, max_price)
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, internal or purchase price. If no pricing available, returns None
"""
# only get internal price if set and should be used
if internal and self.has_internal_price_breaks:
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
)
if buy_price_range is None:
return bom_price_range
elif bom_price_range is None:
return buy_price_range
return (
min(buy_price_range[0], bom_price_range[0]),
max(buy_price_range[1], bom_price_range[1]),
)
base_cost = models.DecimalField( base_cost = models.DecimalField(
max_digits=19, max_digits=19,
decimal_places=6, decimal_places=6,
@@ -2374,73 +2230,6 @@ class Part(
help_text=_('Sell multiple'), help_text=_('Sell multiple'),
) )
get_price = common.currency.get_price
@property
def has_price_breaks(self):
"""Return True if this part has sale price breaks."""
return self.price_breaks.exists()
@property
def price_breaks(self):
"""Return the associated price breaks in the correct order."""
return self.salepricebreaks.order_by('quantity').all()
@property
def unit_pricing(self):
"""Returns the price of this Part at quantity=1."""
return self.get_price(1)
def add_price_break(self, quantity, price):
"""Create a new price break for this part.
Args:
quantity: Numerical quantity
price: Must be a Money object
"""
# Check if a price break at that quantity already exists...
if self.price_breaks.filter(quantity=quantity, part=self.pk).exists():
return
PartSellPriceBreak.objects.create(part=self, quantity=quantity, price=price)
def get_internal_price(self, quantity, moq=True, multiples=True, currency=None):
"""Return the internal price of this Part at the specified quantity."""
return common.currency.get_price(
self, quantity, moq, multiples, currency, break_name='internal_price_breaks'
)
@property
def has_internal_price_breaks(self):
"""Return True if this Part has internal pricing information."""
return self.internal_price_breaks.exists()
@property
def internal_price_breaks(self):
"""Return the associated price breaks in the correct order."""
return self.internalpricebreaks.order_by('quantity').all()
def get_purchase_price(self, quantity):
"""Calculate the purchase price for this part at the specified quantity.
- Looks at available supplier pricing data
- Calculates the price base on the closest price point
"""
currency = currency_code_default()
try:
prices = [
convert_money(item.purchase_price, currency).amount
for item in self.stock_items.all()
if item.purchase_price
]
except MissingRate:
prices = None
if prices:
return min(prices) * quantity, max(prices) * quantity
return None
@transaction.atomic @transaction.atomic
def copy_bom_from(self, other, clear: bool = True, **kwargs): def copy_bom_from(self, other, clear: bool = True, **kwargs):
"""Copy the BOM from another part. """Copy the BOM from another part.
@@ -1,7 +1,5 @@
"""Unit tests for the BomItem model.""" """Unit tests for the BomItem model."""
from decimal import Decimal
import django.core.exceptions as django_exceptions import django.core.exceptions as django_exceptions
from django.db import transaction from django.db import transaction
from django.test import TestCase from django.test import TestCase
@@ -171,20 +169,6 @@ class BomItemTest(TestCase):
self.assertNotEqual(h1, h2) self.assertNotEqual(h1, h2)
def test_pricing(self):
"""Test BOM pricing."""
self.bob.get_price(1)
self.assertEqual(
self.bob.get_bom_price_range(1, internal=True),
(Decimal(29.5), Decimal(89.5)),
)
# remove internal price for R_2K2_0805
self.r1.internal_price_breaks.delete()
self.assertEqual(
self.bob.get_bom_price_range(1, internal=True),
(Decimal(27.5), Decimal(87.5)),
)
def test_substitutes(self): def test_substitutes(self):
"""Tests for BOM item substitutes.""" """Tests for BOM item substitutes."""
# We will make some substitute parts for the "orphan" part # We will make some substitute parts for the "orphan" part
-16
View File
@@ -250,22 +250,6 @@ class PartTest(TestCase):
barcode = self.r1.format_barcode() barcode = self.r1.format_barcode()
self.assertEqual('INV-PA3', barcode) self.assertEqual('INV-PA3', barcode)
def test_sell_pricing(self):
"""Check that the sell pricebreaks were loaded."""
self.assertTrue(self.r1.has_price_breaks)
self.assertEqual(self.r1.price_breaks.count(), 2)
# check that the sell pricebreaks work
self.assertEqual(float(self.r1.get_price(1)), 0.15)
self.assertEqual(float(self.r1.get_price(10)), 1.0)
def test_internal_pricing(self):
"""Check that the sell pricebreaks were loaded."""
self.assertTrue(self.r1.has_internal_price_breaks)
self.assertEqual(self.r1.internal_price_breaks.count(), 2)
# check that the sell pricebreaks work
self.assertEqual(float(self.r1.get_internal_price(1)), 0.08)
self.assertEqual(float(self.r1.get_internal_price(10)), 0.5)
def test_metadata(self): def test_metadata(self):
"""Unit tests for the metadata field.""" """Unit tests for the metadata field."""
for model in [Part]: for model in [Part]: