karrio-server-manager 2025.5rc1__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 (102) hide show
  1. karrio/server/manager/__init__.py +1 -0
  2. karrio/server/manager/admin.py +1 -0
  3. karrio/server/manager/apps.py +13 -0
  4. karrio/server/manager/migrations/0001_initial.py +1358 -0
  5. karrio/server/manager/migrations/0002_auto_20201127_0721.py +61 -0
  6. karrio/server/manager/migrations/0003_auto_20201230_0820.py +34 -0
  7. karrio/server/manager/migrations/0004_auto_20210125_2125.py +18 -0
  8. karrio/server/manager/migrations/0005_auto_20210216_0758.py +27 -0
  9. karrio/server/manager/migrations/0006_auto_20210307_0438.py +24 -0
  10. karrio/server/manager/migrations/0006_auto_20210308_0302.py +53 -0
  11. karrio/server/manager/migrations/0007_merge_20210311_1428.py +14 -0
  12. karrio/server/manager/migrations/0008_remove_shipment_doc_images.py +17 -0
  13. karrio/server/manager/migrations/0009_auto_20210326_1425.py +28 -0
  14. karrio/server/manager/migrations/0010_auto_20210403_1404.py +28 -0
  15. karrio/server/manager/migrations/0011_auto_20210426_1924.py +48 -0
  16. karrio/server/manager/migrations/0012_auto_20210427_1319.py +24 -0
  17. karrio/server/manager/migrations/0013_customs_invoice_date.py +18 -0
  18. karrio/server/manager/migrations/0014_auto_20210515_0928.py +24 -0
  19. karrio/server/manager/migrations/0015_auto_20210601_0340.py +182 -0
  20. karrio/server/manager/migrations/0016_shipment_archived.py +18 -0
  21. karrio/server/manager/migrations/0017_auto_20210629_1650.py +22 -0
  22. karrio/server/manager/migrations/0018_auto_20210705_1049.py +23 -0
  23. karrio/server/manager/migrations/0019_auto_20210722_1131.py +43 -0
  24. karrio/server/manager/migrations/0020_tracking_messages.py +20 -0
  25. karrio/server/manager/migrations/0021_tracking_estimated_delivery.py +18 -0
  26. karrio/server/manager/migrations/0022_auto_20211122_2100.py +53 -0
  27. karrio/server/manager/migrations/0023_auto_20211227_2141.py +118 -0
  28. karrio/server/manager/migrations/0024_alter_parcel_items.py +18 -0
  29. karrio/server/manager/migrations/0025_auto_20220113_1158.py +25 -0
  30. karrio/server/manager/migrations/0026_parcel_reference_number.py +18 -0
  31. karrio/server/manager/migrations/0027_custom_migration_2021_1.py +47 -0
  32. karrio/server/manager/migrations/0028_auto_20220303_1153.py +39 -0
  33. karrio/server/manager/migrations/0029_auto_20220303_1249.py +55 -0
  34. karrio/server/manager/migrations/0030_alter_shipment_status.py +44 -0
  35. karrio/server/manager/migrations/0031_shipment_invoice.py +34 -0
  36. karrio/server/manager/migrations/0032_custom_migration_2022_3.py +26 -0
  37. karrio/server/manager/migrations/0033_auto_20220504_1335.py +57 -0
  38. karrio/server/manager/migrations/0034_commodity_hs_code.py +18 -0
  39. karrio/server/manager/migrations/0035_parcel_options.py +26 -0
  40. karrio/server/manager/migrations/0036_alter_tracking_shipment.py +24 -0
  41. karrio/server/manager/migrations/0037_auto_20220710_1350.py +28 -0
  42. karrio/server/manager/migrations/0038_alter_tracking_status.py +18 -0
  43. karrio/server/manager/migrations/0039_documentuploadrecord.py +43 -0
  44. karrio/server/manager/migrations/0040_parcel_freight_class.py +18 -0
  45. karrio/server/manager/migrations/0041_alter_commodity_options_alter_parcel_options.py +29 -0
  46. karrio/server/manager/migrations/0042_remove_shipment_shipment_tracking_number_idx_and_more.py +658 -0
  47. karrio/server/manager/migrations/0043_customs_duty_billing_address_and_more.py +62 -0
  48. karrio/server/manager/migrations/0044_address_address_line1_temp_and_more.py +326 -0
  49. karrio/server/manager/migrations/0045_alter_customs_duty_billing_address_and_more.py +45 -0
  50. karrio/server/manager/migrations/0046_auto_20230114_0930.py +78 -0
  51. karrio/server/manager/migrations/0047_remove_shipment_shipment_tracking_number_idx_and_more.py +595 -0
  52. karrio/server/manager/migrations/0048_commodity_title_alter_commodity_description_and_more.py +53 -0
  53. karrio/server/manager/migrations/0049_auto_20230318_0708.py +39 -0
  54. karrio/server/manager/migrations/0050_address_street_number_tracking_account_number_and_more.py +60 -0
  55. karrio/server/manager/migrations/0051_auto_20230330_0556.py +56 -0
  56. karrio/server/manager/migrations/0052_auto_20230520_0811.py +35 -0
  57. karrio/server/manager/migrations/0053_alter_commodity_weight_unit_alter_parcel_weight_unit.py +32 -0
  58. karrio/server/manager/migrations/0054_alter_address_company_name_alter_address_person_name.py +22 -0
  59. karrio/server/manager/migrations/0055_alter_tracking_status.py +32 -0
  60. karrio/server/manager/migrations/0056_tracking_delivery_image_tracking_signature_image.py +22 -0
  61. karrio/server/manager/migrations/0057_alter_customs_invoice_date.py +18 -0
  62. karrio/server/manager/migrations/0058_manifest_shipment_manifest.py +124 -0
  63. karrio/server/manager/migrations/0059_shipment_return_address.py +24 -0
  64. karrio/server/manager/migrations/0060_pickup_meta_alter_address_country_code_and_more.py +527 -0
  65. karrio/server/manager/migrations/0061_alter_customs_incoterm.py +37 -0
  66. karrio/server/manager/migrations/0062_alter_tracking_status.py +35 -0
  67. karrio/server/manager/migrations/__init__.py +0 -0
  68. karrio/server/manager/models.py +984 -0
  69. karrio/server/manager/router.py +3 -0
  70. karrio/server/manager/serializers/__init__.py +50 -0
  71. karrio/server/manager/serializers/address.py +82 -0
  72. karrio/server/manager/serializers/commodity.py +51 -0
  73. karrio/server/manager/serializers/customs.py +84 -0
  74. karrio/server/manager/serializers/document.py +113 -0
  75. karrio/server/manager/serializers/manifest.py +85 -0
  76. karrio/server/manager/serializers/parcel.py +84 -0
  77. karrio/server/manager/serializers/pickup.py +285 -0
  78. karrio/server/manager/serializers/rate.py +19 -0
  79. karrio/server/manager/serializers/shipment.py +869 -0
  80. karrio/server/manager/serializers/tracking.py +250 -0
  81. karrio/server/manager/signals.py +70 -0
  82. karrio/server/manager/tests/__init__.py +10 -0
  83. karrio/server/manager/tests/test_addresses.py +110 -0
  84. karrio/server/manager/tests/test_custom_infos.py +97 -0
  85. karrio/server/manager/tests/test_parcels.py +104 -0
  86. karrio/server/manager/tests/test_pickups.py +345 -0
  87. karrio/server/manager/tests/test_shipments.py +833 -0
  88. karrio/server/manager/tests/test_trackers.py +215 -0
  89. karrio/server/manager/urls.py +10 -0
  90. karrio/server/manager/views/__init__.py +9 -0
  91. karrio/server/manager/views/addresses.py +154 -0
  92. karrio/server/manager/views/customs.py +159 -0
  93. karrio/server/manager/views/documents.py +131 -0
  94. karrio/server/manager/views/manifests.py +160 -0
  95. karrio/server/manager/views/parcels.py +155 -0
  96. karrio/server/manager/views/pickups.py +182 -0
  97. karrio/server/manager/views/shipments.py +335 -0
  98. karrio/server/manager/views/trackers.py +364 -0
  99. karrio_server_manager-2025.5rc1.dist-info/METADATA +28 -0
  100. karrio_server_manager-2025.5rc1.dist-info/RECORD +102 -0
  101. karrio_server_manager-2025.5rc1.dist-info/WHEEL +5 -0
  102. karrio_server_manager-2025.5rc1.dist-info/top_level.txt +2 -0
@@ -0,0 +1,215 @@
1
+ import json
2
+ from time import sleep
3
+ from django.urls import reverse
4
+ from rest_framework import status
5
+ from unittest.mock import patch, ANY
6
+ from karrio.core.models import TrackingDetails, TrackingEvent
7
+ from karrio.server.core.tests import APITestCase
8
+ import karrio.server.manager.models as models
9
+
10
+
11
+ class TestTrackers(APITestCase):
12
+ def test_shipment_tracking(self):
13
+ url = reverse(
14
+ "karrio.server.manager:shipment-tracker",
15
+ kwargs=dict(tracking_number="1Z12345E6205277936", carrier_name="ups"),
16
+ )
17
+
18
+ with patch("karrio.server.core.gateway.utils.identity") as mock:
19
+ mock.return_value = RETURNED_VALUE
20
+ response = self.client.get(f"{url}")
21
+ response_data = json.loads(response.content)
22
+
23
+ self.assertResponseNoErrors(response)
24
+ self.assertEqual(response.status_code, status.HTTP_202_ACCEPTED)
25
+ self.assertDictEqual(response_data, TRACKING_RESPONSE)
26
+
27
+ def test_shipment_tracking_retry(self):
28
+ url = reverse(
29
+ "karrio.server.manager:shipment-tracker",
30
+ kwargs=dict(tracking_number="1Z12345E6205277936", carrier_name="ups"),
31
+ )
32
+
33
+ with patch("karrio.server.core.gateway.utils.identity") as mock:
34
+ mock.return_value = RETURNED_VALUE
35
+ self.client.get(f"{url}")
36
+ sleep(0.1)
37
+ response = self.client.get(f"{url}")
38
+ response_data = json.loads(response.content)
39
+
40
+ self.assertResponseNoErrors(response)
41
+ self.assertEqual(response.status_code, status.HTTP_202_ACCEPTED)
42
+ self.assertDictEqual(response_data, TRACKING_RESPONSE)
43
+ self.assertEqual(len(self.user.tracking_set.all()), 1)
44
+
45
+
46
+ class TestTrackersUpdate(APITestCase):
47
+ def setUp(self) -> None:
48
+ super().setUp()
49
+ self.tracker = models.Tracking.objects.create(
50
+ **{
51
+ "tracking_number": "00340434292135100124",
52
+ "test_mode": True,
53
+ "delivered": False,
54
+ "events": [
55
+ {
56
+ "date": "2021-01-11",
57
+ "description": "The instruction data for this shipment have been provided by the sender to DHL electronically",
58
+ "location": "BONN",
59
+ "code": "pre-transit",
60
+ "time": "20:34",
61
+ }
62
+ ],
63
+ "status": "in_transit",
64
+ "created_by": self.user,
65
+ "tracking_carrier": self.dhl_carrier,
66
+ "info": {
67
+ "carrier_tracking_link": "https://www.dhl.com/ca-en/home/tracking/tracking-parcel.html?submit=1&tracking-id=00340434292135100124",
68
+ "package_weight": 0.74,
69
+ "package_weight_unit": "KG",
70
+ "shipping_date": "2021-01-11",
71
+ },
72
+ }
73
+ )
74
+
75
+ def test_update_tracker_info(self):
76
+ url = reverse(
77
+ "karrio.server.manager:tracker-details",
78
+ kwargs=dict(id_or_tracking_number=self.tracker.pk),
79
+ )
80
+ data = dict(info=TRACKING_INFO)
81
+
82
+ response = self.client.put(url, data)
83
+ response_data = json.loads(response.content)
84
+
85
+ self.assertResponseNoErrors(response)
86
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
87
+ self.assertDictEqual(response_data, UPDATE_TRACKING_RESPONSE)
88
+
89
+
90
+ RETURNED_VALUE = (
91
+ [
92
+ TrackingDetails(
93
+ carrier_id="ups_package",
94
+ carrier_name="ups",
95
+ tracking_number="1Z12345E6205277936",
96
+ delivered=False,
97
+ events=[
98
+ TrackingEvent(
99
+ code="KB",
100
+ date="2010-08-30",
101
+ description="UPS INTERNAL ACTIVITY CODE",
102
+ location="BONN",
103
+ time="10:39",
104
+ )
105
+ ],
106
+ )
107
+ ],
108
+ [],
109
+ )
110
+
111
+ TRACKING_RESPONSE = {
112
+ "id": ANY,
113
+ "object_type": "tracker",
114
+ "carrier_id": "ups_package",
115
+ "carrier_name": "ups",
116
+ "tracking_number": "1Z12345E6205277936",
117
+ "test_mode": True,
118
+ "delivered": False,
119
+ "status": "in_transit",
120
+ "estimated_delivery": ANY,
121
+ "delivery_image_url": None,
122
+ "signature_image_url": None,
123
+ "events": [
124
+ {
125
+ "code": "KB",
126
+ "date": "2010-08-30",
127
+ "description": "UPS INTERNAL ACTIVITY CODE",
128
+ "location": "BONN",
129
+ "time": "10:39",
130
+ "latitude": None,
131
+ "longitude": None,
132
+ }
133
+ ],
134
+ "messages": [],
135
+ "meta": {
136
+ "ext": "ups",
137
+ "carrier": "ups",
138
+ },
139
+ "metadata": {},
140
+ "info": {
141
+ "carrier_tracking_link": "https://www.ups.com/track?loc=en_US&requester=QUIC&tracknum=1Z12345E6205277936/trackdetails",
142
+ "customer_name": None,
143
+ "expected_delivery": None,
144
+ "note": None,
145
+ "order_date": None,
146
+ "order_id": None,
147
+ "package_weight": None,
148
+ "package_weight_unit": None,
149
+ "shipment_delivery_date": None,
150
+ "shipment_destination_country": None,
151
+ "shipment_destination_postal_code": None,
152
+ "shipment_origin_country": None,
153
+ "shipment_origin_postal_code": None,
154
+ "shipment_package_count": None,
155
+ "shipment_pickup_date": None,
156
+ "shipment_service": None,
157
+ "shipping_date": None,
158
+ "signed_by": None,
159
+ "source": "api",
160
+ },
161
+ }
162
+
163
+ TRACKING_INFO = {
164
+ "shipment_service": "dhl_express_worldwide",
165
+ "signed_by": "Jane Doe",
166
+ }
167
+
168
+ UPDATE_TRACKING_RESPONSE = {
169
+ "id": ANY,
170
+ "object_type": "tracker",
171
+ "carrier_name": "dhl_express",
172
+ "carrier_id": "dhl_express",
173
+ "tracking_number": "00340434292135100124",
174
+ "events": [
175
+ {
176
+ "date": "2021-01-11",
177
+ "description": "The instruction data for this shipment have been provided by the sender to DHL electronically",
178
+ "code": "pre-transit",
179
+ "latitude": None,
180
+ "location": "BONN",
181
+ "longitude": None,
182
+ "time": "20:34",
183
+ }
184
+ ],
185
+ "delivered": False,
186
+ "test_mode": True,
187
+ "status": "in_transit",
188
+ "estimated_delivery": ANY,
189
+ "delivery_image_url": None,
190
+ "signature_image_url": None,
191
+ "messages": [],
192
+ "meta": {},
193
+ "metadata": {},
194
+ "info": {
195
+ "carrier_tracking_link": "https://www.dhl.com/ca-en/home/tracking/tracking-parcel.html?submit=1&tracking-id=00340434292135100124",
196
+ "customer_name": None,
197
+ "expected_delivery": None,
198
+ "note": None,
199
+ "order_date": None,
200
+ "order_id": None,
201
+ "package_weight": "0.74",
202
+ "package_weight_unit": "KG",
203
+ "shipment_delivery_date": None,
204
+ "shipment_destination_country": None,
205
+ "shipment_destination_postal_code": None,
206
+ "shipment_origin_country": None,
207
+ "shipment_origin_postal_code": None,
208
+ "shipment_package_count": None,
209
+ "shipment_pickup_date": None,
210
+ "shipment_service": "dhl_express_worldwide",
211
+ "shipping_date": "2021-01-11",
212
+ "signed_by": "Jane Doe",
213
+ "source": None,
214
+ },
215
+ }
@@ -0,0 +1,10 @@
1
+ """
2
+ karrio server manager module urls
3
+ """
4
+ from django.urls import include, path
5
+ from karrio.server.manager.views import router
6
+
7
+ app_name = "karrio.server.manager"
8
+ urlpatterns = [
9
+ path("v1/", include(router.urls)),
10
+ ]
@@ -0,0 +1,9 @@
1
+ import karrio.server.manager.views.addresses
2
+ import karrio.server.manager.views.parcels
3
+ import karrio.server.manager.views.shipments
4
+ import karrio.server.manager.views.trackers
5
+ import karrio.server.manager.views.customs
6
+ import karrio.server.manager.views.pickups
7
+ import karrio.server.manager.views.documents
8
+ import karrio.server.manager.views.manifests
9
+ from karrio.server.manager.router import router
@@ -0,0 +1,154 @@
1
+ import logging
2
+
3
+ from django.urls import path
4
+ from rest_framework import status
5
+ from rest_framework.request import Request
6
+ from rest_framework.response import Response
7
+ from rest_framework.pagination import LimitOffsetPagination
8
+
9
+ from karrio.server.core.views.api import GenericAPIView, APIView
10
+ from karrio.server.manager.serializers import (
11
+ PaginatedResult,
12
+ ErrorResponse,
13
+ AddressData,
14
+ Address,
15
+ AddressSerializer,
16
+ can_mutate_address,
17
+ )
18
+ from karrio.server.manager.router import router
19
+ import karrio.server.manager.models as models
20
+ import karrio.server.openapi as openapi
21
+
22
+
23
+ ENDPOINT_ID = "$" # This endpoint id is used to make operation ids unique make sure not to duplicate
24
+ logger = logging.getLogger(__name__)
25
+ Addresses = PaginatedResult("AddressList", Address)
26
+
27
+
28
+ class AddressList(GenericAPIView):
29
+ queryset = models.Address.objects
30
+ pagination_class = type(
31
+ "CustomPagination", (LimitOffsetPagination,), dict(default_limit=20)
32
+ )
33
+ serializer_class = Addresses
34
+
35
+ @openapi.extend_schema(
36
+ tags=["Addresses"],
37
+ operation_id=f"{ENDPOINT_ID}list",
38
+ extensions={"x-operationId": "listAddresses"},
39
+ summary="List all addresses",
40
+ responses={
41
+ 200: Addresses(),
42
+ 404: ErrorResponse(),
43
+ 500: ErrorResponse(),
44
+ },
45
+ )
46
+ def get(self, request: Request):
47
+ """
48
+ Retrieve all addresses.
49
+ """
50
+ addresses = models.Address.access_by(request).filter(
51
+ **{
52
+ f"{prop}__isnull": True
53
+ for prop in models.Address.HIDDEN_PROPS
54
+ if prop != "org"
55
+ }
56
+ )
57
+ response = self.paginate_queryset(Address(addresses, many=True).data)
58
+ return self.get_paginated_response(response)
59
+
60
+ @openapi.extend_schema(
61
+ tags=["Addresses"],
62
+ operation_id=f"{ENDPOINT_ID}create",
63
+ extensions={"x-operationId": "createAddress"},
64
+ summary="Create an address",
65
+ request=AddressData(),
66
+ responses={
67
+ 201: Address(),
68
+ 400: ErrorResponse(),
69
+ 500: ErrorResponse(),
70
+ },
71
+ )
72
+ def post(self, request: Request):
73
+ """
74
+ Create a new address.
75
+ """
76
+ address = (
77
+ AddressSerializer.map(data=request.data, context=request).save().instance
78
+ )
79
+ return Response(Address(address).data, status=status.HTTP_201_CREATED)
80
+
81
+
82
+ class AddressDetail(APIView):
83
+
84
+ @openapi.extend_schema(
85
+ tags=["Addresses"],
86
+ operation_id=f"{ENDPOINT_ID}retrieve",
87
+ extensions={"x-operationId": "retrieveAddress"},
88
+ summary="Retrieve an address",
89
+ responses={
90
+ 200: Address(),
91
+ 400: ErrorResponse(),
92
+ 500: ErrorResponse(),
93
+ },
94
+ )
95
+ def get(self, request: Request, pk: str):
96
+ """
97
+ Retrieve an address.
98
+ """
99
+ address = models.Address.access_by(request).get(pk=pk)
100
+ return Response(Address(address).data)
101
+
102
+ @openapi.extend_schema(
103
+ tags=["Addresses"],
104
+ operation_id=f"{ENDPOINT_ID}update",
105
+ extensions={"x-operationId": "updateAddress"},
106
+ summary="Update an address",
107
+ request=AddressData(),
108
+ responses={
109
+ 200: Address(),
110
+ 400: ErrorResponse(),
111
+ 404: ErrorResponse(),
112
+ 409: ErrorResponse(),
113
+ 500: ErrorResponse(),
114
+ },
115
+ )
116
+ def patch(self, request: Request, pk: str):
117
+ """
118
+ update an address.
119
+ """
120
+ address = models.Address.access_by(request).get(pk=pk)
121
+ can_mutate_address(address, update=True)
122
+
123
+ AddressSerializer.map(address, data=request.data).save()
124
+
125
+ return Response(Address(address).data)
126
+
127
+ @openapi.extend_schema(
128
+ tags=["Addresses"],
129
+ operation_id=f"{ENDPOINT_ID}discard",
130
+ extensions={"x-operationId": "discardAddress"},
131
+ summary="Discard an address",
132
+ responses={
133
+ 200: Address(),
134
+ 404: ErrorResponse(),
135
+ 409: ErrorResponse(),
136
+ 500: ErrorResponse(),
137
+ },
138
+ )
139
+ def delete(self, request: Request, pk: str):
140
+ """
141
+ Discard an address.
142
+ """
143
+ address = models.Address.access_by(request).get(pk=pk)
144
+ can_mutate_address(address, update=True, delete=True)
145
+
146
+ address.delete(keep_parents=True)
147
+
148
+ return Response(Address(address).data)
149
+
150
+
151
+ router.urls.append(path("addresses", AddressList.as_view(), name="address-list"))
152
+ router.urls.append(
153
+ path("addresses/<str:pk>", AddressDetail.as_view(), name="address-details")
154
+ )
@@ -0,0 +1,159 @@
1
+ import logging
2
+
3
+ from django.urls import path
4
+ from rest_framework import status
5
+ from rest_framework.request import Request
6
+ from rest_framework.response import Response
7
+ from rest_framework.pagination import LimitOffsetPagination
8
+
9
+ from karrio.server.core.views.api import GenericAPIView, APIView
10
+ from karrio.server.manager.serializers import (
11
+ PaginatedResult,
12
+ ErrorResponse,
13
+ CustomsData,
14
+ Customs,
15
+ CustomsSerializer,
16
+ can_mutate_customs,
17
+ )
18
+ from karrio.server.manager.router import router
19
+ import karrio.server.manager.models as models
20
+ import karrio.server.openapi as openapi
21
+
22
+ ENDPOINT_ID = "$$" # This endpoint id is used to make operation ids unique make sure not to duplicate
23
+ logger = logging.getLogger(__name__)
24
+ CustomsInfoList = PaginatedResult("CustomsList", Customs)
25
+
26
+
27
+ class CustomsList(GenericAPIView):
28
+ queryset = models.Customs.objects
29
+ pagination_class = type(
30
+ "CustomPagination", (LimitOffsetPagination,), dict(default_limit=20)
31
+ )
32
+ serializer_class = CustomsInfoList
33
+
34
+ @openapi.extend_schema(
35
+ exclude=True,
36
+ tags=["Customs"],
37
+ operation_id=f"{ENDPOINT_ID}list",
38
+ extensions={"x-operationId": "listCustomsInfo"},
39
+ summary="List all customs info",
40
+ responses={
41
+ 200: CustomsInfoList(),
42
+ 404: ErrorResponse(),
43
+ 500: ErrorResponse(),
44
+ },
45
+ )
46
+ def get(self, request: Request):
47
+ """
48
+ Retrieve all stored customs declarations.
49
+ """
50
+ customs_info = models.Customs.access_by(request).filter(
51
+ **{
52
+ f"{prop}__isnull": True
53
+ for prop in models.Customs.HIDDEN_PROPS
54
+ if prop != "org"
55
+ }
56
+ )
57
+ serializer = Customs(customs_info, many=True)
58
+ response = self.paginate_queryset(serializer.data)
59
+ return self.get_paginated_response(response)
60
+
61
+ @openapi.extend_schema(
62
+ exclude=True,
63
+ tags=["Customs"],
64
+ operation_id=f"{ENDPOINT_ID}create",
65
+ extensions={"x-operationId": "createCustomsInfo"},
66
+ summary="Create a customs info",
67
+ request=CustomsData(),
68
+ responses={
69
+ 201: Customs(),
70
+ 400: ErrorResponse(),
71
+ 500: ErrorResponse(),
72
+ },
73
+ )
74
+ def post(self, request: Request):
75
+ """
76
+ Create a new customs declaration.
77
+ """
78
+ customs = (
79
+ CustomsSerializer.map(data=request.data, context=request).save().instance
80
+ )
81
+ return Response(Customs(customs).data, status=status.HTTP_201_CREATED)
82
+
83
+
84
+ class CustomsDetail(APIView):
85
+
86
+ @openapi.extend_schema(
87
+ exclude=True,
88
+ tags=["Customs"],
89
+ operation_id=f"{ENDPOINT_ID}retrieve",
90
+ extensions={"x-operationId": "retrieveCustomsInfo"},
91
+ summary="Retrieve a customs info",
92
+ responses={
93
+ 200: Customs(),
94
+ 404: ErrorResponse(),
95
+ 500: ErrorResponse(),
96
+ },
97
+ )
98
+ def get(self, request: Request, pk: str):
99
+ """
100
+ Retrieve customs declaration.
101
+ """
102
+ address = models.Customs.access_by(request).get(pk=pk)
103
+ return Response(Customs(address).data)
104
+
105
+ @openapi.extend_schema(
106
+ exclude=True,
107
+ tags=["Customs"],
108
+ operation_id=f"{ENDPOINT_ID}update",
109
+ extensions={"x-operationId": "updateCustomsInfo"},
110
+ summary="Update a customs info",
111
+ request=CustomsData(),
112
+ responses={
113
+ 200: Customs(),
114
+ 400: ErrorResponse(),
115
+ 404: ErrorResponse(),
116
+ 409: ErrorResponse(),
117
+ 500: ErrorResponse(),
118
+ },
119
+ )
120
+ def patch(self, request: Request, pk: str):
121
+ """
122
+ modify an existing customs declaration.
123
+ """
124
+ customs = models.Customs.access_by(request).get(pk=pk)
125
+ can_mutate_customs(customs)
126
+
127
+ CustomsSerializer.map(customs, data=request.data, context=request).save()
128
+
129
+ return Response(Customs(customs).data)
130
+
131
+ @openapi.extend_schema(
132
+ exclude=True,
133
+ tags=["Customs"],
134
+ operation_id=f"{ENDPOINT_ID}discard",
135
+ extensions={"x-operationId": "discardCustomsInfo"},
136
+ summary="Discard a customs info",
137
+ responses={
138
+ 200: Customs(),
139
+ 404: ErrorResponse(),
140
+ 409: ErrorResponse(),
141
+ 500: ErrorResponse(),
142
+ },
143
+ )
144
+ def delete(self, request: Request, pk: str):
145
+ """
146
+ Discard a customs declaration.
147
+ """
148
+ customs = models.Customs.access_by(request).get(pk=pk)
149
+ can_mutate_customs(customs)
150
+
151
+ customs.delete(keep_parents=True)
152
+
153
+ return Response(Customs(customs).data)
154
+
155
+
156
+ router.urls.append(path("customs_info", CustomsList.as_view(), name="customs-list"))
157
+ router.urls.append(
158
+ path("customs_info/<str:pk>", CustomsDetail.as_view(), name="customs-details")
159
+ )
@@ -0,0 +1,131 @@
1
+ import logging
2
+ from django.urls import path
3
+ from rest_framework import status
4
+ from rest_framework.request import Request
5
+ from rest_framework.response import Response
6
+ from rest_framework.pagination import LimitOffsetPagination
7
+ from django_filters.rest_framework import DjangoFilterBackend
8
+
9
+ from karrio.server.core.views.api import GenericAPIView, APIView
10
+ from karrio.server.core.filters import UploadRecordFilter
11
+ from karrio.server.manager.router import router
12
+ from karrio.server.manager.serializers import (
13
+ ErrorResponse,
14
+ ErrorMessages,
15
+ PaginatedResult,
16
+ DocumentUploadData,
17
+ DocumentUploadRecord,
18
+ DocumentUploadSerializer,
19
+ can_upload_shipment_document,
20
+ )
21
+ import karrio.server.manager.models as models
22
+ import karrio.server.openapi as openapi
23
+
24
+ ENDPOINT_ID = "$$$$$&" # This endpoint id is used to make operation ids unique make sure not to duplicate
25
+ logger = logging.getLogger(__name__)
26
+ DocumentUploadRecords = PaginatedResult("DocumentUploadRecords", DocumentUploadRecord)
27
+
28
+
29
+ class DocumentList(GenericAPIView):
30
+ pagination_class = type(
31
+ "CustomPagination", (LimitOffsetPagination,), dict(default_limit=20)
32
+ )
33
+ filter_backends = (DjangoFilterBackend,)
34
+ filterset_class = UploadRecordFilter
35
+ serializer_class = DocumentUploadRecords
36
+ model = models.DocumentUploadRecord
37
+
38
+ @openapi.extend_schema(
39
+ tags=["Documents"],
40
+ operation_id=f"{ENDPOINT_ID}uploads",
41
+ summary="List all upload records",
42
+ parameters=UploadRecordFilter.parameters,
43
+ responses={
44
+ 200: DocumentUploadRecords(),
45
+ 404: ErrorResponse(),
46
+ 500: ErrorResponse(),
47
+ },
48
+ )
49
+ def get(self, _: Request):
50
+ """
51
+ Retrieve all shipping document upload records.
52
+ """
53
+ upload_records = self.filter_queryset(self.get_queryset())
54
+ response = self.paginate_queryset(
55
+ DocumentUploadRecord(upload_records, many=True).data
56
+ )
57
+
58
+ return self.get_paginated_response(response)
59
+
60
+ @openapi.extend_schema(
61
+ tags=["Documents"],
62
+ operation_id=f"{ENDPOINT_ID}upload",
63
+ summary="Upload documents",
64
+ responses={
65
+ 201: DocumentUploadRecord(),
66
+ 400: ErrorResponse(),
67
+ 424: ErrorMessages(),
68
+ 500: ErrorResponse(),
69
+ },
70
+ request=DocumentUploadData(),
71
+ )
72
+ def post(self, request: Request):
73
+ """Upload a shipping document."""
74
+ shipment = (
75
+ models.Shipment.access_by(request)
76
+ .filter(
77
+ pk=request.data.get("shipment_id"),
78
+ selected_rate_carrier__isnull=False,
79
+ )
80
+ .first()
81
+ )
82
+
83
+ can_upload_shipment_document(shipment)
84
+
85
+ upload_record = (
86
+ DocumentUploadSerializer.map(
87
+ getattr(shipment, "shipment_upload_record", None),
88
+ data=request.data,
89
+ context=request,
90
+ )
91
+ .save(shipment=shipment)
92
+ .instance
93
+ )
94
+
95
+ return Response(
96
+ DocumentUploadRecord(upload_record).data, status=status.HTTP_201_CREATED
97
+ )
98
+
99
+
100
+ class DocumentDetails(APIView):
101
+ @openapi.extend_schema(
102
+ tags=["Documents"],
103
+ operation_id=f"{ENDPOINT_ID}retrieve_upload",
104
+ summary="Retrieve upload record",
105
+ responses={
106
+ 200: DocumentUploadRecord(),
107
+ 404: ErrorResponse(),
108
+ 500: ErrorResponse(),
109
+ },
110
+ )
111
+ def get(self, request: Request, pk: str):
112
+ """Retrieve a shipping document upload record."""
113
+ upload_record = models.UploadRecord.access_by(request).get(pk=pk)
114
+
115
+ return Response(DocumentUploadRecord(upload_record).data)
116
+
117
+
118
+ router.urls.append(
119
+ path(
120
+ "documents/uploads",
121
+ DocumentList.as_view(),
122
+ name="document-upload-list",
123
+ )
124
+ )
125
+ router.urls.append(
126
+ path(
127
+ "documents/uploads/<str:pk>",
128
+ DocumentDetails.as_view(),
129
+ name="document-upload-details",
130
+ )
131
+ )