karrio-server-pricing 2026.1__py3-none-any.whl → 2026.1.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,283 @@
1
+ """
2
+ Migration: Convert Fee model from FK-based to denormalized snapshot model.
3
+
4
+ Changes:
5
+ - Replace FK shipment → CharField shipment_id (snapshot)
6
+ - Replace FK markup → CharField markup_id (snapshot)
7
+ - Add account_id CharField (org context snapshot)
8
+ - Add test_mode BooleanField
9
+ - Rename markup_type → fee_type
10
+ - Rename markup_percentage → percentage
11
+ - Add composite indexes for time-series queries
12
+ - Data migration: populate account_id and test_mode from shipment data
13
+
14
+ Strategy for FK → CharField conversion:
15
+ 1. Add temporary CharField fields (_shipment_id, _markup_id)
16
+ 2. Data migration: copy FK values to temp fields
17
+ 3. Remove FK fields (drops constraints automatically)
18
+ 4. Rename temp fields to final names
19
+ This is fully portable across SQLite, PostgreSQL, and MySQL.
20
+ """
21
+
22
+ from django.db import migrations, models
23
+
24
+
25
+ def copy_fk_values_to_temp_fields(apps, schema_editor):
26
+ """Copy FK IDs to temporary CharField fields before dropping FKs."""
27
+ Fee = apps.get_model("pricing", "Fee")
28
+ batch_size = 500
29
+ update_batch = []
30
+
31
+ for fee in Fee.objects.all().iterator(chunk_size=batch_size):
32
+ fee._shipment_id = fee.shipment_id or ""
33
+ fee._markup_id = fee.markup_id
34
+ update_batch.append(fee)
35
+
36
+ if len(update_batch) >= batch_size:
37
+ Fee.objects.bulk_update(
38
+ update_batch, ["_shipment_id", "_markup_id"], batch_size=batch_size
39
+ )
40
+ update_batch = []
41
+
42
+ if update_batch:
43
+ Fee.objects.bulk_update(
44
+ update_batch, ["_shipment_id", "_markup_id"], batch_size=batch_size
45
+ )
46
+
47
+
48
+ def populate_snapshot_fields(apps, schema_editor):
49
+ """Populate account_id and test_mode from existing shipment data."""
50
+ Fee = apps.get_model("pricing", "Fee")
51
+ batch_size = 500
52
+
53
+ # Populate test_mode from shipment
54
+ try:
55
+ Shipment = apps.get_model("manager", "Shipment")
56
+ shipment_ids = list(
57
+ Fee.objects.values_list("shipment_id", flat=True).distinct()[:10000]
58
+ )
59
+ test_mode_map = dict(
60
+ Shipment.objects.filter(id__in=shipment_ids)
61
+ .values_list("id", "test_mode")
62
+ )
63
+
64
+ update_batch = []
65
+ for fee in Fee.objects.all().iterator(chunk_size=batch_size):
66
+ test_mode = test_mode_map.get(fee.shipment_id, False)
67
+ if test_mode:
68
+ fee.test_mode = test_mode
69
+ update_batch.append(fee)
70
+ if len(update_batch) >= batch_size:
71
+ Fee.objects.bulk_update(update_batch, ["test_mode"], batch_size=batch_size)
72
+ update_batch = []
73
+ if update_batch:
74
+ Fee.objects.bulk_update(update_batch, ["test_mode"], batch_size=batch_size)
75
+ except LookupError:
76
+ pass
77
+
78
+ # Populate account_id from shipment org links
79
+ try:
80
+ ShipmentLink = apps.get_model("orgs", "ShipmentLink")
81
+ shipment_ids = list(
82
+ Fee.objects.filter(account_id__isnull=True)
83
+ .values_list("shipment_id", flat=True)
84
+ .distinct()[:10000]
85
+ )
86
+ link_map = dict(
87
+ ShipmentLink.objects.filter(item_id__in=shipment_ids)
88
+ .values_list("item_id", "org_id")
89
+ )
90
+
91
+ update_batch = []
92
+ for fee in Fee.objects.filter(account_id__isnull=True).iterator(chunk_size=batch_size):
93
+ org_id = link_map.get(fee.shipment_id)
94
+ if org_id:
95
+ fee.account_id = org_id
96
+ update_batch.append(fee)
97
+ if len(update_batch) >= batch_size:
98
+ Fee.objects.bulk_update(update_batch, ["account_id"], batch_size=batch_size)
99
+ update_batch = []
100
+ if update_batch:
101
+ Fee.objects.bulk_update(update_batch, ["account_id"], batch_size=batch_size)
102
+ except LookupError:
103
+ pass
104
+
105
+
106
+ class Migration(migrations.Migration):
107
+
108
+ dependencies = [
109
+ ("pricing", "0078_cleanup"),
110
+ ]
111
+
112
+ operations = [
113
+ # ─── Step 1: Remove old indexes ──────────────────────────────────
114
+ migrations.RemoveIndex(
115
+ model_name="fee",
116
+ name="fee_shipmen_cf6644_idx",
117
+ ),
118
+ migrations.RemoveIndex(
119
+ model_name="fee",
120
+ name="fee_markup__6438ea_idx",
121
+ ),
122
+ migrations.RemoveIndex(
123
+ model_name="fee",
124
+ name="fee_carrier_86bb46_idx",
125
+ ),
126
+ migrations.RemoveIndex(
127
+ model_name="fee",
128
+ name="fee_created_050ccc_idx",
129
+ ),
130
+
131
+ # ─── Step 2: Add temporary CharField fields to hold FK values ────
132
+ migrations.AddField(
133
+ model_name="fee",
134
+ name="_shipment_id",
135
+ field=models.CharField(
136
+ max_length=50,
137
+ default="",
138
+ ),
139
+ preserve_default=False,
140
+ ),
141
+ migrations.AddField(
142
+ model_name="fee",
143
+ name="_markup_id",
144
+ field=models.CharField(
145
+ max_length=50,
146
+ null=True,
147
+ blank=True,
148
+ ),
149
+ ),
150
+
151
+ # ─── Step 3: Copy FK values to temp fields ──────────────────────
152
+ migrations.RunPython(
153
+ copy_fk_values_to_temp_fields,
154
+ migrations.RunPython.noop,
155
+ ),
156
+
157
+ # ─── Step 4: Remove FK fields (drops constraints automatically) ─
158
+ migrations.RemoveField(
159
+ model_name="fee",
160
+ name="shipment",
161
+ ),
162
+ migrations.RemoveField(
163
+ model_name="fee",
164
+ name="markup",
165
+ ),
166
+
167
+ # ─── Step 5: Rename temp fields to final names ──────────────────
168
+ migrations.RenameField(
169
+ model_name="fee",
170
+ old_name="_shipment_id",
171
+ new_name="shipment_id",
172
+ ),
173
+ migrations.RenameField(
174
+ model_name="fee",
175
+ old_name="_markup_id",
176
+ new_name="markup_id",
177
+ ),
178
+
179
+ # ─── Step 6: Update field attributes (add indexes, help_text) ───
180
+ migrations.AlterField(
181
+ model_name="fee",
182
+ name="shipment_id",
183
+ field=models.CharField(
184
+ max_length=50,
185
+ db_index=True,
186
+ help_text="The shipment this fee was applied to",
187
+ ),
188
+ ),
189
+ migrations.AlterField(
190
+ model_name="fee",
191
+ name="markup_id",
192
+ field=models.CharField(
193
+ max_length=50,
194
+ null=True,
195
+ blank=True,
196
+ db_index=True,
197
+ help_text="The markup ID that generated this fee",
198
+ ),
199
+ ),
200
+
201
+ # ─── Step 7: Rename existing fields ─────────────────────────────
202
+ migrations.RenameField(
203
+ model_name="fee",
204
+ old_name="markup_type",
205
+ new_name="fee_type",
206
+ ),
207
+ migrations.RenameField(
208
+ model_name="fee",
209
+ old_name="markup_percentage",
210
+ new_name="percentage",
211
+ ),
212
+
213
+ # ─── Step 8: Add new snapshot fields ─────────────────────────────
214
+ migrations.AddField(
215
+ model_name="fee",
216
+ name="account_id",
217
+ field=models.CharField(
218
+ max_length=50,
219
+ null=True,
220
+ blank=True,
221
+ db_index=True,
222
+ help_text="The organization/account this fee belongs to",
223
+ ),
224
+ ),
225
+ migrations.AddField(
226
+ model_name="fee",
227
+ name="test_mode",
228
+ field=models.BooleanField(default=False),
229
+ ),
230
+
231
+ # ─── Step 9: Update connection_id to be indexed ─────────────────
232
+ migrations.AlterField(
233
+ model_name="fee",
234
+ name="connection_id",
235
+ field=models.CharField(
236
+ max_length=50,
237
+ db_index=True,
238
+ help_text="Connection ID used for this shipment",
239
+ ),
240
+ ),
241
+
242
+ # ─── Step 10: Data migration — populate snapshot fields ─────────
243
+ migrations.RunPython(
244
+ populate_snapshot_fields,
245
+ migrations.RunPython.noop,
246
+ ),
247
+
248
+ # ─── Step 11: Add indexes ───────────────────────────────────────
249
+ # Single-field indexes for fields without db_index=True
250
+ # (shipment_id, markup_id, account_id, connection_id already
251
+ # get indexes via db_index=True on the field definition)
252
+ migrations.AddIndex(
253
+ model_name="fee",
254
+ index=models.Index(
255
+ fields=["carrier_code"], name="fee_carrier_86bb46_idx"
256
+ ),
257
+ ),
258
+ migrations.AddIndex(
259
+ model_name="fee",
260
+ index=models.Index(
261
+ fields=["created_at"], name="fee_created_050ccc_idx"
262
+ ),
263
+ ),
264
+ # Composite indexes for time-series queries
265
+ migrations.AddIndex(
266
+ model_name="fee",
267
+ index=models.Index(
268
+ fields=["account_id", "created_at"], name="fee_account_507a12_idx"
269
+ ),
270
+ ),
271
+ migrations.AddIndex(
272
+ model_name="fee",
273
+ index=models.Index(
274
+ fields=["connection_id", "created_at"], name="fee_connect_3aeeec_idx"
275
+ ),
276
+ ),
277
+ migrations.AddIndex(
278
+ model_name="fee",
279
+ index=models.Index(
280
+ fields=["markup_id", "created_at"], name="fee_markup__130c94_idx"
281
+ ),
282
+ ),
283
+ ]