karrio-server-core 2025.5rc12__py3-none-any.whl → 2026.1.1__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 (112) hide show
  1. karrio/server/core/authentication.py +59 -25
  2. karrio/server/core/config.py +31 -0
  3. karrio/server/core/datatypes.py +30 -4
  4. karrio/server/core/dataunits.py +53 -22
  5. karrio/server/core/exceptions.py +287 -17
  6. karrio/server/core/filters.py +14 -0
  7. karrio/server/core/gateway.py +285 -10
  8. karrio/server/core/logging.py +403 -0
  9. karrio/server/core/management/commands/runserver.py +5 -0
  10. karrio/server/core/middleware.py +104 -2
  11. karrio/server/core/migrations/0006_add_api_log_requested_at_index.py +22 -0
  12. karrio/server/core/models/base.py +34 -1
  13. karrio/server/core/oauth_validators.py +2 -3
  14. karrio/server/core/permissions.py +1 -2
  15. karrio/server/core/serializers.py +183 -10
  16. karrio/server/core/signals.py +22 -28
  17. karrio/server/core/telemetry.py +573 -0
  18. karrio/server/core/tests/__init__.py +27 -0
  19. karrio/server/core/{tests.py → tests/base.py} +6 -7
  20. karrio/server/core/tests/test_exception_level.py +159 -0
  21. karrio/server/core/tests/test_resource_token.py +593 -0
  22. karrio/server/core/utils.py +688 -38
  23. karrio/server/core/validators.py +144 -222
  24. karrio/server/core/views/oauth.py +13 -12
  25. karrio/server/core/views/references.py +2 -2
  26. karrio/server/iam/apps.py +1 -4
  27. karrio/server/iam/migrations/0002_setup_carrier_permission_groups.py +103 -0
  28. karrio/server/iam/migrations/0003_remove_permission_groups.py +91 -0
  29. karrio/server/iam/permissions.py +7 -134
  30. karrio/server/iam/serializers.py +17 -2
  31. karrio/server/iam/signals.py +2 -4
  32. karrio/server/providers/admin.py +1 -1
  33. karrio/server/providers/management/commands/migrate_rate_sheets.py +101 -0
  34. karrio/server/providers/migrations/0082_add_zone_identifiers.py +50 -0
  35. karrio/server/providers/migrations/0083_add_optimized_rate_sheet_structure.py +33 -0
  36. karrio/server/providers/migrations/0084_alter_servicelevel_currency.py +168 -0
  37. karrio/server/providers/migrations/0085_populate_dhl_parcel_de_oauth_credentials.py +82 -0
  38. karrio/server/providers/migrations/0086_rename_dhl_parcel_de_customer_number_to_billing_number.py +71 -0
  39. karrio/server/providers/migrations/0087_alter_carrier_capabilities.py +38 -0
  40. karrio/server/providers/migrations/0088_servicelevel_surcharges.py +24 -0
  41. karrio/server/providers/migrations/0089_servicelevel_cost_max_volume.py +31 -0
  42. karrio/server/providers/migrations/0090_ratesheet_surcharges_servicelevel_zone_surcharge_ids.py +47 -0
  43. karrio/server/providers/migrations/0091_migrate_legacy_zones_surcharges.py +154 -0
  44. karrio/server/providers/models/__init__.py +1 -2
  45. karrio/server/providers/models/carrier.py +103 -18
  46. karrio/server/providers/models/service.py +188 -1
  47. karrio/server/providers/models/sheet.py +371 -0
  48. karrio/server/providers/serializers/base.py +263 -2
  49. karrio/server/providers/signals.py +2 -4
  50. karrio/server/providers/templates/providers/oauth_callback.html +105 -0
  51. karrio/server/providers/tests/__init__.py +5 -0
  52. karrio/server/providers/tests/test_connections.py +895 -0
  53. karrio/server/providers/views/carriers.py +1 -3
  54. karrio/server/providers/views/connections.py +322 -2
  55. karrio/server/samples.py +1 -1
  56. karrio/server/serializers/abstract.py +116 -21
  57. karrio/server/tracing/migrations/0007_tracingrecord_tracing_created_at_idx.py +19 -0
  58. karrio/server/tracing/models.py +2 -0
  59. karrio/server/tracing/utils.py +5 -8
  60. karrio/server/user/migrations/0007_user_metadata.py +25 -0
  61. karrio/server/user/models.py +38 -23
  62. karrio/server/user/serializers.py +1 -0
  63. karrio/server/user/templates/registration/registration_confirm_email.html +1 -1
  64. {karrio_server_core-2025.5rc12.dist-info → karrio_server_core-2026.1.1.dist-info}/METADATA +2 -2
  65. {karrio_server_core-2025.5rc12.dist-info → karrio_server_core-2026.1.1.dist-info}/RECORD +67 -86
  66. karrio/server/providers/extension/__init__.py +0 -1
  67. karrio/server/providers/extension/models/__init__.py +0 -1
  68. karrio/server/providers/extension/models/allied_express.py +0 -22
  69. karrio/server/providers/extension/models/allied_express_local.py +0 -22
  70. karrio/server/providers/extension/models/amazon_shipping.py +0 -27
  71. karrio/server/providers/extension/models/aramex.py +0 -25
  72. karrio/server/providers/extension/models/asendia_us.py +0 -21
  73. karrio/server/providers/extension/models/australiapost.py +0 -20
  74. karrio/server/providers/extension/models/boxknight.py +0 -19
  75. karrio/server/providers/extension/models/bpost.py +0 -21
  76. karrio/server/providers/extension/models/canadapost.py +0 -21
  77. karrio/server/providers/extension/models/canpar.py +0 -19
  78. karrio/server/providers/extension/models/chronopost.py +0 -22
  79. karrio/server/providers/extension/models/colissimo.py +0 -22
  80. karrio/server/providers/extension/models/dhl_express.py +0 -23
  81. karrio/server/providers/extension/models/dhl_parcel_de.py +0 -25
  82. karrio/server/providers/extension/models/dhl_poland.py +0 -22
  83. karrio/server/providers/extension/models/dhl_universal.py +0 -19
  84. karrio/server/providers/extension/models/dicom.py +0 -20
  85. karrio/server/providers/extension/models/dpd.py +0 -37
  86. karrio/server/providers/extension/models/dpdhl.py +0 -26
  87. karrio/server/providers/extension/models/easypost.py +0 -20
  88. karrio/server/providers/extension/models/eshipper.py +0 -21
  89. karrio/server/providers/extension/models/fedex.py +0 -25
  90. karrio/server/providers/extension/models/fedex_ws.py +0 -24
  91. karrio/server/providers/extension/models/freightcom.py +0 -21
  92. karrio/server/providers/extension/models/generic.py +0 -35
  93. karrio/server/providers/extension/models/geodis.py +0 -22
  94. karrio/server/providers/extension/models/hay_post.py +0 -22
  95. karrio/server/providers/extension/models/laposte.py +0 -19
  96. karrio/server/providers/extension/models/locate2u.py +0 -22
  97. karrio/server/providers/extension/models/nationex.py +0 -22
  98. karrio/server/providers/extension/models/purolator.py +0 -21
  99. karrio/server/providers/extension/models/roadie.py +0 -18
  100. karrio/server/providers/extension/models/royalmail.py +0 -19
  101. karrio/server/providers/extension/models/sendle.py +0 -22
  102. karrio/server/providers/extension/models/tge.py +0 -63
  103. karrio/server/providers/extension/models/tnt.py +0 -23
  104. karrio/server/providers/extension/models/ups.py +0 -23
  105. karrio/server/providers/extension/models/usps.py +0 -23
  106. karrio/server/providers/extension/models/usps_international.py +0 -23
  107. karrio/server/providers/extension/models/usps_wt.py +0 -24
  108. karrio/server/providers/extension/models/usps_wt_international.py +0 -24
  109. karrio/server/providers/extension/models/zoom2u.py +0 -23
  110. karrio/server/providers/tests.py +0 -3
  111. {karrio_server_core-2025.5rc12.dist-info → karrio_server_core-2026.1.1.dist-info}/WHEEL +0 -0
  112. {karrio_server_core-2025.5rc12.dist-info → karrio_server_core-2026.1.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,91 @@
1
+ # Generated migration to remove permission groups
2
+
3
+ from django.db import migrations
4
+
5
+
6
+ def remove_permission_groups(apps, schema_editor):
7
+ """
8
+ Remove all permission groups that were created by setup_groups().
9
+
10
+ This migration removes the following groups:
11
+ - manage_apps
12
+ - manage_carriers (deprecated)
13
+ - read_carriers
14
+ - write_carriers
15
+ - manage_orders
16
+ - manage_team
17
+ - manage_org_owner
18
+ - manage_webhooks
19
+ - manage_data
20
+ - manage_shipments
21
+ - manage_system
22
+ """
23
+ Group = apps.get_model("user", "Group")
24
+ ContextPermission = apps.get_model("iam", "ContextPermission")
25
+
26
+ # List of groups to remove
27
+ groups_to_remove = [
28
+ "manage_apps",
29
+ "manage_carriers",
30
+ "read_carriers",
31
+ "write_carriers",
32
+ "manage_orders",
33
+ "manage_team",
34
+ "manage_org_owner",
35
+ "manage_webhooks",
36
+ "manage_data",
37
+ "manage_shipments",
38
+ "manage_system",
39
+ "manage_pickups",
40
+ "manage_trackers",
41
+ ]
42
+
43
+ # First, remove the groups from all ContextPermissions
44
+ for group_name in groups_to_remove:
45
+ group = Group.objects.filter(name=group_name).first()
46
+ if group:
47
+ # Remove this group from all context permissions
48
+ for ctx_perm in ContextPermission.objects.filter(groups=group):
49
+ ctx_perm.groups.remove(group)
50
+
51
+ # Then delete the groups themselves
52
+ Group.objects.filter(name__in=groups_to_remove).delete()
53
+
54
+
55
+ def reverse_migration(apps, schema_editor):
56
+ """
57
+ Reverse migration - recreate groups (without permissions, which were set dynamically).
58
+ Note: This won't restore the full permission setup, only creates empty groups.
59
+ """
60
+ Group = apps.get_model("user", "Group")
61
+
62
+ groups_to_create = [
63
+ "manage_apps",
64
+ "manage_carriers",
65
+ "read_carriers",
66
+ "write_carriers",
67
+ "manage_orders",
68
+ "manage_team",
69
+ "manage_org_owner",
70
+ "manage_webhooks",
71
+ "manage_data",
72
+ "manage_shipments",
73
+ "manage_system",
74
+ "manage_pickups",
75
+ "manage_trackers",
76
+ ]
77
+
78
+ for group_name in groups_to_create:
79
+ Group.objects.get_or_create(name=group_name)
80
+
81
+
82
+ class Migration(migrations.Migration):
83
+
84
+ dependencies = [
85
+ ("iam", "0002_setup_carrier_permission_groups"),
86
+ ("user", "0004_group"),
87
+ ]
88
+
89
+ operations = [
90
+ migrations.RunPython(remove_permission_groups, reverse_migration),
91
+ ]
@@ -1,134 +1,7 @@
1
- import typing
2
- import logging
3
- from django.db import models
4
- from django.contrib.auth import get_user_model
5
- from django.contrib.auth.models import Permission
6
-
7
- import karrio.server.core.utils as utils
8
- import karrio.server.user.models as users
9
- import karrio.server.iam.serializers as serializers
10
-
11
- logger = logging.getLogger(__name__)
12
- User = get_user_model()
13
-
14
-
15
- @utils.skip_on_loadata
16
- @utils.async_wrapper
17
- @utils.tenant_aware
18
- def setup_groups(**_):
19
- """This function create all standard group permissions if they don't exsist."""
20
- print("> setting up permissions")
21
-
22
- # manage_apps
23
- setup_group(
24
- serializers.PermissionGroup.manage_apps.name,
25
- permissions=Permission.objects.filter(content_type__app_label="apps"),
26
- )
27
-
28
- # manage_carriers
29
- setup_group(
30
- serializers.PermissionGroup.manage_carriers.name,
31
- permissions=[
32
- *Permission.objects.filter(content_type__app_label="providers"),
33
- *Permission.objects.filter(
34
- models.Q(content_type__app_label="orgs")
35
- & models.Q(name__icontains="carrier")
36
- ),
37
- ],
38
- override=True,
39
- )
40
-
41
- # manage_orders
42
- setup_group(
43
- serializers.PermissionGroup.manage_orders.name,
44
- permissions=Permission.objects.filter(content_type__app_label="orders"),
45
- )
46
-
47
- # manage_team
48
- setup_group(
49
- serializers.PermissionGroup.manage_team.name,
50
- permissions=(
51
- Permission.objects.filter(
52
- content_type__app_label="orgs", name__icontains="organization"
53
- ).exclude(name__icontains="owner")
54
- ),
55
- override=True,
56
- )
57
-
58
- # manage_org_owner
59
- setup_group(
60
- serializers.PermissionGroup.manage_org_owner.name,
61
- permissions=Permission.objects.filter(
62
- content_type__model="OrganizationOwner".lower()
63
- ),
64
- )
65
-
66
- # manage_webhooks
67
- setup_group(
68
- serializers.PermissionGroup.manage_webhooks.name,
69
- permissions=Permission.objects.filter(content_type__model="Webhook".lower()),
70
- )
71
-
72
- # manage_data
73
- setup_group(
74
- serializers.PermissionGroup.manage_data.name,
75
- permissions=[
76
- *Permission.objects.filter(
77
- content_type__app_label__in=["data", "graph", "documents"]
78
- ),
79
- *Permission.objects.filter(
80
- content_type__app_label="audit", name__icontains="view"
81
- ),
82
- *Permission.objects.filter(
83
- content_type__app_label="rest_framework_tracking",
84
- name__icontains="view",
85
- ),
86
- ],
87
- override=True,
88
- )
89
-
90
- # manage_shipments
91
- setup_group(
92
- serializers.PermissionGroup.manage_shipments.name,
93
- permissions=[
94
- *Permission.objects.filter(content_type__app_label="manager"),
95
- *Permission.objects.filter(
96
- models.Q(content_type__app_label="orgs")
97
- & (
98
- models.Q(name__icontains="address")
99
- | models.Q(name__icontains="parcel")
100
- | models.Q(name__icontains="commodity")
101
- | models.Q(name__icontains="customs")
102
- | models.Q(name__icontains="pickup")
103
- | models.Q(name__icontains="tracker")
104
- | models.Q(name__icontains="shipment")
105
- )
106
- ),
107
- ],
108
- )
109
-
110
- # manage_system
111
- setup_group(
112
- serializers.PermissionGroup.manage_system.name,
113
- permissions=Permission.objects.filter(
114
- content_type__app_label__in=[
115
- "admin",
116
- "user",
117
- "pricing",
118
- "providers",
119
- "audit",
120
- "database",
121
- "rest_framework_tracking",
122
- ]
123
- ),
124
- )
125
-
126
-
127
- def setup_group(
128
- name: str, permissions: typing.List[Permission], override: bool = False
129
- ):
130
- group, created = users.Group.objects.get_or_create(name=name)
131
-
132
- if created or override:
133
- group.permissions.set(permissions)
134
- group.save()
1
+ # This module previously contained permission group setup logic.
2
+ # The setup_groups() function has been removed to:
3
+ # 1. Fix Django warning about database access during app initialization
4
+ # 2. Prepare for a better RBAC implementation in the future
5
+ #
6
+ # The PermissionGroup enum and ROLES_GROUPS mapping are preserved in
7
+ # karrio.server.iam.serializers for organization role management.
@@ -9,7 +9,9 @@ class PermissionGroup(lib.StrEnum):
9
9
  manage_orders = "manage_orders"
10
10
  manage_data = "manage_data"
11
11
  manage_pickups = "manage_pickups"
12
- manage_carriers = "manage_carriers"
12
+ manage_carriers = "manage_carriers" # Deprecated: use read_carriers + write_carriers
13
+ read_carriers = "read_carriers"
14
+ write_carriers = "write_carriers"
13
15
  manage_trackers = "manage_trackers"
14
16
  manage_webhooks = "manage_webhooks"
15
17
  manage_shipments = "manage_shipments"
@@ -20,14 +22,26 @@ PERMISSION_GROUPS = [(p.name, p.name) for p in list(PermissionGroup)]
20
22
  ROLES_GROUPS: typing.Dict[str, typing.List[str]] = {
21
23
  "owner": [
22
24
  PermissionGroup.manage_org_owner.value,
25
+ PermissionGroup.manage_team.value,
26
+ PermissionGroup.manage_apps.value,
27
+ PermissionGroup.read_carriers.value,
28
+ PermissionGroup.write_carriers.value,
29
+ PermissionGroup.manage_webhooks.value,
30
+ PermissionGroup.manage_data.value,
31
+ PermissionGroup.manage_orders.value,
32
+ PermissionGroup.manage_pickups.value,
33
+ PermissionGroup.manage_trackers.value,
34
+ PermissionGroup.manage_shipments.value,
23
35
  ],
24
36
  "admin": [
25
37
  PermissionGroup.manage_team.value,
26
38
  PermissionGroup.manage_apps.value,
27
- PermissionGroup.manage_carriers.value,
39
+ PermissionGroup.read_carriers.value,
40
+ PermissionGroup.write_carriers.value,
28
41
  ],
29
42
  "developer": [
30
43
  PermissionGroup.manage_webhooks.value,
44
+ PermissionGroup.read_carriers.value,
31
45
  ],
32
46
  "member": [
33
47
  PermissionGroup.manage_data.value,
@@ -35,5 +49,6 @@ ROLES_GROUPS: typing.Dict[str, typing.List[str]] = {
35
49
  PermissionGroup.manage_pickups.value,
36
50
  PermissionGroup.manage_trackers.value,
37
51
  PermissionGroup.manage_shipments.value,
52
+ PermissionGroup.read_carriers.value,
38
53
  ],
39
54
  }
@@ -1,17 +1,15 @@
1
- import logging
2
1
  from django.db.models import signals
3
2
 
3
+ from karrio.server.core.logging import logger
4
4
  import karrio.server.core.utils as utils
5
5
  import karrio.server.user.models as user
6
6
  import karrio.server.iam.models as models
7
7
 
8
- logger = logging.getLogger(__name__)
9
-
10
8
 
11
9
  def register_all():
12
10
  signals.post_delete.connect(context_object_deleted, sender=user.Token)
13
11
 
14
- logger.info("karrio.iam signals registered...")
12
+ logger.info("Signal registration complete", module="karrio.iam")
15
13
 
16
14
 
17
15
  @utils.disable_for_loaddata
@@ -354,7 +354,7 @@ class LabelTemplateAdmin(admin.ModelAdmin):
354
354
  return False
355
355
 
356
356
 
357
- @utils.skip_on_commands()
357
+ @utils.skip_on_commands(["loaddata", "migrate", "makemigrations", "shell"])
358
358
  def register_carrier_admins():
359
359
  for carrier_name, display_name in ref.REFERENCES["carriers"].items():
360
360
  proxy = providers.create_carrier_proxy(carrier_name, display_name)
@@ -0,0 +1,101 @@
1
+ from django.core.management.base import BaseCommand
2
+ from karrio.server.providers.models import RateSheet
3
+
4
+
5
+ class Command(BaseCommand):
6
+ help = 'Migrate existing rate sheets from legacy format to optimized zone reuse structure'
7
+
8
+ def add_arguments(self, parser):
9
+ parser.add_argument(
10
+ '--dry-run',
11
+ action='store_true',
12
+ help='Show what would be migrated without making changes',
13
+ )
14
+ parser.add_argument(
15
+ '--force',
16
+ action='store_true',
17
+ help='Force migration even if rate sheet already has optimized structure',
18
+ )
19
+
20
+ def handle(self, *args, **options):
21
+ dry_run = options['dry_run']
22
+ force = options['force']
23
+
24
+ rate_sheets = RateSheet.objects.all()
25
+
26
+ if not rate_sheets.exists():
27
+ self.stdout.write(self.style.WARNING('No rate sheets found.'))
28
+ return
29
+
30
+ migrated_count = 0
31
+ skipped_count = 0
32
+ error_count = 0
33
+
34
+ for rate_sheet in rate_sheets:
35
+ try:
36
+ # Check if already migrated
37
+ if not force and (rate_sheet.zones or rate_sheet.service_rates):
38
+ self.stdout.write(
39
+ self.style.WARNING(f'Skipping {rate_sheet.name} - already has optimized structure')
40
+ )
41
+ skipped_count += 1
42
+ continue
43
+
44
+ # Check if has services with zones to migrate
45
+ has_zones = any(
46
+ service.zones for service in rate_sheet.services.all()
47
+ )
48
+
49
+ if not has_zones:
50
+ self.stdout.write(
51
+ self.style.WARNING(f'Skipping {rate_sheet.name} - no zones to migrate')
52
+ )
53
+ skipped_count += 1
54
+ continue
55
+
56
+ if dry_run:
57
+ self.stdout.write(
58
+ self.style.SUCCESS(f'Would migrate: {rate_sheet.name}')
59
+ )
60
+ migrated_count += 1
61
+ else:
62
+ # Perform migration
63
+ old_zones_count = sum(
64
+ len(service.zones or []) for service in rate_sheet.services.all()
65
+ )
66
+
67
+ rate_sheet.migrate_from_legacy_format()
68
+
69
+ new_zones_count = len(rate_sheet.zones or [])
70
+ new_rates_count = len(rate_sheet.service_rates or [])
71
+
72
+ self.stdout.write(
73
+ self.style.SUCCESS(
74
+ f'Migrated {rate_sheet.name}: '
75
+ f'{old_zones_count} duplicated zones → '
76
+ f'{new_zones_count} shared zones + {new_rates_count} rates'
77
+ )
78
+ )
79
+ migrated_count += 1
80
+
81
+ except Exception as e:
82
+ self.stdout.write(
83
+ self.style.ERROR(f'Error migrating {rate_sheet.name}: {str(e)}')
84
+ )
85
+ error_count += 1
86
+
87
+ # Summary
88
+ if dry_run:
89
+ self.stdout.write(
90
+ self.style.SUCCESS(
91
+ f'\nDry run complete: {migrated_count} rate sheets would be migrated, '
92
+ f'{skipped_count} skipped, {error_count} errors'
93
+ )
94
+ )
95
+ else:
96
+ self.stdout.write(
97
+ self.style.SUCCESS(
98
+ f'\nMigration complete: {migrated_count} rate sheets migrated, '
99
+ f'{skipped_count} skipped, {error_count} errors'
100
+ )
101
+ )
@@ -0,0 +1,50 @@
1
+ # Generated migration to add unique identifiers to ServiceLevel zones
2
+ from django.db import migrations
3
+ import uuid
4
+
5
+
6
+ def add_zone_identifiers(apps, schema_editor):
7
+ """Add unique IDs to existing zones in ServiceLevel objects"""
8
+ ServiceLevel = apps.get_model('providers', 'ServiceLevel')
9
+
10
+ for service in ServiceLevel.objects.all():
11
+ if service.zones:
12
+ updated = False
13
+ for i, zone in enumerate(service.zones):
14
+ if 'id' not in zone:
15
+ # Generate unique zone ID
16
+ zone['id'] = f"zone_{uuid.uuid4().hex[:8]}"
17
+ updated = True
18
+
19
+ if updated:
20
+ service.save(update_fields=['zones'])
21
+
22
+
23
+ def reverse_zone_identifiers(apps, schema_editor):
24
+ """Remove zone IDs (for rollback)"""
25
+ ServiceLevel = apps.get_model('providers', 'ServiceLevel')
26
+
27
+ for service in ServiceLevel.objects.all():
28
+ if service.zones:
29
+ updated = False
30
+ for zone in service.zones:
31
+ if 'id' in zone:
32
+ del zone['id']
33
+ updated = True
34
+
35
+ if updated:
36
+ service.save(update_fields=['zones'])
37
+
38
+
39
+ class Migration(migrations.Migration):
40
+
41
+ dependencies = [
42
+ ('providers', '0081_remove_alliedexpresssettings_carrier_ptr_and_more'),
43
+ ]
44
+
45
+ operations = [
46
+ migrations.RunPython(
47
+ add_zone_identifiers,
48
+ reverse_zone_identifiers
49
+ ),
50
+ ]
@@ -0,0 +1,33 @@
1
+ # Generated migration to add optimized rate sheet structure
2
+
3
+ from django.db import migrations, models
4
+ import karrio.server.core.models as core
5
+
6
+
7
+ class Migration(migrations.Migration):
8
+ dependencies = [
9
+ ('providers', '0082_add_zone_identifiers'),
10
+ ]
11
+
12
+ operations = [
13
+ migrations.AddField(
14
+ model_name='ratesheet',
15
+ name='zones',
16
+ field=models.JSONField(
17
+ blank=True,
18
+ null=True,
19
+ default=core.field_default([]),
20
+ help_text="Shared zone definitions: [{'id': 'zone_1', 'label': 'Zone 1', 'cities': [...], 'country_codes': [...]}]"
21
+ ),
22
+ ),
23
+ migrations.AddField(
24
+ model_name='ratesheet',
25
+ name='service_rates',
26
+ field=models.JSONField(
27
+ blank=True,
28
+ null=True,
29
+ default=core.field_default([]),
30
+ help_text="Service-zone rate mapping: [{'service_id': 'svc_1', 'zone_id': 'zone_1', 'rate': 10.50}]"
31
+ ),
32
+ ),
33
+ ]
@@ -0,0 +1,168 @@
1
+ # Generated by Django 5.2.5 on 2025-08-19 19:09
2
+
3
+ from django.db import migrations, models
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+
8
+ dependencies = [
9
+ ("providers", "0083_add_optimized_rate_sheet_structure"),
10
+ ]
11
+
12
+ operations = [
13
+ migrations.AlterField(
14
+ model_name="servicelevel",
15
+ name="currency",
16
+ field=models.CharField(
17
+ blank=True,
18
+ choices=[
19
+ ("EUR", "EUR"),
20
+ ("AED", "AED"),
21
+ ("USD", "USD"),
22
+ ("XCD", "XCD"),
23
+ ("AMD", "AMD"),
24
+ ("ANG", "ANG"),
25
+ ("AOA", "AOA"),
26
+ ("ARS", "ARS"),
27
+ ("AUD", "AUD"),
28
+ ("AWG", "AWG"),
29
+ ("AZN", "AZN"),
30
+ ("BAM", "BAM"),
31
+ ("BBD", "BBD"),
32
+ ("BDT", "BDT"),
33
+ ("XOF", "XOF"),
34
+ ("BGN", "BGN"),
35
+ ("BHD", "BHD"),
36
+ ("BIF", "BIF"),
37
+ ("BMD", "BMD"),
38
+ ("BND", "BND"),
39
+ ("BOB", "BOB"),
40
+ ("BRL", "BRL"),
41
+ ("BSD", "BSD"),
42
+ ("BTN", "BTN"),
43
+ ("BWP", "BWP"),
44
+ ("BYN", "BYN"),
45
+ ("BZD", "BZD"),
46
+ ("CAD", "CAD"),
47
+ ("CDF", "CDF"),
48
+ ("XAF", "XAF"),
49
+ ("CHF", "CHF"),
50
+ ("NZD", "NZD"),
51
+ ("CLP", "CLP"),
52
+ ("CNY", "CNY"),
53
+ ("COP", "COP"),
54
+ ("CRC", "CRC"),
55
+ ("CUC", "CUC"),
56
+ ("CVE", "CVE"),
57
+ ("CZK", "CZK"),
58
+ ("DJF", "DJF"),
59
+ ("DKK", "DKK"),
60
+ ("DOP", "DOP"),
61
+ ("DZD", "DZD"),
62
+ ("EGP", "EGP"),
63
+ ("ERN", "ERN"),
64
+ ("ETB", "ETB"),
65
+ ("FJD", "FJD"),
66
+ ("GBP", "GBP"),
67
+ ("GEL", "GEL"),
68
+ ("GHS", "GHS"),
69
+ ("GMD", "GMD"),
70
+ ("GNF", "GNF"),
71
+ ("GTQ", "GTQ"),
72
+ ("GYD", "GYD"),
73
+ ("HKD", "HKD"),
74
+ ("HNL", "HNL"),
75
+ ("HRK", "HRK"),
76
+ ("HTG", "HTG"),
77
+ ("HUF", "HUF"),
78
+ ("IDR", "IDR"),
79
+ ("ILS", "ILS"),
80
+ ("INR", "INR"),
81
+ ("IRR", "IRR"),
82
+ ("ISK", "ISK"),
83
+ ("JMD", "JMD"),
84
+ ("JOD", "JOD"),
85
+ ("JPY", "JPY"),
86
+ ("KES", "KES"),
87
+ ("KGS", "KGS"),
88
+ ("KHR", "KHR"),
89
+ ("KMF", "KMF"),
90
+ ("KPW", "KPW"),
91
+ ("KRW", "KRW"),
92
+ ("KWD", "KWD"),
93
+ ("KYD", "KYD"),
94
+ ("KZT", "KZT"),
95
+ ("LAK", "LAK"),
96
+ ("LKR", "LKR"),
97
+ ("LRD", "LRD"),
98
+ ("LSL", "LSL"),
99
+ ("LYD", "LYD"),
100
+ ("MAD", "MAD"),
101
+ ("MDL", "MDL"),
102
+ ("MGA", "MGA"),
103
+ ("MKD", "MKD"),
104
+ ("MMK", "MMK"),
105
+ ("MNT", "MNT"),
106
+ ("MOP", "MOP"),
107
+ ("MRO", "MRO"),
108
+ ("MUR", "MUR"),
109
+ ("MVR", "MVR"),
110
+ ("MWK", "MWK"),
111
+ ("MXN", "MXN"),
112
+ ("MYR", "MYR"),
113
+ ("MZN", "MZN"),
114
+ ("NAD", "NAD"),
115
+ ("XPF", "XPF"),
116
+ ("NGN", "NGN"),
117
+ ("NIO", "NIO"),
118
+ ("NOK", "NOK"),
119
+ ("NPR", "NPR"),
120
+ ("OMR", "OMR"),
121
+ ("PEN", "PEN"),
122
+ ("PGK", "PGK"),
123
+ ("PHP", "PHP"),
124
+ ("PKR", "PKR"),
125
+ ("PLN", "PLN"),
126
+ ("PYG", "PYG"),
127
+ ("QAR", "QAR"),
128
+ ("RON", "RON"),
129
+ ("RSD", "RSD"),
130
+ ("RUB", "RUB"),
131
+ ("RWF", "RWF"),
132
+ ("SAR", "SAR"),
133
+ ("SBD", "SBD"),
134
+ ("SCR", "SCR"),
135
+ ("SDG", "SDG"),
136
+ ("SEK", "SEK"),
137
+ ("SGD", "SGD"),
138
+ ("SHP", "SHP"),
139
+ ("SLL", "SLL"),
140
+ ("SOS", "SOS"),
141
+ ("SRD", "SRD"),
142
+ ("SSP", "SSP"),
143
+ ("STD", "STD"),
144
+ ("SYP", "SYP"),
145
+ ("SZL", "SZL"),
146
+ ("THB", "THB"),
147
+ ("TJS", "TJS"),
148
+ ("TND", "TND"),
149
+ ("TOP", "TOP"),
150
+ ("TRY", "TRY"),
151
+ ("TTD", "TTD"),
152
+ ("TWD", "TWD"),
153
+ ("TZS", "TZS"),
154
+ ("UAH", "UAH"),
155
+ ("UYU", "UYU"),
156
+ ("UZS", "UZS"),
157
+ ("VEF", "VEF"),
158
+ ("VND", "VND"),
159
+ ("VUV", "VUV"),
160
+ ("WST", "WST"),
161
+ ("YER", "YER"),
162
+ ("ZAR", "ZAR"),
163
+ ],
164
+ max_length=4,
165
+ null=True,
166
+ ),
167
+ ),
168
+ ]