diff --git a/InvenTree/build/api.py b/InvenTree/build/api.py
index 7920003d8b..ddedd60ce6 100644
--- a/InvenTree/build/api.py
+++ b/InvenTree/build/api.py
@@ -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'),
     ])),
diff --git a/InvenTree/build/models.py b/InvenTree/build/models.py
index 449776579e..994d060585 100644
--- a/InvenTree/build/models.py
+++ b/InvenTree/build/models.py
@@ -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):
diff --git a/InvenTree/build/serializers.py b/InvenTree/build/serializers.py
index 547f565905..cf7c8065fe 100644
--- a/InvenTree/build/serializers.py
+++ b/InvenTree/build/serializers.py
@@ -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
diff --git a/InvenTree/build/templates/build/build_base.html b/InvenTree/build/templates/build/build_base.html
index e3119e6fdb..5f53fa36bb 100644
--- a/InvenTree/build/templates/build/build_base.html
+++ b/InvenTree/build/templates/build/build_base.html
@@ -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 %}
diff --git a/InvenTree/build/test_build.py b/InvenTree/build/test_build.py
index a0874d0979..31e4cf1822 100644
--- a/InvenTree/build/test_build.py
+++ b/InvenTree/build/test_build.py
@@ -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)
 
diff --git a/InvenTree/build/views.py b/InvenTree/build/views.py
index 8c63c1296c..37ec567f1a 100644
--- a/InvenTree/build/views.py
+++ b/InvenTree/build/views.py
@@ -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,
diff --git a/InvenTree/order/api.py b/InvenTree/order/api.py
index af30a3a5c5..0a540c3412 100644
--- a/InvenTree/order/api.py
+++ b/InvenTree/order/api.py
@@ -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):
     """
diff --git a/InvenTree/templates/js/translated/build.js b/InvenTree/templates/js/translated/build.js
index d3499deedf..686e8e1bfa 100644
--- a/InvenTree/templates/js/translated/build.js
+++ b/InvenTree/templates/js/translated/build.js
@@ -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