mirror of
https://github.com/inventree/InvenTree.git
synced 2025-06-16 20:15:44 +00:00
Clean out existing pricing functions
This commit is contained in:
@ -2,7 +2,6 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import decimal
|
||||
import hashlib
|
||||
import logging
|
||||
import os
|
||||
@ -44,7 +43,7 @@ from common.settings import currency_code_default
|
||||
from company.models import SupplierPart
|
||||
from InvenTree import helpers, validators
|
||||
from InvenTree.fields import InvenTreeNotesField, InvenTreeURLField
|
||||
from InvenTree.helpers import decimal2money, decimal2string, normalize
|
||||
from InvenTree.helpers import decimal2money, decimal2string
|
||||
from InvenTree.models import (DataImportMixin, InvenTreeAttachment,
|
||||
InvenTreeBarcodeMixin, InvenTreeTree)
|
||||
from InvenTree.status_codes import (BuildStatus, PurchaseOrderStatus,
|
||||
@ -1667,119 +1666,6 @@ class Part(InvenTreeBarcodeMixin, MetadataMixin, MPTTModel):
|
||||
except (PartPricing.DoesNotExist, IntegrityError):
|
||||
pass
|
||||
|
||||
def get_supplier_price_range(self, quantity=1):
|
||||
"""Return the supplier price range of this part:
|
||||
|
||||
- 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(f"WARNING: BomItem ID {item.pk} contains itself in BOM")
|
||||
continue
|
||||
|
||||
q = decimal.Decimal(quantity)
|
||||
i = decimal.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 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
|
||||
|
||||
else:
|
||||
return (
|
||||
min(buy_price_range[0], bom_price_range[0]),
|
||||
max(buy_price_range[1], bom_price_range[1])
|
||||
)
|
||||
|
||||
base_cost = models.DecimalField(max_digits=19, decimal_places=6, default=0, validators=[MinValueValidator(0)], verbose_name=_('base cost'), help_text=_('Minimum charge (e.g. stocking fee)'))
|
||||
|
||||
multiple = models.PositiveIntegerField(default=1, validators=[MinValueValidator(1)], verbose_name=_('multiple'), help_text=_('Sell multiple'))
|
||||
@ -1828,23 +1714,6 @@ class Part(InvenTreeBarcodeMixin, MetadataMixin, MPTTModel):
|
||||
"""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
|
||||
def copy_bom_from(self, other, clear=True, **kwargs):
|
||||
"""Copy the BOM from another part.
|
||||
|
@ -1,8 +1,6 @@
|
||||
|
||||
"""Unit tests for the BomItem model"""
|
||||
|
||||
from decimal import Decimal
|
||||
|
||||
import django.core.exceptions as django_exceptions
|
||||
from django.db import transaction
|
||||
from django.test import TestCase
|
||||
@ -117,20 +115,6 @@ class BomItemTest(TestCase):
|
||||
|
||||
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):
|
||||
"""Tests for BOM item substitutes."""
|
||||
# We will make some subtitute parts for the "orphan" part
|
||||
|
Reference in New Issue
Block a user