karrio-server-manager 2025.5.1__py3-none-any.whl → 2025.5.2__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.
@@ -1,3 +1,4 @@
1
+ import uuid
1
2
  import typing
2
3
  import rest_framework.status as status
3
4
  import django.db.transaction as transaction
@@ -129,20 +130,33 @@ class ShipmentSerializer(ShipmentData):
129
130
  def create(
130
131
  self, validated_data: dict, context: Context, **kwargs
131
132
  ) -> models.Shipment:
133
+ # fmt: off
132
134
  service = validated_data.get("service")
133
135
  carrier_ids = validated_data.get("carrier_ids") or []
134
136
  fetch_rates = validated_data.get("fetch_rates") is not False
135
137
  services = [service] if service is not None else validated_data.get("services")
138
+ options = validated_data.get("options") or {}
139
+
140
+ # Check if we should skip rate fetching for has_alternative_services
141
+ skip_rate_fetching, resolved_carrier_name, _ = (
142
+ resolve_alternative_service_carrier(
143
+ service=service,
144
+ carrier_ids=carrier_ids,
145
+ carriers=[], # Pre-check before loading carriers
146
+ options=options,
147
+ context=context,
148
+ )
149
+ )
150
+
136
151
  carriers = gateway.Carriers.list(
137
152
  context=context,
138
153
  carrier_ids=carrier_ids,
139
- **({"services": services} if any(services) else {}),
154
+ **({"carrier_name": resolved_carrier_name} if resolved_carrier_name else {}),
155
+ **({"services": services} if any(services) and not skip_rate_fetching else {}),
140
156
  **{"raise_not_found": True, **DEFAULT_CARRIER_FILTER},
141
157
  )
142
158
  payment = validated_data.get("payment") or lib.to_dict(
143
- datatypes.Payment(
144
- currency=(validated_data.get("options") or {}).get("currency")
145
- )
159
+ datatypes.Payment(currency=options.get("currency"))
146
160
  )
147
161
  rating_data = {
148
162
  **validated_data,
@@ -152,11 +166,11 @@ class ShipmentSerializer(ShipmentData):
152
166
  messages = validated_data.get("messages") or []
153
167
  apply_shipping_rules = lib.identity(
154
168
  getattr(conf.settings, "SHIPPING_RULES", False)
155
- and (validated_data.get("options") or {}).get("apply_shipping_rules", False)
169
+ and options.get("apply_shipping_rules", False)
156
170
  )
157
171
 
158
- # Get live rates.
159
- if fetch_rates or apply_shipping_rules:
172
+ # Get live rates (skip if has_alternative_services is enabled)
173
+ if (fetch_rates or apply_shipping_rules) and not skip_rate_fetching:
160
174
  rate_response: datatypes.RateResponse = (
161
175
  RateSerializer.map(data=rating_data, context=context)
162
176
  .save(carriers=carriers)
@@ -165,6 +179,16 @@ class ShipmentSerializer(ShipmentData):
165
179
  rates = lib.to_dict(rate_response.rates)
166
180
  messages = lib.to_dict(rate_response.messages)
167
181
 
182
+ # Create synthetic rate when skipping rate fetching
183
+ if skip_rate_fetching:
184
+ _, _, rates = resolve_alternative_service_carrier(
185
+ service=service,
186
+ carrier_ids=carrier_ids,
187
+ carriers=carriers,
188
+ options=options,
189
+ context=context,
190
+ )
191
+
168
192
  shipment = models.Shipment.objects.create(
169
193
  **{
170
194
  **{
@@ -220,14 +244,14 @@ class ShipmentSerializer(ShipmentData):
220
244
  context=context,
221
245
  )
222
246
 
223
- # Buy label if preferred service is selected or shipping rules should applied.
224
- if (service and fetch_rates) or apply_shipping_rules:
247
+ # Buy label if preferred service is selected, shipping rules applied, or skip rate fetching
248
+ if (service and fetch_rates) or apply_shipping_rules or skip_rate_fetching:
225
249
  return buy_shipment_label(
226
250
  shipment,
227
251
  context=context,
228
252
  service=service,
229
253
  )
230
-
254
+ # fmt: on
231
255
  return shipment
232
256
 
233
257
  @transaction.atomic
@@ -896,3 +920,81 @@ def upload_customs_forms(shipment: models.Shipment, document: dict, context=None
896
920
  .save(shipment=shipment)
897
921
  .instance
898
922
  )
923
+
924
+
925
+ def resolve_alternative_service_carrier(
926
+ service: str,
927
+ carrier_ids: list,
928
+ carriers: list,
929
+ options: dict,
930
+ context: Context,
931
+ ) -> typing.Tuple[bool, typing.Optional[str], typing.List[dict]]:
932
+ """
933
+ Resolve carrier and create synthetic rate for has_alternative_services flow.
934
+
935
+ When has_alternative_services=True and a service is specified, this function:
936
+ 1. Determines if rate fetching should be skipped
937
+ 2. Resolves the carrier from the service name
938
+ 3. Creates a synthetic rate for direct label purchase
939
+
940
+ Returns:
941
+ Tuple of (skip_rate_fetching, resolved_carrier_name, synthetic_rates)
942
+ """
943
+ has_alternative_services = options.get("has_alternative_services", False)
944
+ skip_rate_fetching = service is not None and has_alternative_services
945
+
946
+ if not skip_rate_fetching:
947
+ return False, None, []
948
+
949
+ # Resolve carrier from service when no explicit carrier_ids provided
950
+ resolved_carrier_name = None
951
+ if not any(carrier_ids):
952
+ resolved_carrier_name = utils._get_carrier_for_service(service, context=context)
953
+ if resolved_carrier_name is None:
954
+ raise exceptions.APIException(
955
+ f"Could not resolve carrier for service '{service}'",
956
+ code="validation_error",
957
+ status_code=status.HTTP_400_BAD_REQUEST,
958
+ )
959
+
960
+ if len(carriers) == 0:
961
+ return skip_rate_fetching, resolved_carrier_name, []
962
+
963
+ # Find carrier connection matching the service's carrier
964
+ carrier_name = resolved_carrier_name or utils._get_carrier_for_service(
965
+ service, context=context
966
+ )
967
+ carrier = lib.identity(
968
+ next(
969
+ (c for c in carriers if c.carrier_name == carrier_name),
970
+ carriers[0] if carrier_name is None else None,
971
+ )
972
+ )
973
+
974
+ if carrier is None:
975
+ raise exceptions.APIException(
976
+ f"No carrier connection found for service '{service}'",
977
+ code="validation_error",
978
+ status_code=status.HTTP_400_BAD_REQUEST,
979
+ )
980
+
981
+ # Create synthetic rate for direct label purchase
982
+ synthetic_rates = [
983
+ {
984
+ "id": f"rat_{uuid.uuid4().hex}",
985
+ "carrier_id": carrier.carrier_id,
986
+ "carrier_name": carrier.carrier_name,
987
+ "service": service,
988
+ "currency": options.get("currency") or "USD",
989
+ "total_charge": 0,
990
+ "meta": {
991
+ "carrier_connection_id": carrier.pk,
992
+ "has_alternative_services": True,
993
+ "rate_provider": carrier.carrier_name,
994
+ "service_name": service.upper().replace("_", " "),
995
+ },
996
+ "test_mode": context.test_mode,
997
+ }
998
+ ]
999
+
1000
+ return skip_rate_fetching, resolved_carrier_name, synthetic_rates
@@ -212,6 +212,144 @@ class TestShipmentPurchase(TestShipmentFixture):
212
212
  ).exists()
213
213
  )
214
214
 
215
+ def test_purchase_shipment_with_has_alternative_services(self):
216
+ """
217
+ Test that when has_alternative_services is enabled and service is requested
218
+ but not in rates, the purchase proceeds by delegating service resolution to the carrier.
219
+ """
220
+ url = reverse(
221
+ "karrio.server.manager:shipment-purchase",
222
+ kwargs=dict(pk=self.shipment.pk),
223
+ )
224
+ self.shipment.options = {"has_alternative_services": True}
225
+ self.shipment.save()
226
+ data = {"service": "canadapost_expedited_parcel"}
227
+
228
+ with patch("karrio.server.core.gateway.utils.identity") as mock:
229
+ mock.return_value = CREATED_SHIPMENT_RESPONSE
230
+ response = self.client.post(url, data)
231
+ response_data = json.loads(response.content)
232
+
233
+ self.assertResponseNoErrors(response) # type: ignore
234
+ self.assertDictEqual(
235
+ dict(status=response_data["status"], service=response_data["service"]),
236
+ dict(status="purchased", service="canadapost_expedited_parcel"),
237
+ )
238
+
239
+
240
+ class TestSingleCallLabelPurchase(APITestCase):
241
+ """Test single call label purchase via POST to shipment-list with a service specified."""
242
+
243
+ def test_single_call_label_purchase(self):
244
+ """
245
+ Test that when a shipment is created with a service specified,
246
+ the label is purchased in a single call after fetching rates.
247
+ """
248
+ url = reverse("karrio.server.manager:shipment-list")
249
+ data = SINGLE_CALL_LABEL_DATA
250
+
251
+ with patch("karrio.server.core.gateway.utils.identity") as mock:
252
+ mock.side_effect = [RETURNED_RATES_VALUE, CREATED_SHIPMENT_RESPONSE]
253
+ response = self.client.post(url, data)
254
+ response_data = json.loads(response.content)
255
+
256
+ self.assertResponseNoErrors(response) # type: ignore
257
+ self.assertDictEqual(
258
+ {
259
+ "status": response_data["status"],
260
+ "carrier_name": response_data["carrier_name"],
261
+ "service": response_data["service"],
262
+ "tracking_number": response_data["tracking_number"],
263
+ "services": response_data["services"],
264
+ "rates_count": len(response_data["rates"]),
265
+ },
266
+ {
267
+ "status": "purchased",
268
+ "carrier_name": "canadapost",
269
+ "service": "canadapost_priority",
270
+ "tracking_number": "123456789012",
271
+ "services": ["canadapost_priority"],
272
+ "rates_count": 1,
273
+ },
274
+ )
275
+
276
+
277
+ class TestSingleCallWithAlternativeServices(APITestCase):
278
+ """Test single call label purchase with has_alternative_services flag (skip rate fetching)."""
279
+
280
+ def test_single_call_label_purchase_skip_rates(self):
281
+ """
282
+ Test that when has_alternative_services=True and service is specified,
283
+ rate fetching is skipped and label is purchased directly.
284
+ Carrier is resolved from the service name.
285
+ """
286
+ url = reverse("karrio.server.manager:shipment-list")
287
+ data = SINGLE_CALL_SKIP_RATES_DATA
288
+
289
+ with patch("karrio.server.core.gateway.utils.identity") as mock:
290
+ mock.return_value = CREATED_SHIPMENT_RESPONSE
291
+ response = self.client.post(url, data)
292
+ response_data = json.loads(response.content)
293
+
294
+ # Verify only 1 call was made (rates were skipped)
295
+ self.assertEqual(mock.call_count, 1)
296
+
297
+ self.assertResponseNoErrors(response) # type: ignore
298
+ self.assertDictEqual(
299
+ {
300
+ "status": response_data["status"],
301
+ "carrier_name": response_data["carrier_name"],
302
+ "service": response_data["service"],
303
+ "tracking_number": response_data["tracking_number"],
304
+ "has_alternative_services": response_data["selected_rate"]["meta"].get(
305
+ "has_alternative_services"
306
+ ),
307
+ },
308
+ {
309
+ "status": "purchased",
310
+ "carrier_name": "canadapost",
311
+ "service": "canadapost_priority",
312
+ "tracking_number": "123456789012",
313
+ "has_alternative_services": True,
314
+ },
315
+ )
316
+
317
+ def test_single_call_label_purchase_skip_rates_with_carrier_ids(self):
318
+ """
319
+ Test that when has_alternative_services=True, service, and carrier_ids are specified,
320
+ rate fetching is skipped and label is purchased directly.
321
+ """
322
+ url = reverse("karrio.server.manager:shipment-list")
323
+ data = {**SINGLE_CALL_SKIP_RATES_DATA, "carrier_ids": ["canadapost"]}
324
+
325
+ with patch("karrio.server.core.gateway.utils.identity") as mock:
326
+ mock.return_value = CREATED_SHIPMENT_RESPONSE
327
+ response = self.client.post(url, data)
328
+ response_data = json.loads(response.content)
329
+
330
+ # Verify only 1 call was made (rates were skipped)
331
+ self.assertEqual(mock.call_count, 1)
332
+
333
+ self.assertResponseNoErrors(response) # type: ignore
334
+ self.assertDictEqual(
335
+ {
336
+ "status": response_data["status"],
337
+ "carrier_name": response_data["carrier_name"],
338
+ "service": response_data["service"],
339
+ "tracking_number": response_data["tracking_number"],
340
+ "has_alternative_services": response_data["selected_rate"]["meta"].get(
341
+ "has_alternative_services"
342
+ ),
343
+ },
344
+ {
345
+ "status": "purchased",
346
+ "carrier_name": "canadapost",
347
+ "service": "canadapost_priority",
348
+ "tracking_number": "123456789012",
349
+ "has_alternative_services": True,
350
+ },
351
+ )
352
+
215
353
 
216
354
  SHIPMENT_DATA = {
217
355
  "recipient": {
@@ -260,11 +398,21 @@ SHIPMENT_RATES = {
260
398
  "total_charge": 106.71,
261
399
  "transit_days": 2,
262
400
  "extra_charges": [
263
- {"name": "Duty and taxes", "amount": 13.92, "currency": "CAD", "id": ANY},
401
+ {
402
+ "name": "Duty and taxes",
403
+ "amount": 13.92,
404
+ "currency": "CAD",
405
+ "id": ANY,
406
+ },
264
407
  {"name": "Fuel surcharge", "amount": 2.7, "currency": "CAD", "id": ANY},
265
408
  {"name": "SMB Savings", "amount": -11.74, "currency": "CAD", "id": ANY},
266
409
  {"name": "Discount", "amount": -9.04, "currency": "CAD", "id": ANY},
267
- {"name": "Base surcharge", "amount": 101.83, "currency": "CAD", "id": ANY},
410
+ {
411
+ "name": "Base surcharge",
412
+ "amount": 101.83,
413
+ "currency": "CAD",
414
+ "id": ANY,
415
+ },
268
416
  ],
269
417
  "meta": {
270
418
  "ext": "canadapost",
@@ -535,7 +683,12 @@ PURCHASED_SHIPMENT = {
535
683
  {"name": "Fuel surcharge", "amount": 2.7, "currency": "CAD", "id": ANY},
536
684
  {"name": "SMB Savings", "amount": -11.74, "currency": "CAD", "id": ANY},
537
685
  {"name": "Discount", "amount": -9.04, "currency": "CAD", "id": ANY},
538
- {"name": "Duties and taxes", "amount": 13.92, "currency": "CAD", "id": ANY},
686
+ {
687
+ "name": "Duties and taxes",
688
+ "amount": 13.92,
689
+ "currency": "CAD",
690
+ "id": ANY,
691
+ },
539
692
  ],
540
693
  "meta": {
541
694
  "service_name": "CANADAPOST PRIORITY",
@@ -679,11 +832,31 @@ CANCEL_RESPONSE = {
679
832
  "total_charge": 106.71,
680
833
  "transit_days": 2,
681
834
  "extra_charges": [
682
- {"name": "Base charge", "amount": 101.83, "currency": "CAD", "id": None},
683
- {"name": "Fuel surcharge", "amount": 2.7, "currency": "CAD", "id": None},
684
- {"name": "SMB Savings", "amount": -11.74, "currency": "CAD", "id": None},
835
+ {
836
+ "name": "Base charge",
837
+ "amount": 101.83,
838
+ "currency": "CAD",
839
+ "id": None,
840
+ },
841
+ {
842
+ "name": "Fuel surcharge",
843
+ "amount": 2.7,
844
+ "currency": "CAD",
845
+ "id": None,
846
+ },
847
+ {
848
+ "name": "SMB Savings",
849
+ "amount": -11.74,
850
+ "currency": "CAD",
851
+ "id": None,
852
+ },
685
853
  {"name": "Discount", "amount": -9.04, "currency": "CAD", "id": None},
686
- {"name": "Duties and taxes", "amount": 13.92, "currency": "CAD", "id": None},
854
+ {
855
+ "name": "Duties and taxes",
856
+ "amount": 13.92,
857
+ "currency": "CAD",
858
+ "id": None,
859
+ },
687
860
  ],
688
861
  "meta": {
689
862
  "carrier_connection_id": ANY,
@@ -797,11 +970,31 @@ CANCEL_PURCHASED_RESPONSE = {
797
970
  "total_charge": 106.71,
798
971
  "transit_days": 2,
799
972
  "extra_charges": [
800
- {"name": "Base charge", "amount": 101.83, "currency": "CAD", "id": None},
801
- {"name": "Fuel surcharge", "amount": 2.7, "currency": "CAD", "id": None},
802
- {"name": "SMB Savings", "amount": -11.74, "currency": "CAD", "id": None},
973
+ {
974
+ "name": "Base charge",
975
+ "amount": 101.83,
976
+ "currency": "CAD",
977
+ "id": None,
978
+ },
979
+ {
980
+ "name": "Fuel surcharge",
981
+ "amount": 2.7,
982
+ "currency": "CAD",
983
+ "id": None,
984
+ },
985
+ {
986
+ "name": "SMB Savings",
987
+ "amount": -11.74,
988
+ "currency": "CAD",
989
+ "id": None,
990
+ },
803
991
  {"name": "Discount", "amount": -9.04, "currency": "CAD", "id": None},
804
- {"name": "Duties and taxes", "amount": 13.92, "currency": "CAD", "id": None},
992
+ {
993
+ "name": "Duties and taxes",
994
+ "amount": 13.92,
995
+ "currency": "CAD",
996
+ "id": None,
997
+ },
805
998
  ],
806
999
  "meta": {
807
1000
  "carrier_connection_id": ANY,
@@ -832,194 +1025,74 @@ CANCEL_PURCHASED_RESPONSE = {
832
1025
  "invoice_url": None,
833
1026
  }
834
1027
 
835
-
836
- class TestShipmentPurchaseWithAlternativeServices(TestShipmentFixture):
837
- def setUp(self) -> None:
838
- super().setUp()
839
- carrier = providers.Carrier.objects.get(carrier_id="canadapost")
840
- # Rates have "canadapost_regular_parcel" but we'll request "canadapost_priority"
841
- self.shipment.rates = [
842
- {
843
- "id": "rat_alt_service_test",
844
- "carrier_id": "canadapost",
845
- "carrier_name": "canadapost",
846
- "currency": "CAD",
847
- "estimated_delivery": None,
848
- "extra_charges": [
849
- {"amount": 50.00, "currency": "CAD", "name": "Base charge"},
850
- ],
851
- "service": "canadapost_regular_parcel",
852
- "total_charge": 50.00,
853
- "transit_days": 5,
854
- "test_mode": True,
855
- "meta": {
856
- "rate_provider": "canadapost",
857
- "service_name": "CANADAPOST REGULAR PARCEL",
858
- "carrier_connection_id": carrier.pk,
859
- },
860
- }
861
- ]
862
- self.shipment.options = {"has_alternative_services": True}
863
- self.shipment.save()
864
-
865
- def test_purchase_with_alternative_service(self):
866
- """
867
- Test that when canadapost_priority is requested but only canadapost_regular_parcel
868
- is in rates, the purchase proceeds with has_alternative_services=True,
869
- delegating service resolution to the carrier.
870
- """
871
- url = reverse(
872
- "karrio.server.manager:shipment-purchase",
873
- kwargs=dict(pk=self.shipment.pk),
874
- )
875
- data = {"service": "canadapost_priority"}
876
-
877
- with patch("karrio.server.core.gateway.utils.identity") as mock:
878
- mock.return_value = CREATED_SHIPMENT_RESPONSE
879
- response = self.client.post(url, data)
880
- response_data = json.loads(response.content)
881
-
882
- self.assertEqual(response.status_code, status.HTTP_200_OK)
883
- self.assertDictEqual(response_data, ALTERNATIVE_SERVICE_PURCHASED_SHIPMENT)
884
-
885
-
886
- ALTERNATIVE_SERVICE_PURCHASED_SHIPMENT = {
887
- "id": ANY,
888
- "object_type": "shipment",
889
- "tracking_url": "/v1/trackers/canadapost/123456789012",
890
- "shipper": {
891
- "id": ANY,
892
- "postal_code": "E1C4Z8",
893
- "city": "Moncton",
894
- "federal_tax_id": None,
895
- "state_tax_id": None,
1028
+ SINGLE_CALL_LABEL_DATA = {
1029
+ "recipient": {
1030
+ "address_line1": "125 Church St",
896
1031
  "person_name": "John Poop",
897
1032
  "company_name": "A corp.",
898
- "country_code": "CA",
899
- "email": None,
900
1033
  "phone_number": "514 000 0000",
901
- "state_code": "NB",
902
- "street_number": None,
1034
+ "city": "Moncton",
1035
+ "country_code": "CA",
1036
+ "postal_code": "E1C4Z8",
903
1037
  "residential": False,
904
- "address_line1": "125 Church St",
905
- "address_line2": None,
906
- "validate_location": False,
907
- "object_type": "address",
908
- "validation": None,
1038
+ "state_code": "NB",
909
1039
  },
910
- "recipient": {
911
- "id": ANY,
912
- "postal_code": "V6M2V9",
913
- "city": "Vancouver",
914
- "federal_tax_id": None,
915
- "state_tax_id": None,
1040
+ "shipper": {
1041
+ "address_line1": "5840 Oak St",
916
1042
  "person_name": "Jane Doe",
917
1043
  "company_name": "B corp.",
918
- "country_code": "CA",
919
- "email": None,
920
1044
  "phone_number": "514 000 9999",
921
- "state_code": "BC",
922
- "street_number": None,
1045
+ "city": "Vancouver",
1046
+ "country_code": "CA",
1047
+ "postal_code": "V6M2V9",
923
1048
  "residential": False,
924
- "address_line1": "5840 Oak St",
925
- "address_line2": None,
926
- "validate_location": False,
927
- "object_type": "address",
928
- "validation": None,
1049
+ "state_code": "BC",
929
1050
  },
930
1051
  "parcels": [
931
1052
  {
932
- "id": ANY,
933
- "weight": 1.0,
934
- "width": None,
935
- "height": None,
936
- "length": None,
937
- "packaging_type": None,
938
- "package_preset": "canadapost_corrugated_small_box",
939
- "description": None,
940
- "content": None,
941
- "is_document": False,
1053
+ "weight": 1,
942
1054
  "weight_unit": "KG",
943
- "dimension_unit": None,
944
- "items": [],
945
- "freight_class": None,
946
- "reference_number": ANY,
947
- "object_type": "parcel",
948
- "options": {},
1055
+ "package_preset": "canadapost_corrugated_small_box",
949
1056
  }
950
1057
  ],
951
- "services": [],
952
- "options": {"has_alternative_services": True, "shipping_date": ANY, "shipment_date": ANY},
953
- "payment": {"paid_by": "sender", "currency": "CAD", "account_number": None},
954
- "return_address": None,
955
- "billing_address": None,
956
- "customs": None,
957
- "rates": [
1058
+ "payment": {"currency": "CAD", "paid_by": "sender"},
1059
+ "service": "canadapost_priority",
1060
+ "carrier_ids": ["canadapost"],
1061
+ "options": {"insurance": 100},
1062
+ }
1063
+
1064
+ SINGLE_CALL_SKIP_RATES_DATA = {
1065
+ # Note: No carrier_ids provided - carrier is resolved from service name
1066
+ "recipient": {
1067
+ "address_line1": "125 Church St",
1068
+ "person_name": "John Poop",
1069
+ "company_name": "A corp.",
1070
+ "phone_number": "514 000 0000",
1071
+ "city": "Moncton",
1072
+ "country_code": "CA",
1073
+ "postal_code": "E1C4Z8",
1074
+ "residential": False,
1075
+ "state_code": "NB",
1076
+ },
1077
+ "shipper": {
1078
+ "address_line1": "5840 Oak St",
1079
+ "person_name": "Jane Doe",
1080
+ "company_name": "B corp.",
1081
+ "phone_number": "514 000 9999",
1082
+ "city": "Vancouver",
1083
+ "country_code": "CA",
1084
+ "postal_code": "V6M2V9",
1085
+ "residential": False,
1086
+ "state_code": "BC",
1087
+ },
1088
+ "parcels": [
958
1089
  {
959
- "id": ANY,
960
- "object_type": "rate",
961
- "carrier_name": "canadapost",
962
- "carrier_id": "canadapost",
963
- "currency": "CAD",
964
- "estimated_delivery": ANY,
965
- "service": "canadapost_regular_parcel",
966
- "total_charge": 50.00,
967
- "transit_days": 5,
968
- "extra_charges": [
969
- {"name": "Base charge", "amount": 50.00, "currency": "CAD", "id": None},
970
- ],
971
- "meta": {
972
- "service_name": "CANADAPOST REGULAR PARCEL",
973
- "rate_provider": "canadapost",
974
- "carrier_connection_id": ANY,
975
- },
976
- "test_mode": True,
1090
+ "weight": 1,
1091
+ "weight_unit": "KG",
1092
+ "package_preset": "canadapost_corrugated_small_box",
977
1093
  }
978
1094
  ],
979
- "reference": None,
980
- "label_type": "PDF",
981
- "carrier_ids": [],
982
- "tracker_id": ANY,
983
- "created_at": ANY,
984
- "metadata": {},
985
- "messages": [],
986
- "status": "purchased",
987
- "carrier_name": "canadapost",
988
- "carrier_id": "canadapost",
989
- "tracking_number": "123456789012",
990
- "shipment_identifier": "123456789012",
991
- "selected_rate": {
992
- "id": ANY,
993
- "object_type": "rate",
994
- "carrier_name": "canadapost",
995
- "carrier_id": "canadapost",
996
- "currency": "CAD",
997
- "estimated_delivery": ANY,
998
- "service": "canadapost_priority",
999
- "total_charge": 50.00,
1000
- "transit_days": 5,
1001
- "extra_charges": [
1002
- {"name": "Base charge", "amount": 50.00, "currency": "CAD", "id": None},
1003
- ],
1004
- "meta": {
1005
- "ext": "canadapost",
1006
- "carrier": "canadapost",
1007
- "service_name": "CANADAPOST REGULAR PARCEL",
1008
- "rate_provider": "canadapost",
1009
- "carrier_connection_id": ANY,
1010
- "has_alternative_services": True,
1011
- },
1012
- "test_mode": True,
1013
- },
1014
- "meta": {
1015
- "ext": "canadapost",
1016
- "carrier": "canadapost",
1017
- "rate_provider": "canadapost",
1018
- "service_name": "CANADAPOST PRIORITY",
1019
- },
1095
+ "payment": {"currency": "CAD", "paid_by": "sender"},
1020
1096
  "service": "canadapost_priority",
1021
- "selected_rate_id": ANY,
1022
- "test_mode": True,
1023
- "label_url": ANY,
1024
- "invoice_url": None,
1097
+ "options": {"has_alternative_services": True},
1025
1098
  }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: karrio_server_manager
3
- Version: 2025.5.1
3
+ Version: 2025.5.2
4
4
  Summary: Multi-carrier shipping API Shipments manager module
5
5
  Author-email: karrio <hello@karrio.io>
6
6
  License-Expression: LGPL-3.0
@@ -82,7 +82,7 @@ karrio/server/manager/serializers/manifest.py,sha256=mSneCk_7HMXpi64_7hggWvkR7Ma
82
82
  karrio/server/manager/serializers/parcel.py,sha256=733Bg26lVbEkoWtAVM5Qt2IRBS2QDuVxhG40Hiqh3bw,2621
83
83
  karrio/server/manager/serializers/pickup.py,sha256=sX0VmcQxGkXn3IEosMuFwdXh4HhdkPcuBOp79O8PoDQ,9233
84
84
  karrio/server/manager/serializers/rate.py,sha256=7vYK_v8iWEDnswqYHG2Lir16_UhHTOxW5rdC6lw3lzA,652
85
- karrio/server/manager/serializers/shipment.py,sha256=gkDFTDaKXA8Iz3yjS6fxRi46AzZPw40RhJEOlnBZy6o,31143
85
+ karrio/server/manager/serializers/shipment.py,sha256=N4mld4eIM1HQ6NdsQ7gDt73aDv4j0wHM4AAMfgChnMc,34919
86
86
  karrio/server/manager/serializers/tracking.py,sha256=ixrAjIiZQsvSt4y0qtisGkt6TFOJ3ORNkJAQVt6YQrA,12483
87
87
  karrio/server/manager/tests/__init__.py,sha256=Y1UNteEE60vWdUAkjbldu_r_-h4u0He8-UoiBgTjKcU,391
88
88
  karrio/server/manager/tests/test_addresses.py,sha256=pNkZC_yJyb29ZlEOtOAs4blcEYiOarw0zhZIZC5uj1w,3111
@@ -90,7 +90,7 @@ karrio/server/manager/tests/test_custom_infos.py,sha256=iv2cLdZVoVWFZK_mDUEnrZss
90
90
  karrio/server/manager/tests/test_errors.py,sha256=QYsGLUtwMvrHeX1XSCpdteTKbug7-y1-Xgvbl96aN9g,3220
91
91
  karrio/server/manager/tests/test_parcels.py,sha256=lVLBOsHzXgXQvYjHIUy5oiPvrMfxYpueVvvhtuhstWk,2559
92
92
  karrio/server/manager/tests/test_pickups.py,sha256=8jxddwTnBvBM9FOyWxW9TtZ-GOVYUje7HQ2EZjsbtD8,10681
93
- karrio/server/manager/tests/test_shipments.py,sha256=N1eVH-n0BTANmzehKL0cp917goskU5X8l_ftXzcVSmw,33349
93
+ karrio/server/manager/tests/test_shipments.py,sha256=LBblskbeJyUvWtJdy5hZPvumuOT2PE__ikIK3YlvcnY,35914
94
94
  karrio/server/manager/tests/test_trackers.py,sha256=KvmWkplokNDZ0dzB16mFl0WcMJ0OYp_ErZeWJPGW_NA,7151
95
95
  karrio/server/manager/views/__init__.py,sha256=kDFUaORRQ3Xh0ZPm-Jk88Ss8dgGYM57iUFXb9TPMzh0,401
96
96
  karrio/server/manager/views/addresses.py,sha256=7YCAs2ZYgd1icYwMcGGWfX7A7vZEL4BEAbU4eIxhiMY,4620
@@ -101,7 +101,7 @@ karrio/server/manager/views/parcels.py,sha256=hZY45rg6SrTWfQqyJ38MGKSor1yqgPUEVH
101
101
  karrio/server/manager/views/pickups.py,sha256=gmpxz9ot1OR-BP1qh-0MXU3kUJi1ht_74hfaLJzJ42w,5503
102
102
  karrio/server/manager/views/shipments.py,sha256=TqLpBH5Jf-rI3enJwvNptRwGzfo7co9R1VSP_oqhB3o,10419
103
103
  karrio/server/manager/views/trackers.py,sha256=3oGn2qDpHgk8GZvuz-Cb93Fc0j_h_HbXQR692Zhfiok,12363
104
- karrio_server_manager-2025.5.1.dist-info/METADATA,sha256=7G3zIzLe_6ux6VkjCj1ub1xQc4b9Cz-uiyzwvBpSDDs,730
105
- karrio_server_manager-2025.5.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
106
- karrio_server_manager-2025.5.1.dist-info/top_level.txt,sha256=D1D7x8R3cTfjF_15mfiO7wCQ5QMtuM4x8GaPr7z5i78,12
107
- karrio_server_manager-2025.5.1.dist-info/RECORD,,
104
+ karrio_server_manager-2025.5.2.dist-info/METADATA,sha256=WZxA5Br3FwwK7PO0Au-FKvQPHFPUcXevyuoaK596rWQ,730
105
+ karrio_server_manager-2025.5.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
106
+ karrio_server_manager-2025.5.2.dist-info/top_level.txt,sha256=D1D7x8R3cTfjF_15mfiO7wCQ5QMtuM4x8GaPr7z5i78,12
107
+ karrio_server_manager-2025.5.2.dist-info/RECORD,,