From 22a8e82108b633e4531dd150a2c185bd6b34b1fb Mon Sep 17 00:00:00 2001
From: Oliver Walters <oliver.henry.walters@gmail.com>
Date: Thu, 11 Jun 2020 12:21:33 +1000
Subject: [PATCH] Add endpoint for assigning a particular barcode to a
 StockItem

---
 .../static/script/inventree/barcode.js        |  24 ++++
 InvenTree/barcode/api.py                      | 107 ++++++++++++++++++
 InvenTree/barcode/barcode.py                  |   6 +
 3 files changed, 137 insertions(+)

diff --git a/InvenTree/InvenTree/static/script/inventree/barcode.js b/InvenTree/InvenTree/static/script/inventree/barcode.js
index 77ccf17c6e..77c344f778 100644
--- a/InvenTree/InvenTree/static/script/inventree/barcode.js
+++ b/InvenTree/InvenTree/static/script/inventree/barcode.js
@@ -19,4 +19,28 @@ function scanBarcode(barcode, options={}) {
             },
         }
     );
+}
+
+
+/*
+ * Associate barcode data with a StockItem
+ */
+function associateBarcode(barcode, stockitem, options={}) {
+
+    console.log('Associating barcode data:');
+    console.log('barcode: ' + barcode);
+
+    inventreePut(
+        '/api/barcode/assign/',
+        {
+            'barcode': barcode,
+            'stockitem': stockitem,
+        },
+        {
+            method: 'POST',
+            success: function(response, status) {
+                console.log(response);
+            },
+        }
+    );
 }
\ No newline at end of file
diff --git a/InvenTree/barcode/api.py b/InvenTree/barcode/api.py
index bc89b395dd..e53b789794 100644
--- a/InvenTree/barcode/api.py
+++ b/InvenTree/barcode/api.py
@@ -125,7 +125,114 @@ class BarcodeScan(APIView):
 
         return Response(response)
 
+
+class BarcodeAssign(APIView):
+    """
+    Endpoint for assigning a barcode to a stock item.
+    
+    - This only works if the barcode is not already associated with an object in the database
+    - If the barcode does not match an object, then the barcode hash is assigned to the StockItem
+    """
+
+    permission_classes = [
+        permissions.IsAuthenticated
+    ]
+
+    def post(self, request, *args, **kwargs):
+
+        data = request.data
+
+        if 'barcode' not in data:
+            raise ValidationError({'barcode': 'Must provide barcode_data parameter'})
+
+        if 'stockitem' not in data:
+            raise ValidationError({'stockitem': 'Must provide stockitem parameter'})
+
+        barcode_data = data['barcode']
+
+        try:
+            item = StockItem.objects.get(pk=data['stockitem'])
+        except (ValueError, StockItem.DoesNotExist):
+            raise ValidationError({'stockitem': 'No matching stock item found'})
+
+        plugins = load_barcode_plugins()
+
+        plugin = None
+
+        for plugin_class in plugins:
+            plugin_instance = plugin_class(barcode_data)
+
+            if plugin_instance.validate():
+                plugin = plugin_instance
+                break
+
+        match_found = False
+
+        response = {}
+
+        response['barcode_data'] = barcode_data
+
+        # Matching plugin was found
+        if plugin is not None:
+            
+            hash = plugin.hash()
+            response['hash'] = hash
+            response['plugin'] = plugin.name
+
+            # Ensure that the barcode does not already match a database entry
+
+            if plugin.getStockItem() is not None:
+                match_found = True
+                response['error'] = 'Barcode already matches StockItem object'
+
+            if plugin.getStockLocation() is not None:
+                match_found = True
+                response['error'] = 'Barcode already matches StockLocation object'
+
+            if plugin.getPart() is not None:
+                match_found = True
+                response['error'] = 'Barcode already matches Part object' 
+
+            if not match_found:
+                # Try to associate by hash
+                try:
+                    item = StockItem.objects.get(uid=hash)
+                    response['error'] = 'Barcode hash already matches StockItem object'
+                    match_found = True
+                except StockItem.DoesNotExist:
+                    pass
+
+        else:
+            hash = hash_barcode(barcode_data)
+
+            response['hash'] = hash
+            response['plugin'] = None
+
+            # Lookup stock item by hash
+            try:
+                item = StockItem.objects.get(uid=hash)
+                response['error'] = 'Barcode hash already matches StockItem object'
+                match_found = True
+            except StockItem.DoesNotExist:
+                pass
+
+        if not match_found:
+            response['success'] = 'Barcode associated with StockItem'
+
+            # Save the barcode hash
+            item.uid = response['hash']
+            item.save()
+
+            serializer = StockItemSerializer(item, part_detail=True, location_detail=True, supplier_part_detail=True)
+            response['stockitem'] = serializer.data
+
+
+        return Response(response)
+
+
 barcode_api_urls = [
+
+    url(r'^assign/$', BarcodeAssign.as_view(), name='api-barcode-assign'),
     
     # Catch-all performs barcode 'scan'
     url(r'^.*$', BarcodeScan.as_view(), name='api-barcode-plugin'),
diff --git a/InvenTree/barcode/barcode.py b/InvenTree/barcode/barcode.py
index f4d6c368b4..b871198038 100644
--- a/InvenTree/barcode/barcode.py
+++ b/InvenTree/barcode/barcode.py
@@ -33,6 +33,12 @@ class BarcodePlugin:
         return self.PLUGIN_NAME
 
     def __init__(self, barcode_data):
+        """
+        Initialize the BarcodePlugin instance
+
+        Args:
+            barcode_data - The raw barcode data
+        """
 
         self.data = barcode_data