From 661fbf0e3dbdf6444d3d25b02d68ad229925d87c Mon Sep 17 00:00:00 2001
From: Oliver Walters <oliver.henry.walters@gmail.com>
Date: Thu, 14 Jul 2022 20:10:56 +1000
Subject: [PATCH] Add fix for stock migration

- Ensure the serial number is not too large when performing migration
- Add unit test for data migration
---
 .../migrations/0069_auto_20211109_2347.py     |  3 +
 InvenTree/stock/test_migrations.py            | 69 +++++++++++++++++++
 2 files changed, 72 insertions(+)
 create mode 100644 InvenTree/stock/test_migrations.py

diff --git a/InvenTree/stock/migrations/0069_auto_20211109_2347.py b/InvenTree/stock/migrations/0069_auto_20211109_2347.py
index e4e8128f1c..2691b6305e 100644
--- a/InvenTree/stock/migrations/0069_auto_20211109_2347.py
+++ b/InvenTree/stock/migrations/0069_auto_20211109_2347.py
@@ -28,6 +28,9 @@ def update_serials(apps, schema_editor):
             except Exception:
                 serial = 0
 
+        # Ensure the integer value is not too large for the database field
+        if serial > 0x7fffffff:
+            serial = 0x7fffffff
 
         item.serial_int = serial
         item.save()
diff --git a/InvenTree/stock/test_migrations.py b/InvenTree/stock/test_migrations.py
new file mode 100644
index 0000000000..07fa9f2fc7
--- /dev/null
+++ b/InvenTree/stock/test_migrations.py
@@ -0,0 +1,69 @@
+"""Unit tests for data migrations in the 'stock' app"""
+
+from django_test_migrations.contrib.unittest_case import MigratorTestCase
+
+from InvenTree import helpers
+
+
+class TestSerialNumberMigration(MigratorTestCase):
+    """Test data migration which updates serial numbers"""
+
+    migrate_from = ('stock', '0067_alter_stockitem_part')
+    migrate_to = ('stock', helpers.getNewestMigrationFile('stock'))
+
+    def prepare(self):
+        """Create initial data for this migration"""
+
+        Part = self.old_state.apps.get_model('part', 'part')
+        StockItem = self.old_state.apps.get_model('stock', 'stockitem')
+
+        # Create a base part
+        my_part = Part.objects.create(
+            name='PART-123',
+            description='Some part',
+            active=True,
+            trackable=True,
+            level=0,
+            tree_id=0,
+            lft=0, rght=0
+        )
+
+        # Create some serialized stock items
+        for sn in range(10, 20):
+            StockItem.objects.create(
+                part=my_part,
+                quantity=1,
+                serial=sn,
+                level=0,
+                tree_id=0,
+                lft=0, rght=0
+            )
+
+        # Create a stock item with a very large serial number
+        item = StockItem.objects.create(
+            part=my_part,
+            quantity=1,
+            serial='9999999999999999999999999999999999999999999999999999999999999',
+            level=0,
+            tree_id=0,
+            lft=0, rght=0
+        )
+
+        self.big_ref_pk = item.pk
+
+    def test_migrations(self):
+        """Test that the migrations have been applied correctly"""
+
+        StockItem = self.new_state.apps.get_model('stock', 'stockitem')
+
+        # Check that the serial number integer conversion has been applied correctly
+        for sn in range(10, 20):
+            item = StockItem.objects.get(serial_int=sn)
+
+            self.assertEqual(item.serial, str(sn))
+
+        big_ref_item = StockItem.objects.get(pk=self.big_ref_pk)
+
+        # Check that the StockItem maximum serial number
+        self.assertEqual(big_ref_item.serial, '9999999999999999999999999999999999999999999999999999999999999')
+        self.assertEqual(big_ref_item.serial_int, 0x7fffffff)