diff --git a/InvenTree/part/models.py b/InvenTree/part/models.py index 4acd4a594e..1a8048a985 100644 --- a/InvenTree/part/models.py +++ b/InvenTree/part/models.py @@ -1109,6 +1109,17 @@ class Part(MPTTModel): return self.parameters.order_by('template__name') + @property + def has_variants(self): + """ Check if this Part object has variants underneath it. """ + + return self.get_all_variants().count() > 0 + + def get_all_variants(self): + """ Return all Part object which exist as a variant under this part. """ + + return self.get_descendants(include_self=False) + def attach_file(instance, filename): """ Function for storing a file for a PartAttachment diff --git a/InvenTree/stock/forms.py b/InvenTree/stock/forms.py index edf3d28df8..ddd7390246 100644 --- a/InvenTree/stock/forms.py +++ b/InvenTree/stock/forms.py @@ -63,6 +63,18 @@ class EditStockLocationForm(HelperForm): ] +class ConvertStockItemForm(HelperForm): + """ + Form for converting a StockItem to a variant of its current part. + """ + + class Meta: + model = StockItem + fields = [ + 'part' + ] + + class CreateStockItemForm(HelperForm): """ Form for creating a new StockItem """ diff --git a/InvenTree/stock/migrations/0043_auto_20200525_0420.py b/InvenTree/stock/migrations/0043_auto_20200525_0420.py new file mode 100644 index 0000000000..4acc19edf2 --- /dev/null +++ b/InvenTree/stock/migrations/0043_auto_20200525_0420.py @@ -0,0 +1,20 @@ +# Generated by Django 3.0.5 on 2020-05-25 04:20 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('part', '0042_auto_20200518_0900'), + ('stock', '0042_auto_20200523_0121'), + ] + + operations = [ + migrations.AlterField( + model_name='stockitem', + name='part', + field=models.ForeignKey(help_text='Base part', limit_choices_to={'active': True, 'virtual': False}, on_delete=django.db.models.deletion.CASCADE, related_name='stock_items', to='part.Part', verbose_name='Base Part'), + ), + ] diff --git a/InvenTree/stock/models.py b/InvenTree/stock/models.py index 90a6fd365c..3d9394979c 100644 --- a/InvenTree/stock/models.py +++ b/InvenTree/stock/models.py @@ -331,7 +331,6 @@ class StockItem(MPTTModel): verbose_name=_('Base Part'), related_name='stock_items', help_text=_('Base part'), limit_choices_to={ - 'is_template': False, 'active': True, 'virtual': False }) diff --git a/InvenTree/stock/templates/stock/item_base.html b/InvenTree/stock/templates/stock/item_base.html index 819c138988..bc187588d0 100644 --- a/InvenTree/stock/templates/stock/item_base.html +++ b/InvenTree/stock/templates/stock/item_base.html @@ -93,6 +93,11 @@ InvenTree | {% trans "Stock Item" %} - {{ item }} {% endif %} + {% if item.part.has_variants %} + + + + {% endif %} {% if item.part.has_test_report_templates %} @@ -324,6 +329,16 @@ function itemAdjust(action) { ); } +{% if item.part.has_variants %} +$("#stock-convert").click(function() { + launchModalForm("{% url 'stock-item-convert' item.id %}", + { + reload: true, + } + ); +}); +{% endif %} + $("#stock-move").click(function() { itemAdjust("move"); }); diff --git a/InvenTree/stock/templates/stock/stockitem_convert.html b/InvenTree/stock/templates/stock/stockitem_convert.html new file mode 100644 index 0000000000..55b565a60b --- /dev/null +++ b/InvenTree/stock/templates/stock/stockitem_convert.html @@ -0,0 +1,17 @@ +{% extends "modal_form.html" %} +{% load i18n %} + +{% block pre_form_content %} + + + {% trans "Convert Stock Item" %} + {% trans "This stock item is current an instance of " %}{{ item.part }} + {% trans "It can be converted to one of the part variants listed below." %} + + + + {% trans "Warning" %} + {% trans "This action cannot be easily undone" %} + + +{% endblock %} \ No newline at end of file diff --git a/InvenTree/stock/urls.py b/InvenTree/stock/urls.py index e28d15b97d..7742f96fe2 100644 --- a/InvenTree/stock/urls.py +++ b/InvenTree/stock/urls.py @@ -18,6 +18,7 @@ stock_location_detail_urls = [ stock_item_detail_urls = [ url(r'^edit/', views.StockItemEdit.as_view(), name='stock-item-edit'), + url(r'^convert/', views.StockItemConvert.as_view(), name='stock-item-convert'), url(r'^serialize/', views.StockItemSerialize.as_view(), name='stock-item-serialize'), url(r'^delete/', views.StockItemDelete.as_view(), name='stock-item-delete'), url(r'^qr_code/', views.StockItemQRCode.as_view(), name='stock-item-qr'), diff --git a/InvenTree/stock/views.py b/InvenTree/stock/views.py index 02a7ee9719..ae6f593717 100644 --- a/InvenTree/stock/views.py +++ b/InvenTree/stock/views.py @@ -887,6 +887,30 @@ class StockItemEdit(AjaxUpdateView): return form +class StockItemConvert(AjaxUpdateView): + """ + View for 'converting' a StockItem to a variant of its current part. + """ + + model = StockItem + form_class = StockForms.ConvertStockItemForm + ajax_form_title = _('Convert Stock Item') + ajax_template_name = 'stock/stockitem_convert.html' + context_object_name = 'item' + + def get_form(self): + """ + Filter the available parts. + """ + + form = super().get_form() + item = self.get_object() + + form.fields['part'].queryset = item.part.get_all_variants() + + return form + + class StockLocationCreate(AjaxCreateView): """ View for creating a new StockLocation