From b4f8136142aa0901ed9da32bfe9dd28c8193f3b8 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Mon, 21 Mar 2022 21:58:21 +1100 Subject: [PATCH 01/16] Don't add "remove row" button if there is only one row --- InvenTree/templates/js/translated/order.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/InvenTree/templates/js/translated/order.js b/InvenTree/templates/js/translated/order.js index 9d66fa2ab4..ed0ddbb4fa 100644 --- a/InvenTree/templates/js/translated/order.js +++ b/InvenTree/templates/js/translated/order.js @@ -655,12 +655,14 @@ function receivePurchaseOrderItems(order_id, line_items, options={}) { ); } - buttons += makeIconButton( - 'fa-times icon-red', - 'button-row-remove', - pk, - '{% trans "Remove row" %}', - ); + if (line_items.length > 1) { + buttons += makeIconButton( + 'fa-times icon-red', + 'button-row-remove', + pk, + '{% trans "Remove row" %}', + ); + } buttons += ''; From 64bbcd25707eecb83a3f94a25253973836de7ec6 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Mon, 21 Mar 2022 22:41:50 +1100 Subject: [PATCH 02/16] Add validation checks for the PurchaseOrderLineItem serializer --- InvenTree/order/serializers.py | 37 +++++++++++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/InvenTree/order/serializers.py b/InvenTree/order/serializers.py index 2f4c1ea5df..56e21b1b2b 100644 --- a/InvenTree/order/serializers.py +++ b/InvenTree/order/serializers.py @@ -164,8 +164,23 @@ class POLineItemSerializer(InvenTreeModelSerializer): if order_detail is not True: self.fields.pop('order_detail') - quantity = serializers.FloatField(default=1) - received = serializers.FloatField(default=0) + quantity = serializers.FloatField(min_value=0, required=True) + + def validate_quantity(self, quantity): + + if quantity <= 0: + raise ValidationError(_("Quantity must be greater than zero")) + + return quantity + + def validate_purchase_order(self, purchase_order): + + if purchase_order.status not in PurchaseOrderStatus.OPEN: + raise ValidationError(_('Order is not open')) + + return purchase_order + + received = serializers.FloatField(default=0, read_only=True) overdue = serializers.BooleanField(required=False, read_only=True) @@ -189,6 +204,22 @@ class POLineItemSerializer(InvenTreeModelSerializer): order_detail = POSerializer(source='order', read_only=True, many=False) + def validate(self, data): + + data = super().validate(data) + + supplier_part = data['part'] + purchase_order = data['order'] + + # Check that the supplier part and purchase order match + if supplier_part is not None and supplier_part.supplier != purchase_order.supplier: + raise ValidationError({ + 'part': _('Supplier must match purchase order'), + 'order': _('Purchase order must match supplier'), + }) + + return data + class Meta: model = order.models.PurchaseOrderLineItem @@ -349,7 +380,7 @@ class POReceiveSerializer(serializers.Serializer): Serializer for receiving items against a purchase order """ - items = POLineItemReceiveSerializer(many=True) + items = POLineItemReceiveSerializer(many=True, required=True) location = serializers.PrimaryKeyRelatedField( queryset=stock.models.StockLocation.objects.all(), From f2806b2e41e8219359f1f6aea6aeab8e10005a81 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Mon, 21 Mar 2022 23:19:27 +1100 Subject: [PATCH 03/16] Tweaks for existing form code --- InvenTree/templates/js/translated/forms.js | 5 +++++ InvenTree/templates/js/translated/model_renderers.js | 10 ++++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/InvenTree/templates/js/translated/forms.js b/InvenTree/templates/js/translated/forms.js index 88c9c5badb..e01835ae36 100644 --- a/InvenTree/templates/js/translated/forms.js +++ b/InvenTree/templates/js/translated/forms.js @@ -1780,6 +1780,11 @@ function initializeRelatedField(field, fields, options={}) { // Only a single result is available, given the provided filters if (data.count == 1) { setRelatedFieldData(name, data.results[0], options); + + // Run "callback" function (if supplied) + if (field.onEdit) { + field.onEdit(data.results[0], name, field, options); + } } } }); diff --git a/InvenTree/templates/js/translated/model_renderers.js b/InvenTree/templates/js/translated/model_renderers.js index e3abe1186f..72c1ed378b 100644 --- a/InvenTree/templates/js/translated/model_renderers.js +++ b/InvenTree/templates/js/translated/model_renderers.js @@ -371,10 +371,16 @@ function renderSupplierPart(name, data, parameters, options) { var html = ''; html += select2Thumbnail(supplier_image); - html += select2Thumbnail(part_image); + + if (data.part_detail) { + html += select2Thumbnail(part_image); + } html += ` ${data.supplier_detail.name} - ${data.SKU}`; - html += ` - ${data.part_detail.full_name}`; + + if (data.part_detail) { + html += ` - ${data.part_detail.full_name}`; + } html += `{% trans "Supplier Part ID" %}: ${data.pk}`; From 4fc605ee2857943859e2b411366648ed1b23ed68 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Wed, 23 Mar 2022 21:26:11 +1100 Subject: [PATCH 04/16] Render a form for ordering parts --- InvenTree/part/templates/part/part_base.html | 16 ++ InvenTree/templates/js/translated/order.js | 212 +++++++++++++++++++ 2 files changed, 228 insertions(+) diff --git a/InvenTree/part/templates/part/part_base.html b/InvenTree/part/templates/part/part_base.html index d14cfbdfd5..e47396d714 100644 --- a/InvenTree/part/templates/part/part_base.html +++ b/InvenTree/part/templates/part/part_base.html @@ -558,6 +558,22 @@ {% endif %} $("#part-order").click(function() { + + inventreeGet( + '{% url "api-part-detail" part.pk %}', + {}, + { + success: function(part) { + orderParts( + [part], + {} + ); + } + } + ); + + return; + launchModalForm("{% url 'order-parts' %}", { data: { part: {{ part.id }}, diff --git a/InvenTree/templates/js/translated/order.js b/InvenTree/templates/js/translated/order.js index ed0ddbb4fa..78f6d39ee9 100644 --- a/InvenTree/templates/js/translated/order.js +++ b/InvenTree/templates/js/translated/order.js @@ -33,6 +33,7 @@ loadSalesOrderTable, newPurchaseOrderFromOrderWizard, newSupplierPartFromOrderWizard, + orderParts, removeOrderRowFromOrderWizard, removePurchaseOrderLineItem, */ @@ -450,6 +451,217 @@ function exportOrder(redirect_url, options={}) { }); } + +/* + * Create a new form to order parts based on the list of provided parts. + */ +function orderParts(parts_list, options={}) { + + var parts = []; + + parts_list.forEach(function(part) { + if (part.purchaseable) { + parts.push(part); + } + }); + + if (parts.length == 0) { + showAlertDialog( + '{% trans "Select Parts" %}', + '{% trans "At least one purchaseable part must be selected" %}', + ); + return; + } + + // Render a single part within the dialog + function renderPart(part, opts={}) { + + var pk = part.pk; + + var thumb = thumbnailImage(part.thumbnail || part.image); + + // The "quantity" field should have been provided for each part + var quantity = part.quantity || 0; + + if (quantity < 0) { + quantity = 0; + } + + var quantity_input = constructField( + `items_quantity_${pk}`, + { + type: 'decimal', + min_value: 0, + value: quantity, + title: '{% trans "Quantity to order" %}', + required: true, + }, + { + hideLabels: true, + } + ) + + var supplier_part_prefix = ` + + + + `; + + var supplier_part_input = constructField( + `supplier_part_${pk}`, + { + type: 'related field', + required: true, + prefixRaw: supplier_part_prefix, + }, + { + hideLabels: true, + } + ); + + var purchase_order_prefix = ` + + + + `; + + var purchase_order_input = constructField( + `purchase_order_${pk}`, + { + type: 'related field', + required: true, + prefixRaw: purchase_order_prefix, + }, + { + hideLabels: 'true', + } + ); + + var buttons = `
`; + + buttons += makeIconButton( + 'fa-check-circle icon-green', + 'button-row-complete', + pk, + '{% trans "Add to order" %}', + ); + + if (parts.length > 1) { + buttons += makeIconButton( + 'fa-times icon-red', + 'button-row-remove', + pk, + '{% trans "Remove row" %}', + ); + } + + buttons += `
`; + + var html = ` + + ${thumb} ${part.full_name} + ${supplier_part_input} + ${purchase_order_input} + ${quantity_input} + ${buttons} + + `; + + return html; + } + + var table_entries = ''; + + parts.forEach(function(part) { + table_entries += renderPart(part); + }); + + var html = ''; + + // Add table + html += ` + + + + + + + + + + + + ${table_entries} + +
{% trans "Part" %}{% trans "Supplier Part" %}{% trans "Purchase Order" %}{% trans "Quantity" %}
+ `; + + constructFormBody({}, { + preFormContent: html, + title: '{% trans "Order Parts" %}', + hideSubmitButton: true, + afterRender: function(fields, opts) { + // TODO + parts.forEach(function(part) { + // Configure the "supplier part" field + initializeRelatedField({ + name: `supplier_part_${part.pk}`, + model: 'supplierpart', + api_url: '{% url "api-supplier-part-list" %}', + required: true, + type: 'related field', + auto_fill: true, + filters: { + part: part.pk, + supplier_detail: true, + part_detail: false, + }, + noResults: function(query) { + return '{% trans "No matching supplier parts" %}'; + } + }, null, opts); + + // Configure the "purchase order" field + initializeRelatedField({ + name: `purchase_order_${part.pk}`, + model: 'purchaseorder', + api_url: '{% url "api-po-list" %}', + required: true, + type: 'related field', + auto_fill: false, + filters: { + status: {{ PurchaseOrderStatus.PENDING }}, + }, + adjustFilters: function(query, opts) { + + // Whenever we open the drop-down to select an order, + // ensure we are only displaying orders which match the selected supplier part + var supplier_part_pk = getFormFieldValue(`supplier_part_${part.pk}`, opts); + + inventreeGet( + `/api/company/part/${supplier_part_pk}/`, + {}, + { + async: false, + success: function(data) { + query.supplier = data.supplier; + } + } + ); + + return query; + }, + noResults: function(query) { + return '{% trans "No matching purchase orders" %}'; + } + }, null, opts); + + }); + } + }); + +} + function newPurchaseOrderFromOrderWizard(e) { /* Create a new purchase order directly from an order form. * Launches a secondary modal and (if successful), From e1fbd961e50706a4e884392b0101caf4352eb428 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Thu, 7 Apr 2022 19:05:25 +1000 Subject: [PATCH 05/16] Refactor form renderer functions --- .../js/translated/model_renderers.js | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/InvenTree/templates/js/translated/model_renderers.js b/InvenTree/templates/js/translated/model_renderers.js index e45c4298a9..bdc2528af9 100644 --- a/InvenTree/templates/js/translated/model_renderers.js +++ b/InvenTree/templates/js/translated/model_renderers.js @@ -34,8 +34,8 @@ // Should the ID be rendered for this string function renderId(title, pk, parameters={}) { - // Default = true - var render = true; + // Default = do not render + var render = false; if ('render_pk' in parameters) { render = parameters['render_pk']; @@ -138,7 +138,7 @@ function renderStockLocation(name, data, parameters={}, options={}) { html += ` - ${data.description}`; } - html += `{% trans "Location ID" %}: ${data.pk}`; + html += renderId('{% trans "Location ID" %}', data.pk, parameters); return html; } @@ -155,7 +155,8 @@ function renderBuild(name, data, parameters={}, options={}) { var html = select2Thumbnail(image); html += `${data.reference} - ${data.quantity} x ${data.part_detail.full_name}`; - html += `{% trans "Build ID" %}: ${data.pk}`; + + html += renderId('{% trans "Build ID" %}', data.pk, parameters); html += `

${data.title}

`; @@ -293,12 +294,13 @@ function renderSalesOrderShipment(name, data, parameters={}, options={}) { var so_prefix = global_settings.SALESORDER_REFERENCE_PREFIX; var html = ` - ${so_prefix}${data.order_detail.reference} - {% trans "Shipment" %} ${data.reference} - - {% trans "Shipment ID" %}: ${data.pk} + + ${so_prefix}${data.order_detail.reference} - {% trans "Shipment" %} ${data.reference} `; + html += renderId('{% trans "Shipment ID" %}', data.pk, parameters); + return html; } @@ -315,7 +317,7 @@ function renderPartCategory(name, data, parameters={}, options={}) { html += ` - ${data.description}`; } - html += `{% trans "Category ID" %}: ${data.pk}`; + html += renderId('{% trans "Category ID" %}', data.pk, parameters); return html; } @@ -358,7 +360,7 @@ function renderManufacturerPart(name, data, parameters={}, options={}) { html += ` ${data.manufacturer_detail.name} - ${data.MPN}`; html += ` - ${data.part_detail.full_name}`; - html += `{% trans "Manufacturer Part ID" %}: ${data.pk}`; + html += renderId('{% trans "Manufacturer Part ID" %}', data.pk, parameters); return html; } @@ -393,8 +395,7 @@ function renderSupplierPart(name, data, parameters={}, options={}) { html += ` - ${data.part_detail.full_name}`; } - html += `{% trans "Supplier Part ID" %}: ${data.pk}`; - + html += renderId('{% trans "Supplier Part ID" %}', data.pk, parameters); return html; From e225d3b7657ea23d447fb222353819bf1b136d48 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Thu, 7 Apr 2022 19:09:43 +1000 Subject: [PATCH 06/16] Fix action buttons in "part" table on category page --- InvenTree/part/templates/part/category.html | 13 +++++++++---- InvenTree/templates/js/translated/order.js | 2 +- InvenTree/templates/js/translated/part.js | 13 ------------- 3 files changed, 10 insertions(+), 18 deletions(-) diff --git a/InvenTree/part/templates/part/category.html b/InvenTree/part/templates/part/category.html index 6a61ef2fbf..8c15b6151f 100644 --- a/InvenTree/part/templates/part/category.html +++ b/InvenTree/part/templates/part/category.html @@ -169,13 +169,18 @@ {% include "filter_list.html" with id="parts" %} diff --git a/InvenTree/templates/js/translated/order.js b/InvenTree/templates/js/translated/order.js index 78f6d39ee9..6b1db9fc4c 100644 --- a/InvenTree/templates/js/translated/order.js +++ b/InvenTree/templates/js/translated/order.js @@ -481,7 +481,7 @@ function orderParts(parts_list, options={}) { var thumb = thumbnailImage(part.thumbnail || part.image); // The "quantity" field should have been provided for each part - var quantity = part.quantity || 0; + var quantity = part.quantity || 1; if (quantity < 0) { quantity = 0; diff --git a/InvenTree/templates/js/translated/part.js b/InvenTree/templates/js/translated/part.js index 08b258fdc2..f26d87653a 100644 --- a/InvenTree/templates/js/translated/part.js +++ b/InvenTree/templates/js/translated/part.js @@ -1581,19 +1581,6 @@ function loadPartTable(table, url, options={}) { printPartLabels(items); }); - - $('#multi-part-export').click(function() { - var selections = $(table).bootstrapTable('getSelections'); - - var parts = ''; - - selections.forEach(function(item) { - parts += item.pk; - parts += ','; - }); - - location.href = '/part/export/?parts=' + parts; - }); } From c66cd1d51b448e22297c1693c8b689549069d3ae Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 26 Apr 2022 22:14:01 +1000 Subject: [PATCH 07/16] Adds button to expand row for "extra" information --- InvenTree/templates/js/translated/order.js | 29 ++++++++++++++++++---- InvenTree/templates/js/translated/part.js | 15 +++++------ 2 files changed, 32 insertions(+), 12 deletions(-) diff --git a/InvenTree/templates/js/translated/order.js b/InvenTree/templates/js/translated/order.js index 6b1db9fc4c..f55deeacef 100644 --- a/InvenTree/templates/js/translated/order.js +++ b/InvenTree/templates/js/translated/order.js @@ -540,10 +540,13 @@ function orderParts(parts_list, options={}) { var buttons = `
`; buttons += makeIconButton( - 'fa-check-circle icon-green', - 'button-row-complete', + 'fa-layer-group', + 'button-row-expand', pk, - '{% trans "Add to order" %}', + '{% trans "Expand Row" %}', + { + collapseTarget: `order_row_expand_${pk}`, + } ); if (parts.length > 1) { @@ -564,8 +567,18 @@ function orderParts(parts_list, options={}) { ${purchase_order_input} ${quantity_input} ${buttons} - - `; + `; + + // Add a second row "underneath" the first one, but collapsed + // Allows extra data to be added if required, but hidden by default + html += ` + + + reference goes here + + + + `; return html; } @@ -656,6 +669,12 @@ function orderParts(parts_list, options={}) { } }, null, opts); + // Add callback for "remove row" button + $(opts.modal).find('.button-row-remove').click(function() { + var pk = $(this).attr('pk'); + + $(opts.modal).find(`#order_row_${pk}`).remove(); + }); }); } }); diff --git a/InvenTree/templates/js/translated/part.js b/InvenTree/templates/js/translated/part.js index 6fd144d5f0..3aa3b936cf 100644 --- a/InvenTree/templates/js/translated/part.js +++ b/InvenTree/templates/js/translated/part.js @@ -1559,15 +1559,16 @@ function loadPartTable(table, url, options={}) { var parts = []; - selections.forEach(function(item) { - parts.push(item.pk); + selections.forEach(function(part) { + parts.push(part); }); - launchModalForm('/order/purchase-order/order-parts/', { - data: { - parts: parts, - }, - }); + orderParts( + parts, + { + + } + ); }); $('#multi-part-category').click(function() { From bf11e8361e80c4de1f94e5b67470ef310aa17242 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 26 Apr 2022 22:18:27 +1000 Subject: [PATCH 08/16] Add (empty) callbacks to prefix buttons --- InvenTree/templates/js/translated/order.js | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/InvenTree/templates/js/translated/order.js b/InvenTree/templates/js/translated/order.js index f55deeacef..8db8f68067 100644 --- a/InvenTree/templates/js/translated/order.js +++ b/InvenTree/templates/js/translated/order.js @@ -502,9 +502,9 @@ function orderParts(parts_list, options={}) { ) var supplier_part_prefix = ` - + `; var supplier_part_input = constructField( @@ -520,9 +520,9 @@ function orderParts(parts_list, options={}) { ); var purchase_order_prefix = ` - + `; var purchase_order_input = constructField( @@ -675,6 +675,20 @@ function orderParts(parts_list, options={}) { $(opts.modal).find(`#order_row_${pk}`).remove(); }); + + // Add callback for "new supplier part" button + $(opts.modal).find('.button-row-new-sp').click(function() { + var pk = $(this).attr('pk'); + + // Launch dialog to create new supplier part + }); + + // Add callback for "new purchase order" button + $(opts.modal).find('.button-row-new-po').click(function() { + var pk = $(this).attr('pk'); + + // Launch dialog to create new purchase order + }); }); } }); From 340d4d8a897656469d2ca266f61cd9571eb10954 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Wed, 27 Apr 2022 00:12:12 +1000 Subject: [PATCH 09/16] Launch new forms --- InvenTree/templates/js/translated/order.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/InvenTree/templates/js/translated/order.js b/InvenTree/templates/js/translated/order.js index 8db8f68067..723b462adb 100644 --- a/InvenTree/templates/js/translated/order.js +++ b/InvenTree/templates/js/translated/order.js @@ -681,6 +681,12 @@ function orderParts(parts_list, options={}) { var pk = $(this).attr('pk'); // Launch dialog to create new supplier part + createSupplierPart({ + part: pk, + onSuccess: function(response) { + // TODO + } + }); }); // Add callback for "new purchase order" button @@ -688,6 +694,11 @@ function orderParts(parts_list, options={}) { var pk = $(this).attr('pk'); // Launch dialog to create new purchase order + createPurchaseOrder({ + onSuccess: function(response) { + // TODO + } + }); }); }); } From 24af2bd2c8c23e6ae47ab2b2b480e7682a4169b5 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Wed, 27 Apr 2022 21:45:00 +1000 Subject: [PATCH 10/16] Update console output for forms.js --- InvenTree/templates/js/translated/forms.js | 50 +++++++++++----------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/InvenTree/templates/js/translated/forms.js b/InvenTree/templates/js/translated/forms.js index e01835ae36..01f9e162eb 100644 --- a/InvenTree/templates/js/translated/forms.js +++ b/InvenTree/templates/js/translated/forms.js @@ -135,7 +135,7 @@ function getApiEndpointOptions(url, callback) { success: callback, error: function(xhr) { // TODO: Handle error - console.log(`ERROR in getApiEndpointOptions at '${url}'`); + console.error(`Error in getApiEndpointOptions at '${url}'`); showApiError(xhr, url); } }); @@ -227,7 +227,7 @@ function constructChangeForm(fields, options) { }, error: function(xhr) { // TODO: Handle error here - console.log(`ERROR in constructChangeForm at '${options.url}'`); + console.error(`Error in constructChangeForm at '${options.url}'`); showApiError(xhr, options.url); } @@ -268,7 +268,7 @@ function constructDeleteForm(fields, options) { }, error: function(xhr) { // TODO: Handle error here - console.log(`ERROR in constructDeleteForm at '${options.url}`); + console.error(`Error in constructDeleteForm at '${options.url}`); showApiError(xhr, options.url); } @@ -354,7 +354,7 @@ function constructForm(url, options) { icon: 'fas fa-user-times', }); - console.log(`'POST action unavailable at ${url}`); + console.warn(`'POST action unavailable at ${url}`); } break; case 'PUT': @@ -369,7 +369,7 @@ function constructForm(url, options) { icon: 'fas fa-user-times', }); - console.log(`${options.method} action unavailable at ${url}`); + console.warn(`${options.method} action unavailable at ${url}`); } break; case 'DELETE': @@ -383,7 +383,7 @@ function constructForm(url, options) { icon: 'fas fa-user-times', }); - console.log(`DELETE action unavailable at ${url}`); + console.warn(`DELETE action unavailable at ${url}`); } break; case 'GET': @@ -397,11 +397,11 @@ function constructForm(url, options) { icon: 'fas fa-user-times', }); - console.log(`GET action unavailable at ${url}`); + console.warn(`GET action unavailable at ${url}`); } break; default: - console.log(`constructForm() called with invalid method '${options.method}'`); + console.warn(`constructForm() called with invalid method '${options.method}'`); break; } }); @@ -731,7 +731,7 @@ function submitFormData(fields, options) { data[name] = value; } } else { - console.log(`WARNING: Could not find field matching '${name}'`); + console.warn(`Could not find field matching '${name}'`); } } @@ -776,7 +776,7 @@ function submitFormData(fields, options) { default: $(options.modal).modal('hide'); - console.log(`upload error at ${options.url}`); + console.error(`Upload error at ${options.url}`); showApiError(xhr, options.url); break; } @@ -827,7 +827,7 @@ function updateFieldValue(name, value, field, options) { var el = getFormFieldElement(name, options); if (!el) { - console.log(`WARNING: updateFieldValue could not find field '${name}'`); + console.warn(`updateFieldValue could not find field '${name}'`); return; } @@ -870,7 +870,7 @@ function getFormFieldElement(name, options) { } if (!el.exists) { - console.log(`ERROR: Could not find form element for field '${name}'`); + console.error(`Could not find form element for field '${name}'`); } return el; @@ -918,7 +918,7 @@ function getFormFieldValue(name, field={}, options={}) { var el = getFormFieldElement(name, options); if (!el.exists()) { - console.log(`ERROR: getFormFieldValue could not locate field '${name}'`); + console.error(`getFormFieldValue could not locate field '${name}'`); return null; } @@ -1104,7 +1104,7 @@ function handleNestedErrors(errors, field_name, options={}) { // Nest list must be provided! if (!nest_list) { - console.log(`WARNING: handleNestedErrors missing nesting options for field '${fieldName}'`); + console.warn(`handleNestedErrors missing nesting options for field '${fieldName}'`); return; } @@ -1113,7 +1113,7 @@ function handleNestedErrors(errors, field_name, options={}) { var error_item = error_list[idx]; if (idx >= nest_list.length) { - console.log(`WARNING: handleNestedErrors returned greater number of errors (${error_list.length}) than could be handled (${nest_list.length})`); + console.warn(`handleNestedErrors returned greater number of errors (${error_list.length}) than could be handled (${nest_list.length})`); break; } @@ -1285,7 +1285,7 @@ function addFieldErrorMessage(name, error_text, error_idx=0, options={}) { field_dom.append(error_html); } else { - console.log(`WARNING: addFieldErrorMessage could not locate field '${field_name}'`); + console.warn(`addFieldErrorMessage could not locate field '${field_name}'`); } } @@ -1358,7 +1358,7 @@ function addClearCallback(name, field, options={}) { } if (!el) { - console.log(`WARNING: addClearCallback could not find field '${name}'`); + console.warn(`addClearCallback could not find field '${name}'`); return; } @@ -1582,7 +1582,7 @@ function initializeRelatedField(field, fields, options={}) { var name = field.name; if (!field.api_url) { - console.log(`WARNING: Related field '${name}' missing 'api_url' parameter.`); + console.warn(`Related field '${name}' missing 'api_url' parameter.`); return; } @@ -1712,7 +1712,7 @@ function initializeRelatedField(field, fields, options={}) { return $(html); } else { // Return a simple renderering - console.log(`WARNING: templateResult() missing 'field.model' for '${name}'`); + console.warn(`templateResult() missing 'field.model' for '${name}'`); return `${name} - ${item.id}`; } }, @@ -1742,7 +1742,7 @@ function initializeRelatedField(field, fields, options={}) { return $(html); } else { // Return a simple renderering - console.log(`WARNING: templateSelection() missing 'field.model' for '${name}'`); + console.warn(`templateSelection() missing 'field.model' for '${name}'`); return `${name} - ${item.id}`; } } @@ -1916,7 +1916,7 @@ function renderModelData(name, model, data, parameters, options) { if (html != null) { return html; } else { - console.log(`ERROR: Rendering not implemented for model '${model}'`); + console.error(`Rendering not implemented for model '${model}'`); // Simple text rendering return `${model} - ID ${data.id}`; } @@ -2201,7 +2201,7 @@ function constructInput(name, parameters, options={}) { if (func != null) { html = func(name, parameters, options); } else { - console.log(`WARNING: Unhandled form field type: '${parameters.type}'`); + console.warn(`Unhandled form field type: '${parameters.type}'`); } return html; @@ -2504,12 +2504,12 @@ function constructHelpText(name, parameters) { function selectImportFields(url, data={}, options={}) { if (!data.model_fields) { - console.log(`WARNING: selectImportFields is missing 'model_fields'`); + console.warn(`selectImportFields is missing 'model_fields'`); return; } if (!data.file_fields) { - console.log(`WARNING: selectImportFields is missing 'file_fields'`); + console.warn(`selectImportFields is missing 'file_fields'`); return; } @@ -2600,7 +2600,7 @@ function selectImportFields(url, data={}, options={}) { default: $(opts.modal).modal('hide'); - console.log(`upload error at ${opts.url}`); + console.error(`upload error at ${opts.url}`); showApiError(xhr, opts.url); break; } From 4732efb330456c4ae9b1f3fe73a210b21115565b Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 3 May 2022 11:42:13 +1000 Subject: [PATCH 11/16] Fix callbacks for adding new supplier part and/or purchase order "inline" --- InvenTree/company/api.py | 2 +- InvenTree/templates/js/translated/company.js | 8 +- .../js/translated/model_renderers.js | 4 +- InvenTree/templates/js/translated/order.js | 82 ++++++++----------- 4 files changed, 44 insertions(+), 52 deletions(-) diff --git a/InvenTree/company/api.py b/InvenTree/company/api.py index e4a589d9e5..b99fbd01fb 100644 --- a/InvenTree/company/api.py +++ b/InvenTree/company/api.py @@ -312,7 +312,7 @@ class SupplierPartList(generics.ListCreateAPIView): try: params = self.request.query_params kwargs['part_detail'] = str2bool(params.get('part_detail', None)) - kwargs['supplier_detail'] = str2bool(params.get('supplier_detail', None)) + kwargs['supplier_detail'] = str2bool(params.get('supplier_detail', True)) kwargs['manufacturer_detail'] = str2bool(params.get('manufacturer_detail', None)) kwargs['pretty'] = str2bool(params.get('pretty', None)) except AttributeError: diff --git a/InvenTree/templates/js/translated/company.js b/InvenTree/templates/js/translated/company.js index c92bb75d6f..d7b98841e4 100644 --- a/InvenTree/templates/js/translated/company.js +++ b/InvenTree/templates/js/translated/company.js @@ -115,10 +115,6 @@ function supplierPartFields() { return { part: {}, - supplier: {}, - SKU: { - icon: 'fa-hashtag', - }, manufacturer_part: { filters: { part_detail: true, @@ -126,6 +122,10 @@ function supplierPartFields() { }, auto_fill: true, }, + supplier: {}, + SKU: { + icon: 'fa-hashtag', + }, description: {}, link: { icon: 'fa-link', diff --git a/InvenTree/templates/js/translated/model_renderers.js b/InvenTree/templates/js/translated/model_renderers.js index 8fd6a6b3f4..d55d93e531 100644 --- a/InvenTree/templates/js/translated/model_renderers.js +++ b/InvenTree/templates/js/translated/model_renderers.js @@ -394,7 +394,9 @@ function renderSupplierPart(name, data, parameters={}, options={}) { html += select2Thumbnail(part_image); } - html += ` ${data.supplier_detail.name} - ${data.SKU}`; + if (data.supplier_detail) { + html += ` ${data.supplier_detail.name} - ${data.SKU}`; + } if (data.part_detail) { html += ` - ${data.part_detail.full_name}`; diff --git a/InvenTree/templates/js/translated/order.js b/InvenTree/templates/js/translated/order.js index 16b5b1f00d..72ec4197a5 100644 --- a/InvenTree/templates/js/translated/order.js +++ b/InvenTree/templates/js/translated/order.js @@ -260,8 +260,8 @@ function createPurchaseOrder(options={}) { } } }, - supplier_reference: {}, description: {}, + supplier_reference: {}, target_date: { icon: 'fa-calendar-alt', }, @@ -670,61 +670,51 @@ function orderParts(parts_list, options={}) { auto_fill: false, filters: { status: {{ PurchaseOrderStatus.PENDING }}, - }, - adjustFilters: function(query, opts) { - - // Whenever we open the drop-down to select an order, - // ensure we are only displaying orders which match the selected supplier part - var supplier_part_pk = getFormFieldValue(`supplier_part_${part.pk}`, opts); - - inventreeGet( - `/api/company/part/${supplier_part_pk}/`, - {}, - { - async: false, - success: function(data) { - query.supplier = data.supplier; - } - } - ); - - return query; + supplier_detail: true, }, noResults: function(query) { return '{% trans "No matching purchase orders" %}'; } }, null, opts); + }); - // Add callback for "remove row" button - $(opts.modal).find('.button-row-remove').click(function() { - var pk = $(this).attr('pk'); + // Add callback for "remove row" button + $(opts.modal).find('.button-row-remove').click(function() { + var pk = $(this).attr('pk'); - $(opts.modal).find(`#order_row_${pk}`).remove(); + $(opts.modal).find(`#order_row_${pk}`).remove(); + }); + + // Add callback for "new supplier part" button + $(opts.modal).find('.button-row-new-sp').click(function() { + var pk = $(this).attr('pk'); + + // Launch dialog to create new supplier part + createSupplierPart({ + part: pk, + onSuccess: function(response) { + setRelatedFieldData( + `supplier_part_${pk}`, + response, + opts + ); + } }); + }); - // Add callback for "new supplier part" button - $(opts.modal).find('.button-row-new-sp').click(function() { - var pk = $(this).attr('pk'); + // Add callback for "new purchase order" button + $(opts.modal).find('.button-row-new-po').click(function() { + var pk = $(this).attr('pk'); - // Launch dialog to create new supplier part - createSupplierPart({ - part: pk, - onSuccess: function(response) { - // TODO - } - }); - }); - - // Add callback for "new purchase order" button - $(opts.modal).find('.button-row-new-po').click(function() { - var pk = $(this).attr('pk'); - - // Launch dialog to create new purchase order - createPurchaseOrder({ - onSuccess: function(response) { - // TODO - } - }); + // Launch dialog to create new purchase order + createPurchaseOrder({ + onSuccess: function(response) { + setRelatedFieldData( + `purchase_order_${pk}`, + response, + opts + ); + } }); }); } From d69b5811b1fdda28df5c31230c065a8e4ebfd930 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 3 May 2022 12:00:28 +1000 Subject: [PATCH 12/16] Improved javascript log / warn / error messages --- InvenTree/templates/js/translated/api.js | 2 +- InvenTree/templates/js/translated/barcode.js | 1 - InvenTree/templates/js/translated/bom.js | 2 +- InvenTree/templates/js/translated/filters.js | 4 ++-- InvenTree/templates/js/translated/modals.js | 8 ++++---- InvenTree/templates/js/translated/order.js | 14 +++++++------- InvenTree/templates/js/translated/part.js | 2 +- InvenTree/templates/js/translated/tables.js | 4 ++-- 8 files changed, 18 insertions(+), 19 deletions(-) diff --git a/InvenTree/templates/js/translated/api.js b/InvenTree/templates/js/translated/api.js index fccd6bf5ef..119376c310 100644 --- a/InvenTree/templates/js/translated/api.js +++ b/InvenTree/templates/js/translated/api.js @@ -105,7 +105,7 @@ function inventreeFormDataUpload(url, data, options={}) { } }, error: function(xhr, status, error) { - console.log('Form data upload failure: ' + status); + console.error('Form data upload failure: ' + status); if (options.error) { options.error(xhr, status, error); diff --git a/InvenTree/templates/js/translated/barcode.js b/InvenTree/templates/js/translated/barcode.js index eb8a7f98b7..a6305eb1df 100644 --- a/InvenTree/templates/js/translated/barcode.js +++ b/InvenTree/templates/js/translated/barcode.js @@ -86,7 +86,6 @@ function onCameraAvailable(hasCamera, options) { function onBarcodeScanCompleted(result, options) { if (result.data == '') return; - console.log('decoded qr code:', result.data); stopQrScanner(); postBarcodeData(result.data, options); } diff --git a/InvenTree/templates/js/translated/bom.js b/InvenTree/templates/js/translated/bom.js index e4674d5989..7308583ae3 100644 --- a/InvenTree/templates/js/translated/bom.js +++ b/InvenTree/templates/js/translated/bom.js @@ -1061,7 +1061,7 @@ function loadBomTable(table, options={}) { table.bootstrapTable('append', response); }, error: function(xhr) { - console.log('Error requesting BOM for part=' + part_pk); + console.error('Error requesting BOM for part=' + part_pk); showApiError(xhr); } } diff --git a/InvenTree/templates/js/translated/filters.js b/InvenTree/templates/js/translated/filters.js index ceef79f66d..dd45aa6628 100644 --- a/InvenTree/templates/js/translated/filters.js +++ b/InvenTree/templates/js/translated/filters.js @@ -62,7 +62,7 @@ function loadTableFilters(tableKey) { if (f.length == 2) { filters[f[0]] = f[1]; } else { - console.log(`Improperly formatted filter: ${item}`); + console.warn(`Improperly formatted filter: ${item}`); } } }); @@ -274,7 +274,7 @@ function setupFilterList(tableKey, table, target, options={}) { var element = $(target); if (!element || !element.exists()) { - console.log(`WARNING: setupFilterList could not find target '${target}'`); + console.warn(`setupFilterList could not find target '${target}'`); return; } diff --git a/InvenTree/templates/js/translated/modals.js b/InvenTree/templates/js/translated/modals.js index f114f6f419..b72643a0d7 100644 --- a/InvenTree/templates/js/translated/modals.js +++ b/InvenTree/templates/js/translated/modals.js @@ -274,7 +274,7 @@ function reloadFieldOptions(fieldName, options) { setFieldOptions(fieldName, opts); }, error: function() { - console.log('Error GETting field options'); + console.error('Error GETting field options'); } }); } @@ -842,7 +842,7 @@ function attachFieldCallback(modal, callback) { // Run the callback function with the new value of the field! callback.action(field.val(), field); } else { - console.log(`Value changed for field ${callback.field} - ${field.val()}`); + console.info(`Value changed for field ${callback.field} - ${field.val()} (no callback attached)`); } }); } @@ -1085,8 +1085,8 @@ function launchModalForm(url, options = {}) { showAlertDialog('{% trans "Error requesting form data" %}', renderErrorMessage(xhr)); } - console.log('Modal form error: ' + xhr.status); - console.log('Message: ' + xhr.responseText); + console.error('Modal form error: ' + xhr.status); + console.info('Message: ' + xhr.responseText); } }; diff --git a/InvenTree/templates/js/translated/order.js b/InvenTree/templates/js/translated/order.js index 72ec4197a5..882657bb0a 100644 --- a/InvenTree/templates/js/translated/order.js +++ b/InvenTree/templates/js/translated/order.js @@ -1403,7 +1403,7 @@ function loadPurchaseOrderLineItemTable(table, options={}) { var line_item = $(table).bootstrapTable('getRowByUniqueId', pk); if (!line_item) { - console.log('WARNING: getRowByUniqueId returned null'); + console.warn('getRowByUniqueId returned null'); return; } @@ -1662,12 +1662,12 @@ function loadPurchaseOrderExtraLineTable(table, options={}) { options.params = options.params || {}; if (!options.order) { - console.log('ERROR: function called without order ID'); + console.error('function called without order ID'); return; } if (!options.status) { - console.log('ERROR: function called without order status'); + console.error('function called without order status'); return; } @@ -2789,12 +2789,12 @@ function loadSalesOrderLineItemTable(table, options={}) { options.params = options.params || {}; if (!options.order) { - console.log('ERROR: function called without order ID'); + console.error('function called without order ID'); return; } if (!options.status) { - console.log('ERROR: function called without order status'); + console.error('function called without order status'); return; } @@ -3297,12 +3297,12 @@ function loadSalesOrderExtraLineTable(table, options={}) { options.params = options.params || {}; if (!options.order) { - console.log('ERROR: function called without order ID'); + console.error('function called without order ID'); return; } if (!options.status) { - console.log('ERROR: function called without order status'); + console.error('function called without order status'); return; } diff --git a/InvenTree/templates/js/translated/part.js b/InvenTree/templates/js/translated/part.js index 29ac95f149..8d76b833a6 100644 --- a/InvenTree/templates/js/translated/part.js +++ b/InvenTree/templates/js/translated/part.js @@ -876,7 +876,7 @@ function loadPartPurchaseOrderTable(table, part_id, options={}) { var line_item = $(table).bootstrapTable('getRowByUniqueId', pk); if (!line_item) { - console.log('WARNING: getRowByUniqueId returned null'); + console.warn('getRowByUniqueId returned null'); return; } diff --git a/InvenTree/templates/js/translated/tables.js b/InvenTree/templates/js/translated/tables.js index 8a6674299c..96c561b49b 100644 --- a/InvenTree/templates/js/translated/tables.js +++ b/InvenTree/templates/js/translated/tables.js @@ -39,7 +39,7 @@ function downloadTableData(table, opts={}) { var url = table_options.url; if (!url) { - console.log('Error: downloadTableData could not find "url" parameter.'); + console.error('downloadTableData could not find "url" parameter.'); } var query_params = table_options.query_params || {}; @@ -343,7 +343,7 @@ $.fn.inventreeTable = function(options) { } }); } else { - console.log(`Could not get list of visible columns for table '${tableName}'`); + console.error(`Could not get list of visible columns for table '${tableName}'`); } } From 1794f65d197dd02a75ecbdd544bbce903394d749 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 3 May 2022 13:44:28 +1000 Subject: [PATCH 13/16] Button to submit each row individually --- InvenTree/order/serializers.py | 1 + InvenTree/templates/js/translated/forms.js | 35 +++++---- InvenTree/templates/js/translated/order.js | 91 +++++++++++++++------- 3 files changed, 80 insertions(+), 47 deletions(-) diff --git a/InvenTree/order/serializers.py b/InvenTree/order/serializers.py index cc07dd3fea..eabc5e630e 100644 --- a/InvenTree/order/serializers.py +++ b/InvenTree/order/serializers.py @@ -246,6 +246,7 @@ class PurchaseOrderLineItemSerializer(InvenTreeModelSerializer): total_price = serializers.FloatField(read_only=True) part_detail = PartBriefSerializer(source='get_base_part', many=False, read_only=True) + supplier_part_detail = SupplierPartSerializer(source='part', many=False, read_only=True) purchase_price = InvenTreeMoneySerializer( diff --git a/InvenTree/templates/js/translated/forms.js b/InvenTree/templates/js/translated/forms.js index 01f9e162eb..cc138052ef 100644 --- a/InvenTree/templates/js/translated/forms.js +++ b/InvenTree/templates/js/translated/forms.js @@ -1218,29 +1218,26 @@ function handleFormErrors(errors, fields={}, options={}) { for (var field_name in errors) { - if (field_name in fields) { + var field = fields[field_name] || {}; - var field = fields[field_name]; + if ((field.type == 'field') && ('child' in field)) { + // This is a "nested" field + handleNestedErrors(errors, field_name, options); + } else { + // This is a "simple" field - if ((field.type == 'field') && ('child' in field)) { - // This is a "nested" field - handleNestedErrors(errors, field_name, options); - } else { - // This is a "simple" field + var field_errors = errors[field_name]; - var field_errors = errors[field_name]; + if (field_errors && !first_error_field && isFieldVisible(field_name, options)) { + first_error_field = field_name; + } - if (field_errors && !first_error_field && isFieldVisible(field_name, options)) { - first_error_field = field_name; - } + // Add an entry for each returned error message + for (var ii = field_errors.length-1; ii >= 0; ii--) { - // Add an entry for each returned error message - for (var ii = field_errors.length-1; ii >= 0; ii--) { + var error_text = field_errors[ii]; - var error_text = field_errors[ii]; - - addFieldErrorMessage(field_name, error_text, ii, options); - } + addFieldErrorMessage(field_name, error_text, ii, options); } } } @@ -1929,6 +1926,10 @@ function renderModelData(name, model, data, parameters, options) { function getFieldName(name, options={}) { var field_name = name; + if (options.field_suffix) { + field_name += options.field_suffix; + } + if (options && options.depth) { field_name += `_${options.depth}`; } diff --git a/InvenTree/templates/js/translated/order.js b/InvenTree/templates/js/translated/order.js index 882657bb0a..042e5b785b 100644 --- a/InvenTree/templates/js/translated/order.js +++ b/InvenTree/templates/js/translated/order.js @@ -514,7 +514,7 @@ function orderParts(parts_list, options={}) { } var quantity_input = constructField( - `items_quantity_${pk}`, + `quantity_${pk}`, { type: 'decimal', min_value: 0, @@ -552,7 +552,7 @@ function orderParts(parts_list, options={}) { `; var purchase_order_input = constructField( - `purchase_order_${pk}`, + `order_${pk}`, { type: 'related field', required: true, @@ -565,16 +565,6 @@ function orderParts(parts_list, options={}) { var buttons = `
`; - buttons += makeIconButton( - 'fa-layer-group', - 'button-row-expand', - pk, - '{% trans "Expand Row" %}', - { - collapseTarget: `order_row_expand_${pk}`, - } - ); - if (parts.length > 1) { buttons += makeIconButton( 'fa-times icon-red', @@ -584,26 +574,23 @@ function orderParts(parts_list, options={}) { ); } + // Button to add row to purchase order + buttons += makeIconButton( + 'fa-shopping-cart icon-blue', + 'button-row-add', + pk, + '{% trans "Add to purchase order" %}', + ); + buttons += `
`; var html = ` - ${thumb} ${part.full_name} - ${supplier_part_input} - ${purchase_order_input} - ${quantity_input} - ${buttons} - `; - - // Add a second row "underneath" the first one, but collapsed - // Allows extra data to be added if required, but hidden by default - html += ` - - - reference goes here - - - + ${thumb} ${part.full_name} + ${supplier_part_input} + ${purchase_order_input} + ${quantity_input} + ${buttons} `; return html; @@ -662,7 +649,7 @@ function orderParts(parts_list, options={}) { // Configure the "purchase order" field initializeRelatedField({ - name: `purchase_order_${part.pk}`, + name: `order_${part.pk}`, model: 'purchaseorder', api_url: '{% url "api-po-list" %}', required: true, @@ -678,6 +665,50 @@ function orderParts(parts_list, options={}) { }, null, opts); }); + // Add callback for "add to purchase order" button + $(opts.modal).find('.button-row-add').click(function() { + var pk = $(this).attr('pk'); + + opts.field_suffix = null; + + // Extract information from the row + var data = { + quantity: getFormFieldValue(`quantity_${pk}`, {type: 'decimal',}, opts), + supplier_part: getFormFieldValue(`supplier_part_${pk}`, {}, opts), + order: getFormFieldValue(`order_${pk}`, {}, opts), + } + + // $(opts.modal).find(`#order_row_${pk}`).disable(); + // $(this).disable(); + + // Duplicate the form options, to prevent 'field_suffix' override + var row_opts = Object.assign(opts); + row_opts.field_suffix = `_${pk}`; + + inventreePut( + '{% url "api-po-line-list" %}', + data, + { + method: 'POST', + success: function(response) { + // Remove the row + $(opts.modal).find(`#order_row_${pk}`).remove(); + }, + error: function(xhr) { + switch (xhr.status) { + case 400: + handleFormErrors(xhr.responseJSON, fields, row_opts); + break; + default: + console.error(`Error adding line to purchase order`); + showApiError(xhr, options.url); + break; + } + } + } + ); + }); + // Add callback for "remove row" button $(opts.modal).find('.button-row-remove').click(function() { var pk = $(this).attr('pk'); @@ -710,7 +741,7 @@ function orderParts(parts_list, options={}) { createPurchaseOrder({ onSuccess: function(response) { setRelatedFieldData( - `purchase_order_${pk}`, + `order_${pk}`, response, opts ); From 8cd8581dbf8bdba49c765ce698b7582b7f4f4be9 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 3 May 2022 13:51:04 +1000 Subject: [PATCH 14/16] fixes --- InvenTree/order/serializers.py | 14 ++++++++++++-- InvenTree/templates/js/translated/order.js | 8 ++++---- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/InvenTree/order/serializers.py b/InvenTree/order/serializers.py index eabc5e630e..660a278cf5 100644 --- a/InvenTree/order/serializers.py +++ b/InvenTree/order/serializers.py @@ -268,8 +268,18 @@ class PurchaseOrderLineItemSerializer(InvenTreeModelSerializer): data = super().validate(data) - supplier_part = data['part'] - purchase_order = data['order'] + supplier_part = data.get('part', None) + purchase_order = data.get('order', None) + + if not supplier_part: + raise ValidationError({ + 'part': _('Supplier part must be specified'), + }) + + if not purchase_order: + raise ValidationError({ + 'order': _('Purchase order must be specified'), + }) # Check that the supplier part and purchase order match if supplier_part is not None and supplier_part.supplier != purchase_order.supplier: diff --git a/InvenTree/templates/js/translated/order.js b/InvenTree/templates/js/translated/order.js index 042e5b785b..b17130d05b 100644 --- a/InvenTree/templates/js/translated/order.js +++ b/InvenTree/templates/js/translated/order.js @@ -534,7 +534,7 @@ function orderParts(parts_list, options={}) { `; var supplier_part_input = constructField( - `supplier_part_${pk}`, + `part_${pk}`, { type: 'related field', required: true, @@ -631,7 +631,7 @@ function orderParts(parts_list, options={}) { parts.forEach(function(part) { // Configure the "supplier part" field initializeRelatedField({ - name: `supplier_part_${part.pk}`, + name: `part_${part.pk}`, model: 'supplierpart', api_url: '{% url "api-supplier-part-list" %}', required: true, @@ -674,7 +674,7 @@ function orderParts(parts_list, options={}) { // Extract information from the row var data = { quantity: getFormFieldValue(`quantity_${pk}`, {type: 'decimal',}, opts), - supplier_part: getFormFieldValue(`supplier_part_${pk}`, {}, opts), + part: getFormFieldValue(`part_${pk}`, {}, opts), order: getFormFieldValue(`order_${pk}`, {}, opts), } @@ -725,7 +725,7 @@ function orderParts(parts_list, options={}) { part: pk, onSuccess: function(response) { setRelatedFieldData( - `supplier_part_${pk}`, + `part_${pk}`, response, opts ); From 141b764b9496caf24cb913cbc8c21ea5ef787c43 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 3 May 2022 14:00:43 +1000 Subject: [PATCH 15/16] Modal fixes --- InvenTree/order/serializers.py | 2 +- InvenTree/templates/js/translated/bom.js | 4 +-- InvenTree/templates/js/translated/modals.js | 37 ++++++++++++--------- InvenTree/templates/js/translated/order.js | 3 +- 4 files changed, 26 insertions(+), 20 deletions(-) diff --git a/InvenTree/order/serializers.py b/InvenTree/order/serializers.py index 660a278cf5..7d26ce741d 100644 --- a/InvenTree/order/serializers.py +++ b/InvenTree/order/serializers.py @@ -246,7 +246,7 @@ class PurchaseOrderLineItemSerializer(InvenTreeModelSerializer): total_price = serializers.FloatField(read_only=True) part_detail = PartBriefSerializer(source='get_base_part', many=False, read_only=True) - + supplier_part_detail = SupplierPartSerializer(source='part', many=False, read_only=True) purchase_price = InvenTreeMoneySerializer( diff --git a/InvenTree/templates/js/translated/bom.js b/InvenTree/templates/js/translated/bom.js index 7308583ae3..9bd66877da 100644 --- a/InvenTree/templates/js/translated/bom.js +++ b/InvenTree/templates/js/translated/bom.js @@ -129,7 +129,7 @@ function constructBomUploadTable(data, options={}) { var modal = createNewModal({ title: '{% trans "Row Data" %}', - cancelText: '{% trans "Close" %}', + closeText: '{% trans "Close" %}', hideSubmitButton: true }); @@ -617,7 +617,7 @@ function bomSubstitutesDialog(bom_item_id, substitutes, options={}) { }, }, preFormContent: html, - cancelText: '{% trans "Close" %}', + closeText: '{% trans "Close" %}', submitText: '{% trans "Add Substitute" %}', title: '{% trans "Edit BOM Item Substitutes" %}', afterRender: function(fields, opts) { diff --git a/InvenTree/templates/js/translated/modals.js b/InvenTree/templates/js/translated/modals.js index b72643a0d7..85f503682e 100644 --- a/InvenTree/templates/js/translated/modals.js +++ b/InvenTree/templates/js/translated/modals.js @@ -85,12 +85,25 @@ function createNewModal(options={}) { var modal_name = `#modal-form-${id}`; + // Callback *after* the modal has been rendered $(modal_name).on('shown.bs.modal', function() { $(modal_name + ' .modal-form-content').scrollTop(0); if (options.focus) { getFieldByName(modal_name, options.focus).focus(); } + + // Steal keyboard focus + $(modal_name).focus(); + + if (options.hideCloseButton) { + $(modal_name).find('#modal-form-cancel').hide(); + } + + if (options.preventSubmit || options.hideSubmitButton) { + $(modal_name).find('#modal-form-submit').hide(); + } + }); // Automatically remove the modal when it is deleted! @@ -102,8 +115,11 @@ function createNewModal(options={}) { $(modal_name).on('keydown', 'input', function(event) { if (event.keyCode == 13) { event.preventDefault(); - // Simulate a click on the 'Submit' button - $(modal_name).find('#modal-form-submit').click(); + + if (!options.preventSubmit) { + // Simulate a click on the 'Submit' button + $(modal_name).find('#modal-form-submit').click(); + } return false; } @@ -117,18 +133,7 @@ function createNewModal(options={}) { // Set labels based on supplied options modalSetTitle(modal_name, options.title || '{% trans "Form Title" %}'); modalSetSubmitText(modal_name, options.submitText || '{% trans "Submit" %}'); - modalSetCloseText(modal_name, options.cancelText || '{% trans "Cancel" %}'); - - if (options.hideSubmitButton) { - $(modal_name).find('#modal-form-submit').hide(); - } - - if (options.hideCloseButton) { - $(modal_name).find('#modal-form-cancel').hide(); - } - - // Steal keyboard focus - $(modal_name).focus(); + modalSetCloseText(modal_name, options.closeText || '{% trans "Cancel" %}'); // Return the "name" of the modal return modal_name; @@ -581,7 +586,7 @@ function showAlertDialog(title, content, options={}) { var modal = createNewModal({ title: title, - cancelText: '{% trans "Close" %}', + closeText: '{% trans "Close" %}', hideSubmitButton: true, }); @@ -607,7 +612,7 @@ function showQuestionDialog(title, content, options={}) { var modal = createNewModal({ title: title, submitText: options.accept_text || '{% trans "Accept" %}', - cancelText: options.cancel_text || '{% trans "Cancel" %}', + closeText: options.cancel_text || '{% trans "Cancel" %}', }); modalSetContent(modal, content); diff --git a/InvenTree/templates/js/translated/order.js b/InvenTree/templates/js/translated/order.js index b17130d05b..6c0c97cdce 100644 --- a/InvenTree/templates/js/translated/order.js +++ b/InvenTree/templates/js/translated/order.js @@ -625,7 +625,8 @@ function orderParts(parts_list, options={}) { constructFormBody({}, { preFormContent: html, title: '{% trans "Order Parts" %}', - hideSubmitButton: true, + preventSubmit: true, + closeText: '{% trans "Close" %}', afterRender: function(fields, opts) { // TODO parts.forEach(function(part) { From bac5a164919af264b821038b0b1e0713ff70cce7 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 3 May 2022 14:28:41 +1000 Subject: [PATCH 16/16] JS linting fxies --- InvenTree/templates/js/translated/order.js | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/InvenTree/templates/js/translated/order.js b/InvenTree/templates/js/translated/order.js index 6c0c97cdce..7daaffff09 100644 --- a/InvenTree/templates/js/translated/order.js +++ b/InvenTree/templates/js/translated/order.js @@ -525,7 +525,7 @@ function orderParts(parts_list, options={}) { { hideLabels: true, } - ) + ); var supplier_part_prefix = ` @@ -674,13 +674,10 @@ function orderParts(parts_list, options={}) { // Extract information from the row var data = { - quantity: getFormFieldValue(`quantity_${pk}`, {type: 'decimal',}, opts), + quantity: getFormFieldValue(`quantity_${pk}`, {type: 'decimal'}, opts), part: getFormFieldValue(`part_${pk}`, {}, opts), order: getFormFieldValue(`order_${pk}`, {}, opts), - } - - // $(opts.modal).find(`#order_row_${pk}`).disable(); - // $(this).disable(); + }; // Duplicate the form options, to prevent 'field_suffix' override var row_opts = Object.assign(opts);