2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-04-30 04:26:44 +00:00

Merge pull request #1957 from matmair/bpm-purchase-price

BOM - show purchase price
This commit is contained in:
Oliver 2021-08-23 11:07:30 +10:00 committed by GitHub
commit fd1dd792c6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 72 additions and 14 deletions

View File

@ -35,6 +35,8 @@ from stdimage.models import StdImageField
from decimal import Decimal, InvalidOperation from decimal import Decimal, InvalidOperation
from datetime import datetime from datetime import datetime
import hashlib import hashlib
from djmoney.contrib.exchange.models import convert_money
from common.settings import currency_code_default
from InvenTree import helpers from InvenTree import helpers
from InvenTree import validators from InvenTree import validators
@ -1514,7 +1516,7 @@ class Part(MPTTModel):
return (min_price, max_price) return (min_price, max_price)
def get_bom_price_range(self, quantity=1, internal=False): def get_bom_price_range(self, quantity=1, internal=False, purchase=False):
""" Return the price range of the BOM for this part. """ 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.
@ -1531,7 +1533,7 @@ class Part(MPTTModel):
print("Warning: Item contains itself in BOM") print("Warning: Item contains itself in BOM")
continue continue
prices = item.sub_part.get_price_range(quantity * item.quantity, internal=internal) prices = item.sub_part.get_price_range(quantity * item.quantity, internal=internal, purchase=purchase)
if prices is None: if prices is None:
continue continue
@ -1555,16 +1557,17 @@ class Part(MPTTModel):
return (min_price, max_price) return (min_price, max_price)
def get_price_range(self, quantity=1, buy=True, bom=True, internal=False): 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: """ 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)
- Internal price (if set for the part) - Internal price (if set for the part)
- Purchase price (if set for the part)
Returns: Returns:
Minimum of the supplier, BOM or internal price. If no pricing available, returns None Minimum of the supplier, BOM, internal or purchase price. If no pricing available, returns None
""" """
# only get internal price if set and should be used # only get internal price if set and should be used
@ -1572,6 +1575,12 @@ class Part(MPTTModel):
internal_price = self.get_internal_price(quantity) internal_price = self.get_internal_price(quantity)
return internal_price, internal_price return internal_price, internal_price
# 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 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 bom_price_range = self.get_bom_price_range(quantity, internal=internal) if bom else None
@ -1641,6 +1650,13 @@ class Part(MPTTModel):
def internal_unit_pricing(self): def internal_unit_pricing(self):
return self.get_internal_price(1) return self.get_internal_price(1)
def get_purchase_price(self, quantity):
currency = currency_code_default()
prices = [convert_money(item.purchase_price, currency).amount for item in self.stock_items.all() if item.purchase_price]
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):
""" """

View File

@ -60,6 +60,21 @@
<td>Max: {% include "price.html" with price=max_total_bom_price %}</td> <td>Max: {% include "price.html" with price=max_total_bom_price %}</td>
</tr> </tr>
{% endif %} {% endif %}
{% if min_total_bom_purchase_price %}
<tr>
<td><b>{% trans 'Unit Purchase Price' %}</b></td>
<td>Min: {% include "price.html" with price=min_unit_bom_purchase_price %}</td>
<td>Max: {% include "price.html" with price=max_unit_bom_purchase_price %}</td>
</tr>
{% if quantity > 1 %}
<tr>
<td><b>{% trans 'Total Purchase Price' %}</b></td>
<td>Min: {% include "price.html" with price=min_total_bom_purchase_price %}</td>
<td>Max: {% include "price.html" with price=max_total_bom_purchase_price %}</td>
</tr>
{% endif %}
{% endif %}
{% if part.has_complete_bom_pricing == False %} {% if part.has_complete_bom_pricing == False %}
<tr> <tr>
<td colspan='3'> <td colspan='3'>

View File

@ -61,6 +61,25 @@
<td>Max: {% include "price.html" with price=max_total_bom_price %}</td> <td>Max: {% include "price.html" with price=max_total_bom_price %}</td>
</tr> </tr>
{% endif %} {% endif %}
{% if min_total_bom_purchase_price %}
<tr>
<td></td>
<td>{% trans 'Unit Purchase Price' %}</td>
<td>Min: {% include "price.html" with price=min_unit_bom_purchase_price %}</td>
<td>Max: {% include "price.html" with price=max_unit_bom_purchase_price %}</td>
</tr>
{% if quantity > 1 %}
<tr>
<td></td>
<td>{% trans 'Total Purchase Price' %}</td>
<td>Min: {% include "price.html" with price=min_total_bom_purchase_price %}</td>
<td>Max: {% include "price.html" with price=max_total_bom_purchase_price %}</td>
</tr>
{% endif %}
{% endif %}
{% if part.has_complete_bom_pricing == False %} {% if part.has_complete_bom_pricing == False %}
<tr> <tr>
<td colspan='4'> <td colspan='4'>

View File

@ -1351,6 +1351,7 @@ class PartPricing(AjaxView):
use_internal = InvenTreeSetting.get_setting('PART_BOM_USE_INTERNAL_PRICE', False) use_internal = InvenTreeSetting.get_setting('PART_BOM_USE_INTERNAL_PRICE', False)
bom_price = part.get_bom_price_range(quantity, internal=use_internal) bom_price = part.get_bom_price_range(quantity, internal=use_internal)
purchase_price = part.get_bom_price_range(quantity, purchase=True)
if bom_price is not None: if bom_price is not None:
min_bom_price, max_bom_price = bom_price min_bom_price, max_bom_price = bom_price
@ -1358,19 +1359,26 @@ class PartPricing(AjaxView):
min_bom_price /= scaler min_bom_price /= scaler
max_bom_price /= scaler max_bom_price /= scaler
min_unit_bom_price = round(min_bom_price / quantity, 3)
max_unit_bom_price = round(max_bom_price / quantity, 3)
min_bom_price = round(min_bom_price, 3)
max_bom_price = round(max_bom_price, 3)
if min_bom_price: if min_bom_price:
ctx['min_total_bom_price'] = min_bom_price ctx['min_total_bom_price'] = round(min_bom_price, 3)
ctx['min_unit_bom_price'] = min_unit_bom_price ctx['min_unit_bom_price'] = round(min_bom_price / quantity, 3)
if max_bom_price: if max_bom_price:
ctx['max_total_bom_price'] = max_bom_price ctx['max_total_bom_price'] = round(max_bom_price, 3)
ctx['max_unit_bom_price'] = max_unit_bom_price ctx['max_unit_bom_price'] = round(max_bom_price / quantity, 3)
if purchase_price is not None:
min_bom_purchase_price, max_bom_purchase_price = purchase_price
min_bom_purchase_price /= scaler
max_bom_purchase_price /= scaler
if min_bom_purchase_price:
ctx['min_total_bom_purchase_price'] = round(min_bom_purchase_price, 3)
ctx['min_unit_bom_purchase_price'] = round(min_bom_purchase_price / quantity, 3)
if max_bom_purchase_price:
ctx['max_total_bom_purchase_price'] = round(max_bom_purchase_price, 3)
ctx['max_unit_bom_purchase_price'] = round(max_bom_purchase_price / quantity, 3)
# internal part pricing information # internal part pricing information
internal_part_price = part.get_internal_price(quantity) internal_part_price = part.get_internal_price(quantity)