diff --git a/InvenTree/InvenTree/static/script/inventree/inventree.js b/InvenTree/InvenTree/static/script/inventree/inventree.js index 7532b51f58..87fb53b294 100644 --- a/InvenTree/InvenTree/static/script/inventree/inventree.js +++ b/InvenTree/InvenTree/static/script/inventree/inventree.js @@ -78,7 +78,7 @@ function getImageUrlFromTransfer(transfer) { return url; } -function makeIconButton(icon, cls, pk, title) { +function makeIconButton(icon, cls, pk, title, options={}) { // Construct an 'icon button' using the fontawesome set var classes = `btn btn-default btn-glyph ${cls}`; @@ -86,8 +86,14 @@ function makeIconButton(icon, cls, pk, title) { var id = `${cls}-${pk}`; var html = ''; + + var extraProps = ''; + + if (options.disabled) { + extraProps += "disabled='true' "; + } - html += ``; diff --git a/InvenTree/build/forms.py b/InvenTree/build/forms.py index eb276d52d4..b1df528e08 100644 --- a/InvenTree/build/forms.py +++ b/InvenTree/build/forms.py @@ -46,6 +46,29 @@ class EditBuildForm(HelperForm): ] +class BuildOutputDeleteForm(HelperForm): + """ + Form for deleting a build output. + """ + + confirm = forms.BooleanField( + required=False, + help_text=_('Confirm deletion of build output') + ) + + output_id = forms.IntegerField( + required=True, + widget=forms.HiddenInput() + ) + + class Meta: + model = Build + fields = [ + 'confirm', + 'output_id', + ] + + class UnallocateBuildForm(HelperForm): """ Form for auto-de-allocation of stock from a build diff --git a/InvenTree/build/models.py b/InvenTree/build/models.py index b82d9470dd..e46a204a36 100644 --- a/InvenTree/build/models.py +++ b/InvenTree/build/models.py @@ -380,6 +380,29 @@ class Build(MPTTModel): # Remove all the allocations allocations.delete() + def deleteBuildOutput(self, output): + """ + Remove a build output from the database: + + - Unallocate any build items against the output + - Delete the output StockItem + """ + + if not output: + raise ValidationError(_("No build output specified")) + + if not output.is_building: + raise ValidationError(_("Build output is already completed")) + + if not output.build == self: + raise ValidationError(_("Build output does not match Build Order")) + + # Unallocate all build items against the output + self.unallocateStock(output) + + # Remove the build output from the database + output.delete() + @transaction.atomic def autoAllocate(self): """ Run auto-allocation routine to allocate StockItems to this Build. diff --git a/InvenTree/build/urls.py b/InvenTree/build/urls.py index d8cb3c03ea..109dd0b556 100644 --- a/InvenTree/build/urls.py +++ b/InvenTree/build/urls.py @@ -11,6 +11,7 @@ build_detail_urls = [ url(r'^allocate/', views.BuildAllocate.as_view(), name='build-allocate'), url(r'^cancel/', views.BuildCancel.as_view(), name='build-cancel'), url(r'^delete/', views.BuildDelete.as_view(), name='build-delete'), + url(r'^delete-output/', views.BuildOutputDelete.as_view(), name='build-output-delete'), url(r'^complete/?', views.BuildComplete.as_view(), name='build-complete'), url(r'^auto-allocate/?', views.BuildAutoAllocate.as_view(), name='build-auto-allocate'), url(r'^unallocate/', views.BuildUnallocate.as_view(), name='build-unallocate'), diff --git a/InvenTree/build/views.py b/InvenTree/build/views.py index 53c16333cd..73d2561e09 100644 --- a/InvenTree/build/views.py +++ b/InvenTree/build/views.py @@ -141,6 +141,56 @@ class BuildAutoAllocate(AjaxUpdateView): return self.renderJsonResponse(request, form, data, context=self.get_context_data()) +class BuildOutputDelete(AjaxUpdateView): + """ + Delete a build output (StockItem) for a given build. + + Form is a simple confirmation dialog + """ + + model = Build + form_class = forms.BuildOutputDeleteForm + ajax_form_title = _('Delete build output') + role_required = 'build.delete' + + def get_initial(self): + + initials = super().get_initial() + + output = self.get_param('output') + + initials['output_id'] = output + + return initials + + def post(self, request, *args, **kwargs): + + build = self.get_object() + form = self.get_form() + + confirm = request.POST.get('confirm', False) + + output_id = request.POST.get('output_id', None) + + try: + output = StockItem.objects.get(pk=output_id) + except (ValueError, StockItem.DoesNotExist): + output = None + + valid = False + + if output: + build.deleteBuildOutput(output) + valid = True + else: + form.non_field_errors = [_('Build or output not specified')] + + data = { + 'form_valid': valid, + } + + return self.renderJsonResponse(request, form, data) + class BuildUnallocate(AjaxUpdateView): """ View to un-allocate all parts from a build. @@ -151,7 +201,7 @@ class BuildUnallocate(AjaxUpdateView): form_class = forms.UnallocateBuildForm ajax_form_title = _("Unallocate Stock") ajax_template_name = "build/unallocate.html" - form_required = 'build.change' + role_required = 'build.change' def get_initial(self): @@ -161,13 +211,7 @@ class BuildUnallocate(AjaxUpdateView): output = self.get_param('output') if output: - try: - output = StockItem.objects.get(pk=output) - except (ValueError, StockItem.DoesNotExist): - output = None - - if output: - initials['output_id'] = output.pk + initials['output_id'] = output return initials diff --git a/InvenTree/part/models.py b/InvenTree/part/models.py index ddf612da92..aeced2f6f0 100644 --- a/InvenTree/part/models.py +++ b/InvenTree/part/models.py @@ -954,7 +954,8 @@ class Part(MPTTModel): return parts def get_allowed_bom_items(self): - """ Return a list of parts which can be added to a BOM for this part. + """ + Return a list of parts which can be added to a BOM for this part. - Exclude parts which are not 'component' parts - Exclude parts which this part is in the BOM for diff --git a/InvenTree/templates/js/build.js b/InvenTree/templates/js/build.js index ab1741d4bb..2393350fc0 100644 --- a/InvenTree/templates/js/build.js +++ b/InvenTree/templates/js/build.js @@ -52,13 +52,19 @@ function makeBuildOutputActionButtons(output, buildId) { // Add a button to "auto allocate" against the particular build output html += makeIconButton( 'fa-clipboard-check icon-blue', 'button-output-auto', outputId, - '{% trans "Allocate stock items to this output" %}' + '{% trans "Allocate stock items to this output" %}', + { + disabled: true, + } ); if (output.quantity > 1) { html += makeIconButton( 'fa-random icon-blue', 'button-output-split', outputId, '{% trans "Split build output into separate items" %}', + { + disabled: true + } ); } @@ -66,6 +72,9 @@ function makeBuildOutputActionButtons(output, buildId) { html += makeIconButton( 'fa-tools icon-green', 'button-output-complete', outputId, '{% trans "Complete build output" %}', + { + disabled: true + } ); // Add a button to "cancel" the particular build output (unallocate) @@ -116,6 +125,17 @@ function makeBuildOutputActionButtons(output, buildId) { ); }); + $(panel).find(`#button-output-delete-${outputId}`).click(function() { + launchModalForm( + `/build/${buildId}/delete-output/`, + { + reload: true, + data: { + output: outputId + } + } + ); + }); }