diff --git a/InvenTree/InvenTree/api_version.py b/InvenTree/InvenTree/api_version.py index 409c19aad0..8c0cc057cc 100644 --- a/InvenTree/InvenTree/api_version.py +++ b/InvenTree/InvenTree/api_version.py @@ -2,11 +2,14 @@ # InvenTree API version -INVENTREE_API_VERSION = 152 +INVENTREE_API_VERSION = 153 """Increment this API version number whenever there is a significant change to the API that any clients need to know about.""" INVENTREE_API_TEXT = """ +v153 -> 2023-11-21 : https://github.com/inventree/InvenTree/pull/5956 + - Adds override_min and override_max fields to part pricing API + v152 -> 2023-11-20 : https://github.com/inventree/InvenTree/pull/5949 - Adds barcode support for manufacturerpart model - Adds API endpoint for adding parts to purchase order using barcode scan diff --git a/InvenTree/part/migrations/0119_auto_20231120_0457.py b/InvenTree/part/migrations/0119_auto_20231120_0457.py new file mode 100644 index 0000000000..f63ccc70db --- /dev/null +++ b/InvenTree/part/migrations/0119_auto_20231120_0457.py @@ -0,0 +1,36 @@ +# Generated by Django 3.2.23 on 2023-11-20 04:57 + +import InvenTree.fields +from django.db import migrations +import djmoney.models.fields +import djmoney.models.validators + + +class Migration(migrations.Migration): + + dependencies = [ + ('part', '0118_auto_20231024_1844'), + ] + + operations = [ + migrations.AddField( + model_name='partpricing', + name='override_max', + field=InvenTree.fields.InvenTreeModelMoneyField(blank=True, currency_choices=[], decimal_places=6, default_currency='', help_text='Override maximum cost', max_digits=19, null=True, validators=[djmoney.models.validators.MinMoneyValidator(0)], verbose_name='Maximum Cost'), + ), + migrations.AddField( + model_name='partpricing', + name='override_max_currency', + field=djmoney.models.fields.CurrencyField(choices=[], default='', editable=False, max_length=3, null=True), + ), + migrations.AddField( + model_name='partpricing', + name='override_min', + field=InvenTree.fields.InvenTreeModelMoneyField(blank=True, currency_choices=[], decimal_places=6, default_currency='', help_text='Override minimum cost', max_digits=19, null=True, validators=[djmoney.models.validators.MinMoneyValidator(0)], verbose_name='Minimum Cost'), + ), + migrations.AddField( + model_name='partpricing', + name='override_min_currency', + field=djmoney.models.fields.CurrencyField(choices=[], default='', editable=False, max_length=3, null=True), + ), + ] diff --git a/InvenTree/part/models.py b/InvenTree/part/models.py index 4af2fc1b61..33fdd16ee1 100644 --- a/InvenTree/part/models.py +++ b/InvenTree/part/models.py @@ -2369,6 +2369,7 @@ class PartPricing(common.models.MetaMixin): def update_pricing(self, counter: int = 0, cascade: bool = True): """Recalculate all cost data for the referenced Part instance""" # If importing data, skip pricing update + if InvenTree.ready.isImportingData(): return @@ -2698,6 +2699,7 @@ class PartPricing(common.models.MetaMixin): Here we simply take the minimum / maximum values of the other calculated fields. """ + overall_min = None overall_max = None @@ -2758,7 +2760,14 @@ class PartPricing(common.models.MetaMixin): if self.internal_cost_max is not None: overall_max = self.internal_cost_max + if self.override_min is not None: + overall_min = self.convert(self.override_min) + self.overall_min = overall_min + + if self.override_max is not None: + overall_max = self.convert(self.override_max) + self.overall_max = overall_max def update_sale_cost(self, save=True): @@ -2897,6 +2906,18 @@ class PartPricing(common.models.MetaMixin): help_text=_('Calculated maximum cost of variant parts'), ) + override_min = InvenTree.fields.InvenTreeModelMoneyField( + null=True, blank=True, + verbose_name=_('Minimum Cost'), + help_text=_('Override minimum cost'), + ) + + override_max = InvenTree.fields.InvenTreeModelMoneyField( + null=True, blank=True, + verbose_name=_('Maximum Cost'), + help_text=_('Override maximum cost'), + ) + overall_min = InvenTree.fields.InvenTreeModelMoneyField( null=True, blank=True, verbose_name=_('Minimum Cost'), diff --git a/InvenTree/part/serializers.py b/InvenTree/part/serializers.py index 29c8c8860a..8bf7db4d03 100644 --- a/InvenTree/part/serializers.py +++ b/InvenTree/part/serializers.py @@ -5,6 +5,7 @@ import io import logging from decimal import Decimal +from django.core.exceptions import ValidationError from django.core.files.base import ContentFile from django.core.validators import MinValueValidator from django.db import IntegrityError, models, transaction @@ -13,11 +14,14 @@ from django.db.models.functions import Coalesce from django.urls import reverse_lazy from django.utils.translation import gettext_lazy as _ +from djmoney.contrib.exchange.exceptions import MissingRate +from djmoney.contrib.exchange.models import convert_money from rest_framework import serializers from sql_util.utils import SubqueryCount, SubquerySum from taggit.serializers import TagListSerializerField import common.models +import common.settings import company.models import InvenTree.helpers import InvenTree.serializers @@ -1042,6 +1046,10 @@ class PartPricingSerializer(InvenTree.serializers.InvenTreeModelSerializer): 'supplier_price_max', 'variant_cost_min', 'variant_cost_max', + 'override_min', + 'override_min_currency', + 'override_max', + 'override_max_currency', 'overall_min', 'overall_max', 'sale_price_min', @@ -1073,6 +1081,30 @@ class PartPricingSerializer(InvenTree.serializers.InvenTreeModelSerializer): variant_cost_min = InvenTree.serializers.InvenTreeMoneySerializer(allow_null=True, read_only=True) variant_cost_max = InvenTree.serializers.InvenTreeMoneySerializer(allow_null=True, read_only=True) + override_min = InvenTree.serializers.InvenTreeMoneySerializer( + label=_('Minimum Price'), + help_text=_('Override calculated value for minimum price'), + allow_null=True, read_only=False, required=False, + ) + + override_min_currency = serializers.ChoiceField( + label=_('Minimum price currency'), + read_only=False, required=False, + choices=common.settings.currency_code_mappings(), + ) + + override_max = InvenTree.serializers.InvenTreeMoneySerializer( + label=_('Maximum Price'), + help_text=_('Override calculated value for maximum price'), + allow_null=True, read_only=False, required=False, + ) + + override_max_currency = serializers.ChoiceField( + label=_('Maximum price currency'), + read_only=False, required=False, + choices=common.settings.currency_code_mappings(), + ) + overall_min = InvenTree.serializers.InvenTreeMoneySerializer(allow_null=True, read_only=True) overall_max = InvenTree.serializers.InvenTreeMoneySerializer(allow_null=True, read_only=True) @@ -1086,18 +1118,44 @@ class PartPricingSerializer(InvenTree.serializers.InvenTreeModelSerializer): write_only=True, label=_('Update'), help_text=_('Update pricing for this part'), - default=False, - required=False, + default=False, required=False, allow_null=True, ) + def validate(self, data): + """Validate supplied pricing data""" + + super().validate(data) + + # Check that override_min is not greater than override_max + override_min = data.get('override_min', None) + override_max = data.get('override_max', None) + + default_currency = common.settings.currency_code_default() + + if override_min is not None and override_max is not None: + + try: + override_min = convert_money(override_min, default_currency) + override_max = convert_money(override_max, default_currency) + except MissingRate: + raise ValidationError(_(f'Could not convert from provided currencies to {default_currency}')) + + if override_min > override_max: + raise ValidationError({ + 'override_min': _('Minimum price must not be greater than maximum price'), + 'override_max': _('Maximum price must not be less than minimum price') + }) + + return data + def save(self): """Called when the serializer is saved""" - data = self.validated_data - if InvenTree.helpers.str2bool(data.get('update', False)): - # Update part pricing - pricing = self.instance - pricing.update_pricing() + super().save() + + # Update part pricing + pricing = self.instance + pricing.update_pricing() class PartRelationSerializer(InvenTree.serializers.InvenTreeModelSerializer): diff --git a/InvenTree/part/templates/part/prices.html b/InvenTree/part/templates/part/prices.html index 62f082b69d..0328e84e15 100644 --- a/InvenTree/part/templates/part/prices.html +++ b/InvenTree/part/templates/part/prices.html @@ -14,6 +14,9 @@ + @@ -97,6 +100,14 @@