diff --git a/InvenTree/stock/api.py b/InvenTree/stock/api.py index 4a6e7111e8..64be7c885f 100644 --- a/InvenTree/stock/api.py +++ b/InvenTree/stock/api.py @@ -143,7 +143,7 @@ class StockAdjust(APIView): elif 'items' in request.data: _items = request.data['items'] else: - raise ValidationError({'items': 'Request must contain list of stock items'}) + raise ValidationError({'items': _('Request must contain list of stock items')}) # List of validated items self.items = [] @@ -151,13 +151,14 @@ class StockAdjust(APIView): for entry in _items: if not type(entry) == dict: - raise ValidationError({'error': 'Improperly formatted data'}) + raise ValidationError({'error': _('Improperly formatted data')}) try: - pk = entry.get('pk', None) + # Look for 'pk' value first, with 'id' as a backup + pk = entry.get('pk', entry.get('id', None)) item = StockItem.objects.get(pk=pk) except (ValueError, StockItem.DoesNotExist): - raise ValidationError({'pk': 'Each entry must contain a valid pk field'}) + raise ValidationError({'pk': _('Each entry must contain a valid pk field')}) if self.allow_missing_quantity and 'quantity' not in entry: entry['quantity'] = item.quantity @@ -165,10 +166,10 @@ class StockAdjust(APIView): try: quantity = Decimal(str(entry.get('quantity', None))) except (ValueError, TypeError, InvalidOperation): - raise ValidationError({'quantity': "Each entry must contain a valid quantity value"}) + raise ValidationError({'quantity': _("Each entry must contain a valid quantity value")}) if quantity < 0: - raise ValidationError({'quantity': 'Quantity field must not be less than zero'}) + raise ValidationError({'quantity': _('Quantity field must not be less than zero')}) self.items.append({ 'item': item, diff --git a/InvenTree/templates/js/forms.js b/InvenTree/templates/js/forms.js index b7af665393..ffe3868746 100644 --- a/InvenTree/templates/js/forms.js +++ b/InvenTree/templates/js/forms.js @@ -441,7 +441,17 @@ function constructFormBody(fields, options) { modalEnable(modal, true); // Insert generated form content - $(modal).find('.modal-form-content').html(html); + $(modal).find('#form-content').html(html); + + if (options.preFormContent) { + console.log('pre form content', options.preFormContent); + $(modal).find('#pre-form-content').html(options.preFormContent); + } + + if (options.postFormContent) { + console.log('post form content', options.postFormContent); + $(modal).find('#post-form-content').html(options.postFormContent); + } // Clear any existing buttons from the modal $(modal).find('#modal-footer-buttons').html(''); diff --git a/InvenTree/templates/js/stock.js b/InvenTree/templates/js/stock.js index 2413d36089..42cbc74a68 100644 --- a/InvenTree/templates/js/stock.js +++ b/InvenTree/templates/js/stock.js @@ -20,6 +20,138 @@ function stockStatusCodes() { } +/** + * Perform stock adjustments + */ +function adjustStock(items, options={}) { + + var formTitle = 'Form Title Here'; + var actionTitle = null; + + switch (options.action) { + case 'move': + formTitle = '{% trans "Transfer Stock" %}'; + actionTitle = '{% trans "Move" %}'; + break; + case 'count': + formTitle = '{% trans "Count Stock" %}'; + actionTitle = '{% trans "Count" %}'; + break; + case 'take': + formTitle = '{% trans "Remove Stock" %}'; + actionTitle = '{% trans "Take" %}'; + break; + case 'add': + formTitle = '{% trans "Add Stock" %}'; + actionTitle = '{% trans "Add" %}'; + break; + case 'delete': + formTitle = '{% trans "Delete Stock" %}'; + break; + default: + break; + } + + // Generate modal HTML content + var html = ` + + + + + + + + + + + `; + + items.forEach(function(item) { + + var pk = item.pk; + + var image = item.part_detail.thumbnail || item.part_detail.image || blankImage(); + + var status = stockStatusDisplay(item.status, { + classes: 'float-right' + }); + + var quantity = item.quantity; + + var location = locationDetail(item, false); + + if (item.location_detail) { + location = item.location_detail.pathstring; + } + + if (item.serial != null) { + quantity = `#${item.serial}`; + } + + var actionInput = ''; + + if (actionTitle != null) { + actionInput = constructNumberInput( + item.pk, + { + value: item.quantity, + min_value: 0, + } + ) + }; + + var buttons = `
`; + + buttons += makeIconButton( + 'fa-trash-alt icon-red', + 'button-stock-item-remove', + pk, + '{% trans "Remove stock item" %}', + ); + + buttons += `
`; + + html += ` + + + + + + `; + + }); + + html += `
{% trans "Part" %}{% trans "Stock" %}{% trans "Location" %}${actionTitle || ''}
${item.part_detail.full_name}${quantity}${status}${location} +
+ ${actionInput} + ${buttons} +
+
`; + + var modal = createNewModal({ + title: formTitle, + }); + + constructFormBody({}, { + preFormContent: html, + confirm: true, + modal: modal, + }); + + // Attach callbacks for the action buttons + $(modal).find('.button-stock-item-remove').click(function() { + var pk = $(this).attr('pk'); + + $(modal).find(`#stock_item_${pk}`).remove(); + }); + + attachToggle(modal); + + $(modal + ' .select2-container').addClass('select-full-width'); + $(modal + ' .select2-container').css('width', '100%'); +} + + function removeStockRow(e) { // Remove a selected row from a stock modal form @@ -228,6 +360,58 @@ function loadStockTestResultsTable(table, options) { } + +function locationDetail(row, showLink=true) { + /* + * Function to display a "location" of a StockItem. + * + * Complicating factors: A StockItem may not actually *be* in a location! + * - Could be at a customer + * - Could be installed in another stock item + * - Could be assigned to a sales order + * - Could be currently in production! + * + * So, instead of being naive, we'll check! + */ + + // Display text + var text = ''; + + // URL (optional) + var url = ''; + + if (row.is_building && row.build) { + // StockItem is currently being built! + text = '{% trans "In production" %}'; + url = `/build/${row.build}/`; + } else if (row.belongs_to) { + // StockItem is installed inside a different StockItem + text = `{% trans "Installed in Stock Item" %} ${row.belongs_to}`; + url = `/stock/item/${row.belongs_to}/installed/`; + } else if (row.customer) { + // StockItem has been assigned to a customer + text = '{% trans "Shipped to customer" %}'; + url = `/company/${row.customer}/assigned-stock/`; + } else if (row.sales_order) { + // 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) { + text = row.location_detail.pathstring; + url = `/stock/location/${row.location}/`; + } else { + text = '{% trans "No stock location set" %}'; + url = ''; + } + + if (showLink && url) { + return renderLink(text, url); + } else { + return text; + } +} + + function loadStockTable(table, options) { /* Load data into a stock table with adjustable options. * Fetches data (via AJAX) and loads into a bootstrap table. @@ -271,56 +455,6 @@ function loadStockTable(table, options) { filters[key] = params[key]; } - function locationDetail(row) { - /* - * Function to display a "location" of a StockItem. - * - * Complicating factors: A StockItem may not actually *be* in a location! - * - Could be at a customer - * - Could be installed in another stock item - * - Could be assigned to a sales order - * - Could be currently in production! - * - * So, instead of being naive, we'll check! - */ - - // Display text - var text = ''; - - // URL (optional) - var url = ''; - - if (row.is_building && row.build) { - // StockItem is currently being built! - text = '{% trans "In production" %}'; - url = `/build/${row.build}/`; - } else if (row.belongs_to) { - // StockItem is installed inside a different StockItem - text = `{% trans "Installed in Stock Item" %} ${row.belongs_to}`; - url = `/stock/item/${row.belongs_to}/installed/`; - } else if (row.customer) { - // StockItem has been assigned to a customer - text = '{% trans "Shipped to customer" %}'; - url = `/company/${row.customer}/assigned-stock/`; - } else if (row.sales_order) { - // 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) { - text = row.location_detail.pathstring; - url = `/stock/location/${row.location}/`; - } else { - text = '{% trans "No stock location set" %}'; - url = ''; - } - - if (url) { - return renderLink(text, url); - } else { - return text; - } - } - var grouping = true; if ('grouping' in options) { @@ -741,114 +875,14 @@ function loadStockTable(table, options) { ] ); + function stockAdjustment(action) { var items = $("#stock-table").bootstrapTable("getSelections"); - var stock = []; - - items.forEach(function(item) { - stock.push(item.pk); + adjustStock(items, { + action: action, }); - var title = 'Form title'; - - switch (action) { - case 'move': - title = '{% trans "Transfer Stock" %}'; - break; - case 'count': - title = '{% trans "Count Stock" %}'; - break; - case 'take': - title = '{% trans "Remove Stock" %}'; - break; - case 'add': - title = '{% trans "Add Stock" %}'; - break; - case 'delete': - title = '{% trans "Delete Stock" %}'; - break; - default: - break; - } - - var modal = createNewModal({ - title: title, - }); - - // Generate content for the modal - - var html = ` - - - - - - - - - - - `; - - items.forEach(function(item) { - - var pk = item.pk; - - var image = item.part_detail.thumbnail || item.part_detail.image || blankImage(); - - var status = stockStatusDisplay(item.status, { - classes: 'float-right' - }); - - var quantity = item.quantity; - - if (item.serial != null) { - quantity = `#${item.serial}`; - } - - var buttons = `
`; - - buttons += makeIconButton( - 'fa-trash-alt icon-red', - 'button-stock-item-remove', - pk, - '{% trans "Remove stock item" %}', - ); - - buttons += `
`; - - html += ` - - - - - - `; - - }); - - html += `
{% trans "Part" %}{% trans "Stock" %}{% trans "Location" %}
${item.part_detail.full_name}${status}${quantity}${item.location_detail.pathstring}${buttons}
`; - - $(modal).find('.modal-form-content').html(html); - - // Attach callbacks for the action buttons - $(modal).find('.button-stock-item-remove').click(function() { - var pk = $(this).attr('pk'); - - $(modal).find(`#stock_item_${pk}`).remove(); - }); - - // Add a "confirm" button - insertConfirmButton({ - modal: modal, - }); - - attachToggle(modal); - - $(modal + ' .select2-container').addClass('select-full-width'); - $(modal + ' .select2-container').css('width', '100%'); - return; // Buttons for launching secondary modals