From ce5b47460a79c132235b4d9a722dac24095f4d47 Mon Sep 17 00:00:00 2001 From: Oliver Date: Mon, 25 Oct 2021 22:35:27 +1100 Subject: [PATCH] Added data migration for existing SalesOrder instances - If a SalesOrder is "PENDING" or there are allocations available, a shipment is created --- .../migrations/0053_salesordershipment.py | 1 - .../migrations/0055_auto_20211025_0645.py | 89 +++++++++++++++++++ InvenTree/order/models.py | 7 -- InvenTree/order/test_migrations.py | 47 ++++++++++ InvenTree/templates/js/translated/order.js | 1 + 5 files changed, 137 insertions(+), 8 deletions(-) create mode 100644 InvenTree/order/migrations/0055_auto_20211025_0645.py diff --git a/InvenTree/order/migrations/0053_salesordershipment.py b/InvenTree/order/migrations/0053_salesordershipment.py index c981e9c4c3..e20a95e3f8 100644 --- a/InvenTree/order/migrations/0053_salesordershipment.py +++ b/InvenTree/order/migrations/0053_salesordershipment.py @@ -18,7 +18,6 @@ class Migration(migrations.Migration): name='SalesOrderShipment', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('status', models.PositiveIntegerField(choices=[(10, 'Pending'), (20, 'Shipped'), (40, 'Cancelled'), (50, 'Lost'), (60, 'Returned')], default=10, help_text='Shipment status', verbose_name='Status')), ('shipment_date', models.DateField(blank=True, help_text='Date of shipment', null=True, verbose_name='Shipment Date')), ('reference', models.CharField(blank=True, help_text='Shipment reference', max_length=100, verbose_name='Reference')), ('notes', markdownx.models.MarkdownxField(blank=True, help_text='Shipment notes', verbose_name='Notes')), diff --git a/InvenTree/order/migrations/0055_auto_20211025_0645.py b/InvenTree/order/migrations/0055_auto_20211025_0645.py new file mode 100644 index 0000000000..13ea184cfe --- /dev/null +++ b/InvenTree/order/migrations/0055_auto_20211025_0645.py @@ -0,0 +1,89 @@ +# Generated by Django 3.2.5 on 2021-10-25 06:45 + +from django.db import migrations + + +from InvenTree.status_codes import SalesOrderStatus + + +def add_shipment(apps, schema_editor): + """ + Create a SalesOrderShipment for each existing SalesOrder instance. + + Any "allocations" are marked against that shipment. + + For each existing SalesOrder instance, we create a default SalesOrderShipment, + and associate each SalesOrderAllocation with this shipment + """ + + Allocation = apps.get_model('order', 'salesorderallocation') + SalesOrder = apps.get_model('order', 'salesorder') + Shipment = apps.get_model('order', 'salesordershipment') + + n = 0 + + for order in SalesOrder.objects.all(): + + """ + We only create an automatic shipment for "PENDING" orders, + as SalesOrderAllocations were historically deleted for "SHIPPED" or "CANCELLED" orders + """ + + allocations = Allocation.objects.filter( + line__order=order + ) + + if allocations.count() == 0 and order.status != SalesOrderStatus.PENDING: + continue + + # Create a new Shipment instance against this order + shipment = Shipment.objects.create( + order=order, + ) + + shipment.save() + + # Iterate through each allocation associated with this order + for allocation in allocations: + allocation.shipment = shipment + allocation.save() + + n += 1 + + if n > 0: + print(f"\nCreated SalesOrderShipment for {n} SalesOrder instances") + + +def reverse_add_shipment(apps, schema_editor): + """ + Reverse the migration, delete and SalesOrderShipment instances + """ + + Allocation = apps.get_model('order', 'salesorderallocation') + + # First, ensure that all SalesOrderAllocation objects point to a null shipment + for allocation in Allocation.objects.exclude(shipment=None): + allocation.shipment = None + allocation.save() + + SOS = apps.get_model('order', 'salesordershipment') + + n = SOS.objects.count() + + print(f"Deleting {n} SalesOrderShipment instances") + + SOS.objects.all().delete() + + +class Migration(migrations.Migration): + + dependencies = [ + ('order', '0054_salesorderallocation_shipment'), + ] + + operations = [ + migrations.RunPython( + add_shipment, + reverse_code=reverse_add_shipment, + ) + ] diff --git a/InvenTree/order/models.py b/InvenTree/order/models.py index 0566ec9312..dcb63430a3 100644 --- a/InvenTree/order/models.py +++ b/InvenTree/order/models.py @@ -929,13 +929,6 @@ class SalesOrderShipment(models.Model): help_text=_('Sales Order'), ) - status = models.PositiveIntegerField( - default=SalesOrderStatus.PENDING, - choices=SalesOrderStatus.items(), - verbose_name=_('Status'), - help_text=_('Shipment status'), - ) - shipment_date = models.DateField( null=True, blank=True, verbose_name=_('Shipment Date'), diff --git a/InvenTree/order/test_migrations.py b/InvenTree/order/test_migrations.py index b7db1f1b70..6e4d6668d3 100644 --- a/InvenTree/order/test_migrations.py +++ b/InvenTree/order/test_migrations.py @@ -5,6 +5,7 @@ Unit tests for the 'order' model data migrations from django_test_migrations.contrib.unittest_case import MigratorTestCase from InvenTree import helpers +from InvenTree.status_codes import SalesOrderStatus class TestForwardMigrations(MigratorTestCase): @@ -57,3 +58,49 @@ class TestForwardMigrations(MigratorTestCase): # The integer reference field must have been correctly updated self.assertEqual(order.reference_int, ii) + + +class TestShipmentMigration(MigratorTestCase): + """ + Test data migration for the "SalesOrderShipment" model + """ + + migrate_from = ('order', '0051_auto_20211014_0623') + migrate_to = ('order', '0055_auto_20211025_0645') + + def prepare(self): + """ + Create an initial SalesOrder + """ + + Company = self.old_state.apps.get_model('company', 'company') + + customer = Company.objects.create( + name='My customer', + description='A customer we sell stuff too', + is_customer=True + ) + + SalesOrder = self.old_state.apps.get_model('order', 'salesorder') + + for ii in range(5): + order = SalesOrder.objects.create( + reference=f'SO{ii}', + customer=customer, + description='A sales order for stuffs', + status=SalesOrderStatus.PENDING, + ) + + order.save() + + def test_shipment_creation(self): + """ + Check that a SalesOrderShipment has been created + """ + + SalesOrder = self.new_state.apps.get_model('order', 'salesorder') + Shipment = self.new_state.apps.get_model('order', 'salesordershipment') + + # Check that the correct number of Shipments have been created + self.assertEqual(SalesOrder.objects.count(), 5) + self.assertEqual(Shipment.objects.count(), 5) diff --git a/InvenTree/templates/js/translated/order.js b/InvenTree/templates/js/translated/order.js index 75f71e3c4c..e55371703c 100644 --- a/InvenTree/templates/js/translated/order.js +++ b/InvenTree/templates/js/translated/order.js @@ -1681,6 +1681,7 @@ function loadSalesOrderLineItemTable(table, options={}) { location_detail: true, in_stock: true, part: line_item.part, + include_variants: false, exclude_so_allocation: options.order, }, auto_fill: true,