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 = `
+
+
+
+ {% trans "Part" %} |
+ {% trans "Stock" %} |
+ {% trans "Location" %} |
+ ${actionTitle || ''} |
+
+
+
+ `;
+
+ 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 += `
+
+ ${item.part_detail.full_name} |
+ ${quantity}${status} |
+ ${location} |
+
+
+ ${actionInput}
+ ${buttons}
+
+ |
+
`;
+
+ });
+
+ html += `
`;
+
+ 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 = `
-
-
-
- {% trans "Part" %} |
- {% trans "Stock" %} |
- {% trans "Location" %} |
- |
-
-
-
- `;
-
- 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 += `
-
- ${item.part_detail.full_name}${status} |
- ${quantity} |
- ${item.location_detail.pathstring} |
- ${buttons} |
-
`;
-
- });
-
- html += `
`;
-
- $(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