diff --git a/InvenTree/stock/api.py b/InvenTree/stock/api.py index 0c0dafbf41..78b56a43df 100644 --- a/InvenTree/stock/api.py +++ b/InvenTree/stock/api.py @@ -92,13 +92,8 @@ class StockDetail(generics.RetrieveUpdateDestroyAPIView): return self.serializer_class(*args, **kwargs) -class StockItemSerialize(generics.CreateAPIView): - """ - API endpoint for serializing a stock item - """ - - queryset = StockItem.objects.none() - serializer_class = StockSerializers.SerializeStockItemSerializer +class StockItemContextMixin: + """ Mixin class for adding StockItem object to serializer context """ def get_serializer_context(self): @@ -112,8 +107,16 @@ class StockItemSerialize(generics.CreateAPIView): return context +class StockItemSerialize(StockItemContextMixin, generics.CreateAPIView): + """ + API endpoint for serializing a stock item + """ -class StockItemInstall(generics.CreateAPIView): + queryset = StockItem.objects.none() + serializer_class = StockSerializers.SerializeStockItemSerializer + + +class StockItemInstall(StockItemContextMixin, generics.CreateAPIView): """ API endpoint for installing a particular stock item into this stock item. @@ -125,17 +128,14 @@ class StockItemInstall(generics.CreateAPIView): queryset = StockItem.objects.none() serializer_class = StockSerializers.InstallStockItemSerializer - def get_serializer_context(self): - context = super().get_serializer_context() - context['request'] = self.request +class StockItemUninstall(StockItemContextMixin, generics.CreateAPIView): + """ + API endpoint for removing (uninstalling) items from this item + """ - try: - context['item'] = StockItem.objects.get(pk=self.kwargs.get('pk', None)) - except: - pass - - return context + queryset = StockItem.objects.none() + serializer_class = StockSerializers.UninstallStockItemSerializer class StockAdjustView(generics.CreateAPIView): @@ -1421,6 +1421,7 @@ stock_api_urls = [ re_path(r'^(?P\d+)/', include([ re_path(r'^serialize/', StockItemSerialize.as_view(), name='api-stock-item-serialize'), re_path(r'^install/', StockItemInstall.as_view(), name='api-stock-item-install'), + re_path(r'^uninstall/', StockItemUninstall.as_view(), name='api-stock-item-uninstall'), re_path(r'^.*$', StockDetail.as_view(), name='api-stock-detail'), ])), diff --git a/InvenTree/stock/models.py b/InvenTree/stock/models.py index 39697c1bca..040b748521 100644 --- a/InvenTree/stock/models.py +++ b/InvenTree/stock/models.py @@ -1142,7 +1142,7 @@ class StockItem(MPTTModel): ) @transaction.atomic - def uninstallIntoLocation(self, location, user, notes): + def uninstall_into_location(self, location, user, notes): """ Uninstall this stock item from another item, into a location. diff --git a/InvenTree/stock/serializers.py b/InvenTree/stock/serializers.py index a263700a41..6b27b87f93 100644 --- a/InvenTree/stock/serializers.py +++ b/InvenTree/stock/serializers.py @@ -448,6 +448,48 @@ class InstallStockItemSerializer(serializers.Serializer): ) +class UninstallStockItemSerializer(serializers.Serializer): + """ + API serializers for uninstalling an installed item from a stock item + """ + + class Meta: + fields = [ + 'location', + 'note', + ] + + location = serializers.PrimaryKeyRelatedField( + queryset=StockLocation.objects.all(), + many=False, required=True, allow_null=False, + label=_('Location'), + help_text=_('Destination location for uninstalled item') + ) + + note = serializers.CharField( + label=_('Notes'), + help_text=_('Add transaction note (optional)'), + required=False, allow_blank=True, + ) + + def save(self): + + item = self.context['item'] + + data = self.validated_data + request = self.context['request'] + + location = data['location'] + + note = data.get('note', '') + + item.uninstall_into_location( + location, + request.user, + note + ) + + class LocationTreeSerializer(InvenTree.serializers.InvenTreeModelSerializer): """ Serializer for a simple tree view diff --git a/InvenTree/stock/templates/stock/attachment_delete.html b/InvenTree/stock/templates/stock/attachment_delete.html deleted file mode 100644 index 4ee7f03cb1..0000000000 --- a/InvenTree/stock/templates/stock/attachment_delete.html +++ /dev/null @@ -1,7 +0,0 @@ -{% extends "modal_delete_form.html" %} -{% load i18n %} - -{% block pre_form_content %} -{% trans "Are you sure you want to delete this attachment?" %} -
-{% endblock %} \ No newline at end of file diff --git a/InvenTree/stock/templates/stock/item.html b/InvenTree/stock/templates/stock/item.html index 75e53d6758..66069c7a2e 100644 --- a/InvenTree/stock/templates/stock/item.html +++ b/InvenTree/stock/templates/stock/item.html @@ -159,9 +159,12 @@
-
+
+
+ {% include "filter_list.html" with id='installed-items' %} +
-
+
@@ -207,28 +210,6 @@ quantity: {{ item.quantity|unlocalize }}, } ); - - $('#multi-item-uninstall').click(function() { - - var selections = $('#installed-table').bootstrapTable('getSelections'); - - var items = []; - - selections.forEach(function(item) { - items.push(item.pk); - }); - - launchModalForm( - "{% url 'stock-item-uninstall' %}", - { - data: { - 'items[]': items, - }, - reload: true, - } - ); - - }); onPanelLoad('notes', function() { setupNotesField( diff --git a/InvenTree/stock/templates/stock/item_base.html b/InvenTree/stock/templates/stock/item_base.html index dd77d26d1c..ea4480cc52 100644 --- a/InvenTree/stock/templates/stock/item_base.html +++ b/InvenTree/stock/templates/stock/item_base.html @@ -445,12 +445,9 @@ $('#stock-install-in').click(function() { $('#stock-uninstall').click(function() { - launchModalForm( - "{% url 'stock-item-uninstall' %}", + uninstallStockItem( + {{ item.pk }}, { - data: { - 'items[]': [{{ item.pk }}], - }, reload: true, } ); diff --git a/InvenTree/stock/templates/stock/stock_uninstall.html b/InvenTree/stock/templates/stock/stock_uninstall.html deleted file mode 100644 index 2a8d9c7ee4..0000000000 --- a/InvenTree/stock/templates/stock/stock_uninstall.html +++ /dev/null @@ -1,28 +0,0 @@ -{% extends "modal_form.html" %} -{% load i18n %} -{% load inventree_extras %} - -{% block pre_form_content %} - -
- {% trans "The following stock items will be uninstalled" %} -
- - - -{% 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 11ad145d08..3a9ad6c490 100644 --- a/InvenTree/stock/urls.py +++ b/InvenTree/stock/urls.py @@ -43,8 +43,6 @@ stock_urls = [ # Stock location re_path(r'^location/', include(location_urls)), - re_path(r'^item/uninstall/', views.StockItemUninstall.as_view(), name='stock-item-uninstall'), - re_path(r'^track/', include(stock_tracking_urls)), # Individual stock items diff --git a/InvenTree/stock/views.py b/InvenTree/stock/views.py index 1b9aba04c8..69f0426bba 100644 --- a/InvenTree/stock/views.py +++ b/InvenTree/stock/views.py @@ -5,38 +5,23 @@ Django views for interacting with Stock app # -*- coding: utf-8 -*- from __future__ import unicode_literals -from django.core.exceptions import ValidationError -from django.views.generic.edit import FormMixin + from django.views.generic import DetailView, ListView -from django.forms.models import model_to_dict -from django.forms import HiddenInput from django.urls import reverse from django.http import HttpResponseRedirect -from django.contrib.auth import get_user_model -from django.contrib.auth.models import Group from django.utils.translation import gettext_lazy as _ -from moneyed import CURRENCIES - from InvenTree.views import AjaxUpdateView, AjaxDeleteView, AjaxCreateView from InvenTree.views import QRCodeView from InvenTree.views import InvenTreeRoleMixin from InvenTree.forms import ConfirmForm from InvenTree.helpers import str2bool -from InvenTree.helpers import extract_serial_numbers -from decimal import Decimal, InvalidOperation -from datetime import datetime, timedelta - -from company.models import SupplierPart -from part.models import Part from .models import StockItem, StockLocation, StockItemTracking import common.settings -from common.models import InvenTreeSetting -from users.models import Owner from . import forms as StockForms diff --git a/InvenTree/templates/js/translated/model_renderers.js b/InvenTree/templates/js/translated/model_renderers.js index d55d93e531..1d88fb90d7 100644 --- a/InvenTree/templates/js/translated/model_renderers.js +++ b/InvenTree/templates/js/translated/model_renderers.js @@ -81,7 +81,7 @@ function renderStockItem(name, data, parameters={}, options={}) { var part_detail = ''; - if (render_part_detail) { + if (render_part_detail && data.part_detail) { part_detail = `${data.part_detail.full_name} - `; } diff --git a/InvenTree/templates/js/translated/stock.js b/InvenTree/templates/js/translated/stock.js index 94d21fe5b0..94f4d73ae1 100644 --- a/InvenTree/templates/js/translated/stock.js +++ b/InvenTree/templates/js/translated/stock.js @@ -57,6 +57,7 @@ stockItemFields, stockLocationFields, stockStatusCodes, + uninstallStockItem, */ @@ -2630,13 +2631,10 @@ function loadInstalledInTable(table, options) { table.find('.button-uninstall').click(function() { var pk = $(this).attr('pk'); - launchModalForm( - '{% url "stock-item-uninstall" %}', + uninstallStockItem( + pk, { - data: { - 'items[]': pk, - }, - success: function() { + onSuccess: function(response) { table.bootstrapTable('refresh'); } } @@ -2647,6 +2645,43 @@ function loadInstalledInTable(table, options) { } +/* + * Launch a dialog to uninstall a stock item from another stock item +*/ +function uninstallStockItem(installed_item_id, options={}) { + + constructForm( + `/api/stock/${installed_item_id}/uninstall/`, + { + confirm: true, + method: 'POST', + title: '{% trans "Uninstall Stock Item" %}', + fields: { + location: { + icon: 'fa-sitemap', + }, + note: {}, + }, + preFormContent: function(opts) { + var html = ''; + + if (installed_item_id == null) { + html += ` +
+ {% trans "Select stock item to uninstall" %} +
`; + } + + return html; + }, + onSuccess: function(response) { + handleFormSuccess(response, options); + } + } + ); +} + + /* * Launch a dialog to install a stock item into another stock item */