Coverage for src/backend/InvenTree/stock/migrations/0121_auto_20260526_0849.py: 33%
40 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-07 17:47 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-07 17:47 +0000
1# Generated by Django 5.2.14 on 2026-05-26 08:49
3import datetime
4from tqdm import tqdm
6from django.db import migrations
9def set_creation_date(apps, schema_editor):
10 """Set the creation_date field for existing StockItem entries."""
12 StockItem = apps.get_model('stock', 'StockItem')
13 StockItemTracking = apps.get_model('stock', 'StockItemTracking')
15 # First pass - find all StockHistoryCode.CREATED entries
16 # Note: As of 2026-05-26, the relevant status codes were:
17 # CREATED = 1
19 creation_entries = StockItemTracking.objects.filter(tracking_type=1)
21 items_to_update = []
23 def process_item(item):
24 """Process a StockItem entry for update.
26 As we are iterating over a potentially large number of StockItem entries,
27 we periodically write updates to the database in batches to avoid memory issues.
28 """
29 nonlocal items_to_update
31 items_to_update.append(item)
33 if len(items_to_update) >= 250:
34 StockItem.objects.bulk_update(items_to_update, ['creation_date'])
35 items_to_update = []
37 if creation_entries.count() > 0: 37 ↛ 38line 37 didn't jump to line 38 because the condition on line 37 was never true
38 progress = tqdm(total=creation_entries.count(), desc='stock.0121: Setting creation_date for StockItem entries')
41 for entry in creation_entries:
42 item = StockItem.objects.filter(pk=entry.item_id).first()
43 if item and item.creation_date is None:
44 item.creation_date = entry.date
45 process_item(item)
47 progress.update(1)
49 progress.close()
51 # Next: Find any StockItem entries that have a null creation_date
52 items = StockItem.objects.filter(creation_date__isnull=True)
54 if items.count() > 0: 54 ↛ 56line 54 didn't jump to line 56 because the condition on line 54 was never true
56 progress = tqdm(total=items.count(), desc='stock.0121: Setting creation_date for remaining StockItem entries')
58 for item in items:
59 # Source the creation_date from the stock item
60 earliest_entry = StockItemTracking.objects.filter(item_id=item.pk).order_by('date').first()
62 # Gather potential datetime sources for the creation_date field
63 utc = datetime.timezone.utc
65 def make_aware(dt):
66 """Ensure a datetime is UTC-aware for safe cross-type comparison."""
67 return dt.replace(tzinfo=utc) if dt.tzinfo is None else dt.astimezone(utc)
69 raw_options = [
70 item.updated,
71 datetime.datetime(item.stocktake_date.year, item.stocktake_date.month, item.stocktake_date.day, tzinfo=utc) if item.stocktake_date else None,
72 earliest_entry.date if earliest_entry else None,
73 ]
74 date_options = [make_aware(d) for d in raw_options if d is not None]
76 if date_options:
77 item.creation_date = min(date_options)
78 process_item(item)
80 progress.update(1)
82 # Update the remaining items
83 StockItem.objects.bulk_update(items_to_update, ['creation_date'], batch_size=250)
86class Migration(migrations.Migration):
87 """This migration extracts the 'creation_date' field for each StockItem entry.
89 - If a CREATED entry exists in the StockItemTracking table, this is used as the source for the creation_date field.
90 - If no CREATED entry exists, the creation_date is sourced from the earliest of the following
91 - The 'updated' field of the StockItem entry
92 - The 'stocktake_date' field of the StockItem entry (converted to a datetime)
93 - The earliest entry in the StockItemTracking table for that item
94 """
96 dependencies = [
97 ("stock", "0120_stockitem_creation_date"),
98 ]
100 operations = [
101 migrations.RunPython(set_creation_date, reverse_code=migrations.RunPython.noop),
102 ]