From c89fe44feae7e349ea1577065573b1c4280502fc Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 27 Jan 2025 22:54:12 +1100 Subject: [PATCH] SalesOrder migration unit test (#8814) (#8961) * Unit test for SalesOrder data migration * make field checks more stable * Adjust migration strategy * Fix for data migration * Simplify login test for playwright --------- Co-authored-by: Matthias Mair (cherry picked from commit a13f5681a1a65eec9a59892cd53fc6d929703996) Co-authored-by: Oliver --- .../InvenTree/generic/states/fields.py | 4 +- .../migrations/0105_auto_20241128_0431.py | 30 +++++++++--- .../InvenTree/order/test_migrations.py | 49 +++++++++++++++++++ src/frontend/tests/pui_login.spec.ts | 17 ------- 4 files changed, 74 insertions(+), 26 deletions(-) diff --git a/src/backend/InvenTree/generic/states/fields.py b/src/backend/InvenTree/generic/states/fields.py index 7f67f5c14b50..1822ffd725d3 100644 --- a/src/backend/InvenTree/generic/states/fields.py +++ b/src/backend/InvenTree/generic/states/fields.py @@ -100,7 +100,9 @@ def contribute_to_class(self, cls, name): """Add the _custom_key field to the model.""" cls._meta.supports_custom_status = True - if not hasattr(self, '_custom_key_field'): + if not hasattr(self, '_custom_key_field') and not hasattr( + cls, f'{name}_custom_key' + ): self.add_field(cls, name) super().contribute_to_class(cls, name) diff --git a/src/backend/InvenTree/order/migrations/0105_auto_20241128_0431.py b/src/backend/InvenTree/order/migrations/0105_auto_20241128_0431.py index a39cef41ad92..6e293a3e9a97 100644 --- a/src/backend/InvenTree/order/migrations/0105_auto_20241128_0431.py +++ b/src/backend/InvenTree/order/migrations/0105_auto_20241128_0431.py @@ -1,6 +1,6 @@ # Generated by Django 4.2.16 on 2024-11-28 04:31 -from django.db import migrations +from django.db import migrations, connection def update_shipment_date(apps, schema_editor): @@ -18,9 +18,15 @@ def update_shipment_date(apps, schema_editor): shipment_date__isnull=True ) - updated_orders = 0 + update_count = 0 + + cursor = connection.cursor() for order in orders: + + # Check that the shipment date is actually null here + assert order.shipment_date is None, f"SalesOrder {order.pk} has non-null shipment_date" + # Find the latest shipment date for any associated allocations shipments = order.shipments.filter(shipment_date__isnull=False) latest_shipment = shipments.order_by('-shipment_date').first() @@ -29,13 +35,21 @@ def update_shipment_date(apps, schema_editor): continue # Update the order with the new shipment date - order.shipment_date = latest_shipment.shipment_date - order.save() + shipment_date = latest_shipment.shipment_date + + # Raw SQL to prevent some weird migration "order of operations" issues + # Reference: https://github.com/inventree/InvenTree/pull/8814 + query = f"UPDATE order_salesorder SET shipment_date = '{shipment_date}' WHERE id = {order.pk}" + cursor.execute(query) + + # Fetch the updated object, check that the shipment date has been updated + order.refresh_from_db() + assert order.shipment_date is not None, f"SalesOrder {order.pk} still has missing shipment_date" + + update_count += 1 - updated_orders += 1 - - if updated_orders > 0: - print(f"Updated {updated_orders} SalesOrder objects with missing shipment_date") + if update_count > 0: + print(f"Updated {update_count} SalesOrder shipment dates") class Migration(migrations.Migration): diff --git a/src/backend/InvenTree/order/test_migrations.py b/src/backend/InvenTree/order/test_migrations.py index 20ac6a116be6..d79e80559648 100644 --- a/src/backend/InvenTree/order/test_migrations.py +++ b/src/backend/InvenTree/order/test_migrations.py @@ -198,3 +198,52 @@ def test_po_migration(self): # so = SalesOrder.objects.get(reference=f"{ii}-xyz") # self.assertEqual(so.extra_lines, 1) # self.assertEqual(so.lines.count(), 1) + + +class TestShipmentDateMigration(MigratorTestCase): + """Test data migration which fixes empty 'shipment date' on SalesOrder model. + + Ref: 0105_auto_20241128_0431.py + """ + + migrate_from = ('order', '0100_remove_returnorderattachment_order_and_more') + migrate_to = ('order', '0105_auto_20241128_0431') + + def prepare(self): + """Create initial SalesOrder dataset.""" + Company = self.old_state.apps.get_model('company', 'company') + SalesOrder = self.old_state.apps.get_model('order', 'salesorder') + SalesOrderShipment = self.old_state.apps.get_model( + 'order', 'salesordershipment' + ) + + # Create a customer + customer = Company.objects.create( + name='Customer A', + description='A great customer!', + is_customer=True, + is_supplier=False, + ) + + # Create a SalesOrder (Completed, but missing shipment date) + order = SalesOrder.objects.create( + customer=customer, + reference='SO-999', + description='A test sales order', + shipment_date=None, + status=SalesOrderStatus.COMPLETE, + ) + + # Add a shipment + SalesOrderShipment.objects.create(order=order, shipment_date='2024-11-28') + + self.assertEqual(order.shipments.count(), 1) + self.assertIsNone(order.shipment_date) + + def test_migration(self): + """Test that the migration has correctly updated the SalesOrder objects.""" + SalesOrder = self.new_state.apps.get_model('order', 'salesorder') + + order = SalesOrder.objects.get(reference='SO-999') + self.assertIsNotNone(order.shipment_date) + self.assertEqual(order.shipment_date.isoformat(), '2024-11-28') diff --git a/src/frontend/tests/pui_login.spec.ts b/src/frontend/tests/pui_login.spec.ts index 90331a9ef51a..64d2d543af01 100644 --- a/src/frontend/tests/pui_login.spec.ts +++ b/src/frontend/tests/pui_login.spec.ts @@ -8,23 +8,6 @@ test('Login - Basic Test', async ({ page }) => { // Check that the username is provided await page.getByText(user.username); - await expect(page).toHaveTitle(/^InvenTree/); - - // Go to the dashboard - await page.goto(baseUrl); - await page.waitForURL('**/platform'); - - await page.getByText('InvenTree Demo Server -').waitFor(); - - // Check that the username is provided - await page.getByText(user.username); - - await expect(page).toHaveTitle(/^InvenTree/); - - // Go to the dashboard - await page.goto(baseUrl); - await page.waitForURL('**/platform'); - // Logout (via menu) await page.getByRole('button', { name: 'Ally Access' }).click(); await page.getByRole('menuitem', { name: 'Logout' }).click();