karrio-server-manager 2026.1__py3-none-any.whl → 2026.1.3__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/migrations/0070_add_meta_and_product_fields.py +98 -0
- karrio/server/manager/migrations/0071_product_proxy.py +25 -0
- karrio/server/manager/migrations/0072_populate_json_fields.py +267 -0
- karrio/server/manager/migrations/0073_make_shipment_fk_nullable.py +36 -0
- karrio/server/manager/migrations/0074_clean_model_refactoring.py +207 -0
- karrio/server/manager/migrations/0075_populate_template_meta.py +69 -0
- karrio/server/manager/migrations/0076_remove_customs_model.py +66 -0
- karrio/server/manager/migrations/0077_add_carrier_snapshot_fields.py +83 -0
- karrio/server/manager/migrations/0078_populate_carrier_snapshots.py +112 -0
- karrio/server/manager/migrations/0079_remove_carrier_fk_fields.py +56 -0
- karrio/server/manager/migrations/0080_add_carrier_json_indexes.py +137 -0
- karrio/server/manager/migrations/0081_cleanup.py +62 -0
- karrio/server/manager/migrations/0082_shipment_fees.py +26 -0
- karrio/server/manager/models.py +421 -321
- karrio/server/manager/serializers/__init__.py +5 -4
- karrio/server/manager/serializers/address.py +8 -2
- karrio/server/manager/serializers/commodity.py +11 -4
- karrio/server/manager/serializers/document.py +29 -15
- karrio/server/manager/serializers/manifest.py +6 -3
- karrio/server/manager/serializers/parcel.py +5 -2
- karrio/server/manager/serializers/pickup.py +194 -67
- karrio/server/manager/serializers/shipment.py +232 -152
- karrio/server/manager/serializers/tracking.py +53 -12
- karrio/server/manager/tests/__init__.py +0 -1
- karrio/server/manager/tests/test_addresses.py +53 -0
- karrio/server/manager/tests/test_parcels.py +50 -0
- karrio/server/manager/tests/test_pickups.py +286 -50
- karrio/server/manager/tests/test_products.py +597 -0
- karrio/server/manager/tests/test_shipments.py +237 -92
- karrio/server/manager/tests/test_trackers.py +65 -1
- karrio/server/manager/views/__init__.py +1 -1
- karrio/server/manager/views/addresses.py +38 -2
- karrio/server/manager/views/documents.py +1 -1
- karrio/server/manager/views/parcels.py +25 -2
- karrio/server/manager/views/products.py +239 -0
- karrio/server/manager/views/trackers.py +69 -1
- {karrio_server_manager-2026.1.dist-info → karrio_server_manager-2026.1.3.dist-info}/METADATA +1 -1
- {karrio_server_manager-2026.1.dist-info → karrio_server_manager-2026.1.3.dist-info}/RECORD +40 -28
- {karrio_server_manager-2026.1.dist-info → karrio_server_manager-2026.1.3.dist-info}/WHEEL +1 -1
- karrio/server/manager/serializers/customs.py +0 -84
- karrio/server/manager/tests/test_custom_infos.py +0 -101
- karrio/server/manager/views/customs.py +0 -159
- {karrio_server_manager-2026.1.dist-info → karrio_server_manager-2026.1.3.dist-info}/top_level.txt +0 -0
|
@@ -9,6 +9,7 @@ from karrio.core.models import (
|
|
|
9
9
|
ConfirmationDetails,
|
|
10
10
|
)
|
|
11
11
|
from karrio.server.core.tests import APITestCase
|
|
12
|
+
from karrio.server.core.utils import create_carrier_snapshot
|
|
12
13
|
import karrio.server.manager.models as models
|
|
13
14
|
import karrio.server.providers.models as providers
|
|
14
15
|
|
|
@@ -17,64 +18,72 @@ class TestShipmentFixture(APITestCase):
|
|
|
17
18
|
def setUp(self) -> None:
|
|
18
19
|
super().setUp()
|
|
19
20
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
21
|
+
# Shipper and recipient as dict data for JSON fields (use proper JSON-generated ID format)
|
|
22
|
+
self.shipper_data = {
|
|
23
|
+
"id": "adr_111122223333",
|
|
24
|
+
"postal_code": "E1C4Z8",
|
|
25
|
+
"city": "Moncton",
|
|
26
|
+
"federal_tax_id": None,
|
|
27
|
+
"state_tax_id": None,
|
|
28
|
+
"person_name": "John Poop",
|
|
29
|
+
"company_name": "A corp.",
|
|
30
|
+
"country_code": "CA",
|
|
31
|
+
"email": None,
|
|
32
|
+
"phone_number": "514 000 0000",
|
|
33
|
+
"state_code": "NB",
|
|
34
|
+
"street_number": None,
|
|
35
|
+
"residential": False,
|
|
36
|
+
"address_line1": "125 Church St",
|
|
37
|
+
"address_line2": None,
|
|
38
|
+
"validate_location": False,
|
|
39
|
+
"validation": None,
|
|
40
|
+
}
|
|
41
|
+
self.recipient_data = {
|
|
42
|
+
"id": "adr_444455556666",
|
|
43
|
+
"postal_code": "V6M2V9",
|
|
44
|
+
"city": "Vancouver",
|
|
45
|
+
"federal_tax_id": None,
|
|
46
|
+
"state_tax_id": None,
|
|
47
|
+
"person_name": "Jane Doe",
|
|
48
|
+
"company_name": "B corp.",
|
|
49
|
+
"country_code": "CA",
|
|
50
|
+
"email": None,
|
|
51
|
+
"phone_number": "514 000 9999",
|
|
52
|
+
"state_code": "BC",
|
|
53
|
+
"street_number": None,
|
|
54
|
+
"residential": False,
|
|
55
|
+
"address_line1": "5840 Oak St",
|
|
56
|
+
"address_line2": None,
|
|
57
|
+
"validate_location": False,
|
|
58
|
+
"validation": None,
|
|
59
|
+
}
|
|
60
|
+
self.parcel_data = {
|
|
61
|
+
"id": "pcl_777788889999",
|
|
62
|
+
"weight": 1.0,
|
|
63
|
+
"weight_unit": "KG",
|
|
64
|
+
"width": None,
|
|
65
|
+
"height": None,
|
|
66
|
+
"length": None,
|
|
67
|
+
"dimension_unit": None,
|
|
68
|
+
"packaging_type": None,
|
|
69
|
+
"package_preset": "canadapost_corrugated_small_box",
|
|
70
|
+
"description": None,
|
|
71
|
+
"content": None,
|
|
72
|
+
"is_document": False,
|
|
73
|
+
"freight_class": None,
|
|
74
|
+
"reference_number": None,
|
|
75
|
+
"items": [],
|
|
76
|
+
"options": {},
|
|
77
|
+
"meta": {},
|
|
78
|
+
}
|
|
70
79
|
self.shipment: models.Shipment = models.Shipment.objects.create(
|
|
71
|
-
shipper=self.
|
|
72
|
-
recipient=self.
|
|
80
|
+
shipper=self.shipper_data,
|
|
81
|
+
recipient=self.recipient_data,
|
|
82
|
+
parcels=[self.parcel_data],
|
|
73
83
|
created_by=self.user,
|
|
74
84
|
test_mode=True,
|
|
75
85
|
payment={"currency": "CAD", "paid_by": "sender"},
|
|
76
86
|
)
|
|
77
|
-
self.shipment.parcels.set([self.parcel])
|
|
78
87
|
|
|
79
88
|
|
|
80
89
|
class TestShipments(APITestCase):
|
|
@@ -124,7 +133,7 @@ class TestShipmentDetails(TestShipmentFixture):
|
|
|
124
133
|
class TestShipmentPurchase(TestShipmentFixture):
|
|
125
134
|
def setUp(self) -> None:
|
|
126
135
|
super().setUp()
|
|
127
|
-
carrier = providers.
|
|
136
|
+
carrier = providers.CarrierConnection.objects.get(carrier_id="canadapost")
|
|
128
137
|
self.shipment.rates = [
|
|
129
138
|
{
|
|
130
139
|
"id": "rat_f5c1317021cb4b3c8a5d3b7369ed99e4",
|
|
@@ -195,7 +204,11 @@ class TestShipmentPurchase(TestShipmentFixture):
|
|
|
195
204
|
)
|
|
196
205
|
self.shipment.status = "purchased"
|
|
197
206
|
self.shipment.shipment_identifier = "123456789012"
|
|
198
|
-
|
|
207
|
+
# Set selected_rate and carrier snapshot
|
|
208
|
+
self.shipment.selected_rate = {
|
|
209
|
+
**self.shipment.rates[0],
|
|
210
|
+
}
|
|
211
|
+
self.shipment.carrier = create_carrier_snapshot(self.carrier)
|
|
199
212
|
self.shipment.save()
|
|
200
213
|
|
|
201
214
|
with patch("karrio.server.core.gateway.utils.identity") as mock:
|
|
@@ -463,6 +476,7 @@ SHIPMENT_RESPONSE = {
|
|
|
463
476
|
"address_line2": None,
|
|
464
477
|
"validate_location": False,
|
|
465
478
|
"validation": None,
|
|
479
|
+
"meta": {},
|
|
466
480
|
},
|
|
467
481
|
"recipient": {
|
|
468
482
|
"id": ANY,
|
|
@@ -483,6 +497,7 @@ SHIPMENT_RESPONSE = {
|
|
|
483
497
|
"address_line2": None,
|
|
484
498
|
"validate_location": False,
|
|
485
499
|
"validation": None,
|
|
500
|
+
"meta": {},
|
|
486
501
|
},
|
|
487
502
|
"parcels": [
|
|
488
503
|
{
|
|
@@ -503,6 +518,7 @@ SHIPMENT_RESPONSE = {
|
|
|
503
518
|
"freight_class": None,
|
|
504
519
|
"reference_number": ANY,
|
|
505
520
|
"options": {},
|
|
521
|
+
"meta": {},
|
|
506
522
|
}
|
|
507
523
|
],
|
|
508
524
|
"payment": {"account_number": None, "currency": "CAD", "paid_by": "sender"},
|
|
@@ -602,7 +618,7 @@ PURCHASED_SHIPMENT = {
|
|
|
602
618
|
"object_type": "shipment",
|
|
603
619
|
"tracking_url": "/v1/trackers/canadapost/123456789012",
|
|
604
620
|
"shipper": {
|
|
605
|
-
"id":
|
|
621
|
+
"id": "adr_111122223333",
|
|
606
622
|
"postal_code": "E1C4Z8",
|
|
607
623
|
"city": "Moncton",
|
|
608
624
|
"federal_tax_id": None,
|
|
@@ -620,9 +636,10 @@ PURCHASED_SHIPMENT = {
|
|
|
620
636
|
"validate_location": False,
|
|
621
637
|
"object_type": "address",
|
|
622
638
|
"validation": None,
|
|
639
|
+
"meta": {},
|
|
623
640
|
},
|
|
624
641
|
"recipient": {
|
|
625
|
-
"id":
|
|
642
|
+
"id": "adr_444455556666",
|
|
626
643
|
"postal_code": "V6M2V9",
|
|
627
644
|
"city": "Vancouver",
|
|
628
645
|
"federal_tax_id": None,
|
|
@@ -640,26 +657,28 @@ PURCHASED_SHIPMENT = {
|
|
|
640
657
|
"validate_location": False,
|
|
641
658
|
"object_type": "address",
|
|
642
659
|
"validation": None,
|
|
660
|
+
"meta": {},
|
|
643
661
|
},
|
|
644
662
|
"parcels": [
|
|
645
663
|
{
|
|
646
|
-
"id":
|
|
664
|
+
"id": "pcl_777788889999",
|
|
647
665
|
"weight": 1.0,
|
|
648
|
-
"width":
|
|
649
|
-
"height":
|
|
650
|
-
"length":
|
|
666
|
+
"width": 42.0,
|
|
667
|
+
"height": 32.0,
|
|
668
|
+
"length": 32.0,
|
|
651
669
|
"packaging_type": None,
|
|
652
670
|
"package_preset": "canadapost_corrugated_small_box",
|
|
653
671
|
"description": None,
|
|
654
672
|
"content": None,
|
|
655
673
|
"is_document": False,
|
|
656
674
|
"weight_unit": "KG",
|
|
657
|
-
"dimension_unit":
|
|
675
|
+
"dimension_unit": "CM",
|
|
658
676
|
"items": [],
|
|
659
677
|
"freight_class": None,
|
|
660
678
|
"reference_number": ANY,
|
|
661
679
|
"object_type": "parcel",
|
|
662
680
|
"options": {},
|
|
681
|
+
"meta": {},
|
|
663
682
|
}
|
|
664
683
|
],
|
|
665
684
|
"services": [],
|
|
@@ -670,25 +689,25 @@ PURCHASED_SHIPMENT = {
|
|
|
670
689
|
"customs": None,
|
|
671
690
|
"rates": [
|
|
672
691
|
{
|
|
673
|
-
"id":
|
|
692
|
+
"id": "rat_f5c1317021cb4b3c8a5d3b7369ed99e4",
|
|
674
693
|
"object_type": "rate",
|
|
675
694
|
"carrier_name": "canadapost",
|
|
676
695
|
"carrier_id": "canadapost",
|
|
677
696
|
"currency": "CAD",
|
|
678
|
-
"estimated_delivery":
|
|
697
|
+
"estimated_delivery": None,
|
|
679
698
|
"service": "canadapost_priority",
|
|
680
699
|
"total_charge": 106.71,
|
|
681
700
|
"transit_days": 2,
|
|
682
701
|
"extra_charges": [
|
|
683
|
-
{"name": "Base charge", "amount": 101.83, "currency": "CAD", "id":
|
|
684
|
-
{"name": "Fuel surcharge", "amount": 2.7, "currency": "CAD", "id":
|
|
685
|
-
{"name": "SMB Savings", "amount": -11.74, "currency": "CAD", "id":
|
|
686
|
-
{"name": "Discount", "amount": -9.04, "currency": "CAD", "id":
|
|
702
|
+
{"name": "Base charge", "amount": 101.83, "currency": "CAD", "id": None},
|
|
703
|
+
{"name": "Fuel surcharge", "amount": 2.7, "currency": "CAD", "id": None},
|
|
704
|
+
{"name": "SMB Savings", "amount": -11.74, "currency": "CAD", "id": None},
|
|
705
|
+
{"name": "Discount", "amount": -9.04, "currency": "CAD", "id": None},
|
|
687
706
|
{
|
|
688
707
|
"name": "Duties and taxes",
|
|
689
708
|
"amount": 13.92,
|
|
690
709
|
"currency": "CAD",
|
|
691
|
-
"id":
|
|
710
|
+
"id": None,
|
|
692
711
|
},
|
|
693
712
|
],
|
|
694
713
|
"meta": {
|
|
@@ -712,21 +731,21 @@ PURCHASED_SHIPMENT = {
|
|
|
712
731
|
"tracking_number": "123456789012",
|
|
713
732
|
"shipment_identifier": "123456789012",
|
|
714
733
|
"selected_rate": {
|
|
715
|
-
"id":
|
|
734
|
+
"id": "rat_f5c1317021cb4b3c8a5d3b7369ed99e4",
|
|
716
735
|
"object_type": "rate",
|
|
717
736
|
"carrier_name": "canadapost",
|
|
718
737
|
"carrier_id": "canadapost",
|
|
719
738
|
"currency": "CAD",
|
|
720
|
-
"estimated_delivery":
|
|
739
|
+
"estimated_delivery": None,
|
|
721
740
|
"service": "canadapost_priority",
|
|
722
741
|
"total_charge": 106.71,
|
|
723
742
|
"transit_days": 2,
|
|
724
743
|
"extra_charges": [
|
|
725
|
-
{"name": "Base charge", "amount": 101.83, "currency": "CAD", "id":
|
|
726
|
-
{"name": "Fuel surcharge", "amount": 2.7, "currency": "CAD", "id":
|
|
727
|
-
{"name": "SMB Savings", "amount": -11.74, "currency": "CAD", "id":
|
|
728
|
-
{"name": "Discount", "amount": -9.04, "currency": "CAD", "id":
|
|
729
|
-
{"name": "Duties and taxes", "amount": 13.92, "currency": "CAD", "id":
|
|
744
|
+
{"name": "Base charge", "amount": 101.83, "currency": "CAD", "id": None},
|
|
745
|
+
{"name": "Fuel surcharge", "amount": 2.7, "currency": "CAD", "id": None},
|
|
746
|
+
{"name": "SMB Savings", "amount": -11.74, "currency": "CAD", "id": None},
|
|
747
|
+
{"name": "Discount", "amount": -9.04, "currency": "CAD", "id": None},
|
|
748
|
+
{"name": "Duties and taxes", "amount": 13.92, "currency": "CAD", "id": None},
|
|
730
749
|
],
|
|
731
750
|
"meta": {
|
|
732
751
|
"ext": "canadapost",
|
|
@@ -744,7 +763,7 @@ PURCHASED_SHIPMENT = {
|
|
|
744
763
|
"service_name": "CANADAPOST PRIORITY",
|
|
745
764
|
},
|
|
746
765
|
"service": "canadapost_priority",
|
|
747
|
-
"selected_rate_id":
|
|
766
|
+
"selected_rate_id": "rat_f5c1317021cb4b3c8a5d3b7369ed99e4",
|
|
748
767
|
"test_mode": True,
|
|
749
768
|
"label_url": ANY,
|
|
750
769
|
"invoice_url": None,
|
|
@@ -763,7 +782,7 @@ CANCEL_RESPONSE = {
|
|
|
763
782
|
"object_type": "shipment",
|
|
764
783
|
"tracking_url": None,
|
|
765
784
|
"shipper": {
|
|
766
|
-
"id":
|
|
785
|
+
"id": "adr_111122223333",
|
|
767
786
|
"postal_code": "E1C4Z8",
|
|
768
787
|
"city": "Moncton",
|
|
769
788
|
"federal_tax_id": None,
|
|
@@ -781,9 +800,10 @@ CANCEL_RESPONSE = {
|
|
|
781
800
|
"validate_location": False,
|
|
782
801
|
"object_type": "address",
|
|
783
802
|
"validation": None,
|
|
803
|
+
"meta": {},
|
|
784
804
|
},
|
|
785
805
|
"recipient": {
|
|
786
|
-
"id":
|
|
806
|
+
"id": "adr_444455556666",
|
|
787
807
|
"postal_code": "V6M2V9",
|
|
788
808
|
"city": "Vancouver",
|
|
789
809
|
"federal_tax_id": None,
|
|
@@ -801,10 +821,11 @@ CANCEL_RESPONSE = {
|
|
|
801
821
|
"validate_location": False,
|
|
802
822
|
"object_type": "address",
|
|
803
823
|
"validation": None,
|
|
824
|
+
"meta": {},
|
|
804
825
|
},
|
|
805
826
|
"parcels": [
|
|
806
827
|
{
|
|
807
|
-
"id":
|
|
828
|
+
"id": "pcl_777788889999",
|
|
808
829
|
"weight": 1.0,
|
|
809
830
|
"width": None,
|
|
810
831
|
"height": None,
|
|
@@ -818,9 +839,10 @@ CANCEL_RESPONSE = {
|
|
|
818
839
|
"dimension_unit": None,
|
|
819
840
|
"items": [],
|
|
820
841
|
"freight_class": None,
|
|
821
|
-
"reference_number":
|
|
842
|
+
"reference_number": ANY,
|
|
822
843
|
"object_type": "parcel",
|
|
823
844
|
"options": {},
|
|
845
|
+
"meta": {},
|
|
824
846
|
}
|
|
825
847
|
],
|
|
826
848
|
"services": [],
|
|
@@ -831,12 +853,12 @@ CANCEL_RESPONSE = {
|
|
|
831
853
|
"customs": None,
|
|
832
854
|
"rates": [
|
|
833
855
|
{
|
|
834
|
-
"id":
|
|
856
|
+
"id": "rat_f5c1317021cb4b3c8a5d3b7369ed99e4",
|
|
835
857
|
"object_type": "rate",
|
|
836
858
|
"carrier_name": "canadapost",
|
|
837
859
|
"carrier_id": "canadapost",
|
|
838
860
|
"currency": "CAD",
|
|
839
|
-
"estimated_delivery":
|
|
861
|
+
"estimated_delivery": None,
|
|
840
862
|
"service": "canadapost_priority",
|
|
841
863
|
"total_charge": 106.71,
|
|
842
864
|
"transit_days": 2,
|
|
@@ -902,7 +924,7 @@ CANCEL_PURCHASED_RESPONSE = {
|
|
|
902
924
|
"object_type": "shipment",
|
|
903
925
|
"tracking_url": None,
|
|
904
926
|
"shipper": {
|
|
905
|
-
"id":
|
|
927
|
+
"id": "adr_111122223333",
|
|
906
928
|
"postal_code": "E1C4Z8",
|
|
907
929
|
"city": "Moncton",
|
|
908
930
|
"federal_tax_id": None,
|
|
@@ -920,9 +942,10 @@ CANCEL_PURCHASED_RESPONSE = {
|
|
|
920
942
|
"validate_location": False,
|
|
921
943
|
"object_type": "address",
|
|
922
944
|
"validation": None,
|
|
945
|
+
"meta": {},
|
|
923
946
|
},
|
|
924
947
|
"recipient": {
|
|
925
|
-
"id":
|
|
948
|
+
"id": "adr_444455556666",
|
|
926
949
|
"postal_code": "V6M2V9",
|
|
927
950
|
"city": "Vancouver",
|
|
928
951
|
"federal_tax_id": None,
|
|
@@ -940,10 +963,11 @@ CANCEL_PURCHASED_RESPONSE = {
|
|
|
940
963
|
"validate_location": False,
|
|
941
964
|
"object_type": "address",
|
|
942
965
|
"validation": None,
|
|
966
|
+
"meta": {},
|
|
943
967
|
},
|
|
944
968
|
"parcels": [
|
|
945
969
|
{
|
|
946
|
-
"id":
|
|
970
|
+
"id": "pcl_777788889999",
|
|
947
971
|
"weight": 1.0,
|
|
948
972
|
"width": None,
|
|
949
973
|
"height": None,
|
|
@@ -957,9 +981,10 @@ CANCEL_PURCHASED_RESPONSE = {
|
|
|
957
981
|
"dimension_unit": None,
|
|
958
982
|
"items": [],
|
|
959
983
|
"freight_class": None,
|
|
960
|
-
"reference_number":
|
|
984
|
+
"reference_number": ANY,
|
|
961
985
|
"object_type": "parcel",
|
|
962
986
|
"options": {},
|
|
987
|
+
"meta": {},
|
|
963
988
|
}
|
|
964
989
|
],
|
|
965
990
|
"services": [],
|
|
@@ -970,12 +995,12 @@ CANCEL_PURCHASED_RESPONSE = {
|
|
|
970
995
|
"customs": None,
|
|
971
996
|
"rates": [
|
|
972
997
|
{
|
|
973
|
-
"id":
|
|
998
|
+
"id": "rat_f5c1317021cb4b3c8a5d3b7369ed99e4",
|
|
974
999
|
"object_type": "rate",
|
|
975
1000
|
"carrier_name": "canadapost",
|
|
976
1001
|
"carrier_id": "canadapost",
|
|
977
1002
|
"currency": "CAD",
|
|
978
|
-
"estimated_delivery":
|
|
1003
|
+
"estimated_delivery": None,
|
|
979
1004
|
"service": "canadapost_priority",
|
|
980
1005
|
"total_charge": 106.71,
|
|
981
1006
|
"transit_days": 2,
|
|
@@ -1026,10 +1051,33 @@ CANCEL_PURCHASED_RESPONSE = {
|
|
|
1026
1051
|
"carrier_id": "canadapost",
|
|
1027
1052
|
"tracking_number": None,
|
|
1028
1053
|
"shipment_identifier": "123456789012",
|
|
1029
|
-
"selected_rate":
|
|
1054
|
+
"selected_rate": {
|
|
1055
|
+
"id": "rat_f5c1317021cb4b3c8a5d3b7369ed99e4",
|
|
1056
|
+
"object_type": "rate",
|
|
1057
|
+
"carrier_name": "canadapost",
|
|
1058
|
+
"carrier_id": "canadapost",
|
|
1059
|
+
"currency": "CAD",
|
|
1060
|
+
"estimated_delivery": None,
|
|
1061
|
+
"service": "canadapost_priority",
|
|
1062
|
+
"total_charge": 106.71,
|
|
1063
|
+
"transit_days": 2,
|
|
1064
|
+
"extra_charges": [
|
|
1065
|
+
{"name": "Base charge", "amount": 101.83, "currency": "CAD", "id": None},
|
|
1066
|
+
{"name": "Fuel surcharge", "amount": 2.7, "currency": "CAD", "id": None},
|
|
1067
|
+
{"name": "SMB Savings", "amount": -11.74, "currency": "CAD", "id": None},
|
|
1068
|
+
{"name": "Discount", "amount": -9.04, "currency": "CAD", "id": None},
|
|
1069
|
+
{"name": "Duties and taxes", "amount": 13.92, "currency": "CAD", "id": None},
|
|
1070
|
+
],
|
|
1071
|
+
"meta": {
|
|
1072
|
+
"carrier_connection_id": ANY,
|
|
1073
|
+
"rate_provider": "canadapost",
|
|
1074
|
+
"service_name": "CANADAPOST PRIORITY",
|
|
1075
|
+
},
|
|
1076
|
+
"test_mode": True,
|
|
1077
|
+
},
|
|
1030
1078
|
"meta": {},
|
|
1031
|
-
"service":
|
|
1032
|
-
"selected_rate_id":
|
|
1079
|
+
"service": "canadapost_priority",
|
|
1080
|
+
"selected_rate_id": "rat_f5c1317021cb4b3c8a5d3b7369ed99e4",
|
|
1033
1081
|
"test_mode": True,
|
|
1034
1082
|
"label_url": None,
|
|
1035
1083
|
"invoice_url": None,
|
|
@@ -1202,6 +1250,103 @@ class TestShipmentCancelIdempotent(APITestCase):
|
|
|
1202
1250
|
self.assertEqual(response_data["status"], "cancelled")
|
|
1203
1251
|
|
|
1204
1252
|
|
|
1253
|
+
class TestComputeEstimatedDelivery(APITestCase):
|
|
1254
|
+
"""Test compute_estimated_delivery utility function."""
|
|
1255
|
+
|
|
1256
|
+
def test_returns_estimated_delivery_from_rate(self):
|
|
1257
|
+
"""Test that estimated_delivery is returned directly from selected_rate."""
|
|
1258
|
+
from karrio.server.manager.serializers import compute_estimated_delivery
|
|
1259
|
+
|
|
1260
|
+
selected_rate = {"estimated_delivery": "2024-01-20", "transit_days": 5}
|
|
1261
|
+
options = {"shipping_date": "2024-01-15"}
|
|
1262
|
+
|
|
1263
|
+
estimated_delivery, shipping_date_str = compute_estimated_delivery(
|
|
1264
|
+
selected_rate, options
|
|
1265
|
+
)
|
|
1266
|
+
|
|
1267
|
+
self.assertEqual(estimated_delivery, "2024-01-20")
|
|
1268
|
+
self.assertEqual(shipping_date_str, "2024-01-15")
|
|
1269
|
+
|
|
1270
|
+
def test_computes_from_transit_days_when_no_estimated_delivery(self):
|
|
1271
|
+
"""Test that estimated_delivery is computed from transit_days when not provided."""
|
|
1272
|
+
from karrio.server.manager.serializers import compute_estimated_delivery
|
|
1273
|
+
|
|
1274
|
+
selected_rate = {"transit_days": 5}
|
|
1275
|
+
options = {"shipping_date": "2024-01-15"}
|
|
1276
|
+
|
|
1277
|
+
estimated_delivery, shipping_date_str = compute_estimated_delivery(
|
|
1278
|
+
selected_rate, options
|
|
1279
|
+
)
|
|
1280
|
+
|
|
1281
|
+
self.assertEqual(estimated_delivery, "2024-01-20")
|
|
1282
|
+
self.assertEqual(shipping_date_str, "2024-01-15")
|
|
1283
|
+
|
|
1284
|
+
def test_uses_shipment_date_option_as_fallback(self):
|
|
1285
|
+
"""Test that shipment_date is used when shipping_date is not available."""
|
|
1286
|
+
from karrio.server.manager.serializers import compute_estimated_delivery
|
|
1287
|
+
|
|
1288
|
+
selected_rate = {"transit_days": 3}
|
|
1289
|
+
options = {"shipment_date": "2024-01-10"}
|
|
1290
|
+
|
|
1291
|
+
estimated_delivery, shipping_date_str = compute_estimated_delivery(
|
|
1292
|
+
selected_rate, options
|
|
1293
|
+
)
|
|
1294
|
+
|
|
1295
|
+
self.assertEqual(estimated_delivery, "2024-01-13")
|
|
1296
|
+
self.assertEqual(shipping_date_str, "2024-01-10")
|
|
1297
|
+
|
|
1298
|
+
def test_returns_none_when_no_transit_days_or_estimated_delivery(self):
|
|
1299
|
+
"""Test that None is returned when neither estimated_delivery nor transit_days are available."""
|
|
1300
|
+
from karrio.server.manager.serializers import compute_estimated_delivery
|
|
1301
|
+
|
|
1302
|
+
selected_rate = {}
|
|
1303
|
+
options = {"shipping_date": "2024-01-15"}
|
|
1304
|
+
|
|
1305
|
+
estimated_delivery, shipping_date_str = compute_estimated_delivery(
|
|
1306
|
+
selected_rate, options
|
|
1307
|
+
)
|
|
1308
|
+
|
|
1309
|
+
self.assertIsNone(estimated_delivery)
|
|
1310
|
+
self.assertEqual(shipping_date_str, "2024-01-15")
|
|
1311
|
+
|
|
1312
|
+
def test_returns_none_when_no_shipping_date(self):
|
|
1313
|
+
"""Test that None is returned when no shipping date is available."""
|
|
1314
|
+
from karrio.server.manager.serializers import compute_estimated_delivery
|
|
1315
|
+
|
|
1316
|
+
selected_rate = {"transit_days": 5}
|
|
1317
|
+
options = {}
|
|
1318
|
+
|
|
1319
|
+
estimated_delivery, shipping_date_str = compute_estimated_delivery(
|
|
1320
|
+
selected_rate, options
|
|
1321
|
+
)
|
|
1322
|
+
|
|
1323
|
+
self.assertIsNone(estimated_delivery)
|
|
1324
|
+
self.assertIsNone(shipping_date_str)
|
|
1325
|
+
|
|
1326
|
+
def test_handles_none_inputs(self):
|
|
1327
|
+
"""Test that None inputs are handled gracefully."""
|
|
1328
|
+
from karrio.server.manager.serializers import compute_estimated_delivery
|
|
1329
|
+
|
|
1330
|
+
estimated_delivery, shipping_date_str = compute_estimated_delivery(None, None)
|
|
1331
|
+
|
|
1332
|
+
self.assertIsNone(estimated_delivery)
|
|
1333
|
+
self.assertIsNone(shipping_date_str)
|
|
1334
|
+
|
|
1335
|
+
def test_handles_datetime_format_shipping_date(self):
|
|
1336
|
+
"""Test that datetime format shipping_date is handled correctly."""
|
|
1337
|
+
from karrio.server.manager.serializers import compute_estimated_delivery
|
|
1338
|
+
|
|
1339
|
+
selected_rate = {"transit_days": 2}
|
|
1340
|
+
options = {"shipping_date": "2024-01-15T10:30"}
|
|
1341
|
+
|
|
1342
|
+
estimated_delivery, shipping_date_str = compute_estimated_delivery(
|
|
1343
|
+
selected_rate, options
|
|
1344
|
+
)
|
|
1345
|
+
|
|
1346
|
+
self.assertEqual(estimated_delivery, "2024-01-17")
|
|
1347
|
+
self.assertEqual(shipping_date_str, "2024-01-15T10:30")
|
|
1348
|
+
|
|
1349
|
+
|
|
1205
1350
|
LABEL_DOCUMENT_RESPONSE = {
|
|
1206
1351
|
"category": "label",
|
|
1207
1352
|
"format": "PDF",
|
|
@@ -5,7 +5,9 @@ from rest_framework import status
|
|
|
5
5
|
from unittest.mock import patch, ANY
|
|
6
6
|
from karrio.core.models import TrackingDetails, TrackingEvent
|
|
7
7
|
from karrio.server.core.tests import APITestCase
|
|
8
|
+
from karrio.server.core.utils import create_carrier_snapshot
|
|
8
9
|
import karrio.server.manager.models as models
|
|
10
|
+
import karrio.server.manager.serializers as serializers
|
|
9
11
|
|
|
10
12
|
|
|
11
13
|
class TestTrackers(APITestCase):
|
|
@@ -62,7 +64,7 @@ class TestTrackersUpdate(APITestCase):
|
|
|
62
64
|
],
|
|
63
65
|
"status": "in_transit",
|
|
64
66
|
"created_by": self.user,
|
|
65
|
-
"
|
|
67
|
+
"carrier": create_carrier_snapshot(self.dhl_carrier),
|
|
66
68
|
"info": {
|
|
67
69
|
"carrier_tracking_link": "https://www.dhl.com/ca-en/home/tracking/tracking-parcel.html?submit=1&tracking-id=00340434292135100124",
|
|
68
70
|
"package_weight": 0.74,
|
|
@@ -219,3 +221,65 @@ UPDATE_TRACKING_RESPONSE = {
|
|
|
219
221
|
"source": None,
|
|
220
222
|
},
|
|
221
223
|
}
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
class TestTrackerEstimatedDelivery(APITestCase):
|
|
227
|
+
"""Test estimated_delivery computation and sync."""
|
|
228
|
+
|
|
229
|
+
def setUp(self) -> None:
|
|
230
|
+
super().setUp()
|
|
231
|
+
self.tracker = models.Tracking.objects.create(
|
|
232
|
+
**{
|
|
233
|
+
"tracking_number": "TEST123456789",
|
|
234
|
+
"test_mode": True,
|
|
235
|
+
"delivered": False,
|
|
236
|
+
"events": [
|
|
237
|
+
{
|
|
238
|
+
"date": "2024-01-15",
|
|
239
|
+
"description": "Label created",
|
|
240
|
+
"code": "pending",
|
|
241
|
+
"time": "10:00",
|
|
242
|
+
}
|
|
243
|
+
],
|
|
244
|
+
"status": "pending",
|
|
245
|
+
"estimated_delivery": "2024-01-20",
|
|
246
|
+
"created_by": self.user,
|
|
247
|
+
"carrier": create_carrier_snapshot(self.dhl_carrier),
|
|
248
|
+
"info": {
|
|
249
|
+
"shipping_date": "2024-01-15",
|
|
250
|
+
"expected_delivery": "2024-01-20",
|
|
251
|
+
},
|
|
252
|
+
}
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
def test_update_tracker_syncs_estimated_delivery_to_info(self):
|
|
256
|
+
"""Test that when estimated_delivery is updated, info.expected_delivery is also updated."""
|
|
257
|
+
tracking_details = {
|
|
258
|
+
"estimated_delivery": "2024-01-22",
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
serializers.tracking.update_tracker(self.tracker, tracking_details)
|
|
262
|
+
self.tracker.refresh_from_db()
|
|
263
|
+
|
|
264
|
+
self.assertEqual(self.tracker.estimated_delivery.isoformat(), "2024-01-22")
|
|
265
|
+
self.assertEqual(self.tracker.info.get("expected_delivery"), "2024-01-22")
|
|
266
|
+
|
|
267
|
+
def test_update_tracker_carrier_estimated_delivery_supersedes_info(self):
|
|
268
|
+
"""Test that carrier's estimated_delivery supersedes existing info.expected_delivery."""
|
|
269
|
+
# First set a different expected_delivery in info
|
|
270
|
+
self.tracker.info = {**self.tracker.info, "expected_delivery": "2024-01-18"}
|
|
271
|
+
self.tracker.save()
|
|
272
|
+
|
|
273
|
+
# Update with carrier's estimated_delivery
|
|
274
|
+
tracking_details = {
|
|
275
|
+
"estimated_delivery": "2024-01-25",
|
|
276
|
+
"info": {"customer_name": "John Doe"},
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
serializers.tracking.update_tracker(self.tracker, tracking_details)
|
|
280
|
+
self.tracker.refresh_from_db()
|
|
281
|
+
|
|
282
|
+
# Carrier's estimated_delivery should supersede
|
|
283
|
+
self.assertEqual(self.tracker.estimated_delivery.isoformat(), "2024-01-25")
|
|
284
|
+
self.assertEqual(self.tracker.info.get("expected_delivery"), "2024-01-25")
|
|
285
|
+
self.assertEqual(self.tracker.info.get("customer_name"), "John Doe")
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import karrio.server.manager.views.addresses
|
|
2
2
|
import karrio.server.manager.views.parcels
|
|
3
|
+
import karrio.server.manager.views.products
|
|
3
4
|
import karrio.server.manager.views.shipments
|
|
4
5
|
import karrio.server.manager.views.trackers
|
|
5
|
-
import karrio.server.manager.views.customs
|
|
6
6
|
import karrio.server.manager.views.pickups
|
|
7
7
|
import karrio.server.manager.views.documents
|
|
8
8
|
import karrio.server.manager.views.manifests
|