mirror of
				https://github.com/inventree/InvenTree.git
				synced 2025-10-31 13:15:43 +00:00 
			
		
		
		
	Fix for supplier price breaks
- Make "price" field an InvenTreeMoneySerializer instance - Add client-side validation for number inputs
This commit is contained in:
		| @@ -36,6 +36,13 @@ class InvenTreeMoneySerializer(MoneyField): | ||||
|     Ref: https://github.com/django-money/django-money/blob/master/djmoney/contrib/django_rest_framework/fields.py | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, *args, **kwargs): | ||||
|  | ||||
|         kwargs["max_digits"] = kwargs.get("max_digits", 19) | ||||
|         kwargs["decimal_places"] = kwargs.get("decimal_places", 4) | ||||
|  | ||||
|         super().__init__(*args, **kwargs) | ||||
|  | ||||
|     def get_value(self, data): | ||||
|         """ | ||||
|         Test that the returned amount is a valid Decimal | ||||
| @@ -52,7 +59,7 @@ class InvenTreeMoneySerializer(MoneyField): | ||||
|                 amount = Decimal(amount) | ||||
|         except: | ||||
|             raise ValidationError({ | ||||
|                 self.field_name: _("Must be a valid number") | ||||
|                 self.field_name: [_("Must be a valid number")], | ||||
|             }) | ||||
|  | ||||
|         currency = data.get(get_currency_field_name(self.field_name), self.default_currency) | ||||
|   | ||||
| @@ -9,6 +9,7 @@ from rest_framework import serializers | ||||
| from sql_util.utils import SubqueryCount | ||||
|  | ||||
| from InvenTree.serializers import InvenTreeModelSerializer | ||||
| from InvenTree.serializers import InvenTreeMoneySerializer | ||||
| from InvenTree.serializers import InvenTreeImageSerializerField | ||||
|  | ||||
| from part.serializers import PartBriefSerializer | ||||
| @@ -256,7 +257,11 @@ class SupplierPriceBreakSerializer(InvenTreeModelSerializer): | ||||
|  | ||||
|     quantity = serializers.FloatField() | ||||
|  | ||||
|     price = serializers.CharField() | ||||
|     price = InvenTreeMoneySerializer( | ||||
|         allow_null=True, | ||||
|         required=True, | ||||
|         label=_('Price'), | ||||
|     ) | ||||
|  | ||||
|     price_currency = serializers.ChoiceField( | ||||
|         choices=currency_code_mappings(), | ||||
|   | ||||
| @@ -154,7 +154,6 @@ class POLineItemSerializer(InvenTreeModelSerializer): | ||||
|     supplier_part_detail = SupplierPartSerializer(source='part', many=False, read_only=True) | ||||
|  | ||||
|     purchase_price = InvenTreeMoneySerializer( | ||||
|         max_digits=19, decimal_places=4, | ||||
|         allow_null=True | ||||
|     ) | ||||
|  | ||||
| @@ -557,8 +556,6 @@ class SOLineItemSerializer(InvenTreeModelSerializer): | ||||
|     fulfilled = serializers.FloatField(source='fulfilled_quantity', read_only=True) | ||||
|      | ||||
|     sale_price = InvenTreeMoneySerializer( | ||||
|         max_digits=19, | ||||
|         decimal_places=4, | ||||
|         allow_null=True | ||||
|     ) | ||||
|  | ||||
|   | ||||
| @@ -109,7 +109,6 @@ class PartSalePriceSerializer(InvenTreeModelSerializer): | ||||
|     quantity = serializers.FloatField() | ||||
|  | ||||
|     price = InvenTreeMoneySerializer( | ||||
|         max_digits=19, decimal_places=4, | ||||
|         allow_null=True | ||||
|     ) | ||||
|  | ||||
| @@ -134,7 +133,6 @@ class PartInternalPriceSerializer(InvenTreeModelSerializer): | ||||
|     quantity = serializers.FloatField() | ||||
|  | ||||
|     price = InvenTreeMoneySerializer( | ||||
|         max_digits=19, decimal_places=4, | ||||
|         allow_null=True | ||||
|     ) | ||||
|  | ||||
|   | ||||
| @@ -148,7 +148,6 @@ class StockItemSerializer(InvenTreeModelSerializer): | ||||
|  | ||||
|     purchase_price = InvenTreeMoneySerializer( | ||||
|         label=_('Purchase Price'), | ||||
|         max_digits=19, decimal_places=4, | ||||
|         allow_null=True | ||||
|     ) | ||||
|  | ||||
|   | ||||
| @@ -621,6 +621,10 @@ function submitFormData(fields, options) { | ||||
|  | ||||
|     var has_files = false; | ||||
|  | ||||
|     var data_valid = true; | ||||
|  | ||||
|     var data_errors = {}; | ||||
|  | ||||
|     // Extract values for each field | ||||
|     for (var idx = 0; idx < options.field_names.length; idx++) { | ||||
|  | ||||
| @@ -633,6 +637,21 @@ function submitFormData(fields, options) { | ||||
|  | ||||
|         if (field) { | ||||
|  | ||||
|             switch (field.type) { | ||||
|             // Ensure numerical fields are "valid" | ||||
|             case 'integer': | ||||
|             case 'float': | ||||
|             case 'decimal': | ||||
|                 if (!validateFormField(name, options)) { | ||||
|                     data_valid = false; | ||||
|      | ||||
|                     data_errors[name] = ['{% trans "Enter a valid input" %}']; | ||||
|                 } | ||||
|                 break; | ||||
|             default: | ||||
|                 break; | ||||
|             } | ||||
|  | ||||
|             var value = getFormFieldValue(name, field, options); | ||||
|  | ||||
|             // Handle file inputs | ||||
| @@ -662,6 +681,11 @@ function submitFormData(fields, options) { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     if (!data_valid) { | ||||
|         handleFormErrors(data_errors, fields, options); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     var upload_func = inventreePut; | ||||
|  | ||||
|     if (has_files) { | ||||
| @@ -732,7 +756,8 @@ function updateFieldValues(fields, options) { | ||||
|  * Update the value of a named field | ||||
|  */ | ||||
| function updateFieldValue(name, value, field, options) { | ||||
|     var el = $(options.modal).find(`#id_${name}`); | ||||
|  | ||||
|     var el = getFormFieldElement(name, options); | ||||
|  | ||||
|     if (!el) { | ||||
|         console.log(`WARNING: updateFieldValue could not find field '${name}'`); | ||||
| @@ -760,6 +785,30 @@ function updateFieldValue(name, value, field, options) { | ||||
| } | ||||
|  | ||||
|  | ||||
| // Find the named field element in the modal DOM | ||||
| function getFormFieldElement(name, options) { | ||||
|  | ||||
|     var el = $(options.modal).find(`#id_${name}`); | ||||
|  | ||||
|     if (!el.exists) { | ||||
|         console.log(`ERROR: Could not find form element for field '${name}'`); | ||||
|     } | ||||
|  | ||||
|     return el; | ||||
| } | ||||
|  | ||||
|  | ||||
| function validateFormField(name, options) { | ||||
|  | ||||
|     if (getFormFieldElement(name, options)) { | ||||
|         return document.getElementById(`id_${name}`).validity.valid; | ||||
|     } else { | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
| } | ||||
|  | ||||
|  | ||||
| /* | ||||
|  * Extract and field value before sending back to the server | ||||
|  * | ||||
| @@ -771,7 +820,7 @@ function updateFieldValue(name, value, field, options) { | ||||
| function getFormFieldValue(name, field, options) { | ||||
|  | ||||
|     // Find the HTML element | ||||
|     var el = $(options.modal).find(`#id_${name}`); | ||||
|     var el = getFormFieldElement(name, options); | ||||
|  | ||||
|     if (!el) { | ||||
|         return null; | ||||
| @@ -1086,7 +1135,9 @@ function addFieldCallbacks(fields, options) { | ||||
|  | ||||
| function addFieldCallback(name, field, options) { | ||||
|  | ||||
|     $(options.modal).find(`#id_${name}`).change(function() { | ||||
|     var el = getFormFieldElement(name, options); | ||||
|  | ||||
|     el.change(function() { | ||||
|  | ||||
|         var value = getFormFieldValue(name, field, options); | ||||
|  | ||||
| @@ -1299,7 +1350,7 @@ function initializeRelatedField(field, fields, options) { | ||||
|     } | ||||
|  | ||||
|     // Find the select element and attach a select2 to it | ||||
|     var select = $(options.modal).find(`#id_${name}`); | ||||
|     var select = getFormFieldElement(name, options); | ||||
|  | ||||
|     // Add a button to launch a 'secondary' modal | ||||
|     if (field.secondary != null) { | ||||
| @@ -1492,7 +1543,7 @@ function initializeRelatedField(field, fields, options) { | ||||
|  */ | ||||
| function setRelatedFieldData(name, data, options) { | ||||
|  | ||||
|     var select = $(options.modal).find(`#id_${name}`); | ||||
|     var select = getFormFieldElement(name, options); | ||||
|  | ||||
|     var option = new Option(name, data.pk, true, true); | ||||
|  | ||||
| @@ -1513,9 +1564,7 @@ function setRelatedFieldData(name, data, options) { | ||||
|  | ||||
| function initializeChoiceField(field, fields, options) { | ||||
|  | ||||
|     var name = field.name; | ||||
|  | ||||
|     var select = $(options.modal).find(`#id_${name}`); | ||||
|     var select = getFormFieldElement(field.name, options); | ||||
|  | ||||
|     select.select2({ | ||||
|         dropdownAutoWidth: false, | ||||
| @@ -1926,8 +1975,17 @@ function constructInputOptions(name, classes, type, parameters) { | ||||
|         opts.push(`placeholder='${parameters.placeholder}'`); | ||||
|     } | ||||
|  | ||||
|     if (parameters.type == 'boolean') { | ||||
|     switch (parameters.type) { | ||||
|         case 'boolean': | ||||
|             opts.push(`style='display: inline-block; width: 20px; margin-right: 20px;'`); | ||||
|             break; | ||||
|         case 'integer': | ||||
|         case 'float': | ||||
|         case 'decimal': | ||||
|             opts.push(`step='any'`); | ||||
|             break; | ||||
|         default: | ||||
|             break; | ||||
|     } | ||||
|  | ||||
|     if (parameters.multiline) { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user