mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Cleanup pricing algorithms
This commit is contained in:
parent
8adb4f6c20
commit
6ae48d07c4
@ -250,7 +250,7 @@ class SupplierPart(models.Model):
|
|||||||
- If order multiples are to be observed, then we need to calculate based on that, too
|
- If order multiples are to be observed, then we need to calculate based on that, too
|
||||||
"""
|
"""
|
||||||
|
|
||||||
price_breaks = self.price_breaks.all()
|
price_breaks = self.price_breaks.filter(quantity__lte=quantity)
|
||||||
|
|
||||||
# No price break information available?
|
# No price break information available?
|
||||||
if len(price_breaks) == 0:
|
if len(price_breaks) == 0:
|
||||||
|
@ -573,32 +573,16 @@ class Part(models.Model):
|
|||||||
""" Return the number of supplier parts available for this part """
|
""" Return the number of supplier parts available for this part """
|
||||||
return self.supplier_parts.count()
|
return self.supplier_parts.count()
|
||||||
|
|
||||||
@property
|
|
||||||
def min_single_price(self):
|
|
||||||
return self.get_min_supplier_price(1)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def max_single_price(self):
|
|
||||||
return self.get_max_supplier_price(1)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def min_bom_price(self):
|
|
||||||
return self.get_min_bom_price(1)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def max_bom_price(self):
|
|
||||||
return self.get_max_bom_price(1)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def has_pricing_info(self):
|
def has_pricing_info(self):
|
||||||
""" Return true if there is pricing information for this part """
|
""" Return true if there is pricing information for this part """
|
||||||
return self.get_min_price() is not None
|
return self.get_price_range() is not None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def has_complete_bom_pricing(self):
|
def has_complete_bom_pricing(self):
|
||||||
""" Return true if there is pricing information for each item in the BOM. """
|
""" Return true if there is pricing information for each item in the BOM. """
|
||||||
|
|
||||||
for item in self.bom_items.all():
|
for item in self.bom_items.all().prefetch_related('sub_part'):
|
||||||
if not item.sub_part.has_pricing_info:
|
if not item.sub_part.has_pricing_info:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@ -619,71 +603,44 @@ class Part(models.Model):
|
|||||||
bom: Include BOM pricing (default = True)
|
bom: Include BOM pricing (default = True)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
min_price = self.get_min_price(quantity, buy, bom)
|
price_range = self.get_price_range(quantity, buy, bom)
|
||||||
max_price = self.get_max_price(quantity, buy, bom)
|
|
||||||
|
|
||||||
if min_price is None:
|
if price_range is None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
min_price, max_price = price_range
|
||||||
|
|
||||||
if min_price == max_price:
|
if min_price == max_price:
|
||||||
return min_price
|
return min_price
|
||||||
|
|
||||||
return "{a} to {b}".format(a=min_price, b=max_price)
|
return "{a} to {b}".format(a=min_price, b=max_price)
|
||||||
|
|
||||||
def get_min_supplier_price(self, quantity=1):
|
def get_supplier_price_range(self, quantity=1):
|
||||||
""" Return the minimum price of this part from all available suppliers.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
quantity: Number of units we wish to purchase (default = 1)
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Numerical price if pricing is available, else None
|
|
||||||
"""
|
|
||||||
|
|
||||||
min_price = None
|
min_price = None
|
||||||
|
|
||||||
for supplier_part in self.supplier_parts.all():
|
|
||||||
supplier_price = supplier_part.get_price(quantity)
|
|
||||||
|
|
||||||
if supplier_price is None:
|
|
||||||
continue
|
|
||||||
|
|
||||||
if min_price is None or supplier_price < min_price:
|
|
||||||
min_price = supplier_price
|
|
||||||
|
|
||||||
if min_price is None:
|
|
||||||
return None
|
|
||||||
else:
|
|
||||||
return min_price
|
|
||||||
|
|
||||||
def get_max_supplier_price(self, quantity=1):
|
|
||||||
""" Return the maximum price of this part from all available suppliers.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
quantity: Number of units we wish to purchase (default = 1)
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Numerical price if pricing is available, else None
|
|
||||||
"""
|
|
||||||
|
|
||||||
max_price = None
|
max_price = None
|
||||||
|
|
||||||
for supplier_part in self.supplier_parts.all():
|
for supplier in self.supplier_parts.all():
|
||||||
supplier_price = supplier_part.get_price(quantity)
|
|
||||||
|
|
||||||
if supplier_price is None:
|
price = supplier.get_price(quantity)
|
||||||
|
|
||||||
|
if price is None:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if max_price is None or supplier_price > max_price:
|
if min_price is None or price < min_price:
|
||||||
max_price = supplier_price
|
min_price = price
|
||||||
|
|
||||||
if max_price is None:
|
if max_price is None or price > max_price:
|
||||||
|
max_price = price
|
||||||
|
|
||||||
|
if min_price is None or max_price is None:
|
||||||
return None
|
return None
|
||||||
else:
|
|
||||||
return max_price
|
|
||||||
|
|
||||||
def get_min_bom_price(self, quantity=1):
|
return (min_price, max_price)
|
||||||
""" Return the minimum price of the BOM for this part.
|
|
||||||
|
|
||||||
|
def get_bom_price_range(self, quantity=1):
|
||||||
|
""" Return the price range of the BOM for this part.
|
||||||
Adds the minimum price for all components in the BOM.
|
Adds the minimum price for all components in the BOM.
|
||||||
|
|
||||||
Note: If the BOM contains items without pricing information,
|
Note: If the BOM contains items without pricing information,
|
||||||
@ -691,45 +648,33 @@ class Part(models.Model):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
min_price = None
|
min_price = None
|
||||||
|
max_price = None
|
||||||
|
|
||||||
for item in self.bom_items.all():
|
for item in self.bom_items.all().prefetch_related('sub_part'):
|
||||||
price = item.sub_part.get_min_price(quantity * item.quantity)
|
prices = item.sub_part.get_price_range(quantity * item.quantity)
|
||||||
|
|
||||||
if price is None:
|
if prices is None:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
low, high = prices
|
||||||
|
|
||||||
if min_price is None:
|
if min_price is None:
|
||||||
min_price = 0
|
min_price = 0
|
||||||
|
|
||||||
min_price += price
|
|
||||||
|
|
||||||
return min_price
|
|
||||||
|
|
||||||
def get_max_bom_price(self, quantity=1):
|
|
||||||
""" Return the maximum price of the BOM for this part.
|
|
||||||
Adds the maximum price for all components in the BOM.
|
|
||||||
|
|
||||||
Note: If the BOM contains items without pricing information,
|
|
||||||
these items cannot be included in the BOM!
|
|
||||||
"""
|
|
||||||
|
|
||||||
max_price = None
|
|
||||||
|
|
||||||
for item in self.bom_items.all():
|
|
||||||
price = item.sub_part.get_max_price(quantity * item.quantity)
|
|
||||||
|
|
||||||
if price is None:
|
|
||||||
continue
|
|
||||||
|
|
||||||
if max_price is None:
|
if max_price is None:
|
||||||
max_price = 0
|
max_price = 0
|
||||||
|
|
||||||
max_price += price
|
min_price += low
|
||||||
|
max_price += high
|
||||||
|
|
||||||
return max_price
|
if min_price is None or max_price is None:
|
||||||
|
return None
|
||||||
|
|
||||||
def get_min_price(self, quantity=1, buy=True, bom=True):
|
return (min_price, max_price)
|
||||||
""" Return the minimum price for this part. This price can be either:
|
|
||||||
|
def get_price_range(self, quantity=1, buy=True, bom=True):
|
||||||
|
|
||||||
|
""" Return the price range for this part. This price can be either:
|
||||||
|
|
||||||
- Supplier price (if purchased from suppliers)
|
- Supplier price (if purchased from suppliers)
|
||||||
- BOM price (if built from other parts)
|
- BOM price (if built from other parts)
|
||||||
@ -738,37 +683,20 @@ class Part(models.Model):
|
|||||||
Minimum of the supplier price or BOM price. If no pricing available, returns None
|
Minimum of the supplier price or BOM price. If no pricing available, returns None
|
||||||
"""
|
"""
|
||||||
|
|
||||||
buy_price = self.get_min_supplier_price(quantity) if buy else None
|
buy_price_range = self.get_supplier_price_range(quantity) if buy else None
|
||||||
bom_price = self.get_min_bom_price(quantity) if bom else None
|
bom_price_range = self.get_bom_price_range(quantity) if bom else None
|
||||||
|
|
||||||
if buy_price is None:
|
if buy_price_range is None:
|
||||||
return bom_price
|
return bom_price_range
|
||||||
|
|
||||||
if bom_price is None:
|
elif bom_price_range is None:
|
||||||
return buy_price
|
return buy_price_range
|
||||||
|
|
||||||
return min(buy_price, bom_price)
|
else:
|
||||||
|
return (
|
||||||
def get_max_price(self, quantity=1, buy=True, bom=True):
|
min(buy_price_range[0], bom_price_range[0]),
|
||||||
""" Return the maximum price for this part. This price can be either:
|
max(buy_price_range[1], bom_price_range[1])
|
||||||
|
)
|
||||||
- Supplier price (if purchsed from suppliers)
|
|
||||||
- BOM price (if built from other parts)
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Maximum of the supplier price or BOM price. If no pricing available, returns None
|
|
||||||
"""
|
|
||||||
|
|
||||||
buy_price = self.get_max_supplier_price(quantity) if buy else None
|
|
||||||
bom_price = self.get_max_bom_price(quantity) if bom else None
|
|
||||||
|
|
||||||
if buy_price is None:
|
|
||||||
return bom_price
|
|
||||||
|
|
||||||
if bom_price is None:
|
|
||||||
return buy_price
|
|
||||||
|
|
||||||
return max(buy_price, bom_price)
|
|
||||||
|
|
||||||
def deepCopy(self, other, **kwargs):
|
def deepCopy(self, other, **kwargs):
|
||||||
""" Duplicates non-field data from another part.
|
""" Duplicates non-field data from another part.
|
||||||
|
@ -587,30 +587,34 @@ class PartPricing(AjaxView):
|
|||||||
|
|
||||||
# Supplier pricing information
|
# Supplier pricing information
|
||||||
if part.supplier_count > 0:
|
if part.supplier_count > 0:
|
||||||
min_buy_price = part.get_min_supplier_price(quantity)
|
buy_price = part.get_supplier_price_range(quantity)
|
||||||
max_buy_price = part.get_max_supplier_price(quantity)
|
|
||||||
|
|
||||||
if min_buy_price:
|
if buy_price is not None:
|
||||||
ctx['min_total_buy_price'] = min_buy_price
|
min_buy_price, max_buy_price = buy_price
|
||||||
ctx['min_unit_buy_price'] = min_buy_price / quantity
|
|
||||||
|
|
||||||
if max_buy_price:
|
if min_buy_price:
|
||||||
ctx['max_total_buy_price'] = max_buy_price
|
ctx['min_total_buy_price'] = min_buy_price
|
||||||
ctx['max_unit_buy_price'] = max_buy_price / quantity
|
ctx['min_unit_buy_price'] = min_buy_price / quantity
|
||||||
|
|
||||||
|
if max_buy_price:
|
||||||
|
ctx['max_total_buy_price'] = max_buy_price
|
||||||
|
ctx['max_unit_buy_price'] = max_buy_price / quantity
|
||||||
|
|
||||||
# BOM pricing information
|
# BOM pricing information
|
||||||
if part.bom_count > 0:
|
if part.bom_count > 0:
|
||||||
|
|
||||||
min_bom_price = part.get_min_bom_price(quantity)
|
bom_price = part.get_bom_price_range(quantity)
|
||||||
max_bom_price = part.get_max_bom_price(quantity)
|
|
||||||
|
|
||||||
if min_bom_price:
|
if bom_price is not None:
|
||||||
ctx['min_total_bom_price'] = min_bom_price
|
min_bom_price, max_bom_price = bom_price
|
||||||
ctx['min_unit_bom_price'] = min_bom_price / quantity
|
|
||||||
|
|
||||||
if max_bom_price:
|
if min_bom_price:
|
||||||
ctx['max_total_bom_price'] = max_bom_price
|
ctx['min_total_bom_price'] = min_bom_price
|
||||||
ctx['max_unit_bom_price'] = max_bom_price / quantity
|
ctx['min_unit_bom_price'] = min_bom_price / quantity
|
||||||
|
|
||||||
|
if max_bom_price:
|
||||||
|
ctx['max_total_bom_price'] = max_bom_price
|
||||||
|
ctx['max_unit_bom_price'] = max_bom_price / quantity
|
||||||
|
|
||||||
return ctx
|
return ctx
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user