From aa3c8d9ccfdbed2b5dc11c340b8e85740f999e43 Mon Sep 17 00:00:00 2001 From: Daniel K Date: Tue, 3 Sep 2024 22:47:35 -0700 Subject: [PATCH] feat: initial SEKO integration with unit tests --- .../seko/karrio/mappers/seko/proxy.py | 7 +- .../seko/karrio/providers/seko/error.py | 47 +++- .../seko/karrio/providers/seko/manifest.py | 25 +- .../seko/karrio/providers/seko/rate.py | 81 ++++--- .../karrio/providers/seko/shipment/cancel.py | 6 +- .../karrio/providers/seko/shipment/create.py | 219 ++++++++++-------- .../seko/karrio/providers/seko/tracking.py | 27 ++- .../seko/karrio/providers/seko/units.py | 96 ++++++-- .../seko/karrio/providers/seko/utils.py | 1 + .../karrio/schemas/seko/shipping_response.py | 7 +- modules/connectors/seko/tests/seko/fixture.py | 4 +- .../seko/tests/seko/test_manifest.py | 16 +- .../connectors/seko/tests/seko/test_rate.py | 95 +++++--- .../seko/tests/seko/test_shipment.py | 219 ++++++++++-------- .../seko/tests/seko/test_tracking.py | 71 +++++- modules/sdk/karrio/core/units.py | 5 + 16 files changed, 605 insertions(+), 321 deletions(-) diff --git a/modules/connectors/seko/karrio/mappers/seko/proxy.py b/modules/connectors/seko/karrio/mappers/seko/proxy.py index 3e7a73c66..30dd5e52d 100644 --- a/modules/connectors/seko/karrio/mappers/seko/proxy.py +++ b/modules/connectors/seko/karrio/mappers/seko/proxy.py @@ -34,7 +34,7 @@ def create_shipment(self, request: lib.Serializable) -> lib.Deserializable[str]: }, ) - return lib.Deserializable(response, lib.to_dict) + return lib.Deserializable(response, lib.to_dict, request.ctx) def cancel_shipment(self, request: lib.Serializable) -> lib.Deserializable[str]: response = lib.request( @@ -48,7 +48,10 @@ def cancel_shipment(self, request: lib.Serializable) -> lib.Deserializable[str]: }, ) - return lib.Deserializable(response, lib.to_dict) + return lib.Deserializable( + response, + lib.to_dict, + ) def get_tracking(self, request: lib.Serializable) -> lib.Deserializable[str]: response = lib.request( diff --git a/modules/connectors/seko/karrio/providers/seko/error.py b/modules/connectors/seko/karrio/providers/seko/error.py index 45760ccfa..a9f8028c8 100644 --- a/modules/connectors/seko/karrio/providers/seko/error.py +++ b/modules/connectors/seko/karrio/providers/seko/error.py @@ -12,15 +12,54 @@ def parse_error_response( **kwargs, ) -> typing.List[models.Message]: responses = response if isinstance(response, list) else [response] - errors: list = [] # compute the carrier error object list + errors: list = sum( + [ + [ + *lib.identity( + [ + {"code": "Error", "message": e.get("Message"), **e} + for e in _.get("Errors", []) + ] + if any(_.get("Errors", [])) + else [] + ), + *lib.identity( + [ + {"code": "Error", "message": e.get("Message"), **e} + for e in _.get("Error", []) + ] + if any(_.get("Error", [])) + else [] + ), + *lib.identity( + [ + {"code": "Rejected", "message": e.get("Reason"), **e} + for e in _.get("Error", []) + ] + if any(_.get("Rejected", [])) + else [] + ), + *lib.identity( + [ + {"code": "ValidationError", "message": e.get("Message"), **e} + for e in [_.get("ValidationErrors")] + ] + if _.get("ValidationErrors") + else [] + ), + ] + for _ in responses + ], + [], + ) return [ models.Message( carrier_id=settings.carrier_id, carrier_name=settings.carrier_name, - code="", - message="", - details={**kwargs}, + code=error["code"], + message=error["message"], + details={**kwargs, **error}, ) for error in errors ] diff --git a/modules/connectors/seko/karrio/providers/seko/manifest.py b/modules/connectors/seko/karrio/providers/seko/manifest.py index 00945cb08..bcd2a5e70 100644 --- a/modules/connectors/seko/karrio/providers/seko/manifest.py +++ b/modules/connectors/seko/karrio/providers/seko/manifest.py @@ -1,6 +1,5 @@ """Karrio SEKO Logistics manifest API implementation.""" -import karrio.schemas.seko.manifest_request as seko import karrio.schemas.seko.manifest_response as manifest import typing @@ -18,7 +17,11 @@ def parse_manifest_response( response = _response.deserialize() messages = error.parse_error_response(response, settings) - details = _extract_details(response, settings) + details = lib.identity( + _extract_details(response, settings) + if any(response.get("OutboundManifest") or []) + else None + ) return details, messages @@ -27,14 +30,24 @@ def _extract_details( data: dict, settings: provider_utils.Settings, ) -> models.ManifestDetails: - details = None # manifest details parsing - manifest = None # extract carrier manifest file + details = lib.to_object(manifest.ManifestResponseType, data) + manifest_numbers = [_.ManifestNumber for _ in details.OutboundManifest] + manifest_connotes = sum( + [_.ManifestedConnotes for _ in details.OutboundManifest], [] + ) + manifest_doc = lib.bundle_base64( + [_.ManifestContent for _ in details.OutboundManifest], "PDF" + ) return models.ManifestDetails( carrier_id=settings.carrier_id, carrier_name=settings.carrier_id, - doc=models.ManifestDocument(manifest=manifest), - meta=dict(), + doc=models.ManifestDocument(manifest=manifest_doc), + meta=dict( + ManifestNumber=manifest_numbers[0], + ManifestNumbers=manifest_numbers, + ManifestConnotes=manifest_connotes, + ), ) diff --git a/modules/connectors/seko/karrio/providers/seko/rate.py b/modules/connectors/seko/karrio/providers/seko/rate.py index f98742aab..9394c13a4 100644 --- a/modules/connectors/seko/karrio/providers/seko/rate.py +++ b/modules/connectors/seko/karrio/providers/seko/rate.py @@ -19,7 +19,7 @@ def parse_rate_response( response = _response.deserialize() messages = error.parse_error_response(response, settings) - rates = [_extract_details(rate, settings) for rate in response] + rates = [_extract_details(rate, settings) for rate in response.get("Available", [])] return rates, messages @@ -28,17 +28,25 @@ def _extract_details( data: dict, settings: provider_utils.Settings, ) -> models.RateDetails: - details = None # parse carrier rate type + details = lib.to_object(rating.AvailableType, data) + service = provider_units.ShippingService.map(details.CarrierServiceType) return models.RateDetails( carrier_id=settings.carrier_id, carrier_name=settings.carrier_name, - service="", # extract service from rate - total_charge=lib.to_money(0.0), # extract the rate total rate cost - currency="", # extract the rate pricing currency - transit_days=0, # extract the rate transit days + service=service.name_or_key, + total_charge=lib.to_money(details.Cost), + currency="USD", meta=dict( - service_name="", # extract the rate service human readable name + service_name=service.value_or_key, + seko_carrier=details.CarrierName, + Route=details.Route, + QuoteId=details.QuoteId, + DeliveryType=details.DeliveryType, + CarrierServiceType=details.CarrierServiceType, + IsFreightForward=details.IsFreightForward, + IsRuralDelivery=details.IsRuralDelivery, + IsSaturdayDelivery=details.IsSaturdayDelivery, ), ) @@ -47,49 +55,52 @@ def rate_request( payload: models.RateRequest, settings: provider_utils.Settings, ) -> lib.Serializable: - shipper = lib.to_address(payload.shipper) recipient = lib.to_address(payload.recipient) - packages = lib.to_packages(payload.parcels) - services = lib.to_services(payload.services, provider_units.ShippingService) options = lib.to_shipping_options( payload.options, - package_options=packages.options, initializer=provider_units.shipping_options_initializer, ) + packages = lib.to_packages( + payload.parcels, + options=options, + shipping_options_initializer=provider_units.shipping_options_initializer, + ) # map data to convert karrio model to seko specific type - request = seko.RateRequestType( - DeliveryReference=None, + request = seko.RatingRequestType( + DeliveryReference=payload.reference, Destination=seko.DestinationType( - Id=None, - Name=None, + Id=options.seko_destination_id.state, + Name=recipient.company_name, Address=seko.AddressType( - BuildingName=None, - StreetAddress=None, + BuildingName="", + StreetAddress=recipient.street, Suburb=None, - City=None, - PostCode=None, - CountryCode=None, + City=recipient.city, + PostCode=recipient.postal_code, + CountryCode=recipient.country_code, ), - ContactPerson=None, - PhoneNumber=None, - Email=None, - DeliveryInstructions=None, - RecipientTaxId=None, + ContactPerson=recipient.contact, + PhoneNumber=recipient.phone_number, + Email=recipient.email, + DeliveryInstructions=options.destination_instructions.state, + RecipientTaxId=recipient.tax_id, ), - IsSaturdayDelivery=None, - IsSignatureRequired=None, + IsSaturdayDelivery=options.seko_is_saturday_delivery.state, + IsSignatureRequired=options.seko_is_signature_required.state, Packages=[ seko.PackageType( - Height=None, - Length=None, - Id=None, - Width=None, - Kg=None, - Name=None, - PackageCode=None, - Type=None, + Height=package.height.CM, + Length=package.length.CM, + Id=package.options.seko_package_id.state, + Width=package.width.CM, + Kg=package.weight.KG, + Name=lib.text(package.description, max=50), + Type=provider_units.PackagingType.map( + package.packaging_type or "your_packaging" + ).value, ) + for package in packages ], ) diff --git a/modules/connectors/seko/karrio/providers/seko/shipment/cancel.py b/modules/connectors/seko/karrio/providers/seko/shipment/cancel.py index 529e5525a..bd687d41e 100644 --- a/modules/connectors/seko/karrio/providers/seko/shipment/cancel.py +++ b/modules/connectors/seko/karrio/providers/seko/shipment/cancel.py @@ -38,13 +38,15 @@ def shipment_cancel_request( "PickupOptions", # fmt: off { - "shipment_ids": lib.OptionEnum("shipment_ids", lib.to_list), + "shipment_identifiers": lib.OptionEnum("shipment_identifiers", lib.to_list), }, # fmt: on ), ) # map data to convert karrio model to seko specific type - request = lib.identity(options.shipment_ids.state or [payload.shipment_identifier]) + request = lib.identity( + options.shipment_identifiers.state or [payload.shipment_identifier] + ) return lib.Serializable(request, lib.to_dict) diff --git a/modules/connectors/seko/karrio/providers/seko/shipment/create.py b/modules/connectors/seko/karrio/providers/seko/shipment/create.py index df008b17f..82da7b592 100644 --- a/modules/connectors/seko/karrio/providers/seko/shipment/create.py +++ b/modules/connectors/seko/karrio/providers/seko/shipment/create.py @@ -17,10 +17,12 @@ def parse_shipment_response( settings: provider_utils.Settings, ) -> typing.Tuple[typing.List[models.RateDetails], typing.List[models.Message]]: response = _response.deserialize() - + print(_response.ctx) messages = error.parse_error_response(response, settings) - shipment = ( - _extract_details(response, settings) if "tracking_number" in response else None + shipment = lib.identity( + _extract_details(response, settings, ctx=_response.ctx) + if any(response.get("Consignments", [])) + else None ) return shipment, messages @@ -29,23 +31,35 @@ def parse_shipment_response( def _extract_details( data: dict, settings: provider_utils.Settings, + ctx: dict = None, ) -> models.ShipmentDetails: - details = None # parse carrier shipment type from "data" - label = "" # extract and process the shipment label to a valid base64 text - # invoice = "" # extract and process the shipment invoice to a valid base64 text if applies + details = lib.to_object(shipping.ShippingResponseType, data) + Connotes = [_.Connote for _ in details.Consignments] + TrackingUrls = [_.TrackingUrl for _ in details.Consignments] + ConsignmentIds = [_.ConsignmentId for _ in details.Consignments] + label_type = ctx.get("label_type") + label_format = ctx.get("label_format") + + label = lib.bundle_base64( + sum([_["OutputFiles"][label_type] for _ in data["Consignments"]], []), + label_format, + ) return models.ShipmentDetails( carrier_id=settings.carrier_id, carrier_name=settings.carrier_name, - tracking_number="", # extract tracking number from shipment - shipment_identifier="", # extract shipment identifier from shipment - label_type="PDF", # extract shipment label file format - docs=models.Documents( - label=label, # pass label base64 text - # invoice=invoice, # pass invoice base64 text if applies - ), + tracking_number=Connotes[0], + shipment_identifier=ConsignmentIds[0], + label_type=label_format, + docs=models.Documents(label=label), meta=dict( - # any relevent meta + SiteId=details.SiteId, + carrier_tracking_link=TrackingUrls[0], + TrackingUrls=TrackingUrls, + ConsignmentId=ConsignmentIds[0], + ConsignmentIds=ConsignmentIds, + CarrierId=details.CarrierId, + CarrierName=details.CarrierName, ), ) @@ -57,129 +71,130 @@ def shipment_request( shipper = lib.to_address(payload.shipper) recipient = lib.to_address(payload.recipient) packages = lib.to_packages(payload.parcels) - service = provider_units.ShippingService.map(payload.service).value_or_key + service = provider_units.ShippingService.map(payload.service) options = lib.to_shipping_options( payload.options, package_options=packages.options, initializer=provider_units.shipping_options_initializer, ) + customs = lib.to_customs_info( + payload.customs, + shipper=payload.shipper, + recipient=payload.recipient, + weight_unit=units.WeightUnit.KG.name, + option_type=provider_units.CustomsOption, + ) + commodities: units.Products = lib.identity( + customs.commodities if payload.customs else packages.items + ) + [label_format, label_type] = lib.identity( + provider_units.LabelType.map(payload.label_type).value + or provider_units.LabelType.PDF.value + ) # map data to convert karrio model to seko specific type request = seko.ShippingRequestType( - DeliveryReference=None, + DeliveryReference=payload.reference, Reference2=None, Reference3=None, Origin=seko.DestinationType( - Id=None, - Name=None, + Id=options.seko_origin_id.state, + Name=shipper.company_name, Address=seko.AddressType( BuildingName=None, - StreetAddress=None, + StreetAddress=shipper.street, Suburb=None, - City=None, - PostCode=None, - CountryCode=None, + City=shipper.city, + PostCode=shipper.postal_code, + CountryCode=shipper.country_code, ), - ContactPerson=None, - PhoneNumber=None, - Email=None, - DeliveryInstructions=None, - RecipientTaxId=None, + ContactPerson=shipper.contact, + PhoneNumber=shipper.phone_number, + Email=shipper.email, + DeliveryInstructions=options.origin_instructions.state, + RecipientTaxId=shipper.tax_id, SendTrackingEmail=None, ), Destination=seko.DestinationType( - Id=None, - Name=None, + Id=options.seko_destination_id.state, + Name=recipient.company_name, Address=seko.AddressType( BuildingName=None, - StreetAddress=None, + StreetAddress=recipient.street, Suburb=None, - City=None, - PostCode=None, - CountryCode=None, + City=recipient.city, + PostCode=recipient.postal_code, + CountryCode=recipient.country_code, ), - ContactPerson=None, - PhoneNumber=None, - Email=None, - DeliveryInstructions=None, - RecipientTaxId=None, - SendTrackingEmail=None, - ), - DangerousGoods=seko.DangerousGoodsType( - AdditionalHandlingInfo=None, - HazchemCode=None, - IsRadioActive=None, - CargoAircraftOnly=None, - IsDGLQ=None, - TotalQuantity=None, - TotalKg=None, - SignOffName=None, - SignOffRole=None, - LineItems=[ - seko.ItemType( - HarmonizedCode=None, - Description=None, - ClassOrDivision=None, - UNorIDNo=None, - PackingGroup=None, - SubsidaryRisk=None, - Packing=None, - PackingInstr=None, - Authorization=None, - ) - ], + ContactPerson=recipient.contact, + PhoneNumber=recipient.phone_number, + Email=recipient.email, + DeliveryInstructions=options.destination_instructions.state, + RecipientTaxId=recipient.tax_id, + SendTrackingEmail=options.seko_send_tracking_email.state, ), + DangerousGoods=None, Commodities=[ seko.CommodityType( - Description=None, - Units=None, - UnitValue=None, - UnitKg=None, - Currency=None, - Country=None, + Description=lib.text(commodity.description, max=35), + Units=commodity.quantity, + UnitValue=commodity.value_amount, + UnitKg=commodity.weight, + Currency=commodity.value_currency, + Country=commodity.origin_country, IsDG=None, - itemSKU=None, - DangerousGoodsItem=seko.ItemType( - HarmonizedCode=None, - Description=None, - ClassOrDivision=None, - UNorIDNo=None, - PackingGroup=None, - SubsidaryRisk=None, - Packing=None, - PackingInstr=None, - Authorization=None, - ), + itemSKU=commodity.sku, + DangerousGoodsItem=None, ) + for commodity in commodities ], Packages=[ seko.PackageType( - Height=None, - Length=None, - Width=None, - Kg=None, - Name=None, - Type=None, - OverLabelBarcode=None, + Height=package.height.CM, + Length=package.length.CM, + Width=package.width.CM, + Kg=package.weight.KG, + Name=package.description, + Type=lib.identity( + provider_units.PackagingType.map(package.packaging_type).value + ), + OverLabelBarcode=package.reference_number, ) + for package in packages ], - issignaturerequired=None, - DutiesAndTaxesByReceiver=None, - PrintToPrinter=None, - IncludeLineDetails=None, - Carrier=None, - Service=None, - CostCentreName=None, - CodValue=None, - TaxCollected=None, - AmountCollected=None, + issignaturerequired=options.seko_is_signature_required.state, + DutiesAndTaxesByReceiver=lib.identity( + customs.duty.paid_by == "recipient" if payload.customs else None + ), + PrintToPrinter=lib.identity( + options.seko_print_to_printer.state + if options.seko_print_to_printer.state is not None + else True + ), + IncludeLineDetails=True, + Carrier=options.seko_carrier.state, + Service=service.value_or_key, + CostCentreName=settings.connection_config.cost_center.state, + CodValue=options.cash_on_delivery.state, + TaxCollected=lib.identity( + options.seko_tax_collected.state + if options.seko_tax_collected.state is not None + else True + ), + AmountCollected=lib.to_money(options.seko_amount_collected.state), TaxIds=[ seko.TaxIDType( - IdType=None, - IdNumber=None, + IdType=option.code, + IdNumber=option.state, ) + for key, option in customs.options.items() + if key in provider_units.CustomsOption and option.state is not None ], - Outputs=[], + Outputs=[label_type], ) - return lib.Serializable(request, lib.to_dict) + return lib.Serializable( + request, + lib.to_dict, + dict(label_type=label_type, label_format=label_format), + ) diff --git a/modules/connectors/seko/karrio/providers/seko/tracking.py b/modules/connectors/seko/karrio/providers/seko/tracking.py index ce79ce7a4..f318b1a9e 100644 --- a/modules/connectors/seko/karrio/providers/seko/tracking.py +++ b/modules/connectors/seko/karrio/providers/seko/tracking.py @@ -12,7 +12,7 @@ def parse_tracking_response( - _response: lib.Deserializable[typing.List[typing.Tuple[str, dict]]], + _response: lib.Deserializable[typing.List[dict]], settings: provider_utils.Settings, ) -> typing.Tuple[typing.List[models.TrackingDetails], typing.List[models.Message]]: responses = _response.deserialize() @@ -20,7 +20,11 @@ def parse_tracking_response( messages: typing.List[models.Message] = error.parse_error_response( responses, settings ) - tracking_details = [_extract_details(details, settings) for _, details in responses] + tracking_details = [ + _extract_details(_, settings) + for _ in (responses if isinstance(responses, list) else [responses]) + if any(_.get("Events", [])) + ] return tracking_details, messages @@ -29,12 +33,12 @@ def _extract_details( data: dict, settings: provider_utils.Settings, ) -> models.TrackingDetails: - details = None # parse carrier tracking object type + details = lib.to_object(tracking.TrackingResponseElementType, data) status = next( ( status.name for status in list(provider_units.TrackingStatus) - if getattr(details, "status", None) in status.value + if getattr(details, "Status", None) in status.value ), provider_units.TrackingStatus.in_transit.name, ) @@ -42,18 +46,17 @@ def _extract_details( return models.TrackingDetails( carrier_id=settings.carrier_id, carrier_name=settings.carrier_name, - tracking_number="", + tracking_number=details.ConsignmentNo, events=[ models.TrackingEvent( - date=lib.fdate(""), - description="", - code="", - time=lib.flocaltime(""), - location="", + date=lib.fdate(event.EventDT, "%Y-%m-%dT%H:%M:%S.%f"), + description=event.Description, + code=event.Code or event.OmniCode, + time=lib.flocaltime(event.EventDT, "%Y-%m-%dT%H:%M:%S.%f"), + location=event.Location, ) - for event in [] + for event in details.Events ], - estimated_delivery=lib.fdate(""), delivered=status == "delivered", status=status, ) diff --git a/modules/connectors/seko/karrio/providers/seko/units.py b/modules/connectors/seko/karrio/providers/seko/units.py index 078cea263..7ac5ede8b 100644 --- a/modules/connectors/seko/karrio/providers/seko/units.py +++ b/modules/connectors/seko/karrio/providers/seko/units.py @@ -1,35 +1,76 @@ - import karrio.lib as lib import karrio.core.units as units +class LabelType(lib.Enum): + LABEL_PDF = ("PDF", "LABEL_PDF") + LABEL_PNG_100X150 = ("PNG", "LABEL_PNG_100X150") + LABEL_PNG_100X175 = ("PNG", "LABEL_PNG_100X175") + LABEL_PDF_100X175 = ("PDF", "LABEL_PDF_100X175") + LABEL_PDF_100X150 = ("PDF", "LABEL_PDF_100X150") + LABEL_ZPL_100X175 = ("ZPL", "LABEL_ZPL_100X175") + LABEL_ZPL_100X150 = ("ZPL", "LABEL_ZPL_100X150") + + """ Unified Label type mapping """ + PDF = LABEL_PDF_100X150 + ZPL = LABEL_ZPL_100X150 + PNG = LABEL_PNG_100X150 + + class PackagingType(lib.StrEnum): - """ Carrier specific packaging type """ - PACKAGE = "PACKAGE" + """Carrier specific packaging type""" + + Bag = "Bag" + Box = "Box" + Carton = "Carton" + Container = "Container" + Crate = "Crate" + Envelope = "Envelope" + Pail = "Pail" + Pallet = "Pallet" + Satchel = "Satchel" + Tube = "Tube" + Custom = "Custom" """ Unified Packaging type mapping """ - envelope = PACKAGE - pak = PACKAGE - tube = PACKAGE - pallet = PACKAGE - small_box = PACKAGE - medium_box = PACKAGE - your_packaging = PACKAGE + envelope = Envelope + pak = Satchel + tube = Tube + pallet = Pallet + small_box = Box + medium_box = Carton + your_packaging = Custom class ShippingService(lib.StrEnum): - """ Carrier specific services """ - seko_standard_service = "SEKO Logistics Standard Service" + """Carrier specific services""" + + seko_ecommerce_standard_tracked = "eCommerce Standard Tracked" + seko_ecommerce_express_tracked = "eCommerce Express Tracked" + seko_domestic_express = "Domestic Express" + seko_domestic_standard = "Domestic Standard" + seko_domestic_large_parcel = "Domestic Large Parcel" class ShippingOption(lib.Enum): - """ Carrier specific options """ - # seko_option = lib.OptionEnum("code") + """Carrier specific options""" + + seko_carrier = lib.OptionEnum("Carrier") + seko_package_id = lib.OptionEnum("PackageId") + seko_destination_id = lib.OptionEnum("DestinationId") + origin_instructions = lib.OptionEnum("OriginInstructions") + destination_instructions = lib.OptionEnum("DestinationInstructions") + seko_is_saturday_delivery = lib.OptionEnum("IsSaturdayDelivery", bool) + seko_is_signature_required = lib.OptionEnum("IsSignatureRequired", bool) + seko_send_tracking_email = lib.OptionEnum("SendTrackingEmail", bool) + seko_amount_collected = lib.OptionEnum("AmountCollected", float) + seko_tax_collected = lib.OptionEnum("TaxCollected", bool) + seko_cod_amount = lib.OptionEnum("CODAmount", float) """ Unified Option type mapping """ - # insurance = seko_coverage # maps unified karrio option to carrier specific - - pass + saturday_delivery = seko_is_saturday_delivery + signature_required = seko_is_signature_required + email_notification = seko_send_tracking_email def shipping_options_initializer( @@ -49,6 +90,27 @@ def items_filter(key: str) -> bool: return units.ShippingOptions(options, ShippingOption, items_filter=items_filter) +class CustomsOption(lib.Enum): + XIEORINumber = lib.OptionEnum("XIEORINumber") + IOSSNUMBER = lib.OptionEnum("IOSSNUMBER") + GBEORINUMBER = lib.OptionEnum("GBEORINUMBER") + VOECNUMBER = lib.OptionEnum("VOECNUMBER") + VATNUMBER = lib.OptionEnum("VATNUMBER") + VENDORID = lib.OptionEnum("VENDORID") + NZIRDNUMBER = lib.OptionEnum("NZIRDNUMBER") + SWISS_VAT = lib.OptionEnum("SWISS VAT") + OVRNUMBER = lib.OptionEnum("OVRNUMBER") + EUEORINumber = lib.OptionEnum("EUEORINumber") + EUVATNumber = lib.OptionEnum("EUVATNumber") + LVGRegistrationNumber = lib.OptionEnum("LVGRegistrationNumber") + + """ Unified Customs Identifier type mapping """ + + ioss = IOSSNUMBER + nip_number = VATNUMBER + eori_number = EUEORINumber + + class TrackingStatus(lib.Enum): on_hold = ["on_hold"] delivered = ["delivered"] diff --git a/modules/connectors/seko/karrio/providers/seko/utils.py b/modules/connectors/seko/karrio/providers/seko/utils.py index 7ec72bba4..e2c4f56d0 100644 --- a/modules/connectors/seko/karrio/providers/seko/utils.py +++ b/modules/connectors/seko/karrio/providers/seko/utils.py @@ -93,5 +93,6 @@ def connection_config(self) -> lib.units.Options: class ConnectionConfig(lib.Enum): + cost_center = lib.OptionEnum("CostCentreName", str) shipping_options = lib.OptionEnum("shipping_options", list) shipping_services = lib.OptionEnum("shipping_services", list) diff --git a/modules/connectors/seko/karrio/schemas/seko/shipping_response.py b/modules/connectors/seko/karrio/schemas/seko/shipping_response.py index c841a074c..e2c146249 100644 --- a/modules/connectors/seko/karrio/schemas/seko/shipping_response.py +++ b/modules/connectors/seko/karrio/schemas/seko/shipping_response.py @@ -28,11 +28,6 @@ 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 @@ -44,7 +39,7 @@ class ConsignmentType: IsOvernight: Optional[bool] = None HasTrackPaks: Optional[bool] = None ConsignmentId: Optional[int] = None - OutputFiles: Optional[OutputFilesType] = JStruct[OutputFilesType] + OutputFiles: Optional[dict] = {} Items: List[ItemType] = JList[ItemType] diff --git a/modules/connectors/seko/tests/seko/fixture.py b/modules/connectors/seko/tests/seko/fixture.py index 9587f1c2d..26b29e09f 100644 --- a/modules/connectors/seko/tests/seko/fixture.py +++ b/modules/connectors/seko/tests/seko/fixture.py @@ -1,8 +1,8 @@ - import karrio gateway = karrio.gateway["seko"].create( dict( - # add required carrier API setting key/value here + access_key="access_key", + config=dict(cost_center="mysite.com"), ) ) diff --git a/modules/connectors/seko/tests/seko/test_manifest.py b/modules/connectors/seko/tests/seko/test_manifest.py index 06843453d..242e98450 100644 --- a/modules/connectors/seko/tests/seko/test_manifest.py +++ b/modules/connectors/seko/tests/seko/test_manifest.py @@ -25,7 +25,7 @@ def test_create_manifest(self): self.assertEqual( mock.call_args[1]["url"], - f"{gateway.settings.server_url}", + f"{gateway.settings.server_url}/v2/publishmanifestv4", ) def test_parse_manifest_response(self): @@ -43,7 +43,7 @@ def test_parse_manifest_response(self): ManifestPayload = { - "shipment_identifiers": ["794947717776"], + "shipment_identifiers": ["6994008906", "6994008907"], "address": { "city": "Los Angeles", "state_code": "CA", @@ -57,8 +57,14 @@ def test_parse_manifest_response(self): { "carrier_id": "seko", "carrier_name": "seko", - "doc": {"manifest": ANY}, - "meta": {}, + "doc": { + "manifest": "JVBERi0xLjcKJeLjz9MKMSAwIG9iago8PAovVHlwZSAvUGFnZXMKL0NvdW50IDEKL0tpZHMgWyA0IDAgUiBdCj4+CmVuZG9iagoyIDAgb2JqCjw8Ci9Qcm9kdWNlciAoUHlQREYyKQo+PgplbmRvYmoKMyAwIG9iago8PAovVHlwZSAvQ2F0YWxvZwovUGFnZXMgMSAwIFIKPj4KZW5kb2JqCjQgMCBvYmoKPDwKL1R5cGUgL1BhZ2UKL01lZGlhQm94IFsgMCAwIDMuNiAzLjYgXQovQ29udGVudHMgNSAwIFIKL1Jlc291cmNlcyA2IDAgUgovVHJpbUJveCBbIDAgMCAzLjYgMy42IF0KL0JsZWVkQm94IFsgMCAwIDMuNiAzLjYgXQovUGFyZW50IDEgMCBSCj4+CmVuZG9iago1IDAgb2JqCjw8Ci9GaWx0ZXIgL0ZsYXRlRGVjb2RlCi9MZW5ndGggNzEKPj4Kc3RyZWFtCnjaM1QwAEJdQyBhrGemkJzLVchloGduChaGM8DChVyGCiBYlM6ln2ioZ6CQXswFkjTRswDjolSucK48dKE0rkAQBAAu7xTjCmVuZHN0cmVhbQplbmRvYmoKNiAwIG9iago8PAovRXh0R1N0YXRlIDw8Ci9hMS4wIDw8Ci9jYSAxCj4+Cj4+Ci9YT2JqZWN0IDw8Cj4+Ci9QYXR0ZXJuIDw8Cj4+Ci9TaGFkaW5nIDw8Cj4+Ci9Gb250IDcgMCBSCj4+CmVuZG9iago3IDAgb2JqCjw8Cj4+CmVuZG9iagp4cmVmCjAgOAowMDAwMDAwMDAwIDY1NTM1IGYgCjAwMDAwMDAwMTUgMDAwMDAgbiAKMDAwMDAwMDA3NCAwMDAwMCBuIAowMDAwMDAwMTE0IDAwMDAwIG4gCjAwMDAwMDAxNjMgMDAwMDAgbiAKMDAwMDAwMDMyMCAwMDAwMCBuIAowMDAwMDAwNDYyIDAwMDAwIG4gCjAwMDAwMDA1NzUgMDAwMDAgbiAKdHJhaWxlcgo8PAovU2l6ZSA4Ci9Sb290IDMgMCBSCi9JbmZvIDIgMCBSCj4+CnN0YXJ0eHJlZgo1OTYKJSVFT0YK" + }, + "meta": { + "ManifestConnotes": ["01593505840002135181", "01593505840002135198"], + "ManifestNumber": "OHG00288", + "ManifestNumbers": ["OHG00288"], + }, }, [], ] @@ -71,7 +77,7 @@ def test_parse_manifest_response(self): { "ManifestNumber": "OHG00288", "ManifestedConnotes": ["01593505840002135181", "01593505840002135198"], - "ManifestContent": "JVBERi0xLjcgCiXi48/TIAoxIDAgb2JqIAo8PCAKL1R5cGUgL0NhdGFsb2cgCi9QYWdlcyAyIDAgUiAKL1BhZ2VNb2RlIC9Vc2VOb25lIAovVmlld2VyUH...." + "ManifestContent": "JVBERi0xLjcKJfCflqQKNSAwIG9iago8PC9GaWx0ZXIgL0ZsYXRlRGVjb2RlL0xlbmd0aCA3MT4+CnN0cmVhbQp42jNUMABCXUMgYaxnppCcy1XIZaBnbgoWhjPAwoVchgogWJTOpZ9oqGegkF7MBZI00bMA46JUrnCuPHShNK5AEAQALu8U4wplbmRzdHJlYW0KZW5kb2JqCjggMCBvYmoKPDwvVHlwZSAvT2JqU3RtL04gNi9GaXJzdCAzMi9GaWx0ZXIgL0ZsYXRlRGVjb2RlL0xlbmd0aCAyMzE+PgpzdHJlYW0KeNpljzFPwzAQhXd+xY2wxHEdnA6Vh1bAgBBRWwmkqsMRn4JRiZHtSO2/5xwPHTrY1nfv7t2zhBoWoJagoK2hAVlL0CCXGlpQsrlbrcT+8kcgOhwoildnIxw0D22PYuOnMYE0Jnd1wduppwD3H4Tx0gXHml5U6qHoxWWDCU9+KG4gs0+Rn87pZZcwETCgrOr89pjdjRGf718/1CeuMXSYEoWxwO4brRuHAs+ed7ZX02tyvgLlsFkUb2Qdrv0ZDjWzqnQ++T9j4qYIj3PXlqKfQs85m5n3wf3eDq1PRPamPO835h8r6WAfCmVuZHN0cmVhbQplbmRvYmoKOSAwIG9iago8PC9UeXBlIC9YUmVmL0luZGV4IFswIDEwXS9XIFsxIDIgMl0vU2l6ZSAxMC9Sb290IDMgMCBSL0luZm8gMiAwIFIvRmlsdGVyIC9GbGF0ZURlY29kZS9MZW5ndGggNDQ+PgpzdHJlYW0KeNolyLENACAMA8F3CBJd9me6bBJk0VxxwExwwMiEWaL+pdnigtTwAG3LA9cKZW5kc3RyZWFtCmVuZG9iagpzdGFydHhyZWYKNDgwCiUlRU9GCg==" } ], "InboundManifest": [], diff --git a/modules/connectors/seko/tests/seko/test_rate.py b/modules/connectors/seko/tests/seko/test_rate.py index 42872a951..3cbb95d2a 100644 --- a/modules/connectors/seko/tests/seko/test_rate.py +++ b/modules/connectors/seko/tests/seko/test_rate.py @@ -25,7 +25,7 @@ def test_get_rate(self): self.assertEqual( mock.call_args[1]["url"], - f"{gateway.settings.server_url}", + f"{gateway.settings.server_url}/ratesqueryv1/availablerates", ) def test_parse_rate_response(self): @@ -55,62 +55,99 @@ def test_parse_rate_response(self): "phone_number": "(07) 3114 1499", }, "recipient": { - "company_name": "CGI", - "address_line1": "23 jardin private", - "city": "Ottawa", - "postal_code": "k1k 4t3", - "country_code": "CA", - "person_name": "Jain", - "state_code": "ON", + "address_line1": "DestinationStreetAddress", + "city": "Christchurch", + "postal_code": "8061", + "country_code": "NZ", + "person_name": "DestinationName", + "phone_number": "123456789", + "email": "destinationemail@email.com", + "state_tax_id": "123456", }, "parcels": [ { - "height": 50, - "length": 50, - "weight": 20, - "width": 12, + "height": 1, + "length": 1, + "weight": 0.1, + "width": 10, "dimension_unit": "CM", "weight_unit": "KG", + "description": "SATCHEL", + "packaging_type": "small_box", } ], - "options": {}, - "reference": "REF-001", + "options": { + "saturday_delivery": False, + "seko_is_signature_required": True, + "destination_instructions": "Desinationdeliveryinstructions", + }, + "reference": "ORDER123", } -ParsedRateResponse = [] +ParsedRateResponse = [ + [ + { + "carrier_id": "seko", + "carrier_name": "seko", + "currency": "USD", + "meta": { + "CarrierServiceType": "InternationalCourier", + "DeliveryType": "AIR TRACKED", + "IsFreightForward": False, + "IsRuralDelivery": False, + "IsSaturdayDelivery": False, + "QuoteId": "e7fdf36c-8f6a-4d3f-8d96-c8b5893a0e7f", + "Route": "OFFSHORE->AKL- SI", + "seko_carrier": "Omni Parcel", + "service_name": "InternationalCourier", + }, + "service": "InternationalCourier", + "total_charge": 5.82, + } + ], + [ + { + "carrier_id": "seko", + "carrier_name": "seko", + "code": "ValidationError", + "message": "CountryCode is required", + "details": { + "Key": "CountryCode", + "Message": "CountryCode is required", + "Property": "Destination.Address.CountryCode", + "code": "ValidationError", + "message": "CountryCode is required", + }, + } + ], +] RateRequest = { "DeliveryReference": "ORDER123", "Destination": { - "Id": 0, - "Name": "DestinationName", "Address": { - "BuildingName": "", - "StreetAddress": "DestinationStreetAddress", - "Suburb": "Avonside", "City": "Christchurch", - "PostCode": "8061", "CountryCode": "NZ", + "PostCode": "8061", + "StreetAddress": "DestinationStreetAddress", }, - "ContactPerson": "DestinationContact", - "PhoneNumber": "123456789", - "Email": "destinationemail@email.com", + "ContactPerson": "DestinationName", "DeliveryInstructions": "Desinationdeliveryinstructions", + "Email": "destinationemail@email.com", + "PhoneNumber": "123456789", "RecipientTaxId": "123456", }, "IsSaturdayDelivery": False, "IsSignatureRequired": True, "Packages": [ { - "Height": 1, - "Length": 1, - "Id": 0, - "Width": 10, + "Height": 1.0, "Kg": 0.1, + "Length": 1.0, "Name": "SATCHEL", - "PackageCode": "DLE", "Type": "Box", + "Width": 10.0, } ], } diff --git a/modules/connectors/seko/tests/seko/test_shipment.py b/modules/connectors/seko/tests/seko/test_shipment.py index 21ba4c629..ff0878f2d 100644 --- a/modules/connectors/seko/tests/seko/test_shipment.py +++ b/modules/connectors/seko/tests/seko/test_shipment.py @@ -35,7 +35,7 @@ def test_create_shipment(self): self.assertEqual( mock.call_args[1]["url"], - f"{gateway.settings.server_url}", + f"{gateway.settings.server_url}/labels/printshipment", ) def test_cancel_shipment(self): @@ -45,7 +45,7 @@ def test_cancel_shipment(self): self.assertEqual( mock.call_args[1]["url"], - f"{gateway.settings.server_url}", + f"{gateway.settings.server_url}/labels/delete", ) def test_parse_shipment_response(self): @@ -76,48 +76,140 @@ def test_parse_cancel_shipment_response(self): ShipmentPayload = { + "service": "seko_ecommerce_express_tracked", "shipper": { - "company_name": "TESTING COMPANY", - "address_line1": "17 VULCAN RD", - "city": "CANNING VALE", - "postal_code": "6155", + "company_name": "OriginName", + "address_line1": "285 Main Street", + "city": "GLENWOOD", + "postal_code": "2768", "country_code": "AU", - "person_name": "TEST USER", - "state_code": "WA", - "email": "test@gmail.com", - "phone_number": "(07) 3114 1499", + "person_name": "Origin contact name", + "state_code": "NSW", + "email": "originemail@sekologistics.com", + "phone_number": "02 9111 01101", + "state_tax_id": "123456", }, "recipient": { - "company_name": "CGI", - "address_line1": "23 jardin private", - "city": "Ottawa", - "postal_code": "k1k 4t3", - "country_code": "CA", - "person_name": "Jain", - "state_code": "ON", + "company_name": "Destination Name", + "address_line1": "285 Coward Street", + "city": "TESBURY", + "postal_code": "3260", + "country_code": "AU", + "person_name": "JOHN SMITH", + "state_code": "VIC", + "email": "destinationemail@test.com", + "phone_number": "02 9111 1111", + "state_tax_id": "123456", }, "parcels": [ { - "height": 50, - "length": 50, - "weight": 20, - "width": 12, + "height": 1.0, + "length": 1.0, + "weight": 5.0, + "width": 1.0, "dimension_unit": "CM", "weight_unit": "KG", + "reference_number": "TEST0301201902", + "packaging_type": "small_box", + "description": "PARCEL", } ], - "service": "carrier_service", "options": { - "signature_required": True, + "origin_instructions": "Desinationdeliveryinstructions", + "destination_instructions": "LEAVE AT FRONT DOOR", + "seko_send_tracking_email": True, + "seko_carrier": "Omni Parcel", + "signature_required": False, + "seko_amount_collected": 10.0, + "cash_on_delivery": 10.0, + "currency": "USD", + }, + "reference": "OrderNumber123", + "customs": { + "commodities": [ + { + "description": "Food Bar", + "quantity": 1, + "value_amount": 50, + "value_currency": "USD", + "weight": 0.6, + "origin_country": "AU", + "sku": "SKU123", + }, + { + "description": "Food Bar", + "quantity": 1, + "value_amount": 50, + "value_currency": "USD", + "weight": 0.6, + "origin_country": "AU", + }, + ], + "options": { + "XIEORINumber": "0121212112", + "IOSSNUMBER": "0121212112", + "GBEORINUMBER": "0121212112", + "VOECNUMBER": "0121212112", + "VATNUMBER": "0121212112", + "VENDORID": "0121212113", + "NZIRDNUMBER": "0121212115", + "SWISS_VAT": "CHE-123.456.789", + "OVRNUMBER": "0121212112", + "EUEORINumber": "0121212112", + "EUVATNumber": "0121212112", + "LVGRegistrationNumber": "0121212112", + }, }, - "reference": "#Order 11111", } ShipmentCancelPayload = { "shipment_identifier": "794947717776", + "options": { + "shipment_identifiers": [ + "SSPOT014115", + "SSPOT014114", + "SSPOT014113", + "SSPOT014112", + ] + }, } -ParsedShipmentResponse = [] +ParsedShipmentResponse = [ + { + "carrier_id": "seko", + "carrier_name": "seko", + "docs": { + "label": "JVBERi0xLjcKJeLjz9MKMSAwIG9iago8PAovVHlwZSAvUGFnZXMKL0NvdW50IDEKL0tpZHMgWyA0IDAgUiBdCj4+CmVuZG9iagoyIDAgb2JqCjw8Ci9Qcm9kdWNlciAoUHlQREYyKQo+PgplbmRvYmoKMyAwIG9iago8PAovVHlwZSAvQ2F0YWxvZwovUGFnZXMgMSAwIFIKPj4KZW5kb2JqCjQgMCBvYmoKPDwKL1R5cGUgL1BhZ2UKL01lZGlhQm94IFsgMCAwIDMuNiAzLjYgXQovQ29udGVudHMgNSAwIFIKL1Jlc291cmNlcyA2IDAgUgovVHJpbUJveCBbIDAgMCAzLjYgMy42IF0KL0JsZWVkQm94IFsgMCAwIDMuNiAzLjYgXQovUGFyZW50IDEgMCBSCj4+CmVuZG9iago1IDAgb2JqCjw8Ci9GaWx0ZXIgL0ZsYXRlRGVjb2RlCi9MZW5ndGggNzEKPj4Kc3RyZWFtCnjaM1QwAEJdQyBhrGemkJzLVchloGduChaGM8DChVyGCiBYlM6ln2ioZ6CQXswFkjTRswDjolSucK48dKE0rkAQBAAu7xTjCmVuZHN0cmVhbQplbmRvYmoKNiAwIG9iago8PAovRXh0R1N0YXRlIDw8Ci9hMS4wIDw8Ci9jYSAxCj4+Cj4+Ci9YT2JqZWN0IDw8Cj4+Ci9QYXR0ZXJuIDw8Cj4+Ci9TaGFkaW5nIDw8Cj4+Ci9Gb250IDcgMCBSCj4+CmVuZG9iago3IDAgb2JqCjw8Cj4+CmVuZG9iagp4cmVmCjAgOAowMDAwMDAwMDAwIDY1NTM1IGYgCjAwMDAwMDAwMTUgMDAwMDAgbiAKMDAwMDAwMDA3NCAwMDAwMCBuIAowMDAwMDAwMTE0IDAwMDAwIG4gCjAwMDAwMDAxNjMgMDAwMDAgbiAKMDAwMDAwMDMyMCAwMDAwMCBuIAowMDAwMDAwNDYyIDAwMDAwIG4gCjAwMDAwMDA1NzUgMDAwMDAgbiAKdHJhaWxlcgo8PAovU2l6ZSA4Ci9Sb290IDMgMCBSCi9JbmZvIDIgMCBSCj4+CnN0YXJ0eHJlZgo1OTYKJSVFT0YK" + }, + "label_type": "PDF", + "meta": { + "CarrierId": 567, + "CarrierName": "MyChildData", + "ConsignmentId": 5473553, + "ConsignmentIds": [5473553], + "SiteId": 1153896, + "TrackingUrls": ["http://track.omniparcel.com/1153896-6994008906"], + "carrier_tracking_link": "http://track.omniparcel.com/1153896-6994008906", + }, + "shipment_identifier": 5473553, + "tracking_number": "6994008906", + }, + [ + { + "carrier_id": "seko", + "carrier_name": "seko", + "code": "Error", + "details": { + "Key": "CountryCode", + "Message": "CountryCode is required", + "Property": "Destination.Address.CountryCode", + "code": "Error", + "message": "CountryCode is required", + }, + "message": "CountryCode is required", + } + ], +] ParsedCancelShipmentResponse = ParsedCancelShipmentResponse = [ { @@ -132,16 +224,11 @@ def test_parse_cancel_shipment_response(self): ShipmentRequest = { "DeliveryReference": "OrderNumber123", - "Reference2": "", - "Reference3": "", "Origin": { - "Id": 0, "Name": "OriginName", "Address": { - "BuildingName": "", "StreetAddress": "285 Main Street", - "Suburb": "GLENWOOD", - "City": "NSW", + "City": "GLENWOOD", "PostCode": "2768", "CountryCode": "AU", }, @@ -152,13 +239,10 @@ def test_parse_cancel_shipment_response(self): "RecipientTaxId": "123456", }, "Destination": { - "Id": 0, "Name": "Destination Name", "Address": { - "BuildingName": "Markettown", "StreetAddress": "285 Coward Street", - "Suburb": "TESBURY", - "City": "VIC", + "City": "TESBURY", "PostCode": "3260", "CountryCode": "AU", }, @@ -167,72 +251,25 @@ def test_parse_cancel_shipment_response(self): "Email": "destinationemail@test.com", "DeliveryInstructions": "LEAVE AT FRONT DOOR", "RecipientTaxId": "123456", - "SendTrackingEmail": "true", - }, - "DangerousGoods": { - "AdditionalHandlingInfo": "sample", - "HazchemCode": "sample", - "IsRadioActive": "false", - "CargoAircraftOnly": "false", - "IsDGLQ": "false", - "TotalQuantity": 2, - "TotalKg": 1.2, - "SignOffName": "name", - "SignOffRole": "dangerous goods officer", - "LineItems": [ - { - "HarmonizedCode": "sample", - "Description": "sample", - "ClassOrDivision": "sample", - "UNorIDNo": "sample", - "PackingGroup": "sample", - "SubsidaryRisk": "sample", - "Packing": "sample", - "PackingInstr": "sample", - "Authorization": "sample", - } - ], + "SendTrackingEmail": True, }, "Commodities": [ { "Description": "Food Bar", - "Units": "1", + "Units": 1, "UnitValue": 50, "UnitKg": 0.6, "Currency": "USD", "Country": "AU", - "IsDG": true, "itemSKU": "SKU123", - "DangerousGoodsItem": { - "HarmonizedCode": "sample", - "Description": "sample", - "ClassOrDivision": "sample", - "UNorIDNo": "sample", - "PackingGroup": "sample", - "SubsidaryRisk": "sample", - "Packing": "sample", - "PackingInstr": "sample", - "Authorization": "sample", - }, }, { "Description": "Food Bar", - "Units": "1", + "Units": 1, "UnitValue": 50, "UnitKg": 0.6, "Currency": "USD", "Country": "AU", - "IsDG": true, - "DangerousGoodsItem": { - "Description": "sample", - "ClassOrDivision": "sample", - "UNorIDNo": "sample", - "PackingGroup": "sample", - "SubsidaryRisk": "sample", - "Packing": "sample", - "PackingInstr": "sample", - "Authorization": "sample", - }, }, ], "Packages": [ @@ -246,15 +283,15 @@ def test_parse_cancel_shipment_response(self): "OverLabelBarcode": "TEST0301201902", } ], - "issignaturerequired": false, - "DutiesAndTaxesByReceiver": false, - "PrintToPrinter": true, - "IncludeLineDetails": true, + "issignaturerequired": False, + "DutiesAndTaxesByReceiver": False, + "PrintToPrinter": True, + "IncludeLineDetails": True, "Carrier": "Omni Parcel", "Service": "eCommerce Express Tracked", "CostCentreName": "mysite.com", "CodValue": 10.0, - "TaxCollected": true, + "TaxCollected": True, "AmountCollected": 10.0, "TaxIds": [ {"IdType": "XIEORINumber", "IdNumber": "0121212112"}, @@ -307,7 +344,7 @@ def test_parse_cancel_shipment_response(self): "ConsignmentId": 5473553, "OutputFiles": { "LABEL_PDF_100X150": [ - "JVBERi0xLjQKJdP0zOEKMSAwIG9iago8PAovQ3JlYXRpb25EYXRlKEQ6MjAxNzAyMTMwOTA0MTkrMDU...." + "JVBERi0xLjcKJeLjz9MKMSAwIG9iago8PAovVHlwZSAvUGFnZXMKL0NvdW50IDEKL0tpZHMgWyA0IDAgUiBdCj4+CmVuZG9iagoyIDAgb2JqCjw8Ci9Qcm9kdWNlciAoUHlQREYyKQo+PgplbmRvYmoKMyAwIG9iago8PAovVHlwZSAvQ2F0YWxvZwovUGFnZXMgMSAwIFIKPj4KZW5kb2JqCjQgMCBvYmoKPDwKL1R5cGUgL1BhZ2UKL01lZGlhQm94IFsgMCAwIDMuNiAzLjYgXQovQ29udGVudHMgNSAwIFIKL1Jlc291cmNlcyA2IDAgUgovVHJpbUJveCBbIDAgMCAzLjYgMy42IF0KL0JsZWVkQm94IFsgMCAwIDMuNiAzLjYgXQovUGFyZW50IDEgMCBSCj4+CmVuZG9iago1IDAgb2JqCjw8Ci9GaWx0ZXIgL0ZsYXRlRGVjb2RlCi9MZW5ndGggNzEKPj4Kc3RyZWFtCnjaM1QwAEJdQyBhrGemkJzLVchloGduChaGM8DChVyGCiBYlM6ln2ioZ6CQXswFkjTRswDjolSucK48dKE0rkAQBAAu7xTjCmVuZHN0cmVhbQplbmRvYmoKNiAwIG9iago8PAovRXh0R1N0YXRlIDw8Ci9hMS4wIDw8Ci9jYSAxCj4+Cj4+Ci9YT2JqZWN0IDw8Cj4+Ci9QYXR0ZXJuIDw8Cj4+Ci9TaGFkaW5nIDw8Cj4+Ci9Gb250IDcgMCBSCj4+CmVuZG9iago3IDAgb2JqCjw8Cj4+CmVuZG9iagp4cmVmCjAgOAowMDAwMDAwMDAwIDY1NTM1IGYgCjAwMDAwMDAwMTUgMDAwMDAgbiAKMDAwMDAwMDA3NCAwMDAwMCBuIAowMDAwMDAwMTE0IDAwMDAwIG4gCjAwMDAwMDAxNjMgMDAwMDAgbiAKMDAwMDAwMDMyMCAwMDAwMCBuIAowMDAwMDAwNDYyIDAwMDAwIG4gCjAwMDAwMDA1NzUgMDAwMDAgbiAKdHJhaWxlcgo8PAovU2l6ZSA4Ci9Sb290IDMgMCBSCi9JbmZvIDIgMCBSCj4+CnN0YXJ0eHJlZgo1OTYKJSVFT0YK" ] }, "Items": [ diff --git a/modules/connectors/seko/tests/seko/test_tracking.py b/modules/connectors/seko/tests/seko/test_tracking.py index 001664bde..d27d98b2f 100644 --- a/modules/connectors/seko/tests/seko/test_tracking.py +++ b/modules/connectors/seko/tests/seko/test_tracking.py @@ -25,7 +25,7 @@ def test_get_tracking(self): self.assertEqual( mock.call_args[1]["url"], - f"{gateway.settings.server_url}", + f"{gateway.settings.server_url}/labels/statusv2", ) def test_parse_tracking_response(self): @@ -52,12 +52,63 @@ def test_parse_error_response(self): TrackingPayload = { - "tracking_numbers": ["89108749065090"], + "tracking_numbers": ["6994008906", "6994008907"], } -ParsedTrackingResponse = [] +ParsedTrackingResponse = [ + [ + { + "carrier_id": "seko", + "carrier_name": "seko", + "delivered": False, + "events": [ + { + "code": "OP-1", + "date": "2021-03-01", + "description": "Tracking number allocated & order ready", + "location": "SAN BERNARDINO,CA,US", + "time": "21:47 PM", + }, + { + "code": "OP-3", + "date": "2021-03-05", + "description": "Processed through Export Hub", + "location": "Carson, CA,US", + "time": "08:56 AM", + }, + { + "code": "OP-4", + "date": "2021-03-05", + "description": "International transit to destination country ", + "location": "CARSON, CA,US", + "time": "13:53 PM", + }, + ], + "status": "in_transit", + "tracking_number": "WFY9001843", + } + ], + [], +] -ParsedErrorResponse = [] +ParsedErrorResponse = [ + [], + [ + { + "carrier_id": "seko", + "carrier_name": "seko", + "code": "Error", + "details": { + "Key": "CountryCode", + "Message": "CountryCode is required", + "Property": "Destination.Address.CountryCode", + "code": "Error", + "message": "CountryCode is required", + }, + "message": "CountryCode is required", + } + ], +] TrackingRequest = ["6994008906", "6994008907"] @@ -101,9 +152,13 @@ def test_parse_error_response(self): """ ErrorResponse = """{ - "Property": "Destination.Address.CountryCode", - "Message": "CountryCode is required", - "Key": "CountryCode", - "Value": "" + "Errors": [ + { + "Property": "Destination.Address.CountryCode", + "Message": "CountryCode is required", + "Key": "CountryCode", + "Value": "" + } + ] } """ diff --git a/modules/sdk/karrio/core/units.py b/modules/sdk/karrio/core/units.py index 41c2823e6..e3532ba56 100644 --- a/modules/sdk/karrio/core/units.py +++ b/modules/sdk/karrio/core/units.py @@ -783,6 +783,10 @@ def total_value(self) -> typing.Optional[float]: return self.items.value_amount + @property + def reference_number(self) -> typing.Optional[str]: + return self.parcel.reference_number + class Packages(typing.Iterable[Package]): """The parcel collection common processing helper""" @@ -1204,6 +1208,7 @@ class CustomsOption(utils.Enum): """common shipment customs identifiers""" aes = utils.OptionEnum("aes") + ioss = utils.OptionEnum("ioss") eel_pfc = utils.OptionEnum("eel_pfc") nip_number = utils.OptionEnum("eori_number") eori_number = utils.OptionEnum("eori_number")