diff --git a/InvenTree/static/script/inventree/stock.js b/InvenTree/static/script/inventree/stock.js
index 44f563aa51..ddb20ad1f1 100644
--- a/InvenTree/static/script/inventree/stock.js
+++ b/InvenTree/static/script/inventree/stock.js
@@ -79,7 +79,7 @@ function updateStock(items, options={}) {
             html += "max='" + vMax + "' ";
         }
 
-        html += "type='number' id='q-" + item.pk + "'/></td>";
+        html += "type='number' id='q-update-" + item.pk + "'/></td>";
 
         html += '</tr>';
     }
@@ -128,7 +128,7 @@ function updateStock(items, options={}) {
         for (idx = 0; idx < items.length; idx++) {
             var item = items[idx];
 
-            var q = $(modal).find("#q-" + item.pk).val();
+            var q = $(modal).find("#q-update-" + item.pk).val();
 
             stocktake.push({
                 pk: item.pk,
@@ -229,7 +229,7 @@ function moveStockItems(items, options) {
         inventreePut("/api/stock/move/",
             {
                 location: location,
-                'parts[]': parts,
+                'stock': parts,
                 'notes': notes,
             },
             {
@@ -246,7 +246,6 @@ function moveStockItems(items, options) {
     getStockLocations({},
     {
         success: function(response) {
-            
 
             // Extact part row info
             var parts = [];
@@ -267,21 +266,42 @@ function moveStockItems(items, options) {
 
             html += "<p class='warning-msg' id='note-warning'><i>Note field must be filled</i></p>";
 
-            html += "<hr>The following stock items will be moved:<br><ul class='list-group'>\n";
+            html += "<hr>The following stock items will be moved:<hr>";
+
+            html += `
+                <table class='table table-striped table-condensed'>
+                <tr>
+                    <th>Part</th>
+                    <th>Location</th>
+                    <th>Available</th>
+                    <th>Moving</th>
+                </tr>
+                `;
 
             for (i = 0; i < items.length; i++) {
-                parts.push(items[i].pk);
+                
+                parts.push({
+                    pk: items[i].pk,
+                    quantity: items[i].quantity,
+                });
 
-                html += "<li class='list-group-item'>" + items[i].quantity + " &times " + items[i].part.name;
+                var item = items[i];
 
-                if (items[i].location) {
-                    html += " (" + items[i].location.name + ")";
-                }
+                html += "<tr>";
 
-                html += "</li>\n";
+                html += "<td>" + item.part.name + "</td>";
+                html += "<td>" + item.location.pathstring + "</td>";
+                html += "<td>" + item.quantity + "</td>";
+
+                html += "<td>";
+                html += "<input class='form-control' min='0' max='" + item.quantity + "'";
+                html += " value='" + item.quantity + "'";
+                html += "type='number' id='q-move-" + item.pk + "'/></td>";
+
+                html += "</tr>";
             }
 
-            html += "</ul>\n";
+            html += "</table>";
 
             openModal({
                 modal: modal,
@@ -307,6 +327,15 @@ function moveStockItems(items, options) {
                     return false;
                 }
 
+                // Update the quantity for each item
+                for (var ii = 0; ii < parts.length; ii++) {
+                    var pk = parts[ii].pk;
+
+                    var q = $(modal).find('#q-move-' + pk).val();
+
+                    parts[ii].quantity = q;
+                }
+
                 doMove(locId, parts, notes);
             });
         },
diff --git a/InvenTree/stock/api.py b/InvenTree/stock/api.py
index 070e7657a6..0bd048087e 100644
--- a/InvenTree/stock/api.py
+++ b/InvenTree/stock/api.py
@@ -151,46 +151,50 @@ class StockMove(APIView):
 
         data = request.data
 
-        if u'location' not in data:
+        if 'location' not in data:
             raise ValidationError({'location': 'Destination must be specified'})
 
-        loc_id = data.get(u'location')
+        try:
+            loc_id = int(data.get('location'))
+        except ValueError:
+            raise ValidationError({'location': 'Integer ID required'})
 
         try:
             location = StockLocation.objects.get(pk=loc_id)
         except StockLocation.DoesNotExist:
             raise ValidationError({'location': 'Location does not exist'})
 
-        if u'parts[]' not in data:
-            raise ValidationError({'parts[]': 'Parts list must be specified'})
+        if 'stock' not in data:
+            raise ValidationError({'stock': 'Stock list must be specified'})
+        
+        stock_list = data.get('stock')
 
-        part_list = data.get(u'parts[]')
+        if type(stock_list) is not list:
+            raise ValidationError({'stock': 'Stock must be supplied as a list'})
 
-        parts = []
+        if 'notes' not in data:
+            raise ValidationError({'notes': 'Notes field must be supplied'})
 
-        errors = []
-
-        if u'notes' not in data:
-            errors.append({'notes': 'Notes field must be supplied'})
-
-        for pid in part_list:
+        for item in stock_list:
             try:
-                part = StockItem.objects.get(pk=pid)
-                parts.append(part)
+                stock_id = int(item['pk'])
+                quantity = int(item['quantity'])
+            except ValueError:
+                # Ignore this one
+                continue
+
+            # Ignore a zero quantity movement
+            if quantity <= 0:
+                continue
+
+            try:
+                stock = StockItem.objects.get(pk=stock_id)
             except StockItem.DoesNotExist:
-                errors.append({'part': 'Part {id} does not exist'.format(id=pid)})
+                continue
 
-        if len(errors) > 0:
-            raise ValidationError(errors)
+            stock.move(location, data.get('notes'), request.user, quantity=quantity)
 
-        n = 0
-
-        for part in parts:
-            if part.move(location, data.get('notes'), request.user):
-                n += 1
-
-        return Response({'success': 'Moved {n} parts to {loc}'.format(
-            n=n,
+        return Response({'success': 'Moved parts to {loc}'.format(
             loc=str(location)
         )})
 
diff --git a/InvenTree/stock/models.py b/InvenTree/stock/models.py
index b4216c46c1..f24516bfcb 100644
--- a/InvenTree/stock/models.py
+++ b/InvenTree/stock/models.py
@@ -281,7 +281,64 @@ class StockItem(models.Model):
         track.save()
 
     @transaction.atomic
-    def move(self, location, notes, user):
+    def splitStock(self, quantity, user):
+        """ Split this stock item into two items, in the same location.
+        Stock tracking notes for this StockItem will be duplicated,
+        and added to the new StockItem.
+
+        Args:
+            quantity: Number of stock items to remove from this entity, and pass to the next
+
+        Notes:
+            The provided quantity will be subtracted from this item and given to the new one.
+            The new item will have a different StockItem ID, while this will remain the same.
+        """
+
+        # Doesn't make sense for a zero quantity
+        if quantity <= 0:
+            return
+
+        # Also doesn't make sense to split the full amount
+        if quantity >= self.quantity:
+            return
+
+        # Create a new StockItem object, duplicating relevant fields
+        new_stock = StockItem.objects.create(
+            part=self.part,
+            quantity=quantity,
+            supplier_part=self.supplier_part,
+            location=self.location,
+            batch=self.batch,
+            delete_on_deplete=self.delete_on_deplete
+        )
+
+        new_stock.save()
+
+        # Add a new tracking item for the new stock item
+        new_stock.addTransactionNote(
+            "Split from existing stock",
+            user,
+            "Split {n} from existing stock item".format(n=quantity))
+
+        # Remove the specified quantity from THIS stock item
+        self.take_stock(quantity, user, 'Split {n} items into new stock item'.format(n=quantity))
+
+    @transaction.atomic
+    def move(self, location, notes, user, **kwargs):
+        """ Move part to a new location.
+
+        Args:
+            location: Destination location (cannot be null)
+            notes: User notes
+            user: Who is performing the move
+            kwargs:
+                quantity: If provided, override the quantity (default = total stock quantity)
+        """
+
+        quantity = int(kwargs.get('quantity', self.quantity))
+
+        if quantity <= 0:
+            return False
 
         if location is None:
             # TODO - Raise appropriate error (cannot move to blank location)
@@ -290,6 +347,13 @@ class StockItem(models.Model):
             # TODO - Raise appropriate error (cannot move to same location)
             return False
 
+        # Test for a partial movement
+        if quantity < self.quantity:
+            # We need to split the stock!
+
+            # Leave behind certain quantity
+            self.splitStock(self.quantity - quantity, user)
+
         msg = "Moved to {loc}".format(loc=str(location))
 
         if self.location: