Merge remote-tracking branch 'inventree/master'

This commit is contained in:
Oliver Walters 2021-05-04 19:39:10 +10:00
commit 8714d6876a
4 changed files with 59 additions and 24 deletions

View File

@ -6,7 +6,7 @@ Company database model definitions
from __future__ import unicode_literals from __future__ import unicode_literals
import os import os
import decimal
import math import math
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
@ -566,12 +566,15 @@ 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.filter(quantity__lte=quantity) price_breaks = self.price_breaks.all()
# No price break information available? # No price break information available?
if len(price_breaks) == 0: if len(price_breaks) == 0:
return None return None
# Check if quantity is fraction and disable multiples
multiples = (quantity % 1 == 0)
# Order multiples # Order multiples
if multiples: if multiples:
quantity = int(math.ceil(quantity / self.multiple) * self.multiple) quantity = int(math.ceil(quantity / self.multiple) * self.multiple)
@ -584,7 +587,12 @@ class SupplierPart(models.Model):
# Default currency selection # Default currency selection
currency = common.models.InvenTreeSetting.get_setting('INVENTREE_DEFAULT_CURRENCY') currency = common.models.InvenTreeSetting.get_setting('INVENTREE_DEFAULT_CURRENCY')
pb_min = None
for pb in self.price_breaks.all(): for pb in self.price_breaks.all():
# Store smallest price break
if not pb_min:
pb_min = pb
# Ignore this pricebreak (quantity is too high) # Ignore this pricebreak (quantity is too high)
if pb.quantity > quantity: if pb.quantity > quantity:
continue continue
@ -598,6 +606,17 @@ class SupplierPart(models.Model):
# Convert everything to the selected currency # Convert everything to the selected currency
pb_cost = pb.convert_to(currency) pb_cost = pb.convert_to(currency)
# Use smallest price break
if not pb_found and pb_min:
# Update price break information
pb_quantity = pb_min.quantity
pb_cost = pb_min.convert_to(currency)
# Trigger cost calculation using smallest price break
pb_found = True
# Convert quantity to decimal.Decimal format
quantity = decimal.Decimal(f'{quantity}')
if pb_found: if pb_found:
cost = pb_cost * quantity cost = pb_cost * quantity
return normalize(cost + self.base_cost) return normalize(cost + self.base_cost)

View File

@ -5,6 +5,7 @@ from django.test import TestCase
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
import os import os
from decimal import Decimal
from .models import Company, Contact, ManufacturerPart, SupplierPart from .models import Company, Contact, ManufacturerPart, SupplierPart
from .models import rename_company_image from .models import rename_company_image
@ -103,8 +104,9 @@ class CompanySimpleTest(TestCase):
self.assertEqual(p(100), 350) self.assertEqual(p(100), 350)
p = self.acme0002.get_price p = self.acme0002.get_price
self.assertEqual(p(1), None) self.assertEqual(p(0.5), 3.5)
self.assertEqual(p(2), None) self.assertEqual(p(1), 7)
self.assertEqual(p(2), 14)
self.assertEqual(p(5), 35) self.assertEqual(p(5), 35)
self.assertEqual(p(45), 315) self.assertEqual(p(45), 315)
self.assertEqual(p(55), 68.75) self.assertEqual(p(55), 68.75)
@ -112,6 +114,7 @@ class CompanySimpleTest(TestCase):
def test_part_pricing(self): def test_part_pricing(self):
m2x4 = Part.objects.get(name='M2x4 LPHS') m2x4 = Part.objects.get(name='M2x4 LPHS')
self.assertEqual(m2x4.get_price_info(5.5), "38.5 - 41.25")
self.assertEqual(m2x4.get_price_info(10), "70 - 75") self.assertEqual(m2x4.get_price_info(10), "70 - 75")
self.assertEqual(m2x4.get_price_info(100), "125 - 350") self.assertEqual(m2x4.get_price_info(100), "125 - 350")
@ -121,7 +124,8 @@ class CompanySimpleTest(TestCase):
m3x12 = Part.objects.get(name='M3x12 SHCS') m3x12 = Part.objects.get(name='M3x12 SHCS')
self.assertIsNone(m3x12.get_price_info(3)) self.assertEqual(m3x12.get_price_info(0.3), Decimal('2.4'))
self.assertEqual(m3x12.get_price_info(3), Decimal('24'))
self.assertIsNotNone(m3x12.get_price_info(50)) self.assertIsNotNone(m3x12.get_price_info(50))
def test_currency_validation(self): def test_currency_validation(self):

View File

@ -1957,6 +1957,11 @@ class PartPricing(AjaxView):
role_required = ['sales_order.view', 'part.view'] role_required = ['sales_order.view', 'part.view']
def get_quantity(self):
""" Return set quantity in decimal format """
return Decimal(self.request.POST.get('quantity', 1))
def get_part(self): def get_part(self):
try: try:
return Part.objects.get(id=self.kwargs['pk']) return Part.objects.get(id=self.kwargs['pk'])
@ -1965,12 +1970,12 @@ class PartPricing(AjaxView):
def get_pricing(self, quantity=1, currency=None): def get_pricing(self, quantity=1, currency=None):
try: # try:
quantity = int(quantity) # quantity = int(quantity)
except ValueError: # except ValueError:
quantity = 1 # quantity = 1
if quantity < 1: if quantity <= 0:
quantity = 1 quantity = 1
# TODO - Capacity for price comparison in different currencies # TODO - Capacity for price comparison in different currencies
@ -2000,16 +2005,19 @@ class PartPricing(AjaxView):
min_buy_price /= scaler min_buy_price /= scaler
max_buy_price /= scaler max_buy_price /= scaler
min_unit_buy_price = round(min_buy_price / quantity, 3)
max_unit_buy_price = round(max_buy_price / quantity, 3)
min_buy_price = round(min_buy_price, 3) min_buy_price = round(min_buy_price, 3)
max_buy_price = round(max_buy_price, 3) max_buy_price = round(max_buy_price, 3)
if min_buy_price: if min_buy_price:
ctx['min_total_buy_price'] = min_buy_price ctx['min_total_buy_price'] = min_buy_price
ctx['min_unit_buy_price'] = min_buy_price / quantity ctx['min_unit_buy_price'] = min_unit_buy_price
if max_buy_price: if max_buy_price:
ctx['max_total_buy_price'] = max_buy_price ctx['max_total_buy_price'] = max_buy_price
ctx['max_unit_buy_price'] = max_buy_price / quantity ctx['max_unit_buy_price'] = max_unit_buy_price
# BOM pricing information # BOM pricing information
if part.bom_count > 0: if part.bom_count > 0:
@ -2022,16 +2030,19 @@ 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) min_bom_price = round(min_bom_price, 3)
max_bom_price = round(max_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'] = min_bom_price
ctx['min_unit_bom_price'] = min_bom_price / quantity ctx['min_unit_bom_price'] = min_unit_bom_price
if max_bom_price: if max_bom_price:
ctx['max_total_bom_price'] = max_bom_price ctx['max_total_bom_price'] = max_bom_price
ctx['max_unit_bom_price'] = max_bom_price / quantity ctx['max_unit_bom_price'] = max_unit_bom_price
return ctx return ctx
@ -2043,10 +2054,11 @@ class PartPricing(AjaxView):
currency = None currency = None
try: quantity = self.get_quantity()
quantity = int(self.request.POST.get('quantity', 1))
except ValueError: # Retain quantity value set by user
quantity = 1 form = self.form_class()
form.fields['quantity'].initial = quantity
# TODO - How to handle pricing in different currencies? # TODO - How to handle pricing in different currencies?
currency = None currency = None
@ -2056,7 +2068,7 @@ class PartPricing(AjaxView):
'form_valid': False, 'form_valid': False,
} }
return self.renderJsonResponse(request, self.form_class(), data=data, context=self.get_pricing(quantity, currency)) return self.renderJsonResponse(request, form, data=data, context=self.get_pricing(quantity, currency))
class PartParameterTemplateCreate(AjaxCreateView): class PartParameterTemplateCreate(AjaxCreateView):

View File

@ -51,15 +51,15 @@ To contribute to the translation effort, navigate to the [InvenTree crowdin proj
For InvenTree documentation, refer to the [InvenTree documentation website](https://inventree.readthedocs.io/en/latest/). For InvenTree documentation, refer to the [InvenTree documentation website](https://inventree.readthedocs.io/en/latest/).
# Getting Started
Refer to the [getting started guide](https://inventree.readthedocs.io/en/latest/start/install/) for installation and setup instructions.
# Credits # Credits
The credits for all used packages are part of the [InvenTree documentation website](https://inventree.readthedocs.io/en/latest/credits/). The credits for all used packages are part of the [InvenTree documentation website](https://inventree.readthedocs.io/en/latest/credits/).
## Getting Started # Integration
Refer to the [getting started guide](https://inventree.readthedocs.io/en/latest/start/install/) for installation and setup instructions.
## Integration
InvenTree is designed to be extensible, and provides multiple options for integration with external applications or addition of custom plugins: InvenTree is designed to be extensible, and provides multiple options for integration with external applications or addition of custom plugins: