Coverage for src/backend/InvenTree/stock/migrations/0061_auto_20210511_0911.py: 10%
115 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 3.2 on 2021-05-10 23:11
3import re
5from django.db import migrations
7from stock.status_codes import StockHistoryCode
10def update_history(apps, schema_editor):
11 """
12 Update each existing StockItemTracking object,
13 convert the recorded "quantity" to a delta
14 """
16 StockItem = apps.get_model('stock', 'stockitem')
17 StockItemTracking = apps.get_model('stock', 'stockitemtracking')
18 StockLocation = apps.get_model('stock', 'stocklocation')
20 update_count = 0
22 locations = StockLocation.objects.all()
24 for location in locations: # pragma: no cover 24 ↛ 28line 24 didn't jump to line 28 because the loop on line 24 never started
25 # Pre-calculate pathstring
26 # Note we cannot use the 'pathstring' function here as we don't have access to model functions!
28 path = [location.name]
30 loc = location
32 while loc.parent:
33 loc = loc.parent
34 path = [loc.name] + path
36 location._path = '/'.join(path)
38 for item in StockItem.objects.all(): # pragma: no cover 38 ↛ 40line 38 didn't jump to line 40 because the loop on line 38 never started
40 history = StockItemTracking.objects.filter(item=item).order_by('date')
42 if history.count() == 0:
43 continue
45 quantity = history[0].quantity
47 for idx, entry in enumerate(history):
49 deltas = {}
50 updated = False
52 q = entry.quantity
54 if idx == 0 or q != quantity:
56 try:
57 deltas['quantity']= float(q)
58 updated = True
59 except Exception:
60 print(f"WARNING: Error converting quantity '{q}'")
63 quantity = q
65 # Try to "guess" the "type" of tracking entry, based on the title
66 title = entry.title.lower()
68 tracking_type = None
70 if 'completed build' in title:
71 tracking_type = StockHistoryCode.BUILD_OUTPUT_COMPLETED
73 elif 'removed' in title and 'item' in title:
75 if entry.notes.lower().startswith('split '):
76 tracking_type = StockHistoryCode.SPLIT_CHILD_ITEM
77 else:
78 tracking_type = StockHistoryCode.STOCK_REMOVE
80 # Extract the number of removed items
81 result = re.search(r"^removed ([\d\.]+) items", title)
83 if result:
85 removed = result.groups()[0]
87 try:
88 deltas['removed'] = float(removed)
90 # Ensure that 'quantity' is stored too in this case
91 deltas['quantity'] = float(q)
92 except Exception:
93 print(f"WARNING: Error converting removed quantity '{removed}'")
94 else:
95 print(f"Could not decode '{title}'")
97 elif 'split from existing' in title:
98 tracking_type = StockHistoryCode.SPLIT_FROM_PARENT
100 deltas['quantity'] = float(q)
102 elif 'moved to' in title:
103 tracking_type = StockHistoryCode.STOCK_MOVE
105 result = re.search(r'^Moved to (.*)( - )*(.*) \(from.*$', entry.title)
107 if result:
108 # Legacy tracking entries recorded the location in multiple ways, because.. why not?
109 text = result.groups()[0]
111 matches = set()
113 for location in locations:
115 # Direct match for pathstring
116 if text == location._path:
117 matches.add(location)
119 # Direct match for name
120 if text == location.name:
121 matches.add(location)
123 # Match for "name - description"
124 compare = f"{location.name} - {location.description}"
126 if text == compare:
127 matches.add(location)
129 # Match for "pathstring - description"
130 compare = f"{location._path} - {location.description}"
132 if text == compare:
133 matches.add(location)
135 if len(matches) == 1:
136 location = list(matches)[0]
138 deltas['location'] = location.pk
140 else:
141 print(f"No location match: '{text}'")
142 break
144 elif 'created stock item' in title:
145 tracking_type = StockHistoryCode.CREATED
147 elif 'add serial number' in title:
148 tracking_type = StockHistoryCode.ASSIGNED_SERIAL
150 elif 'returned from customer' in title:
151 tracking_type = StockHistoryCode.RETURNED_FROM_CUSTOMER
153 elif 'counted' in title:
154 tracking_type = StockHistoryCode.STOCK_COUNT
156 elif 'added' in title:
157 tracking_type = StockHistoryCode.STOCK_ADD
159 # Extract the number of added items
160 result = re.search(r"^added ([\d\.]+) items", title)
162 if result:
164 added = result.groups()[0]
166 try:
167 deltas['added'] = float(added)
169 # Ensure that 'quantity' is stored too in this case
170 deltas['quantity'] = float(q)
171 except Exception:
172 print(f"WARNING: Error converting added quantity '{added}'")
174 else:
175 print(f"Could not decode '{title}'")
177 elif 'assigned to customer' in title:
178 tracking_type = StockHistoryCode.SENT_TO_CUSTOMER
180 elif 'installed into stock item' in title:
181 tracking_type = StockHistoryCode.INSTALLED_INTO_ASSEMBLY
183 elif 'uninstalled into location' in title:
184 tracking_type = StockHistoryCode.REMOVED_FROM_ASSEMBLY
186 elif 'installed stock item' in title:
187 tracking_type = StockHistoryCode.INSTALLED_CHILD_ITEM
189 elif 'received items' in title:
190 tracking_type = StockHistoryCode.RECEIVED_AGAINST_PURCHASE_ORDER
192 if tracking_type is not None:
193 entry.tracking_type = tracking_type.value
194 updated = True
196 if updated:
197 entry.deltas = deltas
198 entry.save()
199 update_count += 1
202 if update_count > 0: 202 ↛ 203line 202 didn't jump to line 203 because the condition on line 202 was never true
203 print(f"\n==========================\nUpdated {update_count} StockItemHistory entries") # pragma: no cover
206class Migration(migrations.Migration):
208 dependencies = [
209 ('stock', '0060_auto_20210511_1713'),
210 ]
212 operations = [
213 migrations.RunPython(update_history, reverse_code=migrations.RunPython.noop)
214 ]