karrio-server-core 2025.5rc12__py3-none-any.whl → 2025.5rc13__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.

Potentially problematic release.


This version of karrio-server-core might be problematic. Click here for more details.

@@ -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
+ ]
@@ -60,3 +60,133 @@ class ServiceLevel(core.OwnedEntity):
60
60
  @property
61
61
  def object_type(self):
62
62
  return "service_level"
63
+
64
+ @property
65
+ def computed_zones(self):
66
+ """
67
+ Computed property that returns zones in legacy format for backward compatibility.
68
+ If the service belongs to a rate sheet with optimized structure, reconstruct from there.
69
+ Otherwise, fall back to the service's own zones field.
70
+ """
71
+ # Check if this service belongs to a rate sheet with optimized structure
72
+ rate_sheet = getattr(self, '_rate_sheet_cache', None)
73
+ if not rate_sheet:
74
+ # Try to find rate sheet this service belongs to
75
+ try:
76
+ rate_sheet = self.service_sheet.first()
77
+ self._rate_sheet_cache = rate_sheet
78
+ except:
79
+ rate_sheet = None
80
+
81
+ if rate_sheet and rate_sheet.zones and rate_sheet.service_rates:
82
+ # Use optimized structure
83
+ return rate_sheet.get_service_zones_legacy(self.id)
84
+ else:
85
+ # Fall back to legacy zones field
86
+ return self.zones or []
87
+
88
+ def update_zone_cell(self, zone_id: str, field: str, value):
89
+ """Update a single field in a zone by ID or index with validation"""
90
+ # Define allowed fields with their validators
91
+ allowed_fields = {
92
+ 'rate': float,
93
+ 'min_weight': float,
94
+ 'max_weight': float,
95
+ 'transit_days': int,
96
+ 'transit_time': float,
97
+ 'label': str,
98
+ 'radius': float,
99
+ 'latitude': float,
100
+ 'longitude': float,
101
+ }
102
+
103
+ if field not in allowed_fields:
104
+ raise ValueError(f"Field '{field}' is not allowed for zone updates")
105
+
106
+ # Validate and convert the value
107
+ try:
108
+ if value is not None and value != '':
109
+ value = allowed_fields[field](value)
110
+ except (ValueError, TypeError):
111
+ raise ValueError(f"Invalid value '{value}' for field '{field}' (expected {allowed_fields[field].__name__})")
112
+
113
+ zones = self.zones or []
114
+
115
+ # First try to find by zone ID
116
+ for zone in zones:
117
+ if zone.get('id') == zone_id:
118
+ zone[field] = value
119
+ self.save(update_fields=['zones'])
120
+ return zone
121
+
122
+ # Fallback: try to find by index for zones without IDs
123
+ try:
124
+ zone_index = int(zone_id)
125
+ if 0 <= zone_index < len(zones):
126
+ zones[zone_index][field] = value
127
+ self.save(update_fields=['zones'])
128
+ return zones[zone_index]
129
+ except (ValueError, IndexError):
130
+ pass
131
+
132
+ raise ValueError(f"Zone {zone_id} not found")
133
+
134
+ def batch_update_cells(self, updates: list):
135
+ """
136
+ Batch update multiple zone cells with validation
137
+ updates format: [{'zone_id': str, 'field': str, 'value': any}, ...]
138
+ """
139
+ # Define allowed fields with their validators
140
+ allowed_fields = {
141
+ 'rate': float,
142
+ 'min_weight': float,
143
+ 'max_weight': float,
144
+ 'transit_days': int,
145
+ 'transit_time': float,
146
+ 'label': str,
147
+ 'radius': float,
148
+ 'latitude': float,
149
+ 'longitude': float,
150
+ }
151
+
152
+ zones = list(self.zones or [])
153
+
154
+ for update in updates:
155
+ zone_id = update.get('zone_id')
156
+ field = update.get('field')
157
+ value = update.get('value')
158
+
159
+ if field not in allowed_fields:
160
+ raise ValueError(f"Field '{field}' is not allowed for zone updates")
161
+
162
+ # Validate and convert the value
163
+ try:
164
+ if value is not None and value != '':
165
+ value = allowed_fields[field](value)
166
+ except (ValueError, TypeError):
167
+ raise ValueError(f"Invalid value '{value}' for field '{field}' (expected {allowed_fields[field].__name__})")
168
+
169
+ # Find zone by ID first, then by index
170
+ zone_found = False
171
+ for zone in zones:
172
+ if zone.get('id') == zone_id:
173
+ zone[field] = value
174
+ zone_found = True
175
+ break
176
+
177
+ # Fallback to index if zone_id is numeric and zone not found by ID
178
+ if not zone_found:
179
+ try:
180
+ zone_index = int(zone_id)
181
+ if 0 <= zone_index < len(zones):
182
+ zones[zone_index][field] = value
183
+ zone_found = True
184
+ except (ValueError, IndexError):
185
+ pass
186
+
187
+ if not zone_found:
188
+ raise ValueError(f"Zone {zone_id} not found")
189
+
190
+ self.zones = zones
191
+ self.save(update_fields=['zones'])
192
+ return self.zones
@@ -29,6 +29,22 @@ class RateSheet(core.OwnedEntity):
29
29
  services = models.ManyToManyField(
30
30
  "ServiceLevel", blank=True, related_name="service_sheet"
31
31
  )
32
+
33
+ # New optimized structure
34
+ zones = models.JSONField(
35
+ blank=True,
36
+ null=True,
37
+ default=core.field_default([]),
38
+ help_text="Shared zone definitions: [{'id': 'zone_1', 'label': 'Zone 1', 'cities': [...], 'country_codes': [...]}]"
39
+ )
40
+ service_rates = models.JSONField(
41
+ blank=True,
42
+ null=True,
43
+ default=core.field_default([]),
44
+ help_text="Service-zone rate mapping: [{'service_id': 'svc_1', 'zone_id': 'zone_1', 'rate': 10.50}]"
45
+ )
46
+
47
+ # Keep old structure for backward compatibility during migration
32
48
  metadata = models.JSONField(
33
49
  blank=True,
34
50
  null=True,
@@ -58,3 +74,214 @@ class RateSheet(core.OwnedEntity):
58
74
  return providers.Carrier.objects.filter(
59
75
  carrier_code=self.carrier_name, rate_sheet__id=self.id
60
76
  )
77
+
78
+ def get_service_zones_legacy(self, service_id: str):
79
+ """
80
+ Backward compatible method - returns zones in old format for SDK compatibility
81
+ Combines shared zones with service-specific rates
82
+ """
83
+ zones = self.zones or []
84
+ service_rates = self.service_rates or []
85
+
86
+ # Get rates for this service
87
+ service_rate_map = {
88
+ sr['zone_id']: sr for sr in service_rates
89
+ if sr.get('service_id') == service_id
90
+ }
91
+
92
+ # Combine zone definitions with service rates
93
+ legacy_zones = []
94
+ for zone in zones:
95
+ zone_id = zone.get('id')
96
+ rate_data = service_rate_map.get(zone_id, {})
97
+
98
+ legacy_zone = {
99
+ **zone, # Zone definition (label, cities, country_codes, etc.)
100
+ 'rate': rate_data.get('rate', 0),
101
+ 'min_weight': rate_data.get('min_weight'),
102
+ 'max_weight': rate_data.get('max_weight'),
103
+ 'transit_days': rate_data.get('transit_days'),
104
+ 'transit_time': rate_data.get('transit_time'),
105
+ }
106
+ legacy_zones.append(legacy_zone)
107
+
108
+ return legacy_zones
109
+
110
+ def update_service_zone_rate(self, service_id: str, zone_id: str, field: str, value):
111
+ """
112
+ Update a rate field for a specific service-zone combination
113
+ """
114
+ allowed_fields = {
115
+ 'rate': float,
116
+ 'min_weight': float,
117
+ 'max_weight': float,
118
+ 'transit_days': int,
119
+ 'transit_time': float,
120
+ }
121
+
122
+ if field not in allowed_fields:
123
+ raise ValueError(f"Field '{field}' is not allowed for rate updates")
124
+
125
+ # Validate value
126
+ try:
127
+ if value is not None and value != '':
128
+ value = allowed_fields[field](value)
129
+ except (ValueError, TypeError):
130
+ raise ValueError(f"Invalid value '{value}' for field '{field}'")
131
+
132
+ service_rates = list(self.service_rates or [])
133
+
134
+ # Find existing rate record
135
+ for rate_record in service_rates:
136
+ if (rate_record.get('service_id') == service_id and
137
+ rate_record.get('zone_id') == zone_id):
138
+ rate_record[field] = value
139
+ break
140
+ else:
141
+ # Create new rate record
142
+ service_rates.append({
143
+ 'service_id': service_id,
144
+ 'zone_id': zone_id,
145
+ field: value
146
+ })
147
+
148
+ self.service_rates = service_rates
149
+ self.save(update_fields=['service_rates'])
150
+
151
+ def batch_update_service_rates(self, updates):
152
+ """
153
+ Batch update service rates
154
+ updates format: [{'service_id': str, 'zone_id': str, 'field': str, 'value': any}]
155
+ """
156
+ allowed_fields = {
157
+ 'rate': float,
158
+ 'min_weight': float,
159
+ 'max_weight': float,
160
+ 'transit_days': int,
161
+ 'transit_time': float,
162
+ }
163
+
164
+ service_rates = list(self.service_rates or [])
165
+ service_rate_map = {}
166
+
167
+ # Create lookup map for existing rates
168
+ for i, rate in enumerate(service_rates):
169
+ key = f"{rate.get('service_id')}:{rate.get('zone_id')}"
170
+ service_rate_map[key] = i
171
+
172
+ for update in updates:
173
+ service_id = update.get('service_id')
174
+ zone_id = update.get('zone_id')
175
+ field = update.get('field')
176
+ value = update.get('value')
177
+
178
+ if field not in allowed_fields:
179
+ continue
180
+
181
+ # Validate value
182
+ try:
183
+ if value is not None and value != '':
184
+ value = allowed_fields[field](value)
185
+ except (ValueError, TypeError):
186
+ continue
187
+
188
+ key = f"{service_id}:{zone_id}"
189
+
190
+ if key in service_rate_map:
191
+ # Update existing rate
192
+ service_rates[service_rate_map[key]][field] = value
193
+ else:
194
+ # Create new rate record
195
+ new_rate = {
196
+ 'service_id': service_id,
197
+ 'zone_id': zone_id,
198
+ field: value
199
+ }
200
+ service_rates.append(new_rate)
201
+ service_rate_map[key] = len(service_rates) - 1
202
+
203
+ self.service_rates = service_rates
204
+ self.save(update_fields=['service_rates'])
205
+
206
+ def add_zone(self, zone_data):
207
+ """
208
+ Add a new shared zone definition
209
+ """
210
+ zones = list(self.zones or [])
211
+
212
+ # Generate zone ID if not provided
213
+ if not zone_data.get('id'):
214
+ zone_data['id'] = f"zone_{len(zones) + 1}"
215
+
216
+ zones.append(zone_data)
217
+ self.zones = zones
218
+ self.save(update_fields=['zones'])
219
+ return zone_data['id']
220
+
221
+ def remove_zone(self, zone_id: str):
222
+ """
223
+ Remove a zone and all its associated rates
224
+ """
225
+ # Remove zone definition
226
+ zones = [z for z in (self.zones or []) if z.get('id') != zone_id]
227
+ self.zones = zones
228
+
229
+ # Remove all rates for this zone
230
+ service_rates = [sr for sr in (self.service_rates or []) if sr.get('zone_id') != zone_id]
231
+ self.service_rates = service_rates
232
+
233
+ self.save(update_fields=['zones', 'service_rates'])
234
+
235
+ def migrate_from_legacy_format(self):
236
+ """
237
+ Migrate from old format where zones are stored per service to new shared format
238
+ """
239
+ if self.zones or self.service_rates:
240
+ # Already in new format
241
+ return
242
+
243
+ all_zones = {}
244
+ service_rates = []
245
+ zone_counter = 1
246
+
247
+ # Extract unique zones across all services
248
+ for service in self.services.all():
249
+ service_zones = service.zones or []
250
+
251
+ for zone_index, zone_data in enumerate(service_zones):
252
+ # Create zone signature for deduplication
253
+ zone_signature = {
254
+ 'label': zone_data.get('label', f'Zone {zone_index + 1}'),
255
+ 'cities': sorted(zone_data.get('cities', [])),
256
+ 'postal_codes': sorted(zone_data.get('postal_codes', [])),
257
+ 'country_codes': sorted(zone_data.get('country_codes', [])),
258
+ }
259
+
260
+ # Use signature as key for deduplication
261
+ sig_key = str(zone_signature)
262
+
263
+ if sig_key not in all_zones:
264
+ zone_id = f"zone_{zone_counter}"
265
+ all_zones[sig_key] = {
266
+ 'id': zone_id,
267
+ **zone_signature
268
+ }
269
+ zone_counter += 1
270
+
271
+ zone_id = all_zones[sig_key]['id']
272
+
273
+ # Store service rate
274
+ service_rates.append({
275
+ 'service_id': service.id,
276
+ 'zone_id': zone_id,
277
+ 'rate': zone_data.get('rate', 0),
278
+ 'min_weight': zone_data.get('min_weight'),
279
+ 'max_weight': zone_data.get('max_weight'),
280
+ 'transit_days': zone_data.get('transit_days'),
281
+ 'transit_time': zone_data.get('transit_time'),
282
+ })
283
+
284
+ # Save optimized structure
285
+ self.zones = list(all_zones.values())
286
+ self.service_rates = service_rates
287
+ self.save(update_fields=['zones', 'service_rates'])
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: karrio_server_core
3
- Version: 2025.5rc12
3
+ Version: 2025.5rc13
4
4
  Summary: Multi-carrier shipping API Core module
5
5
  Author-email: karrio <hello@karrio.io>
6
6
  License-Expression: Apache-2.0
@@ -106,6 +106,7 @@ karrio/server/providers/extension/models/usps_international.py,sha256=k3opE3W4sv
106
106
  karrio/server/providers/extension/models/usps_wt.py,sha256=6RkR8mnFT9AWMEtW7l-ZB1BY9TkEYnLKtIiLyhwIqw0,731
107
107
  karrio/server/providers/extension/models/usps_wt_international.py,sha256=5QJdeNeshl1KCbQ6ravoh3vrU94QEN89noYZEF9kAW0,825
108
108
  karrio/server/providers/extension/models/zoom2u.py,sha256=3RvkZkAYvpXDW9fCmViOJB5NrSPo2_d_qpjcsWuCVtQ,585
109
+ karrio/server/providers/management/commands/migrate_rate_sheets.py,sha256=piRV1n0itHQR4HZiWXVSjJKzHZPnFGKlrCjWOcy723A,3814
109
110
  karrio/server/providers/migrations/0001_initial.py,sha256=DkKU91a1tMqRisLoarYndyKX3PJplT4Bor6tT53yETw,6772
110
111
  karrio/server/providers/migrations/0002_carrier_active.py,sha256=zW_6cFEKmsZIbiuLimcUlwzVvpckYroJmzkCwep-D2A,378
111
112
  karrio/server/providers/migrations/0003_auto_20201230_0820.py,sha256=qkvoSgtkGxg0XX0Srd35hanzl5tI7bBo3znli_h002g,699
@@ -187,12 +188,14 @@ karrio/server/providers/migrations/0078_auto_20240813_1552.py,sha256=1DXKf6Ak_GP
187
188
  karrio/server/providers/migrations/0079_alter_carrier_options_alter_ratesheet_created_by.py,sha256=wppkE5LZaPiedrLMAkUpVly5FlSg6zbLBzO7a_yyUuk,864
188
189
  karrio/server/providers/migrations/0080_alter_aramexsettings_account_country_code_and_more.py,sha256=ETTlvcAP1kk9Sr4u6f2EaG0hXcIaYVZKz0c6k7f-Cg8,101848
189
190
  karrio/server/providers/migrations/0081_remove_alliedexpresssettings_carrier_ptr_and_more.py,sha256=Lb8w5LEnJgCd-a4suFAdZ3-gWHlIbLIJLahTGlUmi_U,9557
191
+ karrio/server/providers/migrations/0082_add_zone_identifiers.py,sha256=0gRIFemNGNRkvV_pt0u3e-wL2LI89AKwaFWoqOqjAsA,1509
192
+ karrio/server/providers/migrations/0083_add_optimized_rate_sheet_structure.py,sha256=K9jj1LGEs5WQvIUxWac1wbCI6YnrrzTIcD3PzjZZcHA,1057
190
193
  karrio/server/providers/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
191
194
  karrio/server/providers/models/__init__.py,sha256=7D-pgQDTDSfptHhWxfJlOzSQqvYB_71D22zmNqjuBqk,628
192
195
  karrio/server/providers/models/carrier.py,sha256=E3zPcypTAQi1ninAQjKbelGAV8cZI8g_voRJ2KETrxQ,9436
193
196
  karrio/server/providers/models/config.py,sha256=8zuDXFGXBrC4fj2dW1-NpLAtFYPunEJi8uK-PezD7eo,759
194
- karrio/server/providers/models/service.py,sha256=_iT6Cty3HJJmjEXB423PjhEcQ4ArtMPKaxBZYTmhdaY,2161
195
- karrio/server/providers/models/sheet.py,sha256=GokxEfSAkjtKw5AkCtN0enP204RWRI0ZcQI5Lz-XgBU,1683
197
+ karrio/server/providers/models/service.py,sha256=XTkZKyzE2g0I7HxGluty6Nk4El7iSyj2Ze0YXnrF7WQ,7038
198
+ karrio/server/providers/models/sheet.py,sha256=Fuy8KIA719ZOeoqe-VfL6ZjLjhzdYS6Jp-hgE9680u4,9943
196
199
  karrio/server/providers/models/template.py,sha256=rrQXfimqFEFkelKzjyVeAruiu3UmrhpOT4M7JrGOtI0,1109
197
200
  karrio/server/providers/models/utils.py,sha256=fuvrpDz2KzcRC7w94zFQAaI4qtQPvs8f1r6jZ5xM-64,1703
198
201
  karrio/server/providers/serializers/__init__.py,sha256=bc5x1z7wkHyrHA2jP1wZVQF5SoPG-ueRVfn1RndK8VM,140
@@ -235,7 +238,7 @@ karrio/server/user/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5N
235
238
  karrio/server/user/templates/registration/login.html,sha256=3_tj-0rKfwkCk-fp_GT8xFQhLqjGcJs3uZzOAaI40Sw,3690
236
239
  karrio/server/user/templates/registration/registration_confirm_email.html,sha256=zFDkNN_BHMQyrBv_mU8aoqXxYxG91TGuf6pKwRa5jxE,247
237
240
  karrio/server/user/templates/registration/registration_confirm_email.txt,sha256=I_zN_pJTRigfyiYbyQK0wFfrI5Zq1JG8lf0TyLA9fN0,94
238
- karrio_server_core-2025.5rc12.dist-info/METADATA,sha256=mUiD-2FjXZNzxvJy-7eXCtYoIWjUCN2ra_z07f5E7Uc,797
239
- karrio_server_core-2025.5rc12.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
240
- karrio_server_core-2025.5rc12.dist-info/top_level.txt,sha256=D1D7x8R3cTfjF_15mfiO7wCQ5QMtuM4x8GaPr7z5i78,12
241
- karrio_server_core-2025.5rc12.dist-info/RECORD,,
241
+ karrio_server_core-2025.5rc13.dist-info/METADATA,sha256=U9GaWNyppHXD7rf279KJ33gn2l_Xv-fybNL9Im13L44,797
242
+ karrio_server_core-2025.5rc13.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
243
+ karrio_server_core-2025.5rc13.dist-info/top_level.txt,sha256=D1D7x8R3cTfjF_15mfiO7wCQ5QMtuM4x8GaPr7z5i78,12
244
+ karrio_server_core-2025.5rc13.dist-info/RECORD,,