karrio-server-manager 2026.1.1__py3-none-any.whl → 2026.1.4__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.
- karrio/server/manager/migrations/0070_add_meta_and_product_fields.py +98 -0
- karrio/server/manager/migrations/0071_product_proxy.py +25 -0
- karrio/server/manager/migrations/0072_populate_json_fields.py +267 -0
- karrio/server/manager/migrations/0073_make_shipment_fk_nullable.py +36 -0
- karrio/server/manager/migrations/0074_clean_model_refactoring.py +207 -0
- karrio/server/manager/migrations/0075_populate_template_meta.py +69 -0
- karrio/server/manager/migrations/0076_remove_customs_model.py +66 -0
- karrio/server/manager/migrations/0077_add_carrier_snapshot_fields.py +83 -0
- karrio/server/manager/migrations/0078_populate_carrier_snapshots.py +112 -0
- karrio/server/manager/migrations/0079_remove_carrier_fk_fields.py +56 -0
- karrio/server/manager/migrations/0080_add_carrier_json_indexes.py +137 -0
- karrio/server/manager/migrations/0081_cleanup.py +62 -0
- karrio/server/manager/migrations/0082_shipment_fees.py +26 -0
- karrio/server/manager/models.py +421 -321
- karrio/server/manager/serializers/__init__.py +5 -4
- karrio/server/manager/serializers/address.py +8 -2
- karrio/server/manager/serializers/commodity.py +11 -4
- karrio/server/manager/serializers/document.py +29 -15
- karrio/server/manager/serializers/manifest.py +6 -3
- karrio/server/manager/serializers/parcel.py +5 -2
- karrio/server/manager/serializers/pickup.py +194 -67
- karrio/server/manager/serializers/shipment.py +226 -171
- karrio/server/manager/serializers/tracking.py +45 -12
- karrio/server/manager/tests/__init__.py +0 -1
- karrio/server/manager/tests/test_addresses.py +53 -0
- karrio/server/manager/tests/test_parcels.py +50 -0
- karrio/server/manager/tests/test_pickups.py +286 -50
- karrio/server/manager/tests/test_products.py +597 -0
- karrio/server/manager/tests/test_shipments.py +237 -92
- karrio/server/manager/tests/test_trackers.py +4 -3
- karrio/server/manager/views/__init__.py +1 -1
- karrio/server/manager/views/addresses.py +38 -2
- karrio/server/manager/views/documents.py +1 -1
- karrio/server/manager/views/parcels.py +25 -2
- karrio/server/manager/views/pickups.py +6 -6
- karrio/server/manager/views/products.py +239 -0
- karrio/server/manager/views/trackers.py +69 -1
- {karrio_server_manager-2026.1.1.dist-info → karrio_server_manager-2026.1.4.dist-info}/METADATA +1 -1
- {karrio_server_manager-2026.1.1.dist-info → karrio_server_manager-2026.1.4.dist-info}/RECORD +41 -29
- {karrio_server_manager-2026.1.1.dist-info → karrio_server_manager-2026.1.4.dist-info}/WHEEL +1 -1
- karrio/server/manager/serializers/customs.py +0 -84
- karrio/server/manager/tests/test_custom_infos.py +0 -101
- karrio/server/manager/views/customs.py +0 -159
- {karrio_server_manager-2026.1.1.dist-info → karrio_server_manager-2026.1.4.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# Generated by Django 5.2.10 on 2026-01-16 00:43
|
|
2
|
+
|
|
3
|
+
import functools
|
|
4
|
+
import karrio.server.core.utils
|
|
5
|
+
from django.db import migrations, models
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Migration(migrations.Migration):
|
|
9
|
+
|
|
10
|
+
dependencies = [
|
|
11
|
+
("manager", "0069_alter_tracking_status"),
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
operations = [
|
|
15
|
+
migrations.AddField(
|
|
16
|
+
model_name="address",
|
|
17
|
+
name="meta",
|
|
18
|
+
field=models.JSONField(
|
|
19
|
+
blank=True,
|
|
20
|
+
default=functools.partial(
|
|
21
|
+
karrio.server.core.utils.identity, *(), **{"value": {}}
|
|
22
|
+
),
|
|
23
|
+
help_text="Template metadata: label, is_default, usage[]",
|
|
24
|
+
null=True,
|
|
25
|
+
),
|
|
26
|
+
),
|
|
27
|
+
migrations.AddField(
|
|
28
|
+
model_name="parcel",
|
|
29
|
+
name="meta",
|
|
30
|
+
field=models.JSONField(
|
|
31
|
+
blank=True,
|
|
32
|
+
default=functools.partial(
|
|
33
|
+
karrio.server.core.utils.identity, *(), **{"value": {}}
|
|
34
|
+
),
|
|
35
|
+
help_text="Template metadata: label, is_default",
|
|
36
|
+
null=True,
|
|
37
|
+
),
|
|
38
|
+
),
|
|
39
|
+
migrations.AddField(
|
|
40
|
+
model_name="commodity",
|
|
41
|
+
name="meta",
|
|
42
|
+
field=models.JSONField(
|
|
43
|
+
blank=True,
|
|
44
|
+
default=functools.partial(
|
|
45
|
+
karrio.server.core.utils.identity, *(), **{"value": {}}
|
|
46
|
+
),
|
|
47
|
+
help_text="Template metadata: label, is_default",
|
|
48
|
+
null=True,
|
|
49
|
+
),
|
|
50
|
+
),
|
|
51
|
+
migrations.AddField(
|
|
52
|
+
model_name="shipment",
|
|
53
|
+
name="billing_address_data",
|
|
54
|
+
field=models.JSONField(
|
|
55
|
+
blank=True, help_text="Billing address snapshot (JSON)", null=True
|
|
56
|
+
),
|
|
57
|
+
),
|
|
58
|
+
migrations.AddField(
|
|
59
|
+
model_name="shipment",
|
|
60
|
+
name="customs_data",
|
|
61
|
+
field=models.JSONField(
|
|
62
|
+
blank=True, help_text="Customs information snapshot (JSON)", null=True
|
|
63
|
+
),
|
|
64
|
+
),
|
|
65
|
+
migrations.AddField(
|
|
66
|
+
model_name="shipment",
|
|
67
|
+
name="parcels_data",
|
|
68
|
+
field=models.JSONField(
|
|
69
|
+
blank=True,
|
|
70
|
+
default=functools.partial(
|
|
71
|
+
karrio.server.core.utils.identity, *(), **{"value": []}
|
|
72
|
+
),
|
|
73
|
+
help_text="Parcels array with items (JSON)",
|
|
74
|
+
null=True,
|
|
75
|
+
),
|
|
76
|
+
),
|
|
77
|
+
migrations.AddField(
|
|
78
|
+
model_name="shipment",
|
|
79
|
+
name="recipient_data",
|
|
80
|
+
field=models.JSONField(
|
|
81
|
+
blank=True, help_text="Recipient address snapshot (JSON)", null=True
|
|
82
|
+
),
|
|
83
|
+
),
|
|
84
|
+
migrations.AddField(
|
|
85
|
+
model_name="shipment",
|
|
86
|
+
name="return_address_data",
|
|
87
|
+
field=models.JSONField(
|
|
88
|
+
blank=True, help_text="Return address snapshot (JSON)", null=True
|
|
89
|
+
),
|
|
90
|
+
),
|
|
91
|
+
migrations.AddField(
|
|
92
|
+
model_name="shipment",
|
|
93
|
+
name="shipper_data",
|
|
94
|
+
field=models.JSONField(
|
|
95
|
+
blank=True, help_text="Shipper address snapshot (JSON)", null=True
|
|
96
|
+
),
|
|
97
|
+
),
|
|
98
|
+
]
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Generated by Django 5.2.10 on 2026-01-16 00:54
|
|
2
|
+
|
|
3
|
+
from django.db import migrations
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Migration(migrations.Migration):
|
|
7
|
+
|
|
8
|
+
dependencies = [
|
|
9
|
+
("manager", "0070_add_meta_and_product_fields"),
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
operations = [
|
|
13
|
+
migrations.CreateModel(
|
|
14
|
+
name="Product",
|
|
15
|
+
fields=[],
|
|
16
|
+
options={
|
|
17
|
+
"verbose_name": "Product",
|
|
18
|
+
"verbose_name_plural": "Products",
|
|
19
|
+
"proxy": True,
|
|
20
|
+
"indexes": [],
|
|
21
|
+
"constraints": [],
|
|
22
|
+
},
|
|
23
|
+
bases=("manager.commodity",),
|
|
24
|
+
),
|
|
25
|
+
]
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
# Data migration: Populate JSON fields from FK/M2M relations
|
|
2
|
+
# Phase 2 of the Address/Parcel/Product refactoring
|
|
3
|
+
|
|
4
|
+
from django.db import migrations
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def address_to_dict(address):
|
|
8
|
+
"""Convert Address model instance to JSON dict."""
|
|
9
|
+
if address is None:
|
|
10
|
+
return None
|
|
11
|
+
|
|
12
|
+
return {
|
|
13
|
+
"id": address.id,
|
|
14
|
+
"postal_code": address.postal_code,
|
|
15
|
+
"city": address.city,
|
|
16
|
+
"country_code": address.country_code,
|
|
17
|
+
"federal_tax_id": address.federal_tax_id,
|
|
18
|
+
"state_tax_id": address.state_tax_id,
|
|
19
|
+
"person_name": address.person_name,
|
|
20
|
+
"company_name": address.company_name,
|
|
21
|
+
"email": address.email,
|
|
22
|
+
"phone_number": address.phone_number,
|
|
23
|
+
"state_code": address.state_code,
|
|
24
|
+
"suburb": address.suburb,
|
|
25
|
+
"residential": address.residential,
|
|
26
|
+
"street_number": address.street_number,
|
|
27
|
+
"address_line1": address.address_line1,
|
|
28
|
+
"address_line2": address.address_line2,
|
|
29
|
+
"validate_location": address.validate_location,
|
|
30
|
+
"validation": address.validation,
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def commodity_to_dict(commodity, id_prefix="cmd"):
|
|
35
|
+
"""Convert Commodity model instance to JSON dict."""
|
|
36
|
+
if commodity is None:
|
|
37
|
+
return None
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
"id": f"{id_prefix}_{commodity.id[-12:]}", # Generate new JSON ID
|
|
41
|
+
"template_id": commodity.id, # Keep reference to original
|
|
42
|
+
"weight": commodity.weight,
|
|
43
|
+
"weight_unit": commodity.weight_unit,
|
|
44
|
+
"quantity": commodity.quantity,
|
|
45
|
+
"sku": commodity.sku,
|
|
46
|
+
"title": commodity.title,
|
|
47
|
+
"description": commodity.description,
|
|
48
|
+
"value_amount": float(commodity.value_amount) if commodity.value_amount else None,
|
|
49
|
+
"value_currency": commodity.value_currency,
|
|
50
|
+
"origin_country": commodity.origin_country,
|
|
51
|
+
"hs_code": commodity.hs_code,
|
|
52
|
+
"product_url": commodity.product_url,
|
|
53
|
+
"image_url": commodity.image_url,
|
|
54
|
+
"metadata": commodity.metadata,
|
|
55
|
+
"parent_id": commodity.parent_id, # Preserve parent reference
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def parcel_to_dict(parcel):
|
|
60
|
+
"""Convert Parcel model instance to JSON dict with items."""
|
|
61
|
+
if parcel is None:
|
|
62
|
+
return None
|
|
63
|
+
|
|
64
|
+
items = [
|
|
65
|
+
commodity_to_dict(item, id_prefix="itm")
|
|
66
|
+
for item in parcel.items.all()
|
|
67
|
+
]
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
"id": f"pcl_{parcel.id[-12:]}", # Generate new JSON ID
|
|
71
|
+
"template_id": parcel.id, # Keep reference to original
|
|
72
|
+
"weight": parcel.weight,
|
|
73
|
+
"weight_unit": parcel.weight_unit,
|
|
74
|
+
"width": parcel.width,
|
|
75
|
+
"height": parcel.height,
|
|
76
|
+
"length": parcel.length,
|
|
77
|
+
"dimension_unit": parcel.dimension_unit,
|
|
78
|
+
"packaging_type": parcel.packaging_type,
|
|
79
|
+
"package_preset": parcel.package_preset,
|
|
80
|
+
"is_document": parcel.is_document,
|
|
81
|
+
"description": parcel.description,
|
|
82
|
+
"content": parcel.content,
|
|
83
|
+
"reference_number": parcel.reference_number,
|
|
84
|
+
"freight_class": parcel.freight_class,
|
|
85
|
+
"options": parcel.options,
|
|
86
|
+
"items": items,
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def customs_to_dict(customs):
|
|
91
|
+
"""Convert Customs model instance to JSON dict with commodities."""
|
|
92
|
+
if customs is None:
|
|
93
|
+
return None
|
|
94
|
+
|
|
95
|
+
commodities = [
|
|
96
|
+
commodity_to_dict(c, id_prefix="cmd")
|
|
97
|
+
for c in customs.commodities.all()
|
|
98
|
+
]
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
"id": customs.id,
|
|
102
|
+
"content_type": customs.content_type,
|
|
103
|
+
"content_description": customs.content_description,
|
|
104
|
+
"incoterm": customs.incoterm,
|
|
105
|
+
"invoice": customs.invoice,
|
|
106
|
+
"invoice_date": str(customs.invoice_date) if customs.invoice_date else None,
|
|
107
|
+
"commercial_invoice": customs.commercial_invoice,
|
|
108
|
+
"certify": customs.certify,
|
|
109
|
+
"signer": customs.signer,
|
|
110
|
+
"duty": customs.duty,
|
|
111
|
+
"options": customs.options,
|
|
112
|
+
"duty_billing_address": address_to_dict(customs.duty_billing_address),
|
|
113
|
+
"commodities": commodities,
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def populate_shipment_json_fields(apps, schema_editor):
|
|
118
|
+
"""Populate Shipment JSON fields from FK/M2M relations."""
|
|
119
|
+
Shipment = apps.get_model("manager", "Shipment")
|
|
120
|
+
|
|
121
|
+
# Process in batches to avoid memory issues
|
|
122
|
+
batch_size = 500
|
|
123
|
+
total = Shipment.objects.count()
|
|
124
|
+
|
|
125
|
+
for offset in range(0, total, batch_size):
|
|
126
|
+
shipments = Shipment.objects.select_related(
|
|
127
|
+
"shipper",
|
|
128
|
+
"recipient",
|
|
129
|
+
"return_address",
|
|
130
|
+
"billing_address",
|
|
131
|
+
"customs",
|
|
132
|
+
"customs__duty_billing_address",
|
|
133
|
+
).prefetch_related(
|
|
134
|
+
"parcels",
|
|
135
|
+
"parcels__items",
|
|
136
|
+
"customs__commodities",
|
|
137
|
+
)[offset:offset + batch_size]
|
|
138
|
+
|
|
139
|
+
for shipment in shipments:
|
|
140
|
+
changes = []
|
|
141
|
+
|
|
142
|
+
# Populate shipper_data
|
|
143
|
+
if shipment.shipper and not shipment.shipper_data:
|
|
144
|
+
shipment.shipper_data = address_to_dict(shipment.shipper)
|
|
145
|
+
changes.append("shipper_data")
|
|
146
|
+
|
|
147
|
+
# Populate recipient_data
|
|
148
|
+
if shipment.recipient and not shipment.recipient_data:
|
|
149
|
+
shipment.recipient_data = address_to_dict(shipment.recipient)
|
|
150
|
+
changes.append("recipient_data")
|
|
151
|
+
|
|
152
|
+
# Populate return_address_data
|
|
153
|
+
if shipment.return_address and not shipment.return_address_data:
|
|
154
|
+
shipment.return_address_data = address_to_dict(shipment.return_address)
|
|
155
|
+
changes.append("return_address_data")
|
|
156
|
+
|
|
157
|
+
# Populate billing_address_data
|
|
158
|
+
if shipment.billing_address and not shipment.billing_address_data:
|
|
159
|
+
shipment.billing_address_data = address_to_dict(shipment.billing_address)
|
|
160
|
+
changes.append("billing_address_data")
|
|
161
|
+
|
|
162
|
+
# Populate parcels_data
|
|
163
|
+
if shipment.parcels.exists() and not shipment.parcels_data:
|
|
164
|
+
shipment.parcels_data = [
|
|
165
|
+
parcel_to_dict(p) for p in shipment.parcels.all()
|
|
166
|
+
]
|
|
167
|
+
changes.append("parcels_data")
|
|
168
|
+
|
|
169
|
+
# Populate customs_data
|
|
170
|
+
if shipment.customs and not shipment.customs_data:
|
|
171
|
+
shipment.customs_data = customs_to_dict(shipment.customs)
|
|
172
|
+
changes.append("customs_data")
|
|
173
|
+
|
|
174
|
+
if changes:
|
|
175
|
+
shipment.save(update_fields=changes)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def line_item_to_dict(line_item):
|
|
179
|
+
"""Convert LineItem/Commodity to JSON dict for orders."""
|
|
180
|
+
return {
|
|
181
|
+
"id": f"oli_{line_item.id[-12:]}", # Generate new JSON ID
|
|
182
|
+
"template_id": line_item.id, # Keep reference to original
|
|
183
|
+
"weight": line_item.weight,
|
|
184
|
+
"weight_unit": line_item.weight_unit,
|
|
185
|
+
"quantity": line_item.quantity,
|
|
186
|
+
"unfulfilled_quantity": getattr(line_item, "unfulfilled_quantity", line_item.quantity),
|
|
187
|
+
"sku": line_item.sku,
|
|
188
|
+
"title": line_item.title,
|
|
189
|
+
"description": line_item.description,
|
|
190
|
+
"value_amount": float(line_item.value_amount) if line_item.value_amount else None,
|
|
191
|
+
"value_currency": line_item.value_currency,
|
|
192
|
+
"origin_country": line_item.origin_country,
|
|
193
|
+
"hs_code": line_item.hs_code,
|
|
194
|
+
"product_url": line_item.product_url,
|
|
195
|
+
"image_url": line_item.image_url,
|
|
196
|
+
"metadata": line_item.metadata,
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def populate_order_json_fields(apps, schema_editor):
|
|
201
|
+
"""Populate Order JSON fields from FK/M2M relations."""
|
|
202
|
+
Order = apps.get_model("orders", "Order")
|
|
203
|
+
|
|
204
|
+
# Process in batches
|
|
205
|
+
batch_size = 500
|
|
206
|
+
total = Order.objects.count()
|
|
207
|
+
|
|
208
|
+
for offset in range(0, total, batch_size):
|
|
209
|
+
orders = Order.objects.select_related(
|
|
210
|
+
"shipping_to",
|
|
211
|
+
"shipping_from",
|
|
212
|
+
"billing_address",
|
|
213
|
+
).prefetch_related(
|
|
214
|
+
"line_items",
|
|
215
|
+
)[offset:offset + batch_size]
|
|
216
|
+
|
|
217
|
+
for order in orders:
|
|
218
|
+
changes = []
|
|
219
|
+
|
|
220
|
+
# Populate shipping_to_data
|
|
221
|
+
if order.shipping_to and not order.shipping_to_data:
|
|
222
|
+
order.shipping_to_data = address_to_dict(order.shipping_to)
|
|
223
|
+
changes.append("shipping_to_data")
|
|
224
|
+
|
|
225
|
+
# Populate shipping_from_data
|
|
226
|
+
if order.shipping_from and not order.shipping_from_data:
|
|
227
|
+
order.shipping_from_data = address_to_dict(order.shipping_from)
|
|
228
|
+
changes.append("shipping_from_data")
|
|
229
|
+
|
|
230
|
+
# Populate billing_address_data
|
|
231
|
+
if order.billing_address and not order.billing_address_data:
|
|
232
|
+
order.billing_address_data = address_to_dict(order.billing_address)
|
|
233
|
+
changes.append("billing_address_data")
|
|
234
|
+
|
|
235
|
+
# Populate line_items_data
|
|
236
|
+
if order.line_items.exists() and not order.line_items_data:
|
|
237
|
+
order.line_items_data = [
|
|
238
|
+
line_item_to_dict(item) for item in order.line_items.all()
|
|
239
|
+
]
|
|
240
|
+
changes.append("line_items_data")
|
|
241
|
+
|
|
242
|
+
if changes:
|
|
243
|
+
order.save(update_fields=changes)
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def reverse_noop(apps, schema_editor):
|
|
247
|
+
"""Reverse migration is a no-op - data remains in both fields."""
|
|
248
|
+
pass
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
class Migration(migrations.Migration):
|
|
252
|
+
|
|
253
|
+
dependencies = [
|
|
254
|
+
("manager", "0071_product_proxy"),
|
|
255
|
+
("orders", "0021_add_json_fields"),
|
|
256
|
+
]
|
|
257
|
+
|
|
258
|
+
operations = [
|
|
259
|
+
migrations.RunPython(
|
|
260
|
+
populate_shipment_json_fields,
|
|
261
|
+
reverse_noop,
|
|
262
|
+
),
|
|
263
|
+
migrations.RunPython(
|
|
264
|
+
populate_order_json_fields,
|
|
265
|
+
reverse_noop,
|
|
266
|
+
),
|
|
267
|
+
]
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Generated by Django 5.2.10 on 2026-01-16 11:40
|
|
2
|
+
|
|
3
|
+
import django.db.models.deletion
|
|
4
|
+
from django.db import migrations, models
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Migration(migrations.Migration):
|
|
8
|
+
|
|
9
|
+
dependencies = [
|
|
10
|
+
("manager", "0072_populate_json_fields"),
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
operations = [
|
|
14
|
+
migrations.AlterField(
|
|
15
|
+
model_name="shipment",
|
|
16
|
+
name="recipient",
|
|
17
|
+
field=models.OneToOneField(
|
|
18
|
+
blank=True,
|
|
19
|
+
null=True,
|
|
20
|
+
on_delete=django.db.models.deletion.CASCADE,
|
|
21
|
+
related_name="recipient_shipment",
|
|
22
|
+
to="manager.address",
|
|
23
|
+
),
|
|
24
|
+
),
|
|
25
|
+
migrations.AlterField(
|
|
26
|
+
model_name="shipment",
|
|
27
|
+
name="shipper",
|
|
28
|
+
field=models.OneToOneField(
|
|
29
|
+
blank=True,
|
|
30
|
+
null=True,
|
|
31
|
+
on_delete=django.db.models.deletion.CASCADE,
|
|
32
|
+
related_name="shipper_shipment",
|
|
33
|
+
to="manager.address",
|
|
34
|
+
),
|
|
35
|
+
),
|
|
36
|
+
]
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
# Clean model refactoring migration
|
|
2
|
+
# This migration completes the JSON field migration by:
|
|
3
|
+
# 1. Adding JSON address field to Pickup and Manifest
|
|
4
|
+
# 2. Populating Pickup and Manifest address data from FK
|
|
5
|
+
# 3. Dropping legacy FK/M2M fields from Shipment
|
|
6
|
+
# 4. Renaming *_data fields to direct names (removing suffix)
|
|
7
|
+
# 5. Renaming tables to plural form
|
|
8
|
+
|
|
9
|
+
import functools
|
|
10
|
+
from django.db import migrations, models
|
|
11
|
+
import karrio.server.core.utils
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def address_to_dict(address):
|
|
15
|
+
"""Convert Address model instance to JSON dict."""
|
|
16
|
+
if address is None:
|
|
17
|
+
return None
|
|
18
|
+
|
|
19
|
+
return {
|
|
20
|
+
"id": address.id,
|
|
21
|
+
"object_type": "address",
|
|
22
|
+
"postal_code": address.postal_code,
|
|
23
|
+
"city": address.city,
|
|
24
|
+
"country_code": address.country_code,
|
|
25
|
+
"federal_tax_id": address.federal_tax_id,
|
|
26
|
+
"state_tax_id": address.state_tax_id,
|
|
27
|
+
"person_name": address.person_name,
|
|
28
|
+
"company_name": address.company_name,
|
|
29
|
+
"email": address.email,
|
|
30
|
+
"phone_number": address.phone_number,
|
|
31
|
+
"state_code": address.state_code,
|
|
32
|
+
"suburb": address.suburb,
|
|
33
|
+
"residential": address.residential,
|
|
34
|
+
"street_number": address.street_number,
|
|
35
|
+
"address_line1": address.address_line1,
|
|
36
|
+
"address_line2": address.address_line2,
|
|
37
|
+
"validate_location": address.validate_location,
|
|
38
|
+
"validation": address.validation,
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def populate_pickup_manifest_json(apps, schema_editor):
|
|
43
|
+
"""Populate JSON address fields for Pickup and Manifest from FK."""
|
|
44
|
+
Pickup = apps.get_model("manager", "Pickup")
|
|
45
|
+
Manifest = apps.get_model("manager", "Manifest")
|
|
46
|
+
|
|
47
|
+
# Populate Pickup address_data
|
|
48
|
+
for pickup in Pickup.objects.select_related("address").all():
|
|
49
|
+
if pickup.address and not pickup.address_data:
|
|
50
|
+
pickup.address_data = address_to_dict(pickup.address)
|
|
51
|
+
pickup.save(update_fields=["address_data"])
|
|
52
|
+
|
|
53
|
+
# Populate Manifest address_data
|
|
54
|
+
for manifest in Manifest.objects.select_related("address").all():
|
|
55
|
+
if manifest.address and not manifest.address_data:
|
|
56
|
+
manifest.address_data = address_to_dict(manifest.address)
|
|
57
|
+
manifest.save(update_fields=["address_data"])
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def reverse_noop(apps, schema_editor):
|
|
61
|
+
"""Reverse migration is a no-op."""
|
|
62
|
+
pass
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class Migration(migrations.Migration):
|
|
66
|
+
|
|
67
|
+
dependencies = [
|
|
68
|
+
("manager", "0073_make_shipment_fk_nullable"),
|
|
69
|
+
]
|
|
70
|
+
|
|
71
|
+
operations = [
|
|
72
|
+
# =========================================================
|
|
73
|
+
# STEP 1: Add JSON fields to Pickup and Manifest
|
|
74
|
+
# =========================================================
|
|
75
|
+
migrations.AddField(
|
|
76
|
+
model_name="pickup",
|
|
77
|
+
name="address_data",
|
|
78
|
+
field=models.JSONField(
|
|
79
|
+
blank=True,
|
|
80
|
+
null=True,
|
|
81
|
+
help_text="Pickup address (embedded JSON)",
|
|
82
|
+
),
|
|
83
|
+
),
|
|
84
|
+
migrations.AddField(
|
|
85
|
+
model_name="manifest",
|
|
86
|
+
name="address_data",
|
|
87
|
+
field=models.JSONField(
|
|
88
|
+
blank=True,
|
|
89
|
+
null=True,
|
|
90
|
+
help_text="Manifest address (embedded JSON)",
|
|
91
|
+
),
|
|
92
|
+
),
|
|
93
|
+
|
|
94
|
+
# =========================================================
|
|
95
|
+
# STEP 2: Populate JSON fields from FK
|
|
96
|
+
# =========================================================
|
|
97
|
+
migrations.RunPython(
|
|
98
|
+
populate_pickup_manifest_json,
|
|
99
|
+
reverse_noop,
|
|
100
|
+
),
|
|
101
|
+
|
|
102
|
+
# =========================================================
|
|
103
|
+
# STEP 3: Drop Shipment legacy FK/M2M fields
|
|
104
|
+
# =========================================================
|
|
105
|
+
# Drop FK fields (addresses)
|
|
106
|
+
migrations.RemoveField(
|
|
107
|
+
model_name="shipment",
|
|
108
|
+
name="shipper",
|
|
109
|
+
),
|
|
110
|
+
migrations.RemoveField(
|
|
111
|
+
model_name="shipment",
|
|
112
|
+
name="recipient",
|
|
113
|
+
),
|
|
114
|
+
migrations.RemoveField(
|
|
115
|
+
model_name="shipment",
|
|
116
|
+
name="return_address",
|
|
117
|
+
),
|
|
118
|
+
migrations.RemoveField(
|
|
119
|
+
model_name="shipment",
|
|
120
|
+
name="billing_address",
|
|
121
|
+
),
|
|
122
|
+
migrations.RemoveField(
|
|
123
|
+
model_name="shipment",
|
|
124
|
+
name="customs",
|
|
125
|
+
),
|
|
126
|
+
# Drop M2M field (parcels)
|
|
127
|
+
migrations.RemoveField(
|
|
128
|
+
model_name="shipment",
|
|
129
|
+
name="parcels",
|
|
130
|
+
),
|
|
131
|
+
|
|
132
|
+
# =========================================================
|
|
133
|
+
# STEP 4: Drop Pickup and Manifest legacy FK fields
|
|
134
|
+
# =========================================================
|
|
135
|
+
migrations.RemoveField(
|
|
136
|
+
model_name="pickup",
|
|
137
|
+
name="address",
|
|
138
|
+
),
|
|
139
|
+
migrations.RemoveField(
|
|
140
|
+
model_name="manifest",
|
|
141
|
+
name="address",
|
|
142
|
+
),
|
|
143
|
+
|
|
144
|
+
# =========================================================
|
|
145
|
+
# STEP 5: Rename Shipment *_data fields to direct names
|
|
146
|
+
# =========================================================
|
|
147
|
+
migrations.RenameField(
|
|
148
|
+
model_name="shipment",
|
|
149
|
+
old_name="shipper_data",
|
|
150
|
+
new_name="shipper",
|
|
151
|
+
),
|
|
152
|
+
migrations.RenameField(
|
|
153
|
+
model_name="shipment",
|
|
154
|
+
old_name="recipient_data",
|
|
155
|
+
new_name="recipient",
|
|
156
|
+
),
|
|
157
|
+
migrations.RenameField(
|
|
158
|
+
model_name="shipment",
|
|
159
|
+
old_name="return_address_data",
|
|
160
|
+
new_name="return_address",
|
|
161
|
+
),
|
|
162
|
+
migrations.RenameField(
|
|
163
|
+
model_name="shipment",
|
|
164
|
+
old_name="billing_address_data",
|
|
165
|
+
new_name="billing_address",
|
|
166
|
+
),
|
|
167
|
+
migrations.RenameField(
|
|
168
|
+
model_name="shipment",
|
|
169
|
+
old_name="parcels_data",
|
|
170
|
+
new_name="parcels",
|
|
171
|
+
),
|
|
172
|
+
migrations.RenameField(
|
|
173
|
+
model_name="shipment",
|
|
174
|
+
old_name="customs_data",
|
|
175
|
+
new_name="customs",
|
|
176
|
+
),
|
|
177
|
+
|
|
178
|
+
# =========================================================
|
|
179
|
+
# STEP 6: Rename Pickup and Manifest address_data fields
|
|
180
|
+
# =========================================================
|
|
181
|
+
migrations.RenameField(
|
|
182
|
+
model_name="pickup",
|
|
183
|
+
old_name="address_data",
|
|
184
|
+
new_name="address",
|
|
185
|
+
),
|
|
186
|
+
migrations.RenameField(
|
|
187
|
+
model_name="manifest",
|
|
188
|
+
old_name="address_data",
|
|
189
|
+
new_name="address",
|
|
190
|
+
),
|
|
191
|
+
|
|
192
|
+
# =========================================================
|
|
193
|
+
# STEP 7: Rename tables to plural form
|
|
194
|
+
# =========================================================
|
|
195
|
+
migrations.AlterModelTable(
|
|
196
|
+
name="shipment",
|
|
197
|
+
table="shipments",
|
|
198
|
+
),
|
|
199
|
+
migrations.AlterModelTable(
|
|
200
|
+
name="pickup",
|
|
201
|
+
table="pickups",
|
|
202
|
+
),
|
|
203
|
+
migrations.AlterModelTable(
|
|
204
|
+
name="manifest",
|
|
205
|
+
table="manifests",
|
|
206
|
+
),
|
|
207
|
+
]
|