diff --git a/InvenTree/InvenTree/views.py b/InvenTree/InvenTree/views.py
index 26303a6549..04d1978745 100644
--- a/InvenTree/InvenTree/views.py
+++ b/InvenTree/InvenTree/views.py
@@ -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.
diff --git a/InvenTree/build/forms.py b/InvenTree/build/forms.py
index 87116c8646..de56ca34f7 100644
--- a/InvenTree/build/forms.py
+++ b/InvenTree/build/forms.py
@@ -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 """
diff --git a/InvenTree/build/migrations/0008_auto_20190501_2344.py b/InvenTree/build/migrations/0008_auto_20190501_2344.py
new file mode 100644
index 0000000000..febdd2d1b1
--- /dev/null
+++ b/InvenTree/build/migrations/0008_auto_20190501_2344.py
@@ -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'),
+ ),
+ ]
diff --git a/InvenTree/build/models.py b/InvenTree/build/models.py
index d13919c24e..56ff7c5804 100644
--- a/InvenTree/build/models.py
+++ b/InvenTree/build/models.py
@@ -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):
diff --git a/InvenTree/build/templates/build/allocate.html b/InvenTree/build/templates/build/allocate.html
index e9438d898f..c3cda38429 100644
--- a/InvenTree/build/templates/build/allocate.html
+++ b/InvenTree/build/templates/build/allocate.html
@@ -7,7 +7,7 @@
Allocate Parts for Build
-
+{{ build.quantity }} x {{ build.part.name }}
{% 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 %}
\ No newline at end of file
+{% endblock %}
diff --git a/InvenTree/build/templates/build/complete.html b/InvenTree/build/templates/build/complete.html
index 6cf26c2a6c..ce1577259d 100644
--- a/InvenTree/build/templates/build/complete.html
+++ b/InvenTree/build/templates/build/complete.html
@@ -1 +1,13 @@
-Mark as COMPLETE
\ No newline at end of file
+{% extends "modal_form.html" %}
+
+{% block pre_form_content %}
+Build: {{ build.title }} - {{ build.quantity }} x {{ build.part.name }}
+
+Are you sure you want to mark this build as complete?
+
+Completing the build will perform the following actions:
+
+ - Remove allocated parts from stock
+ - Create {{ build.quantity }} new items in the selected location
+
+{% endblock %}
\ No newline at end of file
diff --git a/InvenTree/build/views.py b/InvenTree/build/views.py
index ebc5372596..76558d87ee 100644
--- a/InvenTree/build/views.py
+++ b/InvenTree/build/views.py
@@ -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
+
+ - If the form validation passes, the Build objects completeBuild() method is called
+ - Otherwise, the form is passed back to the client
+ """
- build = get_object_or_404(Build, pk=self.kwargs['pk'])
+ build = self.get_object()
- build.complete()
+ form = self.get_form()
- return self.renderJsonResponse(request, None)
+ 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 """
diff --git a/InvenTree/static/script/inventree/build.js b/InvenTree/static/script/inventree/build.js
index fd20af0743..826e2b1661 100644
--- a/InvenTree/static/script/inventree/build.js
+++ b/InvenTree/static/script/inventree/build.js
@@ -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',
diff --git a/InvenTree/stock/migrations/0010_auto_20190501_2344.py b/InvenTree/stock/migrations/0010_auto_20190501_2344.py
new file mode 100644
index 0000000000..61ea730b03
--- /dev/null
+++ b/InvenTree/stock/migrations/0010_auto_20190501_2344.py
@@ -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),
+ ),
+ ]
diff --git a/InvenTree/stock/models.py b/InvenTree/stock/models.py
index 2f61c068cd..cb0ad8aea4 100644
--- a/InvenTree/stock/models.py
+++ b/InvenTree/stock/models.py
@@ -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
diff --git a/InvenTree/templates/modal_form.html b/InvenTree/templates/modal_form.html
index 1318e4f238..566671c657 100644
--- a/InvenTree/templates/modal_form.html
+++ b/InvenTree/templates/modal_form.html
@@ -1,3 +1,6 @@
+{% block pre_form_content %}
+{% endblock %}
+
{% if form.non_field_errors %}
Error Submitting Form:
@@ -11,4 +14,7 @@
{% crispy form %}
-
\ No newline at end of file
+
+
+{% block post_form_content %}
+{% endblock %}
\ No newline at end of file