karrio-server-data 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/data/__init__.py +0 -0
- karrio/server/data/admin.py +1 -0
- karrio/server/data/apps.py +13 -0
- karrio/server/data/filters.py +43 -0
- karrio/server/data/migrations/0001_initial.py +62 -0
- karrio/server/data/migrations/0002_alter_batchoperation_resource_type_and_more.py +28 -0
- karrio/server/data/migrations/0003_datatemplate_metadata_alter_batchoperation_resources.py +36 -0
- karrio/server/data/migrations/__init__.py +0 -0
- karrio/server/data/models.py +97 -0
- karrio/server/data/resources/__init__.py +53 -0
- karrio/server/data/resources/orders.py +523 -0
- karrio/server/data/resources/shipments.py +473 -0
- karrio/server/data/resources/trackers.py +212 -0
- karrio/server/data/serializers/__init__.py +26 -0
- karrio/server/data/serializers/base.py +107 -0
- karrio/server/data/serializers/batch.py +9 -0
- karrio/server/data/serializers/batch_orders.py +99 -0
- karrio/server/data/serializers/batch_shipments.py +102 -0
- karrio/server/data/serializers/batch_trackers.py +131 -0
- karrio/server/data/serializers/data.py +109 -0
- karrio/server/data/signals.py +52 -0
- karrio/server/data/tests.py +3 -0
- karrio/server/data/urls.py +13 -0
- karrio/server/data/views/__init__.py +0 -0
- karrio/server/data/views/batch.py +72 -0
- karrio/server/data/views/batch_order.py +40 -0
- karrio/server/data/views/batch_shipment.py +40 -0
- karrio/server/data/views/batch_tracking.py +40 -0
- karrio/server/data/views/data.py +171 -0
- karrio/server/events/task_definitions/__init__.py +1 -0
- karrio/server/events/task_definitions/data/__init__.py +136 -0
- karrio/server/events/task_definitions/data/batch.py +130 -0
- karrio/server/events/task_definitions/data/shipments.py +51 -0
- karrio/server/graph/schemas/__init__.py +1 -0
- karrio/server/graph/schemas/data/__init__.py +51 -0
- karrio/server/graph/schemas/data/inputs.py +39 -0
- karrio/server/graph/schemas/data/mutations.py +53 -0
- karrio/server/graph/schemas/data/types.py +78 -0
- karrio/server/settings/data.py +15 -0
- karrio_server_data-2025.5rc1.dist-info/METADATA +18 -0
- karrio_server_data-2025.5rc1.dist-info/RECORD +43 -0
- karrio_server_data-2025.5rc1.dist-info/WHEEL +5 -0
- karrio_server_data-2025.5rc1.dist-info/top_level.txt +2 -0
@@ -0,0 +1,473 @@
|
|
1
|
+
import json
|
2
|
+
from django.db.models import Q
|
3
|
+
from import_export import resources
|
4
|
+
|
5
|
+
import karrio.lib as lib
|
6
|
+
from karrio.core.units import Packages
|
7
|
+
from karrio.server.manager import models
|
8
|
+
from karrio.server.core import serializers as core
|
9
|
+
from karrio.server.core.filters import ShipmentFilters
|
10
|
+
from karrio.server.manager.serializers import ShipmentSerializer
|
11
|
+
from karrio.server.core import datatypes as types, dataunits as units
|
12
|
+
|
13
|
+
DEFAULT_HEADERS = {
|
14
|
+
"id": "ID",
|
15
|
+
"created_at": "Created at",
|
16
|
+
"tracking_number": "Tracking number",
|
17
|
+
"status": "Status",
|
18
|
+
"shipper_id": "Shipper ID",
|
19
|
+
"shipper_name": "Shipper name",
|
20
|
+
"shipper_company": "Shipper Company",
|
21
|
+
"shipper_phone": "Shipper phone",
|
22
|
+
"shipper_email": "Shipper email",
|
23
|
+
"shipper_address1": "Shipper address 1",
|
24
|
+
"shipper_address2": "Shipper address 2",
|
25
|
+
"shipper_city": "Shipper city",
|
26
|
+
"shipper_state": "Shipper state",
|
27
|
+
"shipper_postal_code": "Shipper postal code",
|
28
|
+
"shipper_country": "Shipper country",
|
29
|
+
"shipper_residential": "Shipper residential",
|
30
|
+
"recipient_id": "Recipient ID",
|
31
|
+
"recipient_name": "Recipient name",
|
32
|
+
"recipient_company": "Recipient Company",
|
33
|
+
"recipient_phone": "Recipient phone",
|
34
|
+
"recipient_email": "Recipient email",
|
35
|
+
"recipient_address1": "Recipient address 1",
|
36
|
+
"recipient_address2": "Recipient address 2",
|
37
|
+
"recipient_city": "Recipient city",
|
38
|
+
"recipient_state": "Recipient state",
|
39
|
+
"recipient_postal_code": "Recipient postal code",
|
40
|
+
"recipient_country": "Recipient country",
|
41
|
+
"recipient_residential": "Recipient residential",
|
42
|
+
"parcel_width": "Parcel width",
|
43
|
+
"parcel_height": "Parcel height",
|
44
|
+
"parcel_length": "Parcel length",
|
45
|
+
"parcel_dimension_unit": "Parcel dimension unit",
|
46
|
+
"parcel_weight": "Parcel weight",
|
47
|
+
"parcel_weight_unit": "Parcel weight unit",
|
48
|
+
"parcel_package_preset": "Parcel package preset",
|
49
|
+
"pieces": "Number of pieces",
|
50
|
+
"service": "Service",
|
51
|
+
"carrier": "Carrier",
|
52
|
+
"rate": "Rate",
|
53
|
+
"currency": "Currency",
|
54
|
+
"paid_by": "Payor",
|
55
|
+
"reference": "Reference",
|
56
|
+
"options": "Options",
|
57
|
+
}
|
58
|
+
|
59
|
+
|
60
|
+
def shipment_export_resource(query_params: dict, context, **kwargs):
|
61
|
+
queryset = models.Shipment.access_by(context)
|
62
|
+
_exclude = query_params.get("exclude", "").split(",")
|
63
|
+
_fields = (
|
64
|
+
"id",
|
65
|
+
"tracking_number",
|
66
|
+
"created_at",
|
67
|
+
"status",
|
68
|
+
"reference",
|
69
|
+
)
|
70
|
+
|
71
|
+
if "status" not in query_params:
|
72
|
+
queryset = queryset.filter(
|
73
|
+
Q(status__in=["purchased", "delivered", "shipped", "in_transit"]),
|
74
|
+
)
|
75
|
+
|
76
|
+
class Resource(resources.ModelResource):
|
77
|
+
class Meta:
|
78
|
+
model = models.Shipment
|
79
|
+
fields = _fields
|
80
|
+
exclude = _exclude
|
81
|
+
export_order = [k for k in DEFAULT_HEADERS.keys() if k not in _exclude]
|
82
|
+
|
83
|
+
def get_queryset(self):
|
84
|
+
return ShipmentFilters(query_params, queryset).qs
|
85
|
+
|
86
|
+
def get_export_headers(self):
|
87
|
+
headers = super().get_export_headers()
|
88
|
+
return [DEFAULT_HEADERS.get(k, k) for k in headers]
|
89
|
+
|
90
|
+
@staticmethod
|
91
|
+
def packages(row):
|
92
|
+
parcels = core.Parcel(row.parcels, many=True).data
|
93
|
+
return Packages([lib.to_object(types.Parcel, p) for p in parcels])
|
94
|
+
|
95
|
+
if "service" not in _exclude:
|
96
|
+
service = resources.Field()
|
97
|
+
|
98
|
+
def dehydrate_service(self, row):
|
99
|
+
rate = getattr(row, "selected_rate") or {}
|
100
|
+
return (rate.get("meta") or {}).get("service_name") or rate.get(
|
101
|
+
"service"
|
102
|
+
)
|
103
|
+
|
104
|
+
if "carrier" not in _exclude:
|
105
|
+
carrier = resources.Field()
|
106
|
+
|
107
|
+
def dehydrate_carrier(self, row):
|
108
|
+
carrier = getattr(row, "selected_rate_carrier", None)
|
109
|
+
settings = getattr(carrier, "settings", None)
|
110
|
+
return getattr(
|
111
|
+
settings, "display_name", None
|
112
|
+
) or units.REFERENCE_MODELS["carriers"].get(carrier.carrier_name)
|
113
|
+
|
114
|
+
if "pieces" not in _exclude:
|
115
|
+
pieces = resources.Field()
|
116
|
+
|
117
|
+
def dehydrate_pieces(self, row):
|
118
|
+
return len(row.parcels.all())
|
119
|
+
|
120
|
+
if "rate" not in _exclude:
|
121
|
+
rate = resources.Field()
|
122
|
+
|
123
|
+
def dehydrate_rate(self, row):
|
124
|
+
return (getattr(row, "selected_rate") or {}).get("total_charge", None)
|
125
|
+
|
126
|
+
if "currency" not in _exclude:
|
127
|
+
currency = resources.Field()
|
128
|
+
|
129
|
+
def dehydrate_currency(self, row):
|
130
|
+
return (getattr(row, "selected_rate") or {}).get("currency", None)
|
131
|
+
|
132
|
+
if "paid_by" not in _exclude:
|
133
|
+
paid_by = resources.Field()
|
134
|
+
|
135
|
+
def dehydrate_paid_by(self, row):
|
136
|
+
return (getattr(row, "payment") or {}).get("paid_by", None)
|
137
|
+
|
138
|
+
if "parcel_weight" not in _exclude:
|
139
|
+
parcel_weight = resources.Field()
|
140
|
+
|
141
|
+
def dehydrate_parcel_weight(self, row):
|
142
|
+
return self.packages(row).weight.value
|
143
|
+
|
144
|
+
if "parcel_weight_unit" not in _exclude:
|
145
|
+
parcel_weight_unit = resources.Field()
|
146
|
+
|
147
|
+
def dehydrate_parcel_weight_unit(self, row):
|
148
|
+
return self.packages(row).weight_unit
|
149
|
+
|
150
|
+
if "parcel_length" not in _exclude:
|
151
|
+
parcel_length = resources.Field()
|
152
|
+
|
153
|
+
def dehydrate_parcel_length(self, row):
|
154
|
+
return self.packages(row)[0].parcel.length
|
155
|
+
|
156
|
+
if "parcel_width" not in _exclude:
|
157
|
+
parcel_width = resources.Field()
|
158
|
+
|
159
|
+
def dehydrate_parcel_width(self, row):
|
160
|
+
return self.packages(row)[0].parcel.width
|
161
|
+
|
162
|
+
if "parcel_height" not in _exclude:
|
163
|
+
parcel_height = resources.Field()
|
164
|
+
|
165
|
+
def dehydrate_parcel_height(self, row):
|
166
|
+
return self.packages(row)[0].parcel.height
|
167
|
+
|
168
|
+
if "parcel_dimension_unit" not in _exclude:
|
169
|
+
parcel_dimension_unit = resources.Field()
|
170
|
+
|
171
|
+
def dehydrate_parcel_dimension_unit(self, row):
|
172
|
+
_, unit = self.packages(row).compatible_units
|
173
|
+
return unit.value
|
174
|
+
|
175
|
+
if "parcel_package_preset" not in _exclude:
|
176
|
+
parcel_package_preset = resources.Field()
|
177
|
+
|
178
|
+
def dehydrate_parcel_package_preset(self, row):
|
179
|
+
return self.packages(row)[0].parcel.package_preset
|
180
|
+
|
181
|
+
if "shipment_date" not in _exclude:
|
182
|
+
shipment_date = resources.Field()
|
183
|
+
|
184
|
+
def dehydrate_shipment_date(self, row):
|
185
|
+
return (getattr(row, "options") or {}).get("shipment_date", None) or (
|
186
|
+
lib.fdate(row.created_at, "%Y-%m-%m %H:%M:%M")
|
187
|
+
)
|
188
|
+
|
189
|
+
if "shipper_id" not in _exclude:
|
190
|
+
shipper_id = resources.Field()
|
191
|
+
|
192
|
+
def dehydrate_shipper_id(self, row):
|
193
|
+
return row.shipper.id
|
194
|
+
|
195
|
+
if "shipper_name" not in _exclude:
|
196
|
+
shipper_name = resources.Field()
|
197
|
+
|
198
|
+
def dehydrate_shipper_name(self, row):
|
199
|
+
return row.shipper.person_name
|
200
|
+
|
201
|
+
if "shipper_company" not in _exclude:
|
202
|
+
shipper_company = resources.Field()
|
203
|
+
|
204
|
+
def dehydrate_shipper_company(self, row):
|
205
|
+
return row.shipper.company_name
|
206
|
+
|
207
|
+
if "shipper_phone" not in _exclude:
|
208
|
+
shipper_phone = resources.Field()
|
209
|
+
|
210
|
+
def dehydrate_shipper_phone(self, row):
|
211
|
+
return row.shipper.phone_number
|
212
|
+
|
213
|
+
if "shipper_email" not in _exclude:
|
214
|
+
shipper_email = resources.Field()
|
215
|
+
|
216
|
+
def dehydrate_shipper_email(self, row):
|
217
|
+
return row.shipper.email
|
218
|
+
|
219
|
+
if "shipper_address1" not in _exclude:
|
220
|
+
shipper_address1 = resources.Field()
|
221
|
+
|
222
|
+
def dehydrate_shipper_address1(self, row):
|
223
|
+
return row.shipper.address_line1
|
224
|
+
|
225
|
+
if "shipper_address2" not in _exclude:
|
226
|
+
shipper_address2 = resources.Field()
|
227
|
+
|
228
|
+
def dehydrate_shipper_address2(self, row):
|
229
|
+
return row.shipper.address_line2
|
230
|
+
|
231
|
+
if "shipper_city" not in _exclude:
|
232
|
+
shipper_city = resources.Field()
|
233
|
+
|
234
|
+
def dehydrate_shipper_city(self, row):
|
235
|
+
return row.shipper.city
|
236
|
+
|
237
|
+
if "shipper_state" not in _exclude:
|
238
|
+
shipper_state = resources.Field()
|
239
|
+
|
240
|
+
def dehydrate_shipper_state(self, row):
|
241
|
+
return row.shipper.state_code
|
242
|
+
|
243
|
+
if "shipper_postal_code" not in _exclude:
|
244
|
+
shipper_postal_code = resources.Field()
|
245
|
+
|
246
|
+
def dehydrate_shipper_postal_code(self, row):
|
247
|
+
return row.shipper.postal_code
|
248
|
+
|
249
|
+
if "shipper_country" not in _exclude:
|
250
|
+
shipper_country = resources.Field()
|
251
|
+
|
252
|
+
def dehydrate_shipper_country(self, row):
|
253
|
+
return row.shipper.country_code
|
254
|
+
|
255
|
+
if "shipper_residential" not in _exclude:
|
256
|
+
shipper_residential = resources.Field()
|
257
|
+
|
258
|
+
def dehydrate_shipper_residential(self, row):
|
259
|
+
return row.shipper.residential
|
260
|
+
|
261
|
+
if "recipient_id" not in _exclude:
|
262
|
+
recipient_id = resources.Field()
|
263
|
+
|
264
|
+
def dehydrate_recipient_id(self, row):
|
265
|
+
return row.recipient.id
|
266
|
+
|
267
|
+
if "recipient_name" not in _exclude:
|
268
|
+
recipient_name = resources.Field()
|
269
|
+
|
270
|
+
def dehydrate_recipient_name(self, row):
|
271
|
+
return row.recipient.person_name
|
272
|
+
|
273
|
+
if "recipient_company" not in _exclude:
|
274
|
+
recipient_company = resources.Field()
|
275
|
+
|
276
|
+
def dehydrate_recipient_company(self, row):
|
277
|
+
return row.recipient.company_name
|
278
|
+
|
279
|
+
if "recipient_phone" not in _exclude:
|
280
|
+
recipient_phone = resources.Field()
|
281
|
+
|
282
|
+
def dehydrate_recipient_phone(self, row):
|
283
|
+
return row.recipient.phone_number
|
284
|
+
|
285
|
+
if "recipient_email" not in _exclude:
|
286
|
+
recipient_email = resources.Field()
|
287
|
+
|
288
|
+
def dehydrate_recipient_email(self, row):
|
289
|
+
return row.recipient.email
|
290
|
+
|
291
|
+
if "recipient_address1" not in _exclude:
|
292
|
+
recipient_address1 = resources.Field()
|
293
|
+
|
294
|
+
def dehydrate_recipient_address1(self, row):
|
295
|
+
return row.recipient.address_line1
|
296
|
+
|
297
|
+
if "recipient_address2" not in _exclude:
|
298
|
+
recipient_address2 = resources.Field()
|
299
|
+
|
300
|
+
def dehydrate_recipient_address2(self, row):
|
301
|
+
return row.recipient.address_line2
|
302
|
+
|
303
|
+
if "recipient_city" not in _exclude:
|
304
|
+
recipient_city = resources.Field()
|
305
|
+
|
306
|
+
def dehydrate_recipient_city(self, row):
|
307
|
+
return row.recipient.city
|
308
|
+
|
309
|
+
if "recipient_state" not in _exclude:
|
310
|
+
recipient_state = resources.Field()
|
311
|
+
|
312
|
+
def dehydrate_recipient_state(self, row):
|
313
|
+
return row.recipient.state_code
|
314
|
+
|
315
|
+
if "recipient_postal_code" not in _exclude:
|
316
|
+
recipient_postal_code = resources.Field()
|
317
|
+
|
318
|
+
def dehydrate_recipient_postal_code(self, row):
|
319
|
+
return row.recipient.postal_code
|
320
|
+
|
321
|
+
if "recipient_country" not in _exclude:
|
322
|
+
recipient_country = resources.Field()
|
323
|
+
|
324
|
+
def dehydrate_recipient_country(self, row):
|
325
|
+
return row.recipient.country_code
|
326
|
+
|
327
|
+
if "recipient_residential" not in _exclude:
|
328
|
+
recipient_residential = resources.Field()
|
329
|
+
|
330
|
+
def dehydrate_recipient_residential(self, row):
|
331
|
+
return row.recipient.residential
|
332
|
+
|
333
|
+
if "options" not in _exclude:
|
334
|
+
options = resources.Field()
|
335
|
+
|
336
|
+
def dehydrate_options(self, row):
|
337
|
+
return json.loads(json.dumps(row.options))
|
338
|
+
|
339
|
+
return Resource()
|
340
|
+
|
341
|
+
|
342
|
+
def shipment_import_resource(
|
343
|
+
query_params: dict,
|
344
|
+
context,
|
345
|
+
data_fields: dict = None,
|
346
|
+
batch_id: str = None,
|
347
|
+
**kwargs
|
348
|
+
):
|
349
|
+
queryset = models.Shipment.access_by(context)
|
350
|
+
field_headers = data_fields if data_fields is not None else DEFAULT_HEADERS
|
351
|
+
_exclude = query_params.get("exclude", "").split(",")
|
352
|
+
_fields = (
|
353
|
+
"shipper_name",
|
354
|
+
"shipper_company",
|
355
|
+
"shipper_address_line1",
|
356
|
+
"shipper_address_line2",
|
357
|
+
"shipper_city",
|
358
|
+
"shipper_state",
|
359
|
+
"shipper_postal_code",
|
360
|
+
"shipper_country",
|
361
|
+
"shipper_residential",
|
362
|
+
"recipient_name",
|
363
|
+
"recipient_company",
|
364
|
+
"recipient_address_line1",
|
365
|
+
"recipient_address_line2",
|
366
|
+
"recipient_city",
|
367
|
+
"recipient_state",
|
368
|
+
"recipient_postal_code",
|
369
|
+
"recipient_country",
|
370
|
+
"recipient_residential",
|
371
|
+
"parcel_width",
|
372
|
+
"parcel_height",
|
373
|
+
"parcel_length",
|
374
|
+
"parcel_dimension_unit",
|
375
|
+
"parcel_weight",
|
376
|
+
"parcel_weight_unit",
|
377
|
+
"parcel_package_preset",
|
378
|
+
"service",
|
379
|
+
"reference",
|
380
|
+
"options",
|
381
|
+
)
|
382
|
+
|
383
|
+
_Base = type(
|
384
|
+
"ResourceFields",
|
385
|
+
(resources.ModelResource,),
|
386
|
+
{
|
387
|
+
k: resources.Field(readonly=(k not in models.Shipment.__dict__))
|
388
|
+
for k in field_headers.keys()
|
389
|
+
if k not in _exclude
|
390
|
+
},
|
391
|
+
)
|
392
|
+
|
393
|
+
class Resource(_Base, resources.ModelResource):
|
394
|
+
class Meta:
|
395
|
+
model = models.Shipment
|
396
|
+
fields = _fields
|
397
|
+
exclude = _exclude
|
398
|
+
export_order = [k for k in field_headers.keys() if k not in _exclude]
|
399
|
+
|
400
|
+
def get_queryset(self):
|
401
|
+
return queryset
|
402
|
+
|
403
|
+
def get_export_headers(self):
|
404
|
+
headers = super().get_export_headers()
|
405
|
+
return [field_headers.get(k, k) for k in headers]
|
406
|
+
|
407
|
+
def init_instance(self, row=None):
|
408
|
+
service = row.get(field_headers["service"])
|
409
|
+
svc = {} if service is None else dict(perferred_service=service)
|
410
|
+
batch = {} if batch_id is None else dict(batch_id=batch_id)
|
411
|
+
options = {
|
412
|
+
**lib.to_dict(row.get(field_headers["options"]) or "{}"),
|
413
|
+
**svc,
|
414
|
+
}
|
415
|
+
|
416
|
+
data = lib.to_dict(
|
417
|
+
dict(
|
418
|
+
status="draft",
|
419
|
+
test_mode=context.test_mode,
|
420
|
+
created_by_id=context.user.id,
|
421
|
+
meta={**svc, **batch},
|
422
|
+
options=options,
|
423
|
+
shipper=dict(
|
424
|
+
person_name=row.get(field_headers["shipper_name"]),
|
425
|
+
company_name=row.get(field_headers["shipper_company"]),
|
426
|
+
address_line1=row.get(field_headers["shipper_address1"]),
|
427
|
+
address_line2=row.get(field_headers["shipper_address2"]),
|
428
|
+
city=row.get(field_headers["shipper_city"]),
|
429
|
+
state_code=row.get(field_headers["shipper_state"]),
|
430
|
+
postal_code=row.get(field_headers["shipper_postal_code"]),
|
431
|
+
country_code=row.get(field_headers["shipper_country"]),
|
432
|
+
residential=row.get(field_headers["shipper_residential"]),
|
433
|
+
),
|
434
|
+
recipient=dict(
|
435
|
+
person_name=row.get(field_headers["recipient_name"]),
|
436
|
+
company_name=row.get(field_headers["recipient_company"]),
|
437
|
+
address_line1=row.get(field_headers["recipient_address1"]),
|
438
|
+
address_line2=row.get(field_headers["recipient_address2"]),
|
439
|
+
city=row.get(field_headers["recipient_city"]),
|
440
|
+
state_code=row.get(field_headers["recipient_state"]),
|
441
|
+
postal_code=row.get(field_headers["recipient_postal_code"]),
|
442
|
+
country_code=row.get(field_headers["recipient_country"]),
|
443
|
+
residential=row.get(field_headers["recipient_residential"]),
|
444
|
+
),
|
445
|
+
parcels=[
|
446
|
+
dict(
|
447
|
+
weight=row.get(field_headers["parcel_weight"]),
|
448
|
+
weight_unit=row.get(field_headers["parcel_weight_unit"])
|
449
|
+
or "KG",
|
450
|
+
width=row.get(field_headers["parcel_width"]),
|
451
|
+
height=row.get(field_headers["parcel_height"]),
|
452
|
+
length=row.get(field_headers["parcel_length"]),
|
453
|
+
dimension_unit=row.get(
|
454
|
+
field_headers["parcel_dimension_unit"]
|
455
|
+
)
|
456
|
+
or "CM",
|
457
|
+
package_preset=row.get(
|
458
|
+
field_headers["parcel_package_preset"]
|
459
|
+
),
|
460
|
+
)
|
461
|
+
],
|
462
|
+
)
|
463
|
+
)
|
464
|
+
|
465
|
+
instance = (
|
466
|
+
ShipmentSerializer.map(data=data, context=context)
|
467
|
+
.save(fetch_rates=False)
|
468
|
+
.instance
|
469
|
+
)
|
470
|
+
|
471
|
+
return instance
|
472
|
+
|
473
|
+
return Resource()
|
@@ -0,0 +1,212 @@
|
|
1
|
+
import json
|
2
|
+
from import_export import resources
|
3
|
+
|
4
|
+
import karrio.server.serializers as serializers
|
5
|
+
import karrio.server.manager.models as manager
|
6
|
+
import karrio.server.providers.models as providers
|
7
|
+
from karrio.server.core import utils, gateway, exceptions
|
8
|
+
|
9
|
+
|
10
|
+
DEFAULT_HEADERS = {
|
11
|
+
"id": "ID",
|
12
|
+
"tracking_number": "Tracking Number",
|
13
|
+
"status": "Status",
|
14
|
+
"tracking_carrier": "Carrier",
|
15
|
+
"options": "Options",
|
16
|
+
}
|
17
|
+
|
18
|
+
|
19
|
+
def tracker_export_resource(query_params: dict, context, data_fields: dict = None):
|
20
|
+
queryset = manager.Tracking.access_by(context)
|
21
|
+
field_headers = data_fields if data_fields is not None else DEFAULT_HEADERS
|
22
|
+
_exclude = query_params.get("exclude", "").split(",")
|
23
|
+
_fields = (
|
24
|
+
"id",
|
25
|
+
"tracking_number",
|
26
|
+
"status",
|
27
|
+
"tracking_carrier",
|
28
|
+
"options",
|
29
|
+
)
|
30
|
+
|
31
|
+
class Resource(resources.ModelResource):
|
32
|
+
class Meta:
|
33
|
+
model = manager.Tracking
|
34
|
+
fields = _fields
|
35
|
+
exclude = _exclude
|
36
|
+
export_order = [k for k in field_headers.keys() if k not in _exclude]
|
37
|
+
|
38
|
+
def get_queryset(self):
|
39
|
+
return queryset
|
40
|
+
|
41
|
+
def get_export_headers(self):
|
42
|
+
headers = super().get_export_headers()
|
43
|
+
return [field_headers.get(k, k) for k in headers]
|
44
|
+
|
45
|
+
if "tracking_carrier" not in _exclude:
|
46
|
+
tracking_carrier = resources.Field()
|
47
|
+
|
48
|
+
def dehydrate_tracking_carrier(self, row):
|
49
|
+
carrier = getattr(row, "tracking_carrier", None)
|
50
|
+
|
51
|
+
return getattr(carrier, "carrier_name", None)
|
52
|
+
|
53
|
+
if "options" not in _exclude:
|
54
|
+
options = resources.Field(default=None)
|
55
|
+
|
56
|
+
def dehydrate_options(self, row):
|
57
|
+
return json.loads(json.dumps(row.options))
|
58
|
+
|
59
|
+
return Resource()
|
60
|
+
|
61
|
+
|
62
|
+
def tracker_import_resource(
|
63
|
+
query_params: dict,
|
64
|
+
context,
|
65
|
+
data_fields: dict = None,
|
66
|
+
batch_id: str = None,
|
67
|
+
**kwargs,
|
68
|
+
):
|
69
|
+
queryset = manager.Tracking.access_by(context)
|
70
|
+
field_headers = data_fields if data_fields is not None else DEFAULT_HEADERS
|
71
|
+
_exclude = query_params.get("exclude", "").split(",")
|
72
|
+
_fields = (
|
73
|
+
"id",
|
74
|
+
"tracking_number",
|
75
|
+
"status",
|
76
|
+
"tracking_carrier",
|
77
|
+
"options",
|
78
|
+
)
|
79
|
+
|
80
|
+
class Resource(resources.ModelResource):
|
81
|
+
class Meta:
|
82
|
+
model = manager.Tracking
|
83
|
+
fields = _fields
|
84
|
+
exclude = _exclude
|
85
|
+
export_order = [k for k in field_headers.keys() if k not in _exclude]
|
86
|
+
|
87
|
+
def get_queryset(self):
|
88
|
+
return queryset
|
89
|
+
|
90
|
+
def get_export_headers(self):
|
91
|
+
headers = super().get_export_headers()
|
92
|
+
return [field_headers.get(k, k) for k in headers]
|
93
|
+
|
94
|
+
def init_instance(self, row=None):
|
95
|
+
carrier_id = row.get("tracking_carrier")
|
96
|
+
tracking_number = row.get("tracking_number")
|
97
|
+
|
98
|
+
errors = []
|
99
|
+
if len(tracking_number or "") == 0:
|
100
|
+
errors.append(
|
101
|
+
exceptions.APIException(
|
102
|
+
f"A tracking number is required.",
|
103
|
+
code="tracking_number_required",
|
104
|
+
)
|
105
|
+
)
|
106
|
+
if carrier_id is None:
|
107
|
+
errors.append(
|
108
|
+
exceptions.APIException(
|
109
|
+
f"No carrier connection found.",
|
110
|
+
code="invalid_carrier",
|
111
|
+
)
|
112
|
+
)
|
113
|
+
if any(errors):
|
114
|
+
raise exceptions.APIExceptions(
|
115
|
+
errors,
|
116
|
+
code="invalid_row",
|
117
|
+
)
|
118
|
+
|
119
|
+
tracker = (
|
120
|
+
manager.Tracking.access_by(context)
|
121
|
+
.filter(tracking_number=tracking_number)
|
122
|
+
.first()
|
123
|
+
)
|
124
|
+
carrier = providers.Carrier.objects.get(id=carrier_id).settings
|
125
|
+
exists = getattr(tracker, "carrier_name", None) == carrier.carrier_name
|
126
|
+
meta = {} if batch_id is None else dict(meta=dict(batch_id=batch_id))
|
127
|
+
|
128
|
+
instance = (
|
129
|
+
tracker
|
130
|
+
if exists
|
131
|
+
else manager.Tracking(
|
132
|
+
status="unknown",
|
133
|
+
test_mode=context.test_mode,
|
134
|
+
created_by_id=context.user.id,
|
135
|
+
tracking_number=tracking_number,
|
136
|
+
events=utils.default_tracking_event(
|
137
|
+
description="Awaiting update from carrier...",
|
138
|
+
code="UNKNOWN",
|
139
|
+
),
|
140
|
+
**meta,
|
141
|
+
)
|
142
|
+
)
|
143
|
+
return instance
|
144
|
+
|
145
|
+
def save_instance(
|
146
|
+
self, instance, is_create, using_transactions=True, dry_run=False
|
147
|
+
):
|
148
|
+
is_create = instance.pk is None
|
149
|
+
|
150
|
+
result = super().save_instance(
|
151
|
+
instance,
|
152
|
+
is_create,
|
153
|
+
using_transactions,
|
154
|
+
dry_run,
|
155
|
+
)
|
156
|
+
|
157
|
+
if is_create:
|
158
|
+
serializers.link_org(instance, context)
|
159
|
+
|
160
|
+
return result
|
161
|
+
|
162
|
+
def before_import(self, dataset, using_transactions, dry_run, **kwargs):
|
163
|
+
if dry_run:
|
164
|
+
# Retrieve requested carriers from the database to set their id in the tracking_carrier column
|
165
|
+
carrier_col = dataset.get_col(
|
166
|
+
dataset.headers.index(data_fields.get("tracking_carrier"))
|
167
|
+
)
|
168
|
+
carriers = {
|
169
|
+
carrier_name: utils.failsafe(
|
170
|
+
lambda: gateway.Carriers.first(
|
171
|
+
context=context,
|
172
|
+
capability="tracking",
|
173
|
+
carrier_name=carrier_name,
|
174
|
+
raise_not_found=False,
|
175
|
+
)
|
176
|
+
)
|
177
|
+
for carrier_name in set(carrier_col)
|
178
|
+
}
|
179
|
+
|
180
|
+
del dataset[data_fields.get("tracking_carrier")]
|
181
|
+
dataset.append_col(
|
182
|
+
[
|
183
|
+
getattr(carriers.get(carrier_name), "id", None)
|
184
|
+
for carrier_name in carrier_col
|
185
|
+
],
|
186
|
+
header="tracking_carrier",
|
187
|
+
)
|
188
|
+
|
189
|
+
# set actual fields name to headers
|
190
|
+
dataset.headers = [
|
191
|
+
next(
|
192
|
+
(key for key, value in data_fields.items() if value == header),
|
193
|
+
header,
|
194
|
+
)
|
195
|
+
for header in dataset.headers
|
196
|
+
]
|
197
|
+
|
198
|
+
unknown_headers = [
|
199
|
+
header
|
200
|
+
for header in dataset.headers
|
201
|
+
if header not in manager.Tracking.__dict__
|
202
|
+
]
|
203
|
+
|
204
|
+
if any(unknown_headers):
|
205
|
+
raise exceptions.APIException(
|
206
|
+
code="unknown_headers",
|
207
|
+
detail=unknown_headers,
|
208
|
+
)
|
209
|
+
|
210
|
+
return super().before_import(dataset, using_transactions, dry_run, **kwargs)
|
211
|
+
|
212
|
+
return Resource()
|