mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
More complex pricing calculations
- Calculate BOM price for a part - Calculate total pricing for a part (build or purchase) - Display pricing information in BOM table
This commit is contained in:
parent
4c82714777
commit
2a1fd2b03b
@ -569,6 +569,49 @@ class Part(models.Model):
|
|||||||
def max_single_price(self):
|
def max_single_price(self):
|
||||||
return self.get_max_supplier_price(1)
|
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
|
||||||
|
|
||||||
|
@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():
|
||||||
|
if not item.sub_part.has_pricing_info:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
@property
|
||||||
|
def single_price_info(self):
|
||||||
|
""" Return a simplified pricing string for this part at single quantity """
|
||||||
|
|
||||||
|
return self.get_price_info()
|
||||||
|
|
||||||
|
def get_price_info(self, quantity=1):
|
||||||
|
""" Return a simplified pricing string for this part """
|
||||||
|
|
||||||
|
min_price = self.get_min_price(quantity)
|
||||||
|
max_price = self.get_max_price(quantity)
|
||||||
|
|
||||||
|
if min_price is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
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):
|
def get_min_supplier_price(self, quantity=1):
|
||||||
""" Return the minimum price of this part from all available suppliers.
|
""" Return the minimum price of this part from all available suppliers.
|
||||||
|
|
||||||
@ -590,7 +633,10 @@ class Part(models.Model):
|
|||||||
if min_price is None or supplier_price < min_price:
|
if min_price is None or supplier_price < min_price:
|
||||||
min_price = supplier_price
|
min_price = supplier_price
|
||||||
|
|
||||||
return min_price
|
if min_price is None:
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
return min_price * quantity
|
||||||
|
|
||||||
def get_max_supplier_price(self, quantity=1):
|
def get_max_supplier_price(self, quantity=1):
|
||||||
""" Return the maximum price of this part from all available suppliers.
|
""" Return the maximum price of this part from all available suppliers.
|
||||||
@ -613,8 +659,99 @@ class Part(models.Model):
|
|||||||
if max_price is None or supplier_price > max_price:
|
if max_price is None or supplier_price > max_price:
|
||||||
max_price = supplier_price
|
max_price = supplier_price
|
||||||
|
|
||||||
|
if max_price is None:
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
return max_price * quantity
|
||||||
|
|
||||||
|
def get_min_bom_price(self, quantity=1):
|
||||||
|
""" Return the minimum price 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
|
||||||
|
|
||||||
|
for item in self.bom_items.all():
|
||||||
|
price = item.sub_part.get_min_price(quantity * item.quantity)
|
||||||
|
|
||||||
|
if price is None:
|
||||||
|
continue
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
return max_price
|
return max_price
|
||||||
|
|
||||||
|
def get_min_price(self, quantity=1):
|
||||||
|
""" Return the minimum price for this part. This price can be either:
|
||||||
|
|
||||||
|
- Supplier price (if purchased from suppliers)
|
||||||
|
- BOM price (if built from other parts)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Minimum of the supplier price or BOM price. If no pricing available, returns None
|
||||||
|
"""
|
||||||
|
|
||||||
|
buy_price = self.get_min_supplier_price(quantity)
|
||||||
|
bom_price = self.get_min_bom_price(quantity)
|
||||||
|
|
||||||
|
if buy_price is None:
|
||||||
|
return bom_price
|
||||||
|
|
||||||
|
if bom_price is None:
|
||||||
|
return buy_price
|
||||||
|
|
||||||
|
return min(buy_price, bom_price)
|
||||||
|
|
||||||
|
def get_max_price(self, quantity=1):
|
||||||
|
""" 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)
|
||||||
|
bom_price = self.get_max_bom_price(quantity)
|
||||||
|
|
||||||
|
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.
|
||||||
Does not alter the normal fields of this part,
|
Does not alter the normal fields of this part,
|
||||||
|
@ -34,6 +34,7 @@ class PartBriefSerializer(serializers.ModelSerializer):
|
|||||||
|
|
||||||
url = serializers.CharField(source='get_absolute_url', read_only=True)
|
url = serializers.CharField(source='get_absolute_url', read_only=True)
|
||||||
image_url = serializers.CharField(source='get_image_url', read_only=True)
|
image_url = serializers.CharField(source='get_image_url', read_only=True)
|
||||||
|
single_price_info = serializers.CharField(read_only=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Part
|
model = Part
|
||||||
@ -43,6 +44,7 @@ class PartBriefSerializer(serializers.ModelSerializer):
|
|||||||
'full_name',
|
'full_name',
|
||||||
'description',
|
'description',
|
||||||
'available_stock',
|
'available_stock',
|
||||||
|
'single_price_info',
|
||||||
'image_url',
|
'image_url',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -11,6 +11,14 @@
|
|||||||
|
|
||||||
<h3>Bill of Materials</h3>
|
<h3>Bill of Materials</h3>
|
||||||
|
|
||||||
|
{% if part.has_complete_bom_pricing == False %}
|
||||||
|
<div class='alert alert-block alert-warning'>
|
||||||
|
The BOM for <i>{{ part.full_name }}</i> does not have complete pricing information
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<div class='panel panel-default'>
|
||||||
|
Single BOM Price: {{ part.min_bom_price }} to {{ part.max_bom_price }}
|
||||||
|
</div>
|
||||||
{% if part.bom_checked_date %}
|
{% if part.bom_checked_date %}
|
||||||
{% if part.is_bom_valid %}
|
{% if part.is_bom_valid %}
|
||||||
<div class='alert alert-block alert-info'>
|
<div class='alert alert-block alert-info'>
|
||||||
|
@ -151,6 +151,19 @@ function loadBomTable(table, options) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
cols.push({
|
||||||
|
field: 'sub_part_detail.single_price_info',
|
||||||
|
title: 'Price',
|
||||||
|
sortable: true,
|
||||||
|
formatter: function(value, row, index, field) {
|
||||||
|
if (value) {
|
||||||
|
return value;
|
||||||
|
} else {
|
||||||
|
return "<span class='warning-msg'><i>No pricing information</i></span>";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Part notes
|
// Part notes
|
||||||
|
Loading…
Reference in New Issue
Block a user