2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-06-17 12:35:46 +00:00

Clean out existing pricing functions

This commit is contained in:
Oliver Walters
2023-02-21 21:32:29 +11:00
parent 7c9ad4ff7f
commit 234134d8f9
2 changed files with 1 additions and 148 deletions

View File

@ -2,7 +2,6 @@
from __future__ import annotations from __future__ import annotations
import decimal
import hashlib import hashlib
import logging import logging
import os import os
@ -44,7 +43,7 @@ from common.settings import currency_code_default
from company.models import SupplierPart from company.models import SupplierPart
from InvenTree import helpers, validators from InvenTree import helpers, validators
from InvenTree.fields import InvenTreeNotesField, InvenTreeURLField 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, from InvenTree.models import (DataImportMixin, InvenTreeAttachment,
InvenTreeBarcodeMixin, InvenTreeTree) InvenTreeBarcodeMixin, InvenTreeTree)
from InvenTree.status_codes import (BuildStatus, PurchaseOrderStatus, from InvenTree.status_codes import (BuildStatus, PurchaseOrderStatus,
@ -1667,119 +1666,6 @@ class Part(InvenTreeBarcodeMixin, MetadataMixin, MPTTModel):
except (PartPricing.DoesNotExist, IntegrityError): except (PartPricing.DoesNotExist, IntegrityError):
pass 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)')) 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')) 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 the associated price breaks in the correct order."""
return self.internalpricebreaks.order_by('quantity').all() 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=True, **kwargs): def copy_bom_from(self, other, clear=True, **kwargs):
"""Copy the BOM from another part. """Copy the BOM from another part.

View File

@ -1,8 +1,6 @@
"""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
@ -117,20 +115,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 subtitute parts for the "orphan" part # We will make some subtitute parts for the "orphan" part