diff --git a/modules/connectors/seko/karrio/mappers/seko/__init__.py b/modules/connectors/seko/karrio/mappers/seko/__init__.py index 7323f5e1a..1e24d3d8c 100644 --- a/modules/connectors/seko/karrio/mappers/seko/__init__.py +++ b/modules/connectors/seko/karrio/mappers/seko/__init__.py @@ -16,7 +16,7 @@ Settings=Settings, # Data Units is_hub=False, - # options=units.ShippingOption, - # services=units.ShippingService, - # connection_configs=utils.ConnectionConfig, + options=units.ShippingOption, + services=units.ShippingService, + connection_configs=utils.ConnectionConfig, ) diff --git a/modules/connectors/seko/karrio/mappers/seko/proxy.py b/modules/connectors/seko/karrio/mappers/seko/proxy.py index 30dd5e52d..162d79d76 100644 --- a/modules/connectors/seko/karrio/mappers/seko/proxy.py +++ b/modules/connectors/seko/karrio/mappers/seko/proxy.py @@ -2,6 +2,7 @@ import karrio.lib as lib import karrio.api.proxy as proxy +import karrio.providers.seko.utils as provider_utils import karrio.mappers.seko.settings as provider_settings @@ -16,8 +17,9 @@ def get_rates(self, request: lib.Serializable) -> lib.Deserializable[str]: method="POST", headers={ "Content-Type": "application/json; charset=utf-8", - "Authorization": f"{self.settings.access_key}", + "access_key": f"{self.settings.access_key}", }, + on_error=provider_utils.parse_error_response, ) return lib.Deserializable(response, lib.to_dict) @@ -30,8 +32,9 @@ def create_shipment(self, request: lib.Serializable) -> lib.Deserializable[str]: method="POST", headers={ "Content-Type": "application/json; charset=utf-8", - "Authorization": f"{self.settings.access_key}", + "access_key": f"{self.settings.access_key}", }, + on_error=provider_utils.parse_error_response, ) return lib.Deserializable(response, lib.to_dict, request.ctx) @@ -44,8 +47,9 @@ def cancel_shipment(self, request: lib.Serializable) -> lib.Deserializable[str]: method="POST", headers={ "Content-Type": "application/json; charset=utf-8", - "Authorization": f"{self.settings.access_key}", + "access_key": f"{self.settings.access_key}", }, + on_error=provider_utils.parse_error_response, ) return lib.Deserializable( @@ -61,8 +65,9 @@ def get_tracking(self, request: lib.Serializable) -> lib.Deserializable[str]: method="POST", headers={ "Content-Type": "application/json; charset=utf-8", - "Authorization": f"{self.settings.access_key}", + "access_key": f"{self.settings.access_key}", }, + on_error=provider_utils.parse_error_response, ) return lib.Deserializable(response, lib.to_dict) @@ -75,8 +80,9 @@ def create_manifest(self, request: lib.Serializable) -> lib.Deserializable[str]: method="POST", headers={ "Content-Type": "application/json; charset=utf-8", - "Authorization": f"{self.settings.access_key}", + "access_key": f"{self.settings.access_key}", }, + on_error=provider_utils.parse_error_response, ) return lib.Deserializable(response, lib.to_dict) diff --git a/modules/connectors/seko/karrio/providers/seko/error.py b/modules/connectors/seko/karrio/providers/seko/error.py index a9f8028c8..f0f0ba006 100644 --- a/modules/connectors/seko/karrio/providers/seko/error.py +++ b/modules/connectors/seko/karrio/providers/seko/error.py @@ -59,7 +59,14 @@ def parse_error_response( carrier_name=settings.carrier_name, code=error["code"], message=error["message"], - details={**kwargs, **error}, + details={ + **kwargs, + **{ + k: v + for k, v in error.items() + if k not in ["code", "message", "Code", "Message"] + }, + }, ) for error in errors ] diff --git a/modules/connectors/seko/karrio/providers/seko/rate.py b/modules/connectors/seko/karrio/providers/seko/rate.py index 9394c13a4..aadfe0cf2 100644 --- a/modules/connectors/seko/karrio/providers/seko/rate.py +++ b/modules/connectors/seko/karrio/providers/seko/rate.py @@ -29,7 +29,7 @@ def _extract_details( settings: provider_utils.Settings, ) -> models.RateDetails: details = lib.to_object(rating.AvailableType, data) - service = provider_units.ShippingService.map(details.CarrierServiceType) + service = provider_units.ShippingService.map(details.DeliveryType) return models.RateDetails( carrier_id=settings.carrier_id, @@ -75,8 +75,8 @@ def rate_request( Address=seko.AddressType( BuildingName="", StreetAddress=recipient.street, - Suburb=None, - City=recipient.city, + Suburb=recipient.city, + City=recipient.state_code, PostCode=recipient.postal_code, CountryCode=recipient.country_code, ), diff --git a/modules/connectors/seko/karrio/providers/seko/shipment/cancel.py b/modules/connectors/seko/karrio/providers/seko/shipment/cancel.py index bd687d41e..2daa5739a 100644 --- a/modules/connectors/seko/karrio/providers/seko/shipment/cancel.py +++ b/modules/connectors/seko/karrio/providers/seko/shipment/cancel.py @@ -11,8 +11,17 @@ def parse_shipment_cancel_response( settings: provider_utils.Settings, ) -> typing.Tuple[models.ConfirmationDetails, typing.List[models.Message]]: response = _response.deserialize() - messages = error.parse_error_response(response, settings) - success = True # compute shipment cancel success state + messages = error.parse_error_response( + dict( + Errors=[ + {"ConsignmentId": _, "Message": __} + for _, __ in response.items() + if isinstance(__, str) and "Deleted" not in __ + ] + ), + settings, + ) + success = any(["Deleted" in _ for _ in response.values() if isinstance(_, str)]) confirmation = ( models.ConfirmationDetails( diff --git a/modules/connectors/seko/karrio/providers/seko/shipment/create.py b/modules/connectors/seko/karrio/providers/seko/shipment/create.py index 82da7b592..01a222024 100644 --- a/modules/connectors/seko/karrio/providers/seko/shipment/create.py +++ b/modules/connectors/seko/karrio/providers/seko/shipment/create.py @@ -103,14 +103,14 @@ def shipment_request( Address=seko.AddressType( BuildingName=None, StreetAddress=shipper.street, - Suburb=None, - City=shipper.city, + Suburb=shipper.city, + City=shipper.state_code, PostCode=shipper.postal_code, CountryCode=shipper.country_code, ), ContactPerson=shipper.contact, - PhoneNumber=shipper.phone_number, - Email=shipper.email, + PhoneNumber=shipper.phone_number or "000 000 0000", + Email=shipper.email or " ", DeliveryInstructions=options.origin_instructions.state, RecipientTaxId=shipper.tax_id, SendTrackingEmail=None, @@ -121,14 +121,14 @@ def shipment_request( Address=seko.AddressType( BuildingName=None, StreetAddress=recipient.street, - Suburb=None, - City=recipient.city, + Suburb=recipient.city, + City=recipient.state_code, PostCode=recipient.postal_code, CountryCode=recipient.country_code, ), ContactPerson=recipient.contact, - PhoneNumber=recipient.phone_number, - Email=recipient.email, + PhoneNumber=recipient.phone_number or "000 000 0000", + Email=recipient.email or " ", DeliveryInstructions=options.destination_instructions.state, RecipientTaxId=recipient.tax_id, SendTrackingEmail=options.seko_send_tracking_email.state, @@ -137,6 +137,7 @@ def shipment_request( Commodities=[ seko.CommodityType( Description=lib.text(commodity.description, max=35), + HarmonizedCode=commodity.hs_code, Units=commodity.quantity, UnitValue=commodity.value_amount, UnitKg=commodity.weight, diff --git a/modules/connectors/seko/karrio/providers/seko/utils.py b/modules/connectors/seko/karrio/providers/seko/utils.py index e2c4f56d0..642ba083c 100644 --- a/modules/connectors/seko/karrio/providers/seko/utils.py +++ b/modules/connectors/seko/karrio/providers/seko/utils.py @@ -96,3 +96,15 @@ class ConnectionConfig(lib.Enum): cost_center = lib.OptionEnum("CostCentreName", str) shipping_options = lib.OptionEnum("shipping_options", list) shipping_services = lib.OptionEnum("shipping_services", list) + + +def parse_error_response(response): + """Parse the error response from the SAPIENT API.""" + content = lib.failsafe(lambda: lib.decode(response.read())) + + if any(content or ""): + return content + + return lib.to_json( + dict(Errors=[dict(code=str(response.code), Message=response.reason)]) + ) diff --git a/modules/connectors/seko/karrio/schemas/seko/shipping_request.py b/modules/connectors/seko/karrio/schemas/seko/shipping_request.py index a86a88784..b928ef396 100644 --- a/modules/connectors/seko/karrio/schemas/seko/shipping_request.py +++ b/modules/connectors/seko/karrio/schemas/seko/shipping_request.py @@ -19,6 +19,7 @@ class ItemType: @s(auto_attribs=True) class CommodityType: Description: Optional[str] = None + HarmonizedCode: Optional[str] = None Units: Optional[int] = None UnitValue: Optional[int] = None UnitKg: Optional[float] = None diff --git a/modules/connectors/seko/karrio/schemas/seko/shipping_response.py b/modules/connectors/seko/karrio/schemas/seko/shipping_response.py index e2c146249..c841a074c 100644 --- a/modules/connectors/seko/karrio/schemas/seko/shipping_response.py +++ b/modules/connectors/seko/karrio/schemas/seko/shipping_response.py @@ -28,6 +28,11 @@ class ItemType: TrackingBarcode2: Optional[str] = None +@s(auto_attribs=True) +class OutputFilesType: + LABELPDF100X150: List[str] = [] + + @s(auto_attribs=True) class ConsignmentType: Connote: Optional[str] = None @@ -39,7 +44,7 @@ class ConsignmentType: IsOvernight: Optional[bool] = None HasTrackPaks: Optional[bool] = None ConsignmentId: Optional[int] = None - OutputFiles: Optional[dict] = {} + OutputFiles: Optional[OutputFilesType] = JStruct[OutputFilesType] Items: List[ItemType] = JList[ItemType] diff --git a/modules/connectors/seko/schemas/shipping_request.json b/modules/connectors/seko/schemas/shipping_request.json index da315716c..7c9163e0d 100644 --- a/modules/connectors/seko/schemas/shipping_request.json +++ b/modules/connectors/seko/schemas/shipping_request.json @@ -64,6 +64,7 @@ "Commodities": [ { "Description": "Food Bar", + "HarmonizedCode": "sample", "Units": "1", "UnitValue": 50, "UnitKg": 0.6, diff --git a/modules/connectors/seko/tests/seko/test_rate.py b/modules/connectors/seko/tests/seko/test_rate.py index 3cbb95d2a..0cf3aa114 100644 --- a/modules/connectors/seko/tests/seko/test_rate.py +++ b/modules/connectors/seko/tests/seko/test_rate.py @@ -99,9 +99,9 @@ def test_parse_rate_response(self): "QuoteId": "e7fdf36c-8f6a-4d3f-8d96-c8b5893a0e7f", "Route": "OFFSHORE->AKL- SI", "seko_carrier": "Omni Parcel", - "service_name": "InternationalCourier", + "service_name": "AIR TRACKED", }, - "service": "InternationalCourier", + "service": "AIR TRACKED", "total_charge": 5.82, } ], @@ -113,10 +113,7 @@ def test_parse_rate_response(self): "message": "CountryCode is required", "details": { "Key": "CountryCode", - "Message": "CountryCode is required", "Property": "Destination.Address.CountryCode", - "code": "ValidationError", - "message": "CountryCode is required", }, } ], @@ -127,7 +124,7 @@ def test_parse_rate_response(self): "DeliveryReference": "ORDER123", "Destination": { "Address": { - "City": "Christchurch", + "Suburb": "Christchurch", "CountryCode": "NZ", "PostCode": "8061", "StreetAddress": "DestinationStreetAddress", diff --git a/modules/connectors/seko/tests/seko/test_shipment.py b/modules/connectors/seko/tests/seko/test_shipment.py index ff0878f2d..3516b50ed 100644 --- a/modules/connectors/seko/tests/seko/test_shipment.py +++ b/modules/connectors/seko/tests/seko/test_shipment.py @@ -201,10 +201,7 @@ def test_parse_cancel_shipment_response(self): "code": "Error", "details": { "Key": "CountryCode", - "Message": "CountryCode is required", "Property": "Destination.Address.CountryCode", - "code": "Error", - "message": "CountryCode is required", }, "message": "CountryCode is required", } @@ -218,7 +215,26 @@ def test_parse_cancel_shipment_response(self): "operation": "Cancel Shipment", "success": True, }, - [], + [ + { + "carrier_id": "seko", + "carrier_name": "seko", + "code": "Error", + "details": { + "ConsignmentId": "SSPOT014114", + }, + "message": "Cannot be deleted. Already deleted.", + }, + { + "carrier_id": "seko", + "carrier_name": "seko", + "code": "Error", + "details": { + "ConsignmentId": "SSPOT014115", + }, + "message": "Cannot be deleted. Already in transit.", + }, + ], ] @@ -227,10 +243,11 @@ def test_parse_cancel_shipment_response(self): "Origin": { "Name": "OriginName", "Address": { + "City": "NSW", "StreetAddress": "285 Main Street", - "City": "GLENWOOD", "PostCode": "2768", "CountryCode": "AU", + "Suburb": "GLENWOOD", }, "ContactPerson": "Origin contact name", "PhoneNumber": "02 9111 01101", @@ -242,9 +259,11 @@ def test_parse_cancel_shipment_response(self): "Name": "Destination Name", "Address": { "StreetAddress": "285 Coward Street", - "City": "TESBURY", + "City": "VIC", + "Suburb": "TESBURY", "PostCode": "3260", "CountryCode": "AU", + "Suburb": "TESBURY", }, "ContactPerson": "JOHN SMITH", "PhoneNumber": "02 9111 1111", diff --git a/modules/connectors/seko/tests/seko/test_tracking.py b/modules/connectors/seko/tests/seko/test_tracking.py index d27d98b2f..decdd4302 100644 --- a/modules/connectors/seko/tests/seko/test_tracking.py +++ b/modules/connectors/seko/tests/seko/test_tracking.py @@ -100,10 +100,7 @@ def test_parse_error_response(self): "code": "Error", "details": { "Key": "CountryCode", - "Message": "CountryCode is required", "Property": "Destination.Address.CountryCode", - "code": "Error", - "message": "CountryCode is required", }, "message": "CountryCode is required", }