Complete build now works

- Marks build as complete
- Deletes temporary BuildItem objects
- Preselects the part's default_location if there is one
- Creates a new stockitem in the selected location
This commit is contained in:
Oliver Walters 2019-05-02 00:04:39 +10:00
parent 29f7b1a32b
commit 905d78e25c
11 changed files with 198 additions and 23 deletions

View File

@ -199,7 +199,7 @@ class AjaxUpdateView(AjaxMixin, UpdateView):
form = self.get_form()
return self.renderJsonResponse(request, form)
return self.renderJsonResponse(request, form, context=self.get_context_data())
def post(self, request, *args, **kwargs):
""" Respond to POST request.

View File

@ -6,8 +6,9 @@ Django Forms for interacting with Build objects
from __future__ import unicode_literals
from InvenTree.forms import HelperForm
from django import forms
from .models import Build, BuildItem
from stock.models import StockLocation
class EditBuildForm(HelperForm):
@ -26,6 +27,24 @@ class EditBuildForm(HelperForm):
]
class CompleteBuildForm(HelperForm):
""" Form for marking a Build as complete """
location = forms.ModelChoiceField(
queryset=StockLocation.objects.all(),
help_text='Location of completed parts',
)
confirm = forms.BooleanField(required=False, help_text='Confirm build submission')
class Meta:
model = Build
fields = [
'location',
'confirm'
]
class EditBuildItemForm(HelperForm):
""" Form for adding a new BuildItem to a Build """

View File

@ -0,0 +1,25 @@
# Generated by Django 2.2 on 2019-05-01 13:44
import django.core.validators
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('build', '0007_auto_20190429_2255'),
]
operations = [
migrations.AlterField(
model_name='build',
name='status',
field=models.PositiveIntegerField(choices=[(10, 'Pending'), (30, 'Cancelled'), (40, 'Complete')], default=10, validators=[django.core.validators.MinValueValidator(0)]),
),
migrations.AlterField(
model_name='builditem',
name='build',
field=models.ForeignKey(help_text='Build to allocate parts', on_delete=django.db.models.deletion.CASCADE, related_name='allocated_stock', to='build.Build'),
),
]

View File

@ -5,6 +5,8 @@ Build database model definitions
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from datetime import datetime
from django.utils.translation import ugettext as _
from django.core.exceptions import ValidationError
@ -128,22 +130,56 @@ class Build(models.Model):
- Save the Build object
"""
for item in BuildItem.objects.filter(build=self.id):
for item in self.allocated_stock.all():
item.delete()
self.status = self.CANCELLED
self.save()
print("cancelled!")
@transaction.atomic
def completeBuild(self):
def completeBuild(self, location, user):
""" Mark the Build as COMPLETE
- Takes allocated items from stock
- Delete pending BuildItem objects
"""
print("complete!!!!")
for item in self.allocated_stock.all():
# Subtract stock from the item
item.stock_item.take_stock(
item.quantity,
user,
'Removed {n} items to build {m} x {part}'.format(
n=item.quantity,
m=self.quantity,
part=self.part.name
)
)
# Delete the item
item.delete()
# Mark the date of completion
self.completion_date = datetime.now().date()
# Add stock of the newly created item
item = StockItem.objects.create(
part=self.part,
location=location,
quantity=self.quantity,
batch=str(self.batch) if self.batch else '',
notes='Built {q} on {now}'.format(
q=self.quantity,
now=str(datetime.now().date())
)
)
item.save()
# Finally, mark the build as complete
self.status = self.COMPLETE
self.save()
@property
def required_parts(self):

View File

@ -7,7 +7,7 @@
<h3>Allocate Parts for Build</h3>
<h4><a href="{% url 'build-detail' build.id %}">{{ build.title }}</a></h4>
{{ build.quantity }} x {{ build.part.name }}
<hr>
{% for bom_item in bom_items.all %}
@ -46,8 +46,6 @@
{% endfor %}
/*
$("#complete-build").on('click', function() {
launchModalForm(
"{% url 'build-complete' build.id %}",
@ -57,6 +55,5 @@
}
);
});
*/
{% endblock %}

View File

@ -1 +1,13 @@
Mark as COMPLETE
{% extends "modal_form.html" %}
{% block pre_form_content %}
<b>Build: {{ build.title }}</b> - {{ build.quantity }} x {{ build.part.name }}
<br>
Are you sure you want to mark this build as complete?
<br>
Completing the build will perform the following actions:
<ul>
<li>Remove allocated parts from stock</li>
<li>Create {{ build.quantity }} new items in the selected location</li>
</ul>
{% endblock %}

View File

@ -12,7 +12,8 @@ from django.forms import HiddenInput
from part.models import Part
from .models import Build, BuildItem
from .forms import EditBuildForm, EditBuildItemForm
from .forms import EditBuildForm, EditBuildItemForm, CompleteBuildForm
from stock.models import StockLocation
from InvenTree.views import AjaxView, AjaxUpdateView, AjaxCreateView, AjaxDeleteView
@ -68,7 +69,7 @@ class BuildCancel(AjaxView):
}
class BuildComplete(AjaxView):
class BuildComplete(AjaxUpdateView):
""" View to mark a build as Complete.
- Notifies the user of which parts will be removed from stock.
@ -77,19 +78,76 @@ class BuildComplete(AjaxView):
"""
model = Build
ajax_template_name = "build/complete.html"
ajax_form_title = "Complete Build"
form_class = CompleteBuildForm
context_object_name = "build"
fields = []
ajax_form_title = "Complete Build"
ajax_template_name = "build/complete.html"
def get_initial(self):
""" Get initial form data for the CompleteBuild form
- If the part being built has a default location, pre-select that location
"""
initials = super(BuildComplete, self).get_initial().copy()
build = self.get_object()
if build.part.default_location is not None:
try:
location = StockLocation.objects.get(pk=build.part.default_location.id)
except StockLocation.DoesNotExist:
pass
return initials
def get_context_data(self, **kwargs):
""" Get context data for passing to the rendered form
- Build information is required
"""
context = super(BuildComplete, self).get_context_data(**kwargs).copy()
context['build'] = self.get_object()
return context
def post(self, request, *args, **kwargs):
""" Handle POST request. Mark the build as COMPLETE """
""" Handle POST request. Mark the build as COMPLETE
build = get_object_or_404(Build, pk=self.kwargs['pk'])
- If the form validation passes, the Build objects completeBuild() method is called
- Otherwise, the form is passed back to the client
"""
build.complete()
build = self.get_object()
return self.renderJsonResponse(request, None)
form = self.get_form()
confirm = request.POST.get('confirm', False)
loc_id = request.POST.get('location', None)
valid = False
if confirm is False:
form.errors['confirm'] = [
'Confirm completion of build',
]
else:
try:
location = StockLocation.objects.get(id=loc_id)
valid = True
except StockLocation.DoesNotExist:
print('id:', loc_id)
form.errors['location'] = ['Invalid location selected']
if valid:
build.completeBuild(location, request.user)
data = {
'form_valid': valid,
}
return self.renderJsonResponse(request, form, data)
def get_data(self):
""" Provide feedback data back to the form """

View File

@ -30,6 +30,10 @@ function loadAllocationTable(table, part_id, part, url, required, button) {
return '' + value.quantity + ' x ' + value.part_name + ' @ ' + value.location_name;
}
},
{
field: 'stock_item_detail.quantity',
title: 'Available',
},
{
field: 'quantity',
title: 'Allocated',

View File

@ -0,0 +1,18 @@
# Generated by Django 2.2 on 2019-05-01 13:44
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('stock', '0009_auto_20190428_0841'),
]
operations = [
migrations.AlterField(
model_name='stockitem',
name='batch',
field=models.CharField(blank=True, help_text='Batch code for this stock item', max_length=100, null=True),
),
]

View File

@ -158,7 +158,7 @@ class StockItem(models.Model):
URL = models.URLField(max_length=125, blank=True)
# Optional batch information
batch = models.CharField(max_length=100, blank=True,
batch = models.CharField(max_length=100, blank=True, null=True,
help_text='Batch code for this stock item')
# If this part was produced by a build, point to that build here

View File

@ -1,3 +1,6 @@
{% block pre_form_content %}
{% endblock %}
{% if form.non_field_errors %}
<div class='alert alert-danger' role='alert' style='display: block;'>
<b>Error Submitting Form:</b>
@ -12,3 +15,6 @@
{% crispy form %}
</form>
{% block post_form_content %}
{% endblock %}