mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Add an API serializer to complete build outputs
This commit is contained in:
parent
4b4bf38ae5
commit
54dd05a24d
@ -6,7 +6,7 @@ JSON API for the Build app
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.conf.urls import url, include
|
||||
|
||||
from rest_framework import filters, generics
|
||||
@ -20,7 +20,7 @@ from InvenTree.helpers import str2bool, isNull
|
||||
from InvenTree.status_codes import BuildStatus
|
||||
|
||||
from .models import Build, BuildItem, BuildOrderAttachment
|
||||
from .serializers import BuildAttachmentSerializer, BuildSerializer, BuildItemSerializer
|
||||
from .serializers import BuildAttachmentSerializer, BuildCompleteSerializer, BuildSerializer, BuildItemSerializer
|
||||
from .serializers import BuildAllocationSerializer, BuildUnallocationSerializer
|
||||
|
||||
|
||||
@ -196,30 +196,34 @@ class BuildUnallocate(generics.CreateAPIView):
|
||||
queryset = Build.objects.none()
|
||||
|
||||
serializer_class = BuildUnallocationSerializer
|
||||
|
||||
def get_build(self):
|
||||
"""
|
||||
Returns the BuildOrder associated with this API endpoint
|
||||
"""
|
||||
|
||||
pk = self.kwargs.get('pk', None)
|
||||
|
||||
try:
|
||||
build = Build.objects.get(pk=pk)
|
||||
except (ValueError, Build.DoesNotExist):
|
||||
raise ValidationError(_("Matching build order does not exist"))
|
||||
|
||||
return build
|
||||
|
||||
|
||||
def get_serializer_context(self):
|
||||
|
||||
ctx = super().get_serializer_context()
|
||||
ctx['build'] = self.get_build()
|
||||
ctx['build'] = get_object_or_404(Build, pk=self.kwargs.get('pk', None))
|
||||
ctx['request'] = self.request
|
||||
|
||||
return ctx
|
||||
|
||||
|
||||
class BuildComplete(generics.CreateAPIView):
|
||||
"""
|
||||
API endpoint for completing build outputs
|
||||
"""
|
||||
|
||||
queryset = Build.objects.none()
|
||||
|
||||
serializer_class = BuildCompleteSerializer
|
||||
|
||||
def get_serializer_context(self):
|
||||
ctx = super().get_serializer_context()
|
||||
|
||||
ctx['request'] = self.request
|
||||
ctx['build'] = get_object_or_404(Build, pk=self.kwargs.get('pk', None))
|
||||
|
||||
return ctx
|
||||
|
||||
|
||||
class BuildAllocate(generics.CreateAPIView):
|
||||
"""
|
||||
API endpoint to allocate stock items to a build order
|
||||
@ -236,20 +240,6 @@ class BuildAllocate(generics.CreateAPIView):
|
||||
|
||||
serializer_class = BuildAllocationSerializer
|
||||
|
||||
def get_build(self):
|
||||
"""
|
||||
Returns the BuildOrder associated with this API endpoint
|
||||
"""
|
||||
|
||||
pk = self.kwargs.get('pk', None)
|
||||
|
||||
try:
|
||||
build = Build.objects.get(pk=pk)
|
||||
except (Build.DoesNotExist, ValueError):
|
||||
raise ValidationError(_("Matching build order does not exist"))
|
||||
|
||||
return build
|
||||
|
||||
def get_serializer_context(self):
|
||||
"""
|
||||
Provide the Build object to the serializer context
|
||||
@ -257,7 +247,7 @@ class BuildAllocate(generics.CreateAPIView):
|
||||
|
||||
context = super().get_serializer_context()
|
||||
|
||||
context['build'] = self.get_build()
|
||||
context['build'] = get_object_or_404(Build, pk=self.kwargs.get('pk', None))
|
||||
context['request'] = self.request
|
||||
|
||||
return context
|
||||
@ -385,6 +375,7 @@ build_api_urls = [
|
||||
# Build Detail
|
||||
url(r'^(?P<pk>\d+)/', include([
|
||||
url(r'^allocate/', BuildAllocate.as_view(), name='api-build-allocate'),
|
||||
url(r'^complete/', BuildComplete.as_view(), name='api-build-complete'),
|
||||
url(r'^unallocate/', BuildUnallocate.as_view(), name='api-build-unallocate'),
|
||||
url(r'^.*$', BuildDetail.as_view(), name='api-build-detail'),
|
||||
])),
|
||||
|
@ -722,7 +722,7 @@ class Build(MPTTModel):
|
||||
items.all().delete()
|
||||
|
||||
@transaction.atomic
|
||||
def completeBuildOutput(self, output, user, **kwargs):
|
||||
def complete_build_output(self, output, user, **kwargs):
|
||||
"""
|
||||
Complete a particular build output
|
||||
|
||||
@ -739,10 +739,6 @@ class Build(MPTTModel):
|
||||
allocated_items = output.items_to_install.all()
|
||||
|
||||
for build_item in allocated_items:
|
||||
|
||||
# TODO: This is VERY SLOW as each deletion from the database takes ~1 second to complete
|
||||
# TODO: Use the background worker process to handle this task!
|
||||
|
||||
# Complete the allocation of stock for that item
|
||||
build_item.complete_allocation(user)
|
||||
|
||||
@ -768,6 +764,7 @@ class Build(MPTTModel):
|
||||
|
||||
# Increase the completed quantity for this build
|
||||
self.completed += output.quantity
|
||||
|
||||
self.save()
|
||||
|
||||
def requiredQuantity(self, part, output):
|
||||
|
@ -18,9 +18,10 @@ from rest_framework.serializers import ValidationError
|
||||
from InvenTree.serializers import InvenTreeModelSerializer, InvenTreeAttachmentSerializer
|
||||
from InvenTree.serializers import InvenTreeAttachmentSerializerField, UserSerializerBrief
|
||||
|
||||
from InvenTree.status_codes import StockStatus
|
||||
import InvenTree.helpers
|
||||
|
||||
from stock.models import StockItem
|
||||
from stock.models import StockItem, StockLocation
|
||||
from stock.serializers import StockItemSerializerBrief, LocationSerializer
|
||||
|
||||
from part.models import BomItem
|
||||
@ -120,6 +121,120 @@ class BuildSerializer(InvenTreeModelSerializer):
|
||||
]
|
||||
|
||||
|
||||
class BuildOutputSerializer(serializers.Serializer):
|
||||
"""
|
||||
Serializer for a "BuildOutput"
|
||||
|
||||
Note that a "BuildOutput" is really just a StockItem which is "in production"!
|
||||
"""
|
||||
|
||||
output = serializers.PrimaryKeyRelatedField(
|
||||
queryset=StockItem.objects.all(),
|
||||
many=False,
|
||||
allow_null=False,
|
||||
required=True,
|
||||
label=_('Build Output'),
|
||||
)
|
||||
|
||||
def validate_output(self, output):
|
||||
|
||||
build = self.context['build']
|
||||
|
||||
# The stock item must point to the build
|
||||
if output.build != build:
|
||||
raise ValidationError(_("Build output does not match the parent build"))
|
||||
|
||||
# The part must match!
|
||||
if output.part != build.part:
|
||||
raise ValidationError(_("Output part does not match BuildOrder part"))
|
||||
|
||||
# The build output must be "in production"
|
||||
if not output.is_building:
|
||||
raise ValidationError(_("This build output has already been completed"))
|
||||
|
||||
return output
|
||||
|
||||
class Meta:
|
||||
fields = [
|
||||
'output',
|
||||
]
|
||||
|
||||
|
||||
class BuildCompleteSerializer(serializers.Serializer):
|
||||
"""
|
||||
DRF serializer for completing one or more build outputs
|
||||
"""
|
||||
|
||||
class Meta:
|
||||
fields = [
|
||||
'outputs',
|
||||
'location',
|
||||
'status',
|
||||
'notes',
|
||||
]
|
||||
|
||||
outputs = BuildOutputSerializer(
|
||||
many=True,
|
||||
required=True,
|
||||
)
|
||||
|
||||
location = serializers.PrimaryKeyRelatedField(
|
||||
queryset=StockLocation.objects.all(),
|
||||
required=True,
|
||||
many=False,
|
||||
label=_("Location"),
|
||||
help_text=_("Location for completed build outputs"),
|
||||
)
|
||||
|
||||
status = serializers.ChoiceField(
|
||||
choices=list(StockStatus.items()),
|
||||
default=StockStatus.OK,
|
||||
label=_("Status"),
|
||||
)
|
||||
|
||||
notes = serializers.CharField(
|
||||
label=_("Notes"),
|
||||
required=False,
|
||||
allow_blank=True,
|
||||
)
|
||||
|
||||
def validate(self, data):
|
||||
|
||||
super().validate(data)
|
||||
|
||||
outputs = data.get('outputs', [])
|
||||
|
||||
if len(outputs) == 0:
|
||||
raise ValidationError(_("A list of build outputs must be provided"))
|
||||
|
||||
return data
|
||||
|
||||
def save(self):
|
||||
"""
|
||||
"save" the serializer to complete the build outputs
|
||||
"""
|
||||
|
||||
build = self.context['build']
|
||||
request = self.context['request']
|
||||
|
||||
data = self.validated_data
|
||||
|
||||
outputs = data.get('outputs', [])
|
||||
|
||||
# Mark the specified build outputs as "complete"
|
||||
with transaction.atomic():
|
||||
for item in outputs:
|
||||
|
||||
output = item['output']
|
||||
|
||||
build.complete_build_output(
|
||||
output,
|
||||
request.user,
|
||||
status=data['status'],
|
||||
notes=data.get('notes', '')
|
||||
)
|
||||
|
||||
|
||||
class BuildUnallocationSerializer(serializers.Serializer):
|
||||
"""
|
||||
DRF serializer for unallocating stock from a BuildOrder
|
||||
|
@ -96,11 +96,6 @@ src="{% static 'img/blank_image.png' %}"
|
||||
</div>
|
||||
<!-- Build actions -->
|
||||
{% if roles.build.change %}
|
||||
{% if build.active %}
|
||||
<button id='build-complete' title='{% trans "Complete Build" %}' class='btn btn-success'>
|
||||
<span class='fas fa-paper-plane'></span>
|
||||
</button>
|
||||
{% endif %}
|
||||
<div class='btn-group'>
|
||||
<button id='build-options' title='{% trans "Build actions" %}' class='btn btn-default dropdown-toggle' type='button' data-toggle='dropdown'>
|
||||
<span class='fas fa-tools'></span> <span class='caret'></span>
|
||||
@ -115,6 +110,11 @@ src="{% static 'img/blank_image.png' %}"
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
{% if build.active %}
|
||||
<button id='build-complete' title='{% trans "Complete Build" %}' class='btn btn-success'>
|
||||
<span class='fas fa-check-circle'></span>
|
||||
</button>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
@ -319,11 +319,11 @@ class BuildTest(TestCase):
|
||||
self.assertTrue(self.build.isFullyAllocated(self.output_1))
|
||||
self.assertTrue(self.build.isFullyAllocated(self.output_2))
|
||||
|
||||
self.build.completeBuildOutput(self.output_1, None)
|
||||
self.build.complete_build_output(self.output_1, None)
|
||||
|
||||
self.assertFalse(self.build.can_complete)
|
||||
|
||||
self.build.completeBuildOutput(self.output_2, None)
|
||||
self.build.complete_build_output(self.output_2, None)
|
||||
|
||||
self.assertTrue(self.build.can_complete)
|
||||
|
||||
|
@ -434,7 +434,7 @@ class BuildOutputComplete(AjaxUpdateView):
|
||||
stock_status = StockStatus.OK
|
||||
|
||||
# Complete the build output
|
||||
build.completeBuildOutput(
|
||||
build.complete_build_output(
|
||||
output,
|
||||
self.request.user,
|
||||
location=location,
|
||||
|
@ -8,6 +8,7 @@ from __future__ import unicode_literals
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.conf.urls import url, include
|
||||
from django.db.models import Q, F
|
||||
from django.shortcuts import get_object_or_404
|
||||
|
||||
from django_filters import rest_framework as rest_filters
|
||||
from rest_framework import generics
|
||||
@ -232,25 +233,11 @@ class POReceive(generics.CreateAPIView):
|
||||
context = super().get_serializer_context()
|
||||
|
||||
# Pass the purchase order through to the serializer for validation
|
||||
context['order'] = self.get_order()
|
||||
context['order'] = get_object_or_404(PurchaseOrder, pk=self.kwargs.get('pk', None))
|
||||
context['request'] = self.request
|
||||
|
||||
return context
|
||||
|
||||
def get_order(self):
|
||||
"""
|
||||
Returns the PurchaseOrder associated with this API endpoint
|
||||
"""
|
||||
|
||||
pk = self.kwargs.get('pk', None)
|
||||
|
||||
try:
|
||||
order = PurchaseOrder.objects.get(pk=pk)
|
||||
except (PurchaseOrder.DoesNotExist, ValueError):
|
||||
raise ValidationError(_("Matching purchase order does not exist"))
|
||||
|
||||
return order
|
||||
|
||||
|
||||
class POLineItemFilter(rest_filters.FilterSet):
|
||||
"""
|
||||
|
@ -151,7 +151,7 @@ function makeBuildOutputActionButtons(output, buildInfo, lines) {
|
||||
|
||||
// Add a button to "complete" the particular build output
|
||||
html += makeIconButton(
|
||||
'fa-check icon-green', 'button-output-complete', outputId,
|
||||
'fa-check-circle icon-green', 'button-output-complete', outputId,
|
||||
'{% trans "Complete build output" %}',
|
||||
{
|
||||
// disabled: true
|
||||
|
Loading…
Reference in New Issue
Block a user