diff --git a/InvenTree/InvenTree/static/script/inventree/tables.js b/InvenTree/InvenTree/static/script/inventree/tables.js
index be0e1e6325..cc4320307b 100644
--- a/InvenTree/InvenTree/static/script/inventree/tables.js
+++ b/InvenTree/InvenTree/static/script/inventree/tables.js
@@ -157,6 +157,11 @@ $.fn.inventreeTable = function(options) {
console.log('Could not get list of visible columns!');
}
}
+
+ // Optionally, link buttons to the table selection
+ if (options.buttons) {
+ linkButtonsToSelection(table, options.buttons);
+ }
}
function customGroupSorter(sortName, sortOrder, sortData) {
diff --git a/InvenTree/stock/forms.py b/InvenTree/stock/forms.py
index e0b1623a54..37d8d91c8a 100644
--- a/InvenTree/stock/forms.py
+++ b/InvenTree/stock/forms.py
@@ -271,6 +271,24 @@ class ExportOptionsForm(HelperForm):
self.fields['file_format'].choices = self.get_format_choices()
+class UninstallStockForm(forms.ModelForm):
+ """
+ Form for uninstalling a stock item which is installed in another item.
+ """
+
+ location = TreeNodeChoiceField(queryset=StockLocation.objects.all(), label=_('Location'), help_text=_('Destination location for uninstalled items'))
+
+ confirm = forms.BooleanField(required=False, initial=False, label=_('Confirm uninstall'), help_text=_('Confirm removal of installed stock items'))
+
+ class Meta:
+
+ model = StockItem
+
+ fields = [
+ 'location',
+ 'confirm',
+ ]
+
class AdjustStockForm(forms.ModelForm):
""" Form for performing simple stock adjustments.
@@ -282,15 +300,15 @@ class AdjustStockForm(forms.ModelForm):
This form is used for managing stock adjuments for single or multiple stock items.
"""
- destination = TreeNodeChoiceField(queryset=StockLocation.objects.all(), label='Destination', required=True, help_text=_('Destination stock location'))
+ destination = TreeNodeChoiceField(queryset=StockLocation.objects.all(), label=_('Destination'), required=True, help_text=_('Destination stock location'))
- note = forms.CharField(label='Notes', required=True, help_text='Add note (required)')
+ note = forms.CharField(label=_('Notes'), required=True, help_text=_('Add note (required)'))
# transaction = forms.BooleanField(required=False, initial=False, label='Create Transaction', help_text='Create a stock transaction for these parts')
- confirm = forms.BooleanField(required=False, initial=False, label='Confirm stock adjustment', help_text=_('Confirm movement of stock items'))
+ confirm = forms.BooleanField(required=False, initial=False, label=_('Confirm stock adjustment'), help_text=_('Confirm movement of stock items'))
- set_loc = forms.BooleanField(required=False, initial=False, label='Set Default Location', help_text=_('Set the destination as the default location for selected parts'))
+ set_loc = forms.BooleanField(required=False, initial=False, label=_('Set Default Location'), help_text=_('Set the destination as the default location for selected parts'))
class Meta:
model = StockItem
diff --git a/InvenTree/stock/templates/stock/item_installed.html b/InvenTree/stock/templates/stock/item_installed.html
index 72451f11e9..d6a4675bf4 100644
--- a/InvenTree/stock/templates/stock/item_installed.html
+++ b/InvenTree/stock/templates/stock/item_installed.html
@@ -10,7 +10,18 @@
{% trans "Installed Items" %}
-
+
+
+
{% endblock %}
@@ -37,7 +48,6 @@ $('#installed-table').inventreeTable({
title: '{% trans 'Select' %}',
searchable: false,
switchable: false,
- visible: false,
},
{
field: 'pk',
@@ -133,8 +143,20 @@ $('#installed-table').inventreeTable({
// Find buttons and associate actions
table.find('.button-uninstall').click(function() {
var pk = $(this).attr('pk');
+
+ launchModalForm(
+ "{% url 'stock-item-uninstall' %}",
+ {
+ data: {
+ 'items[]': [pk],
+ }
+ }
+ );
});
- }
+ },
+ buttons: [
+ '#stock-options',
+ ]
});
{% endblock %}
\ No newline at end of file
diff --git a/InvenTree/stock/templates/stock/stock_uninstall.html b/InvenTree/stock/templates/stock/stock_uninstall.html
new file mode 100644
index 0000000000..2a8d9c7ee4
--- /dev/null
+++ b/InvenTree/stock/templates/stock/stock_uninstall.html
@@ -0,0 +1,28 @@
+{% extends "modal_form.html" %}
+{% load i18n %}
+{% load inventree_extras %}
+
+{% block pre_form_content %}
+
+
+ {% trans "The following stock items will be uninstalled" %}
+
+
+
+ {% for item in stock_items %}
+ -
+ {% include "hover_image.html" with image=item.part.image hover=False %}
+ {{ item }}
+
+ {% endfor %}
+
+
+{% endblock %}
+
+{% block form_data %}
+
+{% for item in stock_items %}
+
+{% endfor %}
+
+{% endblock %}
\ No newline at end of file
diff --git a/InvenTree/stock/urls.py b/InvenTree/stock/urls.py
index c6b0b2b54c..4c86995cda 100644
--- a/InvenTree/stock/urls.py
+++ b/InvenTree/stock/urls.py
@@ -60,6 +60,8 @@ stock_urls = [
url(r'^item/new/?', views.StockItemCreate.as_view(), name='stock-item-create'),
+ url(r'^item/uninstall/', views.StockItemUninstall.as_view(), name='stock-item-uninstall'),
+
url(r'^item/test-report-download/', views.StockItemTestReportDownload.as_view(), name='stock-item-test-report-download'),
url(r'^item/print-stock-labels/', views.StockItemPrintLabels.as_view(), name='stock-item-print-labels'),
diff --git a/InvenTree/stock/views.py b/InvenTree/stock/views.py
index aadac17c7a..6aedfe1b4c 100644
--- a/InvenTree/stock/views.py
+++ b/InvenTree/stock/views.py
@@ -683,6 +683,89 @@ class StockItemQRCode(QRCodeView):
return None
+class StockItemUninstall(AjaxView, FormMixin):
+ """
+ View for uninstalling one or more StockItems,
+ which are installed in another stock item.
+
+ Stock items are uninstalled into a location,
+ defaulting to the location that they were "in" before they were installed.
+
+ If multiple default locations are detected,
+ leave the final location up to the user.
+ """
+
+ ajax_template_name = 'stock/stock_uninstall.html'
+ ajax_form_title = _('Uninstall Stock Items')
+ form_class = StockForms.UninstallStockForm
+
+ # List of stock items to uninstall (initially empty)
+ stock_items = []
+
+ def get_stock_items(self):
+
+ return self.stock_items
+
+ def get(self, request, *args, **kwargs):
+
+ """ Extract list of stock items, which are supplied as a list,
+ e.g. items[]=1,2,3
+ """
+
+ if 'items[]' in request.GET:
+ self.stock_items = StockItem.objects.filter(id__in=request.GET.getlist('items[]'))
+ else:
+ self.stock_items = []
+
+ print("GET:", request.GET)
+
+ return self.renderJsonResponse(request, self.get_form())
+
+ def post(self, request, *args, **kwargs):
+
+ """
+ Extract a list of stock items which are included as hidden inputs in the form data.
+ """
+
+ items = []
+
+ for item in self.request.POST:
+ if item.startswith('stock-item-'):
+ pk = item.replace('stock-item', '')
+
+ try:
+ stock_item = StockItem.objects.get(pk=pk)
+ items.append(stock_item)
+ except (ValueError, StockItem.DoesNotExist):
+ pass
+
+ self.stock_items = items
+
+ confirmed = str2bool(request.POST.get('confirm'))
+
+ valid = False
+
+ form = self.get_form()
+
+ if not confirmed:
+ valid = False
+ form.errors['confirm'] = [_('Confirm stock adjustment')]
+
+ data = {
+ 'form_valid': valid,
+ }
+
+ return self.renderJsonResponse(request, form=form, data=data)
+
+ def get_context_data(self):
+
+ context = super().get_context_data()
+
+ context['stock_items'] = self.get_stock_items()
+
+ return context
+
+
class StockAdjust(AjaxView, FormMixin):
""" View for enacting simple stock adjustments: