From faa620b8f049f62917118dd9886b6a482dfd62b1 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 17 Oct 2021 18:52:59 +0200 Subject: [PATCH 1/9] remove commit from super call --- InvenTree/InvenTree/forms.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/InvenTree/InvenTree/forms.py b/InvenTree/InvenTree/forms.py index cd431a5f93..7a7668cf66 100644 --- a/InvenTree/InvenTree/forms.py +++ b/InvenTree/InvenTree/forms.py @@ -266,7 +266,7 @@ class RegistratonMixin: return False def save_user(self, request, user, form, commit=True): - user = super().save_user(request, user, form, commit=commit) + user = super().save_user(request, user, form) start_group = InvenTreeSetting.get_setting('SIGNUP_GROUP') if start_group: try: From 7f6f22fb0388023794050f37c51ff081c3a6c0e7 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 24 Oct 2021 00:40:23 +0200 Subject: [PATCH 2/9] move part toggle button under action buttons to save vertical space when closed --- InvenTree/part/templates/part/part_base.html | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/InvenTree/part/templates/part/part_base.html b/InvenTree/part/templates/part/part_base.html index 2d27283830..ea83ca1336 100644 --- a/InvenTree/part/templates/part/part_base.html +++ b/InvenTree/part/templates/part/part_base.html @@ -128,6 +128,13 @@ {% endif %} + +

+ + +

@@ -208,13 +215,6 @@
-

- - -

-
From a0140dc9a23d06939f8d79aa48ec56f61c61366c Mon Sep 17 00:00:00 2001 From: Oliver Date: Sun, 24 Oct 2021 19:35:15 +1100 Subject: [PATCH 3/9] bug fix: enable "table reload" for tables without any filters - setupFilterList function was returning too early --- InvenTree/templates/js/translated/filters.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/InvenTree/templates/js/translated/filters.js b/InvenTree/templates/js/translated/filters.js index 53429effea..6b109e2d18 100644 --- a/InvenTree/templates/js/translated/filters.js +++ b/InvenTree/templates/js/translated/filters.js @@ -283,6 +283,11 @@ function setupFilterList(tableKey, table, target) { element.append(``); + // Callback for reloading the table + element.find(`#reload-${tableKey}`).click(function() { + $(table).bootstrapTable('refresh'); + }); + // If there are no filters defined for this table, exit now if (jQuery.isEmptyObject(getAvailableTableFilters(tableKey))) { return; @@ -303,11 +308,6 @@ function setupFilterList(tableKey, table, target) { element.append(`
${title} = ${value}x
`); } - // Callback for reloading the table - element.find(`#reload-${tableKey}`).click(function() { - $(table).bootstrapTable('refresh'); - }); - // Add a callback for adding a new filter element.find(`#${add}`).click(function clicked() { From 5f6e67086f0faf63b8b68cea72217e264997a068 Mon Sep 17 00:00:00 2001 From: Oliver Date: Sun, 24 Oct 2021 19:35:23 +1100 Subject: [PATCH 4/9] Add "reload" button for salesorderlineitem table --- .../order/templates/order/sales_order_detail.html | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/InvenTree/order/templates/order/sales_order_detail.html b/InvenTree/order/templates/order/sales_order_detail.html index bd853702c4..cddba74cd6 100644 --- a/InvenTree/order/templates/order/sales_order_detail.html +++ b/InvenTree/order/templates/order/sales_order_detail.html @@ -19,9 +19,13 @@
{% if roles.sales_order.change %}
- +
+ +
+
+
{% endif %} From bc596c1f205d1edd637aa2bbf1ff8d44c5608a0c Mon Sep 17 00:00:00 2001 From: Oliver Date: Sun, 24 Oct 2021 19:39:48 +1100 Subject: [PATCH 5/9] Improve rendering of pricing in sale order table --- InvenTree/templates/js/translated/order.js | 32 ++++++++++++++-------- 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/InvenTree/templates/js/translated/order.js b/InvenTree/templates/js/translated/order.js index 67fef0b853..cd6b63c56d 100644 --- a/InvenTree/templates/js/translated/order.js +++ b/InvenTree/templates/js/translated/order.js @@ -885,8 +885,7 @@ function loadPurchaseOrderLineItemTable(table, options={}) { { field: 'total_price', sortable: true, - field: 'total_price', - title: '{% trans "Total price" %}', + title: '{% trans "Total Price" %}', formatter: function(value, row) { var total = row.purchase_price * row.quantity; var formatter = new Intl.NumberFormat('en-US', {style: 'currency', currency: row.purchase_price_currency}); @@ -1436,7 +1435,7 @@ function loadSalesOrderLineItemTable(table, options={}) { sortable: true, field: 'reference', title: '{% trans "Reference" %}', - switchable: false, + switchable: true, }, { sortable: true, @@ -1456,14 +1455,6 @@ function loadSalesOrderLineItemTable(table, options={}) { field: 'sale_price', title: '{% trans "Unit Price" %}', formatter: function(value, row) { - return row.sale_price_string || row.sale_price; - } - }, - { - sortable: true, - title: '{% trans "Total price" %}', - formatter: function(value, row) { - var total = row.sale_price * row.quantity; var formatter = new Intl.NumberFormat( 'en-US', { @@ -1472,7 +1463,23 @@ function loadSalesOrderLineItemTable(table, options={}) { } ); - return formatter.format(total); + return formatter.format(row.sale_price); + } + }, + { + field: 'total_price', + sortable: true, + title: '{% trans "Total Price" %}', + formatter: function(value, row) { + var formatter = new Intl.NumberFormat( + 'en-US', + { + style: 'currency', + currency: row.sale_price_currency + } + ); + + return formatter.format(row.sale_price * row.quantity); }, footerFormatter: function(data) { var total = data.map(function(row) { @@ -1544,6 +1551,7 @@ function loadSalesOrderLineItemTable(table, options={}) { if (pending) { columns.push({ field: 'buttons', + switchable: false, formatter: function(value, row, index, field) { var html = `
`; From 65b673b2bb159574e98b935a3f5ee9e109b7cc1d Mon Sep 17 00:00:00 2001 From: Oliver Date: Sun, 24 Oct 2021 19:43:05 +1100 Subject: [PATCH 6/9] Improve pricing rendering for purchase order table --- InvenTree/templates/js/translated/order.js | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/InvenTree/templates/js/translated/order.js b/InvenTree/templates/js/translated/order.js index cd6b63c56d..75f71e3c4c 100644 --- a/InvenTree/templates/js/translated/order.js +++ b/InvenTree/templates/js/translated/order.js @@ -864,6 +864,7 @@ function loadPurchaseOrderLineItemTable(table, options={}) { }, { sortable: true, + switchable: false, field: 'quantity', title: '{% trans "Quantity" %}', footerFormatter: function(data) { @@ -879,7 +880,14 @@ function loadPurchaseOrderLineItemTable(table, options={}) { field: 'purchase_price', title: '{% trans "Unit Price" %}', formatter: function(value, row) { - return row.purchase_price_string || row.purchase_price; + var formatter = new Intl.NumberFormat( + 'en-US', + { + style: 'currency', + currency: row.purchase_price_currency + } + ); + return formatter.format(row.purchase_price); } }, { @@ -887,9 +895,14 @@ function loadPurchaseOrderLineItemTable(table, options={}) { sortable: true, title: '{% trans "Total Price" %}', formatter: function(value, row) { - var total = row.purchase_price * row.quantity; - var formatter = new Intl.NumberFormat('en-US', {style: 'currency', currency: row.purchase_price_currency}); - return formatter.format(total); + var formatter = new Intl.NumberFormat( + 'en-US', + { + style: 'currency', + currency: row.purchase_price_currency + } + ); + return formatter.format(row.purchase_price * row.quantity); }, footerFormatter: function(data) { var total = data.map(function(row) { From 2d910022ac6e0919cca62fbb2928bf0f48219ddd Mon Sep 17 00:00:00 2001 From: Oliver Date: Tue, 26 Oct 2021 01:55:41 +1100 Subject: [PATCH 7/9] Fix for supplier price breaks - Make "price" field an InvenTreeMoneySerializer instance - Add client-side validation for number inputs --- InvenTree/InvenTree/serializers.py | 9 ++- InvenTree/company/serializers.py | 7 +- InvenTree/order/serializers.py | 3 - InvenTree/part/serializers.py | 2 - InvenTree/stock/serializers.py | 1 - InvenTree/templates/js/translated/forms.js | 78 +++++++++++++++++++--- 6 files changed, 82 insertions(+), 18 deletions(-) diff --git a/InvenTree/InvenTree/serializers.py b/InvenTree/InvenTree/serializers.py index 0d21550f00..d2d00a932c 100644 --- a/InvenTree/InvenTree/serializers.py +++ b/InvenTree/InvenTree/serializers.py @@ -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) diff --git a/InvenTree/company/serializers.py b/InvenTree/company/serializers.py index a471b87a90..719d94b0a6 100644 --- a/InvenTree/company/serializers.py +++ b/InvenTree/company/serializers.py @@ -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(), diff --git a/InvenTree/order/serializers.py b/InvenTree/order/serializers.py index 40cd2def58..8e737e217b 100644 --- a/InvenTree/order/serializers.py +++ b/InvenTree/order/serializers.py @@ -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 ) diff --git a/InvenTree/part/serializers.py b/InvenTree/part/serializers.py index 869145e1af..ff1fb2c8c6 100644 --- a/InvenTree/part/serializers.py +++ b/InvenTree/part/serializers.py @@ -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 ) diff --git a/InvenTree/stock/serializers.py b/InvenTree/stock/serializers.py index c44dffe94f..d97e81331a 100644 --- a/InvenTree/stock/serializers.py +++ b/InvenTree/stock/serializers.py @@ -148,7 +148,6 @@ class StockItemSerializer(InvenTreeModelSerializer): purchase_price = InvenTreeMoneySerializer( label=_('Purchase Price'), - max_digits=19, decimal_places=4, allow_null=True ) diff --git a/InvenTree/templates/js/translated/forms.js b/InvenTree/templates/js/translated/forms.js index db2c8e46cc..f00d517b0b 100644 --- a/InvenTree/templates/js/translated/forms.js +++ b/InvenTree/templates/js/translated/forms.js @@ -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') { - opts.push(`style='display: inline-block; width: 20px; margin-right: 20px;'`); + 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) { From cbbad98feaac1274800dd12e1dc396a25a41f36b Mon Sep 17 00:00:00 2001 From: Oliver Date: Tue, 26 Oct 2021 07:52:29 +1100 Subject: [PATCH 8/9] Improve client-side validation of numerical inputs --- InvenTree/templates/js/translated/forms.js | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/InvenTree/templates/js/translated/forms.js b/InvenTree/templates/js/translated/forms.js index f00d517b0b..72dca0fbb6 100644 --- a/InvenTree/templates/js/translated/forms.js +++ b/InvenTree/templates/js/translated/forms.js @@ -645,7 +645,7 @@ function submitFormData(fields, options) { if (!validateFormField(name, options)) { data_valid = false; - data_errors[name] = ['{% trans "Enter a valid input" %}']; + data_errors[name] = ['{% trans "Enter a valid number" %}']; } break; default: @@ -798,10 +798,26 @@ function getFormFieldElement(name, options) { } +/* + * Check that a "numerical" input field has a valid number in it. + * An invalid number is expunged at the client side by the getFormFieldValue() function, + * which means that an empty string '' is sent to the server if the number is not valud. + * This can result in confusing error messages displayed under the form field. + * + * So, we can invalid numbers and display errors *before* the form is submitted! + */ function validateFormField(name, options) { if (getFormFieldElement(name, options)) { - return document.getElementById(`id_${name}`).validity.valid; + + var el = document.getElementById(`id_${name}`); + + if (el.validity.valueMissing) { + // Accept empty strings (server will validate) + return true; + } else { + return el.validity.valid; + } } else { return false; } From b35ce79d6a8cfac3f207c6385953e839665dd83b Mon Sep 17 00:00:00 2001 From: Oliver Date: Tue, 26 Oct 2021 08:00:28 +1100 Subject: [PATCH 9/9] js linting fix --- InvenTree/templates/js/translated/forms.js | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/InvenTree/templates/js/translated/forms.js b/InvenTree/templates/js/translated/forms.js index 72dca0fbb6..a338310fd1 100644 --- a/InvenTree/templates/js/translated/forms.js +++ b/InvenTree/templates/js/translated/forms.js @@ -1992,16 +1992,16 @@ function constructInputOptions(name, classes, type, parameters) { } 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; + 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) {