karrio-server-core 2025.5__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 (213) hide show
  1. karrio/server/conf.py +54 -0
  2. karrio/server/core/__init__.py +3 -0
  3. karrio/server/core/admin.py +1 -0
  4. karrio/server/core/apps.py +10 -0
  5. karrio/server/core/authentication.py +347 -0
  6. karrio/server/core/config.py +31 -0
  7. karrio/server/core/context_processors.py +12 -0
  8. karrio/server/core/datatypes.py +394 -0
  9. karrio/server/core/dataunits.py +187 -0
  10. karrio/server/core/exceptions.py +404 -0
  11. karrio/server/core/fields.py +12 -0
  12. karrio/server/core/filters.py +837 -0
  13. karrio/server/core/gateway.py +1011 -0
  14. karrio/server/core/logging.py +403 -0
  15. karrio/server/core/management/commands/cli.py +19 -0
  16. karrio/server/core/management/commands/create_oauth_client.py +41 -0
  17. karrio/server/core/management/commands/runserver.py +5 -0
  18. karrio/server/core/middleware.py +197 -0
  19. karrio/server/core/migrations/0001_initial.py +28 -0
  20. karrio/server/core/migrations/0002_apilogindex.py +69 -0
  21. karrio/server/core/migrations/0003_apilogindex_test_mode.py +62 -0
  22. karrio/server/core/migrations/0004_metafield.py +74 -0
  23. karrio/server/core/migrations/0005_alter_metafield_type_alter_metafield_value.py +23 -0
  24. karrio/server/core/migrations/0006_add_api_log_requested_at_index.py +22 -0
  25. karrio/server/core/migrations/__init__.py +0 -0
  26. karrio/server/core/models/__init__.py +48 -0
  27. karrio/server/core/models/base.py +103 -0
  28. karrio/server/core/models/entity.py +24 -0
  29. karrio/server/core/models/metafield.py +144 -0
  30. karrio/server/core/models/third_party.py +21 -0
  31. karrio/server/core/oauth_validators.py +170 -0
  32. karrio/server/core/permissions.py +36 -0
  33. karrio/server/core/renderers.py +11 -0
  34. karrio/server/core/router.py +3 -0
  35. karrio/server/core/serializers.py +1971 -0
  36. karrio/server/core/signals.py +55 -0
  37. karrio/server/core/telemetry.py +573 -0
  38. karrio/server/core/tests.py +99 -0
  39. karrio/server/core/tests_resource_token.py +411 -0
  40. karrio/server/core/urls.py +12 -0
  41. karrio/server/core/utils.py +1025 -0
  42. karrio/server/core/validators.py +264 -0
  43. karrio/server/core/views/__init__.py +2 -0
  44. karrio/server/core/views/api.py +133 -0
  45. karrio/server/core/views/metadata.py +44 -0
  46. karrio/server/core/views/oauth.py +75 -0
  47. karrio/server/core/views/references.py +82 -0
  48. karrio/server/core/views/schema.py +310 -0
  49. karrio/server/filters/__init__.py +2 -0
  50. karrio/server/filters/abstract.py +26 -0
  51. karrio/server/iam/__init__.py +0 -0
  52. karrio/server/iam/admin.py +3 -0
  53. karrio/server/iam/apps.py +21 -0
  54. karrio/server/iam/migrations/0001_initial.py +33 -0
  55. karrio/server/iam/migrations/__init__.py +0 -0
  56. karrio/server/iam/models.py +48 -0
  57. karrio/server/iam/permissions.py +155 -0
  58. karrio/server/iam/serializers.py +54 -0
  59. karrio/server/iam/signals.py +18 -0
  60. karrio/server/iam/tests.py +3 -0
  61. karrio/server/iam/views.py +3 -0
  62. karrio/server/openapi.py +75 -0
  63. karrio/server/providers/__init__.py +1 -0
  64. karrio/server/providers/admin.py +364 -0
  65. karrio/server/providers/apps.py +10 -0
  66. karrio/server/providers/management/commands/migrate_rate_sheets.py +101 -0
  67. karrio/server/providers/migrations/0001_initial.py +140 -0
  68. karrio/server/providers/migrations/0002_carrier_active.py +18 -0
  69. karrio/server/providers/migrations/0003_auto_20201230_0820.py +24 -0
  70. karrio/server/providers/migrations/0004_auto_20210212_0554.py +178 -0
  71. karrio/server/providers/migrations/0005_auto_20210212_0555.py +18 -0
  72. karrio/server/providers/migrations/0006_australiapostsettings.py +29 -0
  73. karrio/server/providers/migrations/0007_auto_20210213_0206.py +21 -0
  74. karrio/server/providers/migrations/0008_auto_20210214_0409.py +30 -0
  75. karrio/server/providers/migrations/0009_auto_20210308_0302.py +18 -0
  76. karrio/server/providers/migrations/0010_auto_20210409_0852.py +32 -0
  77. karrio/server/providers/migrations/0011_auto_20210409_0853.py +21 -0
  78. karrio/server/providers/migrations/0012_alter_carrier_options.py +17 -0
  79. karrio/server/providers/migrations/0013_tntsettings.py +30 -0
  80. karrio/server/providers/migrations/0014_auto_20210612_1608.py +46 -0
  81. karrio/server/providers/migrations/0015_auto_20210615_1601.py +28 -0
  82. karrio/server/providers/migrations/0016_alter_purolatorsettings_user_token.py +18 -0
  83. karrio/server/providers/migrations/0017_auto_20210805_0359.py +1293 -0
  84. karrio/server/providers/migrations/0018_alter_fedexsettings_user_key.py +18 -0
  85. karrio/server/providers/migrations/0019_dhlpolandsettings_servicelevel.py +65 -0
  86. karrio/server/providers/migrations/0020_genericsettings_labeltemplate.py +52 -0
  87. karrio/server/providers/migrations/0021_auto_20211231_2353.py +40 -0
  88. karrio/server/providers/migrations/0022_carrier_metadata.py +18 -0
  89. karrio/server/providers/migrations/0023_auto_20220124_1916.py +27 -0
  90. karrio/server/providers/migrations/0024_alter_genericsettings_custom_carrier_name.py +19 -0
  91. karrio/server/providers/migrations/0025_alter_servicelevel_service_code.py +19 -0
  92. karrio/server/providers/migrations/0026_auto_20220208_0132.py +59 -0
  93. karrio/server/providers/migrations/0027_auto_20220304_1340.py +29 -0
  94. karrio/server/providers/migrations/0028_auto_20220323_1500.py +33 -0
  95. karrio/server/providers/migrations/0029_easypostsettings.py +27 -0
  96. karrio/server/providers/migrations/0030_amazonmwssettings.py +29 -0
  97. karrio/server/providers/migrations/0031_delete_amazonmwssettings.py +18 -0
  98. karrio/server/providers/migrations/0032_alter_carrier_test.py +18 -0
  99. karrio/server/providers/migrations/0033_auto_20220708_1350.py +22 -0
  100. karrio/server/providers/migrations/0034_amazonmwssettings_dpdhlsettings.py +47 -0
  101. karrio/server/providers/migrations/0035_alter_carrier_capabilities.py +43 -0
  102. karrio/server/providers/migrations/0036_upsfreightsettings.py +31 -0
  103. karrio/server/providers/migrations/0037_chronopostsettings.py +29 -0
  104. karrio/server/providers/migrations/0038_alter_genericsettings_label_template.py +19 -0
  105. karrio/server/providers/migrations/0039_auto_20220906_0612.py +23 -0
  106. karrio/server/providers/migrations/0040_dpdhlsettings_services.py +18 -0
  107. karrio/server/providers/migrations/0041_auto_20221105_0705.py +38 -0
  108. karrio/server/providers/migrations/0042_auto_20221215_1642.py +23 -0
  109. karrio/server/providers/migrations/0043_alter_genericsettings_account_number_and_more.py +39 -0
  110. karrio/server/providers/migrations/0044_carrier_carrier_capabilities.py +64 -0
  111. karrio/server/providers/migrations/0045_alter_carrier_active_alter_carrier_carrier_id.py +31 -0
  112. karrio/server/providers/migrations/0046_remove_dpdhlsettings_signature_and_more.py +41 -0
  113. karrio/server/providers/migrations/0047_dpdsettings.py +286 -0
  114. karrio/server/providers/migrations/0048_servicelevel_min_weight_servicelevel_transit_days_and_more.py +64 -0
  115. karrio/server/providers/migrations/0049_boxknightsettings_geodissettings_lapostesettings_and_more.py +156 -0
  116. karrio/server/providers/migrations/0050_carrier_is_system_alter_carrier_metadata_and_more.py +106 -0
  117. karrio/server/providers/migrations/0051_rename_username_upssettings_client_id_and_more.py +31 -0
  118. karrio/server/providers/migrations/0052_alter_upssettings_account_number_and_more.py +20 -0
  119. karrio/server/providers/migrations/0053_locate2usettings.py +281 -0
  120. karrio/server/providers/migrations/0054_zoom2usettings.py +280 -0
  121. karrio/server/providers/migrations/0055_rename_amazonmwssettings_amazonshippingsettings_and_more.py +44 -0
  122. karrio/server/providers/migrations/0056_asendiaussettings_geodissettings_code_client_and_more.py +75 -0
  123. karrio/server/providers/migrations/0057_alter_servicelevel_weight_unit_belgianpostsettings.py +51 -0
  124. karrio/server/providers/migrations/0058_alliedexpresssettings.py +38 -0
  125. karrio/server/providers/migrations/0059_ratesheet.py +81 -0
  126. karrio/server/providers/migrations/0060_belgianpostsettings_rate_sheet_and_more.py +73 -0
  127. karrio/server/providers/migrations/0061_alliedexpresssettings_service_type.py +17 -0
  128. karrio/server/providers/migrations/0062_sendlesettings_account_country_code.py +257 -0
  129. karrio/server/providers/migrations/0063_servicelevel_metadata.py +25 -0
  130. karrio/server/providers/migrations/0064_alliedexpresslocalsettings.py +43 -0
  131. karrio/server/providers/migrations/0065_servicelevel_carrier_service_code_and_more.py +66 -0
  132. karrio/server/providers/migrations/0066_rename_fedexsettings_fedexwssettings_and_more.py +28 -0
  133. karrio/server/providers/migrations/0067_fedexsettings.py +283 -0
  134. karrio/server/providers/migrations/0068_fedexsettings_track_api_key_and_more.py +38 -0
  135. karrio/server/providers/migrations/0069_alter_canadapostsettings_contract_id_and_more.py +23 -0
  136. karrio/server/providers/migrations/0070_tgesettings_alter_carrier_capabilities.py +65 -0
  137. karrio/server/providers/migrations/0071_alter_tgesettings_my_toll_token.py +18 -0
  138. karrio/server/providers/migrations/0072_rename_eshippersettings_eshipperxmlsettings_and_more.py +28 -0
  139. karrio/server/providers/migrations/0073_delete_eshipperxmlsettings.py +41 -0
  140. karrio/server/providers/migrations/0074_eshippersettings.py +38 -0
  141. karrio/server/providers/migrations/0075_haypostsettings.py +40 -0
  142. karrio/server/providers/migrations/0076_rename_customer_registration_id_uspsinternationalsettings_account_number_and_more.py +125 -0
  143. karrio/server/providers/migrations/0077_uspswtinternationalsettings_uspswtsettings_and_more.py +165 -0
  144. karrio/server/providers/migrations/0078_auto_20240813_1552.py +120 -0
  145. karrio/server/providers/migrations/0079_alter_carrier_options_alter_ratesheet_created_by.py +31 -0
  146. karrio/server/providers/migrations/0080_alter_aramexsettings_account_country_code_and_more.py +3025 -0
  147. karrio/server/providers/migrations/0081_remove_alliedexpresssettings_carrier_ptr_and_more.py +338 -0
  148. karrio/server/providers/migrations/0082_add_zone_identifiers.py +50 -0
  149. karrio/server/providers/migrations/0083_add_optimized_rate_sheet_structure.py +33 -0
  150. karrio/server/providers/migrations/0084_alter_servicelevel_currency.py +168 -0
  151. karrio/server/providers/migrations/0085_populate_dhl_parcel_de_oauth_credentials.py +82 -0
  152. karrio/server/providers/migrations/0086_rename_dhl_parcel_de_customer_number_to_billing_number.py +71 -0
  153. karrio/server/providers/migrations/__init__.py +0 -0
  154. karrio/server/providers/models/__init__.py +16 -0
  155. karrio/server/providers/models/carrier.py +387 -0
  156. karrio/server/providers/models/config.py +30 -0
  157. karrio/server/providers/models/service.py +192 -0
  158. karrio/server/providers/models/sheet.py +287 -0
  159. karrio/server/providers/models/template.py +39 -0
  160. karrio/server/providers/models/utils.py +58 -0
  161. karrio/server/providers/router.py +3 -0
  162. karrio/server/providers/serializers/__init__.py +3 -0
  163. karrio/server/providers/serializers/base.py +538 -0
  164. karrio/server/providers/signals.py +25 -0
  165. karrio/server/providers/templates/providers/oauth_callback.html +105 -0
  166. karrio/server/providers/tests/__init__.py +5 -0
  167. karrio/server/providers/tests/test_connections.py +895 -0
  168. karrio/server/providers/urls.py +11 -0
  169. karrio/server/providers/views/__init__.py +0 -0
  170. karrio/server/providers/views/carriers.py +267 -0
  171. karrio/server/providers/views/connections.py +496 -0
  172. karrio/server/samples.py +352 -0
  173. karrio/server/serializers/__init__.py +2 -0
  174. karrio/server/serializers/abstract.py +602 -0
  175. karrio/server/tracing/__init__.py +0 -0
  176. karrio/server/tracing/admin.py +63 -0
  177. karrio/server/tracing/apps.py +8 -0
  178. karrio/server/tracing/migrations/0001_initial.py +41 -0
  179. karrio/server/tracing/migrations/0002_auto_20220710_1307.py +22 -0
  180. karrio/server/tracing/migrations/0003_auto_20221105_0317.py +43 -0
  181. karrio/server/tracing/migrations/0004_tracingrecord_carrier_account_idx.py +24 -0
  182. karrio/server/tracing/migrations/0005_optimise_tracingrecord_request_log_idx.py +25 -0
  183. karrio/server/tracing/migrations/0006_alter_tracingrecord_options_and_more.py +49 -0
  184. karrio/server/tracing/migrations/0007_tracingrecord_tracing_created_at_idx.py +19 -0
  185. karrio/server/tracing/migrations/__init__.py +0 -0
  186. karrio/server/tracing/models.py +82 -0
  187. karrio/server/tracing/tests.py +3 -0
  188. karrio/server/tracing/utils.py +109 -0
  189. karrio/server/user/__init__.py +0 -0
  190. karrio/server/user/admin.py +96 -0
  191. karrio/server/user/apps.py +7 -0
  192. karrio/server/user/forms.py +35 -0
  193. karrio/server/user/migrations/0001_initial.py +41 -0
  194. karrio/server/user/migrations/0002_token.py +29 -0
  195. karrio/server/user/migrations/0003_token_test_mode.py +20 -0
  196. karrio/server/user/migrations/0004_group.py +26 -0
  197. karrio/server/user/migrations/0005_token_label.py +21 -0
  198. karrio/server/user/migrations/0006_workspaceconfig.py +63 -0
  199. karrio/server/user/migrations/0007_user_metadata.py +25 -0
  200. karrio/server/user/migrations/__init__.py +0 -0
  201. karrio/server/user/models.py +218 -0
  202. karrio/server/user/serializers.py +47 -0
  203. karrio/server/user/templates/registration/login.html +108 -0
  204. karrio/server/user/templates/registration/registration_confirm_email.html +10 -0
  205. karrio/server/user/templates/registration/registration_confirm_email.txt +3 -0
  206. karrio/server/user/tests.py +3 -0
  207. karrio/server/user/urls.py +10 -0
  208. karrio/server/user/utils.py +60 -0
  209. karrio/server/user/views.py +9 -0
  210. karrio_server_core-2025.5.dist-info/METADATA +32 -0
  211. karrio_server_core-2025.5.dist-info/RECORD +213 -0
  212. karrio_server_core-2025.5.dist-info/WHEEL +5 -0
  213. karrio_server_core-2025.5.dist-info/top_level.txt +2 -0
@@ -0,0 +1,287 @@
1
+ import functools
2
+ import django.db.models as models
3
+ import django.utils.translation as translation
4
+
5
+ import django.conf as conf
6
+ import karrio.server.core.models as core
7
+
8
+ _ = translation.gettext_lazy
9
+
10
+
11
+ @core.register_model
12
+ class RateSheet(core.OwnedEntity):
13
+ class Meta:
14
+ db_table = "rate-sheet"
15
+ verbose_name = "Rate Sheet"
16
+ verbose_name_plural = "Rate Sheets"
17
+ ordering = ["-created_at"]
18
+
19
+ id = models.CharField(
20
+ max_length=50,
21
+ editable=False,
22
+ primary_key=True,
23
+ default=functools.partial(core.uuid, prefix="rsht_"),
24
+ )
25
+ name = models.CharField(_("name"), max_length=50, db_index=True)
26
+ slug = models.CharField(_("slug"), max_length=50, db_index=True)
27
+ carrier_name = models.CharField(max_length=50, db_index=True)
28
+ is_system = models.BooleanField(default=False, db_index=True)
29
+ services = models.ManyToManyField(
30
+ "ServiceLevel", blank=True, related_name="service_sheet"
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
48
+ metadata = models.JSONField(
49
+ blank=True,
50
+ null=True,
51
+ default=core.field_default({}),
52
+ )
53
+
54
+ created_by = models.ForeignKey(
55
+ conf.settings.AUTH_USER_MODEL,
56
+ blank=True,
57
+ null=True,
58
+ on_delete=models.CASCADE,
59
+ editable=False,
60
+ )
61
+
62
+ def delete(self, *args, **kwargs):
63
+ self.services.all().delete()
64
+ return super().delete(*args, **kwargs)
65
+
66
+ @property
67
+ def object_type(self):
68
+ return "rate-sheet"
69
+
70
+ @property
71
+ def carriers(self):
72
+ import karrio.server.providers.models as providers
73
+
74
+ return providers.Carrier.objects.filter(
75
+ carrier_code=self.carrier_name, rate_sheet__id=self.id
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'])
@@ -0,0 +1,39 @@
1
+ import functools
2
+ import django.db.models as models
3
+ import django.core.validators as validators
4
+ import karrio.server.core.models as core
5
+
6
+
7
+ LABEL_TEMPLATE_TYPES = [
8
+ ("SVG", "SVG"),
9
+ ("ZPL", "ZPL"),
10
+ ]
11
+
12
+
13
+ @core.register_model
14
+ class LabelTemplate(core.OwnedEntity):
15
+ class Meta:
16
+ db_table = "label-template"
17
+ verbose_name = "Label Template"
18
+ verbose_name_plural = "Label Templates"
19
+ ordering = ["-created_at"]
20
+
21
+ id = models.CharField(
22
+ max_length=50,
23
+ primary_key=True,
24
+ default=functools.partial(core.uuid, prefix="tpl_"),
25
+ editable=False,
26
+ )
27
+ slug = models.SlugField(
28
+ max_length=30,
29
+ validators=[validators.RegexValidator(r"^[a-z0-9_]+$")],
30
+ )
31
+ template = models.TextField()
32
+ template_type = models.CharField(max_length=3, choices=LABEL_TEMPLATE_TYPES)
33
+ width = models.IntegerField(null=True, blank=True)
34
+ height = models.IntegerField(null=True, blank=True)
35
+ shipment_sample = models.JSONField(blank=True, null=True, default=dict)
36
+
37
+ @property
38
+ def object_type(self):
39
+ return "label_template"
@@ -0,0 +1,58 @@
1
+ import django.db.models as models
2
+ import django.core.cache as caching
3
+
4
+ import karrio.lib as lib
5
+
6
+
7
+ def has_rate_sheet(carrier_name: str):
8
+ def decorator(model: models.Model):
9
+ # Add a rate sheet relation to the model
10
+ model.add_to_class(
11
+ "rate_sheet",
12
+ models.ForeignKey(
13
+ "RateSheet",
14
+ null=True,
15
+ blank=True,
16
+ on_delete=models.SET_NULL,
17
+ ),
18
+ )
19
+
20
+ # Add a service list property to the model
21
+ setattr(
22
+ model,
23
+ "service_list",
24
+ property(
25
+ lambda self: (
26
+ self.services
27
+ if self.rate_sheet is None and hasattr(self, "services")
28
+ else self.rate_sheet.services.all()
29
+ )
30
+ ),
31
+ )
32
+
33
+ # Add a default services property to the model
34
+ # skip if it already exists (overridden)
35
+ if not hasattr(model, "default_services"):
36
+ setattr(
37
+ model,
38
+ "default_services",
39
+ property(
40
+ lambda self: lib.to_dict(
41
+ getattr(
42
+ getattr(
43
+ __import__(
44
+ f"karrio.mappers.{carrier_name}", fromlist=["units"]
45
+ ),
46
+ "units",
47
+ None,
48
+ ),
49
+ "DEFAULT_SERVICES",
50
+ [],
51
+ )
52
+ )
53
+ ),
54
+ )
55
+
56
+ return model
57
+
58
+ return decorator
@@ -0,0 +1,3 @@
1
+ from rest_framework.routers import DefaultRouter
2
+
3
+ router = DefaultRouter(trailing_slash=False)
@@ -0,0 +1,3 @@
1
+ from karrio.server.serializers import *
2
+ from karrio.server.core.serializers import *
3
+ from karrio.server.providers.serializers.base import *