From 96a885e4e15a371591aaa2877f23ed7963556a6d Mon Sep 17 00:00:00 2001 From: Oliver Date: Thu, 9 Dec 2021 00:20:45 +1100 Subject: [PATCH] client side form for assigning stock items to customers --- InvenTree/stock/forms.py | 14 -- .../stock/templates/stock/item_base.html | 16 +- InvenTree/stock/urls.py | 1 - InvenTree/stock/views.py | 33 ---- InvenTree/templates/js/translated/stock.js | 158 +++++++++++++++++- 5 files changed, 168 insertions(+), 54 deletions(-) diff --git a/InvenTree/stock/forms.py b/InvenTree/stock/forms.py index 8e998460ca..dcbf722997 100644 --- a/InvenTree/stock/forms.py +++ b/InvenTree/stock/forms.py @@ -21,20 +21,6 @@ from part.models import Part from .models import StockLocation, StockItem, StockItemTracking -class AssignStockItemToCustomerForm(HelperForm): - """ - Form for manually assigning a StockItem to a Customer - - TODO: This could be a simple API driven form! - """ - - class Meta: - model = StockItem - fields = [ - 'customer', - ] - - class ReturnStockItemForm(HelperForm): """ Form for manually returning a StockItem into stock diff --git a/InvenTree/stock/templates/stock/item_base.html b/InvenTree/stock/templates/stock/item_base.html index 949c172e9e..64b45ed0c8 100644 --- a/InvenTree/stock/templates/stock/item_base.html +++ b/InvenTree/stock/templates/stock/item_base.html @@ -568,11 +568,19 @@ $("#stock-convert").click(function() { {% if item.in_stock %} $("#stock-assign-to-customer").click(function() { - launchModalForm("{% url 'stock-item-assign' item.id %}", - { - reload: true, + + inventreeGet('{% url "api-stock-detail" item.pk %}', {}, { + success: function(response) { + assignStockToCustomer( + [response], + { + success: function() { + location.reload(); + }, + } + ); } - ); + }); }); $("#stock-move").click(function() { diff --git a/InvenTree/stock/urls.py b/InvenTree/stock/urls.py index eb4aa2e65c..7f35904b51 100644 --- a/InvenTree/stock/urls.py +++ b/InvenTree/stock/urls.py @@ -23,7 +23,6 @@ stock_item_detail_urls = [ url(r'^delete/', views.StockItemDelete.as_view(), name='stock-item-delete'), url(r'^qr_code/', views.StockItemQRCode.as_view(), name='stock-item-qr'), url(r'^delete_test_data/', views.StockItemDeleteTestData.as_view(), name='stock-item-delete-test-data'), - url(r'^assign/', views.StockItemAssignToCustomer.as_view(), name='stock-item-assign'), url(r'^return/', views.StockItemReturnToStock.as_view(), name='stock-item-return'), url(r'^install/', views.StockItemInstall.as_view(), name='stock-item-install'), diff --git a/InvenTree/stock/views.py b/InvenTree/stock/views.py index 6d93ae47e0..27801f0ed6 100644 --- a/InvenTree/stock/views.py +++ b/InvenTree/stock/views.py @@ -294,39 +294,6 @@ class StockLocationQRCode(QRCodeView): return None -class StockItemAssignToCustomer(AjaxUpdateView): - """ - View for manually assigning a StockItem to a Customer - """ - - model = StockItem - ajax_form_title = _("Assign to Customer") - context_object_name = "item" - form_class = StockForms.AssignStockItemToCustomerForm - - def validate(self, item, form, **kwargs): - - customer = form.cleaned_data.get('customer', None) - - if not customer: - form.add_error('customer', _('Customer must be specified')) - - def save(self, item, form, **kwargs): - """ - Assign the stock item to the customer. - """ - - customer = form.cleaned_data.get('customer', None) - - if customer: - item = item.allocateToCustomer( - customer, - user=self.request.user - ) - - item.clearAllocations() - - class StockItemReturnToStock(AjaxUpdateView): """ View for returning a stock item (which is assigned to a customer) to stock. diff --git a/InvenTree/templates/js/translated/stock.js b/InvenTree/templates/js/translated/stock.js index d6de4fdd45..e9e97e3b87 100644 --- a/InvenTree/templates/js/translated/stock.js +++ b/InvenTree/templates/js/translated/stock.js @@ -38,6 +38,7 @@ */ /* exported + assignStockToCustomer, createNewStockItem, createStockLocation, duplicateStockItem, @@ -533,13 +534,166 @@ function exportStock(params={}) { url += `&${key}=${params[key]}`; } - console.log(url); location.href = url; } }); } +/** + * Assign multiple stock items to a customer + */ +function assignStockToCustomer(items, options={}) { + + // Generate HTML content for the form + var html = ` + + + + + + + + + + + `; + + for (var idx = 0; idx < items.length; idx++) { + + var item = items[idx]; + + var pk = item.pk; + + var part = item.part_detail; + + var thumbnail = thumbnailImage(part.thumbnail || part.image); + + var status = stockStatusDisplay(item.status, {classes: 'float-right'}); + + var quantity = ''; + + if (item.serial && item.quantity == 1) { + quantity = `{% trans "Serial" %}: ${item.serial}`; + } else { + quantity = `{% trans "Quantity" %}: ${item.quantity}`; + } + + quantity += status; + + var location = locationDetail(item, false); + + var buttons = `
`; + + buttons += makeIconButton( + 'fa-times icon-red', + 'button-stock-item-remove', + pk, + '{% trans "Remove row" %}', + ); + + buttons += '
'; + + html += ` + + + + + + + `; + } + + html += `
{% trans "Part" %}{% trans "Stock Item" %}{% trans "Location" %}
${thumbnail} ${part.full_name} +
+ ${quantity} +
+
+
${location}${buttons}
`; + + constructForm('{% url "api-stock-assign" %}', { + method: 'POST', + preFormContent: html, + fields: { + 'customer': { + value: options.customer, + filters: { + is_customer: true, + }, + }, + 'notes': {}, + }, + confirm: true, + confirmMessage: '{% trans "Confirm stock assignment" %}', + title: '{% trans "Assign Stock to Customer" %}', + afterRender: function(fields, opts) { + // Add button callbacks to remove rows + $(opts.modal).find('.button-stock-item-remove').click(function() { + var pk = $(this).attr('pk'); + + $(opts.modal).find(`#stock_item_${pk}`).remove(); + }); + }, + onSubmit: function(fields, opts) { + + // Extract data elements from the form + var data = { + customer: getFormFieldValue('customer', {}, opts), + notes: getFormFieldValue('notes', {}, opts), + items: [], + }; + + var item_pk_values = []; + + items.forEach(function(item) { + var pk = item.pk; + + // Does the row exist in the form? + var row = $(opts.modal).find(`#stock_item_${pk}`); + + if (row) { + item_pk_values.push(pk); + + data.items.push({ + item: pk, + }); + } + }); + + opts.nested = { + 'items': item_pk_values, + } + + inventreePut( + '{% url "api-stock-assign" %}', + data, + { + method: 'POST', + success: function(response) { + $(opts.modal).modal('hide'); + + if (options.success) { + options.success(response); + } + }, + error: function(xhr) { + switch (xhr.status) { + case 400: + handleFormErrors(xhr.responseJSON, fields, opts); + break; + default: + $(opts.modal).modal('hide'); + showApiError(xhr, opts.url); + break; + } + } + } + ); + } + }); +} + + /** * Perform stock adjustments */ @@ -1098,7 +1252,7 @@ function locationDetail(row, showLink=true) { // StockItem has been assigned to a sales order text = '{% trans "Assigned to Sales Order" %}'; url = `/order/sales-order/${row.sales_order}/`; - } else if (row.location) { + } else if (row.location && row.location_detail) { text = row.location_detail.pathstring; url = `/stock/location/${row.location}/`; } else {