karrio-server-manager 2026.1.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 +226 -171
  23. karrio/server/manager/serializers/tracking.py +45 -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 +4 -3
  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.1.dist-info → karrio_server_manager-2026.1.3.dist-info}/METADATA +1 -1
  38. {karrio_server_manager-2026.1.1.dist-info → karrio_server_manager-2026.1.3.dist-info}/RECORD +40 -28
  39. {karrio_server_manager-2026.1.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.1.dist-info → karrio_server_manager-2026.1.3.dist-info}/top_level.txt +0 -0
@@ -74,7 +74,7 @@ class DocumentList(GenericAPIView):
74
74
  models.Shipment.access_by(request)
75
75
  .filter(
76
76
  pk=request.data.get("shipment_id"),
77
- selected_rate_carrier__isnull=False,
77
+ selected_rate__isnull=False, # Ensure shipment was purchased (has carrier info)
78
78
  )
79
79
  .first()
80
80
  )
@@ -36,6 +36,14 @@ class ParcelList(GenericAPIView):
36
36
  operation_id=f"{ENDPOINT_ID}list",
37
37
  extensions={"x-operationId": "listParcels"},
38
38
  summary="List all parcels",
39
+ description="""
40
+ Retrieve all stored parcels.
41
+
42
+ Query Parameters:
43
+ - label: Filter by meta.label (case-insensitive contains)
44
+ - keyword: Search by label
45
+ - usage: Filter by meta.usage (exact match in array)
46
+ """,
39
47
  responses={
40
48
  200: Parcels(),
41
49
  404: ErrorResponse(),
@@ -46,14 +54,29 @@ class ParcelList(GenericAPIView):
46
54
  """
47
55
  Retrieve all stored parcels.
48
56
  """
49
- parcels = models.Parcel.access_by(request).filter(
57
+ queryset = models.Parcel.access_by(request).filter(
50
58
  **{
51
59
  f"{prop}__isnull": True
52
60
  for prop in models.Parcel.HIDDEN_PROPS
53
61
  if prop != "org"
54
62
  }
55
63
  )
56
- serializer = Parcel(parcels, many=True)
64
+
65
+ # Apply query parameter filters
66
+ label = request.query_params.get("label")
67
+ keyword = request.query_params.get("keyword")
68
+ usage = request.query_params.get("usage")
69
+
70
+ if label:
71
+ queryset = queryset.filter(meta__label__icontains=label)
72
+
73
+ if keyword:
74
+ queryset = queryset.filter(meta__label__icontains=keyword)
75
+
76
+ if usage:
77
+ queryset = queryset.filter(meta__usage__contains=usage)
78
+
79
+ serializer = Parcel(queryset, many=True)
57
80
  response = self.paginate_queryset(serializer.data)
58
81
 
59
82
  return self.get_paginated_response(response)
@@ -0,0 +1,239 @@
1
+ """
2
+ Product REST API Views
3
+
4
+ Provides full CRUD operations for product templates (commodities with meta.label).
5
+ Products are reusable commodity templates for customs declarations and shipment items.
6
+
7
+ Endpoints:
8
+ GET /v1/products - List all products
9
+ POST /v1/products - Create a new product
10
+ GET /v1/products/<pk> - Retrieve a product
11
+ PATCH /v1/products/<pk> - Update a product
12
+ DELETE /v1/products/<pk> - Delete a product
13
+ """
14
+ import logging
15
+
16
+ from django.urls import path
17
+ from rest_framework import status
18
+ from rest_framework.request import Request
19
+ from rest_framework.response import Response
20
+ from rest_framework.pagination import LimitOffsetPagination
21
+
22
+ from karrio.server.manager.router import router
23
+ from karrio.server.core.views.api import GenericAPIView, APIView
24
+ from karrio.server.manager.serializers import (
25
+ PaginatedResult,
26
+ ErrorResponse,
27
+ CommodityData,
28
+ Commodity,
29
+ CommoditySerializer,
30
+ can_mutate_commodity,
31
+ )
32
+ import karrio.server.manager.models as models
33
+ import karrio.server.openapi as openapi
34
+
35
+ logger = logging.getLogger(__name__)
36
+
37
+ # Unique endpoint ID for OpenAPI operation IDs
38
+ ENDPOINT_ID = "$&" # This endpoint id is used to make operation ids unique make sure not to duplicate
39
+
40
+ # Response serializer for paginated list
41
+ Products = PaginatedResult("ProductList", Commodity)
42
+
43
+
44
+ class ProductList(GenericAPIView):
45
+ """
46
+ List and create product templates.
47
+
48
+ Products are commodity templates that can be reused across shipments
49
+ and customs declarations. They are identified by having a `meta.label` field.
50
+ """
51
+
52
+ queryset = models.Commodity.objects
53
+ pagination_class = type(
54
+ "CustomPagination", (LimitOffsetPagination,), dict(default_limit=20)
55
+ )
56
+ serializer_class = Products
57
+
58
+ @openapi.extend_schema(
59
+ tags=["Products"],
60
+ operation_id=f"{ENDPOINT_ID}list",
61
+ extensions={"x-operationId": "listProducts"},
62
+ summary="List all products",
63
+ description="""
64
+ Retrieve all product templates.
65
+
66
+ Products are reusable commodity definitions that can be used
67
+ in customs declarations and shipment items.
68
+
69
+ Query Parameters:
70
+ - label: Filter by meta.label (case-insensitive contains)
71
+ - keyword: Search across label, title, sku, description, hs_code
72
+ - usage: Filter by meta.usage (exact match in array)
73
+ """,
74
+ responses={
75
+ 200: Products(),
76
+ 404: ErrorResponse(),
77
+ 500: ErrorResponse(),
78
+ },
79
+ )
80
+ def get(self, request: Request):
81
+ """
82
+ Retrieve all stored products.
83
+ """
84
+ from django.db.models import Q
85
+
86
+ # Filter to only show product templates (those with meta.label)
87
+ # Also exclude products linked to other entities (parcels, customs)
88
+ queryset = models.Commodity.access_by(request).filter(
89
+ meta__label__isnull=False, # Only templates with labels
90
+ **{
91
+ f"{prop}__isnull": True
92
+ for prop in models.Commodity.HIDDEN_PROPS
93
+ if prop != "org"
94
+ }
95
+ )
96
+
97
+ # Apply query parameter filters
98
+ label = request.query_params.get("label")
99
+ keyword = request.query_params.get("keyword")
100
+ usage = request.query_params.get("usage")
101
+
102
+ if label:
103
+ queryset = queryset.filter(meta__label__icontains=label)
104
+
105
+ if keyword:
106
+ queryset = queryset.filter(
107
+ Q(meta__label__icontains=keyword)
108
+ | Q(title__icontains=keyword)
109
+ | Q(sku__icontains=keyword)
110
+ | Q(description__icontains=keyword)
111
+ | Q(hs_code__icontains=keyword)
112
+ )
113
+
114
+ if usage:
115
+ queryset = queryset.filter(meta__usage__contains=usage)
116
+
117
+ serializer = Commodity(queryset, many=True)
118
+ response = self.paginate_queryset(serializer.data)
119
+
120
+ return self.get_paginated_response(response)
121
+
122
+ @openapi.extend_schema(
123
+ tags=["Products"],
124
+ operation_id=f"{ENDPOINT_ID}create",
125
+ extensions={"x-operationId": "createProduct"},
126
+ summary="Create a product",
127
+ description="""
128
+ Create a new product template.
129
+
130
+ Products must include a `meta.label` to be identified as templates.
131
+ """,
132
+ request=CommodityData(),
133
+ responses={
134
+ 201: Commodity(),
135
+ 400: ErrorResponse(),
136
+ 500: ErrorResponse(),
137
+ },
138
+ )
139
+ def post(self, request: Request):
140
+ """
141
+ Create a new product template.
142
+ """
143
+ # Ensure meta.label is set for product templates
144
+ data = request.data.copy() if hasattr(request.data, 'copy') else dict(request.data)
145
+
146
+ # Validate that meta.label is provided
147
+ meta = data.get("meta", {}) or {}
148
+ if not meta.get("label"):
149
+ return Response(
150
+ {"error": "Product templates require a meta.label field"},
151
+ status=status.HTTP_400_BAD_REQUEST
152
+ )
153
+
154
+ product = (
155
+ CommoditySerializer.map(data=data, context=request).save().instance
156
+ )
157
+ return Response(Commodity(product).data, status=status.HTTP_201_CREATED)
158
+
159
+
160
+ class ProductDetail(APIView):
161
+ """
162
+ Retrieve, update, and delete individual product templates.
163
+ """
164
+
165
+ @openapi.extend_schema(
166
+ tags=["Products"],
167
+ operation_id=f"{ENDPOINT_ID}retrieve",
168
+ extensions={"x-operationId": "retrieveProduct"},
169
+ summary="Retrieve a product",
170
+ description="Retrieve a product template by ID.",
171
+ responses={
172
+ 200: Commodity(),
173
+ 404: ErrorResponse(),
174
+ 500: ErrorResponse(),
175
+ },
176
+ )
177
+ def get(self, request: Request, pk: str):
178
+ """
179
+ Retrieve a product template.
180
+ """
181
+ product = models.Commodity.access_by(request).get(pk=pk)
182
+ return Response(Commodity(product).data)
183
+
184
+ @openapi.extend_schema(
185
+ tags=["Products"],
186
+ operation_id=f"{ENDPOINT_ID}update",
187
+ extensions={"x-operationId": "updateProduct"},
188
+ summary="Update a product",
189
+ description="Update an existing product template.",
190
+ request=CommodityData(),
191
+ responses={
192
+ 200: Commodity(),
193
+ 400: ErrorResponse(),
194
+ 404: ErrorResponse(),
195
+ 409: ErrorResponse(),
196
+ 500: ErrorResponse(),
197
+ },
198
+ )
199
+ def patch(self, request: Request, pk: str):
200
+ """
201
+ Update an existing product template.
202
+ """
203
+ product = models.Commodity.access_by(request).get(pk=pk)
204
+ can_mutate_commodity(product, update=True)
205
+
206
+ CommoditySerializer.map(product, data=request.data).save()
207
+
208
+ return Response(Commodity(product).data)
209
+
210
+ @openapi.extend_schema(
211
+ tags=["Products"],
212
+ operation_id=f"{ENDPOINT_ID}discard",
213
+ extensions={"x-operationId": "discardProduct"},
214
+ summary="Remove a product",
215
+ description="Delete a product template.",
216
+ responses={
217
+ 200: Commodity(),
218
+ 404: ErrorResponse(),
219
+ 409: ErrorResponse(),
220
+ 500: ErrorResponse(),
221
+ },
222
+ )
223
+ def delete(self, request: Request, pk: str):
224
+ """
225
+ Remove a product template.
226
+ """
227
+ product = models.Commodity.access_by(request).get(pk=pk)
228
+ can_mutate_commodity(product, update=True, delete=True)
229
+
230
+ product.delete(keep_parents=True)
231
+
232
+ return Response(Commodity(product).data)
233
+
234
+
235
+ # Register routes with the router
236
+ router.urls.append(path("products", ProductList.as_view(), name="product-list"))
237
+ router.urls.append(
238
+ path("products/<str:pk>", ProductDetail.as_view(), name="product-details")
239
+ )
@@ -42,7 +42,8 @@ class TrackerList(GenericAPIView):
42
42
  carrier_name = query_params.get("carrier_name")
43
43
 
44
44
  if carrier_name is not None:
45
- _filters += (Q(tracking_carrier__carrier_code=carrier_name),)
45
+ # Filter by carrier_code in the carrier JSON snapshot
46
+ _filters += (Q(carrier__carrier_code=carrier_name),)
46
47
 
47
48
  return queryset.filter(*_filters)
48
49
 
@@ -339,6 +340,65 @@ class TrackerDocs(django_downloadview.VirtualDownloadView):
339
340
  return ContentFile(buffer.getvalue(), name=self.name)
340
341
 
341
342
 
343
+ class TrackerEventInject(APIView):
344
+ """
345
+ Inbound tracking event API for event injection (testing purposes).
346
+ """
347
+
348
+ @openapi.extend_schema(
349
+ tags=["Trackers"],
350
+ operation_id=f"{ENDPOINT_ID}inject",
351
+ extensions={"x-operationId": "injectTrackingEvents"},
352
+ summary="Inject tracking events",
353
+ description="Inject tracking events into an existing tracker for testing purposes.",
354
+ request=serializers.TrackerEventInjectRequest(),
355
+ responses={
356
+ 200: serializers.Operation(),
357
+ 400: serializers.ErrorResponse(),
358
+ 404: serializers.ErrorResponse(),
359
+ 500: serializers.ErrorResponse(),
360
+ },
361
+ )
362
+ def post(self, request: Request, tracker_id: str):
363
+ """
364
+ Inject tracking events into an existing tracker.
365
+
366
+ This endpoint allows injecting events for testing the tracking pipeline.
367
+ """
368
+ import karrio.lib as lib
369
+ import karrio.server.manager.serializers.tracking as tracking_serializers
370
+
371
+ tracker = models.Tracking.access_by(request).get(pk=tracker_id)
372
+
373
+ serializer = serializers.TrackerEventInjectRequest(data=request.data)
374
+ serializer.is_valid(raise_exception=True)
375
+
376
+ data = serializer.validated_data
377
+ events = lib.to_dict(data.get("events", []))
378
+
379
+ # Build tracking details update payload
380
+ tracking_details = dict(events=events)
381
+
382
+ if data.get("status") is not None:
383
+ tracking_details["status"] = data["status"]
384
+
385
+ if data.get("delivered"):
386
+ tracking_details["delivered"] = data["delivered"]
387
+
388
+ if data.get("estimated_delivery") is not None:
389
+ tracking_details["estimated_delivery"] = data["estimated_delivery"]
390
+
391
+ # Use the centralized update_tracker function
392
+ tracking_serializers.update_tracker(tracker, tracking_details)
393
+
394
+ return Response(
395
+ serializers.Operation(
396
+ dict(operation="Inject Tracking Events", success=True)
397
+ ).data,
398
+ status=status.HTTP_200_OK,
399
+ )
400
+
401
+
342
402
  router.urls.append(path("trackers", TrackerList.as_view(), name="trackers-list"))
343
403
  router.urls.append(
344
404
  path(
@@ -347,6 +407,14 @@ router.urls.append(
347
407
  name="tracker-details",
348
408
  )
349
409
  )
410
+ # Register inject-events BEFORE shipment-tracker to avoid matching ambiguity
411
+ router.urls.append(
412
+ path(
413
+ "trackers/<str:tracker_id>/inject-events",
414
+ TrackerEventInject.as_view(),
415
+ name="tracker-events-inject",
416
+ )
417
+ )
350
418
  router.urls.append(
351
419
  path(
352
420
  "trackers/<str:carrier_name>/<str:tracking_number>",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: karrio_server_manager
3
- Version: 2026.1.1
3
+ Version: 2026.1.3
4
4
  Summary: Multi-carrier shipping API Shipments manager module
5
5
  Author-email: karrio <hello@karrio.io>
6
6
  License-Expression: LGPL-3.0
@@ -1,7 +1,7 @@
1
1
  karrio/server/manager/__init__.py,sha256=lDTMs_O6mQl0DEI2_TniT24TDJbjnkla-5QpnwfYlxs,64
2
2
  karrio/server/manager/admin.py,sha256=QOl5e2m3ekU5aj0yj9Uq4nRQrNMB_FfqNae6RyIxkC0,35
3
3
  karrio/server/manager/apps.py,sha256=WHTQ1t79uDZTbinRzvNg1NjtFwnwEvg0tP_ChrtTRwI,364
4
- karrio/server/manager/models.py,sha256=eMBARl00oZVE_L1YMXV79bNBp5n36mVQO-MlSexBA3M,34458
4
+ karrio/server/manager/models.py,sha256=M0VZOTMvy1r_mw5bl2zvnWUw9a0g0KsS3X9bWFLN-tc,42323
5
5
  karrio/server/manager/router.py,sha256=IBUR7rfBkdEHQzWxYOPcVSM8NBp3fte9G6Q5BVTUNNw,95
6
6
  karrio/server/manager/signals.py,sha256=3ZApZY4Ne8Gb0AT5rjC3Xneb7dnbSQyXp1OiBXt7eIA,1908
7
7
  karrio/server/manager/urls.py,sha256=oXJlvhHNKxFkc_CmpFoyTSAMJcLp4Wt9dHbViQDkqw4,220
@@ -75,37 +75,49 @@ karrio/server/manager/migrations/0066_commodity_image_url_commodity_product_id_a
75
75
  karrio/server/manager/migrations/0067_rename_customs_commodities_table.py,sha256=WPACK9Ab5QUdcX9yxBkV6_MXTAkB9F8vETDOlVqOrT4,1933
76
76
  karrio/server/manager/migrations/0068_shipment_extra_documents.py,sha256=DL4FqB5bf5Rq3YuWVJsBMStkwMDmHIp_WN1BA6hKSTI,536
77
77
  karrio/server/manager/migrations/0069_alter_tracking_status.py,sha256=p7rra3Iva_FT6eBmfKcpvC-E5Zll4bH69kMX9Us3Q-w,1170
78
+ karrio/server/manager/migrations/0070_add_meta_and_product_fields.py,sha256=R7I0nQ8US9QAdvc0taUcz1i8-p0CJM_4HMdAuAmC-a0,3168
79
+ karrio/server/manager/migrations/0071_product_proxy.py,sha256=-N6zIebgDOKXA-FeRZJ2Bo9YWiCzS8Kz3r7HGQdPVGE,599
80
+ karrio/server/manager/migrations/0072_populate_json_fields.py,sha256=SCiNZa6kL6dvOlrDWcxjGVsGQaXTloYToRnOJ__Qb3s,9418
81
+ karrio/server/manager/migrations/0073_make_shipment_fk_nullable.py,sha256=gSFv0VVk7kotkjAVJgCJi4e21sQZbOKorPMHmYElVEk,999
82
+ karrio/server/manager/migrations/0074_clean_model_refactoring.py,sha256=tpupc0RcrEqI2L-sv0m2W_AP5P0SAaKihPq8lWJT3pU,6913
83
+ karrio/server/manager/migrations/0075_populate_template_meta.py,sha256=dAJE3bO1we8yjeV0HQ6RVZvmoPklCuiI-JRsf9HxJjo,2319
84
+ karrio/server/manager/migrations/0076_remove_customs_model.py,sha256=LjH46huACsJkxgrYp79lMM5999VT_AnxIHVO-ZjV0vY,2491
85
+ karrio/server/manager/migrations/0077_add_carrier_snapshot_fields.py,sha256=erC-lLS88PnLXmFDEAz-_Uk5vuLkJxwdPgvLkJqaiTg,2722
86
+ karrio/server/manager/migrations/0078_populate_carrier_snapshots.py,sha256=KuaXeiBqKRhS3LNkiYlErR3sH2q7w-NkyenwOPiVUmk,4502
87
+ karrio/server/manager/migrations/0079_remove_carrier_fk_fields.py,sha256=xDOEq4aPtxw6zM1u1lqlYco_TQBeyUMq570y3uFl2j0,1652
88
+ karrio/server/manager/migrations/0080_add_carrier_json_indexes.py,sha256=P7FUoX5HdXfi8GkcUjFAyA-HbtXBbAM3EXiqdIElxn4,6019
89
+ karrio/server/manager/migrations/0081_cleanup.py,sha256=cvON11RAgUskyXAMKrBwxIiPNHP0WbhIB31DnN3rp0w,1932
90
+ karrio/server/manager/migrations/0082_shipment_fees.py,sha256=CWJc-ZGITLnN2ln6XZDBIgtbDHdQYYz9AyRQpq1VChc,719
78
91
  karrio/server/manager/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
79
- karrio/server/manager/serializers/__init__.py,sha256=FyL9pqzoV7umSJut5P2KzqPFxn1ZWu07Qx7I58H2AQc,1454
80
- karrio/server/manager/serializers/address.py,sha256=yGKvZiNukeI15LEDdbo1ycqqK8QW77ak_vyLMIKyglI,2779
81
- karrio/server/manager/serializers/commodity.py,sha256=PKrW-xAYkiNByk5RF8up_Bt1Z8mJV_BGW0mPWT9w4ME,1658
82
- karrio/server/manager/serializers/customs.py,sha256=gMGC4TJMykjgELBLFHL6v7kPQ5YQKe7cQcMnOGBLL84,2872
83
- karrio/server/manager/serializers/document.py,sha256=U8nZTvHZ6-NlB_zpBWaarcalFoP_ooiqvYedH3Vkh3w,3923
84
- karrio/server/manager/serializers/manifest.py,sha256=mSneCk_7HMXpi64_7hggWvkR7Ma24tgK53acVAFkP1k,2949
85
- karrio/server/manager/serializers/parcel.py,sha256=733Bg26lVbEkoWtAVM5Qt2IRBS2QDuVxhG40Hiqh3bw,2621
86
- karrio/server/manager/serializers/pickup.py,sha256=sX0VmcQxGkXn3IEosMuFwdXh4HhdkPcuBOp79O8PoDQ,9233
92
+ karrio/server/manager/serializers/__init__.py,sha256=AW1RpB3rNLKbMD85rCFFiVcpngj-kPUNdyb1hkaYDrk,1513
93
+ karrio/server/manager/serializers/address.py,sha256=jc_D9gQb0m7ObO6RHk6SEfZipNRPCH4sPKkd_sa7T_Y,2950
94
+ karrio/server/manager/serializers/commodity.py,sha256=tPbGXuDd6N2xqRDWCXTKnSnl4-e1MnHIKe0KdCfQYpA,1897
95
+ karrio/server/manager/serializers/document.py,sha256=MFa8lqcy4Qa0RAUdn4D02AO5cY4_EgxC-aabbxqr-tI,4611
96
+ karrio/server/manager/serializers/manifest.py,sha256=dyEG2A9rWj59Khs5d8ltgRrUWXpk32GU4a2gZNFFU_c,3114
97
+ karrio/server/manager/serializers/parcel.py,sha256=Wfu2trm2pqiOWhsYG-GElhRHSS4HTW5dttkpVNdfXd4,2761
98
+ karrio/server/manager/serializers/pickup.py,sha256=UOIG0trWxjE12jIDyayaoEKvzepmvcSAmER7dSf8WQo,15211
87
99
  karrio/server/manager/serializers/rate.py,sha256=7vYK_v8iWEDnswqYHG2Lir16_UhHTOxW5rdC6lw3lzA,652
88
- karrio/server/manager/serializers/shipment.py,sha256=sZuYnv0LexG1JzrfBCGfaQncGlFHqbvQQKVVueX-2GY,38608
89
- karrio/server/manager/serializers/tracking.py,sha256=Qg4fXzdU7wwTyZq4ybR1-X3ipQ35b1gZTPQ2rTNWfVg,13141
90
- karrio/server/manager/tests/__init__.py,sha256=Y1UNteEE60vWdUAkjbldu_r_-h4u0He8-UoiBgTjKcU,391
91
- karrio/server/manager/tests/test_addresses.py,sha256=pNkZC_yJyb29ZlEOtOAs4blcEYiOarw0zhZIZC5uj1w,3111
92
- karrio/server/manager/tests/test_custom_infos.py,sha256=iv2cLdZVoVWFZK_mDUEnrZssncAnQcn87Rn2sAk8UQI,2731
100
+ karrio/server/manager/serializers/shipment.py,sha256=uM775c5L0sfgW8kTbUwqCOrnVWoCQfsWy1awQ7dRt5M,41343
101
+ karrio/server/manager/serializers/tracking.py,sha256=HCl-WeCF-AjFaTdEd4xSb068NIKcMH-Hbo0QRMSYrvY,14319
102
+ karrio/server/manager/tests/__init__.py,sha256=hAN3DX8-0c442XC46yq-jDXMrtHbCV88P-sBqp8g1_M,331
103
+ karrio/server/manager/tests/test_addresses.py,sha256=4eiFQ_6YhLLrfBuLUbYiIfqeRiPJPcBwmykJ67i6xKw,5018
93
104
  karrio/server/manager/tests/test_errors.py,sha256=x2-mSsXknHkE4V7TajEu8d3rpqV38T_xyAaYJU7xcGQ,3616
94
105
  karrio/server/manager/tests/test_manifests.py,sha256=X35ZTXTFEM4Gxdjz598yiNNkOOKZGpILjHWRC0oM5U4,2764
95
- karrio/server/manager/tests/test_parcels.py,sha256=lVLBOsHzXgXQvYjHIUy5oiPvrMfxYpueVvvhtuhstWk,2559
96
- karrio/server/manager/tests/test_pickups.py,sha256=8jxddwTnBvBM9FOyWxW9TtZ-GOVYUje7HQ2EZjsbtD8,10681
97
- karrio/server/manager/tests/test_shipments.py,sha256=S_mDZtCFd1R5S40AA83LfmQsGJGrJ52gwDzh2SY4YIM,39751
98
- karrio/server/manager/tests/test_trackers.py,sha256=my4UR3GBRt1MrPzrpSrwE3gE_okvsU3hFovuSHhFzF0,9856
99
- karrio/server/manager/views/__init__.py,sha256=kDFUaORRQ3Xh0ZPm-Jk88Ss8dgGYM57iUFXb9TPMzh0,401
100
- karrio/server/manager/views/addresses.py,sha256=7YCAs2ZYgd1icYwMcGGWfX7A7vZEL4BEAbU4eIxhiMY,4620
101
- karrio/server/manager/views/customs.py,sha256=-ZreiKyJ1xeLeNVG53nMfRQFeURduWr1QkDItdLPnE8,4875
102
- karrio/server/manager/views/documents.py,sha256=znW54qJ_k7WInIut5FBZFDT93CioozXTOYFKRSUTBhA,4005
106
+ karrio/server/manager/tests/test_parcels.py,sha256=nB5SaTjwj_ZF2962ZIGy-DXq6XHXc15uhnV3WtKjuUU,4355
107
+ karrio/server/manager/tests/test_pickups.py,sha256=M8x4j12Zw3vp_L8Xw2Tw9oUzopLJK4QVEfvNn5fZyGk,19081
108
+ karrio/server/manager/tests/test_products.py,sha256=mK0TxjO2SBllbl_rB0foZQqTjCPR1qky_yeCVCZvzOY,20984
109
+ karrio/server/manager/tests/test_shipments.py,sha256=G2HdzzruZcs9M66fU6sWb763DAfbL8E7if8XVjcYS48,45545
110
+ karrio/server/manager/tests/test_trackers.py,sha256=pDWxYYCFxjZJTLrYbTp1YReexpIeOBXEU8xifht4-D4,9959
111
+ karrio/server/manager/views/__init__.py,sha256=BwQ7e9oXStfs4JN_ZztatrVH5BlQZn9Ls0mhrQb8kp0,402
112
+ karrio/server/manager/views/addresses.py,sha256=bwW0x75W0n1NxOfP0CdXYbnE25_YZi3xHYEbRY5cQT0,5908
113
+ karrio/server/manager/views/documents.py,sha256=8Lh49y9LZEDiwBJ77pq4EQVtTv04U-68hc7zzY-gwk4,4049
103
114
  karrio/server/manager/views/manifests.py,sha256=KwDoV7GSdRv7YDAzl0SLaNoAx4R-gBguO_WiW_qwfBM,7083
104
- karrio/server/manager/views/parcels.py,sha256=hZY45rg6SrTWfQqyJ38MGKSor1yqgPUEVHtu16aG37g,4594
115
+ karrio/server/manager/views/parcels.py,sha256=mF0BzP92_FLdBohnWaXecQGWWaibudoe3KsHZ3yPkR8,5322
105
116
  karrio/server/manager/views/pickups.py,sha256=gmpxz9ot1OR-BP1qh-0MXU3kUJi1ht_74hfaLJzJ42w,5503
117
+ karrio/server/manager/views/products.py,sha256=1ZxODea-slOhrAZ9pazviLRuJv0I90f4NRTPhA_LxzM,7762
106
118
  karrio/server/manager/views/shipments.py,sha256=vdJFgIKnSHEb2FVGtqJbiMoHuMqzWtDpINMH4UaEtQQ,13154
107
- karrio/server/manager/views/trackers.py,sha256=3oGn2qDpHgk8GZvuz-Cb93Fc0j_h_HbXQR692Zhfiok,12363
108
- karrio_server_manager-2026.1.1.dist-info/METADATA,sha256=xxPVvF-JAwDbxVc5NyNE880ZnVuD8AUssmxF_tUQXhI,730
109
- karrio_server_manager-2026.1.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
110
- karrio_server_manager-2026.1.1.dist-info/top_level.txt,sha256=D1D7x8R3cTfjF_15mfiO7wCQ5QMtuM4x8GaPr7z5i78,12
111
- karrio_server_manager-2026.1.1.dist-info/RECORD,,
119
+ karrio/server/manager/views/trackers.py,sha256=9uRjNiyqTULmKFOminGgIXj-H0RW-e0JUAF6Z3vMNCY,14744
120
+ karrio_server_manager-2026.1.3.dist-info/METADATA,sha256=C7RRJeRwFZdmmk4DArDcABxeHCzLp8qrLvoMvukR654,730
121
+ karrio_server_manager-2026.1.3.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
122
+ karrio_server_manager-2026.1.3.dist-info/top_level.txt,sha256=D1D7x8R3cTfjF_15mfiO7wCQ5QMtuM4x8GaPr7z5i78,12
123
+ karrio_server_manager-2026.1.3.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.9.0)
2
+ Generator: setuptools (80.10.2)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,84 +0,0 @@
1
- from django.db import transaction
2
- from rest_framework import status
3
-
4
- import karrio.server.manager.models as models
5
- import karrio.server.serializers as serializers
6
- import karrio.server.core.exceptions as exceptions
7
- from karrio.server.core.serializers import CustomsData, ShipmentStatus
8
- from karrio.server.manager.serializers.address import AddressSerializer
9
- from karrio.server.manager.serializers.commodity import CommoditySerializer
10
-
11
-
12
- @serializers.owned_model_serializer
13
- class CustomsSerializer(CustomsData):
14
- def __init__(self, instance: models.Customs = None, **kwargs):
15
- data = kwargs.get("data") or {}
16
-
17
- if ("commodities" in data) and (instance is not None):
18
- context = getattr(self, "__context", None) or kwargs.get("context")
19
- serializers.save_many_to_many_data(
20
- "commodities",
21
- CommoditySerializer,
22
- instance,
23
- payload=data,
24
- context=context,
25
- partial=True,
26
- )
27
-
28
- super().__init__(instance, **kwargs)
29
-
30
- @transaction.atomic
31
- def create(self, validated_data: dict, context: dict, **kwargs) -> models.Customs:
32
- instance = models.Customs.objects.create(
33
- **{
34
- **{
35
- key: value
36
- for key, value in validated_data.items()
37
- if key in models.Customs.DIRECT_PROPS
38
- },
39
- "duty_billing_address": serializers.save_one_to_one_data(
40
- "duty_billing_address",
41
- AddressSerializer,
42
- payload=validated_data,
43
- context=context,
44
- ),
45
- }
46
- )
47
-
48
- serializers.save_many_to_many_data(
49
- "commodities",
50
- CommoditySerializer,
51
- instance,
52
- payload=validated_data,
53
- context=context,
54
- )
55
-
56
- return instance
57
-
58
- @transaction.atomic
59
- def update(
60
- self, instance: models.Customs, validated_data: dict, **kwargs
61
- ) -> models.Customs:
62
- data = serializers.process_dictionaries_mutations(
63
- ["options"], validated_data, instance
64
- )
65
- changes = []
66
-
67
- for key, val in data.items():
68
- if key in models.Customs.DIRECT_PROPS and getattr(instance, key) != val:
69
- changes.append(key)
70
- setattr(instance, key, val)
71
-
72
- instance.save(update_fields=changes)
73
- return instance
74
-
75
-
76
- def can_mutate_customs(customs: models.Customs, **kwargs):
77
- shipment = customs.shipment
78
-
79
- if shipment is not None and shipment.status != ShipmentStatus.create.value:
80
- raise exceptions.APIException(
81
- f"Operation not permitted. The related shipment is '{shipment.status}'.",
82
- status_code=status.HTTP_409_CONFLICT,
83
- code="state_error",
84
- )