diff --git a/plugins/freightcom_rest/README.md b/plugins/freightcom_rest/README.md new file mode 100644 index 0000000..035400a --- /dev/null +++ b/plugins/freightcom_rest/README.md @@ -0,0 +1,30 @@ +# karrio.freightcom_rest + +This package is a Freightcom Rest extension of the [karrio](https://pypi.org/project/karrio) multi carrier shipping SDK. + +## Requirements + +`Python 3.11+` + +## Installation + +```bash +pip install karrio.freightcom_rest +``` + +## Usage + +```python +import karrio.sdk as karrio +from karrio.mappers.freightcom_rest.settings import Settings + + +# Initialize a carrier gateway +freightcom_rest = karrio.gateway["freightcom_rest"].create( + Settings( + ... + ) +) +``` + +Check the [Karrio Mutli-carrier SDK docs](https://docs.karrio.io) for Shipping API requests diff --git a/plugins/freightcom_rest/generate b/plugins/freightcom_rest/generate new file mode 100644 index 0000000..66e5b09 --- /dev/null +++ b/plugins/freightcom_rest/generate @@ -0,0 +1,13 @@ +SCHEMAS=./schemas +LIB_MODULES=./karrio/schemas/freightcom_rest +find "${LIB_MODULES}" -name "*.py" -exec rm -r {} \; +touch "${LIB_MODULES}/__init__.py" + +kcli codegen generate "${SCHEMAS}/error_response.json" "${LIB_MODULES}/error_response.py" --nice-property-names +kcli codegen generate "${SCHEMAS}/rate_request.json" "${LIB_MODULES}/rate_request.py" --nice-property-names +kcli codegen generate "${SCHEMAS}/rate_response.json" "${LIB_MODULES}/rate_response.py" --nice-property-names +kcli codegen generate "${SCHEMAS}/shipment_request.json" "${LIB_MODULES}/shipment_request.py" --nice-property-names +kcli codegen generate "${SCHEMAS}/shipment_response.json" "${LIB_MODULES}/shipment_response.py" --nice-property-names +kcli codegen generate "${SCHEMAS}/pickup_request.json" "${LIB_MODULES}/pickup_request.py" --nice-property-names +kcli codegen generate "${SCHEMAS}/tracking_response.json" "${LIB_MODULES}/tracking_response.py" --nice-property-names + diff --git a/plugins/freightcom_rest/karrio/mappers/freightcom_rest/__init__.py b/plugins/freightcom_rest/karrio/mappers/freightcom_rest/__init__.py new file mode 100644 index 0000000..d1473d6 --- /dev/null +++ b/plugins/freightcom_rest/karrio/mappers/freightcom_rest/__init__.py @@ -0,0 +1,3 @@ +from karrio.mappers.freightcom_rest.mapper import Mapper +from karrio.mappers.freightcom_rest.proxy import Proxy +from karrio.mappers.freightcom_rest.settings import Settings \ No newline at end of file diff --git a/plugins/freightcom_rest/karrio/mappers/freightcom_rest/mapper.py b/plugins/freightcom_rest/karrio/mappers/freightcom_rest/mapper.py new file mode 100644 index 0000000..d6043c3 --- /dev/null +++ b/plugins/freightcom_rest/karrio/mappers/freightcom_rest/mapper.py @@ -0,0 +1,54 @@ +"""Karrio Freightcom Rest client mapper.""" + +import typing +import karrio.lib as lib +import karrio.api.mapper as mapper +import karrio.core.models as models +import karrio.providers.freightcom_rest as provider +import karrio.mappers.freightcom_rest.settings as provider_settings + + +class Mapper(mapper.Mapper): + settings: provider_settings.Settings + + def create_rate_request( + self, payload: models.RateRequest + ) -> lib.Serializable: + return provider.rate_request(payload, self.settings) + + # def create_tracking_request( + # self, payload: models.TrackingRequest + # ) -> lib.Serializable: + # return provider.tracking_request(payload, self.settings) + + def create_shipment_request( + self, payload: models.ShipmentRequest + ) -> lib.Serializable: + return provider.shipment_request(payload, self.settings) + + def create_cancel_shipment_request( + self, payload: models.ShipmentCancelRequest + ) -> lib.Serializable[str]: + return provider.shipment_cancel_request(payload, self.settings) + + + def parse_cancel_shipment_response( + self, response: lib.Deserializable[str] + ) -> typing.Tuple[models.ConfirmationDetails, typing.List[models.Message]]: + return provider.parse_shipment_cancel_response(response, self.settings) + + def parse_rate_response( + self, response: lib.Deserializable[str] + ) -> typing.Tuple[typing.List[models.RateDetails], typing.List[models.Message]]: + return provider.parse_rate_response(response, self.settings) + + def parse_shipment_response( + self, response: lib.Deserializable[str] + ) -> typing.Tuple[models.ShipmentDetails, typing.List[models.Message]]: + return provider.parse_shipment_response(response, self.settings) + + # def parse_tracking_response( + # self, response: lib.Deserializable[str] + # ) -> typing.Tuple[typing.List[models.TrackingDetails], typing.List[models.Message]]: + # return provider.parse_tracking_response(response, self.settings) + # diff --git a/plugins/freightcom_rest/karrio/mappers/freightcom_rest/proxy.py b/plugins/freightcom_rest/karrio/mappers/freightcom_rest/proxy.py new file mode 100644 index 0000000..fff812e --- /dev/null +++ b/plugins/freightcom_rest/karrio/mappers/freightcom_rest/proxy.py @@ -0,0 +1,124 @@ +"""Karrio Freightcom Rest client proxy.""" + +import time +import karrio.lib as lib +import karrio.api.proxy as proxy +import karrio.mappers.freightcom_rest.settings as provider_settings + +# IMPLEMENTATION INSTRUCTIONS: +# 1. Import the schema types specific to your carrier API +# 2. Uncomment and adapt the request examples below to work with your carrier API +# 3. Replace the stub responses with actual API calls once you've tested with the example data +# 4. Update URLs, headers, and authentication methods as required by your carrier API +MAX_RETRIES = 10 +POLL_INTERVAL = 2 # seconds + + +class Proxy(proxy.Proxy): + settings: provider_settings.Settings + + def get_rates(self, request: lib.Serializable) -> lib.Deserializable: + # Step 1: Submit rate request and get quote ID + response = self._send_request( + path="/rate", request=lib.Serializable(request.value, lib.to_json) + ) + + rate_id = lib.to_dict(response).get('request_id') + if not rate_id: + return lib.Deserializable(response, lib.to_dict) + + # Step 2: Poll for rate results + for _ in range(MAX_RETRIES): + status_res = self._send_request( + path=f"/rate/{rate_id}", + method="GET" + ) + + status = lib.to_dict(status_res).get('status', {}).get('done', False) + + if status: # Quote is complete + return lib.Deserializable(status_res, lib.to_dict) + + time.sleep(POLL_INTERVAL) + + # If we exceed max retries + return lib.Deserializable({ + 'message': 'Rate calculation timed out' + }, lib.to_dict) + + def create_shipment(self, request: lib.Serializable) -> lib.Deserializable: + + response = self._send_request( + path="/shipment", request=lib.Serializable(request.value, lib.to_json) + ) + response_dict = lib.failsafe(lambda: lib.to_dict(response)) or {} + shipment_id = response_dict.get('id') + + if not shipment_id: + return lib.Deserializable(response if response else "{}", lib.to_dict) + + # Step 2: retry because api return empty bytes if done to fast + time.sleep(1) + for _ in range(MAX_RETRIES): + + shipment_response = self._send_request(path=f"/shipment/{shipment_id}", method="GET") + shipment_res = lib.failsafe(lambda :lib.to_dict(shipment_response)) or lib.decode(shipment_response) + + if shipment_res: # is complete + return lib.Deserializable(shipment_res, lib.to_dict, request._ctx) + + time.sleep(POLL_INTERVAL) + + # If we exceed max retries + return lib.Deserializable({ + 'message': 'timed out creating shipment, shipment maybe created' + }, lib.to_dict) + + + # def get_tracking(self, request: lib.Serializable) -> lib.Deserializable[str]: + # responses = lib.run_asynchronously( + # lambda data: ( + # data["shipment_id"], + # self._send_request(path=f"/shipment/{data['shipment_id']}/tracking-events"), + # ), + # [_ for _ in request.serialize() if _.get("shipment_id")], + # ) + # + # print(lib.to_dict(responses)) + # return lib.Deserializable( + # responses, + # lambda __: [(_[0], lib.to_dict(_[1])) for _ in __], + # ) + + + + def _get_payments_methods(self) -> lib.Deserializable[str]: + response = self._send_request( + path="/finance/payment-methods", + method="GET" + ) + return lib.Deserializable(response, lib.to_dict) + + def cancel_shipment(self, request: lib.Serializable) -> lib.Deserializable: + response = self._send_request( + path=f"/shipment/{request.serialize()}", method="DELETE" + ) + return lib.Deserializable(response if any(response) else "{}", lib.to_dict) + + def _send_request( + self, path: str, request: lib.Serializable = None, method: str = "POST" + ) -> str: + + data: dict = dict(data=request.serialize()) if request is not None else dict() + return lib.request( + **{ + "url": f"{self.settings.server_url}{path}", + "trace": self.trace_as("json"), + "method": method, + "headers": { + "Content-Type": "application/json", + "Authorization": self.settings.api_key, + }, + **data, + } + ) diff --git a/plugins/freightcom_rest/karrio/mappers/freightcom_rest/settings.py b/plugins/freightcom_rest/karrio/mappers/freightcom_rest/settings.py new file mode 100644 index 0000000..3514029 --- /dev/null +++ b/plugins/freightcom_rest/karrio/mappers/freightcom_rest/settings.py @@ -0,0 +1,20 @@ +"""Karrio Freightcom Rest client settings.""" + +import attr +import karrio.providers.freightcom_rest.utils as provider_utils + + +@attr.s(auto_attribs=True) +class Settings(provider_utils.Settings): + """Freightcom Rest connection settings.""" + + # Add carrier specific API connection properties here + api_key: str + + # generic properties + id: str = None + test_mode: bool = False + carrier_id: str = "freightcom_rest" + account_country_code: str = None + metadata: dict = {} + config: dict = {} diff --git a/plugins/freightcom_rest/karrio/plugins/freightcom_rest/__init__.py b/plugins/freightcom_rest/karrio/plugins/freightcom_rest/__init__.py new file mode 100644 index 0000000..147073a --- /dev/null +++ b/plugins/freightcom_rest/karrio/plugins/freightcom_rest/__init__.py @@ -0,0 +1,29 @@ +from karrio.core.metadata import PluginMetadata + +from karrio.mappers.freightcom_rest.mapper import Mapper +from karrio.mappers.freightcom_rest.proxy import Proxy +from karrio.mappers.freightcom_rest.settings import Settings +import karrio.providers.freightcom_rest.units as units +import karrio.providers.freightcom_rest.utils as utils + + +# This METADATA object is used by Karrio to discover and register this plugin +# when loaded through Python entrypoints or local plugin directories. +# The entrypoint is defined in pyproject.toml under [project.entry-points."karrio.plugins"] +METADATA = PluginMetadata( + id="freightcom_rest", + label="Freightcom Rest", + description="Freightcom Rest shipping integration for Karrio", + # Integrations + Mapper=Mapper, + Proxy=Proxy, + Settings=Settings, + # Data Units + is_hub=True, + options=units.ShippingOption, + services=units.ShippingService, + connection_configs=utils.ConnectionConfig, + # Extra info + website="https://www.freightcom.com/", + documentation="https://developer.freightcom.com/", +) diff --git a/plugins/freightcom_rest/karrio/providers/freightcom_rest/__init__.py b/plugins/freightcom_rest/karrio/providers/freightcom_rest/__init__.py new file mode 100644 index 0000000..1a3d868 --- /dev/null +++ b/plugins/freightcom_rest/karrio/providers/freightcom_rest/__init__.py @@ -0,0 +1,16 @@ +"""Karrio Freightcom Rest provider imports.""" +from karrio.providers.freightcom_rest.utils import Settings +from karrio.providers.freightcom_rest.rate import ( + parse_rate_response, + rate_request, +) +from karrio.providers.freightcom_rest.shipment import ( + parse_shipment_cancel_response, + parse_shipment_response, + shipment_cancel_request, + shipment_request, +) +# from karrio.providers.freightcom_rest.tracking import ( +# parse_tracking_response, +# tracking_request, +# ) diff --git a/plugins/freightcom_rest/karrio/providers/freightcom_rest/error.py b/plugins/freightcom_rest/karrio/providers/freightcom_rest/error.py new file mode 100644 index 0000000..5d9543a --- /dev/null +++ b/plugins/freightcom_rest/karrio/providers/freightcom_rest/error.py @@ -0,0 +1,45 @@ +"""Karrio Freightcom Rest error parser.""" + +import typing +import karrio.lib as lib +import karrio.core.models as models +import karrio.providers.freightcom_rest.utils as provider_utils + + +def parse_error_response( + response: dict, + settings: provider_utils.Settings, + **kwargs, +) -> typing.List[models.Message]: + responses = response if isinstance(response, list) else [response] + + errors = [ + *[_ for _ in responses if _.get("message")], + ] + + return [ + models.Message( + carrier_id=settings.carrier_id, + carrier_name=settings.carrier_name, + message=( + error.get("message") + ": " + "; ".join(f"{k.replace('details.', '')}: {v}" for k, v in (error.get("details", {}) or error.get("data", {})).items()) + if (error.get("details", {}) or error.get("data", {})) + else error.get("message") + ), + level=_get_level(error), + details={ + **kwargs, + **(error.get('data', {})) + }, + ) + for error in errors + ] + + +def _get_level(error: dict, default_level: str = "error") -> str: + """Map Freightcom error response to standardized level. + + Freightcom API v2 does not provide a level field in error responses. + All error responses default to "error" level. + """ + return default_level diff --git a/plugins/freightcom_rest/karrio/providers/freightcom_rest/metadata.json b/plugins/freightcom_rest/karrio/providers/freightcom_rest/metadata.json new file mode 100644 index 0000000..861b80d --- /dev/null +++ b/plugins/freightcom_rest/karrio/providers/freightcom_rest/metadata.json @@ -0,0 +1,4671 @@ +{ + "PROD_SERVICES": [ + { + "id": "dhatttransfreightserviceinc-558.standard", + "carrier_name": "DHATT TRANSFREIGHT SERVICE INC.", + "service_name": "Standard" + }, + { + "id": "kelownaexpressfreightinc-570.standard", + "carrier_name": "Kelowna Express Freight Inc.", + "service_name": "Standard" + }, + { + "id": "metrofreightwaysltd-610.standard", + "carrier_name": "METRO FREIGHTWAYS LTD.", + "service_name": "Standard" + }, + { + "id": "scottfreight-190.standard", + "carrier_name": "Scott Freight", + "service_name": "Standard" + }, + { + "id": "tforcefreight-575.standard", + "carrier_name": "TForce Freight", + "service_name": "Standard" + }, + { + "id": "vkdeliverylinehaulltd-499.standard", + "carrier_name": "VK DELIVERY/LINEHAUL LTD", + "service_name": "Standard" + }, + { + "id": "customcourierco-469.standard", + "carrier_name": "Custom Courier Co.", + "service_name": "Standard" + }, + { + "id": "ecotransinc-396.standard", + "carrier_name": "ECOTRANS INC", + "service_name": "Standard" + }, + { + "id": "fedex.economy", + "carrier_name": "FedEx Freight", + "service_name": "Economy" + }, + { + "id": "fedex.standard", + "carrier_name": "FedEx Freight", + "service_name": "Priority" + }, + { + "id": "gtagsm-540.standard", + "carrier_name": "G.T.A GSM", + "service_name": "Standard" + }, + { + "id": "kindersleytransportusa-551.standard", + "carrier_name": "Kindersley Transport | USA", + "service_name": "Standard" + }, + { + "id": "lodestarlogistics-446.standard", + "carrier_name": "LODESTAR LOGISTICS", + "service_name": "Standard" + }, + { + "id": "loomis-express.express-0900", + "carrier_name": "Loomis", + "service_name": "Express 9:00" + }, + { + "id": "loomis-express.express-1200", + "carrier_name": "Loomis", + "service_name": "Express 12:00" + }, + { + "id": "loomis-express.express-1800", + "carrier_name": "Loomis", + "service_name": "Express 18:00" + }, + { + "id": "loomis-express.ground", + "carrier_name": "Loomis", + "service_name": "Ground" + }, + { + "id": "mrflatbedstransportinc-284.standard", + "carrier_name": "Mr Flatbeds Transport Inc", + "service_name": "Standard" + }, + { + "id": "bisontransport-278.standard", + "carrier_name": "Bison Transport", + "service_name": "Standard" + }, + { + "id": "canpar.ground", + "carrier_name": "Canpar", + "service_name": "Ground" + }, + { + "id": "canpar.international", + "carrier_name": "Canpar", + "service_name": "International" + }, + { + "id": "canpar.overnight", + "carrier_name": "Canpar", + "service_name": "Overnight" + }, + { + "id": "canpar.overnight-letter", + "carrier_name": "Canpar", + "service_name": "Overnight Letter" + }, + { + "id": "canpar.overnight-pak", + "carrier_name": "Canpar", + "service_name": "Overnight Pak" + }, + { + "id": "canpar.select", + "carrier_name": "Canpar", + "service_name": "Select" + }, + { + "id": "canpar.select-letter", + "carrier_name": "Canpar", + "service_name": "Select Letter" + }, + { + "id": "canpar.select-pak", + "carrier_name": "Canpar", + "service_name": "Select Pak" + }, + { + "id": "canpar.select-usa", + "carrier_name": "Canpar", + "service_name": "Select U.S.A." + }, + { + "id": "canpar.usa", + "carrier_name": "Canpar", + "service_name": "U.S.A." + }, + { + "id": "canpar.usa-Letter", + "carrier_name": "Canpar", + "service_name": "U.S.A. Letter" + }, + { + "id": "canpar.usa-pak", + "carrier_name": "Canpar", + "service_name": "U.S.A. Pak" + }, + { + "id": "csa.standard", + "carrier_name": "CSA", + "service_name": "Standard" + }, + { + "id": "ettransportgroup-337.standard", + "carrier_name": "ET Transport Group", + "service_name": "Standard" + }, + { + "id": "guardiumlogisticsltd-608.standard", + "carrier_name": "Guardium Logistics Ltd.", + "service_name": "Standard" + }, + { + "id": "keltictransportation-465.standard", + "carrier_name": "Keltic Transportation", + "service_name": "Standard" + }, + { + "id": "kjlxpressinc-583.standard", + "carrier_name": "KJL Xpress Inc.", + "service_name": "Standard" + }, + { + "id": "newpennmotorexpress-445.standard", + "carrier_name": "New Penn Motor Express", + "service_name": "Standard" + }, + { + "id": "bridgepointlogisticsltd-524.standard", + "carrier_name": "BRIDGEPOINT LOGISTICS LTD", + "service_name": "Standard" + }, + { + "id": "canadapost.domestic", + "carrier_name": "Canada Post", + "service_name": "Domestic" + }, + { + "id": "canadapost.expedited-parcel", + "carrier_name": "Canada Post", + "service_name": "Expedited Parcel" + }, + { + "id": "canadapost.expedited-parcel-usa", + "carrier_name": "Canada Post", + "service_name": "Expedited Parcel USA" + }, + { + "id": "canadapost.international", + "carrier_name": "Canada Post", + "service_name": "International" + }, + { + "id": "canadapost.international-parcel-air", + "carrier_name": "Canada Post", + "service_name": "International Parcel Air" + }, + { + "id": "canadapost.international-parcel-surface", + "carrier_name": "Canada Post", + "service_name": "International Parcel Surface" + }, + { + "id": "canadapost.priority", + "carrier_name": "Canada Post", + "service_name": "Priority" + }, + { + "id": "canadapost.priority-ww-envelope-international", + "carrier_name": "Canada Post", + "service_name": "Priority Worldwide envelope INT’L" + }, + { + "id": "canadapost.priority-ww-envelope-usa", + "carrier_name": "Canada Post", + "service_name": "Priority Worldwide envelope USA" + }, + { + "id": "canadapost.priority-ww-pak-international", + "carrier_name": "Canada Post", + "service_name": "Priority Worldwide pak INT’L" + }, + { + "id": "canadapost.priority-ww-pak-usa", + "carrier_name": "Canada Post", + "service_name": "Priority Worldwide pak USA" + }, + { + "id": "canadapost.priority-ww-parcel-international", + "carrier_name": "Canada Post", + "service_name": "Priority Worldwide parcel INT’L" + }, + { + "id": "canadapost.priority-ww-parcel-usa", + "carrier_name": "Canada Post", + "service_name": "Priority Worldwide parcel USA" + }, + { + "id": "canadapost.regular-parcel", + "carrier_name": "Canada Post", + "service_name": "Regular Parcel" + }, + { + "id": "canadapost.small-packet-international-air", + "carrier_name": "Canada Post", + "service_name": "Small Packet International Air" + }, + { + "id": "canadapost.small-packet-international-surface", + "carrier_name": "Canada Post", + "service_name": "Small Packet International Surface" + }, + { + "id": "canadapost.small-packet-usa-air", + "carrier_name": "Canada Post", + "service_name": "Small Packet USA Air" + }, + { + "id": "canadapost.tracked-packet-international", + "carrier_name": "Canada Post", + "service_name": "Tracked Packet - International" + }, + { + "id": "canadapost.tracked-packet-usa", + "carrier_name": "Canada Post", + "service_name": "Tracked Packet USA" + }, + { + "id": "canadapost.xpresspost", + "carrier_name": "Canada Post", + "service_name": "Xpresspost" + }, + { + "id": "canadapost.xpresspost-international", + "carrier_name": "Canada Post", + "service_name": "Xpresspost International" + }, + { + "id": "canadapost.xpresspost-usa", + "carrier_name": "Canada Post", + "service_name": "Xpresspost USA" + }, + { + "id": "gardewine.standard", + "carrier_name": "Gardewine", + "service_name": "Standard" + }, + { + "id": "giantleaftruckinginc-556.standard", + "carrier_name": "GIANT LEAF TRUCKING INC.", + "service_name": "Standard" + }, + { + "id": "purolatorfreight.standard", + "carrier_name": "Purolator Freight", + "service_name": "Standard" + }, + { + "id": "swyft.nextday", + "carrier_name": "Swyft", + "service_name": "Next Day" + }, + { + "id": "swyft.sameday", + "carrier_name": "Swyft", + "service_name": "Same Day" + }, + { + "id": "usps.ground-advantage", + "carrier_name": "USPS", + "service_name": "Ground Advantage" + }, + { + "id": "usps.priority-mail", + "carrier_name": "USPS", + "service_name": "Priority Mail" + }, + { + "id": "usps.priority-mail-express", + "carrier_name": "USPS", + "service_name": "Priority Mail Express" + }, + { + "id": "xwestcarriersinc-547.standard", + "carrier_name": "X West Carriers Inc", + "service_name": "Standard" + }, + { + "id": "paulsfreightline-400.standard", + "carrier_name": "PAULS FREIGHTLINE", + "service_name": "Standard" + }, + { + "id": "ppgroadlinesinc-250.standard", + "carrier_name": "PPG ROADLINES INC.", + "service_name": "Standard" + }, + { + "id": "transportgilleslavigne-592.standard", + "carrier_name": "Transport Gilles Lavigne", + "service_name": "Standard" + }, + { + "id": "xpologistics-265.standard", + "carrier_name": "XPO Logistics", + "service_name": "Standard" + }, + { + "id": "upscourier-162.standard", + "carrier_name": "UPS Courier", + "service_name": "Standard" + }, + { + "id": "94222528qubecincdbagroupefmj-626.standard", + "carrier_name": "9422-2528 Québec Inc. DBA Groupe FMJ", + "service_name": "Standard" + }, + { + "id": "one.standard", + "carrier_name": "ONE Transportation", + "service_name": "Standard" + }, + { + "id": "readygotransportinc-435.standard", + "carrier_name": "READY GO TRANSPORT INC.", + "service_name": "Standard" + }, + { + "id": "streetkingtransportationltd-557.standard", + "carrier_name": "STREET KING TRANSPORTATION LTD", + "service_name": "Standard" + }, + { + "id": "martinroytransport-310.standard", + "carrier_name": "Martin Roy Transport", + "service_name": "Standard" + }, + { + "id": "sameday.2-man-delivery-to-entrance", + "carrier_name": "Day & Ross Commerce Solutions", + "service_name": "Delivery to Entrance - 2 Person" + }, + { + "id": "sameday.2-man-delivery-to-room-of-choice", + "carrier_name": "Day & Ross Commerce Solutions", + "service_name": "Delivery to Room of Choice - 2 Person" + }, + { + "id": "sameday.2-man-delivery-to-room-of-choice-with-debris-removal", + "carrier_name": "Day & Ross Commerce Solutions", + "service_name": "Two-person delivery to room of choice with debris removal" + }, + { + "id": "sameday.dayr-ecom-urgent-pac", + "carrier_name": "Day & Ross Commerce Solutions", + "service_name": "eCommerce Urgent Pak" + }, + { + "id": "sameday.delivery-to-entrance", + "carrier_name": "Day & Ross Commerce Solutions", + "service_name": "Delivery to Entrance" + }, + { + "id": "sameday.delivery-to-room-of-choice", + "carrier_name": "Day & Ross Commerce Solutions", + "service_name": "Delivery to Room of Choice" + }, + { + "id": "sameday.delivery-to-room-of-choice-with-debris-removal", + "carrier_name": "Day & Ross Commerce Solutions", + "service_name": "Delivery to Room of Choice with Debris Removal" + }, + { + "id": "sameday.ground-daynross-road", + "carrier_name": "Day & Ross Commerce Solutions", + "service_name": "Ground - Road" + }, + { + "id": "sameday.next-day-before-5pm", + "carrier_name": "Day & Ross Commerce Solutions", + "service_name": "Next Day Delivery by 5 PM" + }, + { + "id": "sameday.next-day-before-9am", + "carrier_name": "Day & Ross Commerce Solutions", + "service_name": "Next Day Delivery by 9 AM" + }, + { + "id": "sameday.next-day-delivery-before-noon", + "carrier_name": "Day & Ross Commerce Solutions", + "service_name": "Next Day Delivery Before Noon" + }, + { + "id": "searcytrucking-462.standard", + "carrier_name": "Searcy Trucking", + "service_name": "Standard" + }, + { + "id": "transkidinc-453.standard", + "carrier_name": "Transkid Inc", + "service_name": "Standard" + }, + { + "id": "amatransinc-251.standard", + "carrier_name": "AMA Trans Inc", + "service_name": "Standard" + }, + { + "id": "checkercourier-276.standard", + "carrier_name": "Checker Courier", + "service_name": "Standard" + }, + { + "id": "empiretransportltd-413.standard", + "carrier_name": "EMPIRE TRANSPORT LTD", + "service_name": "Standard" + }, + { + "id": "flashtransport-388.standard", + "carrier_name": "Flash Transport", + "service_name": "Standard" + }, + { + "id": "wce.standard", + "carrier_name": "WCE", + "service_name": "Intermodal" + }, + { + "id": "xpoglobalforwardinginc-416.standard", + "carrier_name": "XPO GLOBAL FORWARDING INC", + "service_name": "Standard" + }, + { + "id": "kjstransport-623.standard", + "carrier_name": "KJS Transport", + "service_name": "Standard" + }, + { + "id": "ltlexpressfreight-459.standard", + "carrier_name": "LTL EXPRESS FREIGHT", + "service_name": "Standard" + }, + { + "id": "overland.standard", + "carrier_name": "Overland", + "service_name": "Standard" + }, + { + "id": "dbgtrucking-132.standard", + "carrier_name": "DBG TRUCKING ", + "service_name": "Standard" + }, + { + "id": "dhillondhillontransportltd-528.standard", + "carrier_name": "Dhillon & Dhillon Transport Ltd.", + "service_name": "Standard" + }, + { + "id": "freightboy-565.standard", + "carrier_name": "FREIGHT BOY", + "service_name": "Standard" + }, + { + "id": "highrisetransport-545.standard", + "carrier_name": "HIGH RISE TRANSPORT", + "service_name": "Standard" + }, + { + "id": "yrcfreight-330.standard", + "carrier_name": "YRC Freight", + "service_name": "Standard" + }, + { + "id": "caledoncouriers-294.standard", + "carrier_name": "Caledon Couriers", + "service_name": "Standard" + }, + { + "id": "proactivetransportcoproactivesupplycahinsolutionsinc-471.standard", + "carrier_name": "PROACTIVE TRANSPORT C/O PROACTIVE SUPPLYCAHIN SOLUTIONS INC.", + "service_name": "Standard" + }, + { + "id": "roadtrainexpress-433.standard", + "carrier_name": "ROAD TRAIN EXPRESS ", + "service_name": "Standard" + }, + { + "id": "sablemarcoinc-509.standard", + "carrier_name": "Sable Marco inc.", + "service_name": "Standard" + }, + { + "id": "here2therefreightmanagementinc-629.standard", + "carrier_name": "Here 2 There Freight Management Inc", + "service_name": "Standard" + }, + { + "id": "thompsonemergencyfreightsystems-277.standard", + "carrier_name": "Thompson Emergency Freight Systems", + "service_name": "Standard" + }, + { + "id": "dhl-ecomm.packet-international", + "carrier_name": "DHL eCommerce", + "service_name": "Package International" + }, + { + "id": "dhl-ecomm.parcel-expedited", + "carrier_name": "DHL eCommerce", + "service_name": "Parcel Expedited" + }, + { + "id": "dhl-ecomm.parcel-expedited-max", + "carrier_name": "DHL eCommerce", + "service_name": "Parcel Expedited Max" + }, + { + "id": "dhl-ecomm.parcel-ground", + "carrier_name": "DHL eCommerce", + "service_name": "Parcel Ground" + }, + { + "id": "dhl-ecomm.parcel-international-direct", + "carrier_name": "DHL eCommerce", + "service_name": "Parcel International Direct" + }, + { + "id": "dhl-ecomm.parcel-international-direct-priority", + "carrier_name": "DHL eCommerce", + "service_name": "Parcel International Direct Priority" + }, + { + "id": "dhl-ecomm.parcel-international-direct-standard", + "carrier_name": "DHL eCommerce", + "service_name": "Parcel International Direct Standard" + }, + { + "id": "dhl-ecomm.parcel-international-standard", + "carrier_name": "DHL eCommerce", + "service_name": "Parcel International Standard" + }, + { + "id": "excel-transport.standard", + "carrier_name": "Excel Transportation", + "service_name": "Standard" + }, + { + "id": "newpenn.standard", + "carrier_name": "New Penn", + "service_name": "Standard" + }, + { + "id": "bettertrucks.ddu", + "carrier_name": "Better Trucks", + "service_name": "DDU" + }, + { + "id": "bettertrucks.express", + "carrier_name": "Better Trucks", + "service_name": "Express" + }, + { + "id": "bettertrucks.next_day", + "carrier_name": "Better Trucks", + "service_name": "Next Day" + }, + { + "id": "bettertrucks.same_day", + "carrier_name": "Better Trucks", + "service_name": "Same Day" + }, + { + "id": "boxknight.next-day", + "carrier_name": "BoxKnight", + "service_name": "Next Day" + }, + { + "id": "boxknight.sameday", + "carrier_name": "BoxKnight", + "service_name": "Same Day" + }, + { + "id": "cranestransport-555.standard", + "carrier_name": "CRANES TRANSPORT", + "service_name": "Standard" + }, + { + "id": "daynross.cs", + "carrier_name": "Day & Ross", + "service_name": "CS" + }, + { + "id": "daynross.domestic-standard", + "carrier_name": "Day & Ross", + "service_name": "Domestic Standard" + }, + { + "id": "daynross.transborder-standard", + "carrier_name": "Day & Ross", + "service_name": "Transborder Standard" + }, + { + "id": "loadsafecrossborderfreightinc-493.standard", + "carrier_name": "LOADSAFE CROSSBORDER FREIGHT INC.", + "service_name": "Standard" + }, + { + "id": "maritimeontario-267.standard", + "carrier_name": "Maritime-Ontario", + "service_name": "Standard" + }, + { + "id": "saiamotorfreightinc-139.standard", + "carrier_name": "Saia Motor Freight Inc", + "service_name": "Standard" + }, + { + "id": "speedytransport-153.standard", + "carrier_name": "Speedy Transport", + "service_name": "Standard" + }, + { + "id": "bakshbroscartageinc-485.standard", + "carrier_name": "BAKSH BROS. CARTAGE INC", + "service_name": "Standard" + }, + { + "id": "estesexpresslines-274.standard", + "carrier_name": "ESTES Express Lines", + "service_name": "Standard" + }, + { + "id": "highenergytransportinc-533.standard", + "carrier_name": "High Energy Transport Inc", + "service_name": "Standard" + }, + { + "id": "kriskaytrucklinesinc-443.standard", + "carrier_name": "Kris Kay Truck Lines Inc", + "service_name": "Standard" + }, + { + "id": "rollsright-245.standard", + "carrier_name": "Rolls Right", + "service_name": "Standard" + }, + { + "id": "sunshinecoastlogisticsinc-614.standard", + "carrier_name": "Sunshine Coast Logistics Inc", + "service_name": "Standard" + }, + { + "id": "uppaltransportltddbabluewatertrucking-615.standard", + "carrier_name": "Uppal Transport Ltd dba Bluewater Trucking", + "service_name": "Standard" + }, + { + "id": "dayrossfreightrl-529.standard", + "carrier_name": "Day & Ross Freight | R+L", + "service_name": "Standard" + }, + { + "id": "fgmtrucklines-235.standard", + "carrier_name": "FGM Trucklines", + "service_name": "Standard" + }, + { + "id": "kdrtrucklinesinc-627.standard", + "carrier_name": "KDR Trucklines Inc", + "service_name": "Standard" + }, + { + "id": "psrlogisticsinc-506.standard", + "carrier_name": "PSR LOGISTICS INC.", + "service_name": "Standard" + }, + { + "id": "northplusgroupofcompanies-568.standard", + "carrier_name": "NORTH PLUS GROUP OF COMPANIES", + "service_name": "Standard" + }, + { + "id": "swiftdeliverysystems-268.standard", + "carrier_name": "Swift Delivery Systems", + "service_name": "Standard" + }, + { + "id": "vitran.maxx", + "carrier_name": "Vitran", + "service_name": "Maxx" + }, + { + "id": "vitran.priority", + "carrier_name": "Vitran", + "service_name": "Priority" + }, + { + "id": "vitran.regular", + "carrier_name": "Vitran", + "service_name": "Regular" + }, + { + "id": "fastnflowlogistics-487.standard", + "carrier_name": "FAST N FLOW LOGISTICS", + "service_name": "Standard" + }, + { + "id": "fpifreightpartnersinternationalinc-458.standard", + "carrier_name": "FPI - FREIGHT PARTNERS INTERNATIONAL INC.", + "service_name": "Standard" + }, + { + "id": "gsm.air-skip", + "carrier_name": "GTA GSM", + "service_name": "AirSkip" + }, + { + "id": "gsm.air-skip-plus", + "carrier_name": "GTA GSM", + "service_name": "AirSkip+" + }, + { + "id": "gsm.armed-secure-air", + "carrier_name": "GTA GSM", + "service_name": "Armed Secure Air" + }, + { + "id": "gsm.armed-secure-ground", + "carrier_name": "GTA GSM", + "service_name": "Armed Secure Ground" + }, + { + "id": "gsm.ground", + "carrier_name": "GTA GSM", + "service_name": "Ground" + }, + { + "id": "gsm.secure-air", + "carrier_name": "GTA GSM", + "service_name": "Secure Air" + }, + { + "id": "gsm.secure-ground", + "carrier_name": "GTA GSM", + "service_name": "Secure Ground" + }, + { + "id": "gsm.zone-skip", + "carrier_name": "GTA GSM", + "service_name": "ZoneSkip" + }, + { + "id": "gsm.zone-skip-plus", + "carrier_name": "GTA GSM", + "service_name": "ZoneSkip+" + }, + { + "id": "mcarthurexpress-503.standard", + "carrier_name": "Mcarthur Express", + "service_name": "Standard" + }, + { + "id": "himmatpuralogisticsinc-473.standard", + "carrier_name": "HIMMATPURA LOGISTICS INC", + "service_name": "Standard" + }, + { + "id": "proformanceintermodalinc-489.standard", + "carrier_name": "Pro-Formance Intermodal Inc.", + "service_name": "Standard" + }, + { + "id": "whistlercourier-621.standard", + "carrier_name": "Whistler Courier", + "service_name": "Standard" + }, + { + "id": "armourtransportationsystems-562.standard", + "carrier_name": "Armour Transportation Systems", + "service_name": "Standard" + }, + { + "id": "fastfrategroup-498.standard", + "carrier_name": "Fastfrate Group", + "service_name": "Standard" + }, + { + "id": "glotransports-530.standard", + "carrier_name": "GLO TRANSPORTS", + "service_name": "Standard" + }, + { + "id": "gsdirect-292.standard", + "carrier_name": "G&S Direct", + "service_name": "Standard" + }, + { + "id": "gls.ground", + "carrier_name": "GLS", + "service_name": "Ground" + }, + { + "id": "lighthousetransportationinc-619.standard", + "carrier_name": "LIGHTHOUSE TRANSPORTATION INC.", + "service_name": "Standard" + }, + { + "id": "pbtransport-507.standard", + "carrier_name": "P&B TRANSPORT", + "service_name": "Standard" + }, + { + "id": "servicestarfreightways-441.standard", + "carrier_name": "ServiceStar Freightways", + "service_name": "Standard" + }, + { + "id": "tforcefreight.tforcefreight-guarnteed", + "carrier_name": "TForceFreight", + "service_name": "TForceFreight Guaranteed" + }, + { + "id": "tforcefreight.tforcefreight-ltl", + "carrier_name": "TForceFreight", + "service_name": "TForceFreight LTL" + }, + { + "id": "tforcefreight.tforcefreight-standard", + "carrier_name": "TForceFreight", + "service_name": "TForceFreight Standard" + }, + { + "id": "zipcourier-468.standard", + "carrier_name": "Zip Courier", + "service_name": "Standard" + }, + { + "id": "cmwexpress-537.standard", + "carrier_name": "CMW Express", + "service_name": "Standard" + }, + { + "id": "ktslogisticsinc-620.standard", + "carrier_name": "KTS LOGISTICS INC", + "service_name": "Standard" + }, + { + "id": "raymanmotorfreight-357.standard", + "carrier_name": "Rayman Motor Freight", + "service_name": "Standard" + }, + { + "id": "safeloadtransportltd-510.standard", + "carrier_name": "SAFE LOAD TRANSPORT LTD. ", + "service_name": "Standard" + }, + { + "id": "milyardgroupinc-391.standard", + "carrier_name": "Milyard Group Inc.", + "service_name": "Standard" + }, + { + "id": "minimax.standard", + "carrier_name": "Minimax", + "service_name": "Standard" + }, + { + "id": "suretrackgroup-369.standard", + "carrier_name": "SureTrack Group", + "service_name": "Standard" + }, + { + "id": "ctmatransport-535.standard", + "carrier_name": "CTMA Transport", + "service_name": "Standard" + }, + { + "id": "kawarthacourier-425.standard", + "carrier_name": "Kawartha Courier", + "service_name": "Standard" + }, + { + "id": "kepatransportinc-516.standard", + "carrier_name": "KEPA Transport Inc", + "service_name": "Standard" + }, + { + "id": "kindersley-freight.domestic-expedited", + "carrier_name": "Kindersley Transport", + "service_name": "Domestic Expedited" + }, + { + "id": "kindersley-freight.domestic-rail", + "carrier_name": "Kindersley Transport", + "service_name": "Domestic Rail" + }, + { + "id": "kindersley-freight.domestic-road", + "carrier_name": "Kindersley Transport", + "service_name": "Domestic Road" + }, + { + "id": "kindersley-freight.transborder", + "carrier_name": "Kindersley Transport", + "service_name": "Transborder" + }, + { + "id": "boeingtruckinginc-593.standard", + "carrier_name": "Boeing Trucking Inc. ", + "service_name": "Standard" + }, + { + "id": "freightcom-134.standard", + "carrier_name": "Freightcom", + "service_name": "Standard" + }, + { + "id": "groupemorneau-599.standard", + "carrier_name": "Groupe Morneau", + "service_name": "Standard" + }, + { + "id": "xpofreightbrokerage-280.standard", + "carrier_name": "XPO Freight Brokerage", + "service_name": "Standard" + }, + { + "id": "aalfatransportationcorporation-596.standard", + "carrier_name": "A ALFA TRANSPORTATION CORPORATION", + "service_name": "Standard" + }, + { + "id": "barriedirecttransportation-428.standard", + "carrier_name": "Barrie Direct Transportation ", + "service_name": "Standard" + }, + { + "id": "fastfrate.express", + "carrier_name": "Fastfrate", + "service_name": "Express" + }, + { + "id": "fastfrate.standard", + "carrier_name": "Fastfrate", + "service_name": "Standard" + }, + { + "id": "flatouttransportation-566.standard", + "carrier_name": "Flat Out Transportation", + "service_name": "Standard" + }, + { + "id": "reddaway.guaranteed-3pm", + "carrier_name": "Reddaway", + "service_name": "Guaranteed Delivery Before 3:30 PM" + }, + { + "id": "reddaway.guaranteed-9am", + "carrier_name": "Reddaway", + "service_name": "Guaranteed Delivery Before 9 AM" + }, + { + "id": "reddaway.guaranteed-noon", + "carrier_name": "Reddaway", + "service_name": "Guaranteed Delivery Before 12:00 PM (noon)" + }, + { + "id": "reddaway.guaranteed-weekend", + "carrier_name": "Reddaway", + "service_name": "Guaranteed Weekend" + }, + { + "id": "reddaway.guarenteed-9am", + "carrier_name": "Reddaway", + "service_name": "Guaranteed Delivery Before 9 AM" + }, + { + "id": "reddaway.guarenteed-noon", + "carrier_name": "Reddaway", + "service_name": "Guaranteed Delivery Before 12:00 PM (noon)" + }, + { + "id": "reddaway.interline", + "carrier_name": "Reddaway", + "service_name": "Interline Delivery" + }, + { + "id": "reddaway.multi-hour-window", + "carrier_name": "Reddaway", + "service_name": "Guaranteed Window - Multi-Hour Window" + }, + { + "id": "reddaway.regional-delivery", + "carrier_name": "Reddaway", + "service_name": "Regional Delivery" + }, + { + "id": "reddaway.single-hour-window", + "carrier_name": "Reddaway", + "service_name": "Guaranteed Window - Single-hour Window" + }, + { + "id": "reddaway.single-or-multi-day", + "carrier_name": "Reddaway", + "service_name": "Guaranteed Window - Single or Multi-day Window" + }, + { + "id": "reddaway.standard", + "carrier_name": "Reddaway", + "service_name": "Standard" + }, + { + "id": "urbanvalleytransport-526.standard", + "carrier_name": "Urban Valley Transport ", + "service_name": "Standard" + }, + { + "id": "xrpexpress-618.standard", + "carrier_name": "XRP Express", + "service_name": "Standard" + }, + { + "id": "actionforcetransportltd-363.standard", + "carrier_name": "Action Force Transport Ltd", + "service_name": "Standard" + }, + { + "id": "airpro-255.standard", + "carrier_name": "Air Pro", + "service_name": "Standard" + }, + { + "id": "asslafreightinc-588.standard", + "carrier_name": "ASSLA FREIGHT INC.", + "service_name": "Standard" + }, + { + "id": "dhlexpress.domestic-express", + "carrier_name": "DHL Express", + "service_name": "Domestic Express" + }, + { + "id": "dhlexpress.domestic-express1030am", + "carrier_name": "DHL Express", + "service_name": "Domestic Express 10:30" + }, + { + "id": "dhlexpress.domestic-express9am", + "carrier_name": "DHL Express", + "service_name": "Domestic Express 9:00" + }, + { + "id": "dhlexpress.economy-select", + "carrier_name": "DHL Express", + "service_name": "Economy Select" + }, + { + "id": "dhlexpress.express-easy", + "carrier_name": "DHL Express", + "service_name": "Express Easy" + }, + { + "id": "dhlexpress.express-worldwide", + "carrier_name": "DHL Express", + "service_name": "Express Worldwide" + }, + { + "id": "dhlexpress.express1030am", + "carrier_name": "DHL Express", + "service_name": "Express 10:30" + }, + { + "id": "dhlexpress.express12pm", + "carrier_name": "DHL Express", + "service_name": "Express 12:00" + }, + { + "id": "dhlexpress.express9am", + "carrier_name": "DHL Express", + "service_name": "Express 9:00" + }, + { + "id": "intelcom.standard", + "carrier_name": "Intelcom", + "service_name": "Standard" + }, + { + "id": "jardinetransport-217.standard", + "carrier_name": "JARDINE TRANSPORT", + "service_name": "Standard" + }, + { + "id": "sutcocontractingltddbasutcotransportationspecialist-552.standard", + "carrier_name": "SUTCO CONTRACTING LTD./DBA SUTCO TRANSPORTATION SPECIALIST", + "service_name": "Standard" + }, + { + "id": "whistler99courier-621.standard", + "carrier_name": "Whistler 99 Courier", + "service_name": "Standard" + }, + { + "id": "apps.intermodal", + "carrier_name": "APPS", + "service_name": "Intermodal" + }, + { + "id": "apps.standard", + "carrier_name": "APPS", + "service_name": "Standard" + }, + { + "id": "dbgtrucking-527.standard", + "carrier_name": "DBG TRUCKING ", + "service_name": "Standard" + }, + { + "id": "deliverytechinc-442.standard", + "carrier_name": "Delivery Tech Inc", + "service_name": "Standard" + }, + { + "id": "hiway.standard", + "carrier_name": "Hi-Way9", + "service_name": "Standard" + }, + { + "id": "speedxtransport-319.standard", + "carrier_name": "SpeedX Transport", + "service_name": "Standard" + }, + { + "id": "alldaystransportltd-449.standard", + "carrier_name": "ALL DAYS TRANSPORT LTD", + "service_name": "Standard" + }, + { + "id": "dhlexpress-230.standard", + "carrier_name": "DHL Express", + "service_name": "Standard" + }, + { + "id": "freightcomfulfilment-429.standard", + "carrier_name": "Freightcom Fulfilment", + "service_name": "Standard" + }, + { + "id": "makfreightlinesltd-422.standard", + "carrier_name": "MAK FREIGHT LINES LTD", + "service_name": "Standard" + }, + { + "id": "nationex.standard", + "carrier_name": "Nationex", + "service_name": "Standard" + }, + { + "id": "pacemarathon-611.standard", + "carrier_name": "Pace Marathon", + "service_name": "Standard" + }, + { + "id": "transprofreightsystemsltd-394.standard", + "carrier_name": "Transpro Freight Systems Ltd", + "service_name": "Standard" + }, + { + "id": "diamonddelivery-275.standard", + "carrier_name": "Diamond Delivery", + "service_name": "Standard" + }, + { + "id": "driverdirect-586.standard", + "carrier_name": "Driver Direct", + "service_name": "Standard" + }, + { + "id": "inetexpress-521.standard", + "carrier_name": "I-Net Express", + "service_name": "Standard" + }, + { + "id": "morneau.standard", + "carrier_name": "Morneau Transport", + "service_name": "Standard" + }, + { + "id": "wtmlogisticsltd-579.standard", + "carrier_name": "WTM LOGISTICS LTD.", + "service_name": "Standard" + }, + { + "id": "ab-courier.canada-1030am", + "carrier_name": "A&B Courier", + "service_name": "Canada 10:30am" + }, + { + "id": "ab-courier.canada-930am", + "carrier_name": "A&B Courier", + "service_name": "Canada 9:30am" + }, + { + "id": "ab-courier.canada-ground", + "carrier_name": "A&B Courier", + "service_name": "Canada Ground" + }, + { + "id": "ab-courier.canada-overnight", + "carrier_name": "A&B Courier", + "service_name": "Canada Overnight" + }, + { + "id": "ab-courier.direct", + "carrier_name": "A&B Courier", + "service_name": "Direct" + }, + { + "id": "ab-courier.four-hour", + "carrier_name": "A&B Courier", + "service_name": "Four Hour" + }, + { + "id": "ab-courier.rush", + "carrier_name": "A&B Courier", + "service_name": "Rush" + }, + { + "id": "ab-courier.sameday", + "carrier_name": "A&B Courier", + "service_name": "Sameday" + }, + { + "id": "ab-courier.usa-ground", + "carrier_name": "A&B Courier", + "service_name": "USA Ground" + }, + { + "id": "daytonfreight-581.standard", + "carrier_name": "Dayton Freight", + "service_name": "Standard" + }, + { + "id": "mtslogisticsint-307.standard", + "carrier_name": "MTS Logistics Int.", + "service_name": "Standard" + }, + { + "id": "quikxtransportationinc-490.standard", + "carrier_name": "Quik X Transportation Inc.", + "service_name": "Standard" + }, + { + "id": "sabbystransportinc-574.standard", + "carrier_name": "Sabby?s Transport Inc ", + "service_name": "Standard" + }, + { + "id": "spring-gds.spring-direct", + "carrier_name": "Spring GDS", + "service_name": "Spring Direct" + }, + { + "id": "spring-gds.spring-gateway-parcel", + "carrier_name": "Spring GDS", + "service_name": "Spring Gateway Parcel" + }, + { + "id": "spring-gds.spring-packet-plus", + "carrier_name": "Spring GDS", + "service_name": "Spring Packet Plus Registered" + }, + { + "id": "spring-gds.spring-packet-tracked", + "carrier_name": "Spring GDS", + "service_name": "Spring Packet Tracked" + }, + { + "id": "spring-gds.spring-packet-untracked", + "carrier_name": "Spring GDS", + "service_name": "Spring Packet Untracked" + }, + { + "id": "lplogisticsinc-622.standard", + "carrier_name": "LP Logistics Inc", + "service_name": "Standard" + }, + { + "id": "pctransport-256.standard", + "carrier_name": "PC Transport", + "service_name": "Standard" + }, + { + "id": "peaktransport-497.standard", + "carrier_name": "PEAK TRANSPORT", + "service_name": "Standard" + }, + { + "id": "polaristransportation-188.standard", + "carrier_name": "Polaris Transportation ", + "service_name": "Standard" + }, + { + "id": "uts-470.standard", + "carrier_name": "UTS ", + "service_name": "Standard" + }, + { + "id": "dhillonsnationalservices-531.standard", + "carrier_name": "DHILLON'S NATIONAL SERVICES", + "service_name": "Standard" + }, + { + "id": "gryphontransportationinc-563.standard", + "carrier_name": "GRYPHON TRANSPORTATION INC.", + "service_name": "Standard" + }, + { + "id": "holmesfreight-346.standard", + "carrier_name": "Holmes Freight", + "service_name": "Standard" + }, + { + "id": "transporttransramexpressinc-370.standard", + "carrier_name": "Transport Transram Express Inc", + "service_name": "Standard" + }, + { + "id": "overlandwestfreightlines-253.standard", + "carrier_name": "Overland West Freight Lines", + "service_name": "Standard" + }, + { + "id": "cbstealthexpressinc-520.standard", + "carrier_name": "C.B. Stealth Express Inc.", + "service_name": "Standard" + }, + { + "id": "geowavelogistics-604.standard", + "carrier_name": "Geo Wave Logistics", + "service_name": "Standard" + }, + { + "id": "heartlandtransportltd-624.standard", + "carrier_name": "Heartland Transport Ltd.", + "service_name": "Standard" + }, + { + "id": "kindersley-courier.standard", + "carrier_name": "Kindersley Transport", + "service_name": "Standard" + }, + { + "id": "diamondtransportlogistics-477.standard", + "carrier_name": "Diamond Transport Logistics", + "service_name": "Standard" + }, + { + "id": "maritime.dry", + "carrier_name": "Maritime", + "service_name": "Dry" + }, + { + "id": "maritime.frozen", + "carrier_name": "Maritime", + "service_name": "Reefer" + }, + { + "id": "maritime.heat", + "carrier_name": "Maritime", + "service_name": "Heat" + }, + { + "id": "proactiftransportinc-460.standard", + "carrier_name": "ProActif Transport Inc.", + "service_name": "Standard" + }, + { + "id": "rightservicerightchoicetransportationandwarehouse-578.standard", + "carrier_name": "RIGHT SERVICE RIGHT CHOICE TRANSPORTATION AND WAREHOUSE", + "service_name": "Standard" + }, + { + "id": "averittexpress-576.standard", + "carrier_name": "Averitt Express", + "service_name": "Standard" + }, + { + "id": "bitzertruckingltd-591.standard", + "carrier_name": "Bitzer Trucking Ltd", + "service_name": "Standard" + }, + { + "id": "cctcanada-211.standard", + "carrier_name": "CCT Canada", + "service_name": "Standard" + }, + { + "id": "centraltransport-427.standard", + "carrier_name": "Central Transport", + "service_name": "Standard" + }, + { + "id": "saraixpresstruckinginc-336.standard", + "carrier_name": "SARAI XPRESS TRUCKING INC", + "service_name": "Standard" + }, + { + "id": "onroutefreightservicesinc-598.standard", + "carrier_name": "ONROUTE FREIGHT SERVICES INC.", + "service_name": "Standard" + }, + { + "id": "ontargettransportation-283.standard", + "carrier_name": "On Target Transportation", + "service_name": "Standard" + }, + { + "id": "sunstarhaulersinc-590.standard", + "carrier_name": "SUNSTAR HAULERS INC.", + "service_name": "Standard" + }, + { + "id": "transportecononord-495.standard", + "carrier_name": "Transport Econo Nord", + "service_name": "Standard" + }, + { + "id": "allspeed-432.standard", + "carrier_name": "All Speed", + "service_name": "Standard" + }, + { + "id": "canedatransport-189.standard", + "carrier_name": "Caneda Transport", + "service_name": "Standard" + }, + { + "id": "globaltranslogisticsltd-464.standard", + "carrier_name": "GLOBAL TRANS & LOGISTICS LTD", + "service_name": "Standard" + }, + { + "id": "holland.guaranteed-330-pm", + "carrier_name": "Holland Freight", + "service_name": "Guaranteed by 3:30 PM" + }, + { + "id": "holland.guaranteed-9-am", + "carrier_name": "Holland Freight", + "service_name": "Guaranteed by 9:00 AM" + }, + { + "id": "holland.guaranteed-day", + "carrier_name": "Holland Freight", + "service_name": "Guaranteed Day" + }, + { + "id": "holland.guaranteed-hour", + "carrier_name": "Holland Freight", + "service_name": "Guaranteed Hour" + }, + { + "id": "holland.guaranteed-multi-hour", + "carrier_name": "Holland Freight", + "service_name": "Guaranteed Multi Hour" + }, + { + "id": "holland.guaranteed-noon", + "carrier_name": "Holland Freight", + "service_name": "Guaranteed by Noon" + }, + { + "id": "holland.guaranteed-weekend", + "carrier_name": "Holland Freight", + "service_name": "Guaranteed Weekend" + }, + { + "id": "holland.inter-regional", + "carrier_name": "Holland Freight", + "service_name": "Inter-Regional" + }, + { + "id": "holland.interline", + "carrier_name": "Holland Freight", + "service_name": "Interline" + }, + { + "id": "holland.regional", + "carrier_name": "Holland Freight", + "service_name": "Regional" + }, + { + "id": "westerncanadaexpress-259.standard", + "carrier_name": "Western Canada Express", + "service_name": "Standard" + }, + { + "id": "roadlinkxpress-553.standard", + "carrier_name": "ROAD LINK XPRESS", + "service_name": "Standard" + }, + { + "id": "stishehwaztransportinc-597.standard", + "carrier_name": "STI - Shehwaz Transport Inc.", + "service_name": "Standard" + }, + { + "id": "jbfexpress-430.standard", + "carrier_name": "JBF Express", + "service_name": "Standard" + }, + { + "id": "mjexpress-377.standard", + "carrier_name": "MJ Express", + "service_name": "Standard" + }, + { + "id": "pdfreight-612.standard", + "carrier_name": "PD Freight", + "service_name": "Standard" + }, + { + "id": "riserstransportinc-523.standard", + "carrier_name": "RISERS TRANSPORT INC.", + "service_name": "Standard" + }, + { + "id": "greenwaycarriers-475.standard", + "carrier_name": "GREENWAY CARRIERS ", + "service_name": "Standard" + }, + { + "id": "kimberlytransportltd-327.standard", + "carrier_name": "Kimberly Transport Ltd", + "service_name": "Standard" + }, + { + "id": "kindersleytransport-263.standard", + "carrier_name": "Kindersley Transport", + "service_name": "Standard" + }, + { + "id": "southeasternfreightlines-573.standard", + "carrier_name": "Southeastern Freight Lines", + "service_name": "Standard" + }, + { + "id": "dayton-freight.standard", + "carrier_name": "Dayton Freight", + "service_name": "Standard" + }, + { + "id": "otxlogisticscanadaltd-414.standard", + "carrier_name": "OTX Logistics Canada Ltd", + "service_name": "Standard" + }, + { + "id": "overlandfreightinternational-534.standard", + "carrier_name": "Overland Freight International", + "service_name": "Standard" + }, + { + "id": "westtransautoinc-367.standard", + "carrier_name": "WestTransAuto Inc.", + "service_name": "Standard" + }, + { + "id": "hollandmotorfreight-371.standard", + "carrier_name": "Holland Motor Freight", + "service_name": "Standard" + }, + { + "id": "lowfreightratecaltd-406.standard", + "carrier_name": "LOW FREIGHT RATE.CA LTD", + "service_name": "Standard" + }, + { + "id": "precisiontrucklines-410.standard", + "carrier_name": "PRECISION TRUCK LINES", + "service_name": "Standard" + }, + { + "id": "sewaenterpriseltd-405.standard", + "carrier_name": "SEWA ENTERPRISE LTD", + "service_name": "Standard" + }, + { + "id": "ab-courier-ltl.ltl-direct", + "carrier_name": "A&B Courier", + "service_name": "Direct (Pallet)" + }, + { + "id": "ab-courier-ltl.ltl-rush", + "carrier_name": "A&B Courier", + "service_name": "Rush (Pallet)" + }, + { + "id": "ab-courier-ltl.ltl-sameday", + "carrier_name": "A&B Courier", + "service_name": "Sameday (Pallet)" + }, + { + "id": "bmptransport-318.standard", + "carrier_name": "BMP Transport", + "service_name": "Standard" + }, + { + "id": "courrierplus-494.standard", + "carrier_name": "Courrier Plus", + "service_name": "Standard" + }, + { + "id": "fedexground-426.standard", + "carrier_name": "FedEx Ground", + "service_name": "Standard" + }, + { + "id": "mototransportation-264.standard", + "carrier_name": "Moto Transportation", + "service_name": "Standard" + }, + { + "id": "acecourier-519.standard", + "carrier_name": "ACE Courier", + "service_name": "Standard" + }, + { + "id": "caneda-189.standard", + "carrier_name": "Caneda", + "service_name": "Standard" + }, + { + "id": "expotrans-518.standard", + "carrier_name": "EXPOTRANS", + "service_name": "Standard" + }, + { + "id": "frontlinecarriersystemsinc-496.standard", + "carrier_name": "Frontline Carrier Systems Inc.", + "service_name": "Standard" + }, + { + "id": "frontlinefreight-492.standard", + "carrier_name": "Frontline Freight", + "service_name": "Standard" + }, + { + "id": "nishantransportinc-423.standard", + "carrier_name": "NISHAN TRANSPORT INC.", + "service_name": "Standard" + }, + { + "id": "transamcarriersinc-482.standard", + "carrier_name": "TRANSAM CARRIERS INC", + "service_name": "Standard" + }, + { + "id": "aarontruckingltd-513.standard", + "carrier_name": "AARON TRUCKING LTD", + "service_name": "Standard" + }, + { + "id": "apexmotorexpress-258.standard", + "carrier_name": "Apex Motor Express", + "service_name": "Standard" + }, + { + "id": "comox.standard", + "carrier_name": "Comox Pacific Express", + "service_name": "Standard" + }, + { + "id": "fleet-optics.standard", + "carrier_name": "Fleet Optics", + "service_name": "Standard" + }, + { + "id": "garrymercertrucking-452.standard", + "carrier_name": "Garry Mercer Trucking", + "service_name": "Standard" + }, + { + "id": "hnmlogisticsinc-436.standard", + "carrier_name": "HNM LOGISTICS INC.", + "service_name": "Standard" + }, + { + "id": "smartexpressltd-488.standard", + "carrier_name": "s.m.a.r.t. Express Ltd.", + "service_name": "Standard" + }, + { + "id": "aaacooper-594.standard", + "carrier_name": "AAA Cooper", + "service_name": "Standard" + }, + { + "id": "abcourier-480.standard", + "carrier_name": "A&B Courier", + "service_name": "Standard" + }, + { + "id": "cretransport-287.standard", + "carrier_name": "CRE Transport", + "service_name": "Standard" + }, + { + "id": "fedexexpress-272.standard", + "carrier_name": "FedEx Express", + "service_name": "Standard" + }, + { + "id": "rangefreightwaysltd-448.standard", + "carrier_name": "Range Freightways Ltd.", + "service_name": "Standard" + }, + { + "id": "recalltransportservicesinc-609.standard", + "carrier_name": "Recall Transport Services Inc.", + "service_name": "Standard" + }, + { + "id": "saraixpresstruckinginc-379.standard", + "carrier_name": "Sarai Xpress Trucking Inc", + "service_name": "Standard" + }, + { + "id": "trans2-293.standard", + "carrier_name": "Trans2", + "service_name": "Standard" + }, + { + "id": "coastlineexpress-536.standard", + "carrier_name": "Coastline Express", + "service_name": "Standard" + }, + { + "id": "d4logisticsinc-505.standard", + "carrier_name": "D4 LOGISTICS INC.", + "service_name": "Standard" + }, + { + "id": "exceltransportation-328.standard", + "carrier_name": "Excel Transportation", + "service_name": "Standard" + }, + { + "id": "purolatorcourier.express", + "carrier_name": "Purolator", + "service_name": "Express" + }, + { + "id": "purolatorcourier.express-box", + "carrier_name": "Purolator", + "service_name": "Express Box" + }, + { + "id": "purolatorcourier.express-box-international", + "carrier_name": "Purolator", + "service_name": "Express Box International" + }, + { + "id": "purolatorcourier.express-box1030am", + "carrier_name": "Purolator", + "service_name": "Express Box 10:30 AM" + }, + { + "id": "purolatorcourier.express-box9am", + "carrier_name": "Purolator", + "service_name": "Express Box 9 AM" + }, + { + "id": "purolatorcourier.express-boxUS", + "carrier_name": "Purolator", + "service_name": "Express Box U.S." + }, + { + "id": "purolatorcourier.express-envelope", + "carrier_name": "Purolator", + "service_name": "Express Envelope" + }, + { + "id": "purolatorcourier.express-envelope-international", + "carrier_name": "Purolator", + "service_name": "Express Envelope International" + }, + { + "id": "purolatorcourier.express-envelope-us", + "carrier_name": "Purolator", + "service_name": "Express Envelope U.S." + }, + { + "id": "purolatorcourier.express-envelope1030am", + "carrier_name": "Purolator", + "service_name": "Express Envelope 10:30 AM" + }, + { + "id": "purolatorcourier.express-envelope9am", + "carrier_name": "Purolator", + "service_name": "Express Envelope 9 AM" + }, + { + "id": "purolatorcourier.express-international", + "carrier_name": "Purolator", + "service_name": "Express International" + }, + { + "id": "purolatorcourier.express-pack", + "carrier_name": "Purolator", + "service_name": "Express Pack" + }, + { + "id": "purolatorcourier.express-pack-international", + "carrier_name": "Purolator", + "service_name": "Express Pack International" + }, + { + "id": "purolatorcourier.express-pack-us", + "carrier_name": "Purolator", + "service_name": "Express Pack U.S." + }, + { + "id": "purolatorcourier.express-pack1030am", + "carrier_name": "Purolator", + "service_name": "Express Pack 10:30 AM" + }, + { + "id": "purolatorcourier.express-pack9am", + "carrier_name": "Purolator", + "service_name": "Express Pack 9 AM" + }, + { + "id": "purolatorcourier.express-us", + "carrier_name": "Purolator", + "service_name": "Express U.S." + }, + { + "id": "purolatorcourier.express-us-1030am", + "carrier_name": "Purolator", + "service_name": "Express U.S. 10:30 AM" + }, + { + "id": "purolatorcourier.express-us-9am", + "carrier_name": "Purolator", + "service_name": "Express U.S. 9 AM" + }, + { + "id": "purolatorcourier.express-us-box1030AM", + "carrier_name": "Purolator", + "service_name": "Express U.S. Box 10:30 AM" + }, + { + "id": "purolatorcourier.express-us-box9AM", + "carrier_name": "Purolator", + "service_name": "Express U.S. Box 9 AM" + }, + { + "id": "purolatorcourier.express-us-envelope1030am", + "carrier_name": "Purolator", + "service_name": "Express U.S. Envelope 10:30 AM" + }, + { + "id": "purolatorcourier.express-us-envelope9am", + "carrier_name": "Purolator", + "service_name": "Express U.S. Envelope 9 AM" + }, + { + "id": "purolatorcourier.express-us-pack1030am", + "carrier_name": "Purolator", + "service_name": "Express U.S. Pack 10:30 AM" + }, + { + "id": "purolatorcourier.express-us-pack9am", + "carrier_name": "Purolator", + "service_name": "Express U.S. Pack 9 AM" + }, + { + "id": "purolatorcourier.express1030AM", + "carrier_name": "Purolator", + "service_name": "Express 10:30 AM" + }, + { + "id": "purolatorcourier.express9AM", + "carrier_name": "Purolator", + "service_name": "Express 9 AM" + }, + { + "id": "purolatorcourier.ground", + "carrier_name": "Purolator", + "service_name": "Ground" + }, + { + "id": "purolatorcourier.ground-us", + "carrier_name": "Purolator", + "service_name": "Ground U.S." + }, + { + "id": "purolatorcourier.standard", + "carrier_name": "Purolator", + "service_name": "Standard" + }, + { + "id": "a1delivery-514.standard", + "carrier_name": "A-1 Delivery", + "service_name": "Standard" + }, + { + "id": "fedex-courier.2-day", + "carrier_name": "FedEx Courier", + "service_name": "2-Day" + }, + { + "id": "fedex-courier.2-day-a-m", + "carrier_name": "FedEx Courier", + "service_name": "2-Day AM" + }, + { + "id": "fedex-courier.express-saver", + "carrier_name": "FedEx Courier", + "service_name": "Express Saver" + }, + { + "id": "fedex-courier.first-overnight", + "carrier_name": "FedEx Courier", + "service_name": "First Overnight" + }, + { + "id": "fedex-courier.ground", + "carrier_name": "FedEx Courier", + "service_name": "Ground" + }, + { + "id": "fedex-courier.international-connect-plus", + "carrier_name": "FedEx Courier", + "service_name": "International Connect Plus" + }, + { + "id": "fedex-courier.international-economy", + "carrier_name": "FedEx Courier", + "service_name": "International Economy" + }, + { + "id": "fedex-courier.international-ground", + "carrier_name": "FedEx Courier", + "service_name": "International Ground" + }, + { + "id": "fedex-courier.international-priority", + "carrier_name": "FedEx Courier", + "service_name": "International Priority" + }, + { + "id": "fedex-courier.international-priority-express", + "carrier_name": "FedEx Courier", + "service_name": "International Priority Express" + }, + { + "id": "fedex-courier.overnight", + "carrier_name": "FedEx Courier", + "service_name": "Overnight" + }, + { + "id": "fedex-courier.priority-overnight", + "carrier_name": "FedEx Courier", + "service_name": "Priority Overnight" + }, + { + "id": "fedex-courier.same-day", + "carrier_name": "FedEx Courier", + "service_name": "Same Day" + }, + { + "id": "spadytransportltd-384.standard", + "carrier_name": "SPADY TRANSPORT LTD", + "service_name": "Standard" + }, + { + "id": "tstapi.intermodal", + "carrier_name": "TST", + "service_name": "Intermodal" + }, + { + "id": "tstapi.standard", + "carrier_name": "TST", + "service_name": "Standard" + }, + { + "id": "spanalaska-544.standard", + "carrier_name": "Span Alaska", + "service_name": "Standard" + }, + { + "id": "sunshinelogistics-321.standard", + "carrier_name": "Sunshine Logistics", + "service_name": "Standard" + }, + { + "id": "yellowlogistics-525.standard", + "carrier_name": "Yellow Logistics", + "service_name": "Standard" + }, + { + "id": "abffreight-455.standard", + "carrier_name": "ABF Freight", + "service_name": "Standard" + }, + { + "id": "fastexact-585.standard", + "carrier_name": "Fast Exact", + "service_name": "Standard" + }, + { + "id": "gardewinegroupinc-424.standard", + "carrier_name": "Gardewine Group Inc.", + "service_name": "Standard" + }, + { + "id": "saia.standard", + "carrier_name": "Saia Motor Freight", + "service_name": "Standard" + }, + { + "id": "roadridertransportltd-417.standard", + "carrier_name": "ROAD RIDER Transport Ltd", + "service_name": "Standard" + }, + { + "id": "cct.expedited", + "carrier_name": "CCT", + "service_name": "Expedited" + }, + { + "id": "cct.intermodal", + "carrier_name": "CCT", + "service_name": "Intermodal" + }, + { + "id": "dependablehawaiianexpress-567.standard", + "carrier_name": "Dependable Hawaiian Express", + "service_name": "Standard" + }, + { + "id": "interpacifictransport-457.standard", + "carrier_name": "Inter-Pacific Transport", + "service_name": "Standard" + }, + { + "id": "oneforfreight-481.standard", + "carrier_name": "ONE For Freight", + "service_name": "Standard" + }, + { + "id": "seawayexpress-431.standard", + "carrier_name": "Seaway Express", + "service_name": "Standard" + }, + { + "id": "allpointsfreightinc-546.standard", + "carrier_name": "ALL POINTS FREIGHT INC.", + "service_name": "Standard" + }, + { + "id": "groupelafrance-434.standard", + "carrier_name": "Groupe Lafrance", + "service_name": "Standard" + }, + { + "id": "hiltontransportation-317.standard", + "carrier_name": "Hilton Transportation", + "service_name": "Standard" + }, + { + "id": "locomoteexpress-538.standard", + "carrier_name": "Locomote Express", + "service_name": "Standard" + }, + { + "id": "sendr-603.standard", + "carrier_name": "SENDR", + "service_name": "Standard" + }, + { + "id": "airinuitcargo-539.standard", + "carrier_name": "Air Inuit Cargo", + "service_name": "Standard" + }, + { + "id": "cmttransportinc-628.standard", + "carrier_name": "C M T TRANSPORT INC.", + "service_name": "Standard" + }, + { + "id": "galaxyfreightline-221.standard", + "carrier_name": "Galaxy Freightline", + "service_name": "Standard" + }, + { + "id": "purolatorfreight-577.standard", + "carrier_name": "Purolator Freight", + "service_name": "Standard" + }, + { + "id": "mcdispatch-467.standard", + "carrier_name": "MC Dispatch", + "service_name": "Standard" + }, + { + "id": "midlandtransport-437.standard", + "carrier_name": "Midland Transport", + "service_name": "Standard" + }, + { + "id": "dukesfreightservices-617.standard", + "carrier_name": "Dukes Freight Services", + "service_name": "Standard" + }, + { + "id": "frontiersupplychainsolutions-451.standard", + "carrier_name": "Frontier Supply Chain Solutions", + "service_name": "Standard" + }, + { + "id": "gls-freight.ground", + "carrier_name": "GLS Freight", + "service_name": "Ground" + }, + { + "id": "loadkingtransportinc-385.standard", + "carrier_name": "LOAD KING TRANSPORT INC", + "service_name": "Standard" + }, + { + "id": "dayrossfreightcanada-559.standard", + "carrier_name": "Day & Ross Freight | Canada", + "service_name": "Standard" + }, + { + "id": "gillcologistics-601.standard", + "carrier_name": "Gillco Logistics", + "service_name": "Standard" + }, + { + "id": "reddaway-402.standard", + "carrier_name": "Reddaway", + "service_name": "Standard" + }, + { + "id": "xpo.standard", + "carrier_name": "XPO", + "service_name": "Standard" + }, + { + "id": "lodextransportltd-572.standard", + "carrier_name": "LODEX TRANSPORT LTD", + "service_name": "Standard" + }, + { + "id": "moto.standard", + "carrier_name": "Moto Transportation Services Corp", + "service_name": "Standard" + }, + { + "id": "schneidernational-501.standard", + "carrier_name": "Schneider National", + "service_name": "Standard" + }, + { + "id": "glsfreight-186.standard", + "carrier_name": "GLS Freight", + "service_name": "Standard" + }, + { + "id": "loadlandlogisticsinc-607.standard", + "carrier_name": "LOAD LAND LOGISTICS INC.", + "service_name": "Standard" + }, + { + "id": "pacificcoastexpress-373.standard", + "carrier_name": "Pacific Coast Express", + "service_name": "Standard" + }, + { + "id": "purolatorcourier-401.standard", + "carrier_name": "Purolator Courier", + "service_name": "Standard" + }, + { + "id": "albrighttruckinginc-454.standard", + "carrier_name": "Albright Trucking Inc.", + "service_name": "Standard" + }, + { + "id": "beyondtransportation-285.standard", + "carrier_name": "Beyond Transportation", + "service_name": "Standard" + }, + { + "id": "dinkaenterprises-630.standard", + "carrier_name": "DINKA Enterprises", + "service_name": "Standard" + }, + { + "id": "gls-us.am-select-8a-12p", + "carrier_name": "GLS US", + "service_name": "AM Select 8a-12p" + }, + { + "id": "gls-us.early-priority-overnight", + "carrier_name": "GLS US", + "service_name": "Early Priority Overnight" + }, + { + "id": "gls-us.early-saturday-delivery", + "carrier_name": "GLS US", + "service_name": "Early Saturday Delivery" + }, + { + "id": "gls-us.evening-select-4p-8p", + "carrier_name": "GLS US", + "service_name": "Evening Select 4p-8p" + }, + { + "id": "gls-us.gls-ground", + "carrier_name": "GLS US", + "service_name": "GLS Ground" + }, + { + "id": "gls-us.noon-priority-overnight-sds–saturday-delivery", + "carrier_name": "GLS US", + "service_name": "Noon Priority Overnight SDS – Saturday Delivery" + }, + { + "id": "gls-us.pm-select-12p-4p", + "carrier_name": "GLS US", + "service_name": "PM Select 12p-4p" + }, + { + "id": "gls-us.priority-overnight", + "carrier_name": "GLS US", + "service_name": "Priority Overnight" + }, + { + "id": "gls-us.saturday-delivery", + "carrier_name": "GLS US", + "service_name": "Saturday Delivery" + }, + { + "id": "wsbellcartage-584.standard", + "carrier_name": "W.S. Bell Cartage", + "service_name": "Standard" + }, + { + "id": "manitoulintransport-440.standard", + "carrier_name": "Manitoulin Transport", + "service_name": "Standard" + }, + { + "id": "arbaztransportincdbakrgtransport-415.standard", + "carrier_name": "ARBAZ TRANSPORT INC. DBA KRG TRANSPORT", + "service_name": "Standard" + }, + { + "id": "garantlogisticsltd-548.standard", + "carrier_name": "GARANT LOGISTICS LTD", + "service_name": "Standard" + }, + { + "id": "highlightmotorgroup-542.standard", + "carrier_name": "HIGHLIGHT MOTOR GROUP", + "service_name": "Standard" + }, + { + "id": "kaynortransport-508.standard", + "carrier_name": "KAYNOR TRANSPORT", + "service_name": "Standard" + }, + { + "id": "rpmtransit-356.standard", + "carrier_name": "RPM Transit", + "service_name": "Standard" + }, + { + "id": "bestbaylogistics-532.standard", + "carrier_name": "BEST BAY LOGISTICS", + "service_name": "Standard" + }, + { + "id": "csatransportation-191.standard", + "carrier_name": "CSA Transportation", + "service_name": "Standard" + }, + { + "id": "himmatlogistics-587.standard", + "carrier_name": "HIMMAT LOGISTICS", + "service_name": "Standard" + }, + { + "id": "pacificnorthwestfreightsytems-560.standard", + "carrier_name": "Pacific Northwest Freight Sytems", + "service_name": "Standard" + }, + { + "id": "bowlinggreenlogistics-466.standard", + "carrier_name": "BOWLING GREEN LOGISTICS", + "service_name": "Standard" + }, + { + "id": "eknaamshippingcorp-512.standard", + "carrier_name": "EK NAAM SHIPPING CORP.", + "service_name": "Standard" + }, + { + "id": "frontline.standard", + "carrier_name": "Frontline", + "service_name": "Standard" + }, + { + "id": "jmtransitinc-439.standard", + "carrier_name": "J.M. Transit Inc.", + "service_name": "Standard" + }, + { + "id": "midland.econoline", + "carrier_name": "MidLand Transport", + "service_name": "Econo Line" + }, + { + "id": "midland.standard", + "carrier_name": "MidLand Transport", + "service_name": "Standard" + }, + { + "id": "mjexpress-380.standard", + "carrier_name": "MJ Express", + "service_name": "Standard" + }, + { + "id": "robusttransportservicesltd-479.standard", + "carrier_name": "ROBUST TRANSPORT SERVICES LTD", + "service_name": "Standard" + }, + { + "id": "aonetransport2007ltd-625.standard", + "carrier_name": "A-One Transport (2007) Ltd.", + "service_name": "Standard" + }, + { + "id": "atlaslogistics-262.standard", + "carrier_name": "Atlas Logistics", + "service_name": "Standard" + }, + { + "id": "gurshanlogistics-486.standard", + "carrier_name": "Gurshan Logistics", + "service_name": "Standard" + }, + { + "id": "jdtransportltd-456.standard", + "carrier_name": "J.D.TRANSPORT LTD", + "service_name": "Standard" + }, + { + "id": "rollsright.standard", + "carrier_name": "Rollsright", + "service_name": "Standard" + }, + { + "id": "speedy.standard", + "carrier_name": "Speedy", + "service_name": "Standard" + }, + { + "id": "ettransport-337.standard", + "carrier_name": "E.T. Transport", + "service_name": "Standard" + }, + { + "id": "fedexfreight-154.standard", + "carrier_name": "FedEx Freight", + "service_name": "Standard" + }, + { + "id": "tstcfexpresscanada-282.standard", + "carrier_name": "TST-CF Express | Canada", + "service_name": "Standard" + }, + { + "id": "suntransportationsystems-476.standard", + "carrier_name": "Sun Transportation Systems", + "service_name": "Standard" + }, + { + "id": "universallogisticssolution-517.standard", + "carrier_name": "UNIVERSAL LOGISTICS SOLUTION", + "service_name": "Standard" + }, + { + "id": "apex.standard", + "carrier_name": "Apex", + "service_name": "Standard" + }, + { + "id": "dayrossfreightcanada-133.standard", + "carrier_name": "Day & Ross Freight | Canada", + "service_name": "Standard" + }, + { + "id": "indocanadiancarriers-386.standard", + "carrier_name": "Indo Canadian Carriers", + "service_name": "Standard" + }, + { + "id": "stitransport-250.standard", + "carrier_name": "STI Transport", + "service_name": "Standard" + }, + { + "id": "stallion.apc-priority-worldwide", + "carrier_name": "Stallion", + "service_name": "APC Priority Worldwide" + }, + { + "id": "stallion.apc-priority-worldwide-tracked", + "carrier_name": "Stallion", + "service_name": "APC Priority Worldwide Tracked" + }, + { + "id": "stallion.economy-usa", + "carrier_name": "Stallion", + "service_name": "Stallion Economy USA" + }, + { + "id": "stallion.express", + "carrier_name": "Stallion", + "service_name": "Stallion Express" + }, + { + "id": "stallion.usps-express-mail", + "carrier_name": "Stallion", + "service_name": "USPS Express Mail" + }, + { + "id": "stallion.usps-first-class-mail", + "carrier_name": "Stallion", + "service_name": "USPS First Class Mail" + }, + { + "id": "stallion.usps-library-mail", + "carrier_name": "Stallion", + "service_name": "USPS Library Mail" + }, + { + "id": "stallion.usps-media-mail", + "carrier_name": "Stallion", + "service_name": "USPS Media Mail" + }, + { + "id": "stallion.usps-parcel-select-ground", + "carrier_name": "Stallion", + "service_name": "USPS Parcel Select Ground" + }, + { + "id": "stallion.usps-priority-mail", + "carrier_name": "Stallion", + "service_name": "USPS Priority Mail" + }, + { + "id": "stallion.usps-priority-mail-express", + "carrier_name": "Stallion", + "service_name": "USPS Priority Mail Express" + }, + { + "id": "transkid.standard", + "carrier_name": "Transkid", + "service_name": "Standard" + }, + { + "id": "versacold-438.standard", + "carrier_name": "VersaCold", + "service_name": "Standard" + }, + { + "id": "vttranssolutionsinc-472.standard", + "carrier_name": "VT TRANS SOLUTIONS INC", + "service_name": "Standard" + }, + { + "id": "bestwaycartage-355.standard", + "carrier_name": "BestWay Cartage", + "service_name": "Standard" + }, + { + "id": "commanderwesttrucking-582.standard", + "carrier_name": "Commander West Trucking", + "service_name": "Standard" + }, + { + "id": "roundthelakesmotorexpress-233.standard", + "carrier_name": "Round the Lakes Motor Express", + "service_name": "Standard" + }, + { + "id": "trafalgarsupplyco-511.standard", + "carrier_name": "Trafalgar Supply Co. ", + "service_name": "Standard" + }, + { + "id": "sunburytransportlimited-478.standard", + "carrier_name": "SUNBURY TRANSPORT LIMITED", + "service_name": "Standard" + }, + { + "id": "winniefreight-549.standard", + "carrier_name": "Winnie Freight", + "service_name": "Standard" + }, + { + "id": "gladiatorriggs-483.standard", + "carrier_name": "GLADIATOR RIGGS", + "service_name": "Standard" + }, + { + "id": "hewingstransportationinc-515.standard", + "carrier_name": "Hewings Transportation Inc.", + "service_name": "Standard" + }, + { + "id": "kbdtransportation-420.standard", + "carrier_name": "KBD TRANSPORTATION ", + "service_name": "Standard" + }, + { + "id": "mopallet.standard", + "carrier_name": "Maritime ", + "service_name": "Pallet Program" + }, + { + "id": "transcomax-504.standard", + "carrier_name": "TranscoMAX", + "service_name": "Standard" + }, + { + "id": "uniteddhillontrucklinesudtl-600.standard", + "carrier_name": "UNITED DHILLON TRUCK LINES (UDTL)", + "service_name": "Standard" + }, + { + "id": "atripcodeliveryservice-279.standard", + "carrier_name": "Atripco Delivery Service", + "service_name": "Standard" + }, + { + "id": "interloadtruckserviceltd-569.standard", + "carrier_name": "INTERLOAD TRUCK SERVICE LTD.", + "service_name": "Standard" + }, + { + "id": "kindersley.expedited", + "carrier_name": "Kindersley", + "service_name": "Expedited" + }, + { + "id": "kindersley.intermodal", + "carrier_name": "Kindersley", + "service_name": "Intermodal" + }, + { + "id": "kindersley.standard", + "carrier_name": "Kindersley", + "service_name": "Regular" + }, + { + "id": "polarisweb.standard", + "carrier_name": "Polaris FPP", + "service_name": "Pallet Program" + }, + { + "id": "alainnormandtransportinc-595.standard", + "carrier_name": "ALAIN NORMAND TRANSPORT INC.", + "service_name": "Standard" + }, + { + "id": "ariesgloballogisticsinc-135.standard", + "carrier_name": "Aries Global Logistics Inc", + "service_name": "Standard" + }, + { + "id": "dhillontransport-550.standard", + "carrier_name": "Dhillon Transport", + "service_name": "Standard" + }, + { + "id": "springcreekcarriersinc-291.standard", + "carrier_name": "Spring Creek Carriers Inc", + "service_name": "Standard" + }, + { + "id": "ilclogistics-315.standard", + "carrier_name": "ILC Logistics", + "service_name": "Standard" + }, + { + "id": "transontarioexpress-248.standard", + "carrier_name": "Trans-Ontario Express", + "service_name": "Standard" + }, + { + "id": "yrc.accelerated", + "carrier_name": "YRC", + "service_name": "Accelerated" + }, + { + "id": "yrc.freight-canada-to-us", + "carrier_name": "YRC", + "service_name": "Canada to US" + }, + { + "id": "yrc.freight-dedicated-equipment", + "carrier_name": "YRC", + "service_name": "Dedicated Equipment" + }, + { + "id": "yrc.standard", + "carrier_name": "YRC", + "service_name": "Standard" + }, + { + "id": "yrc.time-critical-by-5pm", + "carrier_name": "YRC", + "service_name": "Critical by 5 PM" + }, + { + "id": "yrc.time-critical-by-afternoon", + "carrier_name": "YRC", + "service_name": "Critical by Afternoon" + }, + { + "id": "yrc.time-critical-fastest-ground", + "carrier_name": "YRC", + "service_name": "Critical Fastest Ground" + }, + { + "id": "yrc.time-critical-hour-window", + "carrier_name": "YRC", + "service_name": "Critical Hour Window" + }, + { + "id": "b2bfreightwayinc-605.standard", + "carrier_name": "B2B Freightway Inc.", + "service_name": "Standard" + }, + { + "id": "caribootruckterminalsltd-616.standard", + "carrier_name": "Cariboo Truck Terminals Ltd", + "service_name": "Standard" + }, + { + "id": "eximlogisticsinc-421.standard", + "carrier_name": "ExIm Logistics Inc.", + "service_name": "Standard" + }, + { + "id": "ics.ground", + "carrier_name": "ICS", + "service_name": "Ground" + }, + { + "id": "ics.next-day", + "carrier_name": "ICS", + "service_name": "Next day" + }, + { + "id": "tstcfexpresssaia-185.standard", + "carrier_name": "TST-CF Express | SAIA", + "service_name": "Standard" + }, + { + "id": "comoxpacificexpress-257.standard", + "carrier_name": "Comox Pacific Express", + "service_name": "Standard" + }, + { + "id": "galaxyfreightlineinc-183.standard", + "carrier_name": "Galaxy Freightline Inc", + "service_name": "Standard" + }, + { + "id": "samoyedtransport-602.standard", + "carrier_name": "Samoyed Transport", + "service_name": "Standard" + }, + { + "id": "sprinterdeliveryltd-522.standard", + "carrier_name": "Sprinter Delivery Ltd.", + "service_name": "Standard" + }, + { + "id": "gtboltoninc-491.standard", + "carrier_name": "G.T. BOLTON INC.", + "service_name": "Standard" + }, + { + "id": "polarisdirect.standard", + "carrier_name": "Polaris", + "service_name": "Direct" + }, + { + "id": "ups.3day-select", + "carrier_name": "UPS", + "service_name": "3 Day Select" + }, + { + "id": "ups.expedited", + "carrier_name": "UPS", + "service_name": "Expedited" + }, + { + "id": "ups.express", + "carrier_name": "UPS", + "service_name": "Express" + }, + { + "id": "ups.express-early", + "carrier_name": "UPS", + "service_name": "Express Early" + }, + { + "id": "ups.express-saver", + "carrier_name": "UPS", + "service_name": "Express Saver" + }, + { + "id": "ups.ground", + "carrier_name": "UPS", + "service_name": "Ground" + }, + { + "id": "ups.standard", + "carrier_name": "UPS", + "service_name": "Standard" + }, + { + "id": "ups.worldwide-expedited", + "carrier_name": "UPS", + "service_name": "Worldwide Expedited" + }, + { + "id": "ups.worldwide-express", + "carrier_name": "UPS", + "service_name": "Worldwide Express" + }, + { + "id": "ups.worldwide-express-plus", + "carrier_name": "UPS", + "service_name": "Worldwide Express Plus" + }, + { + "id": "ups.worldwide-express-saver", + "carrier_name": "UPS", + "service_name": "Worldwide Express Saver" + }, + { + "id": "argustransportcanada-463.standard", + "carrier_name": "ARGUS Transport Canada", + "service_name": "Standard" + }, + { + "id": "crosscountryfreightsolutions-571.standard", + "carrier_name": "CrossCountry Freight Solutions", + "service_name": "Standard" + }, + { + "id": "geysertransportltd-606.standard", + "carrier_name": "Geyser Transport Ltd", + "service_name": "Standard" + }, + { + "id": "greysertransportltd-606.standard", + "carrier_name": "Greyser Transport Ltd", + "service_name": "Standard" + }, + { + "id": "steelestransportationgroup-580.standard", + "carrier_name": "Steele's Transportation Group", + "service_name": "Standard" + }, + { + "id": "titantranslineinc-450.standard", + "carrier_name": "Titan Transline Inc.", + "service_name": "Standard" + }, + { + "id": "2stopdelivery-393.standard", + "carrier_name": "2 STOP DELIVERY", + "service_name": "Standard" + }, + { + "id": "aduiepyleinc-589.standard", + "carrier_name": "A Duie Pyle, Inc", + "service_name": "Standard" + }, + { + "id": "centralislanddistributors-554.standard", + "carrier_name": "Central Island Distributors", + "service_name": "Standard" + }, + { + "id": "minimaxexpress-418.standard", + "carrier_name": "Minimax Express", + "service_name": "Standard" + }, + { + "id": "dayrosscommerce-266.standard", + "carrier_name": "Day & Ross Commerce", + "service_name": "Standard" + }, + { + "id": "highlightmotorgroup-500.standard", + "carrier_name": "HIGHLIGHT MOTOR GROUP", + "service_name": "Standard" + }, + { + "id": "internordtransportation-461.standard", + "carrier_name": "Inter-Nord Transportation", + "service_name": "Standard" + }, + { + "id": "westmancourier-419.standard", + "carrier_name": "Westman Courier", + "service_name": "Standard" + } + ], + "DEV_SERVICES": [ + { + "id": "kindersley-freight.domestic-expedited", + "carrier_name": "Kindersley Transport", + "service_name": "Domestic Expedited" + }, + { + "id": "kindersley-freight.domestic-rail", + "carrier_name": "Kindersley Transport", + "service_name": "Domestic Rail" + }, + { + "id": "kindersley-freight.domestic-road", + "carrier_name": "Kindersley Transport", + "service_name": "Domestic Road" + }, + { + "id": "kindersley-freight.transborder", + "carrier_name": "Kindersley Transport", + "service_name": "Transborder" + }, + { + "id": "newpennmotorexpress-445.standard", + "carrier_name": "New Penn Motor Express", + "service_name": "Standard" + }, + { + "id": "a10cD2MfJjiOCcRejjgv3cHMyRcRBhE3.standard", + "carrier_name": "Bill's Spot Carrier", + "service_name": "Standard" + }, + { + "id": "apps.intermodal", + "carrier_name": "APPS", + "service_name": "Intermodal" + }, + { + "id": "apps.standard", + "carrier_name": "APPS", + "service_name": "Standard" + }, + { + "id": "dayrosscommerce-266.standard", + "carrier_name": "Day & Ross Commerce", + "service_name": "Standard" + }, + { + "id": "fedex-courier.2-day", + "carrier_name": "FedEx Courier", + "service_name": "2-Day" + }, + { + "id": "fedex-courier.2-day-a-m", + "carrier_name": "FedEx Courier", + "service_name": "2-Day AM" + }, + { + "id": "fedex-courier.express-saver", + "carrier_name": "FedEx Courier", + "service_name": "Express Saver" + }, + { + "id": "fedex-courier.first-overnight", + "carrier_name": "FedEx Courier", + "service_name": "First Overnight" + }, + { + "id": "fedex-courier.ground", + "carrier_name": "FedEx Courier", + "service_name": "Ground" + }, + { + "id": "fedex-courier.international-connect-plus", + "carrier_name": "FedEx Courier", + "service_name": "International Connect Plus" + }, + { + "id": "fedex-courier.international-economy", + "carrier_name": "FedEx Courier", + "service_name": "International Economy" + }, + { + "id": "fedex-courier.international-ground", + "carrier_name": "FedEx Courier", + "service_name": "International Ground" + }, + { + "id": "fedex-courier.international-priority", + "carrier_name": "FedEx Courier", + "service_name": "International Priority" + }, + { + "id": "fedex-courier.international-priority-express", + "carrier_name": "FedEx Courier", + "service_name": "International Priority Express" + }, + { + "id": "fedex-courier.overnight", + "carrier_name": "FedEx Courier", + "service_name": "Overnight" + }, + { + "id": "fedex-courier.priority-overnight", + "carrier_name": "FedEx Courier", + "service_name": "Priority Overnight" + }, + { + "id": "fedex-courier.same-day", + "carrier_name": "FedEx Courier", + "service_name": "Same Day" + }, + { + "id": "fedexfreight-154.standard", + "carrier_name": "FedEx Freight", + "service_name": "Standard" + }, + { + "id": "gsm.air-skip", + "carrier_name": "GTA GSM", + "service_name": "AirSkip" + }, + { + "id": "gsm.air-skip-eco", + "carrier_name": "GTA GSM", + "service_name": "AirSkipEco" + }, + { + "id": "gsm.air-skip-plus", + "carrier_name": "GTA GSM", + "service_name": "AirSkip+" + }, + { + "id": "gsm.armed-secure-air", + "carrier_name": "GTA GSM", + "service_name": "Armed Secure Air" + }, + { + "id": "gsm.armed-secure-ground", + "carrier_name": "GTA GSM", + "service_name": "Armed Secure Ground" + }, + { + "id": "gsm.ground", + "carrier_name": "GTA GSM", + "service_name": "Ground" + }, + { + "id": "gsm.secure-air", + "carrier_name": "GTA GSM", + "service_name": "Secure Air" + }, + { + "id": "gsm.secure-ground", + "carrier_name": "GTA GSM", + "service_name": "Secure Ground" + }, + { + "id": "gsm.zone-skip", + "carrier_name": "GTA GSM", + "service_name": "ZoneSkip" + }, + { + "id": "gsm.zone-skip-plus", + "carrier_name": "GTA GSM", + "service_name": "ZoneSkip+" + }, + { + "id": "dhlexpress.domestic-express", + "carrier_name": "DHL Express", + "service_name": "Domestic Express" + }, + { + "id": "dhlexpress.domestic-express1030am", + "carrier_name": "DHL Express", + "service_name": "Domestic Express 10:30" + }, + { + "id": "dhlexpress.domestic-express9am", + "carrier_name": "DHL Express", + "service_name": "Domestic Express 9:00" + }, + { + "id": "dhlexpress.economy-select", + "carrier_name": "DHL Express", + "service_name": "Economy Select" + }, + { + "id": "dhlexpress.express-easy", + "carrier_name": "DHL Express", + "service_name": "Express Easy" + }, + { + "id": "dhlexpress.express-worldwide", + "carrier_name": "DHL Express", + "service_name": "Express Worldwide" + }, + { + "id": "dhlexpress.express1030am", + "carrier_name": "DHL Express", + "service_name": "Express 10:30" + }, + { + "id": "dhlexpress.express12pm", + "carrier_name": "DHL Express", + "service_name": "Express 12:00" + }, + { + "id": "dhlexpress.express9am", + "carrier_name": "DHL Express", + "service_name": "Express 9:00" + }, + { + "id": "nin.next-day", + "carrier_name": "NIN", + "service_name": "Next Day" + }, + { + "id": "nin.same-day", + "carrier_name": "NIN", + "service_name": "Same Day" + }, + { + "id": "one.standard", + "carrier_name": "ONE Transportation", + "service_name": "Standard" + }, + { + "id": "rollsright.standard", + "carrier_name": "Rollsright", + "service_name": "Standard" + }, + { + "id": "stallion.apc-priority-worldwide", + "carrier_name": "Stallion", + "service_name": "APC Priority Worldwide" + }, + { + "id": "stallion.apc-priority-worldwide-tracked", + "carrier_name": "Stallion", + "service_name": "APC Priority Worldwide Tracked" + }, + { + "id": "stallion.economy-usa", + "carrier_name": "Stallion", + "service_name": "Stallion Economy USA" + }, + { + "id": "stallion.express", + "carrier_name": "Stallion", + "service_name": "Stallion Express" + }, + { + "id": "stallion.usps-express-mail", + "carrier_name": "Stallion", + "service_name": "USPS Express Mail" + }, + { + "id": "stallion.usps-first-class-mail", + "carrier_name": "Stallion", + "service_name": "USPS First Class Mail" + }, + { + "id": "stallion.usps-library-mail", + "carrier_name": "Stallion", + "service_name": "USPS Library Mail" + }, + { + "id": "stallion.usps-media-mail", + "carrier_name": "Stallion", + "service_name": "USPS Media Mail" + }, + { + "id": "stallion.usps-parcel-select-ground", + "carrier_name": "Stallion", + "service_name": "USPS Parcel Select Ground" + }, + { + "id": "stallion.usps-priority-mail", + "carrier_name": "Stallion", + "service_name": "USPS Priority Mail" + }, + { + "id": "stallion.usps-priority-mail-express", + "carrier_name": "Stallion", + "service_name": "USPS Priority Mail Express" + }, + { + "id": "wuPUFbL2xJpG1vd4jHDl0vaXiXZ99WO4.standard", + "carrier_name": "test manager add", + "service_name": "Standard" + }, + { + "id": "apexmotorexpress-258.standard", + "carrier_name": "Apex Motor Express", + "service_name": "Standard" + }, + { + "id": "bettertrucks.ddu", + "carrier_name": "Better Trucks", + "service_name": "DDU" + }, + { + "id": "bettertrucks.express", + "carrier_name": "Better Trucks", + "service_name": "Express" + }, + { + "id": "bettertrucks.next_day", + "carrier_name": "Better Trucks", + "service_name": "Next Day" + }, + { + "id": "bettertrucks.same_day", + "carrier_name": "Better Trucks", + "service_name": "Same Day" + }, + { + "id": "IQ1rjQyWY8EflOBKVyqGM1syqer1O65V.standard", + "carrier_name": "Carrier Ben", + "service_name": "Standard" + }, + { + "id": "kindersley.expedited", + "carrier_name": "Kindersley", + "service_name": "Expedited" + }, + { + "id": "kindersley.intermodal", + "carrier_name": "Kindersley", + "service_name": "Intermodal" + }, + { + "id": "kindersley.standard", + "carrier_name": "Kindersley", + "service_name": "Regular" + }, + { + "id": "speedy.standard", + "carrier_name": "Speedy", + "service_name": "Standard" + }, + { + "id": "vitran.maxx", + "carrier_name": "Vitran", + "service_name": "Maxx" + }, + { + "id": "vitran.priority", + "carrier_name": "Vitran", + "service_name": "Priority" + }, + { + "id": "vitran.regular", + "carrier_name": "Vitran", + "service_name": "Regular" + }, + { + "id": "c5itKPJ6v6s4cmHEhjMDbbp9XD5lKoPR.standard", + "carrier_name": "Bill's Truck Shop", + "service_name": "Standard" + }, + { + "id": "caledoncouriers-294.standard", + "carrier_name": "Caledon Couriers", + "service_name": "Standard" + }, + { + "id": "dayton-freight.standard", + "carrier_name": "Dayton Freight", + "service_name": "Standard" + }, + { + "id": "dj9Lv4FUhkBIG7tdKiWh4n3U2lP2Drc5.standard", + "carrier_name": "testNewCarrierGar", + "service_name": "Standard" + }, + { + "id": "hiway.standard", + "carrier_name": "Hi-Way9", + "service_name": "Standard" + }, + { + "id": "boxknight.next-day", + "carrier_name": "BoxKnight", + "service_name": "Next Day" + }, + { + "id": "boxknight.sameday", + "carrier_name": "BoxKnight", + "service_name": "Same Day" + }, + { + "id": "cct.expedited", + "carrier_name": "CCT", + "service_name": "Expedited" + }, + { + "id": "cct.intermodal", + "carrier_name": "CCT", + "service_name": "Intermodal" + }, + { + "id": "peglobal-511.standard", + "carrier_name": "PE Global", + "service_name": "Standard" + }, + { + "id": "polarisdirect.standard", + "carrier_name": "Polaris", + "service_name": "Direct" + }, + { + "id": "billatransport-203.standard", + "carrier_name": "Billa Transport", + "service_name": "Standard" + }, + { + "id": "purolatorfreight-151.standard", + "carrier_name": "Purolator Freight ", + "service_name": "Standard" + }, + { + "id": "wce.standard", + "carrier_name": "WCE", + "service_name": "Intermodal" + }, + { + "id": "ajWA9sU9dV6U6oSB5GrokBvlzfV2RRf7.standard", + "carrier_name": "Garima-test", + "service_name": "Standard" + }, + { + "id": "c19h151C0I4Sdnp3Mqt0jRLxjTcNtksA.standard", + "carrier_name": "BinduCarrier", + "service_name": "Standard" + }, + { + "id": "FzXhDtSYDgMYP97Zi28GgU4j8QNXBllS.standard", + "carrier_name": "Apex-SPOT", + "service_name": "Standard" + }, + { + "id": "holland.guaranteed-330-pm", + "carrier_name": "Holland Freight", + "service_name": "Guaranteed by 3:30 PM" + }, + { + "id": "holland.guaranteed-9-am", + "carrier_name": "Holland Freight", + "service_name": "Guaranteed by 9:00 AM" + }, + { + "id": "holland.guaranteed-day", + "carrier_name": "Holland Freight", + "service_name": "Guaranteed Day" + }, + { + "id": "holland.guaranteed-hour", + "carrier_name": "Holland Freight", + "service_name": "Guaranteed Hour" + }, + { + "id": "holland.guaranteed-multi-hour", + "carrier_name": "Holland Freight", + "service_name": "Guaranteed Multi Hour" + }, + { + "id": "holland.guaranteed-noon", + "carrier_name": "Holland Freight", + "service_name": "Guaranteed by Noon" + }, + { + "id": "holland.guaranteed-weekend", + "carrier_name": "Holland Freight", + "service_name": "Guaranteed Weekend" + }, + { + "id": "holland.inter-regional", + "carrier_name": "Holland Freight", + "service_name": "Inter-Regional" + }, + { + "id": "holland.interline", + "carrier_name": "Holland Freight", + "service_name": "Interline" + }, + { + "id": "holland.regional", + "carrier_name": "Holland Freight", + "service_name": "Regional" + }, + { + "id": "LfT69HBpfJrjS9wv0tkFVylVxcMwWIVt.standard", + "carrier_name": "test create 2", + "service_name": "Standard" + }, + { + "id": "mopallet.standard", + "carrier_name": "Maritime ", + "service_name": "Pallet Program" + }, + { + "id": "bgxtransportation-361.standard", + "carrier_name": "BGX Transportation", + "service_name": "Standard" + }, + { + "id": "ei8AJgRFI3Xslob8UmFawLq71jhlKyZX.standard", + "carrier_name": "Service Test", + "service_name": "Standard" + }, + { + "id": "vankam.standard", + "carrier_name": "Vankam", + "service_name": "Standard" + }, + { + "id": "AbqB0FuehDpOtTxHzhhEvBs8CRJ44Z2I.standard", + "carrier_name": "Manager Carrier", + "service_name": "Standard" + }, + { + "id": "bbWZeWwm92b0eyQBYnRNT5PUJb7vslCY.standard", + "carrier_name": "1Manager", + "service_name": "Standard" + }, + { + "id": "dhl-ecomm.packet-international", + "carrier_name": "DHL eCommerce", + "service_name": "Package International" + }, + { + "id": "dhl-ecomm.parcel-expedited", + "carrier_name": "DHL eCommerce", + "service_name": "Parcel Expedited" + }, + { + "id": "dhl-ecomm.parcel-expedited-max", + "carrier_name": "DHL eCommerce", + "service_name": "Parcel Expedited Max" + }, + { + "id": "dhl-ecomm.parcel-ground", + "carrier_name": "DHL eCommerce", + "service_name": "Parcel Ground" + }, + { + "id": "dhl-ecomm.parcel-international-direct", + "carrier_name": "DHL eCommerce", + "service_name": "Parcel International Direct" + }, + { + "id": "dhl-ecomm.parcel-international-direct-priority", + "carrier_name": "DHL eCommerce", + "service_name": "Parcel International Direct Priority" + }, + { + "id": "dhl-ecomm.parcel-international-direct-standard", + "carrier_name": "DHL eCommerce", + "service_name": "Parcel International Direct Standard" + }, + { + "id": "dhl-ecomm.parcel-international-standard", + "carrier_name": "DHL eCommerce", + "service_name": "Parcel International Standard" + }, + { + "id": "saia.standard", + "carrier_name": "Saia Motor Freight", + "service_name": "Standard" + }, + { + "id": "Mg5oX7RWPCrENCdFkqaijO3NI0qKLFPt.standard", + "carrier_name": "2garima", + "service_name": "Standard" + }, + { + "id": "moto.standard", + "carrier_name": "Moto Transportation Services Corp", + "service_name": "Standard" + }, + { + "id": "2vlSmNNDnF6OWQFjUTFPlwLpTv0RkgXp.standard", + "carrier_name": "New Carrier1", + "service_name": "Standard" + }, + { + "id": "6D24l95xtm4jFC550GYo0tLEBrjgavVS.standard", + "carrier_name": "Admin Added carrier", + "service_name": "Standard" + }, + { + "id": "ab-courier.canada-1030am", + "carrier_name": "A&B Courier", + "service_name": "Canada 10:30am" + }, + { + "id": "ab-courier.canada-930am", + "carrier_name": "A&B Courier", + "service_name": "Canada 9:30am" + }, + { + "id": "ab-courier.canada-ground", + "carrier_name": "A&B Courier", + "service_name": "Canada Ground" + }, + { + "id": "ab-courier.canada-overnight", + "carrier_name": "A&B Courier", + "service_name": "Canada Overnight" + }, + { + "id": "ab-courier.direct", + "carrier_name": "A&B Courier", + "service_name": "Direct" + }, + { + "id": "ab-courier.four-hour", + "carrier_name": "A&B Courier", + "service_name": "Four Hour" + }, + { + "id": "ab-courier.rush", + "carrier_name": "A&B Courier", + "service_name": "Rush" + }, + { + "id": "ab-courier.sameday", + "carrier_name": "A&B Courier", + "service_name": "Sameday" + }, + { + "id": "ab-courier.usa-ground", + "carrier_name": "A&B Courier", + "service_name": "USA Ground" + }, + { + "id": "daynross.cs", + "carrier_name": "Day & Ross", + "service_name": "CS" + }, + { + "id": "daynross.domestic-standard", + "carrier_name": "Day & Ross", + "service_name": "Domestic Standard" + }, + { + "id": "daynross.transborder-standard", + "carrier_name": "Day & Ross", + "service_name": "Transborder Standard" + }, + { + "id": "dayrossrlc-132.standard", + "carrier_name": "Day & Ross RLC", + "service_name": "Standard" + }, + { + "id": "LtViz90ze09z34gcXmBbnFhSSqvvO6h5.standard", + "carrier_name": "FastfrateTest", + "service_name": "Standard" + }, + { + "id": "B6lmGiW6bT5uYz0ceJwoqduC0QRf51cr.standard", + "carrier_name": "1NewDinesh", + "service_name": "Standard" + }, + { + "id": "fedex.economy", + "carrier_name": "FedEx Freight", + "service_name": "Economy" + }, + { + "id": "fedex.standard", + "carrier_name": "FedEx Freight", + "service_name": "Priority" + }, + { + "id": "VwSa0UuckhcLkEzVoCSkhPJoTygfzeIk.standard", + "carrier_name": "Insurance Testing Carrier", + "service_name": "Standard" + }, + { + "id": "YlhNxO31X2UtvtKauwVLdIo4skgYEtDi.standard", + "carrier_name": "Dino Trucking Inc", + "service_name": "Standard" + }, + { + "id": "anq7q2st2UWtnj2WPWqXtgIgaUa4AZF4.standard", + "carrier_name": "Air lines ", + "service_name": "Standard" + }, + { + "id": "dhlcanada-230.standard", + "carrier_name": "DHL CANADA", + "service_name": "Standard" + }, + { + "id": "fedexexpress-272.standard", + "carrier_name": "FedEx Express", + "service_name": "Standard" + }, + { + "id": "KQfppq3g1UUR3BabBSUvQko4MWDUPnul.standard", + "carrier_name": "FC Carrier", + "service_name": "Standard" + }, + { + "id": "speedytransport-153.standard", + "carrier_name": "Speedy Transport", + "service_name": "Standard" + }, + { + "id": "tstapi.intermodal", + "carrier_name": "TST", + "service_name": "Intermodal" + }, + { + "id": "tstapi.standard", + "carrier_name": "TST", + "service_name": "Standard" + }, + { + "id": "ab-courier-ltl.ltl-direct", + "carrier_name": "A&B Courier", + "service_name": "Direct (Pallet)" + }, + { + "id": "ab-courier-ltl.ltl-rush", + "carrier_name": "A&B Courier", + "service_name": "Rush (Pallet)" + }, + { + "id": "ab-courier-ltl.ltl-sameday", + "carrier_name": "A&B Courier", + "service_name": "Sameday (Pallet)" + }, + { + "id": "accordtransportation-176.standard", + "carrier_name": "Accord Transportation", + "service_name": "Standard" + }, + { + "id": "am0cHEYPS0pZbXDbVXUM6opDv3c4YyxP.standard", + "carrier_name": "testGar", + "service_name": "Standard" + }, + { + "id": "purolatorcourier-401.standard", + "carrier_name": "Purolator Courier", + "service_name": "Standard" + }, + { + "id": "swyft.nextday", + "carrier_name": "Swyft", + "service_name": "Next Day" + }, + { + "id": "swyft.sameday", + "carrier_name": "Swyft", + "service_name": "Same Day" + }, + { + "id": "upscourier-162.standard", + "carrier_name": "UPS Courier", + "service_name": "Standard" + }, + { + "id": "2ya9w4n9GgNbqWlBgKcWro3WYkm0SI2d.standard", + "carrier_name": "SD Carrier", + "service_name": "Standard" + }, + { + "id": "csa.standard", + "carrier_name": "CSA", + "service_name": "Standard" + }, + { + "id": "newpenn.standard", + "carrier_name": "New Penn", + "service_name": "Standard" + }, + { + "id": "spring-gds.spring-direct", + "carrier_name": "Spring GDS", + "service_name": "Spring Direct" + }, + { + "id": "spring-gds.spring-gateway-parcel", + "carrier_name": "Spring GDS", + "service_name": "Spring Gateway Parcel" + }, + { + "id": "spring-gds.spring-packet-plus", + "carrier_name": "Spring GDS", + "service_name": "Spring Packet Plus Registered" + }, + { + "id": "spring-gds.spring-packet-tracked", + "carrier_name": "Spring GDS", + "service_name": "Spring Packet Tracked" + }, + { + "id": "spring-gds.spring-packet-untracked", + "carrier_name": "Spring GDS", + "service_name": "Spring Packet Untracked" + }, + { + "id": "TXbrc3AuAEXSJ9uQnT40gRG0zk41qTml.standard", + "carrier_name": "1Dinesh carrier", + "service_name": "Standard" + }, + { + "id": "6AS1YHCOu0JmUMnb0kEHBAS1blnvdi3C.standard", + "carrier_name": "SushmaCarrier", + "service_name": "Standard" + }, + { + "id": "gls-us.am-select-8a-12p", + "carrier_name": "GLS US", + "service_name": "AM Select 8a-12p" + }, + { + "id": "gls-us.early-priority-overnight", + "carrier_name": "GLS US", + "service_name": "Early Priority Overnight" + }, + { + "id": "gls-us.early-saturday-delivery", + "carrier_name": "GLS US", + "service_name": "Early Saturday Delivery" + }, + { + "id": "gls-us.evening-select-4p-8p", + "carrier_name": "GLS US", + "service_name": "Evening Select 4p-8p" + }, + { + "id": "gls-us.gls-ground", + "carrier_name": "GLS US", + "service_name": "GLS Ground" + }, + { + "id": "gls-us.noon-priority-overnight-sds–saturday-delivery", + "carrier_name": "GLS US", + "service_name": "Noon Priority Overnight SDS – Saturday Delivery" + }, + { + "id": "gls-us.pm-select-12p-4p", + "carrier_name": "GLS US", + "service_name": "PM Select 12p-4p" + }, + { + "id": "gls-us.priority-overnight", + "carrier_name": "GLS US", + "service_name": "Priority Overnight" + }, + { + "id": "gls-us.saturday-delivery", + "carrier_name": "GLS US", + "service_name": "Saturday Delivery" + }, + { + "id": "6XTleMYOSAZrdGo5dkg0AB2FS3E35oMF.standard", + "carrier_name": "Show Demo", + "service_name": "Standard" + }, + { + "id": "abcourier-480.standard", + "carrier_name": "A&B Courier (Manual)", + "service_name": "Standard" + }, + { + "id": "allspeed-432.standard", + "carrier_name": "All Speed", + "service_name": "Standard" + }, + { + "id": "KqwXT5YZbXOvt0PXziMUxkj3J6V1pTsQ.standard", + "carrier_name": "SushmaCarrier", + "service_name": "Standard" + }, + { + "id": "sameday.2-man-delivery-to-entrance", + "carrier_name": "Day & Ross Commerce Solutions", + "service_name": "Delivery to Entrance - 2 Person" + }, + { + "id": "sameday.2-man-delivery-to-room-of-choice", + "carrier_name": "Day & Ross Commerce Solutions", + "service_name": "Delivery to Room of Choice - 2 Person" + }, + { + "id": "sameday.2-man-delivery-to-room-of-choice-with-debris-removal", + "carrier_name": "Day & Ross Commerce Solutions", + "service_name": "Two-person delivery to room of choice with debris removal" + }, + { + "id": "sameday.dayr-ecom-urgent-pac", + "carrier_name": "Day & Ross Commerce Solutions", + "service_name": "eCommerce Urgent Pak" + }, + { + "id": "sameday.delivery-to-entrance", + "carrier_name": "Day & Ross Commerce Solutions", + "service_name": "Delivery to Entrance" + }, + { + "id": "sameday.delivery-to-room-of-choice", + "carrier_name": "Day & Ross Commerce Solutions", + "service_name": "Delivery to Room of Choice" + }, + { + "id": "sameday.delivery-to-room-of-choice-with-debris-removal", + "carrier_name": "Day & Ross Commerce Solutions", + "service_name": "Delivery to Room of Choice with Debris Removal" + }, + { + "id": "sameday.ground-daynross-road", + "carrier_name": "Day & Ross Commerce Solutions", + "service_name": "Ground - Road" + }, + { + "id": "sameday.next-day-before-5pm", + "carrier_name": "Day & Ross Commerce Solutions", + "service_name": "Next Day Delivery by 5 PM" + }, + { + "id": "sameday.next-day-before-9am", + "carrier_name": "Day & Ross Commerce Solutions", + "service_name": "Next Day Delivery by 9 AM" + }, + { + "id": "sameday.next-day-delivery-before-noon", + "carrier_name": "Day & Ross Commerce Solutions", + "service_name": "Next Day Delivery Before Noon" + }, + { + "id": "transkid.standard", + "carrier_name": "Transkid", + "service_name": "Standard" + }, + { + "id": "37SyVWjCgJxqehNHTdBaFhz1MQxKK0Vd.standard", + "carrier_name": "2New Spot carrier", + "service_name": "Standard" + }, + { + "id": "atlantistransport-347.standard", + "carrier_name": "Atlantis Transport", + "service_name": "Standard" + }, + { + "id": "excel-transport.standard", + "carrier_name": "Excel Transportation", + "service_name": "Standard" + }, + { + "id": "fleet-optics.standard", + "carrier_name": "Fleet Optics", + "service_name": "Standard" + }, + { + "id": "gardewine.standard", + "carrier_name": "Gardewine", + "service_name": "Standard" + }, + { + "id": "ics.ground", + "carrier_name": "ICS", + "service_name": "Ground" + }, + { + "id": "ics.next-day", + "carrier_name": "ICS", + "service_name": "Next day" + }, + { + "id": "usps.first-class", + "carrier_name": "USPS", + "service_name": "First Class" + }, + { + "id": "usps.ground-advantage", + "carrier_name": "USPS", + "service_name": "Ground Advantage" + }, + { + "id": "usps.parcel-select-ground", + "carrier_name": "USPS", + "service_name": "Parcel Select Ground" + }, + { + "id": "usps.priority-mail", + "carrier_name": "USPS", + "service_name": "Priority Mail" + }, + { + "id": "usps.priority-mail-express", + "carrier_name": "USPS", + "service_name": "Priority Mail Express" + }, + { + "id": "ups.3day-select", + "carrier_name": "UPS", + "service_name": "3 Day Select" + }, + { + "id": "ups.expedited", + "carrier_name": "UPS", + "service_name": "Expedited" + }, + { + "id": "ups.express", + "carrier_name": "UPS", + "service_name": "Express" + }, + { + "id": "ups.express-early", + "carrier_name": "UPS", + "service_name": "Express Early" + }, + { + "id": "ups.express-saver", + "carrier_name": "UPS", + "service_name": "Express Saver" + }, + { + "id": "ups.ground", + "carrier_name": "UPS", + "service_name": "Ground" + }, + { + "id": "ups.standard", + "carrier_name": "UPS", + "service_name": "Standard" + }, + { + "id": "ups.worldwide-expedited", + "carrier_name": "UPS", + "service_name": "Worldwide Expedited" + }, + { + "id": "ups.worldwide-express", + "carrier_name": "UPS", + "service_name": "Worldwide Express" + }, + { + "id": "ups.worldwide-express-plus", + "carrier_name": "UPS", + "service_name": "Worldwide Express Plus" + }, + { + "id": "ups.worldwide-express-saver", + "carrier_name": "UPS", + "service_name": "Worldwide Express Saver" + }, + { + "id": "airpro-255.standard", + "carrier_name": "Air Pro", + "service_name": "Standard" + }, + { + "id": "apex.standard", + "carrier_name": "Apex", + "service_name": "Standard" + }, + { + "id": "frontline.standard", + "carrier_name": "Frontline", + "service_name": "Standard" + }, + { + "id": "nationex.standard", + "carrier_name": "Nationex", + "service_name": "Standard" + }, + { + "id": "qM4cPFVXO20tNUz8fuU2bZCd6s0a6oKE.standard", + "carrier_name": "GL Freight", + "service_name": "Standard" + }, + { + "id": "swiftdeliverysystems-268.standard", + "carrier_name": "Swift Delivery Systems", + "service_name": "Standard" + }, + { + "id": "comox.standard", + "carrier_name": "Comox Pacific Express", + "service_name": "Standard" + }, + { + "id": "h9d2M32kjQDeB0GldPN0AMYvhuEXI45c.standard", + "carrier_name": "New2New carrier ", + "service_name": "Standard" + }, + { + "id": "maritime.dry", + "carrier_name": "Maritime", + "service_name": "Dry" + }, + { + "id": "maritime.frozen", + "carrier_name": "Maritime", + "service_name": "Reefer" + }, + { + "id": "maritime.heat", + "carrier_name": "Maritime", + "service_name": "Heat" + }, + { + "id": "overland.standard", + "carrier_name": "Overland", + "service_name": "Standard" + }, + { + "id": "manitoulintransport-440.standard", + "carrier_name": "Manitoulin Transport", + "service_name": "Standard" + }, + { + "id": "tforcefreight.tforcefreight-guarnteed", + "carrier_name": "TForceFreight", + "service_name": "TForceFreight Guaranteed" + }, + { + "id": "tforcefreight.tforcefreight-ltl", + "carrier_name": "TForceFreight", + "service_name": "TForceFreight LTL" + }, + { + "id": "tforcefreight.tforcefreight-standard", + "carrier_name": "TForceFreight", + "service_name": "TForceFreight Standard" + }, + { + "id": "V44U22d1DwvXrhpvW7UtPrZ4GQQ6x6Hb.standard", + "carrier_name": "11Manager Carriers", + "service_name": "Standard" + }, + { + "id": "yrc.accelerated", + "carrier_name": "YRC", + "service_name": "Accelerated" + }, + { + "id": "yrc.freight-canada-to-us", + "carrier_name": "YRC", + "service_name": "Canada to US" + }, + { + "id": "yrc.freight-dedicated-equipment", + "carrier_name": "YRC", + "service_name": "Dedicated Equipment" + }, + { + "id": "yrc.standard", + "carrier_name": "YRC", + "service_name": "Standard" + }, + { + "id": "yrc.time-critical-by-5pm", + "carrier_name": "YRC", + "service_name": "Critical by 5 PM" + }, + { + "id": "yrc.time-critical-by-afternoon", + "carrier_name": "YRC", + "service_name": "Critical by Afternoon" + }, + { + "id": "yrc.time-critical-fastest-ground", + "carrier_name": "YRC", + "service_name": "Critical Fastest Ground" + }, + { + "id": "yrc.time-critical-hour-window", + "carrier_name": "YRC", + "service_name": "Critical Hour Window" + }, + { + "id": "Zs8TafpKGZ0fx9utJ42E4ZFyLtoRUmzE.standard", + "carrier_name": "BinduTestCarrier", + "service_name": "Standard" + }, + { + "id": "abffreight-455.standard", + "carrier_name": "ABF Freight", + "service_name": "Standard" + }, + { + "id": "dayrosscdn-133.standard", + "carrier_name": "Day & Ross CDN", + "service_name": "Standard" + }, + { + "id": "gMS33dZBLDmtyw0ileP8b0wxytNPUNEe.standard", + "carrier_name": "SPOT Polaris", + "service_name": "Standard" + }, + { + "id": "intelcom.standard", + "carrier_name": "Intelcom", + "service_name": "Standard" + }, + { + "id": "maritimeontario-267.standard", + "carrier_name": "Maritime-Ontario", + "service_name": "Standard" + }, + { + "id": "minimax.standard", + "carrier_name": "Minimax", + "service_name": "Standard" + }, + { + "id": "allmodes-147.standard", + "carrier_name": "All Modes", + "service_name": "Standard" + }, + { + "id": "gls-freight.ground", + "carrier_name": "GLS Freight", + "service_name": "Ground" + }, + { + "id": "kindersley-courier.standard", + "carrier_name": "Kindersley Transport", + "service_name": "Standard" + }, + { + "id": "LfuLpu4OaMgNiVnBFYbNUXxiAyY66z3m.standard", + "carrier_name": "Joshuas super advanced and not sketchy at all spot carrier", + "service_name": "Standard" + }, + { + "id": "midland.econoline", + "carrier_name": "MidLand Transport", + "service_name": "Econo Line" + }, + { + "id": "midland.reefer", + "carrier_name": "MidLand Transport", + "service_name": "Reefer" + }, + { + "id": "midland.standard", + "carrier_name": "MidLand Transport", + "service_name": "Standard" + }, + { + "id": "TKzEkXVCaQIkGU0SqHyix6Lujv1YiCaR.standard", + "carrier_name": "Test carrier", + "service_name": "Standard" + }, + { + "id": "1lFVuiEgI85Be3BmNH9Jc8QKe5DEDVs0.standard", + "carrier_name": "vicky", + "service_name": "Standard" + }, + { + "id": "gY2Ah4O53ACmkb6D75rHLg6eMfzpsIpw.standard", + "carrier_name": "Flex transport", + "service_name": "Standard" + }, + { + "id": "morneau.standard", + "carrier_name": "Morneau Transport", + "service_name": "Standard" + }, + { + "id": "4TBSrW3F8aUzohICK0A0XH2mk25Aipdx.standard", + "carrier_name": "Bill Cartage", + "service_name": "Standard" + }, + { + "id": "nishantransportinc-423.standard", + "carrier_name": "NISHAN TRANSPORT INC.", + "service_name": "Standard" + }, + { + "id": "xpologistics-265.standard", + "carrier_name": "XPO Logistics", + "service_name": "Standard" + }, + { + "id": "Fqrdds8xTJcw2gopMEKjgdNR0jTkxxVm.standard", + "carrier_name": "gartest2Carrier", + "service_name": "Standard" + }, + { + "id": "mBbc7RwNE6CPvx22XiDZ50kdsC2fLNEF.standard", + "carrier_name": "CSS Carrier", + "service_name": "Standard" + }, + { + "id": "courrierplus-494.standard", + "carrier_name": "Courrier Plus", + "service_name": "Standard" + }, + { + "id": "loomis-express.express-0900", + "carrier_name": "Loomis", + "service_name": "Express 9:00" + }, + { + "id": "loomis-express.express-1200", + "carrier_name": "Loomis", + "service_name": "Express 12:00" + }, + { + "id": "loomis-express.express-1800", + "carrier_name": "Loomis", + "service_name": "Express 18:00" + }, + { + "id": "loomis-express.ground", + "carrier_name": "Loomis", + "service_name": "Ground" + }, + { + "id": "purolatorfreight.standard", + "carrier_name": "Purolator Freight", + "service_name": "Standard" + }, + { + "id": "csatransportation-191.standard", + "carrier_name": "CSA Transportation", + "service_name": "Standard" + }, + { + "id": "XiX2uCQlUYTCn4Mi5z3APT4Mi2Xn2x3a.standard", + "carrier_name": "New GLS Freight", + "service_name": "Standard" + }, + { + "id": "fastfrate.express", + "carrier_name": "Fastfrate", + "service_name": "Express" + }, + { + "id": "fastfrate.standard", + "carrier_name": "Fastfrate", + "service_name": "Standard" + }, + { + "id": "martinroytransport-310.standard", + "carrier_name": "Martin Roy Transport", + "service_name": "Standard" + }, + { + "id": "reddaway.guaranteed-3pm", + "carrier_name": "Reddaway", + "service_name": "Guaranteed Delivery Before 3:30 PM" + }, + { + "id": "reddaway.guaranteed-9am", + "carrier_name": "Reddaway", + "service_name": "Guaranteed Delivery Before 9 AM" + }, + { + "id": "reddaway.guaranteed-noon", + "carrier_name": "Reddaway", + "service_name": "Guaranteed Delivery Before 12:00 PM (noon)" + }, + { + "id": "reddaway.guaranteed-weekend", + "carrier_name": "Reddaway", + "service_name": "Guaranteed Weekend" + }, + { + "id": "reddaway.guarenteed-9am", + "carrier_name": "Reddaway", + "service_name": "Guaranteed Delivery Before 9 AM" + }, + { + "id": "reddaway.guarenteed-noon", + "carrier_name": "Reddaway", + "service_name": "Guaranteed Delivery Before 12:00 PM (noon)" + }, + { + "id": "reddaway.interline", + "carrier_name": "Reddaway", + "service_name": "Interline Delivery" + }, + { + "id": "reddaway.multi-hour-window", + "carrier_name": "Reddaway", + "service_name": "Guaranteed Window - Multi-Hour Window" + }, + { + "id": "reddaway.regional-delivery", + "carrier_name": "Reddaway", + "service_name": "Regional Delivery" + }, + { + "id": "reddaway.single-hour-window", + "carrier_name": "Reddaway", + "service_name": "Guaranteed Window - Single-hour Window" + }, + { + "id": "reddaway.single-or-multi-day", + "carrier_name": "Reddaway", + "service_name": "Guaranteed Window - Single or Multi-day Window" + }, + { + "id": "reddaway.standard", + "carrier_name": "Reddaway", + "service_name": "Standard" + }, + { + "id": "xpo.standard", + "carrier_name": "XPO", + "service_name": "Standard" + }, + { + "id": "yrcfreight-330.standard", + "carrier_name": "YRC Freight", + "service_name": "Standard" + }, + { + "id": "amatransinc-251.standard", + "carrier_name": "AMA Trans Inc", + "service_name": "Standard" + }, + { + "id": "canadapost.domestic", + "carrier_name": "Canada Post", + "service_name": "Domestic" + }, + { + "id": "canadapost.expedited-parcel", + "carrier_name": "Canada Post", + "service_name": "Expedited Parcel" + }, + { + "id": "canadapost.expedited-parcel-usa", + "carrier_name": "Canada Post", + "service_name": "Expedited Parcel USA" + }, + { + "id": "canadapost.international", + "carrier_name": "Canada Post", + "service_name": "International" + }, + { + "id": "canadapost.international-parcel-air", + "carrier_name": "Canada Post", + "service_name": "International Parcel Air" + }, + { + "id": "canadapost.international-parcel-surface", + "carrier_name": "Canada Post", + "service_name": "International Parcel Surface" + }, + { + "id": "canadapost.priority", + "carrier_name": "Canada Post", + "service_name": "Priority" + }, + { + "id": "canadapost.priority-ww-envelope-international", + "carrier_name": "Canada Post", + "service_name": "Priority Worldwide envelope INT’L" + }, + { + "id": "canadapost.priority-ww-envelope-usa", + "carrier_name": "Canada Post", + "service_name": "Priority Worldwide envelope USA" + }, + { + "id": "canadapost.priority-ww-pak-international", + "carrier_name": "Canada Post", + "service_name": "Priority Worldwide pak INT’L" + }, + { + "id": "canadapost.priority-ww-pak-usa", + "carrier_name": "Canada Post", + "service_name": "Priority Worldwide pak USA" + }, + { + "id": "canadapost.priority-ww-parcel-international", + "carrier_name": "Canada Post", + "service_name": "Priority Worldwide parcel INT’L" + }, + { + "id": "canadapost.priority-ww-parcel-usa", + "carrier_name": "Canada Post", + "service_name": "Priority Worldwide parcel USA" + }, + { + "id": "canadapost.regular-parcel", + "carrier_name": "Canada Post", + "service_name": "Regular Parcel" + }, + { + "id": "canadapost.small-packet-international-air", + "carrier_name": "Canada Post", + "service_name": "Small Packet International Air" + }, + { + "id": "canadapost.small-packet-international-surface", + "carrier_name": "Canada Post", + "service_name": "Small Packet International Surface" + }, + { + "id": "canadapost.small-packet-usa-air", + "carrier_name": "Canada Post", + "service_name": "Small Packet USA Air" + }, + { + "id": "canadapost.tracked-packet-international", + "carrier_name": "Canada Post", + "service_name": "Tracked Packet - International" + }, + { + "id": "canadapost.tracked-packet-usa", + "carrier_name": "Canada Post", + "service_name": "Tracked Packet USA" + }, + { + "id": "canadapost.xpresspost", + "carrier_name": "Canada Post", + "service_name": "Xpresspost" + }, + { + "id": "canadapost.xpresspost-international", + "carrier_name": "Canada Post", + "service_name": "Xpresspost International" + }, + { + "id": "canadapost.xpresspost-usa", + "carrier_name": "Canada Post", + "service_name": "Xpresspost USA" + }, + { + "id": "canpar.ground", + "carrier_name": "Canpar", + "service_name": "Ground" + }, + { + "id": "canpar.international", + "carrier_name": "Canpar", + "service_name": "International" + }, + { + "id": "canpar.overnight", + "carrier_name": "Canpar", + "service_name": "Overnight" + }, + { + "id": "canpar.overnight-letter", + "carrier_name": "Canpar", + "service_name": "Overnight Letter" + }, + { + "id": "canpar.overnight-pak", + "carrier_name": "Canpar", + "service_name": "Overnight Pak" + }, + { + "id": "canpar.select", + "carrier_name": "Canpar", + "service_name": "Select" + }, + { + "id": "canpar.select-letter", + "carrier_name": "Canpar", + "service_name": "Select Letter" + }, + { + "id": "canpar.select-pak", + "carrier_name": "Canpar", + "service_name": "Select Pak" + }, + { + "id": "canpar.select-usa", + "carrier_name": "Canpar", + "service_name": "Select U.S.A." + }, + { + "id": "canpar.usa", + "carrier_name": "Canpar", + "service_name": "U.S.A." + }, + { + "id": "canpar.usa-Letter", + "carrier_name": "Canpar", + "service_name": "U.S.A. Letter" + }, + { + "id": "canpar.usa-pak", + "carrier_name": "Canpar", + "service_name": "U.S.A. Pak" + }, + { + "id": "gls.ground", + "carrier_name": "GLS", + "service_name": "Ground" + }, + { + "id": "QzGk6Gz9BPcw0v7zsJcIaTAAXd6X7732.standard", + "carrier_name": "BinduTestCarrier2", + "service_name": "Standard" + }, + { + "id": "w2oXoP88bl44DA3Y4hft8dZoLFDp3KFX.standard", + "carrier_name": "Carrier Sonu", + "service_name": "Standard" + }, + { + "id": "e5966ZlRusUUM61sXUM1QQx0Td17iIan.standard", + "carrier_name": "Dolphin Carrier", + "service_name": "Standard" + }, + { + "id": "polarisweb.standard", + "carrier_name": "Polaris FPP", + "service_name": "Pallet Program" + }, + { + "id": "purolatorcourier.express", + "carrier_name": "Purolator", + "service_name": "Express" + }, + { + "id": "purolatorcourier.express-box", + "carrier_name": "Purolator", + "service_name": "Express Box" + }, + { + "id": "purolatorcourier.express-box-international", + "carrier_name": "Purolator", + "service_name": "Express Box International" + }, + { + "id": "purolatorcourier.express-box1030am", + "carrier_name": "Purolator", + "service_name": "Express Box 10:30 AM" + }, + { + "id": "purolatorcourier.express-box9am", + "carrier_name": "Purolator", + "service_name": "Express Box 9 AM" + }, + { + "id": "purolatorcourier.express-boxUS", + "carrier_name": "Purolator", + "service_name": "Express Box U.S." + }, + { + "id": "purolatorcourier.express-envelope", + "carrier_name": "Purolator", + "service_name": "Express Envelope" + }, + { + "id": "purolatorcourier.express-envelope-international", + "carrier_name": "Purolator", + "service_name": "Express Envelope International" + }, + { + "id": "purolatorcourier.express-envelope-us", + "carrier_name": "Purolator", + "service_name": "Express Envelope U.S." + }, + { + "id": "purolatorcourier.express-envelope1030am", + "carrier_name": "Purolator", + "service_name": "Express Envelope 10:30 AM" + }, + { + "id": "purolatorcourier.express-envelope9am", + "carrier_name": "Purolator", + "service_name": "Express Envelope 9 AM" + }, + { + "id": "purolatorcourier.express-international", + "carrier_name": "Purolator", + "service_name": "Express International" + }, + { + "id": "purolatorcourier.express-pack", + "carrier_name": "Purolator", + "service_name": "Express Pack" + }, + { + "id": "purolatorcourier.express-pack-international", + "carrier_name": "Purolator", + "service_name": "Express Pack International" + }, + { + "id": "purolatorcourier.express-pack-us", + "carrier_name": "Purolator", + "service_name": "Express Pack U.S." + }, + { + "id": "purolatorcourier.express-pack1030am", + "carrier_name": "Purolator", + "service_name": "Express Pack 10:30 AM" + }, + { + "id": "purolatorcourier.express-pack9am", + "carrier_name": "Purolator", + "service_name": "Express Pack 9 AM" + }, + { + "id": "purolatorcourier.express-us", + "carrier_name": "Purolator", + "service_name": "Express U.S." + }, + { + "id": "purolatorcourier.express-us-1030am", + "carrier_name": "Purolator", + "service_name": "Express U.S. 10:30 AM" + }, + { + "id": "purolatorcourier.express-us-9am", + "carrier_name": "Purolator", + "service_name": "Express U.S. 9 AM" + }, + { + "id": "purolatorcourier.express-us-box1030AM", + "carrier_name": "Purolator", + "service_name": "Express U.S. Box 10:30 AM" + }, + { + "id": "purolatorcourier.express-us-box9AM", + "carrier_name": "Purolator", + "service_name": "Express U.S. Box 9 AM" + }, + { + "id": "purolatorcourier.express-us-envelope1030am", + "carrier_name": "Purolator", + "service_name": "Express U.S. Envelope 10:30 AM" + }, + { + "id": "purolatorcourier.express-us-envelope9am", + "carrier_name": "Purolator", + "service_name": "Express U.S. Envelope 9 AM" + }, + { + "id": "purolatorcourier.express-us-pack1030am", + "carrier_name": "Purolator", + "service_name": "Express U.S. Pack 10:30 AM" + }, + { + "id": "purolatorcourier.express-us-pack9am", + "carrier_name": "Purolator", + "service_name": "Express U.S. Pack 9 AM" + }, + { + "id": "purolatorcourier.express1030AM", + "carrier_name": "Purolator", + "service_name": "Express 10:30 AM" + }, + { + "id": "purolatorcourier.express9AM", + "carrier_name": "Purolator", + "service_name": "Express 9 AM" + }, + { + "id": "purolatorcourier.ground", + "carrier_name": "Purolator", + "service_name": "Ground" + }, + { + "id": "purolatorcourier.ground-us", + "carrier_name": "Purolator", + "service_name": "Ground U.S." + }, + { + "id": "purolatorcourier.standard", + "carrier_name": "Purolator", + "service_name": "Standard" + } + ] +} diff --git a/plugins/freightcom_rest/karrio/providers/freightcom_rest/rate.py b/plugins/freightcom_rest/karrio/providers/freightcom_rest/rate.py new file mode 100644 index 0000000..d31abcc --- /dev/null +++ b/plugins/freightcom_rest/karrio/providers/freightcom_rest/rate.py @@ -0,0 +1,289 @@ +"""Karrio Freightcom Rest rate API implementation.""" + + +import karrio.schemas.freightcom_rest.rate_request as freightcom_rest_req +import karrio.schemas.freightcom_rest.rate_response as freightcom_rest_res + +import typing +import karrio.lib as lib +import karrio.core.units as units +import karrio.core.models as models +import karrio.providers.freightcom_rest.error as error +import karrio.providers.freightcom_rest.utils as provider_utils +import karrio.providers.freightcom_rest.units as provider_units + +import datetime + +def parse_rate_response( + _response: lib.Deserializable[dict], + settings: provider_utils.Settings, +) -> typing.Tuple[typing.List[models.RateDetails], typing.List[models.Message]]: + response = _response.deserialize() + + messages = error.parse_error_response(response, settings) + + # Extract rate objects from the response - adjust based on carrier API structure + + # For JSON APIs, find the path to rate objects + rate_objects = response.get("rates", []) if hasattr(response, 'get') else [] + rates = [_extract_details(rate, settings) for rate in rate_objects] + + + return rates, messages + + +def _extract_details( + data: dict, + settings: provider_utils.Settings, +) -> models.RateDetails: + """ + Extract rate details from carrier response data + + data: The carrier-specific rate data structure + settings: The carrier connection settings + + Returns a RateDetails object with extracted rate information + """ + # Convert the carrier data to a proper object for easy attribute access + + # For JSON APIs, convert dict to proper response object + rate = lib.to_object(freightcom_rest_res.RateType, data) + + # Now access data through the object attributes + service = provider_units.ShippingService.map( + rate.service_id, + ) + service_name = service.name_or_key if service else "" + + courier = provider_units.ShippingCourier.find(rate.service_id) + rate_provider = courier.name_or_key if courier else "" + + total_obj = rate.total if hasattr(rate, 'total') else None + + total = float(int(total_obj.value) / 100) if hasattr(total_obj, 'value') and total_obj.value else 0.0 + currency = total_obj.currency if hasattr(total_obj, 'currency') else "USD" + transit_days = int(rate.transit_time_days) if hasattr(rate, 'transit_time_days') and rate.transit_time_days else 0 + + charges = [ + ("Base charge", rate.base.value, rate.base.currency), + *((surcharge.type, surcharge.amount.value, surcharge.amount.currency) for surcharge in rate.surcharges), + *((tax.type, tax.amount.value, tax.amount.currency) for tax in rate.taxes), + ] + + return models.RateDetails( + carrier_id=settings.carrier_id, + carrier_name=settings.carrier_name, + service=service_name, + total_charge=lib.to_money(total), + currency=currency, + transit_days=transit_days, + extra_charges=[ + models.ChargeDetails( + name=name, + currency=currency, + amount=lib.to_money(int(amount) / 100), + ) + for name, amount, currency in charges + if charges + ], + meta=dict( + service_name=service_name, + rate_provider=rate_provider, + is_rate_guaranteed=( + rate.customs_charge_data.is_rate_guaranteed + if hasattr(rate, 'customs_charge_data') and rate.customs_charge_data and hasattr(rate.customs_charge_data, 'is_rate_guaranteed') + else None + ), + ), + ) + + +def rate_request( + payload: models.RateRequest, + settings: provider_utils.Settings, +) -> lib.Serializable: + """ + Create a rate request for the carrier API + + payload: The standardized RateRequest from karrio + settings: The carrier connection settings + + Returns a Serializable object that can be sent to the carrier API + """ + # Convert karrio models to carrier-specific format + 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, + ) + + # Create the carrier-specific request object + is_intl = shipper.country_code != recipient.country_code + is_ca_to_us = shipper.country_code == "CA" and recipient.country_code == "US" + + customs = lib.to_customs_info( + payload.customs, + shipper=payload.shipper, + recipient=payload.recipient, + weight_unit=packages.weight_unit, + ) + commodities = lib.identity( + (customs.commodities if any(customs.commodities) else packages.items) + if any(packages.items) or any(customs.commodities) + else [] + ) + + packaging_type = provider_units.PackagingType.map(packages.package_type or "small_box").value + ship_datetime = lib.to_next_business_datetime( + options.shipping_date.state or datetime.datetime.now(), + current_format="%Y-%m-%dT%H:%M", + ) + + request = freightcom_rest_req.RateRequestType( + services=[provider_units.ShippingService.map(service).value_or_key for service in payload.services], + excluded_services=[], + details=freightcom_rest_req.DetailsType( + origin=freightcom_rest_req.DestinationType( + name=shipper.company_name or shipper.person_name, + address=freightcom_rest_req.AddressType( + address_line_1=shipper.address_line1, + address_line_2=shipper.address_line2, + city=shipper.city, + region=shipper.state_code, + country=shipper.country_code, + postal_code=shipper.postal_code, + ), + residential=shipper.residential is True, + contact_name=shipper.person_name if shipper.company_name else "", + phone_number=freightcom_rest_req.PhoneNumberType( + number=shipper.phone_number, + ) if shipper.phone_number else None, + email_addresses=lib.join(shipper.email), + ), + destination=freightcom_rest_req.DestinationType( + name=recipient.company_name or recipient.person_name, + address=freightcom_rest_req.AddressType( + address_line_1=recipient.address_line1, + address_line_2=recipient.address_line2, + city=recipient.city, + region=recipient.state_code, + country=recipient.country_code, + postal_code=recipient.postal_code, + ), + residential=recipient.residential is True, + contact_name=recipient.person_name, + phone_number=freightcom_rest_req.PhoneNumberType( + number=recipient.phone_number + ) if recipient.phone_number else None, + email_addresses=lib.join(recipient.email), + ready_at=freightcom_rest_req.ReadyType( + hour=ship_datetime.hour, + minute=0 + ), + ready_until=freightcom_rest_req.ReadyType( + hour=17, + minute=0 + ), + receives_email_updates=options.email_notification.state, + signature_requirement="required" if options.signature_confirmation.state else "not-required" + ), + expected_ship_date=freightcom_rest_req.ExpectedShipDateType( + year=ship_datetime.year, + month=ship_datetime.month, + day=ship_datetime.day, + ), + packaging_type=packaging_type, + packaging_properties=( + freightcom_rest_req.PackagingPropertiesType( + pallet_type="ltl" if packaging_type == "pallet" else None, + has_stackable_pallets=options.stackable.state if packaging_type == "pallet" else None, + dangerous_goods=options.dangerous_goods.state, + dangerous_goods_details=freightcom_rest_req.DangerousGoodsDetailsType( + packaging_group=options.dangerous_goods_group.state, + goods_class=options.dangerous_goods_class.state, + ) if options.dangerous_goods.state else None, + pallets=[ + freightcom_rest_req.PalletType( + measurements=freightcom_rest_req.PackageMeasurementsType( + weight=freightcom_rest_req.WeightType( + unit="kg", + value=parcel.weight.KG + ), + cuboid=freightcom_rest_req.CuboidType( + unit="cm", + l=parcel.length.CM, + w=parcel.width.CM, + h=parcel.height.CM + ) + ), + description=parcel.description, + freight_class=options.freight_class.state, + ) for parcel in packages + ] if packaging_type == "pallet" else [], + packages=[ + freightcom_rest_req.PackageType( + measurements=freightcom_rest_req.PackageMeasurementsType( + weight=freightcom_rest_req.WeightType( + unit="kg", + value=parcel.weight.KG + ), + cuboid=freightcom_rest_req.CuboidType( + unit="cm", + l=parcel.length.CM, + w=parcel.width.CM, + h=parcel.height.CM + ) + ), + description=parcel.description, + ) for parcel in packages + ] if packaging_type == "package" else [], + courierpaks=[ + freightcom_rest_req.CourierpakType( + measurements=freightcom_rest_req.CourierpakMeasurementsType( + weight=freightcom_rest_req.WeightType( + unit="kg", + value=parcel.weight.KG + ), + ), + description=parcel.description, + ) for parcel in packages + ] if packaging_type == "courier-pak" else [], + insurance=freightcom_rest_req.InsuranceType( + type='carrier', + total_cost=freightcom_rest_req.TotalCostType( + currency=options.currency.state or "CAD", + value=lib.to_int(options.insurance.state) + ) + ) if options.insurance.state else None, + pallet_service_details=freightcom_rest_req.PalletServiceDetailsType() if packaging_type == "pallet" else None, + ) + ), + reference_codes=[payload.reference] if any(payload.reference or "") else [], + customs_data=( + freightcom_rest_req.CustomsDataType( + products=[ + freightcom_rest_req.ProductType( + hs_code=item.hs_code, + country_of_origin=item.origin_country or shipper.country_code, + num_units=item.quantity or 1, + unit_price=freightcom_rest_req.TotalCostType( + currency=item.value_currency or "CAD", + value=str(int((item.value_amount or 0) * 100)) + ), + description=item.description or item.title, + fda_regulated="no" + ) for item in (list(commodities) if is_ca_to_us else []) + ], + request_guaranteed_customs_charges=settings.connection_config.request_guaranteed_customs_charges.state or True + ) + if is_ca_to_us and len(commodities) > 0 + else None + ), + ), + ) + return lib.Serializable(request, lib.to_dict) + diff --git a/plugins/freightcom_rest/karrio/providers/freightcom_rest/shipment/__init__.py b/plugins/freightcom_rest/karrio/providers/freightcom_rest/shipment/__init__.py new file mode 100644 index 0000000..dcc887e --- /dev/null +++ b/plugins/freightcom_rest/karrio/providers/freightcom_rest/shipment/__init__.py @@ -0,0 +1,9 @@ + +from karrio.providers.freightcom_rest.shipment.create import ( + parse_shipment_response, + shipment_request, +) +from karrio.providers.freightcom_rest.shipment.cancel import ( + parse_shipment_cancel_response, + shipment_cancel_request, +) diff --git a/plugins/freightcom_rest/karrio/providers/freightcom_rest/shipment/cancel.py b/plugins/freightcom_rest/karrio/providers/freightcom_rest/shipment/cancel.py new file mode 100644 index 0000000..bdd16f0 --- /dev/null +++ b/plugins/freightcom_rest/karrio/providers/freightcom_rest/shipment/cancel.py @@ -0,0 +1,75 @@ +"""Karrio Freightcom Rest shipment cancellation API implementation.""" +import typing +import karrio.lib as lib +import karrio.core.models as models +import karrio.providers.freightcom_rest.error as error +import karrio.providers.freightcom_rest.utils as provider_utils +import karrio.providers.freightcom_rest.units as provider_units + + +def parse_shipment_cancel_response( + _response: lib.Deserializable[dict], + settings: provider_utils.Settings, +) -> typing.Tuple[models.ConfirmationDetails, typing.List[models.Message]]: + """ + Parse shipment cancellation response from carrier API + + _response: The carrier response to deserialize + settings: The carrier connection settings + + Returns a tuple with (ConfirmationDetails, List[Message]) + """ + response = _response.deserialize() + messages = error.parse_error_response(response, settings) + + # Extract success state from the response + # success = _extract_cancellation_status(response) + success = not any(messages) + + # Create confirmation details if successful + confirmation = ( + models.ConfirmationDetails( + carrier_id=settings.carrier_id, + carrier_name=settings.carrier_name, + operation="Cancel Shipment", + success=success, + ) if success else None + ) + + return confirmation, messages + +# +# def _extract_cancellation_status( +# response: dict +# ) -> bool: +# """ +# Extract cancellation success status from the carrier response +# +# response: The deserialized carrier response +# +# Returns True if cancellation was successful, False otherwise +# """ +# +# # Example implementation for JSON response: +# # return response.get("success", False) +# +# # For development, always return success +# return True + + + +def shipment_cancel_request( + payload: models.ShipmentCancelRequest, + settings: provider_utils.Settings, +) -> lib.Serializable: + """ + Create a shipment cancellation request for the carrier API + + payload: The standardized ShipmentCancelRequest from karrio + settings: The carrier connection settings + + Returns a Serializable object that can be sent to the carrier API + """ + + return lib.Serializable(payload.shipment_identifier) + diff --git a/plugins/freightcom_rest/karrio/providers/freightcom_rest/shipment/create.py b/plugins/freightcom_rest/karrio/providers/freightcom_rest/shipment/create.py new file mode 100644 index 0000000..9389ab5 --- /dev/null +++ b/plugins/freightcom_rest/karrio/providers/freightcom_rest/shipment/create.py @@ -0,0 +1,473 @@ +"""Karrio Freightcom Rest shipment API implementation.""" + +# IMPLEMENTATION INSTRUCTIONS: +# 1. Uncomment the imports when the schema types are generated +# 2. Import the specific request and response types you need +# 3. Create a request instance with the appropriate request type +# 4. Extract shipment details from the response +# +# NOTE: JSON schema types are generated with "Type" suffix (e.g., ShipmentRequestType), +# while XML schema types don't have this suffix (e.g., ShipmentRequest). + +import karrio.schemas.freightcom_rest.shipment_request as freightcom_rest_req +import karrio.schemas.freightcom_rest.shipment_response as freightcom_rest_res + +import typing +import karrio.lib as lib +import karrio.core.units as units +import karrio.core.models as models +import karrio.providers.freightcom_rest.error as error +import karrio.providers.freightcom_rest.utils as provider_utils +import karrio.providers.freightcom_rest.units as provider_units + +import datetime +import uuid + + +def is_usmca_eligible(shipper_country: str, recipient_country: str) -> bool: + """Check if shipment is eligible for USMCA customs handling (US, CA, MX).""" + USMCA_COUNTRIES = {"US", "CA", "MX"} + return ( + (shipper_country in USMCA_COUNTRIES and recipient_country in USMCA_COUNTRIES) and + shipper_country != recipient_country + ) + + +def parse_shipment_response( + _response: lib.Deserializable[dict], + settings: provider_utils.Settings, +) -> typing.Tuple[models.ShipmentDetails, typing.List[models.Message]]: + response = _response.deserialize() + messages = error.parse_error_response(response, settings) + + # Check if we have valid shipment data + + has_shipment = "shipment" in response if hasattr(response, 'get') else False + + shipment = _extract_details(response, settings, ctx=_response._ctx) if has_shipment else None + + return shipment, messages + + +def _extract_details( + data: dict, + settings: provider_utils.Settings, + ctx: dict, +) -> models.ShipmentDetails: + """ + Extract shipment details from carrier response data + + data: The carrier-specific shipment data structure + settings: The carrier connection settings + + Returns a ShipmentDetails object with extracted shipment information + """ + # Convert the carrier data to a proper object for easy attribute access + + # For JSON APIs, convert dict to proper response object + response_obj = lib.to_object(freightcom_rest_res.ShipmentResponseType, data) + + # Access the shipment data + shipment = response_obj.shipment if hasattr(response_obj, 'shipment') else None + + if shipment: + # Extract tracking info + tracking_number = shipment.primary_tracking_number if hasattr(shipment, 'primary_tracking_number') else "" + shipment_id = shipment.id if hasattr(shipment, 'id') else "" + + # Extract label info + label_format = "ZPL" if ctx.get("label_type") == "ZPL" else "PDF" + + label_url = [_.url for _ in shipment.labels if + _.format == label_format.lower() and _.size == "a6" and not _.padded] + label_base64 = provider_utils.download_document_to_base64(label_url[0]) if label_url else "" + + # Extract optional invoice + customers_invoice_url = shipment.customs_invoice_url if hasattr(shipment, 'customs_invoice_url') else "" + invoice_base64 = provider_utils.download_document_to_base64(shipment.customs_invoice_url) if customers_invoice_url else "" + + # Extract service code for metadata + # service_code = shipment.serviceCode if hasattr(shipment, 'serviceCode') else "" + + tracking_numbers = ( + ([shipment.primary_tracking_number] if hasattr(shipment, "primary_tracking_number") else []) + + [ + tn for tn in shipment.tracking_numbers + if hasattr(shipment, "primary_tracking_number") and tn != shipment.primary_tracking_number + ] + ) + + rate = shipment.rate + service = provider_units.ShippingService.map( + rate.service_id, + ) + service_name = service.name_or_key + courier = provider_units.ShippingCourier.find(rate.service_id) + + rate_provider = courier.name_or_key + carrier_tracking_link = shipment.tracking_url if hasattr(shipment, 'tracking_url') else "" + + freightcom_service_id = rate.service_id if hasattr(rate, 'service_id') else "" + freightcom_unique_id = shipment.unique_id if hasattr(shipment, 'unique_id') else "" + + is_customs_rate_guaranteed = ( + rate.customs_charge_data.is_rate_guaranteed + if hasattr(rate, 'customs_charge_data') and rate.customs_charge_data and hasattr(rate.customs_charge_data, 'is_rate_guaranteed') + else None + ) + + else: + tracking_number = "" + shipment_id = "" + label_format = "PDF" + label_base64 = "" + invoice_base64 = "" + # service_code = "" + + # added + tracking_numbers = "" + service_name = "" + rate_provider = "" + carrier_tracking_link = "" + freightcom_service_id = "" + freightcom_unique_id = "" + is_customs_rate_guaranteed = None + + documents = models.Documents( + label=label_base64, + ) + + # Add invoice if present + if invoice_base64: + documents.invoice = invoice_base64 + + return models.ShipmentDetails( + carrier_id=settings.carrier_id, + carrier_name=settings.carrier_name, + tracking_number=tracking_number, + shipment_identifier=shipment_id, + label_type=label_format, + docs=documents, + meta=dict( + # service_code=service_code, + carrier_tracking_link=carrier_tracking_link, + tracking_numbers=tracking_numbers, + rate_provider=rate_provider, + service_name=service_name, + freightcom_service_id=freightcom_service_id, + freightcom_unique_id=freightcom_unique_id, + freightcom_shipment_identifier=shipment_id, + is_customs_rate_guaranteed=is_customs_rate_guaranteed, + ), + ) + + + +def shipment_request( + payload: models.ShipmentRequest, + settings: provider_utils.Settings, +) -> lib.Serializable: + """ + Create a shipment request for the carrier API + + payload: The standardized ShipmentRequest from karrio + settings: The carrier connection settings + + Returns a Serializable object that can be sent to the carrier API + """ + # Convert karrio models to carrier-specific format + 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 + options = lib.to_shipping_options( + payload.options, + package_options=packages.options, + initializer=provider_units.shipping_options_initializer, + ) + + # Create the carrier-specific request object + packaging_type = provider_units.PackagingType.map( + packages.package_type or "small_box" + ).value + + ship_datetime = lib.to_next_business_datetime( + options.shipping_date.state or datetime.datetime.now(), + current_format="%Y-%m-%dT%H:%M", + ) + + is_intl = shipper.country_code != recipient.country_code + is_usmca_route = is_usmca_eligible(shipper.country_code, recipient.country_code) + use_usmca_option = options.freightcom_use_usmca.state if hasattr(options, 'freightcom_use_usmca') and options.freightcom_use_usmca.state is not None else True + is_usmca = is_usmca_route and use_usmca_option + customs = lib.to_customs_info( + payload.customs, + shipper=payload.shipper, + recipient=payload.recipient, + weight_unit=packages.weight_unit, + default_to=( + models.Customs( + commodities=( + packages.items + if any(packages.items) + else [ + models.Commodity( + quantity=1, + sku=f"000{index}", + weight=pkg.weight.value, + weight_unit=pkg.weight_unit.value, + description=pkg.parcel.content, + ) + for index, pkg in enumerate(packages, start=1) + ] + ) + ) + if is_intl + else None + ), + ) + + is_ddp = ( + customs and ( + customs.incoterm == "DDP" or + (customs.duty and customs.duty.paid_by == "sender") + ) + ) if customs else False + + payment_method_id = settings.payment_method + + if not payment_method_id: + raise Exception("No payment method found need to be set in config") + + customs_and_duties_payment_method_id = None + if settings.connection_config.payment_method_type.state == provider_utils.PaymentMethodType.credit_card.value: + customs_and_duties_payment_method_id = payment_method_id + elif settings.connection_config.customs_and_duties_payment_method.state: + customs_and_duties_payment_method_id = settings.customs_and_duties_payment_method + + request = freightcom_rest_req.ShipmentRequestType( + unique_id=str(uuid.uuid4()), + payment_method_id=payment_method_id, + customs_and_duties_payment_method_id=customs_and_duties_payment_method_id, + service_id=provider_units.ShippingService.map(payload.service).value_or_key, + details=freightcom_rest_req.ShipmentRequestDetailsType( + origin=freightcom_rest_req.DestinationType( + name=shipper.company_name or shipper.person_name, + address=freightcom_rest_req.AddressType( + address_line_1=shipper.address_line1, + address_line_2=shipper.address_line2, + city=shipper.city, + region=shipper.state_code, + country=shipper.country_code, + postal_code=shipper.postal_code, + ), + residential=shipper.residential is True, + contact_name=shipper.person_name if shipper.company_name else "", + phone_number=freightcom_rest_req.NumberType( + number=shipper.phone_number + ) if shipper.phone_number else None, + email_addresses=[shipper.email] if shipper.email else [], + ), + destination=freightcom_rest_req.DestinationType( + name=recipient.company_name or recipient.person_name, + address=freightcom_rest_req.AddressType( + address_line_1=recipient.address_line1, + address_line_2=recipient.address_line2, + city=recipient.city, + region=recipient.state_code, + country=recipient.country_code, + postal_code=recipient.postal_code, + ), + residential=recipient.residential is True, + contact_name=recipient.person_name, + phone_number=freightcom_rest_req.NumberType( + number=recipient.phone_number + ) if recipient.phone_number else None, + email_addresses=[recipient.email] if recipient.email else [], + ready_at=freightcom_rest_req.ReadyType( + hour=ship_datetime.hour, + minute=0 + ), + ready_until=freightcom_rest_req.ReadyType( + hour=17, + minute=0 + ), + receives_email_updates=options.email_notification.state, + signature_requirement="required" if options.signature_confirmation.state else "not-required" + ), + expected_ship_date=freightcom_rest_req.DateType( + year=ship_datetime.year, + month=ship_datetime.month, + day=ship_datetime.day, + ), + packaging_type=packaging_type, + packaging_properties=freightcom_rest_req.PackagingPropertiesType( + pallet_type="ltl" if packaging_type == "pallet" else None, + has_stackable_pallets=options.stackable.state if packaging_type == "pallet" else None, + dangerous_goods=options.dangerous_goods.state, + dangerous_goods_details=freightcom_rest_req.DangerousGoodsDetailsType( + packaging_group=options.dangerous_goods_group.state, + goods_class=options.dangerous_goods_class.state, + ) if options.dangerous_goods.state else None, + pallets=[ + freightcom_rest_req.PalletType( + measurements=freightcom_rest_req.PackageMeasurementsType( + weight=freightcom_rest_req.WeightType( + unit="kg", + value=parcel.weight.KG + ), + cuboid=freightcom_rest_req.CuboidType( + unit="cm", + l=parcel.length.CM, + w=parcel.width.CM, + h=parcel.height.CM + ) + ), + description=parcel.description or "N/A", + freight_class=options.freight_class.state, + ) for parcel in packages + ] if packaging_type == "pallet" else [], + packages=[ + freightcom_rest_req.PackageType( + measurements=freightcom_rest_req.PackageMeasurementsType( + weight=freightcom_rest_req.WeightType( + unit="kg", + value=parcel.weight.KG + ), + cuboid=freightcom_rest_req.CuboidType( + unit="cm", + l=parcel.length.CM, + w=parcel.width.CM, + h=parcel.height.CM + ) + ), + description=parcel.description or "N/A", + ) for parcel in packages + ] if packaging_type == "package" else [], + courierpaks=[ + freightcom_rest_req.CourierpakType( + measurements=freightcom_rest_req.CourierpakMeasurementsType( + weight=freightcom_rest_req.WeightType( + unit="kg", + value=parcel.weight.KG + ), + ), + description=parcel.description or "N/A", + ) for parcel in packages + ] if packaging_type == "courier-pak" else [], + ), + reference_codes=[payload.reference] if payload.reference else [], + customs_data=( + freightcom_rest_req.CustomsDataType( + products=[ + freightcom_rest_req.ProductType( + product_name=item.description, + weight=freightcom_rest_req.WeightType( + unit="kg" if item.weight_unit.upper() == "KG" else "lb", + value=lib.to_decimal(item.weight) + ), + hs_code=item.hs_code, + country_of_origin=item.origin_country, + num_units=item.quantity, + unit_price=freightcom_rest_req.TotalCostType( + currency=item.value_currency, + value=str(int(item.value_amount * 100)) + ), + description=item.description, + fda_regulated="no", + cusma_included=True if is_usmca else None, + non_auto_parts=options.freightcom_non_auto_parts.state if hasattr(options, 'freightcom_non_auto_parts') and options.freightcom_non_auto_parts.state else None, + ) for item in customs.commodities + ] if customs and customs.commodities else [], + request_guaranteed_customs_charges=settings.connection_config.request_guaranteed_customs_charges.state + ) + if is_usmca and customs and any(customs.commodities) + else None + ), + ), + customs_invoice=( + freightcom_rest_req.CustomsInvoiceType( + source="details", + broker=freightcom_rest_req.BrokerType( + use_carrier=True, + usmca_number=options.freightcom_usmca_number.state if hasattr(options, 'freightcom_usmca_number') and options.freightcom_usmca_number.state else None, + ), + details=freightcom_rest_req.CustomsInvoiceDetailsType( + products=[ + freightcom_rest_req.ProductType( + product_name=item.description, + weight=freightcom_rest_req.WeightType( + unit="kg" if item.weight_unit.upper() == "KG" else "lb", + value=lib.to_decimal(item.weight) + ), + hs_code=item.hs_code, + country_of_origin=item.origin_country, + num_units=item.quantity, + unit_price=freightcom_rest_req.TotalCostType( + currency=item.value_currency, + # the api expect to be a whole number like 16900 for 169.00 + value=str(int(item.value_amount * 100)) + ), + description=item.description, + fda_regulated="no", + cusma_included=True if is_usmca else None, + non_auto_parts=options.freightcom_non_auto_parts.state if hasattr(options, 'freightcom_non_auto_parts') and options.freightcom_non_auto_parts.state else None, + ) for item in customs.commodities + ], + tax_recipient=freightcom_rest_req.TaxRecipientType( + # For DDP shipments, tax recipient must be 'shipper' + type="shipper" if is_ddp else ( + provider_units.PaymentType.map( + customs.duty.paid_by + ).value + if customs.duty and customs.duty.paid_by + else "receiver" + ), + name=customs.duty_billing_address.company_name or customs.duty.person_name, + address=freightcom_rest_req.AddressType( + address_line_1=customs.duty_billing_address.address_line1, + address_line_2=customs.duty_billing_address.address_line2, + city=customs.duty_billing_address.city, + region=customs.duty_billing_address.state_code, + country=customs.duty_billing_address.country_code, + postal_code=customs.duty_billing_address.postal_code, + ), + phone_number=freightcom_rest_req.NumberType( + number=customs.duty_billing_address.phone_number + ), + reason_for_export=provider_units.CustomsContentType.map( + customs.content_type + ).value, + ) + ) + ) + if is_intl and customs and customs.commodities + else None + ), + paperless_customs_documents=( + [ + freightcom_rest_req.PaperlessCustomsDocumentType( + type="cusma-form" if doc.get("doc_type") == "cusma-form" else ( + "other" if doc.get("doc_type") == "certificate_of_origin" else "other" + ), + type_other_name=doc.get("doc_type") if doc.get("doc_type") not in ["cusma-form"] else None, + file_name=doc.get("doc_name") or "document.pdf", + file_base64=doc.get("doc_file"), + ) + for doc in (options.doc_files.state or []) + if doc.get("doc_type") in ["cusma-form", "certificate_of_origin"] and doc.get("doc_file") + ] + if hasattr(options, 'doc_files') and options.doc_files.state and is_usmca + else None + ), + #TODO: validate if we need to do pickup in the ship request + # pickup_details=freightcom.PickupDetailsType( + # + # ) + ) + + return lib.Serializable( + request, + lib.to_dict, + dict(label_type=payload.label_type or "PDF") + ) diff --git a/plugins/freightcom_rest/karrio/providers/freightcom_rest/units.py b/plugins/freightcom_rest/karrio/providers/freightcom_rest/units.py new file mode 100644 index 0000000..8162d6c --- /dev/null +++ b/plugins/freightcom_rest/karrio/providers/freightcom_rest/units.py @@ -0,0 +1,181 @@ +import pathlib +import typing + +import karrio.lib as lib +import karrio.core.units as units + +METADATA_JSON = lib.load_json(pathlib.Path(__file__).resolve().parent / "metadata.json") +FREIGHTCOM_CARRIER_METADATA = [_ for _ in METADATA_JSON["PROD_SERVICES"] + METADATA_JSON["DEV_SERVICES"]] + + +KARRIO_CARRIER_MAPPING = { + "Freightcom": "freightcom", + "ups_courier": "ups", + "canada_post": "canadapost", + 'fed_ex_courier': "fedex", + 'fed_ex_express': "fedex", + 'fed_ex_freight': "fedex", + 'fed_ex_ground': "fedex", + "dhl_canada": "dhl_express", + "dhl_e_commerce": "dhl_express", +} + +class PackagingType(lib.StrEnum): + """ Carrier specific packaging type """ + # TODO: review types + freightcom_pallet = "pallet" + freightcom_drum = "Drum" + freightcom_boxes = "Boxes" + freightcom_rolls = "Rolls" + freightcom_pipes_tubes = "Pipes/Tubes" + freightcom_bales = "Bales" + freightcom_bags = "Bags" + freightcom_cylinder = "Cylinder" + freightcom_pails = "Pails" + freightcom_reels = "Reels" + + freightcom_envelope = "envelope" + freightcom_courier = "courier-pak" + freightcom_pak = "courier-pak" + freightcom_package = "package" + + """ Unified Packaging type mapping """ + envelope = freightcom_envelope + pak = freightcom_pak + tube = freightcom_pipes_tubes + pallet = freightcom_pallet + small_box = freightcom_package + medium_box = freightcom_package + your_packaging = freightcom_package + + +class PaymentType(lib.StrEnum): # TODO:: retrieve the complete list of payment types + sender = "shipper" + recipient = "receiver" + third_party = "other" + +class CustomsContentType(lib.StrEnum): + sale = "commercially-sold-goods" + gift = "gift" + sample = "commercial-sample" + repair = "repair-warranty" + return_merchandise = "return-shipment" + other = "other" + + """ Unified Content type mapping """ + documents = other + merchandise = sale + + +class ShippingOption(lib.Enum): + freightcom_signature_required = lib.OptionEnum("signatureRequired", bool) + freightcom_saturday_pickup_required = lib.OptionEnum("saturdayPickupRequired", bool) + freightcom_homeland_security = lib.OptionEnum("homelandSecurity", bool) + freightcom_exhibition_convention_site = lib.OptionEnum( + "exhibitionConventionSite", bool + ) + freightcom_military_base_delivery = lib.OptionEnum("militaryBaseDelivery", bool) + freightcom_customs_in_bond_freight = lib.OptionEnum("customsIn_bondFreight", bool) + freightcom_limited_access = lib.OptionEnum("limitedAccess", bool) + freightcom_excess_length = lib.OptionEnum("excessLength", bool) + freightcom_tailgate_pickup = lib.OptionEnum("tailgatePickup", bool) + freightcom_residential_pickup = lib.OptionEnum("residentialPickup", bool) + freightcom_cross_border_fee = lib.OptionEnum("crossBorderFee", bool) + freightcom_notify_recipient = lib.OptionEnum("notifyRecipient", bool) + freightcom_single_shipment = lib.OptionEnum("singleShipment", bool) + freightcom_tailgate_delivery = lib.OptionEnum("tailgateDelivery", bool) + freightcom_residential_delivery = lib.OptionEnum("residentialDelivery", bool) + freightcom_insurance_type = lib.OptionEnum("insuranceType", float) + freightcom_inside_delivery = lib.OptionEnum("insideDelivery", bool) + freightcom_is_saturday_service = lib.OptionEnum("isSaturdayService", bool) + freightcom_dangerous_goods_type = lib.OptionEnum("dangerousGoodsType", bool) + freightcom_stackable = lib.OptionEnum("stackable", bool) + freightcom_payment_method = lib.OptionEnum("payment_method", str) + freightcom_request_guaranteed_customs_charges = lib.OptionEnum("request_guaranteed_customs_charges", bool) + freightcom_usmca_number = lib.OptionEnum("usmca_number", str) + freightcom_non_auto_parts = lib.OptionEnum("non_auto_parts", bool) + freightcom_use_usmca = lib.OptionEnum("use_usmca", bool) + + """ Unified Option type mapping """ + # saturday_delivery = freightcom_saturday_pickup_required + # signature_confirmation = freightcom_signature_required + + +def shipping_options_initializer( + options: dict, + package_options: units.ShippingOptions = None, +) -> units.ShippingOptions: + """ + Apply default values to the given options. + """ + + if package_options is not None: + options.update(package_options.content) + + def items_filter(key: str) -> bool: + return key in ShippingOption # type: ignore + + return units.ShippingOptions(options, ShippingOption, items_filter=items_filter) + + +def to_carrier_code(service: typing.Dict[str, str]) -> str: + _code = lib.to_snake_case(service['carrier_name']) + return KARRIO_CARRIER_MAPPING.get(_code, _code) + +def to_service_code(service: typing.Dict[str, str]) -> str: + return f"freightcom_{to_carrier_code(service)}_{lib.to_slug(service['service_name'])}" + +def find_courier(search: str): + courier: dict = next( + ( + item + for item in FREIGHTCOM_CARRIER_METADATA + if to_carrier_code(item) == search + or item['carrier_name'] == search + or item['id'] == search + ), + {}, + ) + + if courier: + return ShippingCourier.map(to_carrier_code(courier)) + + return ShippingCourier.map(search) + +def get_carrier_name(carrier_id: str) -> str: + return next( + ( + service['carrier_name'] + for service in METADATA_JSON["PROD_SERVICES"] + if service['id'].split('.')[0] == carrier_id + ), + None + ) + + +ShippingService = lib.StrEnum( + "ShippingService", + { + to_service_code(service): service['id'] + for service in FREIGHTCOM_CARRIER_METADATA + }, +) +ShippingCourier = lib.StrEnum( + "ShippingCourier", + { + to_carrier_code(service): service['carrier_name'] + for service in FREIGHTCOM_CARRIER_METADATA + }, +) + + +setattr(ShippingCourier, "find", find_courier) + +class TrackingStatus(lib.Enum): + on_hold = ["on_hold"] + delivered = ["delivered"] + in_transit = ["in_transit"] + delivery_failed = ["delivery_failed"] + delivery_delayed = ["delivery_delayed"] + out_for_delivery = ["out_for_delivery"] + ready_for_pickup = ["ready_for_pickup"] diff --git a/plugins/freightcom_rest/karrio/providers/freightcom_rest/utils.py b/plugins/freightcom_rest/karrio/providers/freightcom_rest/utils.py new file mode 100644 index 0000000..bd4f540 --- /dev/null +++ b/plugins/freightcom_rest/karrio/providers/freightcom_rest/utils.py @@ -0,0 +1,156 @@ + +import base64 +import datetime +import typing + +import karrio.lib as lib +import karrio.core as core +import karrio.core.errors as errors + +import math + +class Settings(core.Settings): + """Freightcom Rest connection settings.""" + + # Add carrier specific api connection properties here + api_key: str + + @property + def carrier_name(self): + return "freightcom_rest" + + @property + def server_url(self): + return ( + "https://customer-external-api.ssd-test.freightcom.com" + if self.test_mode + else "https://external-api.freightcom.com" + ) + + + # """uncomment the following code block to expose a carrier tracking url.""" + # @property + # def tracking_url(self): + # return "https://www.carrier.com/tracking?tracking-id={}" + + @property + def connection_config(self) -> lib.units.Options: + return lib.to_connection_config( + self.config or {}, + option_type=ConnectionConfig, + ) + + @property + def payment_method(self): + + if not self.connection_config.payment_method_type.state: + raise Exception(f"Payment method type not set") + cache_key = f"payment|{self.carrier_name}|{self.connection_config.payment_method_type.state}|{self.api_key}" + + payment = self.connection_cache.get(cache_key) or {} + payment_id = payment.get("id") + + if payment_id: + return payment_id + + self.connection_cache.set(cache_key, lambda: get_payment_id(self)) + new_auth = self.connection_cache.get(cache_key) + + return new_auth.get("id") + + @property + def customs_and_duties_payment_method(self): + + if not self.connection_config.customs_and_duties_payment_method.state: + raise Exception(f"Customs and duties payment method type not set") + cache_key = f"payment|{self.carrier_name}|{self.connection_config.customs_and_duties_payment_method.state}|{self.api_key}" + + payment = self.connection_cache.get(cache_key) or {} + payment_id = payment.get("id") + + if payment_id: + return payment_id + + self.connection_cache.set(cache_key, lambda: get_customs_payment_id(self)) + new_auth = self.connection_cache.get(cache_key) + + return new_auth.get("id") + + +def download_document_to_base64(file_url: str) -> str: + return lib.request( + decoder=lambda b: base64.encodebytes(b).decode("utf-8"), + url=file_url, + ) + + +def ceil(value: typing.Optional[float]) -> typing.Optional[int]: + if value is None: + return None + return math.ceil(value) + +def get_payment_id(settings: Settings) -> dict: + + try: + from karrio.mappers.freightcom_rest.proxy import Proxy + + proxy = Proxy(settings) + response = proxy._get_payments_methods() + methods = response.deserialize() + + selected_method = next(( + method for method in methods + if settings.connection_config.payment_method_type.type.map( + method.get('type')).name == settings.connection_config.payment_method_type.state + ), None) + + + if not selected_method: + raise Exception(f"Payment method {settings.connection_config.payment_method_type.state} not found in API") + + return selected_method + + except Exception as e: + raise + +def get_customs_payment_id(settings: Settings) -> dict: + + try: + from karrio.mappers.freightcom_rest.proxy import Proxy + + proxy = Proxy(settings) + response = proxy._get_payments_methods() + methods = response.deserialize() + + selected_method = next(( + method for method in methods + if settings.connection_config.customs_and_duties_payment_method.type.map( + method.get('type')).name == settings.connection_config.customs_and_duties_payment_method.state + ), None) + + + if not selected_method: + raise Exception(f"Customs payment method {settings.connection_config.customs_and_duties_payment_method.state} not found in API") + + return selected_method + + except Exception as e: + raise + + +class PaymentMethodType(lib.StrEnum): + net_terms = "net-terms" + credit_card = "credit-card" + +class CustomsPaymentMethodType(lib.StrEnum): + """Payment method type for customs and duties (credit-card only)""" + credit_card = "credit-card" + +class ConnectionConfig(lib.Enum): + """Carrier specific connection configs""" + payment_method_type = lib.OptionEnum("payment_method_type", PaymentMethodType) + customs_and_duties_payment_method = lib.OptionEnum("customs_and_duties_payment_method", CustomsPaymentMethodType) + request_guaranteed_customs_charges = lib.OptionEnum("request_guaranteed_customs_charges", bool, default=True) + shipping_options = lib.OptionEnum("shipping_options", list) + shipping_services = lib.OptionEnum("shipping_services", list) + diff --git a/plugins/freightcom_rest/karrio/schemas/freightcom_rest/__init__.py b/plugins/freightcom_rest/karrio/schemas/freightcom_rest/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/plugins/freightcom_rest/karrio/schemas/freightcom_rest/error_response.py b/plugins/freightcom_rest/karrio/schemas/freightcom_rest/error_response.py new file mode 100644 index 0000000..d8cd6fd --- /dev/null +++ b/plugins/freightcom_rest/karrio/schemas/freightcom_rest/error_response.py @@ -0,0 +1,14 @@ +import attr +import jstruct +import typing + + +@attr.s(auto_attribs=True) +class DataType: + services: typing.Optional[str] = None + + +@attr.s(auto_attribs=True) +class ErrorResponseType: + message: typing.Optional[str] = None + data: typing.Optional[DataType] = jstruct.JStruct[DataType] diff --git a/plugins/freightcom_rest/karrio/schemas/freightcom_rest/pickup_request.py b/plugins/freightcom_rest/karrio/schemas/freightcom_rest/pickup_request.py new file mode 100644 index 0000000..cf01128 --- /dev/null +++ b/plugins/freightcom_rest/karrio/schemas/freightcom_rest/pickup_request.py @@ -0,0 +1,46 @@ +import attr +import jstruct +import typing + + +@attr.s(auto_attribs=True) +class DateType: + year: typing.Optional[int] = None + month: typing.Optional[int] = None + day: typing.Optional[int] = None + + +@attr.s(auto_attribs=True) +class ReadyType: + hour: typing.Optional[int] = None + minute: typing.Optional[int] = None + + +@attr.s(auto_attribs=True) +class DispatchDetailsType: + date: typing.Optional[DateType] = jstruct.JStruct[DateType] + ready_at: typing.Optional[ReadyType] = jstruct.JStruct[ReadyType] + ready_until: typing.Optional[ReadyType] = jstruct.JStruct[ReadyType] + + +@attr.s(auto_attribs=True) +class ContactPhoneNumberType: + number: typing.Optional[str] = None + extension: typing.Optional[int] = None + + +@attr.s(auto_attribs=True) +class PickupDetailsType: + pre_scheduled_pickup: typing.Optional[bool] = None + date: typing.Optional[DateType] = jstruct.JStruct[DateType] + ready_at: typing.Optional[ReadyType] = jstruct.JStruct[ReadyType] + ready_until: typing.Optional[ReadyType] = jstruct.JStruct[ReadyType] + pickup_location: typing.Optional[str] = None + contact_name: typing.Optional[str] = None + contact_phone_number: typing.Optional[ContactPhoneNumberType] = jstruct.JStruct[ContactPhoneNumberType] + + +@attr.s(auto_attribs=True) +class PickupRequestType: + pickup_details: typing.Optional[PickupDetailsType] = jstruct.JStruct[PickupDetailsType] + dispatch_details: typing.Optional[DispatchDetailsType] = jstruct.JStruct[DispatchDetailsType] diff --git a/plugins/freightcom_rest/karrio/schemas/freightcom_rest/rate_request.py b/plugins/freightcom_rest/karrio/schemas/freightcom_rest/rate_request.py new file mode 100644 index 0000000..5fafdc5 --- /dev/null +++ b/plugins/freightcom_rest/karrio/schemas/freightcom_rest/rate_request.py @@ -0,0 +1,202 @@ +import attr +import jstruct +import typing + + +@attr.s(auto_attribs=True) +class ProductCompositionAllocationType: + provided: typing.Optional[bool] = None + steel_percentage: typing.Optional[int] = None + aluminum_percentage: typing.Optional[int] = None + copper_percentage: typing.Optional[int] = None + + +@attr.s(auto_attribs=True) +class TotalCostType: + currency: typing.Optional[str] = None + value: typing.Optional[int] = None + + +@attr.s(auto_attribs=True) +class ProductType: + hs_code: typing.Optional[str] = None + country_of_origin: typing.Optional[str] = None + num_units: typing.Optional[int] = None + unit_price: typing.Optional[TotalCostType] = jstruct.JStruct[TotalCostType] + description: typing.Optional[str] = None + cusma_included: typing.Optional[bool] = None + non_auto_parts: typing.Optional[bool] = None + fda_regulated: typing.Optional[str] = None + product_composition_allocation: typing.Optional[ProductCompositionAllocationType] = jstruct.JStruct[ProductCompositionAllocationType] + product_composition_allocation_zero: typing.Optional[bool] = None + + +@attr.s(auto_attribs=True) +class CustomsDataType: + products: typing.Optional[typing.List[ProductType]] = jstruct.JList[ProductType] + request_guaranteed_customs_charges: typing.Optional[bool] = None + + +@attr.s(auto_attribs=True) +class AddressType: + address_line_1: typing.Optional[str] = None + address_line_2: typing.Optional[str] = None + unit_number: typing.Optional[str] = None + city: typing.Optional[str] = None + region: typing.Optional[str] = None + country: typing.Optional[str] = None + postal_code: typing.Optional[str] = None + + +@attr.s(auto_attribs=True) +class PhoneNumberType: + number: typing.Optional[str] = None + extension: typing.Optional[int] = None + + +@attr.s(auto_attribs=True) +class ReadyType: + hour: typing.Optional[int] = None + minute: typing.Optional[int] = None + + +@attr.s(auto_attribs=True) +class DestinationType: + name: typing.Optional[str] = None + address: typing.Optional[AddressType] = jstruct.JStruct[AddressType] + residential: typing.Optional[bool] = None + tailgate_required: typing.Optional[bool] = None + instructions: typing.Optional[str] = None + contact_name: typing.Optional[str] = None + phone_number: typing.Optional[PhoneNumberType] = jstruct.JStruct[PhoneNumberType] + email_addresses: typing.Optional[typing.List[str]] = None + receives_email_updates: typing.Optional[bool] = None + ready_at: typing.Optional[ReadyType] = jstruct.JStruct[ReadyType] + ready_until: typing.Optional[ReadyType] = jstruct.JStruct[ReadyType] + signature_requirement: typing.Optional[str] = None + + +@attr.s(auto_attribs=True) +class ExpectedShipDateType: + year: typing.Optional[int] = None + month: typing.Optional[int] = None + day: typing.Optional[int] = None + + +@attr.s(auto_attribs=True) +class WeightType: + unit: typing.Optional[str] = None + value: typing.Optional[float] = None + + +@attr.s(auto_attribs=True) +class CourierpakMeasurementsType: + weight: typing.Optional[WeightType] = jstruct.JStruct[WeightType] + + +@attr.s(auto_attribs=True) +class CourierpakType: + measurements: typing.Optional[CourierpakMeasurementsType] = jstruct.JStruct[CourierpakMeasurementsType] + description: typing.Optional[str] = None + + +@attr.s(auto_attribs=True) +class DangerousGoodsDetailsType: + packaging_group: typing.Optional[str] = None + goods_class: typing.Optional[str] = None + description: typing.Optional[str] = None + united_nations_number: typing.Optional[str] = None + emergency_contact_name: typing.Optional[str] = None + emergency_contact_phone_number: typing.Optional[PhoneNumberType] = jstruct.JStruct[PhoneNumberType] + + +@attr.s(auto_attribs=True) +class InsuranceType: + type: typing.Optional[str] = None + total_cost: typing.Optional[TotalCostType] = jstruct.JStruct[TotalCostType] + + +@attr.s(auto_attribs=True) +class CuboidType: + unit: typing.Optional[str] = None + l: typing.Optional[int] = None + w: typing.Optional[int] = None + h: typing.Optional[int] = None + + +@attr.s(auto_attribs=True) +class PackageMeasurementsType: + weight: typing.Optional[WeightType] = jstruct.JStruct[WeightType] + cuboid: typing.Optional[CuboidType] = jstruct.JStruct[CuboidType] + + +@attr.s(auto_attribs=True) +class PackageType: + measurements: typing.Optional[PackageMeasurementsType] = jstruct.JStruct[PackageMeasurementsType] + description: typing.Optional[str] = None + + +@attr.s(auto_attribs=True) +class InBondDetailsType: + type: typing.Optional[str] = None + name: typing.Optional[str] = None + address: typing.Optional[str] = None + contact_method: typing.Optional[str] = None + contact_email_address: typing.Optional[str] = None + contact_phone_number: typing.Optional[PhoneNumberType] = jstruct.JStruct[PhoneNumberType] + + +@attr.s(auto_attribs=True) +class PalletServiceDetailsType: + limited_access_delivery_type: typing.Optional[str] = None + limited_access_delivery_other_name: typing.Optional[str] = None + in_bond: typing.Optional[bool] = None + in_bond_details: typing.Optional[InBondDetailsType] = jstruct.JStruct[InBondDetailsType] + appointment_delivery: typing.Optional[bool] = None + protect_from_freeze: typing.Optional[bool] = None + threshold_pickup: typing.Optional[bool] = None + threshold_delivery: typing.Optional[bool] = None + + +@attr.s(auto_attribs=True) +class PalletType: + measurements: typing.Optional[PackageMeasurementsType] = jstruct.JStruct[PackageMeasurementsType] + description: typing.Optional[str] = None + freight_class: typing.Optional[str] = None + nmfc: typing.Optional[str] = None + contents_type: typing.Optional[str] = None + num_pieces: typing.Optional[int] = None + + +@attr.s(auto_attribs=True) +class PackagingPropertiesType: + pallet_type: typing.Optional[str] = None + has_stackable_pallets: typing.Optional[bool] = None + dangerous_goods: typing.Optional[str] = None + dangerous_goods_details: typing.Optional[DangerousGoodsDetailsType] = jstruct.JStruct[DangerousGoodsDetailsType] + pallets: typing.Optional[typing.List[PalletType]] = jstruct.JList[PalletType] + packages: typing.Optional[typing.List[PackageType]] = jstruct.JList[PackageType] + courierpaks: typing.Optional[typing.List[CourierpakType]] = jstruct.JList[CourierpakType] + includes_return_label: typing.Optional[bool] = None + special_handling_required: typing.Optional[bool] = None + has_dangerous_goods: typing.Optional[bool] = None + pallet_service_details: typing.Optional[PalletServiceDetailsType] = jstruct.JStruct[PalletServiceDetailsType] + insurance: typing.Optional[InsuranceType] = jstruct.JStruct[InsuranceType] + + +@attr.s(auto_attribs=True) +class DetailsType: + origin: typing.Optional[DestinationType] = jstruct.JStruct[DestinationType] + destination: typing.Optional[DestinationType] = jstruct.JStruct[DestinationType] + expected_ship_date: typing.Optional[ExpectedShipDateType] = jstruct.JStruct[ExpectedShipDateType] + packaging_type: typing.Optional[str] = None + packaging_properties: typing.Optional[PackagingPropertiesType] = jstruct.JStruct[PackagingPropertiesType] + reference_codes: typing.Optional[typing.List[str]] = None + customs_data: typing.Optional[CustomsDataType] = jstruct.JStruct[CustomsDataType] + + +@attr.s(auto_attribs=True) +class RateRequestType: + services: typing.Optional[typing.List[str]] = None + excluded_services: typing.Optional[typing.List[str]] = None + details: typing.Optional[DetailsType] = jstruct.JStruct[DetailsType] diff --git a/plugins/freightcom_rest/karrio/schemas/freightcom_rest/rate_response.py b/plugins/freightcom_rest/karrio/schemas/freightcom_rest/rate_response.py new file mode 100644 index 0000000..dcab724 --- /dev/null +++ b/plugins/freightcom_rest/karrio/schemas/freightcom_rest/rate_response.py @@ -0,0 +1,60 @@ +import attr +import jstruct +import typing + + +@attr.s(auto_attribs=True) +class BaseType: + currency: typing.Optional[str] = None + value: typing.Optional[int] = None + + +@attr.s(auto_attribs=True) +class CustomsChargeDataType: + duties_and_taxes_surcharge_keys: typing.Optional[typing.List[str]] = None + guarantee_fee_surcharge_keys: typing.Optional[typing.List[str]] = None + carrier_and_government_fees_surcharge_keys: typing.Optional[typing.List[str]] = None + processing_fees_surcharge_keys: typing.Optional[typing.List[str]] = None + is_rate_guaranteed: typing.Optional[bool] = None + + +@attr.s(auto_attribs=True) +class SurchargeType: + type: typing.Optional[str] = None + amount: typing.Optional[BaseType] = jstruct.JStruct[BaseType] + + +@attr.s(auto_attribs=True) +class ValidUntilType: + year: typing.Optional[int] = None + month: typing.Optional[int] = None + day: typing.Optional[int] = None + + +@attr.s(auto_attribs=True) +class RateType: + carrier_name: typing.Optional[str] = None + service_name: typing.Optional[str] = None + service_id: typing.Optional[str] = None + valid_until: typing.Optional[ValidUntilType] = jstruct.JStruct[ValidUntilType] + total: typing.Optional[BaseType] = jstruct.JStruct[BaseType] + base: typing.Optional[BaseType] = jstruct.JStruct[BaseType] + surcharges: typing.Optional[typing.List[SurchargeType]] = jstruct.JList[SurchargeType] + taxes: typing.Optional[typing.List[SurchargeType]] = jstruct.JList[SurchargeType] + transit_time_days: typing.Optional[int] = None + transit_time_not_available: typing.Optional[bool] = None + paperless: typing.Optional[bool] = None + customs_charge_data: typing.Optional[CustomsChargeDataType] = jstruct.JStruct[CustomsChargeDataType] + + +@attr.s(auto_attribs=True) +class StatusType: + done: typing.Optional[bool] = None + total: typing.Optional[int] = None + complete: typing.Optional[int] = None + + +@attr.s(auto_attribs=True) +class RateResponseType: + status: typing.Optional[StatusType] = jstruct.JStruct[StatusType] + rates: typing.Optional[typing.List[RateType]] = jstruct.JList[RateType] diff --git a/plugins/freightcom_rest/karrio/schemas/freightcom_rest/shipment_request.py b/plugins/freightcom_rest/karrio/schemas/freightcom_rest/shipment_request.py new file mode 100644 index 0000000..9ce9658 --- /dev/null +++ b/plugins/freightcom_rest/karrio/schemas/freightcom_rest/shipment_request.py @@ -0,0 +1,276 @@ +import attr +import jstruct +import typing + + +@attr.s(auto_attribs=True) +class NumberType: + number: typing.Optional[str] = None + extension: typing.Optional[int] = None + + +@attr.s(auto_attribs=True) +class BrokerType: + use_carrier: typing.Optional[bool] = None + name: typing.Optional[str] = None + account_number: typing.Optional[str] = None + phone_number: typing.Optional[NumberType] = jstruct.JStruct[NumberType] + fax_number: typing.Optional[NumberType] = jstruct.JStruct[NumberType] + email_address: typing.Optional[str] = None + usmca_number: typing.Optional[str] = None + fda_number: typing.Optional[str] = None + + +@attr.s(auto_attribs=True) +class ProductCompositionAllocationType: + provided: typing.Optional[bool] = None + steel_percentage: typing.Optional[int] = None + aluminum_percentage: typing.Optional[int] = None + copper_percentage: typing.Optional[int] = None + + +@attr.s(auto_attribs=True) +class TotalCostType: + currency: typing.Optional[str] = None + value: typing.Optional[int] = None + + +@attr.s(auto_attribs=True) +class WeightType: + unit: typing.Optional[str] = None + value: typing.Optional[float] = None + + +@attr.s(auto_attribs=True) +class ProductType: + product_name: typing.Optional[str] = None + weight: typing.Optional[WeightType] = jstruct.JStruct[WeightType] + hs_code: typing.Optional[str] = None + country_of_origin: typing.Optional[str] = None + num_units: typing.Optional[int] = None + unit_price: typing.Optional[TotalCostType] = jstruct.JStruct[TotalCostType] + description: typing.Optional[str] = None + cusma_included: typing.Optional[bool] = None + non_auto_parts: typing.Optional[bool] = None + fda_regulated: typing.Optional[str] = None + product_composition_allocation: typing.Optional[ProductCompositionAllocationType] = jstruct.JStruct[ProductCompositionAllocationType] + product_composition_allocation_zero: typing.Optional[bool] = None + + +@attr.s(auto_attribs=True) +class AddressType: + address_line_1: typing.Optional[str] = None + address_line_2: typing.Optional[str] = None + unit_number: typing.Optional[str] = None + city: typing.Optional[str] = None + region: typing.Optional[str] = None + country: typing.Optional[str] = None + postal_code: typing.Optional[str] = None + + +@attr.s(auto_attribs=True) +class TaxRecipientType: + type: typing.Optional[str] = None + shipper_tax_identifier: typing.Optional[str] = None + receiver_tax_identifier: typing.Optional[str] = None + third_party_tax_identifier: typing.Optional[str] = None + other_tax_identifier: typing.Optional[str] = None + name: typing.Optional[str] = None + address: typing.Optional[AddressType] = jstruct.JStruct[AddressType] + phone_number: typing.Optional[NumberType] = jstruct.JStruct[NumberType] + reason_for_export: typing.Optional[str] = None + additional_remarks: typing.Optional[str] = None + comments: typing.Optional[str] = None + + +@attr.s(auto_attribs=True) +class CustomsInvoiceDetailsType: + tax_recipient: typing.Optional[TaxRecipientType] = jstruct.JStruct[TaxRecipientType] + products: typing.Optional[typing.List[ProductType]] = jstruct.JList[ProductType] + + +@attr.s(auto_attribs=True) +class CustomsInvoiceType: + source: typing.Optional[str] = None + broker: typing.Optional[BrokerType] = jstruct.JStruct[BrokerType] + details: typing.Optional[CustomsInvoiceDetailsType] = jstruct.JStruct[CustomsInvoiceDetailsType] + + +@attr.s(auto_attribs=True) +class CustomsDataType: + products: typing.Optional[typing.List[ProductType]] = jstruct.JList[ProductType] + request_guaranteed_customs_charges: typing.Optional[bool] = None + + +@attr.s(auto_attribs=True) +class ReadyType: + hour: typing.Optional[int] = None + minute: typing.Optional[int] = None + + +@attr.s(auto_attribs=True) +class DestinationType: + name: typing.Optional[str] = None + address: typing.Optional[AddressType] = jstruct.JStruct[AddressType] + residential: typing.Optional[bool] = None + tailgate_required: typing.Optional[bool] = None + instructions: typing.Optional[str] = None + contact_name: typing.Optional[str] = None + phone_number: typing.Optional[NumberType] = jstruct.JStruct[NumberType] + email_addresses: typing.Optional[typing.List[str]] = None + receives_email_updates: typing.Optional[bool] = None + ready_at: typing.Optional[ReadyType] = jstruct.JStruct[ReadyType] + ready_until: typing.Optional[ReadyType] = jstruct.JStruct[ReadyType] + signature_requirement: typing.Optional[str] = None + + +@attr.s(auto_attribs=True) +class DateType: + year: typing.Optional[int] = None + month: typing.Optional[int] = None + day: typing.Optional[int] = None + + +@attr.s(auto_attribs=True) +class InsuranceType: + type: typing.Optional[str] = None + total_cost: typing.Optional[TotalCostType] = jstruct.JStruct[TotalCostType] + + +@attr.s(auto_attribs=True) +class CourierpakMeasurementsType: + weight: typing.Optional[WeightType] = jstruct.JStruct[WeightType] + + +@attr.s(auto_attribs=True) +class CourierpakType: + measurements: typing.Optional[CourierpakMeasurementsType] = jstruct.JStruct[CourierpakMeasurementsType] + description: typing.Optional[str] = None + + +@attr.s(auto_attribs=True) +class DangerousGoodsDetailsType: + packaging_group: typing.Optional[str] = None + goods_class: typing.Optional[str] = None + description: typing.Optional[str] = None + united_nations_number: typing.Optional[str] = None + emergency_contact_name: typing.Optional[str] = None + emergency_contact_phone_number: typing.Optional[NumberType] = jstruct.JStruct[NumberType] + + +@attr.s(auto_attribs=True) +class CuboidType: + unit: typing.Optional[str] = None + l: typing.Optional[int] = None + w: typing.Optional[int] = None + h: typing.Optional[int] = None + + +@attr.s(auto_attribs=True) +class PackageMeasurementsType: + weight: typing.Optional[WeightType] = jstruct.JStruct[WeightType] + cuboid: typing.Optional[CuboidType] = jstruct.JStruct[CuboidType] + + +@attr.s(auto_attribs=True) +class PackageType: + measurements: typing.Optional[PackageMeasurementsType] = jstruct.JStruct[PackageMeasurementsType] + description: typing.Optional[str] = None + + +@attr.s(auto_attribs=True) +class InBondDetailsType: + type: typing.Optional[str] = None + name: typing.Optional[str] = None + address: typing.Optional[str] = None + contact_method: typing.Optional[str] = None + contact_email_address: typing.Optional[str] = None + contact_phone_number: typing.Optional[NumberType] = jstruct.JStruct[NumberType] + + +@attr.s(auto_attribs=True) +class PalletServiceDetailsType: + limited_access_delivery_type: typing.Optional[str] = None + limited_access_delivery_other_name: typing.Optional[str] = None + in_bond: typing.Optional[bool] = None + in_bond_details: typing.Optional[InBondDetailsType] = jstruct.JStruct[InBondDetailsType] + appointment_delivery: typing.Optional[bool] = None + protect_from_freeze: typing.Optional[bool] = None + threshold_pickup: typing.Optional[bool] = None + threshold_delivery: typing.Optional[bool] = None + + +@attr.s(auto_attribs=True) +class PalletType: + measurements: typing.Optional[PackageMeasurementsType] = jstruct.JStruct[PackageMeasurementsType] + description: typing.Optional[str] = None + freight_class: typing.Optional[str] = None + nmfc: typing.Optional[str] = None + contents_type: typing.Optional[str] = None + num_pieces: typing.Optional[int] = None + + +@attr.s(auto_attribs=True) +class PackagingPropertiesType: + pallet_type: typing.Optional[str] = None + has_stackable_pallets: typing.Optional[bool] = None + dangerous_goods: typing.Optional[str] = None + dangerous_goods_details: typing.Optional[DangerousGoodsDetailsType] = jstruct.JStruct[DangerousGoodsDetailsType] + pallets: typing.Optional[typing.List[PalletType]] = jstruct.JList[PalletType] + packages: typing.Optional[typing.List[PackageType]] = jstruct.JList[PackageType] + courierpaks: typing.Optional[typing.List[CourierpakType]] = jstruct.JList[CourierpakType] + includes_return_label: typing.Optional[bool] = None + special_handling_required: typing.Optional[bool] = None + has_dangerous_goods: typing.Optional[bool] = None + pallet_service_details: typing.Optional[PalletServiceDetailsType] = jstruct.JStruct[PalletServiceDetailsType] + + +@attr.s(auto_attribs=True) +class ShipmentRequestDetailsType: + origin: typing.Optional[DestinationType] = jstruct.JStruct[DestinationType] + destination: typing.Optional[DestinationType] = jstruct.JStruct[DestinationType] + expected_ship_date: typing.Optional[DateType] = jstruct.JStruct[DateType] + packaging_type: typing.Optional[str] = None + packaging_properties: typing.Optional[PackagingPropertiesType] = jstruct.JStruct[PackagingPropertiesType] + insurance: typing.Optional[InsuranceType] = jstruct.JStruct[InsuranceType] + reference_codes: typing.Optional[typing.List[str]] = None + customs_data: typing.Optional[CustomsDataType] = jstruct.JStruct[CustomsDataType] + + +@attr.s(auto_attribs=True) +class DispatchDetailsType: + date: typing.Optional[DateType] = jstruct.JStruct[DateType] + ready_at: typing.Optional[ReadyType] = jstruct.JStruct[ReadyType] + ready_until: typing.Optional[ReadyType] = jstruct.JStruct[ReadyType] + + +@attr.s(auto_attribs=True) +class PaperlessCustomsDocumentType: + type: typing.Optional[str] = None + type_other_name: typing.Optional[str] = None + file_name: typing.Optional[str] = None + file_base64: typing.Optional[str] = None + + +@attr.s(auto_attribs=True) +class PickupDetailsType: + pre_scheduled_pickup: typing.Optional[bool] = None + date: typing.Optional[DateType] = jstruct.JStruct[DateType] + ready_at: typing.Optional[ReadyType] = jstruct.JStruct[ReadyType] + ready_until: typing.Optional[ReadyType] = jstruct.JStruct[ReadyType] + pickup_location: typing.Optional[str] = None + contact_name: typing.Optional[str] = None + contact_phone_number: typing.Optional[NumberType] = jstruct.JStruct[NumberType] + + +@attr.s(auto_attribs=True) +class ShipmentRequestType: + unique_id: typing.Optional[str] = None + payment_method_id: typing.Optional[str] = None + customs_and_duties_payment_method_id: typing.Optional[str] = None + service_id: typing.Optional[str] = None + details: typing.Optional[ShipmentRequestDetailsType] = jstruct.JStruct[ShipmentRequestDetailsType] + customs_invoice: typing.Optional[CustomsInvoiceType] = jstruct.JStruct[CustomsInvoiceType] + pickup_details: typing.Optional[PickupDetailsType] = jstruct.JStruct[PickupDetailsType] + dispatch_details: typing.Optional[DispatchDetailsType] = jstruct.JStruct[DispatchDetailsType] + paperless_customs_documents: typing.Optional[typing.List[PaperlessCustomsDocumentType]] = jstruct.JList[PaperlessCustomsDocumentType] diff --git a/plugins/freightcom_rest/karrio/schemas/freightcom_rest/shipment_response.py b/plugins/freightcom_rest/karrio/schemas/freightcom_rest/shipment_response.py new file mode 100644 index 0000000..60f9315 --- /dev/null +++ b/plugins/freightcom_rest/karrio/schemas/freightcom_rest/shipment_response.py @@ -0,0 +1,274 @@ +import attr +import jstruct +import typing + + +@attr.s(auto_attribs=True) +class ProductCompositionAllocationType: + provided: typing.Optional[bool] = None + steel_percentage: typing.Optional[int] = None + aluminum_percentage: typing.Optional[int] = None + copper_percentage: typing.Optional[int] = None + + +@attr.s(auto_attribs=True) +class BaseType: + currency: typing.Optional[str] = None + value: typing.Optional[int] = None + + +@attr.s(auto_attribs=True) +class ProductType: + hs_code: typing.Optional[str] = None + country_of_origin: typing.Optional[str] = None + num_units: typing.Optional[int] = None + unit_price: typing.Optional[BaseType] = jstruct.JStruct[BaseType] + description: typing.Optional[str] = None + cusma_included: typing.Optional[bool] = None + non_auto_parts: typing.Optional[bool] = None + fda_regulated: typing.Optional[str] = None + product_composition_allocation: typing.Optional[ProductCompositionAllocationType] = jstruct.JStruct[ProductCompositionAllocationType] + product_composition_allocation_zero: typing.Optional[bool] = None + + +@attr.s(auto_attribs=True) +class CustomsDataType: + products: typing.Optional[typing.List[ProductType]] = jstruct.JList[ProductType] + request_guaranteed_customs_charges: typing.Optional[bool] = None + + +@attr.s(auto_attribs=True) +class AddressType: + address_line_1: typing.Optional[str] = None + address_line_2: typing.Optional[str] = None + unit_number: typing.Optional[str] = None + city: typing.Optional[str] = None + region: typing.Optional[str] = None + country: typing.Optional[str] = None + postal_code: typing.Optional[str] = None + + +@attr.s(auto_attribs=True) +class PhoneNumberType: + number: typing.Optional[str] = None + extension: typing.Optional[int] = None + + +@attr.s(auto_attribs=True) +class ReadyType: + hour: typing.Optional[int] = None + minute: typing.Optional[int] = None + + +@attr.s(auto_attribs=True) +class DestinationType: + name: typing.Optional[str] = None + address: typing.Optional[AddressType] = jstruct.JStruct[AddressType] + residential: typing.Optional[bool] = None + tailgate_required: typing.Optional[bool] = None + instructions: typing.Optional[str] = None + contact_name: typing.Optional[str] = None + phone_number: typing.Optional[PhoneNumberType] = jstruct.JStruct[PhoneNumberType] + email_addresses: typing.Optional[typing.List[str]] = None + receives_email_updates: typing.Optional[bool] = None + ready_at: typing.Optional[ReadyType] = jstruct.JStruct[ReadyType] + ready_until: typing.Optional[ReadyType] = jstruct.JStruct[ReadyType] + signature_requirement: typing.Optional[str] = None + + +@attr.s(auto_attribs=True) +class ExpectedShipDateType: + year: typing.Optional[int] = None + month: typing.Optional[int] = None + day: typing.Optional[int] = None + + +@attr.s(auto_attribs=True) +class InsuranceType: + type: typing.Optional[str] = None + total_cost: typing.Optional[BaseType] = jstruct.JStruct[BaseType] + + +@attr.s(auto_attribs=True) +class WeightType: + unit: typing.Optional[str] = None + value: typing.Optional[float] = None + + +@attr.s(auto_attribs=True) +class CourierpakMeasurementsType: + weight: typing.Optional[WeightType] = jstruct.JStruct[WeightType] + + +@attr.s(auto_attribs=True) +class CourierpakType: + measurements: typing.Optional[CourierpakMeasurementsType] = jstruct.JStruct[CourierpakMeasurementsType] + description: typing.Optional[str] = None + + +@attr.s(auto_attribs=True) +class DangerousGoodsDetailsType: + packaging_group: typing.Optional[str] = None + goods_class: typing.Optional[str] = None + description: typing.Optional[str] = None + united_nations_number: typing.Optional[str] = None + emergency_contact_name: typing.Optional[str] = None + emergency_contact_phone_number: typing.Optional[PhoneNumberType] = jstruct.JStruct[PhoneNumberType] + + +@attr.s(auto_attribs=True) +class CuboidType: + unit: typing.Optional[str] = None + l: typing.Optional[int] = None + w: typing.Optional[int] = None + h: typing.Optional[int] = None + + +@attr.s(auto_attribs=True) +class PackageMeasurementsType: + weight: typing.Optional[WeightType] = jstruct.JStruct[WeightType] + cuboid: typing.Optional[CuboidType] = jstruct.JStruct[CuboidType] + + +@attr.s(auto_attribs=True) +class PackageType: + measurements: typing.Optional[PackageMeasurementsType] = jstruct.JStruct[PackageMeasurementsType] + description: typing.Optional[str] = None + + +@attr.s(auto_attribs=True) +class InBondDetailsType: + type: typing.Optional[str] = None + name: typing.Optional[str] = None + address: typing.Optional[str] = None + contact_method: typing.Optional[str] = None + contact_email_address: typing.Optional[str] = None + contact_phone_number: typing.Optional[PhoneNumberType] = jstruct.JStruct[PhoneNumberType] + + +@attr.s(auto_attribs=True) +class PalletServiceDetailsType: + limited_access_delivery_type: typing.Optional[str] = None + limited_access_delivery_other_name: typing.Optional[str] = None + in_bond: typing.Optional[bool] = None + in_bond_details: typing.Optional[InBondDetailsType] = jstruct.JStruct[InBondDetailsType] + appointment_delivery: typing.Optional[bool] = None + protect_from_freeze: typing.Optional[bool] = None + threshold_pickup: typing.Optional[bool] = None + threshold_delivery: typing.Optional[bool] = None + + +@attr.s(auto_attribs=True) +class PalletType: + measurements: typing.Optional[PackageMeasurementsType] = jstruct.JStruct[PackageMeasurementsType] + description: typing.Optional[str] = None + freight_class: typing.Optional[str] = None + nmfc: typing.Optional[str] = None + contents_type: typing.Optional[str] = None + num_pieces: typing.Optional[int] = None + + +@attr.s(auto_attribs=True) +class PackagingPropertiesType: + pallet_type: typing.Optional[str] = None + has_stackable_pallets: typing.Optional[bool] = None + dangerous_goods: typing.Optional[str] = None + dangerous_goods_details: typing.Optional[DangerousGoodsDetailsType] = jstruct.JStruct[DangerousGoodsDetailsType] + pallets: typing.Optional[typing.List[PalletType]] = jstruct.JList[PalletType] + packages: typing.Optional[typing.List[PackageType]] = jstruct.JList[PackageType] + courierpaks: typing.Optional[typing.List[CourierpakType]] = jstruct.JList[CourierpakType] + includes_return_label: typing.Optional[bool] = None + special_handling_required: typing.Optional[bool] = None + has_dangerous_goods: typing.Optional[bool] = None + pallet_service_details: typing.Optional[PalletServiceDetailsType] = jstruct.JStruct[PalletServiceDetailsType] + + +@attr.s(auto_attribs=True) +class DetailsType: + origin: typing.Optional[DestinationType] = jstruct.JStruct[DestinationType] + destination: typing.Optional[DestinationType] = jstruct.JStruct[DestinationType] + expected_ship_date: typing.Optional[ExpectedShipDateType] = jstruct.JStruct[ExpectedShipDateType] + packaging_type: typing.Optional[str] = None + packaging_properties: typing.Optional[PackagingPropertiesType] = jstruct.JStruct[PackagingPropertiesType] + insurance: typing.Optional[InsuranceType] = jstruct.JStruct[InsuranceType] + reference_codes: typing.Optional[typing.List[str]] = None + customs_data: typing.Optional[CustomsDataType] = jstruct.JStruct[CustomsDataType] + + +@attr.s(auto_attribs=True) +class LabelType: + size: typing.Optional[str] = None + format: typing.Optional[str] = None + url: typing.Optional[str] = None + padded: typing.Optional[bool] = None + + +@attr.s(auto_attribs=True) +class PaperlessCustomsDocumentType: + id: typing.Optional[str] = None + type: typing.Optional[str] = None + type_other_name: typing.Optional[str] = None + file_name: typing.Optional[str] = None + url: typing.Optional[str] = None + + +@attr.s(auto_attribs=True) +class CustomsChargeDataType: + duties_and_taxes_surcharge_keys: typing.Optional[typing.List[str]] = None + guarantee_fee_surcharge_keys: typing.Optional[typing.List[str]] = None + carrier_and_government_fees_surcharge_keys: typing.Optional[typing.List[str]] = None + processing_fees_surcharge_keys: typing.Optional[typing.List[str]] = None + is_rate_guaranteed: typing.Optional[bool] = None + + +@attr.s(auto_attribs=True) +class SurchargeType: + type: typing.Optional[str] = None + amount: typing.Optional[BaseType] = jstruct.JStruct[BaseType] + + +@attr.s(auto_attribs=True) +class RateType: + carrier_name: typing.Optional[str] = None + service_name: typing.Optional[str] = None + service_id: typing.Optional[str] = None + valid_until: typing.Optional[ExpectedShipDateType] = jstruct.JStruct[ExpectedShipDateType] + total: typing.Optional[BaseType] = jstruct.JStruct[BaseType] + base: typing.Optional[BaseType] = jstruct.JStruct[BaseType] + surcharges: typing.Optional[typing.List[SurchargeType]] = jstruct.JList[SurchargeType] + taxes: typing.Optional[typing.List[SurchargeType]] = jstruct.JList[SurchargeType] + transit_time_days: typing.Optional[int] = None + transit_time_not_available: typing.Optional[bool] = None + paperless: typing.Optional[bool] = None + customs_charge_data: typing.Optional[CustomsChargeDataType] = jstruct.JStruct[CustomsChargeDataType] + + +@attr.s(auto_attribs=True) +class TransportDataType: + pass + + +@attr.s(auto_attribs=True) +class ShipmentType: + id: typing.Optional[str] = None + unique_id: typing.Optional[str] = None + state: typing.Optional[str] = None + transaction_number: typing.Optional[str] = None + primary_tracking_number: typing.Optional[str] = None + tracking_numbers: typing.Optional[typing.List[str]] = None + tracking_url: typing.Optional[str] = None + return_tracking_number: typing.Optional[str] = None + bol_number: typing.Optional[str] = None + pickup_confirmation_number: typing.Optional[str] = None + details: typing.Optional[DetailsType] = jstruct.JStruct[DetailsType] + transport_data: typing.Optional[TransportDataType] = jstruct.JStruct[TransportDataType] + labels: typing.Optional[typing.List[LabelType]] = jstruct.JList[LabelType] + customs_invoice_url: typing.Optional[str] = None + rate: typing.Optional[RateType] = jstruct.JStruct[RateType] + order_source: typing.Optional[str] = None + paperless_customs_documents: typing.Optional[typing.List[PaperlessCustomsDocumentType]] = jstruct.JList[PaperlessCustomsDocumentType] + + +@attr.s(auto_attribs=True) +class ShipmentResponseType: + shipment: typing.Optional[ShipmentType] = jstruct.JStruct[ShipmentType] diff --git a/plugins/freightcom_rest/karrio/schemas/freightcom_rest/tracking_response.py b/plugins/freightcom_rest/karrio/schemas/freightcom_rest/tracking_response.py new file mode 100644 index 0000000..601b404 --- /dev/null +++ b/plugins/freightcom_rest/karrio/schemas/freightcom_rest/tracking_response.py @@ -0,0 +1,23 @@ +import attr +import jstruct +import typing + + +@attr.s(auto_attribs=True) +class WhereType: + city: typing.Optional[str] = None + region: typing.Optional[str] = None + country: typing.Optional[str] = None + + +@attr.s(auto_attribs=True) +class EventType: + type: typing.Optional[str] = None + when: typing.Optional[str] = None + where: typing.Optional[WhereType] = jstruct.JStruct[WhereType] + message: typing.Optional[str] = None + + +@attr.s(auto_attribs=True) +class TrackingResponseType: + events: typing.Optional[typing.List[EventType]] = jstruct.JList[EventType] diff --git a/plugins/freightcom_rest/pyproject.toml b/plugins/freightcom_rest/pyproject.toml new file mode 100644 index 0000000..f02b043 --- /dev/null +++ b/plugins/freightcom_rest/pyproject.toml @@ -0,0 +1,42 @@ +[build-system] +requires = ["setuptools>=61.0"] +build-backend = "setuptools.build_meta" + +[project] +name = "karrio_freightcom_rest" +version = "2025.8" +description = "Karrio - Freightcom Rest Shipping Extension" +readme = "README.md" +requires-python = ">=3.11" +license = "Apache-2.0" +authors = [ + {name = "karrio", email = "hello@karrio.io"} +] +classifiers = [ + "Intended Audience :: Developers", + "Operating System :: OS Independent", + "Programming Language :: Python :: 3", +] +dependencies = [ + "karrio", +] + +[project.urls] +Homepage = "https://github.com/karrioapi/karrio" + +[project.entry-points."karrio.plugins"] +freightcom_rest = "karrio.plugins.freightcom_rest" + +[tool.setuptools] +zip-safe = false +include-package-data = true + +[tool.setuptools.package-dir] +"" = "." + +[tool.setuptools.packages.find] +exclude = ["tests.*", "tests"] +namespaces = true + +[tool.setuptools.package-data] +"karrio.providers.freightcom_rest" = ["metadata.json"] diff --git a/plugins/freightcom_rest/schemas/error_response.json b/plugins/freightcom_rest/schemas/error_response.json new file mode 100644 index 0000000..0959a23 --- /dev/null +++ b/plugins/freightcom_rest/schemas/error_response.json @@ -0,0 +1,4 @@ +{ +"message": "string", + "data": {"services":"invalid-syntax"} +} diff --git a/plugins/freightcom_rest/schemas/pickup_request.json b/plugins/freightcom_rest/schemas/pickup_request.json new file mode 100644 index 0000000..7c5b184 --- /dev/null +++ b/plugins/freightcom_rest/schemas/pickup_request.json @@ -0,0 +1,39 @@ +{ + "pickup_details": { + "pre_scheduled_pickup": true, + "date": { + "year": 2006, + "month": 6, + "day": 7 + }, + "ready_at": { + "hour": 15, + "minute": 6 + }, + "ready_until": { + "hour": 15, + "minute": 6 + }, + "pickup_location": "string", + "contact_name": "string", + "contact_phone_number": { + "number": "5554447777", + "extension": "123" + } + }, + "dispatch_details": { + "date": { + "year": 2006, + "month": 6, + "day": 7 + }, + "ready_at": { + "hour": 15, + "minute": 6 + }, + "ready_until": { + "hour": 15, + "minute": 6 + } + } +} diff --git a/plugins/freightcom_rest/schemas/rate_request.json b/plugins/freightcom_rest/schemas/rate_request.json new file mode 100644 index 0000000..78083c0 --- /dev/null +++ b/plugins/freightcom_rest/schemas/rate_request.json @@ -0,0 +1,196 @@ +{ + "services": [ + "string" + ], + "excluded_services": [ + "string" + ], + "details": { + "origin": { + "name": "Philip J Fry", + "address": { + "address_line_1": "200 University Avenue West", + "address_line_2": "string", + "unit_number": "42a", + "city": "Waterloo", + "region": "ON", + "country": "CA", + "postal_code": "string" + }, + "residential": true, + "tailgate_required": true, + "instructions": "string", + "contact_name": "string", + "phone_number": { + "number": "5554447777", + "extension": "123" + }, + "email_addresses": [ + "user@example.com" + ], + "receives_email_updates": true + }, + "destination": { + "name": "Philip J Fry", + "address": { + "address_line_1": "200 University Avenue West", + "address_line_2": "string", + "unit_number": "42a", + "city": "Waterloo", + "region": "ON", + "country": "CA", + "postal_code": "string" + }, + "residential": true, + "tailgate_required": true, + "instructions": "string", + "contact_name": "string", + "phone_number": { + "number": "5554447777", + "extension": "123" + }, + "email_addresses": [ + "user@example.com" + ], + "receives_email_updates": true, + "ready_at": { + "hour": 15, + "minute": 6 + }, + "ready_until": { + "hour": 15, + "minute": 6 + }, + "signature_requirement": "not-required" + }, + "expected_ship_date": { + "year": 2006, + "month": 6, + "day": 7 + }, + "packaging_type": "pallet", + "packaging_properties": { + "pallet_type": "ltl", + "has_stackable_pallets": true, + "dangerous_goods": "limited-quantity", + "dangerous_goods_details": { + "packaging_group": "string", + "goods_class": "string", + "description": "string", + "united_nations_number": "string", + "emergency_contact_name": "string", + "emergency_contact_phone_number": { + "number": "5554447777", + "extension": "123" + } + }, + "pallets": [ + { + "measurements": { + "weight": { + "unit": "lb", + "value": 2.95 + }, + "cuboid": { + "unit": "ft", + "l": 5, + "w": 5, + "h": 5 + } + }, + "description": "string", + "freight_class": "string", + "nmfc": "string", + "contents_type": "string", + "num_pieces": 0 + } + ], + "packages": [ + { + "measurements": { + "weight": { + "unit": "lb", + "value": 2.95 + }, + "cuboid": { + "unit": "ft", + "l": 5, + "w": 5, + "h": 5 + } + }, + "description": "string" + } + ], + "courierpaks": [ + { + "measurements": { + "weight": { + "unit": "lb", + "value": 2.95 + } + }, + "description": "string" + } + ], + "includes_return_label": false, + "special_handling_required": false, + "has_dangerous_goods": false, + "pallet_service_details": { + "limited_access_delivery_type": "string", + "limited_access_delivery_other_name": "string", + "in_bond": true, + "in_bond_details": { + "type": "immediate-exportation", + "name": "string", + "address": "string", + "contact_method": "email-address", + "contact_email_address": "string", + "contact_phone_number": { + "number": "5554447777", + "extension": "123" + } + }, + "appointment_delivery": true, + "protect_from_freeze": true, + "threshold_pickup": true, + "threshold_delivery": true + }, + "insurance": { + "type": "internal", + "total_cost": { + "currency": "CAD", + "value": "4250" + } + } + }, + "reference_codes": [ + "string" + ], + "customs_data": { + "products": [ + { + "hs_code": "string", + "country_of_origin": "CA", + "num_units": 1, + "unit_price": { + "currency": "CAD", + "value": "4250" + }, + "description": "string", + "cusma_included": true, + "non_auto_parts": true, + "fda_regulated": "yes", + "product_composition_allocation": { + "provided": true, + "steel_percentage": 1, + "aluminum_percentage": 1, + "copper_percentage": 1 + }, + "product_composition_allocation_zero": true + } + ], + "request_guaranteed_customs_charges": true + } + } +} \ No newline at end of file diff --git a/plugins/freightcom_rest/schemas/rate_response.json b/plugins/freightcom_rest/schemas/rate_response.json new file mode 100644 index 0000000..821cf86 --- /dev/null +++ b/plugins/freightcom_rest/schemas/rate_response.json @@ -0,0 +1,63 @@ +{ + "status": { + "done": true, + "total": 0, + "complete": 0 + }, + "rates": [ + { + "carrier_name": "string", + "service_name": "string", + "service_id": "string", + "valid_until": { + "year": 2006, + "month": 6, + "day": 7 + }, + "total": { + "currency": "CAD", + "value": "4250" + }, + "base": { + "currency": "CAD", + "value": "4250" + }, + "surcharges": [ + { + "type": "fuel", + "amount": { + "currency": "CAD", + "value": "4250" + } + } + ], + "taxes": [ + { + "type": "fuel", + "amount": { + "currency": "CAD", + "value": "4250" + } + } + ], + "transit_time_days": 5, + "transit_time_not_available": true, + "paperless": true, + "customs_charge_data": { + "duties_and_taxes_surcharge_keys": [ + "string" + ], + "guarantee_fee_surcharge_keys": [ + "string" + ], + "carrier_and_government_fees_surcharge_keys": [ + "string" + ], + "processing_fees_surcharge_keys": [ + "string" + ], + "is_rate_guaranteed": true + } + } + ] +} \ No newline at end of file diff --git a/plugins/freightcom_rest/schemas/shipment_request.json b/plugins/freightcom_rest/schemas/shipment_request.json new file mode 100644 index 0000000..d978da1 --- /dev/null +++ b/plugins/freightcom_rest/schemas/shipment_request.json @@ -0,0 +1,311 @@ +{ + "unique_id": "string", + "payment_method_id": "string", + "customs_and_duties_payment_method_id": "string", + "service_id": "string", + "details": { + "origin": { + "name": "Philip J Fry", + "address": { + "address_line_1": "200 University Avenue West", + "address_line_2": "string", + "unit_number": "42a", + "city": "Waterloo", + "region": "ON", + "country": "CA", + "postal_code": "string" + }, + "residential": true, + "tailgate_required": true, + "instructions": "string", + "contact_name": "string", + "phone_number": { + "number": "5554447777", + "extension": "123" + }, + "email_addresses": [ + "user@example.com" + ], + "receives_email_updates": true + }, + "destination": { + "name": "Philip J Fry", + "address": { + "address_line_1": "200 University Avenue West", + "address_line_2": "string", + "unit_number": "42a", + "city": "Waterloo", + "region": "ON", + "country": "CA", + "postal_code": "string" + }, + "residential": true, + "tailgate_required": true, + "instructions": "string", + "contact_name": "string", + "phone_number": { + "number": "5554447777", + "extension": "123" + }, + "email_addresses": [ + "user@example.com" + ], + "receives_email_updates": true, + "ready_at": { + "hour": 15, + "minute": 6 + }, + "ready_until": { + "hour": 15, + "minute": 6 + }, + "signature_requirement": "not-required" + }, + "expected_ship_date": { + "year": 2006, + "month": 6, + "day": 7 + }, + "packaging_type": "pallet", + "packaging_properties": { + "pallet_type": "ltl", + "has_stackable_pallets": true, + "dangerous_goods": "limited-quantity", + "dangerous_goods_details": { + "packaging_group": "string", + "goods_class": "string", + "description": "string", + "united_nations_number": "string", + "emergency_contact_name": "string", + "emergency_contact_phone_number": { + "number": "5554447777", + "extension": "123" + } + }, + "pallets": [ + { + "measurements": { + "weight": { + "unit": "lb", + "value": 2.95 + }, + "cuboid": { + "unit": "ft", + "l": 5, + "w": 5, + "h": 5 + } + }, + "description": "string", + "freight_class": "string", + "nmfc": "string", + "contents_type": "string", + "num_pieces": 0 + } + ], + "packages": [ + { + "measurements": { + "weight": { + "unit": "lb", + "value": 2.95 + }, + "cuboid": { + "unit": "ft", + "l": 5, + "w": 5, + "h": 5 + } + }, + "description": "string" + } + ], + "courierpaks": [ + { + "measurements": { + "weight": { + "unit": "lb", + "value": 2.95 + } + }, + "description": "string" + } + ], + "includes_return_label": false, + "special_handling_required": false, + "has_dangerous_goods": false, + "pallet_service_details": { + "limited_access_delivery_type": "construction-site", + "limited_access_delivery_other_name": "string", + "in_bond": true, + "in_bond_details": { + "type": "immediate-exportation", + "name": "string", + "address": "string", + "contact_method": "email-address", + "contact_email_address": "string", + "contact_phone_number": { + "number": "5554447777", + "extension": "123" + } + }, + "appointment_delivery": true, + "protect_from_freeze": true, + "threshold_pickup": true, + "threshold_delivery": true + } + }, + "insurance": { + "type": "internal", + "total_cost": { + "currency": "CAD", + "value": "4250" + } + }, + "reference_codes": [ + "string" + ], + "customs_data": { + "products": [ + { + "hs_code": "string", + "country_of_origin": "CA", + "num_units": 1, + "unit_price": { + "currency": "CAD", + "value": "4250" + }, + "description": "string", + "cusma_included": true, + "non_auto_parts": true, + "fda_regulated": "yes", + "product_composition_allocation": { + "provided": true, + "steel_percentage": 1, + "aluminum_percentage": 1, + "copper_percentage": 1 + }, + "product_composition_allocation_zero": true + } + ], + "request_guaranteed_customs_charges": true + } + }, + "customs_invoice": { + "source": "details", + "broker": { + "use_carrier": true, + "name": "string", + "account_number": "string", + "phone_number": { + "number": "5554447777", + "extension": "123" + }, + "fax_number": { + "number": "5554447777", + "extension": "123" + }, + "email_address": "string", + "usmca_number": "string", + "fda_number": "string" + }, + "details": { + "tax_recipient": { + "type": "shipper", + "shipper_tax_identifier": "string", + "receiver_tax_identifier": "string", + "third_party_tax_identifier": "string", + "other_tax_identifier": "string", + "name": "string", + "address": { + "address_line_1": "200 University Avenue West", + "address_line_2": "string", + "unit_number": "42a", + "city": "Waterloo", + "region": "ON", + "country": "CA", + "postal_code": "string" + }, + "phone_number": { + "number": "5554447777", + "extension": "123" + }, + "reason_for_export": "gift", + "additional_remarks": "string", + "comments": "string" + }, + "products": [ + { + "product_name": "string", + "weight": { + "unit": "lb", + "value": 2.95 + }, + "hs_code": "string", + "country_of_origin": "CA", + "num_units": 1, + "unit_price": { + "currency": "CAD", + "value": "4250" + }, + "description": "string", + "cusma_included": true, + "non_auto_parts": true, + "fda_regulated": "yes", + "product_composition_allocation": { + "provided": true, + "steel_percentage": 1, + "aluminum_percentage": 1, + "copper_percentage": 1 + }, + "product_composition_allocation_zero": true + } + ] + } + }, + "pickup_details": { + "pre_scheduled_pickup": true, + "date": { + "year": 2006, + "month": 6, + "day": 7 + }, + "ready_at": { + "hour": 15, + "minute": 6 + }, + "ready_until": { + "hour": 15, + "minute": 6 + }, + "pickup_location": "string", + "contact_name": "string", + "contact_phone_number": { + "number": "5554447777", + "extension": "123" + } + }, + "dispatch_details": { + "date": { + "year": 2006, + "month": 6, + "day": 7 + }, + "ready_at": { + "hour": 15, + "minute": 6 + }, + "ready_until": { + "hour": 15, + "minute": 6 + } + }, + "paperless_customs_documents": [ + { + "type": "cusma-form", + "type_other_name": "string", + "file_name": "string", + "file_base64": "string" + } + ] +} \ No newline at end of file diff --git a/plugins/freightcom_rest/schemas/shipment_response.json b/plugins/freightcom_rest/schemas/shipment_response.json new file mode 100644 index 0000000..ab334fc --- /dev/null +++ b/plugins/freightcom_rest/schemas/shipment_response.json @@ -0,0 +1,278 @@ +{ + "shipment": { + "id": "string", + "unique_id": "string", + "state": "draft", + "transaction_number": "string", + "primary_tracking_number": "string", + "tracking_numbers": [ + "string" + ], + "tracking_url": "string", + "return_tracking_number": "string", + "bol_number": "string", + "pickup_confirmation_number": "string", + "details": { + "origin": { + "name": "Philip J Fry", + "address": { + "address_line_1": "200 University Avenue West", + "address_line_2": "string", + "unit_number": "42a", + "city": "Waterloo", + "region": "ON", + "country": "CA", + "postal_code": "string" + }, + "residential": true, + "tailgate_required": true, + "instructions": "string", + "contact_name": "string", + "phone_number": { + "number": "5554447777", + "extension": "123" + }, + "email_addresses": [ + "user@example.com" + ], + "receives_email_updates": true + }, + "destination": { + "name": "Philip J Fry", + "address": { + "address_line_1": "200 University Avenue West", + "address_line_2": "string", + "unit_number": "42a", + "city": "Waterloo", + "region": "ON", + "country": "CA", + "postal_code": "string" + }, + "residential": true, + "tailgate_required": true, + "instructions": "string", + "contact_name": "string", + "phone_number": { + "number": "5554447777", + "extension": "123" + }, + "email_addresses": [ + "user@example.com" + ], + "receives_email_updates": true, + "ready_at": { + "hour": 15, + "minute": 6 + }, + "ready_until": { + "hour": 15, + "minute": 6 + }, + "signature_requirement": "not-required" + }, + "expected_ship_date": { + "year": 2006, + "month": 6, + "day": 7 + }, + "packaging_type": "pallet", + "packaging_properties": { + "pallet_type": "ltl", + "has_stackable_pallets": true, + "dangerous_goods": "limited-quantity", + "dangerous_goods_details": { + "packaging_group": "string", + "goods_class": "string", + "description": "string", + "united_nations_number": "string", + "emergency_contact_name": "string", + "emergency_contact_phone_number": { + "number": "5554447777", + "extension": "123" + } + }, + "pallets": [ + { + "measurements": { + "weight": { + "unit": "lb", + "value": 2.95 + }, + "cuboid": { + "unit": "ft", + "l": 5, + "w": 5, + "h": 5 + } + }, + "description": "string", + "freight_class": "string", + "nmfc": "string", + "contents_type": "string", + "num_pieces": 0 + } + ], + "packages": [ + { + "measurements": { + "weight": { + "unit": "lb", + "value": 2.95 + }, + "cuboid": { + "unit": "ft", + "l": 5, + "w": 5, + "h": 5 + } + }, + "description": "string" + } + ], + "courierpaks": [ + { + "measurements": { + "weight": { + "unit": "lb", + "value": 2.95 + } + }, + "description": "string" + } + ], + "includes_return_label": false, + "special_handling_required": false, + "has_dangerous_goods": false, + "pallet_service_details": { + "limited_access_delivery_type": "construction-site", + "limited_access_delivery_other_name": "string", + "in_bond": true, + "in_bond_details": { + "type": "immediate-exportation", + "name": "string", + "address": "string", + "contact_method": "email-address", + "contact_email_address": "string", + "contact_phone_number": { + "number": "5554447777", + "extension": "123" + } + }, + "appointment_delivery": true, + "protect_from_freeze": true, + "threshold_pickup": true, + "threshold_delivery": true + } + }, + "insurance": { + "type": "internal", + "total_cost": { + "currency": "CAD", + "value": "4250" + } + }, + "reference_codes": [ + "string" + ], + "customs_data": { + "products": [ + { + "hs_code": "string", + "country_of_origin": "CA", + "num_units": 1, + "unit_price": { + "currency": "CAD", + "value": "4250" + }, + "description": "string", + "cusma_included": true, + "non_auto_parts": true, + "fda_regulated": "yes", + "product_composition_allocation": { + "provided": true, + "steel_percentage": 1, + "aluminum_percentage": 1, + "copper_percentage": 1 + }, + "product_composition_allocation_zero": true + } + ], + "request_guaranteed_customs_charges": true + } + }, + "transport_data": {}, + "labels": [ + { + "size": "letter", + "format": "pdf", + "url": "string", + "padded": true + } + ], + "customs_invoice_url": "string", + "rate": { + "carrier_name": "string", + "service_name": "string", + "service_id": "string", + "valid_until": { + "year": 2006, + "month": 6, + "day": 7 + }, + "total": { + "currency": "CAD", + "value": "4250" + }, + "base": { + "currency": "CAD", + "value": "4250" + }, + "surcharges": [ + { + "type": "fuel", + "amount": { + "currency": "CAD", + "value": "4250" + } + } + ], + "taxes": [ + { + "type": "fuel", + "amount": { + "currency": "CAD", + "value": "4250" + } + } + ], + "transit_time_days": 5, + "transit_time_not_available": true, + "paperless": true, + "customs_charge_data": { + "duties_and_taxes_surcharge_keys": [ + "string" + ], + "guarantee_fee_surcharge_keys": [ + "string" + ], + "carrier_and_government_fees_surcharge_keys": [ + "string" + ], + "processing_fees_surcharge_keys": [ + "string" + ], + "is_rate_guaranteed": true + } + }, + "order_source": "string", + "paperless_customs_documents": [ + { + "id": "string", + "type": "string", + "type_other_name": "string", + "file_name": "string", + "url": "string" + } + ] + } +} \ No newline at end of file diff --git a/plugins/freightcom_rest/schemas/tracking_response.json b/plugins/freightcom_rest/schemas/tracking_response.json new file mode 100644 index 0000000..b0310d6 --- /dev/null +++ b/plugins/freightcom_rest/schemas/tracking_response.json @@ -0,0 +1,14 @@ +{ + "events": [ + { + "type": "label-created", + "when": "string", + "where": { + "city": "string", + "region": "string", + "country": "string" + }, + "message": "string" + } + ] +} diff --git a/plugins/freightcom_rest/tests/__init__.py b/plugins/freightcom_rest/tests/__init__.py new file mode 100644 index 0000000..c041abf --- /dev/null +++ b/plugins/freightcom_rest/tests/__init__.py @@ -0,0 +1,4 @@ + +from freightcom_rest.test_rate import * +from freightcom_rest.test_tracking import * +from freightcom_rest.test_shipment import * \ No newline at end of file diff --git a/plugins/freightcom_rest/tests/freightcom_rest/__init__.py b/plugins/freightcom_rest/tests/freightcom_rest/__init__.py new file mode 100644 index 0000000..54814c8 --- /dev/null +++ b/plugins/freightcom_rest/tests/freightcom_rest/__init__.py @@ -0,0 +1 @@ +from .fixture import gateway \ No newline at end of file diff --git a/plugins/freightcom_rest/tests/freightcom_rest/fixture.py b/plugins/freightcom_rest/tests/freightcom_rest/fixture.py new file mode 100644 index 0000000..bb82a31 --- /dev/null +++ b/plugins/freightcom_rest/tests/freightcom_rest/fixture.py @@ -0,0 +1,22 @@ +"""Freightcom Rest carrier tests fixtures.""" + +import karrio.sdk as karrio +import karrio.lib as lib + +cached_payment_method_id = { + f"payment|freightcom_rest|net_terms|TEST_API_KEY": dict( + id="string", + type= "net-terms", + label="Net Terms" + ) +} +gateway = karrio.gateway["freightcom_rest"].create( + dict( + api_key="TEST_API_KEY", + config=dict( + payment_method_type="net_terms" + ), + ), + cache=lib.Cache(**cached_payment_method_id), + +) diff --git a/plugins/freightcom_rest/tests/freightcom_rest/test_rate.py b/plugins/freightcom_rest/tests/freightcom_rest/test_rate.py new file mode 100644 index 0000000..6973615 --- /dev/null +++ b/plugins/freightcom_rest/tests/freightcom_rest/test_rate.py @@ -0,0 +1,459 @@ +"""Freightcom Rest carrier rate tests.""" +import datetime +import unittest +from unittest.mock import patch, ANY +from .fixture import gateway +import logging +import karrio.sdk as karrio +import karrio.lib as lib +import karrio.core.models as models + +logger = logging.getLogger(__name__) + + +class TestFreightcomRestRating(unittest.TestCase): + def setUp(self): + self.maxDiff = None + self.RateRequest = models.RateRequest(**RatePayload) + + def test_create_rate_request(self): + request = gateway.mapper.create_rate_request(self.RateRequest) + self.assertEqual(lib.to_dict(request.serialize()), RateRequest) + + def test_get_rates(self): + with patch("karrio.mappers.freightcom_rest.proxy.lib.request") as mock: + mock.return_value = "{}" + karrio.Rating.fetch(self.RateRequest).from_(gateway) + self.assertEqual( + mock.call_args[1]["url"], + f"{gateway.settings.server_url}/rate" + ) + + def test_parse_rate_response(self): + with patch("karrio.mappers.freightcom_rest.proxy.lib.request") as mock: + mock.return_value = RateResponse + parsed_response = ( + karrio.Rating.fetch(self.RateRequest) + .from_(gateway) + .parse() + ) + self.assertListEqual(lib.to_dict(parsed_response), ParsedRateResponse) + + def test_parse_error_response(self): + with patch("karrio.mappers.freightcom_rest.proxy.lib.request") as mock: + mock.return_value = ErrorResponse + parsed_response = ( + karrio.Rating.fetch(self.RateRequest) + .from_(gateway) + .parse() + ) + self.assertListEqual(lib.to_dict(parsed_response), ParsedErrorResponse) + + +if __name__ == "__main__": + unittest.main() + + +RatePayload = { + "shipper": { + "company_name": "Test Company - From", + "address_line1": "9, Van Der Graaf Court", + "city": "Brampton", + "postal_code": "L4T3T1", + "country_code": "CA", + "state_code": "ON", + "email": "shipper@example.com", + "phone_number": "(123) 114 1499" + }, + "recipient": { + "company_name": "Test Company - Destination", + "address_line1": "1410 Fall River Rd", + "city": "Fall River", + "country_code": "CA", + "postal_code": "B2T1J1", + "residential": "true", + "state_code": "NS", + "email": "recipient@example.com", + "phone_number": "(999) 999 9999" + }, + "parcels": [ + { + "height": 50, + "length": 50, + "weight": 20, + "width": 12, + "dimension_unit": "CM", + "weight_unit": "KG", + "description": "Package 1 Description" + }, + { + "height": 30, + "length": 50, + "weight": 20, + "width": 12, + "dimension_unit": "CM", + "weight_unit": "KG", + "description": "Package 2 Description" + } + ], + "reference": "REF-001", + "options": { + "email_notification": True, + "shipping_date": datetime.datetime(2025, 2, 25, 1,0).strftime("%Y-%m-%dT%H:%M"), + } +} + + +RateRequest = { + "details": { + "destination": { + "address": { + "address_line_1": "1410 Fall River Rd", + "city": "Fall River", + "country": "CA", + "postal_code": "B2T1J1", + "region": "NS", + }, + "email_addresses": ["recipient@example.com"], + 'name': 'Test Company - Destination', + "phone_number": {"number": "(999) 999 9999"}, + "ready_at": { + "hour": 10, "minute": 0 + }, + "ready_until": { + "hour": 17, "minute": 0 + }, + "receives_email_updates": True, + "residential": False, + "signature_requirement": "not-required" + }, + "origin": { + "address": { + "address_line_1": "9, Van Der Graaf Court", + "city": "Brampton", + "country": "CA", + "postal_code": "L4T3T1", + "region": "ON", + }, + "name": "Test Company - From", + "email_addresses": ["shipper@example.com"], + "phone_number": {"number": "(123) 114 1499"}, + "residential": False + }, + "expected_ship_date": {"day": 25, "month": 2, "year": 2025}, + "packaging_type": "package", + "packaging_properties": { + "packages": [ + { + "description": "Package 1 Description", + "measurements": { + "cuboid": { + "h": 50.0, + "l": 50.0, + "unit": "cm", + "w": 12.0 + }, + "weight": { + "unit": "kg", + "value": 20.0 + } + } + }, + { + "description": "Package 2 Description", + "measurements": { + "cuboid": { + "h": 30.0, + "l": 50.0, + "unit": "cm", + "w": 12.0 + }, + "weight": { + "unit": "kg", + "value": 20.0 + } + } + } + ], + }, + "reference_codes": ["REF-001"] + } +} + + +RateResponse = """ +{ + "status": { + "done": true, + "total": 99, + "complete": 99 + }, + "rates": [ + { + "service_id": "canpar.ground", + "valid_until": { + "year": 2025, + "month": 3, + "day": 3 + }, + "total": { + "value": "5334", + "currency": "CAD" + }, + "base": { + "value": "3368", + "currency": "CAD" + }, + "surcharges": [ + { + "type": "fuel", + "amount": { + "value": "1001", + "currency": "CAD" + } + }, + { + "type": "carbon-surcharge", + "amount": { + "value": "51", + "currency": "CAD" + } + }, + { + "type": "residential-delivery", + "amount": { + "value": "218", + "currency": "CAD" + } + } + ], + "taxes": [ + { + "type": "tax-hst-ns", + "amount": { + "value": "696", + "currency": "CAD" + } + } + ], + "transit_time_days": 2, + "transit_time_not_available": false, + "carrier_name": "Canpar", + "service_name": "Ground" + }, + { + "service_id": "fedex-courier.ground", + "valid_until": { + "year": 2025, + "month": 3, + "day": 3 + }, + "total": { + "value": "6462", + "currency": "CAD" + }, + "base": { + "value": "4087", + "currency": "CAD" + }, + "surcharges": [ + { + "type": "fuel", + "amount": { + "value": "1254", + "currency": "CAD" + } + }, + { + "type": "residential-delivery", + "amount": { + "value": "278", + "currency": "CAD" + } + } + ], + "taxes": [ + { + "type": "tax-hst-ns", + "amount": { + "value": "843", + "currency": "CAD" + } + } + ], + "transit_time_days": 2, + "transit_time_not_available": false, + "carrier_name": "FedEx Courier", + "service_name": "Ground" + }, + { + "service_id": "purolatorcourier.ground", + "valid_until": { + "year": 2025, + "month": 3, + "day": 3 + }, + "total": { + "value": "6047", + "currency": "CAD" + }, + "base": { + "value": "4134", + "currency": "CAD" + }, + "surcharges": [ + { + "type": "fuel", + "amount": { + "value": "1124", + "currency": "CAD" + } + } + ], + "taxes": [ + { + "type": "tax-hst-ns", + "amount": { + "value": "789", + "currency": "CAD" + } + } + ], + "transit_time_days": 3, + "transit_time_not_available": false, + "carrier_name": "Purolator", + "service_name": "Ground" + } + ] +} +""" + +ErrorResponse = """ +{ + "message": "Unable to get rates", + "data": {"services":"invalid-syntax"} +} +""" + +ParsedRateResponse = [ + [ + { + "carrier_id": "freightcom_rest", + "carrier_name": "freightcom_rest", + "currency": "CAD", + "extra_charges": [ + { + "amount": 33.68, + "currency": "CAD", + "name": "Base charge" + }, + { + "amount": 10.01, + "currency": "CAD", + "name": "fuel" + }, + { + "amount": 0.51, + "currency": "CAD", + "name": "carbon-surcharge" + }, + { + "amount": 2.18, + "currency": "CAD", + "name": "residential-delivery" + }, + { + "amount": 6.96, + "currency": "CAD", + "name": "tax-hst-ns" + } + ], + "meta": { + "rate_provider": "canpar", + "service_name": "freightcom_canpar_ground" + }, + "service": "freightcom_canpar_ground", + "total_charge": 53.34, + "transit_days": 2 + }, + { + "carrier_id": "freightcom_rest", + "carrier_name": "freightcom_rest", + "currency": "CAD", + "extra_charges": [ + { + "amount": 40.87, + "currency": "CAD", + "name": "Base charge" + }, + { + "amount": 12.54, + "currency": "CAD", + "name": "fuel" + }, + { + "amount": 2.78, + "currency": "CAD", + "name": "residential-delivery" + }, + { + "amount": 8.43, + "currency": "CAD", + "name": "tax-hst-ns" + } + ], + "meta": { + "rate_provider": "fedex", + "service_name": "freightcom_fedex_ground" + }, + "service": "freightcom_fedex_ground", + "total_charge": 64.62, + "transit_days": 2 + }, + { + "carrier_id": "freightcom_rest", + "carrier_name": "freightcom_rest", + "currency": "CAD", + "extra_charges": [ + { + "amount": 41.34, + "currency": "CAD", + "name": "Base charge" + }, + { + "amount": 11.24, + "currency": "CAD", + "name": "fuel" + }, + { + "amount": 7.89, + "currency": "CAD", + "name": "tax-hst-ns" + } + ], + "meta": { + "rate_provider": "purolator", + "service_name": "freightcom_purolator_ground" + }, + "service": "freightcom_purolator_ground", + "total_charge": 60.47, + "transit_days": 3 + } + ], + [] +] + +ParsedErrorResponse = [ + [], + [ + { + "carrier_id": "freightcom_rest", + "carrier_name": "freightcom_rest", + "message": "Unable to get rates: services: invalid-syntax", + "level": "error", + "details": { + "services": "invalid-syntax" + } + } + ] +] diff --git a/plugins/freightcom_rest/tests/freightcom_rest/test_shipment.py b/plugins/freightcom_rest/tests/freightcom_rest/test_shipment.py new file mode 100644 index 0000000..81dfc09 --- /dev/null +++ b/plugins/freightcom_rest/tests/freightcom_rest/test_shipment.py @@ -0,0 +1,487 @@ +"""Freightcom Rest carrier shipment tests.""" +import datetime +import unittest +from unittest.mock import patch, ANY +from .fixture import gateway +import logging +import karrio.sdk as karrio +import karrio.lib as lib +import karrio.core.models as models + +logger = logging.getLogger(__name__) + +class TestFreightcomRestShipment(unittest.TestCase): + def setUp(self): + self.maxDiff = None + self.ShipmentRequest = models.ShipmentRequest(**ShipmentPayload) + self.ShipmentCancelRequest = models.ShipmentCancelRequest(**ShipmentCancelPayload) + + def test_create_shipment_request(self): + request = gateway.mapper.create_shipment_request(self.ShipmentRequest) + self.assertEqual(lib.to_dict(request.serialize()), ShipmentRequest) + + def test_create_shipment(self): + with patch("karrio.mappers.freightcom_rest.proxy.lib.request") as mock: + mock.side_effect = [ShipmentFirstResponse, ShipmentResponse] + karrio.Shipment.create(self.ShipmentRequest).from_(gateway) + + self.assertEqual(mock.call_count, 2) + + # Check first API call to create shipment + self.assertEqual( + mock.call_args_list[0][1]["url"], + f"{gateway.settings.server_url}/shipment" + ) + + # Check second API call to get shipment details using the ID + self.assertEqual( + mock.call_args_list[1][1]["url"], + f"{gateway.settings.server_url}/shipment/HNrzG2iRHKJ6XN0CQwYgKBQnABvx2Yi5" + ) + self.assertEqual(mock.call_args_list[1][1]["method"], "GET") + + def test_parse_shipment_response(self): + with patch("karrio.mappers.freightcom_rest.proxy.lib.request") as mock: + mock.side_effect = [ + ShipmentFirstResponse, + ShipmentResponse, + "base64_encoded_label_data", + "base64_encoded_invoice_data" + ] + + parsed_response = ( + karrio.Shipment.create(self.ShipmentRequest) + .from_(gateway) + .parse() + ) + + self.assertEqual(mock.call_count, 4) + + # Check that the result matches the expected parsed response + self.assertListEqual(lib.to_dict(parsed_response), ParsedShipmentResponse) + + def test_create_shipment_cancel_request(self): + request = gateway.mapper.create_cancel_shipment_request(self.ShipmentCancelRequest) + self.assertEqual(request.serialize(), ShipmentCancelRequest) + + def test_cancel_shipment(self): + with patch("karrio.mappers.freightcom_rest.proxy.lib.request") as mock: + mock.return_value = "{}" + karrio.Shipment.cancel(self.ShipmentCancelRequest).from_(gateway) + self.assertEqual( + mock.call_args[1]["url"], + f"{gateway.settings.server_url}/shipment/{self.ShipmentCancelRequest.shipment_identifier}", + ) + + def test_parse_shipment_cancel_response(self): + with patch("karrio.mappers.freightcom_rest.proxy.lib.request") as mock: + mock.return_value = ShipmentCancelResponse + parsed_response = ( + karrio.Shipment.cancel(self.ShipmentCancelRequest) + .from_(gateway) + .parse() + ) + self.assertListEqual(lib.to_dict(parsed_response), ParsedShipmentCancelResponse) + + # def test_parse_error_response(self): + # with patch("karrio.mappers.freightcom_rest.proxy.lib.request") as mock: + # mock.return_value = ErrorResponse + # parsed_response = ( + # karrio.Shipment.create(self.ShipmentRequest) + # .from_(gateway) + # .parse() + # ) + # self.assertListEqual(lib.to_dict(parsed_response), ParsedErrorResponse) + + +if __name__ == "__main__": + unittest.main() + + +ShipmentPayload = { + "shipper": { + "company_name": "Test Company - From", + "address_line1": "9, Van Der Graaf Court", + "city": "Brampton", + "postal_code": "L4T3T1", + "country_code": "CA", + "state_code": "ON", + "email": "shipper@example.com", + "phone_number": "(123) 114 1499" + }, + "recipient": { + "company_name": "Test Company - Destination", + "address_line1": "1410 Fall River Rd", + "city": "Fall River", + "country_code": "CA", + "postal_code": "B2T1J1", + "residential": "true", + "state_code": "NS", + "email": "recipient@example.com", + "phone_number": "(999) 999 9999" + }, + "parcels": [ + { + "height": 50, + "length": 50, + "weight": 20, + "width": 12, + "dimension_unit": "CM", + "weight_unit": "KG", + "description": "Package 1 Description" + }, + { + "height": 30, + "length": 50, + "weight": 20, + "width": 12, + "dimension_unit": "CM", + "weight_unit": "KG", + "description": "Package 2 Description" + } + ], + "service": "freightcom_canpar_ground", + "options": { + "signature_confirmation": True, + "shipping_date": datetime.datetime(2025, 2, 25, 1, 0).strftime("%Y-%m-%dT%H:%M"), + }, + "reference": "#Order 11111", +} + +ShipmentCancelPayload = { + "shipment_identifier": "shipment_id", +} + + +ShipmentRequest = { + "details": { + "destination": { + "address": { + "address_line_1": "1410 Fall River Rd", + "city": "Fall River", + "country": "CA", + "postal_code": "B2T1J1", + "region": "NS", + }, + "email_addresses": ["recipient@example.com"], + 'name': 'Test Company - Destination', + "phone_number": {"number": "(999) 999 9999"}, + "ready_at": { + "hour": 10, "minute": 0 + }, + "ready_until": { + "hour": 17, "minute": 0 + }, + "receives_email_updates": True, + "residential": False, + "signature_requirement": "required" + }, + "origin": { + "address": { + "address_line_1": "9, Van Der Graaf Court", + "city": "Brampton", + "country": "CA", + "postal_code": "L4T3T1", + "region": "ON", + }, + "name": "Test Company - From", + "email_addresses": ["shipper@example.com"], + "phone_number": {"number": "(123) 114 1499"}, + "residential": False + }, + "expected_ship_date": {"day": 25, "month": 2, "year": 2025}, + "packaging_type": "package", + "packaging_properties": { + "packages": [ + { + "description": "Package 1 Description", + "measurements": { + "cuboid": { + "h": 50.0, + "l": 50.0, + "unit": "cm", + "w": 12.0 + }, + "weight": { + "unit": "kg", + "value": 20.0 + } + } + }, + { + "description": "Package 2 Description", + "measurements": { + "cuboid": { + "h": 30.0, + "l": 50.0, + "unit": "cm", + "w": 12.0 + }, + "weight": { + "unit": "kg", + "value": 20.0 + } + } + } + ], + }, + "reference_codes": ["#Order 11111"] + }, + 'payment_method_id': 'string', + "service_id": "canpar.ground", + "unique_id": ANY +} + +ShipmentCancelRequest = "shipment_id" + + +ShipmentResponse = """ +{ +"shipment": { + "id": "uQeh1XwbVIbIyP9mEPtVM2puAFZYmAYA", + "unique_id": "38a8b937-4262-497b-8f5c-b9d9d4c6bae6", + "state": "waiting-for-scheduling", + "transaction_number": "19989362", + "primary_tracking_number": "1ZXXXXXXXXXXXXXXXX", + "tracking_numbers": [ + "1ZXXXXXXXXXXXXXXXX", + "1ZXXXXXXXXXXXXXXXX" + ], + "tracking_url": "https://www.ups.com/WebTracking?trackingNumber=1ZXXXXXXXXXXXXXXXX", + "return_tracking_number": "", + "bolnumber": "", + "pickup_confirmation_number": "", + "details": { + "id": "HNrzG2iRHKJ6XN0CQwYgKBQnABvx2Yi5", + "expected_ship_date": { + "year": 2025, + "month": 2, + "day": 12 + }, + "origin": { + "searchable_id": "", + "name": "Cheques Plus", + "address": { + "address_line1": "4054 Rue Alfred Laliberté", + "address_line2": "", + "unit_number": "", + "city": "Boisbriand", + "region": "QC", + "country": "CA", + "postal_code": "J7H 1P8", + "validated": false + }, + "residential": false, + "business_type": "", + "tailgate_required": false, + "instructions": "", + "contact_name": "Shipping", + "phone_number": { + "number": "+1 450-323-6247", + "extension": "" + }, + "email_addresses": [ + "sales@chequesplus.com" + ], + "receives_email_updates": false, + "address_book_contact_id": "" + }, + "destination": { + "searchable_id": "", + "name": "ASAP Cheques", + "address": { + "address_line1": "623 Fortune Crescent #100", + "address_line2": "", + "unit_number": "", + "city": "Kingston", + "region": "ON", + "country": "CA", + "postal_code": "K7P 0L5", + "validated": false + }, + "residential": false, + "business_type": "", + "tailgate_required": false, + "instructions": "", + "contact_name": "ASAP Cheques Kingston", + "phone_number": { + "number": "+1 888-324-3783", + "extension": "" + }, + "email_addresses": [ + "admin@shipngo.ca" + ], + "receives_email_updates": false, + "address_book_contact_id": "", + "ready_at": { + "hour": 10, + "minute": 0, + "populated": true + }, + "ready_until": { + "hour": 17, + "minute": 0, + "populated": true + }, + "signature_requirement": "not-required" + }, + "alternate_destination": null, + "reference_codes": [ + "ss" + ], + "packaging_type": "package", + "packaging_properties": { + "packages": [ + { + "measurements": { + "cuboid": { + "l": 10, + "w": 20, + "h": 18.2, + "unit": "cm" + }, + "weight": { + "value": 1, + "unit": "kg" + } + }, + "description": "N/A", + "special_handling_required": false + }, + { + "measurements": { + "cuboid": { + "l": 10, + "w": 33.7, + "h": 18.2, + "unit": "cm" + }, + "weight": { + "value": 1, + "unit": "kg" + } + }, + "description": "N/A", + "special_handling_required": false + } + ], + "includes_return_label": false + }, + "insurance": null, + "billing_contact": null + }, + "transport_data": null, + "labels": [ + { + "size": "letter", + "format": "pdf", + "url": "https://s3.us-east-2.amazonaws.com/ssd-test-external/labels/uQeh1XwbVIbIyP9mEPtVM2puAFZYmAYA/yRWNRmUkCGMKIZOjMHNUSy9JlXPYjvVb/shipping-label-19989362-letter.pdf", + "padded": false + }, + { + "size": "a6", + "format": "zpl", + "url": "https://s3.us-east-2.amazonaws.com/ssd-test-external/labels/uQeh1XwbVIbIyP9mEPtVM2puAFZYmAYA/yRWNRmUkCGMKIZOjMHNUSy9JlXPYjvVb/shipping-label-19989362-a6.zpl", + "padded": false + }, + { + "size": "a6", + "format": "pdf", + "url": "https://s3.us-east-2.amazonaws.com/ssd-test-external/labels/uQeh1XwbVIbIyP9mEPtVM2puAFZYmAYA/yRWNRmUkCGMKIZOjMHNUSy9JlXPYjvVb/shipping-label-19989362-a6.pdf", + "padded": false + }, + { + "size": "a6", + "format": "pdf", + "url": "https://s3.us-east-2.amazonaws.com/ssd-test-external/labels/uQeh1XwbVIbIyP9mEPtVM2puAFZYmAYA/yRWNRmUkCGMKIZOjMHNUSy9JlXPYjvVb/shipping-label-19989362-a6-w-padding.pdf", + "padded": true + } + ], + "customs_invoice_url": "https://s3.us-east-2.amazonaws.com/ssd-test-external/labels/uQeh1XwbVIbIyP9mEPtVM2puAFZYmAYA/yRWNRmUkCGMKIZOjMHNUSy9JlXPYjvVb/shipping-label-19989362-a6-w-padding.pdf", + "rate": { + "service_id": "ups.standard", + "valid_until": { + "year": 2025, + "month": 2, + "day": 20 + }, + "total": { + "value": "1779", + "currency": "CAD" + }, + "base": { + "value": "1779", + "currency": "CAD" + }, + "surcharges": [], + "taxes": [], + "transit_time_days": 1, + "transit_time_not_available": false, + "carrier_name": "UPS", + "service_name": "Standard" + }, + "order_source": "Api" + } +} +""" + +ShipmentCancelResponse = """{} +""" + +ErrorResponse = """{ + "message": "Unable to get rates", + "data": {"services":"invalid-syntax"} +}""" + +ParsedShipmentResponse = [ + { + "carrier_id": "freightcom_rest", + "carrier_name": "freightcom_rest", + "docs": { + "invoice": "base64_encoded_invoice_data", + "label": "base64_encoded_label_data", + }, + "label_type": "PDF", + "meta": { + "carrier_tracking_link": "https://www.ups.com/WebTracking?trackingNumber=1ZXXXXXXXXXXXXXXXX", + "freightcom_service_id": "ups.standard", + "freightcom_shipment_identifier": "uQeh1XwbVIbIyP9mEPtVM2puAFZYmAYA", + "freightcom_unique_id": "38a8b937-4262-497b-8f5c-b9d9d4c6bae6", + "rate_provider": "ups", + "service_name": "freightcom_ups_standard", + "tracking_numbers": ["1ZXXXXXXXXXXXXXXXX"] + }, + "shipment_identifier": "uQeh1XwbVIbIyP9mEPtVM2puAFZYmAYA", + "tracking_number": "1ZXXXXXXXXXXXXXXXX", + }, + [] +] + +ParsedShipmentCancelResponse = [ + { + "carrier_id": "freightcom_rest", + "carrier_name": "freightcom_rest", + "success": True, + "operation": "Cancel Shipment" + }, + [] +] + +ParsedErrorResponse = [ + [], + [ + { + "carrier_id": "freightcom_rest", + "carrier_name": "freightcom_rest", + "message": "Unable to get rates", + "details": { + "services": "invalid-syntax" + } + } + ] +] + + +ShipmentFirstResponse = """ +{"id": "HNrzG2iRHKJ6XN0CQwYgKBQnABvx2Yi5"} +"""