Refactor CancelBuild form

This commit is contained in:
Oliver Walters 2022-05-04 16:30:46 +10:00
parent 768e23c7b8
commit bd3d6f47a1
11 changed files with 153 additions and 68 deletions

View File

@ -322,6 +322,13 @@ class BuildAllocate(BuildOrderContextMixin, generics.CreateAPIView):
serializer_class = build.serializers.BuildAllocationSerializer
class BuildCancel(BuildOrderContextMixin, generics.CreateAPIView):
""" API endpoint for cancelling a BuildOrder """
queryset = Build.objects.all()
serializer_class = build.serializers.BuildCancelSerializer
class BuildItemDetail(generics.RetrieveUpdateDestroyAPIView):
"""
API endpoint for detail view of a BuildItem object
@ -462,6 +469,7 @@ build_api_urls = [
re_path(r'^create-output/', BuildOutputCreate.as_view(), name='api-build-output-create'),
re_path(r'^delete-outputs/', BuildOutputDelete.as_view(), name='api-build-output-delete'),
re_path(r'^finish/', BuildFinish.as_view(), name='api-build-finish'),
re_path(r'^cancel/', BuildCancel.as_view(), name='api-build-cancel'),
re_path(r'^unallocate/', BuildUnallocate.as_view(), name='api-build-unallocate'),
re_path(r'^.*$', BuildDetail.as_view(), name='api-build-detail'),
])),

View File

@ -5,22 +5,3 @@ Django Forms for interacting with Build objects
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.utils.translation import gettext_lazy as _
from django import forms
from InvenTree.forms import HelperForm
from .models import Build
class CancelBuildForm(HelperForm):
""" Form for cancelling a build """
confirm_cancel = forms.BooleanField(required=False, label=_('Confirm cancel'), help_text=_('Confirm build cancellation'))
class Meta:
model = Build
fields = [
'confirm_cancel'
]

View File

@ -479,6 +479,16 @@ class Build(MPTTModel, ReferenceIndexingMixin):
return outputs
@property
def complete_count(self):
quantity = 0
for output in self.complete_outputs:
quantity += output.quantity
return quantity
@property
def incomplete_outputs(self):
"""
@ -588,7 +598,7 @@ class Build(MPTTModel, ReferenceIndexingMixin):
trigger_event('build.completed', id=self.pk)
@transaction.atomic
def cancelBuild(self, user):
def cancel_build(self, user, **kwargs):
""" Mark the Build as CANCELLED
- Delete any pending BuildItem objects (but do not remove items from stock)
@ -596,8 +606,23 @@ class Build(MPTTModel, ReferenceIndexingMixin):
- Save the Build object
"""
for item in self.allocated_stock.all():
item.delete()
remove_allocated_stock = kwargs.get('remove_allocated_stock', False)
remove_incomplete_outputs = kwargs.get('remove_incomplete_outputs', False)
# Handle stock allocations
for build_item in self.allocated_stock.all():
if remove_allocated_stock:
build_item.complete_allocation(user)
build_item.delete()
# Remove incomplete outputs (if required)
if remove_incomplete_outputs:
outputs = self.build_outputs.filter(is_building=True)
for output in outputs:
output.delete()
# Date of 'completion' is the date the build was cancelled
self.completion_date = datetime.now().date()
@ -1025,6 +1050,24 @@ class Build(MPTTModel, ReferenceIndexingMixin):
# All parts must be fully allocated!
return True
def is_partially_allocated(self, output):
"""
Returns True if the particular build output is (at least) partially allocated
"""
# If output is not specified, we are talking about "untracked" items
if output is None:
bom_items = self.untracked_bom_items
else:
bom_items = self.tracked_bom_items
for bom_item in bom_items:
if self.allocated_quantity(bom_item, output) > 0:
return True
return False
def are_untracked_parts_allocated(self):
"""
Returns True if the un-tracked parts are fully allocated for this BuildOrder

View File

@ -438,6 +438,52 @@ class BuildOutputCompleteSerializer(serializers.Serializer):
)
class BuildCancelSerializer(serializers.Serializer):
class Meta:
fields = [
'remove_allocated_stock',
'remove_incomplete_outputs',
]
def get_context_data(self):
build = self.context['build']
return {
'has_allocated_stock': build.is_partially_allocated(None),
'incomplete_outputs': build.incomplete_count,
'completed_outputs': build.complete_count,
}
remove_allocated_stock = serializers.BooleanField(
label=_('Remove Allocated Stock'),
help_text=_('Subtract any stock which has already been allocated to this build'),
required=False,
default=False,
)
remove_incomplete_outputs = serializers.BooleanField(
label=_('Remove Incomplete Outputs'),
help_text=_('Delete any build outputs which have not been completed'),
required=False,
default=False,
)
def save(self):
build = self.context['build']
request = self.context['request']
data = self.validated_data
build.cancel_build(
request.user,
remove_allocated_stock=data.get('remove_unallocated_stock', False),
remove_incomplete_outputs=data.get('remove_incomplete_outputs', False),
)
class BuildCompleteSerializer(serializers.Serializer):
"""
DRF serializer for marking a BuildOrder as complete

View File

@ -214,11 +214,13 @@ src="{% static 'img/blank_image.png' %}"
});
$("#build-cancel").click(function() {
launchModalForm("{% url 'build-cancel' build.id %}",
{
reload: true,
submit_text: '{% trans "Cancel Build" %}',
});
cancelBuildOrder(
{{ build.pk }},
{
reload: true,
}
);
});
$("#build-complete").on('click', function() {

View File

@ -1,7 +0,0 @@
{% extends "modal_form.html" %}
{% load i18n %}
{% block pre_form_content %}
{% trans "Are you sure you wish to cancel this build?" %}
{% endblock %}

View File

@ -304,7 +304,7 @@ class BuildTest(BuildTestBase):
"""
self.allocate_stock(50, 50, 200, self.output_1)
self.build.cancelBuild(None)
self.build.cancel_build(None)
self.assertEqual(BuildItem.objects.count(), 0)
"""

View File

@ -107,7 +107,7 @@ class BuildTestSimple(TestCase):
self.assertEqual(build.status, BuildStatus.PENDING)
build.cancelBuild(self.user)
build.cancel_build(self.user)
self.assertEqual(build.status, BuildStatus.CANCELLED)

View File

@ -7,7 +7,6 @@ from django.urls import include, re_path
from . import views
build_detail_urls = [
re_path(r'^cancel/', views.BuildCancel.as_view(), name='build-cancel'),
re_path(r'^delete/', views.BuildDelete.as_view(), name='build-delete'),
re_path(r'^.*$', views.BuildDetail.as_view(), name='build-detail'),

View File

@ -43,37 +43,6 @@ class BuildIndex(InvenTreeRoleMixin, ListView):
return context
class BuildCancel(AjaxUpdateView):
""" View to cancel a Build.
Provides a cancellation information dialog
"""
model = Build
ajax_template_name = 'build/cancel.html'
ajax_form_title = _('Cancel Build')
context_object_name = 'build'
form_class = forms.CancelBuildForm
def validate(self, build, form, **kwargs):
confirm = str2bool(form.cleaned_data.get('confirm_cancel', False))
if not confirm:
form.add_error('confirm_cancel', _('Confirm build cancellation'))
def save(self, build, form, **kwargs):
"""
Cancel the build.
"""
build.cancelBuild(self.request.user)
def get_data(self):
return {
'danger': _('Build was cancelled')
}
class BuildDetail(InvenTreeRoleMixin, DetailView):
"""
Detail view of a single Build object.

View File

@ -21,6 +21,7 @@
/* exported
allocateStockToBuild,
autoAllocateStockToBuild,
cancelBuildOrder,
completeBuildOrder,
createBuildOutput,
editBuildOrder,
@ -123,6 +124,49 @@ function newBuildOrder(options={}) {
}
/* Construct a form to cancel a build order */
function cancelBuildOrder(build_id, options={}) {
constructForm(
`/api/build/${build_id}/cancel/`,
{
method: 'POST',
title: '{% trans "Cancel Build Order" %}',
confirm: true,
fields: {
remove_allocated_stock: {},
remove_incomplete_outputs: {},
},
preFormContent: function(opts) {
var html = `
<div class='alert alert-block alert-info'>
{% trans "Are you sure you wish to cancel this build?" %}
</div>`;
if (opts.context.has_allocated_stock) {
html += `
<div class='alert alert-block alert-warning'>
{% trans "Stock items have been allocated to this build order" %}
</div>`;
}
if (opts.context.incomplete_outputs) {
html += `
<div class='alert alert-block alert-warning'>
{% trans "There are incomplete outputs remaining for this build order" %}
</div>`;
}
return html;
},
onSuccess: function(response) {
handleFormSuccess(response, options);
}
}
);
}
/* Construct a form to "complete" (finish) a build order */
function completeBuildOrder(build_id, options={}) {