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,250 @@
|
|
1
|
+
import typing
|
2
|
+
import logging
|
3
|
+
from django.utils import timezone
|
4
|
+
|
5
|
+
import karrio.lib as lib
|
6
|
+
import karrio.server.serializers as serializers
|
7
|
+
import karrio.server.core.utils as utils
|
8
|
+
from karrio.server.core.gateway import Shipments, Carriers
|
9
|
+
from karrio.server.core.serializers import (
|
10
|
+
TrackingDetails,
|
11
|
+
TrackingRequest,
|
12
|
+
ShipmentStatus,
|
13
|
+
TrackerStatus,
|
14
|
+
TrackingInfo,
|
15
|
+
)
|
16
|
+
|
17
|
+
import karrio.server.manager.models as models
|
18
|
+
|
19
|
+
logger = logging.getLogger(__name__)
|
20
|
+
DEFAULT_CARRIER_FILTER: typing.Any = dict(active=True, capability="tracking")
|
21
|
+
|
22
|
+
|
23
|
+
@serializers.owned_model_serializer
|
24
|
+
class TrackingSerializer(TrackingDetails):
|
25
|
+
carrier_id = serializers.CharField(required=False)
|
26
|
+
carrier_name = serializers.CharField(required=False)
|
27
|
+
test_mode = serializers.BooleanField(required=False)
|
28
|
+
options = serializers.PlainDictField(
|
29
|
+
required=False,
|
30
|
+
default={},
|
31
|
+
help_text="additional tracking options",
|
32
|
+
)
|
33
|
+
info = TrackingInfo(
|
34
|
+
required=False,
|
35
|
+
default={},
|
36
|
+
help_text="The package and shipment tracking details",
|
37
|
+
)
|
38
|
+
metadata = serializers.PlainDictField(
|
39
|
+
required=False,
|
40
|
+
default={},
|
41
|
+
help_text="The carrier user metadata.",
|
42
|
+
)
|
43
|
+
|
44
|
+
def create(self, validated_data: dict, context, **kwargs) -> models.Tracking:
|
45
|
+
options = validated_data["options"]
|
46
|
+
metadata = validated_data["metadata"]
|
47
|
+
carrier_filter = validated_data["carrier_filter"]
|
48
|
+
tracking_number = validated_data["tracking_number"]
|
49
|
+
account_number = validated_data.get("account_number")
|
50
|
+
info = validated_data.get("info")
|
51
|
+
reference = validated_data.get("reference")
|
52
|
+
pending_pickup = validated_data.get("pending_pickup")
|
53
|
+
carrier = Carriers.first(
|
54
|
+
context=context,
|
55
|
+
**{"raise_not_found": True, **DEFAULT_CARRIER_FILTER, **carrier_filter}
|
56
|
+
)
|
57
|
+
|
58
|
+
response = Shipments.track(
|
59
|
+
TrackingRequest(
|
60
|
+
dict(
|
61
|
+
tracking_numbers=[tracking_number],
|
62
|
+
account_number=account_number,
|
63
|
+
reference=reference,
|
64
|
+
options=options,
|
65
|
+
info=info,
|
66
|
+
)
|
67
|
+
).data,
|
68
|
+
carrier=carrier,
|
69
|
+
raise_on_error=(pending_pickup is not True),
|
70
|
+
)
|
71
|
+
|
72
|
+
return models.Tracking.objects.create(
|
73
|
+
created_by=context.user,
|
74
|
+
tracking_number=tracking_number,
|
75
|
+
account_number=account_number,
|
76
|
+
events=lib.to_dict(response.tracking.events),
|
77
|
+
test_mode=response.tracking.test_mode,
|
78
|
+
delivered=response.tracking.delivered,
|
79
|
+
status=response.tracking.status,
|
80
|
+
tracking_carrier=carrier,
|
81
|
+
estimated_delivery=response.tracking.estimated_delivery,
|
82
|
+
messages=lib.to_dict(response.messages),
|
83
|
+
info=lib.to_dict(response.tracking.info),
|
84
|
+
meta=response.tracking.meta,
|
85
|
+
options=response.tracking.options,
|
86
|
+
reference=reference,
|
87
|
+
metadata=metadata,
|
88
|
+
delivery_image=getattr(response.tracking.images, "delivery_image", None),
|
89
|
+
signature_image=getattr(response.tracking.images, "signature_image", None),
|
90
|
+
)
|
91
|
+
|
92
|
+
def update(
|
93
|
+
self, instance: models.Tracking, validated_data: dict, context, **kwargs
|
94
|
+
) -> models.Tracking:
|
95
|
+
last_fetch = (
|
96
|
+
timezone.now() - instance.updated_at
|
97
|
+
).seconds / 60 # minutes since last fetch
|
98
|
+
|
99
|
+
if last_fetch >= 1 and instance.delivered is not True:
|
100
|
+
carrier_filter = validated_data["carrier_filter"]
|
101
|
+
options = {
|
102
|
+
instance.tracking_number: {
|
103
|
+
**(instance.options.get(instance.tracking_number) or {}),
|
104
|
+
**(
|
105
|
+
(validated_data.get("options") or {}).get(
|
106
|
+
instance.tracking_number
|
107
|
+
)
|
108
|
+
or {}
|
109
|
+
),
|
110
|
+
}
|
111
|
+
}
|
112
|
+
carrier = (
|
113
|
+
Carriers.first(
|
114
|
+
context=context, **{**DEFAULT_CARRIER_FILTER, **carrier_filter}
|
115
|
+
)
|
116
|
+
or instance.tracking_carrier
|
117
|
+
)
|
118
|
+
|
119
|
+
response = Shipments.track(
|
120
|
+
payload=TrackingRequest(
|
121
|
+
dict(tracking_numbers=[instance.tracking_number], options=options)
|
122
|
+
).data,
|
123
|
+
carrier=carrier,
|
124
|
+
)
|
125
|
+
# update values only if changed; This is important for webhooks notification
|
126
|
+
changes = []
|
127
|
+
details = response.tracking
|
128
|
+
info = lib.to_dict(details.info or {})
|
129
|
+
events = utils.process_events(
|
130
|
+
response_events=details.events, current_events=instance.events
|
131
|
+
)
|
132
|
+
|
133
|
+
if events != instance.events:
|
134
|
+
instance.events = events
|
135
|
+
changes.append("events")
|
136
|
+
|
137
|
+
if response.messages != instance.messages:
|
138
|
+
instance.messages = lib.to_dict(response.messages)
|
139
|
+
changes.append("messages")
|
140
|
+
|
141
|
+
if details.delivered != instance.delivered:
|
142
|
+
instance.delivered = details.delivered
|
143
|
+
changes.append("delivered")
|
144
|
+
|
145
|
+
if details.status != instance.status:
|
146
|
+
instance.status = details.status
|
147
|
+
changes.append("status")
|
148
|
+
|
149
|
+
if details.estimated_delivery != instance.estimated_delivery:
|
150
|
+
instance.estimated_delivery = details.estimated_delivery
|
151
|
+
changes.append("estimated_delivery")
|
152
|
+
|
153
|
+
if details.options != instance.options:
|
154
|
+
instance.options = details.options
|
155
|
+
changes.append("options")
|
156
|
+
|
157
|
+
if any(info.keys()) and info != instance.info:
|
158
|
+
instance.info = serializers.process_dictionaries_mutations(
|
159
|
+
["info"], dict(info=info), instance
|
160
|
+
)["info"]
|
161
|
+
changes.append("info")
|
162
|
+
|
163
|
+
if carrier.id != instance.tracking_carrier.id:
|
164
|
+
instance.carrier = carrier
|
165
|
+
changes.append("tracking_carrier")
|
166
|
+
|
167
|
+
if details.images is not None and (
|
168
|
+
details.images.delivery_image != instance.delivery_image
|
169
|
+
or details.images.signature_image != instance.signature_image
|
170
|
+
):
|
171
|
+
changes.append("delivery_image")
|
172
|
+
changes.append("signature_image")
|
173
|
+
instance.delivery_image = (
|
174
|
+
details.images.delivery_image or instance.delivery_image
|
175
|
+
)
|
176
|
+
instance.signature_image = (
|
177
|
+
details.images.signature_image or instance.signature_image
|
178
|
+
)
|
179
|
+
|
180
|
+
if any(changes):
|
181
|
+
instance.save(update_fields=changes)
|
182
|
+
update_shipment_tracker(instance)
|
183
|
+
|
184
|
+
return instance
|
185
|
+
|
186
|
+
|
187
|
+
@serializers.owned_model_serializer
|
188
|
+
class TrackerUpdateData(serializers.Serializer):
|
189
|
+
info = TrackingInfo(
|
190
|
+
required=False,
|
191
|
+
allow_null=True,
|
192
|
+
help_text="The package and shipment tracking details",
|
193
|
+
)
|
194
|
+
metadata = serializers.PlainDictField(
|
195
|
+
required=False, help_text="User metadata for the tracker"
|
196
|
+
)
|
197
|
+
|
198
|
+
def update(
|
199
|
+
self, instance: models.Tracking, validated_data: dict, **kwargs
|
200
|
+
) -> models.Tracking:
|
201
|
+
changes = []
|
202
|
+
data = validated_data.copy()
|
203
|
+
|
204
|
+
for key, val in data.items():
|
205
|
+
if key in models.Tracking.DIRECT_PROPS and getattr(instance, key) != val:
|
206
|
+
setattr(instance, key, val)
|
207
|
+
changes.append(key)
|
208
|
+
validated_data.pop(key)
|
209
|
+
|
210
|
+
if any(changes):
|
211
|
+
instance.save(update_fields=changes)
|
212
|
+
|
213
|
+
return instance
|
214
|
+
|
215
|
+
|
216
|
+
def can_mutate_tracker(
|
217
|
+
tracker: models.Tracking,
|
218
|
+
update: bool = False,
|
219
|
+
payload: dict = None,
|
220
|
+
):
|
221
|
+
if update and tracker.delivered and [*(payload or {}).keys()] == ["metadata"]:
|
222
|
+
return
|
223
|
+
|
224
|
+
if update and all([key in ["metadata", "info"] for key in (payload or {}).keys()]):
|
225
|
+
return
|
226
|
+
|
227
|
+
|
228
|
+
def update_shipment_tracker(tracker: models.Tracking):
|
229
|
+
try:
|
230
|
+
if tracker.status == TrackerStatus.delivered.value:
|
231
|
+
status = ShipmentStatus.delivered.value
|
232
|
+
elif tracker.status == TrackerStatus.pending.value:
|
233
|
+
status = tracker.shipment.status
|
234
|
+
elif tracker.status == TrackerStatus.out_for_delivery.value:
|
235
|
+
status = ShipmentStatus.out_for_delivery.value
|
236
|
+
elif tracker.status == TrackerStatus.delivery_failed.value:
|
237
|
+
status = ShipmentStatus.delivery_failed.value
|
238
|
+
elif tracker.status in [
|
239
|
+
TrackerStatus.on_hold.value,
|
240
|
+
TrackerStatus.delivery_delayed.value,
|
241
|
+
]:
|
242
|
+
status = ShipmentStatus.needs_attention.value
|
243
|
+
else:
|
244
|
+
status = ShipmentStatus.in_transit.value
|
245
|
+
|
246
|
+
if tracker.shipment is not None and tracker.shipment.status != status:
|
247
|
+
tracker.shipment.status = status
|
248
|
+
tracker.shipment.save(update_fields=["status"])
|
249
|
+
except Exception as e:
|
250
|
+
logger.exception("Failed to update the tracked shipment", e)
|
@@ -0,0 +1,70 @@
|
|
1
|
+
import logging
|
2
|
+
from django.db.models import signals
|
3
|
+
|
4
|
+
from karrio.server.core import utils
|
5
|
+
import karrio.server.manager.models as models
|
6
|
+
import karrio.server.manager.serializers as serializers
|
7
|
+
|
8
|
+
logger = logging.getLogger(__name__)
|
9
|
+
RATE_RELATED_CHANGES = [
|
10
|
+
# address related changes
|
11
|
+
"address_line1",
|
12
|
+
"address_line2",
|
13
|
+
"postal_code",
|
14
|
+
"city",
|
15
|
+
"state_province",
|
16
|
+
"country_code",
|
17
|
+
"residential",
|
18
|
+
# parcel related changes
|
19
|
+
"length",
|
20
|
+
"width",
|
21
|
+
"height",
|
22
|
+
"weight",
|
23
|
+
"weight_unit",
|
24
|
+
"dimension_unit",
|
25
|
+
"is_document",
|
26
|
+
]
|
27
|
+
|
28
|
+
|
29
|
+
def register_signals():
|
30
|
+
signals.post_save.connect(address_updated, sender=models.Address)
|
31
|
+
signals.post_save.connect(parcel_updated, sender=models.Parcel)
|
32
|
+
signals.post_delete.connect(parcel_deleted, sender=models.Parcel)
|
33
|
+
|
34
|
+
logger.info("karrio.manager signals registered...")
|
35
|
+
|
36
|
+
|
37
|
+
@utils.disable_for_loaddata
|
38
|
+
def address_updated(
|
39
|
+
sender, instance, created, raw, using, update_fields, *args, **kwargs
|
40
|
+
):
|
41
|
+
""" """
|
42
|
+
changes = update_fields or []
|
43
|
+
|
44
|
+
if any([change in RATE_RELATED_CHANGES for change in changes]):
|
45
|
+
serializers.reset_related_shipment_rates(instance.shipment)
|
46
|
+
|
47
|
+
|
48
|
+
@utils.disable_for_loaddata
|
49
|
+
def parcel_updated(
|
50
|
+
sender, instance, created, raw, using, update_fields, *args, **kwargs
|
51
|
+
):
|
52
|
+
""" """
|
53
|
+
changes = update_fields or []
|
54
|
+
|
55
|
+
if instance.reference_number is None:
|
56
|
+
count = models.Parcel.objects.filter(
|
57
|
+
**({"org__id": instance.link.org.id} if hasattr(instance, "link") else {})
|
58
|
+
).count()
|
59
|
+
|
60
|
+
instance.reference_number = str(count + 1).zfill(10)
|
61
|
+
instance.save()
|
62
|
+
|
63
|
+
if any([change in RATE_RELATED_CHANGES for change in changes]):
|
64
|
+
serializers.reset_related_shipment_rates(instance.shipment)
|
65
|
+
|
66
|
+
|
67
|
+
@utils.disable_for_loaddata
|
68
|
+
def parcel_deleted(sender, instance, *args, **kwargs):
|
69
|
+
""" """
|
70
|
+
serializers.reset_related_shipment_rates(instance.shipment)
|
@@ -0,0 +1,10 @@
|
|
1
|
+
import logging
|
2
|
+
|
3
|
+
logging.disable(logging.CRITICAL)
|
4
|
+
|
5
|
+
from karrio.server.manager.tests.test_addresses import *
|
6
|
+
from karrio.server.manager.tests.test_parcels import *
|
7
|
+
from karrio.server.manager.tests.test_shipments import *
|
8
|
+
from karrio.server.manager.tests.test_trackers import *
|
9
|
+
from karrio.server.manager.tests.test_custom_infos import *
|
10
|
+
from karrio.server.manager.tests.test_pickups import *
|
@@ -0,0 +1,110 @@
|
|
1
|
+
import json
|
2
|
+
from unittest.mock import ANY
|
3
|
+
from django.urls import reverse
|
4
|
+
from rest_framework import status
|
5
|
+
from karrio.server.core.tests import APITestCase
|
6
|
+
from karrio.server.manager.models import Address
|
7
|
+
|
8
|
+
|
9
|
+
class TestAddresses(APITestCase):
|
10
|
+
def test_create_address(self):
|
11
|
+
url = reverse("karrio.server.manager:address-list")
|
12
|
+
data = ADDRESS_DATA
|
13
|
+
|
14
|
+
response = self.client.post(url, data)
|
15
|
+
response_data = json.loads(response.content)
|
16
|
+
|
17
|
+
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
18
|
+
self.assertDictEqual(response_data, ADDRESS_RESPONSE)
|
19
|
+
|
20
|
+
|
21
|
+
class TestAddressDetails(APITestCase):
|
22
|
+
def setUp(self) -> None:
|
23
|
+
super().setUp()
|
24
|
+
self.address: Address = Address.objects.create(
|
25
|
+
**{
|
26
|
+
"address_line1": "5205 rue riviera",
|
27
|
+
"person_name": "Old town Daniel",
|
28
|
+
"phone_number": "438 222 2222",
|
29
|
+
"city": "Montreal",
|
30
|
+
"country_code": "CA",
|
31
|
+
"postal_code": "H8Z2Z3",
|
32
|
+
"residential": True,
|
33
|
+
"state_code": "QC",
|
34
|
+
"validate_location": False,
|
35
|
+
"validation": None,
|
36
|
+
"created_by": self.user,
|
37
|
+
}
|
38
|
+
)
|
39
|
+
|
40
|
+
def test_update_address(self):
|
41
|
+
url = reverse(
|
42
|
+
"karrio.server.manager:address-details", kwargs=dict(pk=self.address.pk)
|
43
|
+
)
|
44
|
+
data = ADDRESS_UPDATE_DATA
|
45
|
+
|
46
|
+
response = self.client.patch(url, data)
|
47
|
+
response_data = json.loads(response.content)
|
48
|
+
|
49
|
+
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
50
|
+
self.assertDictEqual(response_data, ADDRESS_UPDATE_RESPONSE)
|
51
|
+
|
52
|
+
|
53
|
+
ADDRESS_DATA = {
|
54
|
+
"address_line1": "5205 rue riviera",
|
55
|
+
"person_name": "Old town Daniel",
|
56
|
+
"phone_number": "438 222 2222",
|
57
|
+
"city": "Montreal",
|
58
|
+
"country_code": "CA",
|
59
|
+
"postal_code": "H8Z2Z3",
|
60
|
+
"residential": True,
|
61
|
+
"state_code": "QC",
|
62
|
+
}
|
63
|
+
|
64
|
+
ADDRESS_RESPONSE = {
|
65
|
+
"id": ANY,
|
66
|
+
"object_type": "address",
|
67
|
+
"postal_code": "H8Z2Z3",
|
68
|
+
"city": "Montreal",
|
69
|
+
"federal_tax_id": None,
|
70
|
+
"state_tax_id": None,
|
71
|
+
"person_name": "Old town Daniel",
|
72
|
+
"company_name": None,
|
73
|
+
"country_code": "CA",
|
74
|
+
"email": None,
|
75
|
+
"phone_number": "+1 438-222-2222",
|
76
|
+
"state_code": "QC",
|
77
|
+
"street_number": None,
|
78
|
+
"residential": True,
|
79
|
+
"address_line1": "5205 rue riviera",
|
80
|
+
"address_line2": None,
|
81
|
+
"validate_location": False,
|
82
|
+
"validation": None,
|
83
|
+
}
|
84
|
+
|
85
|
+
ADDRESS_UPDATE_DATA = {
|
86
|
+
"person_name": "John Doe",
|
87
|
+
"company_name": "Doe corp",
|
88
|
+
"residential": False,
|
89
|
+
}
|
90
|
+
|
91
|
+
ADDRESS_UPDATE_RESPONSE = {
|
92
|
+
"id": ANY,
|
93
|
+
"object_type": "address",
|
94
|
+
"postal_code": "H8Z2Z3",
|
95
|
+
"city": "Montreal",
|
96
|
+
"federal_tax_id": None,
|
97
|
+
"state_tax_id": None,
|
98
|
+
"person_name": "John Doe",
|
99
|
+
"company_name": "Doe corp",
|
100
|
+
"country_code": "CA",
|
101
|
+
"email": None,
|
102
|
+
"phone_number": "438 222 2222",
|
103
|
+
"state_code": "QC",
|
104
|
+
"street_number": None,
|
105
|
+
"residential": False,
|
106
|
+
"address_line1": "5205 rue riviera",
|
107
|
+
"address_line2": None,
|
108
|
+
"validate_location": False,
|
109
|
+
"validation": None,
|
110
|
+
}
|
@@ -0,0 +1,97 @@
|
|
1
|
+
import json
|
2
|
+
from unittest.mock import ANY
|
3
|
+
from django.urls import reverse
|
4
|
+
from rest_framework import status
|
5
|
+
from karrio.server.core.tests import APITestCase
|
6
|
+
from karrio.server.manager.models import Customs
|
7
|
+
|
8
|
+
|
9
|
+
class TestCustomsInfo(APITestCase):
|
10
|
+
def test_create_customs(self):
|
11
|
+
url = reverse("karrio.server.manager:customs-list")
|
12
|
+
data = CUSTOMS_DATA
|
13
|
+
|
14
|
+
response = self.client.post(url, data)
|
15
|
+
response_data = json.loads(response.content)
|
16
|
+
|
17
|
+
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
18
|
+
self.assertDictEqual(response_data, CUSTOMS_RESPONSE)
|
19
|
+
|
20
|
+
|
21
|
+
class TestCustomsInfoDetails(APITestCase):
|
22
|
+
def setUp(self) -> None:
|
23
|
+
super().setUp()
|
24
|
+
self.customs: Customs = Customs.objects.create(**{"created_by": self.user})
|
25
|
+
|
26
|
+
def test_update_customs(self):
|
27
|
+
url = reverse(
|
28
|
+
"karrio.server.manager:customs-details", kwargs=dict(pk=self.customs.pk)
|
29
|
+
)
|
30
|
+
data = CUSTOMS_UPDATE_DATA
|
31
|
+
|
32
|
+
response = self.client.patch(url, data)
|
33
|
+
response_data = json.loads(response.content)
|
34
|
+
|
35
|
+
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
36
|
+
self.assertDictEqual(response_data, CUSTOMS_UPDATE_RESPONSE)
|
37
|
+
|
38
|
+
|
39
|
+
CUSTOMS_DATA = {
|
40
|
+
"incoterm": "DDU",
|
41
|
+
"commodities": [
|
42
|
+
{"title": "cn", "weight": 4.0, "weight_unit": "KG", "sku": "cc"},
|
43
|
+
],
|
44
|
+
}
|
45
|
+
|
46
|
+
CUSTOMS_RESPONSE = {
|
47
|
+
"certify": None,
|
48
|
+
"commercial_invoice": None,
|
49
|
+
"commodities": [
|
50
|
+
{
|
51
|
+
"description": None,
|
52
|
+
"title": "cn",
|
53
|
+
"id": ANY,
|
54
|
+
"object_type": "commodity",
|
55
|
+
"origin_country": None,
|
56
|
+
"parent_id": None,
|
57
|
+
"quantity": 1,
|
58
|
+
"metadata": {},
|
59
|
+
"sku": "cc",
|
60
|
+
"hs_code": None,
|
61
|
+
"value_amount": None,
|
62
|
+
"value_currency": None,
|
63
|
+
"weight": 4.0,
|
64
|
+
"weight_unit": "KG",
|
65
|
+
}
|
66
|
+
],
|
67
|
+
"content_description": None,
|
68
|
+
"content_type": None,
|
69
|
+
"duty": None,
|
70
|
+
"duty_billing_address": None,
|
71
|
+
"id": ANY,
|
72
|
+
"object_type": "customs_info",
|
73
|
+
"incoterm": "DDU",
|
74
|
+
"invoice": None,
|
75
|
+
"invoice_date": None,
|
76
|
+
"options": {},
|
77
|
+
"signer": None,
|
78
|
+
}
|
79
|
+
|
80
|
+
CUSTOMS_UPDATE_DATA = {"incoterm": "DDP"}
|
81
|
+
|
82
|
+
CUSTOMS_UPDATE_RESPONSE = {
|
83
|
+
"certify": None,
|
84
|
+
"commercial_invoice": None,
|
85
|
+
"commodities": [],
|
86
|
+
"content_description": None,
|
87
|
+
"content_type": None,
|
88
|
+
"duty": None,
|
89
|
+
"duty_billing_address": None,
|
90
|
+
"id": ANY,
|
91
|
+
"object_type": "customs_info",
|
92
|
+
"incoterm": "DDP",
|
93
|
+
"invoice": None,
|
94
|
+
"invoice_date": None,
|
95
|
+
"options": {},
|
96
|
+
"signer": None,
|
97
|
+
}
|
@@ -0,0 +1,104 @@
|
|
1
|
+
import json
|
2
|
+
from unittest.mock import ANY
|
3
|
+
from django.urls import reverse
|
4
|
+
from rest_framework import status
|
5
|
+
from karrio.server.core.tests import APITestCase
|
6
|
+
from karrio.server.manager.models import Parcel
|
7
|
+
|
8
|
+
|
9
|
+
class TestParcels(APITestCase):
|
10
|
+
def test_create_parcel(self):
|
11
|
+
url = reverse("karrio.server.manager:parcel-list")
|
12
|
+
data = PARCEL_DATA
|
13
|
+
|
14
|
+
response = self.client.post(url, data)
|
15
|
+
response_data = json.loads(response.content)
|
16
|
+
|
17
|
+
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
18
|
+
self.assertDictEqual(response_data, PARCEL_RESPONSE)
|
19
|
+
|
20
|
+
|
21
|
+
class TestParcelDetails(APITestCase):
|
22
|
+
def setUp(self) -> None:
|
23
|
+
super().setUp()
|
24
|
+
self.parcel: Parcel = Parcel.objects.create(
|
25
|
+
**{
|
26
|
+
"weight": 1,
|
27
|
+
"width": 20,
|
28
|
+
"height": 10,
|
29
|
+
"length": 29,
|
30
|
+
"weight_unit": "KG",
|
31
|
+
"dimension_unit": "CM",
|
32
|
+
"created_by": self.user,
|
33
|
+
}
|
34
|
+
)
|
35
|
+
|
36
|
+
def test_update_parcel(self):
|
37
|
+
url = reverse(
|
38
|
+
"karrio.server.manager:parcel-details", kwargs=dict(pk=self.parcel.pk)
|
39
|
+
)
|
40
|
+
data = PARCEL_UPDATE_DATA
|
41
|
+
|
42
|
+
response = self.client.patch(url, data)
|
43
|
+
response_data = json.loads(response.content)
|
44
|
+
|
45
|
+
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
46
|
+
self.assertDictEqual(response_data, PARCEL_UPDATE_RESPONSE)
|
47
|
+
|
48
|
+
|
49
|
+
PARCEL_DATA = {
|
50
|
+
"weight": 1,
|
51
|
+
"width": 20,
|
52
|
+
"height": 10,
|
53
|
+
"length": 29,
|
54
|
+
"weight_unit": "KG",
|
55
|
+
"dimension_unit": "CM",
|
56
|
+
}
|
57
|
+
|
58
|
+
PARCEL_RESPONSE = {
|
59
|
+
"id": ANY,
|
60
|
+
"object_type": "parcel",
|
61
|
+
"weight": 1.0,
|
62
|
+
"width": 20.0,
|
63
|
+
"height": 10.0,
|
64
|
+
"length": 29.0,
|
65
|
+
"packaging_type": None,
|
66
|
+
"package_preset": None,
|
67
|
+
"description": None,
|
68
|
+
"content": None,
|
69
|
+
"is_document": False,
|
70
|
+
"items": [],
|
71
|
+
"weight_unit": "KG",
|
72
|
+
"dimension_unit": "CM",
|
73
|
+
"freight_class": None,
|
74
|
+
"reference_number": ANY,
|
75
|
+
"options": {},
|
76
|
+
}
|
77
|
+
|
78
|
+
PARCEL_UPDATE_DATA = {
|
79
|
+
"width": 5.0,
|
80
|
+
"height": 4.5,
|
81
|
+
"length": 10.0,
|
82
|
+
"weight_unit": "LB",
|
83
|
+
"dimension_unit": "IN",
|
84
|
+
}
|
85
|
+
|
86
|
+
PARCEL_UPDATE_RESPONSE = {
|
87
|
+
"id": ANY,
|
88
|
+
"object_type": "parcel",
|
89
|
+
"weight": 1.0,
|
90
|
+
"width": 5.0,
|
91
|
+
"height": 4.5,
|
92
|
+
"length": 10.0,
|
93
|
+
"packaging_type": None,
|
94
|
+
"package_preset": None,
|
95
|
+
"description": None,
|
96
|
+
"content": None,
|
97
|
+
"is_document": False,
|
98
|
+
"items": [],
|
99
|
+
"weight_unit": "LB",
|
100
|
+
"dimension_unit": "IN",
|
101
|
+
"freight_class": None,
|
102
|
+
"reference_number": ANY,
|
103
|
+
"options": {},
|
104
|
+
}
|