mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Merge remote-tracking branch 'inventree/master'
This commit is contained in:
commit
8714d6876a
@ -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)
|
||||||
|
@ -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):
|
||||||
|
@ -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):
|
||||||
|
10
README.md
10
README.md
@ -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:
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user