mirror of
https://github.com/inventree/InvenTree.git
synced 2025-04-30 12:36:45 +00:00
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:
parent
29f7b1a32b
commit
905d78e25c
@ -199,7 +199,7 @@ class AjaxUpdateView(AjaxMixin, UpdateView):
|
|||||||
|
|
||||||
form = self.get_form()
|
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):
|
def post(self, request, *args, **kwargs):
|
||||||
""" Respond to POST request.
|
""" Respond to POST request.
|
||||||
|
@ -6,8 +6,9 @@ Django Forms for interacting with Build objects
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from InvenTree.forms import HelperForm
|
from InvenTree.forms import HelperForm
|
||||||
|
from django import forms
|
||||||
from .models import Build, BuildItem
|
from .models import Build, BuildItem
|
||||||
|
from stock.models import StockLocation
|
||||||
|
|
||||||
|
|
||||||
class EditBuildForm(HelperForm):
|
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):
|
class EditBuildItemForm(HelperForm):
|
||||||
""" Form for adding a new BuildItem to a Build """
|
""" Form for adding a new BuildItem to a Build """
|
||||||
|
|
||||||
|
25
InvenTree/build/migrations/0008_auto_20190501_2344.py
Normal file
25
InvenTree/build/migrations/0008_auto_20190501_2344.py
Normal 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'),
|
||||||
|
),
|
||||||
|
]
|
@ -5,6 +5,8 @@ Build database model definitions
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
|
|
||||||
@ -128,22 +130,56 @@ class Build(models.Model):
|
|||||||
- Save the Build object
|
- Save the Build object
|
||||||
"""
|
"""
|
||||||
|
|
||||||
for item in BuildItem.objects.filter(build=self.id):
|
for item in self.allocated_stock.all():
|
||||||
item.delete()
|
item.delete()
|
||||||
|
|
||||||
self.status = self.CANCELLED
|
self.status = self.CANCELLED
|
||||||
self.save()
|
self.save()
|
||||||
print("cancelled!")
|
|
||||||
|
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
def completeBuild(self):
|
def completeBuild(self, location, user):
|
||||||
""" Mark the Build as COMPLETE
|
""" Mark the Build as COMPLETE
|
||||||
|
|
||||||
- Takes allocated items from stock
|
- Takes allocated items from stock
|
||||||
- Delete pending BuildItem objects
|
- 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
|
@property
|
||||||
def required_parts(self):
|
def required_parts(self):
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
<h3>Allocate Parts for Build</h3>
|
<h3>Allocate Parts for Build</h3>
|
||||||
|
|
||||||
<h4><a href="{% url 'build-detail' build.id %}">{{ build.title }}</a></h4>
|
<h4><a href="{% url 'build-detail' build.id %}">{{ build.title }}</a></h4>
|
||||||
|
{{ build.quantity }} x {{ build.part.name }}
|
||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
{% for bom_item in bom_items.all %}
|
{% for bom_item in bom_items.all %}
|
||||||
@ -46,8 +46,6 @@
|
|||||||
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
$("#complete-build").on('click', function() {
|
$("#complete-build").on('click', function() {
|
||||||
launchModalForm(
|
launchModalForm(
|
||||||
"{% url 'build-complete' build.id %}",
|
"{% url 'build-complete' build.id %}",
|
||||||
@ -57,6 +55,5 @@
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
*/
|
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
@ -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 %}
|
@ -12,7 +12,8 @@ from django.forms import HiddenInput
|
|||||||
|
|
||||||
from part.models import Part
|
from part.models import Part
|
||||||
from .models import Build, BuildItem
|
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
|
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.
|
""" View to mark a build as Complete.
|
||||||
|
|
||||||
- Notifies the user of which parts will be removed from stock.
|
- Notifies the user of which parts will be removed from stock.
|
||||||
@ -77,19 +78,76 @@ class BuildComplete(AjaxView):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
model = Build
|
model = Build
|
||||||
ajax_template_name = "build/complete.html"
|
form_class = CompleteBuildForm
|
||||||
ajax_form_title = "Complete Build"
|
|
||||||
context_object_name = "build"
|
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):
|
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):
|
def get_data(self):
|
||||||
""" Provide feedback data back to the form """
|
""" Provide feedback data back to the form """
|
||||||
|
@ -30,6 +30,10 @@ function loadAllocationTable(table, part_id, part, url, required, button) {
|
|||||||
return '' + value.quantity + ' x ' + value.part_name + ' @ ' + value.location_name;
|
return '' + value.quantity + ' x ' + value.part_name + ' @ ' + value.location_name;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
field: 'stock_item_detail.quantity',
|
||||||
|
title: 'Available',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
field: 'quantity',
|
field: 'quantity',
|
||||||
title: 'Allocated',
|
title: 'Allocated',
|
||||||
|
18
InvenTree/stock/migrations/0010_auto_20190501_2344.py
Normal file
18
InvenTree/stock/migrations/0010_auto_20190501_2344.py
Normal 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),
|
||||||
|
),
|
||||||
|
]
|
@ -158,7 +158,7 @@ class StockItem(models.Model):
|
|||||||
URL = models.URLField(max_length=125, blank=True)
|
URL = models.URLField(max_length=125, blank=True)
|
||||||
|
|
||||||
# Optional batch information
|
# 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')
|
help_text='Batch code for this stock item')
|
||||||
|
|
||||||
# If this part was produced by a build, point to that build here
|
# If this part was produced by a build, point to that build here
|
||||||
|
@ -1,3 +1,6 @@
|
|||||||
|
{% block pre_form_content %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
{% if form.non_field_errors %}
|
{% if form.non_field_errors %}
|
||||||
<div class='alert alert-danger' role='alert' style='display: block;'>
|
<div class='alert alert-danger' role='alert' style='display: block;'>
|
||||||
<b>Error Submitting Form:</b>
|
<b>Error Submitting Form:</b>
|
||||||
@ -12,3 +15,6 @@
|
|||||||
|
|
||||||
{% crispy form %}
|
{% crispy form %}
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
{% block post_form_content %}
|
||||||
|
{% endblock %}
|
Loading…
x
Reference in New Issue
Block a user