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.
- karrio/server/manager/__init__.py +1 -0
- karrio/server/manager/admin.py +1 -0
- karrio/server/manager/apps.py +13 -0
- karrio/server/manager/migrations/0001_initial.py +1358 -0
- karrio/server/manager/migrations/0002_auto_20201127_0721.py +61 -0
- karrio/server/manager/migrations/0003_auto_20201230_0820.py +34 -0
- karrio/server/manager/migrations/0004_auto_20210125_2125.py +18 -0
- karrio/server/manager/migrations/0005_auto_20210216_0758.py +27 -0
- karrio/server/manager/migrations/0006_auto_20210307_0438.py +24 -0
- karrio/server/manager/migrations/0006_auto_20210308_0302.py +53 -0
- karrio/server/manager/migrations/0007_merge_20210311_1428.py +14 -0
- karrio/server/manager/migrations/0008_remove_shipment_doc_images.py +17 -0
- karrio/server/manager/migrations/0009_auto_20210326_1425.py +28 -0
- karrio/server/manager/migrations/0010_auto_20210403_1404.py +28 -0
- karrio/server/manager/migrations/0011_auto_20210426_1924.py +48 -0
- karrio/server/manager/migrations/0012_auto_20210427_1319.py +24 -0
- karrio/server/manager/migrations/0013_customs_invoice_date.py +18 -0
- karrio/server/manager/migrations/0014_auto_20210515_0928.py +24 -0
- karrio/server/manager/migrations/0015_auto_20210601_0340.py +182 -0
- karrio/server/manager/migrations/0016_shipment_archived.py +18 -0
- karrio/server/manager/migrations/0017_auto_20210629_1650.py +22 -0
- karrio/server/manager/migrations/0018_auto_20210705_1049.py +23 -0
- karrio/server/manager/migrations/0019_auto_20210722_1131.py +43 -0
- karrio/server/manager/migrations/0020_tracking_messages.py +20 -0
- karrio/server/manager/migrations/0021_tracking_estimated_delivery.py +18 -0
- karrio/server/manager/migrations/0022_auto_20211122_2100.py +53 -0
- karrio/server/manager/migrations/0023_auto_20211227_2141.py +118 -0
- karrio/server/manager/migrations/0024_alter_parcel_items.py +18 -0
- karrio/server/manager/migrations/0025_auto_20220113_1158.py +25 -0
- karrio/server/manager/migrations/0026_parcel_reference_number.py +18 -0
- karrio/server/manager/migrations/0027_custom_migration_2021_1.py +47 -0
- karrio/server/manager/migrations/0028_auto_20220303_1153.py +39 -0
- karrio/server/manager/migrations/0029_auto_20220303_1249.py +55 -0
- karrio/server/manager/migrations/0030_alter_shipment_status.py +44 -0
- karrio/server/manager/migrations/0031_shipment_invoice.py +34 -0
- karrio/server/manager/migrations/0032_custom_migration_2022_3.py +26 -0
- karrio/server/manager/migrations/0033_auto_20220504_1335.py +57 -0
- karrio/server/manager/migrations/0034_commodity_hs_code.py +18 -0
- karrio/server/manager/migrations/0035_parcel_options.py +26 -0
- karrio/server/manager/migrations/0036_alter_tracking_shipment.py +24 -0
- karrio/server/manager/migrations/0037_auto_20220710_1350.py +28 -0
- karrio/server/manager/migrations/0038_alter_tracking_status.py +18 -0
- karrio/server/manager/migrations/0039_documentuploadrecord.py +43 -0
- karrio/server/manager/migrations/0040_parcel_freight_class.py +18 -0
- karrio/server/manager/migrations/0041_alter_commodity_options_alter_parcel_options.py +29 -0
- karrio/server/manager/migrations/0042_remove_shipment_shipment_tracking_number_idx_and_more.py +658 -0
- karrio/server/manager/migrations/0043_customs_duty_billing_address_and_more.py +62 -0
- karrio/server/manager/migrations/0044_address_address_line1_temp_and_more.py +326 -0
- karrio/server/manager/migrations/0045_alter_customs_duty_billing_address_and_more.py +45 -0
- karrio/server/manager/migrations/0046_auto_20230114_0930.py +78 -0
- karrio/server/manager/migrations/0047_remove_shipment_shipment_tracking_number_idx_and_more.py +595 -0
- karrio/server/manager/migrations/0048_commodity_title_alter_commodity_description_and_more.py +53 -0
- karrio/server/manager/migrations/0049_auto_20230318_0708.py +39 -0
- karrio/server/manager/migrations/0050_address_street_number_tracking_account_number_and_more.py +60 -0
- karrio/server/manager/migrations/0051_auto_20230330_0556.py +56 -0
- karrio/server/manager/migrations/0052_auto_20230520_0811.py +35 -0
- karrio/server/manager/migrations/0053_alter_commodity_weight_unit_alter_parcel_weight_unit.py +32 -0
- karrio/server/manager/migrations/0054_alter_address_company_name_alter_address_person_name.py +22 -0
- karrio/server/manager/migrations/0055_alter_tracking_status.py +32 -0
- karrio/server/manager/migrations/0056_tracking_delivery_image_tracking_signature_image.py +22 -0
- karrio/server/manager/migrations/0057_alter_customs_invoice_date.py +18 -0
- karrio/server/manager/migrations/0058_manifest_shipment_manifest.py +124 -0
- karrio/server/manager/migrations/0059_shipment_return_address.py +24 -0
- karrio/server/manager/migrations/0060_pickup_meta_alter_address_country_code_and_more.py +527 -0
- karrio/server/manager/migrations/0061_alter_customs_incoterm.py +37 -0
- karrio/server/manager/migrations/0062_alter_tracking_status.py +35 -0
- karrio/server/manager/migrations/__init__.py +0 -0
- karrio/server/manager/models.py +984 -0
- karrio/server/manager/router.py +3 -0
- karrio/server/manager/serializers/__init__.py +50 -0
- karrio/server/manager/serializers/address.py +82 -0
- karrio/server/manager/serializers/commodity.py +51 -0
- karrio/server/manager/serializers/customs.py +84 -0
- karrio/server/manager/serializers/document.py +113 -0
- karrio/server/manager/serializers/manifest.py +85 -0
- karrio/server/manager/serializers/parcel.py +84 -0
- karrio/server/manager/serializers/pickup.py +285 -0
- karrio/server/manager/serializers/rate.py +19 -0
- karrio/server/manager/serializers/shipment.py +869 -0
- karrio/server/manager/serializers/tracking.py +250 -0
- karrio/server/manager/signals.py +70 -0
- karrio/server/manager/tests/__init__.py +10 -0
- karrio/server/manager/tests/test_addresses.py +110 -0
- karrio/server/manager/tests/test_custom_infos.py +97 -0
- karrio/server/manager/tests/test_parcels.py +104 -0
- karrio/server/manager/tests/test_pickups.py +345 -0
- karrio/server/manager/tests/test_shipments.py +833 -0
- karrio/server/manager/tests/test_trackers.py +215 -0
- karrio/server/manager/urls.py +10 -0
- karrio/server/manager/views/__init__.py +9 -0
- karrio/server/manager/views/addresses.py +154 -0
- karrio/server/manager/views/customs.py +159 -0
- karrio/server/manager/views/documents.py +131 -0
- karrio/server/manager/views/manifests.py +160 -0
- karrio/server/manager/views/parcels.py +155 -0
- karrio/server/manager/views/pickups.py +182 -0
- karrio/server/manager/views/shipments.py +335 -0
- karrio/server/manager/views/trackers.py +364 -0
- karrio_server_manager-2025.5rc1.dist-info/METADATA +28 -0
- karrio_server_manager-2025.5rc1.dist-info/RECORD +102 -0
- karrio_server_manager-2025.5rc1.dist-info/WHEEL +5 -0
- 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,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
|
+
)
|