Cleanup pricing algorithms

This commit is contained in:
Oliver Walters 2019-05-20 23:53:39 +10:00
parent 8adb4f6c20
commit 6ae48d07c4
3 changed files with 71 additions and 139 deletions

View File

@ -250,7 +250,7 @@ class SupplierPart(models.Model):
- 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?
if len(price_breaks) == 0:

View File

@ -573,32 +573,16 @@ class Part(models.Model):
""" Return the number of supplier parts available for this part """
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
def has_pricing_info(self):
""" 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
def has_complete_bom_pricing(self):
""" 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:
return False
@ -619,71 +603,44 @@ class Part(models.Model):
bom: Include BOM pricing (default = True)
"""
min_price = self.get_min_price(quantity, buy, bom)
max_price = self.get_max_price(quantity, buy, bom)
price_range = self.get_price_range(quantity, buy, bom)
if min_price is None:
if price_range is None:
return None
min_price, max_price = price_range
if min_price == max_price:
return min_price
return "{a} to {b}".format(a=min_price, b=max_price)
def get_min_supplier_price(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
"""
def get_supplier_price_range(self, quantity=1):
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
for supplier_part in self.supplier_parts.all():
supplier_price = supplier_part.get_price(quantity)
for supplier in self.supplier_parts.all():
if supplier_price is None:
price = supplier.get_price(quantity)
if price is None:
continue
if max_price is None or supplier_price > max_price:
max_price = supplier_price
if min_price is None or price < min_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
else:
return max_price
def get_min_bom_price(self, quantity=1):
""" Return the minimum price of the BOM for this part.
return (min_price, max_price)
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.
Note: If the BOM contains items without pricing information,
@ -691,45 +648,33 @@ class Part(models.Model):
"""
min_price = None
max_price = None
for item in self.bom_items.all():
price = item.sub_part.get_min_price(quantity * item.quantity)
for item in self.bom_items.all().prefetch_related('sub_part'):
prices = item.sub_part.get_price_range(quantity * item.quantity)
if price is None:
if prices is None:
continue
low, high = prices
if min_price is None:
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:
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 the minimum price for this part. This price can be either:
return (min_price, max_price)
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)
- 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
"""
buy_price = self.get_min_supplier_price(quantity) if buy else None
bom_price = self.get_min_bom_price(quantity) if bom else None
buy_price_range = self.get_supplier_price_range(quantity) if buy else None
bom_price_range = self.get_bom_price_range(quantity) if bom else None
if buy_price is None:
return bom_price
if buy_price_range is None:
return bom_price_range
if bom_price is None:
return buy_price
elif bom_price_range is None:
return buy_price_range
return min(buy_price, bom_price)
def get_max_price(self, quantity=1, buy=True, bom=True):
""" Return the maximum price for this part. This price can be either:
- 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)
else:
return (
min(buy_price_range[0], bom_price_range[0]),
max(buy_price_range[1], bom_price_range[1])
)
def deepCopy(self, other, **kwargs):
""" Duplicates non-field data from another part.

View File

@ -587,30 +587,34 @@ class PartPricing(AjaxView):
# Supplier pricing information
if part.supplier_count > 0:
min_buy_price = part.get_min_supplier_price(quantity)
max_buy_price = part.get_max_supplier_price(quantity)
buy_price = part.get_supplier_price_range(quantity)
if min_buy_price:
ctx['min_total_buy_price'] = min_buy_price
ctx['min_unit_buy_price'] = min_buy_price / quantity
if buy_price is not None:
min_buy_price, max_buy_price = buy_price
if max_buy_price:
ctx['max_total_buy_price'] = max_buy_price
ctx['max_unit_buy_price'] = max_buy_price / quantity
if min_buy_price:
ctx['min_total_buy_price'] = min_buy_price
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
if part.bom_count > 0:
min_bom_price = part.get_min_bom_price(quantity)
max_bom_price = part.get_max_bom_price(quantity)
bom_price = part.get_bom_price_range(quantity)
if min_bom_price:
ctx['min_total_bom_price'] = min_bom_price
ctx['min_unit_bom_price'] = min_bom_price / quantity
if bom_price is not None:
min_bom_price, max_bom_price = bom_price
if max_bom_price:
ctx['max_total_bom_price'] = max_bom_price
ctx['max_unit_bom_price'] = max_bom_price / quantity
if min_bom_price:
ctx['min_total_bom_price'] = min_bom_price
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