karrio-server-manager 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.
Files changed (43) hide show
  1. karrio/server/manager/migrations/0070_add_meta_and_product_fields.py +98 -0
  2. karrio/server/manager/migrations/0071_product_proxy.py +25 -0
  3. karrio/server/manager/migrations/0072_populate_json_fields.py +267 -0
  4. karrio/server/manager/migrations/0073_make_shipment_fk_nullable.py +36 -0
  5. karrio/server/manager/migrations/0074_clean_model_refactoring.py +207 -0
  6. karrio/server/manager/migrations/0075_populate_template_meta.py +69 -0
  7. karrio/server/manager/migrations/0076_remove_customs_model.py +66 -0
  8. karrio/server/manager/migrations/0077_add_carrier_snapshot_fields.py +83 -0
  9. karrio/server/manager/migrations/0078_populate_carrier_snapshots.py +112 -0
  10. karrio/server/manager/migrations/0079_remove_carrier_fk_fields.py +56 -0
  11. karrio/server/manager/migrations/0080_add_carrier_json_indexes.py +137 -0
  12. karrio/server/manager/migrations/0081_cleanup.py +62 -0
  13. karrio/server/manager/migrations/0082_shipment_fees.py +26 -0
  14. karrio/server/manager/models.py +421 -321
  15. karrio/server/manager/serializers/__init__.py +5 -4
  16. karrio/server/manager/serializers/address.py +8 -2
  17. karrio/server/manager/serializers/commodity.py +11 -4
  18. karrio/server/manager/serializers/document.py +29 -15
  19. karrio/server/manager/serializers/manifest.py +6 -3
  20. karrio/server/manager/serializers/parcel.py +5 -2
  21. karrio/server/manager/serializers/pickup.py +194 -67
  22. karrio/server/manager/serializers/shipment.py +232 -152
  23. karrio/server/manager/serializers/tracking.py +53 -12
  24. karrio/server/manager/tests/__init__.py +0 -1
  25. karrio/server/manager/tests/test_addresses.py +53 -0
  26. karrio/server/manager/tests/test_parcels.py +50 -0
  27. karrio/server/manager/tests/test_pickups.py +286 -50
  28. karrio/server/manager/tests/test_products.py +597 -0
  29. karrio/server/manager/tests/test_shipments.py +237 -92
  30. karrio/server/manager/tests/test_trackers.py +65 -1
  31. karrio/server/manager/views/__init__.py +1 -1
  32. karrio/server/manager/views/addresses.py +38 -2
  33. karrio/server/manager/views/documents.py +1 -1
  34. karrio/server/manager/views/parcels.py +25 -2
  35. karrio/server/manager/views/products.py +239 -0
  36. karrio/server/manager/views/trackers.py +69 -1
  37. {karrio_server_manager-2026.1.dist-info → karrio_server_manager-2026.1.3.dist-info}/METADATA +1 -1
  38. {karrio_server_manager-2026.1.dist-info → karrio_server_manager-2026.1.3.dist-info}/RECORD +40 -28
  39. {karrio_server_manager-2026.1.dist-info → karrio_server_manager-2026.1.3.dist-info}/WHEEL +1 -1
  40. karrio/server/manager/serializers/customs.py +0 -84
  41. karrio/server/manager/tests/test_custom_infos.py +0 -101
  42. karrio/server/manager/views/customs.py +0 -159
  43. {karrio_server_manager-2026.1.dist-info → karrio_server_manager-2026.1.3.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,69 @@
1
+ # Data migration: Transfer labels from Template model to meta.label
2
+ # This ensures backward compatibility by preserving existing template labels
3
+
4
+ from django.db import migrations
5
+
6
+
7
+ def transfer_template_labels(apps, schema_editor):
8
+ """
9
+ Transfer label and is_default from Template model to Address/Parcel meta field.
10
+
11
+ The Template model (in graph module) stores:
12
+ - label: The template name
13
+ - is_default: Whether it's the default template
14
+ - address: OneToOne FK to Address
15
+ - parcel: OneToOne FK to Parcel
16
+
17
+ We copy these to the meta.label and meta.is_default fields on the related models.
18
+ Note: Customs templates are not migrated as the Customs model is being removed.
19
+ """
20
+ # Get models - Template is in graph module
21
+ Template = apps.get_model("graph", "Template")
22
+ Address = apps.get_model("manager", "Address")
23
+ Parcel = apps.get_model("manager", "Parcel")
24
+
25
+ # Process all templates
26
+ for template in Template.objects.all():
27
+ # Transfer to Address
28
+ if template.address_id:
29
+ try:
30
+ address = Address.objects.get(pk=template.address_id)
31
+ meta = address.meta or {}
32
+ meta["label"] = template.label
33
+ meta["is_default"] = template.is_default
34
+ address.meta = meta
35
+ address.save(update_fields=["meta"])
36
+ except Address.DoesNotExist:
37
+ pass
38
+
39
+ # Transfer to Parcel
40
+ if template.parcel_id:
41
+ try:
42
+ parcel = Parcel.objects.get(pk=template.parcel_id)
43
+ meta = parcel.meta or {}
44
+ meta["label"] = template.label
45
+ meta["is_default"] = template.is_default
46
+ parcel.meta = meta
47
+ parcel.save(update_fields=["meta"])
48
+ except Parcel.DoesNotExist:
49
+ pass
50
+
51
+
52
+ def reverse_noop(apps, schema_editor):
53
+ """Reverse migration is a no-op - we don't want to remove labels."""
54
+ pass
55
+
56
+
57
+ class Migration(migrations.Migration):
58
+
59
+ dependencies = [
60
+ ("manager", "0074_clean_model_refactoring"),
61
+ ("graph", "0002_auto_20210512_1353"), # Ensure Template model exists
62
+ ]
63
+
64
+ operations = [
65
+ migrations.RunPython(
66
+ transfer_template_labels,
67
+ reverse_noop,
68
+ ),
69
+ ]
@@ -0,0 +1,66 @@
1
+ from django.db import migrations
2
+
3
+
4
+ def cleanup_orgs_customs_links(apps, schema_editor):
5
+ """
6
+ Clean up any orgs link tables that reference Customs before deleting the model.
7
+ This handles the case where the database was created in insiders mode but migrations
8
+ are running in OSS mode.
9
+
10
+ In insiders mode, orgs.0024_remove_organization_customs runs first (via run_before)
11
+ and handles this cleanup properly. This is a fallback for OSS mode with insiders DB.
12
+ """
13
+ # Try to get orgs link models and clean them up using Django ORM
14
+ try:
15
+ CustomsLink = apps.get_model("orgs", "CustomsLink")
16
+ CustomsLink.objects.all().delete()
17
+ except LookupError:
18
+ # Model not registered - check if table exists using Django introspection
19
+ connection = schema_editor.connection
20
+ table_names = connection.introspection.table_names()
21
+ if "orgs_customslink" in table_names:
22
+ # Table exists but model isn't registered - use Django's cursor
23
+ with connection.cursor() as cursor:
24
+ cursor.execute("DELETE FROM orgs_customslink")
25
+
26
+ # Also try to clear any M2M relationship
27
+ try:
28
+ Organization = apps.get_model("orgs", "Organization")
29
+ for org in Organization.objects.all():
30
+ if hasattr(org, "customs"):
31
+ org.customs.clear()
32
+ except LookupError:
33
+ pass # Model doesn't exist in OSS mode
34
+
35
+
36
+ def noop(apps, schema_editor):
37
+ """Reverse migration is a no-op since we can't restore deleted links."""
38
+ pass
39
+
40
+
41
+ class Migration(migrations.Migration):
42
+
43
+ dependencies = [
44
+ ("manager", "0075_populate_template_meta"),
45
+ ("graph", "0003_remove_template_customs"), # Remove template.customs FK first
46
+ # Note: orgs.0024_remove_organization_customs (insiders) uses run_before to ensure proper ordering
47
+ ]
48
+
49
+ operations = [
50
+ # Clean up any orgs link tables first (handles insiders DB in OSS mode)
51
+ migrations.RunPython(cleanup_orgs_customs_links, noop),
52
+ # Remove M2M relationship first (customs_commodities junction table)
53
+ migrations.RemoveField(
54
+ model_name="customs",
55
+ name="commodities",
56
+ ),
57
+ # Remove FK to Address
58
+ migrations.RemoveField(
59
+ model_name="customs",
60
+ name="duty_billing_address",
61
+ ),
62
+ # Then delete the model (drops the customs table)
63
+ migrations.DeleteModel(
64
+ name="Customs",
65
+ ),
66
+ ]
@@ -0,0 +1,83 @@
1
+ # Add carrier JSONField to models before data migration
2
+
3
+ import functools
4
+ import karrio.server.core.utils as utils
5
+ from django.db import migrations, models
6
+
7
+
8
+ class Migration(migrations.Migration):
9
+ """
10
+ Step 1: Add carrier JSONField to Pickup, Tracking, DocumentUploadRecord, Manifest, Shipment.
11
+ Also adds carrier_ids JSONField to Shipment.
12
+
13
+ This migration adds the new carrier snapshot fields but does NOT remove the old FK fields yet.
14
+ The old FK fields are needed by the data migration (0078) to populate the carrier snapshots.
15
+ """
16
+
17
+ dependencies = [
18
+ ("manager", "0076_remove_customs_model"),
19
+ ]
20
+
21
+ operations = [
22
+ # Add carrier field to Pickup
23
+ migrations.AddField(
24
+ model_name="pickup",
25
+ name="carrier",
26
+ field=models.JSONField(
27
+ blank=True,
28
+ null=True,
29
+ help_text="Carrier snapshot at time of pickup creation",
30
+ ),
31
+ ),
32
+ # Add carrier field to Tracking
33
+ migrations.AddField(
34
+ model_name="tracking",
35
+ name="carrier",
36
+ field=models.JSONField(
37
+ blank=True,
38
+ null=True,
39
+ help_text="Carrier snapshot at time of tracker creation",
40
+ ),
41
+ ),
42
+ # Add carrier field to DocumentUploadRecord
43
+ migrations.AddField(
44
+ model_name="documentuploadrecord",
45
+ name="carrier",
46
+ field=models.JSONField(
47
+ blank=True,
48
+ null=True,
49
+ help_text="Carrier snapshot at time of document upload",
50
+ ),
51
+ ),
52
+ # Add carrier field to Manifest
53
+ migrations.AddField(
54
+ model_name="manifest",
55
+ name="carrier",
56
+ field=models.JSONField(
57
+ blank=True,
58
+ null=True,
59
+ help_text="Carrier snapshot at time of manifest creation",
60
+ ),
61
+ ),
62
+ # Add carrier field to Shipment (consistent with other models)
63
+ migrations.AddField(
64
+ model_name="shipment",
65
+ name="carrier",
66
+ field=models.JSONField(
67
+ blank=True,
68
+ null=True,
69
+ help_text="Carrier snapshot at time of label purchase",
70
+ ),
71
+ ),
72
+ # Add carrier_ids field to Shipment
73
+ migrations.AddField(
74
+ model_name="shipment",
75
+ name="carrier_ids",
76
+ field=models.JSONField(
77
+ blank=True,
78
+ null=True,
79
+ default=functools.partial(utils.identity, value=[]),
80
+ help_text="List of carrier IDs to filter rate requests",
81
+ ),
82
+ ),
83
+ ]
@@ -0,0 +1,112 @@
1
+ # Data migration: Populate carrier JSONField from FK relationships
2
+
3
+ from django.db import migrations
4
+
5
+
6
+ def get_carrier_name(carrier):
7
+ """Get carrier name from carrier code."""
8
+ from karrio.core.utils import DP
9
+
10
+ try:
11
+ return DP.to_dict(carrier.data).get("carrier_name", carrier.carrier_code)
12
+ except Exception:
13
+ return carrier.carrier_code
14
+
15
+
16
+ def create_carrier_snapshot(carrier):
17
+ """Create carrier snapshot dict from Carrier model instance."""
18
+ if carrier is None:
19
+ return None
20
+
21
+ return {
22
+ "connection_id": carrier.id,
23
+ "connection_type": "account", # All existing carriers are account connections
24
+ "carrier_code": carrier.carrier_code,
25
+ "carrier_id": carrier.carrier_id,
26
+ "carrier_name": get_carrier_name(carrier),
27
+ "test_mode": carrier.test_mode,
28
+ }
29
+
30
+
31
+ def populate_carrier_snapshots(apps, schema_editor):
32
+ """
33
+ Populate carrier JSONField from FK relationships.
34
+
35
+ This copies carrier information from:
36
+ - Pickup.pickup_carrier -> Pickup.carrier
37
+ - Tracking.tracking_carrier -> Tracking.carrier
38
+ - DocumentUploadRecord.upload_carrier -> DocumentUploadRecord.carrier
39
+ - Manifest.manifest_carrier -> Manifest.carrier
40
+ - Shipment.selected_rate_carrier -> Shipment.carrier (dedicated field, consistent with other models)
41
+ """
42
+ Pickup = apps.get_model("manager", "Pickup")
43
+ Tracking = apps.get_model("manager", "Tracking")
44
+ DocumentUploadRecord = apps.get_model("manager", "DocumentUploadRecord")
45
+ Manifest = apps.get_model("manager", "Manifest")
46
+ Shipment = apps.get_model("manager", "Shipment")
47
+
48
+ # Populate Pickup.carrier from pickup_carrier FK
49
+ for pickup in Pickup.objects.select_related("pickup_carrier").all():
50
+ if pickup.pickup_carrier and not pickup.carrier:
51
+ pickup.carrier = create_carrier_snapshot(pickup.pickup_carrier)
52
+ pickup.save(update_fields=["carrier"])
53
+
54
+ # Populate Tracking.carrier from tracking_carrier FK
55
+ for tracking in Tracking.objects.select_related("tracking_carrier").all():
56
+ if tracking.tracking_carrier and not tracking.carrier:
57
+ tracking.carrier = create_carrier_snapshot(tracking.tracking_carrier)
58
+ tracking.save(update_fields=["carrier"])
59
+
60
+ # Populate DocumentUploadRecord.carrier from upload_carrier FK
61
+ for record in DocumentUploadRecord.objects.select_related("upload_carrier").all():
62
+ if record.upload_carrier and not record.carrier:
63
+ record.carrier = create_carrier_snapshot(record.upload_carrier)
64
+ record.save(update_fields=["carrier"])
65
+
66
+ # Populate Manifest.carrier from manifest_carrier FK
67
+ for manifest in Manifest.objects.select_related("manifest_carrier").all():
68
+ if manifest.manifest_carrier and not manifest.carrier:
69
+ manifest.carrier = create_carrier_snapshot(manifest.manifest_carrier)
70
+ manifest.save(update_fields=["carrier"])
71
+
72
+ # Populate Shipment.carrier from selected_rate_carrier FK (consistent with other models)
73
+ for shipment in Shipment.objects.select_related("selected_rate_carrier").all():
74
+ if shipment.selected_rate_carrier and not shipment.carrier:
75
+ shipment.carrier = create_carrier_snapshot(shipment.selected_rate_carrier)
76
+ shipment.save(update_fields=["carrier"])
77
+
78
+
79
+ def reverse_migration(apps, schema_editor):
80
+ """
81
+ Reverse migration: Clear carrier JSONField (data preserved in FKs).
82
+
83
+ Note: The FK data is still preserved, so this is safe to reverse.
84
+ """
85
+ Pickup = apps.get_model("manager", "Pickup")
86
+ Tracking = apps.get_model("manager", "Tracking")
87
+ DocumentUploadRecord = apps.get_model("manager", "DocumentUploadRecord")
88
+ Manifest = apps.get_model("manager", "Manifest")
89
+ Shipment = apps.get_model("manager", "Shipment")
90
+
91
+ Pickup.objects.update(carrier=None)
92
+ Tracking.objects.update(carrier=None)
93
+ DocumentUploadRecord.objects.update(carrier=None)
94
+ Manifest.objects.update(carrier=None)
95
+ Shipment.objects.update(carrier=None)
96
+
97
+
98
+ class Migration(migrations.Migration):
99
+ """
100
+ Step 2: Data migration - Populate carrier snapshots from FK relationships.
101
+
102
+ This migration copies carrier information from FK fields to the new JSONField.
103
+ The FK fields are still preserved at this point.
104
+ """
105
+
106
+ dependencies = [
107
+ ("manager", "0077_add_carrier_snapshot_fields"),
108
+ ]
109
+
110
+ operations = [
111
+ migrations.RunPython(populate_carrier_snapshots, reverse_migration),
112
+ ]
@@ -0,0 +1,56 @@
1
+ # Cleanup migration: Remove carrier FK/M2M fields after data migration
2
+
3
+ from django.db import migrations
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+ """
8
+ Step 3: Remove carrier FK/M2M fields after data has been migrated to JSONField.
9
+
10
+ This migration removes:
11
+ - Pickup.pickup_carrier (FK)
12
+ - Tracking.tracking_carrier (FK)
13
+ - DocumentUploadRecord.upload_carrier (FK)
14
+ - Manifest.manifest_carrier (FK)
15
+ - Shipment.selected_rate_carrier (FK)
16
+ - Shipment.carriers (M2M)
17
+
18
+ All carrier data is now stored in JSONField carrier snapshots.
19
+ """
20
+
21
+ dependencies = [
22
+ ("manager", "0078_populate_carrier_snapshots"),
23
+ ]
24
+
25
+ operations = [
26
+ # Remove Pickup.pickup_carrier FK
27
+ migrations.RemoveField(
28
+ model_name="pickup",
29
+ name="pickup_carrier",
30
+ ),
31
+ # Remove Tracking.tracking_carrier FK
32
+ migrations.RemoveField(
33
+ model_name="tracking",
34
+ name="tracking_carrier",
35
+ ),
36
+ # Remove DocumentUploadRecord.upload_carrier FK
37
+ migrations.RemoveField(
38
+ model_name="documentuploadrecord",
39
+ name="upload_carrier",
40
+ ),
41
+ # Remove Manifest.manifest_carrier FK
42
+ migrations.RemoveField(
43
+ model_name="manifest",
44
+ name="manifest_carrier",
45
+ ),
46
+ # Remove Shipment.selected_rate_carrier FK
47
+ migrations.RemoveField(
48
+ model_name="shipment",
49
+ name="selected_rate_carrier",
50
+ ),
51
+ # Remove Shipment.carriers M2M
52
+ migrations.RemoveField(
53
+ model_name="shipment",
54
+ name="carriers",
55
+ ),
56
+ ]
@@ -0,0 +1,137 @@
1
+ # Add indexes on JSONFields for query optimization
2
+
3
+ import django.db.models as models
4
+ import django.db.models.fields.json as json_fields
5
+ from django.db import migrations, connection
6
+
7
+
8
+ def create_gin_indexes(apps, schema_editor):
9
+ """Create GIN indexes for PostgreSQL only."""
10
+ if "postgresql" not in connection.vendor:
11
+ return # Skip for non-PostgreSQL databases
12
+
13
+ with connection.cursor() as cursor:
14
+ # Shipment GIN indexes
15
+ cursor.execute("""
16
+ CREATE INDEX IF NOT EXISTS "shipment_meta_gin_idx"
17
+ ON "shipments" USING gin ("meta" jsonb_path_ops)
18
+ WHERE "meta" IS NOT NULL;
19
+ """)
20
+ cursor.execute("""
21
+ CREATE INDEX IF NOT EXISTS "shipment_options_gin_idx"
22
+ ON "shipments" USING gin ("options" jsonb_path_ops)
23
+ WHERE "options" IS NOT NULL;
24
+ """)
25
+ cursor.execute("""
26
+ CREATE INDEX IF NOT EXISTS "shipment_metadata_gin_idx"
27
+ ON "shipments" USING gin ("metadata" jsonb_path_ops)
28
+ WHERE "metadata" IS NOT NULL;
29
+ """)
30
+ cursor.execute("""
31
+ CREATE INDEX IF NOT EXISTS "shipment_recipient_gin_idx"
32
+ ON "shipments" USING gin ("recipient" jsonb_path_ops)
33
+ WHERE "recipient" IS NOT NULL;
34
+ """)
35
+
36
+ # Tracking GIN indexes
37
+ cursor.execute("""
38
+ CREATE INDEX IF NOT EXISTS "tracking_meta_gin_idx"
39
+ ON "tracking-status" USING gin ("meta" jsonb_path_ops)
40
+ WHERE "meta" IS NOT NULL;
41
+ """)
42
+
43
+ # Pickup GIN index
44
+ cursor.execute("""
45
+ CREATE INDEX IF NOT EXISTS "pickup_address_gin_idx"
46
+ ON "pickups" USING gin ("address" jsonb_path_ops)
47
+ WHERE "address" IS NOT NULL;
48
+ """)
49
+
50
+
51
+ def drop_gin_indexes(apps, schema_editor):
52
+ """Drop GIN indexes (reverse migration)."""
53
+ if "postgresql" not in connection.vendor:
54
+ return
55
+
56
+ with connection.cursor() as cursor:
57
+ cursor.execute('DROP INDEX IF EXISTS "shipment_meta_gin_idx";')
58
+ cursor.execute('DROP INDEX IF EXISTS "shipment_options_gin_idx";')
59
+ cursor.execute('DROP INDEX IF EXISTS "shipment_metadata_gin_idx";')
60
+ cursor.execute('DROP INDEX IF EXISTS "shipment_recipient_gin_idx";')
61
+ cursor.execute('DROP INDEX IF EXISTS "tracking_meta_gin_idx";')
62
+ cursor.execute('DROP INDEX IF EXISTS "pickup_address_gin_idx";')
63
+
64
+
65
+ class Migration(migrations.Migration):
66
+ """
67
+ Step 4: Add indexes on JSONFields for performance optimization.
68
+
69
+ Index Types:
70
+ 1. KeyTextTransform indexes - For exact match queries on specific JSON keys
71
+ - carrier.carrier_code, carrier.connection_id
72
+ 2. GIN indexes (PostgreSQL only) - For has_key, contains, and varied lookups
73
+ - meta, options, metadata, recipient fields
74
+
75
+ Notes:
76
+ - PostgreSQL: Full support for both index types
77
+ - SQLite: KeyTextTransform indexes created but not used; GIN indexes skipped
78
+ - Conditional indexes avoid indexing NULL values to save space
79
+ """
80
+
81
+ dependencies = [
82
+ ("manager", "0079_remove_carrier_fk_fields"),
83
+ ]
84
+
85
+ operations = [
86
+ # ─────────────────────────────────────────────────────────────────
87
+ # CARRIER SNAPSHOT INDEXES (KeyTextTransform - exact match queries)
88
+ # ─────────────────────────────────────────────────────────────────
89
+
90
+ # Shipment.carrier.carrier_code index
91
+ # Used by: ShipmentFilters.carrier_filter, ManifestSerializer shipment filter
92
+ migrations.AddIndex(
93
+ model_name="shipment",
94
+ index=models.Index(
95
+ json_fields.KeyTextTransform("carrier_code", "carrier"),
96
+ condition=models.Q(carrier__isnull=False),
97
+ name="shipment_carrier_code_idx",
98
+ ),
99
+ ),
100
+ # Tracking.carrier.carrier_code index
101
+ # Used by: TrackerFilters.carrier_filter, tracker views
102
+ migrations.AddIndex(
103
+ model_name="tracking",
104
+ index=models.Index(
105
+ json_fields.KeyTextTransform("carrier_code", "carrier"),
106
+ condition=models.Q(carrier__isnull=False),
107
+ name="tracking_carrier_code_idx",
108
+ ),
109
+ ),
110
+ # Tracking.carrier.connection_id index
111
+ # Used by: CarrierConnection serializer to find related trackers
112
+ migrations.AddIndex(
113
+ model_name="tracking",
114
+ index=models.Index(
115
+ json_fields.KeyTextTransform("connection_id", "carrier"),
116
+ condition=models.Q(carrier__isnull=False),
117
+ name="tracking_connection_id_idx",
118
+ ),
119
+ ),
120
+ # Manifest.carrier.carrier_code index
121
+ # Used by: ManifestFilters.carrier_filter
122
+ migrations.AddIndex(
123
+ model_name="manifest",
124
+ index=models.Index(
125
+ json_fields.KeyTextTransform("carrier_code", "carrier"),
126
+ condition=models.Q(carrier__isnull=False),
127
+ name="manifest_carrier_code_idx",
128
+ ),
129
+ ),
130
+
131
+ # ─────────────────────────────────────────────────────────────────
132
+ # GIN INDEXES (PostgreSQL only - for has_key, contains, text search)
133
+ # ─────────────────────────────────────────────────────────────────
134
+ # These indexes support: __has_key, __contains, __icontains on JSON keys
135
+ # RunPython is used to conditionally create indexes based on DB vendor
136
+ migrations.RunPython(create_gin_indexes, drop_gin_indexes),
137
+ ]
@@ -0,0 +1,62 @@
1
+ # Generated by Django 5.2.10 on 2026-01-22 14:29
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", "0080_add_carrier_json_indexes"),
12
+ ]
13
+
14
+ operations = [
15
+ migrations.AlterField(
16
+ model_name="shipment",
17
+ name="billing_address",
18
+ field=models.JSONField(
19
+ blank=True, help_text="Billing address (embedded JSON)", null=True
20
+ ),
21
+ ),
22
+ migrations.AlterField(
23
+ model_name="shipment",
24
+ name="customs",
25
+ field=models.JSONField(
26
+ blank=True, help_text="Customs information (embedded JSON)", null=True
27
+ ),
28
+ ),
29
+ migrations.AlterField(
30
+ model_name="shipment",
31
+ name="parcels",
32
+ field=models.JSONField(
33
+ blank=True,
34
+ default=functools.partial(
35
+ karrio.server.core.utils.identity, *(), **{"value": []}
36
+ ),
37
+ help_text="Parcels array with nested items (embedded JSON)",
38
+ null=True,
39
+ ),
40
+ ),
41
+ migrations.AlterField(
42
+ model_name="shipment",
43
+ name="recipient",
44
+ field=models.JSONField(
45
+ blank=True, help_text="Recipient address (embedded JSON)", null=True
46
+ ),
47
+ ),
48
+ migrations.AlterField(
49
+ model_name="shipment",
50
+ name="return_address",
51
+ field=models.JSONField(
52
+ blank=True, help_text="Return address (embedded JSON)", null=True
53
+ ),
54
+ ),
55
+ migrations.AlterField(
56
+ model_name="shipment",
57
+ name="shipper",
58
+ field=models.JSONField(
59
+ blank=True, help_text="Shipper address (embedded JSON)", null=True
60
+ ),
61
+ ),
62
+ ]
@@ -0,0 +1,26 @@
1
+ # Generated by Django 5.2.10 on 2026-01-29
2
+ # Adds applied_fees field to Shipment for tracking COGS accounting data
3
+
4
+ import functools
5
+ from django.db import migrations, models
6
+ import karrio.server.core.utils as utils
7
+
8
+
9
+ class Migration(migrations.Migration):
10
+
11
+ dependencies = [
12
+ ("manager", "0081_cleanup"),
13
+ ]
14
+
15
+ operations = [
16
+ migrations.AddField(
17
+ model_name="shipment",
18
+ name="applied_fees",
19
+ field=models.JSONField(
20
+ blank=True,
21
+ null=True,
22
+ default=functools.partial(utils.identity, value=[]),
23
+ help_text="Applied fees for accounting: addons + surcharge COGS values",
24
+ ),
25
+ ),
26
+ ]