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,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
+ )