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,285 @@
|
|
1
|
+
import typing
|
2
|
+
|
3
|
+
from karrio.server import serializers
|
4
|
+
from karrio.server.serializers import (
|
5
|
+
owned_model_serializer,
|
6
|
+
save_one_to_one_data,
|
7
|
+
Context,
|
8
|
+
PlainDictField,
|
9
|
+
)
|
10
|
+
from karrio.server.core.gateway import Pickups, Carriers
|
11
|
+
from karrio.server.core.datatypes import Confirmation
|
12
|
+
from karrio.server.core.serializers import (
|
13
|
+
Pickup,
|
14
|
+
AddressData,
|
15
|
+
PickupRequest,
|
16
|
+
PickupUpdateRequest,
|
17
|
+
PickupCancelRequest,
|
18
|
+
)
|
19
|
+
from karrio.server.manager.serializers import AddressSerializer
|
20
|
+
import karrio.server.manager.models as models
|
21
|
+
|
22
|
+
DEFAULT_CARRIER_FILTER: typing.Any = dict(active=True, capability="pickup")
|
23
|
+
|
24
|
+
|
25
|
+
def shipment_exists(value):
|
26
|
+
validation = {
|
27
|
+
key: models.Shipment.objects.filter(tracking_number=key) for key in value
|
28
|
+
}
|
29
|
+
|
30
|
+
if not all(val.exists() for val in validation.values()):
|
31
|
+
invalids = [key for key, val in validation.items() if val.exists() is False]
|
32
|
+
raise serializers.ValidationError(
|
33
|
+
f"Shipment with the tracking numbers: {invalids} not found", code="invalid"
|
34
|
+
)
|
35
|
+
|
36
|
+
if any(val.first().shipment_pickup.exists() for val in validation.values()):
|
37
|
+
scheduled = [
|
38
|
+
key
|
39
|
+
for key, val in validation.items()
|
40
|
+
if val.first().shipment_pickup.exists() is True
|
41
|
+
]
|
42
|
+
raise serializers.ValidationError(
|
43
|
+
f"The following shipments {scheduled} are already scheduled for pickups",
|
44
|
+
code="invalid",
|
45
|
+
)
|
46
|
+
|
47
|
+
|
48
|
+
def pickup_exists(value):
|
49
|
+
validation = {
|
50
|
+
key: models.Pickup.objects.filter(tracking_number=key).exists() for key in value
|
51
|
+
}
|
52
|
+
|
53
|
+
if not all(validation.values()):
|
54
|
+
invalids = [key for key, val in validation.items() if val is False]
|
55
|
+
raise serializers.ValidationError(
|
56
|
+
f"Shipment with the tracking numbers: {invalids} not found", code="invalid"
|
57
|
+
)
|
58
|
+
|
59
|
+
|
60
|
+
def address_exists(value):
|
61
|
+
if value is str and not models.Address.objects.filter(pk=value).exists():
|
62
|
+
raise serializers.ValidationError(
|
63
|
+
{"address": f"Address with id {value} not found: {value}"}, code="invalid"
|
64
|
+
)
|
65
|
+
|
66
|
+
|
67
|
+
class PickupSerializer(PickupRequest):
|
68
|
+
parcels = None
|
69
|
+
address = AddressData(
|
70
|
+
required=False, validators=[address_exists], help_text="The pickup address"
|
71
|
+
)
|
72
|
+
tracking_numbers = serializers.StringListField(
|
73
|
+
required=True,
|
74
|
+
validators=[shipment_exists],
|
75
|
+
help_text="The list of shipments to be picked up",
|
76
|
+
)
|
77
|
+
metadata = PlainDictField(
|
78
|
+
required=False, default={}, help_text="User metadata for the pickup"
|
79
|
+
)
|
80
|
+
|
81
|
+
def __init__(self, instance: models.Pickup = None, **kwargs):
|
82
|
+
if "data" in kwargs:
|
83
|
+
data = kwargs.get("data").copy()
|
84
|
+
|
85
|
+
self._shipments: typing.List[models.Shipment] = (
|
86
|
+
models.Shipment.objects.filter(
|
87
|
+
tracking_number__in=data.get("tracking_numbers", [])
|
88
|
+
)
|
89
|
+
)
|
90
|
+
|
91
|
+
if data.get("address") is None and instance is None:
|
92
|
+
address = next(
|
93
|
+
(AddressData(s.shipper).data for s in self._shipments), None
|
94
|
+
)
|
95
|
+
elif data.get("address") is None and instance is not None:
|
96
|
+
address = AddressData(instance.address).data
|
97
|
+
elif data.get("address") is str:
|
98
|
+
address = models.Shipment.objects.get(pk=data.get("address"))
|
99
|
+
else:
|
100
|
+
address = data.get("address")
|
101
|
+
|
102
|
+
if address is not None:
|
103
|
+
data.update(address=address)
|
104
|
+
|
105
|
+
kwargs.update(data=data)
|
106
|
+
|
107
|
+
super().__init__(instance, **kwargs)
|
108
|
+
|
109
|
+
def validate(self, data):
|
110
|
+
validated_data = super(PickupRequest, self).validate(data)
|
111
|
+
|
112
|
+
if (
|
113
|
+
len(validated_data.get("tracking_numbers", [])) > 1
|
114
|
+
and validated_data.get("address") is None
|
115
|
+
):
|
116
|
+
raise serializers.ValidationError(
|
117
|
+
"address must be specified for multi-shipments pickup", code="required"
|
118
|
+
)
|
119
|
+
|
120
|
+
return validated_data
|
121
|
+
|
122
|
+
|
123
|
+
@owned_model_serializer
|
124
|
+
class PickupData(PickupSerializer):
|
125
|
+
def create(self, validated_data: dict, context: Context, **kwargs) -> models.Pickup:
|
126
|
+
carrier_filter = validated_data["carrier_filter"]
|
127
|
+
shipment_identifiers = [
|
128
|
+
_
|
129
|
+
for shipment in self._shipments
|
130
|
+
for _ in set(
|
131
|
+
[
|
132
|
+
*(shipment.meta.get("shipment_identifiers") or []),
|
133
|
+
shipment.shipment_identifier,
|
134
|
+
]
|
135
|
+
)
|
136
|
+
]
|
137
|
+
carrier = Carriers.first(
|
138
|
+
context=context,
|
139
|
+
**{"raise_not_found": True, **DEFAULT_CARRIER_FILTER, **carrier_filter},
|
140
|
+
)
|
141
|
+
request_data = PickupRequest(
|
142
|
+
{
|
143
|
+
**validated_data,
|
144
|
+
"parcels": sum([list(s.parcels.all()) for s in self._shipments], []),
|
145
|
+
"options": {
|
146
|
+
"shipment_identifiers": shipment_identifiers,
|
147
|
+
**(validated_data.get("options") or {}),
|
148
|
+
},
|
149
|
+
}
|
150
|
+
).data
|
151
|
+
|
152
|
+
response = Pickups.schedule(payload=request_data, carrier=carrier)
|
153
|
+
payload = {
|
154
|
+
key: value
|
155
|
+
for key, value in Pickup(response.pickup).data.items()
|
156
|
+
if key in models.Pickup.DIRECT_PROPS
|
157
|
+
}
|
158
|
+
address = save_one_to_one_data(
|
159
|
+
"address", AddressSerializer, payload=validated_data, context=context
|
160
|
+
)
|
161
|
+
|
162
|
+
pickup = models.Pickup.objects.create(
|
163
|
+
**{
|
164
|
+
**payload,
|
165
|
+
"address": address,
|
166
|
+
"pickup_carrier": carrier,
|
167
|
+
"created_by": context.user,
|
168
|
+
"test_mode": response.pickup.test_mode,
|
169
|
+
"confirmation_number": response.pickup.confirmation_number,
|
170
|
+
}
|
171
|
+
)
|
172
|
+
pickup.shipments.set(self._shipments)
|
173
|
+
|
174
|
+
return pickup
|
175
|
+
|
176
|
+
|
177
|
+
@owned_model_serializer
|
178
|
+
class PickupUpdateData(PickupSerializer):
|
179
|
+
confirmation_number = serializers.CharField(
|
180
|
+
required=True, help_text="pickup identification number"
|
181
|
+
)
|
182
|
+
pickup_date = serializers.CharField(
|
183
|
+
required=False,
|
184
|
+
help_text="""The expected pickup date.<br/>
|
185
|
+
Date Format: YYYY-MM-DD
|
186
|
+
""",
|
187
|
+
)
|
188
|
+
ready_time = serializers.CharField(
|
189
|
+
required=False,
|
190
|
+
allow_blank=True,
|
191
|
+
allow_null=True,
|
192
|
+
help_text="The ready time for pickup.",
|
193
|
+
)
|
194
|
+
closing_time = serializers.CharField(
|
195
|
+
required=False,
|
196
|
+
allow_blank=True,
|
197
|
+
allow_null=True,
|
198
|
+
help_text="The closing or late time of the pickup",
|
199
|
+
)
|
200
|
+
instruction = serializers.CharField(
|
201
|
+
required=False,
|
202
|
+
allow_blank=True,
|
203
|
+
allow_null=True,
|
204
|
+
help_text="""The pickup instruction.<br/>
|
205
|
+
eg: Handle with care.
|
206
|
+
""",
|
207
|
+
)
|
208
|
+
package_location = serializers.CharField(
|
209
|
+
required=False,
|
210
|
+
allow_blank=True,
|
211
|
+
allow_null=True,
|
212
|
+
help_text="""The package(s) location.<br/>
|
213
|
+
eg: Behind the entrance door.
|
214
|
+
""",
|
215
|
+
)
|
216
|
+
tracking_numbers = serializers.StringListField(
|
217
|
+
required=False,
|
218
|
+
validators=[shipment_exists],
|
219
|
+
help_text="The list of shipments to be picked up",
|
220
|
+
)
|
221
|
+
|
222
|
+
def update(
|
223
|
+
self, instance: models.Pickup, validated_data: dict, context: dict, **kwargs
|
224
|
+
) -> models.Tracking:
|
225
|
+
shipment_identifiers = [
|
226
|
+
_
|
227
|
+
for shipment in self._shipments
|
228
|
+
for _ in set(
|
229
|
+
[
|
230
|
+
*(shipment.meta.get("shipment_identifiers") or []),
|
231
|
+
shipment.shipment_identifier,
|
232
|
+
]
|
233
|
+
)
|
234
|
+
]
|
235
|
+
|
236
|
+
request_data = PickupUpdateRequest(
|
237
|
+
{
|
238
|
+
**PickupUpdateRequest(instance).data,
|
239
|
+
**validated_data,
|
240
|
+
"address": AddressData(
|
241
|
+
{**AddressData(instance.address).data, **validated_data["address"]}
|
242
|
+
).data,
|
243
|
+
"options": {
|
244
|
+
"shipment_identifiers": shipment_identifiers,
|
245
|
+
**(instance.meta or {}),
|
246
|
+
**(validated_data.get("options") or {}),
|
247
|
+
},
|
248
|
+
}
|
249
|
+
).data
|
250
|
+
|
251
|
+
Pickups.update(payload=request_data, carrier=instance.pickup_carrier)
|
252
|
+
|
253
|
+
data = validated_data.copy()
|
254
|
+
for key, val in data.items():
|
255
|
+
if key in models.Pickup.DIRECT_PROPS:
|
256
|
+
setattr(instance, key, val)
|
257
|
+
validated_data.pop(key)
|
258
|
+
|
259
|
+
save_one_to_one_data(
|
260
|
+
"address",
|
261
|
+
AddressSerializer,
|
262
|
+
instance,
|
263
|
+
payload=validated_data,
|
264
|
+
context=context,
|
265
|
+
)
|
266
|
+
|
267
|
+
instance.save()
|
268
|
+
return instance
|
269
|
+
|
270
|
+
|
271
|
+
class PickupCancelData(serializers.Serializer):
|
272
|
+
reason = serializers.CharField(
|
273
|
+
required=False, help_text="The reason of the pickup cancellation"
|
274
|
+
)
|
275
|
+
|
276
|
+
def update(
|
277
|
+
self, instance: models.Pickup, validated_data: dict, **kwargs
|
278
|
+
) -> Confirmation:
|
279
|
+
request = PickupCancelRequest(
|
280
|
+
{**PickupCancelRequest(instance).data, **validated_data}
|
281
|
+
)
|
282
|
+
Pickups.cancel(payload=request.data, carrier=instance.pickup_carrier)
|
283
|
+
instance.delete()
|
284
|
+
|
285
|
+
return instance
|
@@ -0,0 +1,19 @@
|
|
1
|
+
from karrio.server.core.gateway import Rates
|
2
|
+
from karrio.server.core.serializers import RateRequest, RateResponse
|
3
|
+
from karrio.server.serializers import owned_model_serializer, Context
|
4
|
+
|
5
|
+
|
6
|
+
@owned_model_serializer
|
7
|
+
class RateSerializer(RateRequest):
|
8
|
+
|
9
|
+
def create(self, validated_data: dict, context: Context, **kwargs) -> RateResponse:
|
10
|
+
carrier_filters = validated_data.get("carrier_filters") or {}
|
11
|
+
carriers = validated_data.get("carriers")
|
12
|
+
|
13
|
+
return Rates.fetch(
|
14
|
+
RateRequest(validated_data).data,
|
15
|
+
context=context,
|
16
|
+
carriers=carriers,
|
17
|
+
**kwargs,
|
18
|
+
**carrier_filters,
|
19
|
+
)
|