karrio-freightcom 2025.5.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- karrio/mappers/freightcom/__init__.py +3 -0
- karrio/mappers/freightcom/mapper.py +55 -0
- karrio/mappers/freightcom/proxy.py +38 -0
- karrio/mappers/freightcom/settings.py +19 -0
- karrio/plugins/freightcom/__init__.py +19 -0
- karrio/providers/freightcom/__init__.py +6 -0
- karrio/providers/freightcom/error.py +40 -0
- karrio/providers/freightcom/quote.py +179 -0
- karrio/providers/freightcom/shipping.py +294 -0
- karrio/providers/freightcom/units.py +153 -0
- karrio/providers/freightcom/utils.py +39 -0
- karrio/providers/freightcom/void_shipment.py +50 -0
- karrio/schemas/freightcom/__init__.py +0 -0
- karrio/schemas/freightcom/error.py +1859 -0
- karrio/schemas/freightcom/quote_reply.py +2555 -0
- karrio/schemas/freightcom/quote_request.py +4522 -0
- karrio/schemas/freightcom/shipment_cancel_reply.py +2063 -0
- karrio/schemas/freightcom/shipment_cancel_request.py +1915 -0
- karrio/schemas/freightcom/shipping_reply.py +4236 -0
- karrio/schemas/freightcom/shipping_request.py +5953 -0
- karrio_freightcom-2025.5.6.dist-info/METADATA +44 -0
- karrio_freightcom-2025.5.6.dist-info/RECORD +25 -0
- karrio_freightcom-2025.5.6.dist-info/WHEEL +5 -0
- karrio_freightcom-2025.5.6.dist-info/entry_points.txt +2 -0
- karrio_freightcom-2025.5.6.dist-info/top_level.txt +3 -0
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
from typing import List, Tuple
|
|
2
|
+
from karrio.api.mapper import Mapper as BaseMapper
|
|
3
|
+
from karrio.mappers.freightcom.settings import Settings
|
|
4
|
+
from karrio.core.utils.serializable import Deserializable, Serializable
|
|
5
|
+
from karrio.core.models import (
|
|
6
|
+
RateRequest,
|
|
7
|
+
ShipmentRequest,
|
|
8
|
+
ShipmentDetails,
|
|
9
|
+
RateDetails,
|
|
10
|
+
Message,
|
|
11
|
+
ShipmentCancelRequest,
|
|
12
|
+
ConfirmationDetails,
|
|
13
|
+
)
|
|
14
|
+
from karrio.providers.freightcom import (
|
|
15
|
+
parse_quote_reply,
|
|
16
|
+
quote_request,
|
|
17
|
+
parse_shipping_reply,
|
|
18
|
+
shipping_request,
|
|
19
|
+
shipment_cancel_request,
|
|
20
|
+
parse_shipment_cancel_reply,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class Mapper(BaseMapper):
|
|
25
|
+
settings: Settings
|
|
26
|
+
|
|
27
|
+
# Request Mappers
|
|
28
|
+
|
|
29
|
+
def create_rate_request(self, payload: RateRequest) -> Serializable:
|
|
30
|
+
return quote_request(payload, self.settings)
|
|
31
|
+
|
|
32
|
+
def create_shipment_request(self, payload: ShipmentRequest) -> Serializable:
|
|
33
|
+
return shipping_request(payload, self.settings)
|
|
34
|
+
|
|
35
|
+
def create_cancel_shipment_request(
|
|
36
|
+
self, payload: ShipmentCancelRequest
|
|
37
|
+
) -> Serializable:
|
|
38
|
+
return shipment_cancel_request(payload, self.settings)
|
|
39
|
+
|
|
40
|
+
# Response Parsers
|
|
41
|
+
|
|
42
|
+
def parse_rate_response(
|
|
43
|
+
self, response: Deserializable
|
|
44
|
+
) -> Tuple[List[RateDetails], List[Message]]:
|
|
45
|
+
return parse_quote_reply(response, self.settings)
|
|
46
|
+
|
|
47
|
+
def parse_shipment_response(
|
|
48
|
+
self, response: Deserializable
|
|
49
|
+
) -> Tuple[ShipmentDetails, List[Message]]:
|
|
50
|
+
return parse_shipping_reply(response, self.settings)
|
|
51
|
+
|
|
52
|
+
def parse_cancel_shipment_response(
|
|
53
|
+
self, response: Deserializable
|
|
54
|
+
) -> Tuple[ConfirmationDetails, List[Message]]:
|
|
55
|
+
return parse_shipment_cancel_reply(response, self.settings)
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from karrio.core.utils import request as http, XP
|
|
2
|
+
from karrio.api.proxy import Proxy as BaseProxy
|
|
3
|
+
from karrio.mappers.freightcom.settings import Settings
|
|
4
|
+
from karrio.core.utils.serializable import Serializable, Deserializable
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Proxy(BaseProxy):
|
|
8
|
+
settings: Settings
|
|
9
|
+
|
|
10
|
+
def get_rates(self, request: Serializable) -> Deserializable:
|
|
11
|
+
response = http(
|
|
12
|
+
url=self.settings.server_url,
|
|
13
|
+
data=request.serialize(),
|
|
14
|
+
trace=self.trace_as("xml"),
|
|
15
|
+
method="POST",
|
|
16
|
+
headers={"Content-Type": "application/xml"},
|
|
17
|
+
)
|
|
18
|
+
return Deserializable(response, XP.to_xml)
|
|
19
|
+
|
|
20
|
+
def create_shipment(self, request: Serializable) -> Deserializable:
|
|
21
|
+
response = http(
|
|
22
|
+
url=self.settings.server_url,
|
|
23
|
+
data=request.serialize(),
|
|
24
|
+
trace=self.trace_as("xml"),
|
|
25
|
+
method="POST",
|
|
26
|
+
headers={"Content-Type": "application/xml"},
|
|
27
|
+
)
|
|
28
|
+
return Deserializable(response, XP.to_xml)
|
|
29
|
+
|
|
30
|
+
def cancel_shipment(self, request: Serializable) -> Deserializable:
|
|
31
|
+
response = http(
|
|
32
|
+
url=self.settings.server_url,
|
|
33
|
+
data=request.serialize(),
|
|
34
|
+
trace=self.trace_as("xml"),
|
|
35
|
+
method="POST",
|
|
36
|
+
headers={"Content-Type": "application/xml"},
|
|
37
|
+
)
|
|
38
|
+
return Deserializable(response, XP.to_xml)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""Karrio freightcom connection settings."""
|
|
2
|
+
|
|
3
|
+
import attr
|
|
4
|
+
from karrio.providers.freightcom.utils import Settings as BaseSettings
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@attr.s(auto_attribs=True)
|
|
8
|
+
class Settings(BaseSettings):
|
|
9
|
+
"""Freightcom connection settings."""
|
|
10
|
+
|
|
11
|
+
username: str
|
|
12
|
+
password: str
|
|
13
|
+
|
|
14
|
+
id: str = None
|
|
15
|
+
test_mode: bool = False
|
|
16
|
+
carrier_id: str = "freightcom"
|
|
17
|
+
account_country_code: str = None
|
|
18
|
+
metadata: dict = {}
|
|
19
|
+
config: dict = {}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import karrio.core.metadata as metadata
|
|
2
|
+
import karrio.mappers.freightcom as mappers
|
|
3
|
+
import karrio.providers.freightcom.units as units
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
METADATA = metadata.PluginMetadata(
|
|
7
|
+
status="deprecated",
|
|
8
|
+
id="freightcom",
|
|
9
|
+
label="Freightcom",
|
|
10
|
+
is_hub=True,
|
|
11
|
+
# Integrations
|
|
12
|
+
Mapper=mappers.Mapper,
|
|
13
|
+
Proxy=mappers.Proxy,
|
|
14
|
+
Settings=mappers.Settings,
|
|
15
|
+
# Data Units
|
|
16
|
+
options=units.ShippingOption,
|
|
17
|
+
services=units.ShippingService,
|
|
18
|
+
hub_carriers=units.CARRIER_IDS,
|
|
19
|
+
)
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
from karrio.providers.freightcom.quote import parse_quote_reply, quote_request
|
|
2
|
+
from karrio.providers.freightcom.shipping import (
|
|
3
|
+
parse_shipping_reply,
|
|
4
|
+
shipping_request,
|
|
5
|
+
)
|
|
6
|
+
from karrio.providers.freightcom.void_shipment import shipment_cancel_request, parse_shipment_cancel_reply
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from typing import List
|
|
2
|
+
from karrio.schemas.freightcom.error import ErrorType
|
|
3
|
+
from karrio.schemas.freightcom.quote_reply import CarrierErrorMessageType
|
|
4
|
+
from karrio.core.models import Message
|
|
5
|
+
from karrio.core.utils import Element, XP
|
|
6
|
+
from karrio.providers.freightcom.utils import Settings
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def parse_error_response(response: Element, settings: Settings) -> List[Message]:
|
|
10
|
+
errors = XP.find("Error", response, ErrorType)
|
|
11
|
+
carrier_errors = XP.find("CarrierErrorMessage", response, CarrierErrorMessageType)
|
|
12
|
+
|
|
13
|
+
return [
|
|
14
|
+
*[_extract_error(er, settings) for er in errors if er.Message != ""],
|
|
15
|
+
*[
|
|
16
|
+
_extract_carrier_error(er, settings)
|
|
17
|
+
for er in carrier_errors
|
|
18
|
+
if er.errorMessage0 != ""
|
|
19
|
+
],
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _extract_carrier_error(
|
|
24
|
+
error: CarrierErrorMessageType, settings: Settings
|
|
25
|
+
) -> Message:
|
|
26
|
+
return Message(
|
|
27
|
+
code="CarrierErrorMessage",
|
|
28
|
+
carrier_name=settings.carrier_name,
|
|
29
|
+
carrier_id=settings.carrier_id,
|
|
30
|
+
message=error.errorMessage0,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _extract_error(error: ErrorType, settings: Settings) -> Message:
|
|
35
|
+
return Message(
|
|
36
|
+
code="Error",
|
|
37
|
+
carrier_name=settings.carrier_name,
|
|
38
|
+
carrier_id=settings.carrier_id,
|
|
39
|
+
message=error.Message,
|
|
40
|
+
)
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
from karrio.schemas.freightcom.quote_request import (
|
|
2
|
+
Freightcom,
|
|
3
|
+
QuoteRequestType,
|
|
4
|
+
FromType,
|
|
5
|
+
ToType,
|
|
6
|
+
PackagesType,
|
|
7
|
+
PackageType,
|
|
8
|
+
)
|
|
9
|
+
from karrio.schemas.freightcom.quote_reply import QuoteType
|
|
10
|
+
|
|
11
|
+
import typing
|
|
12
|
+
import karrio.lib as lib
|
|
13
|
+
import karrio.core.models as models
|
|
14
|
+
import karrio.providers.freightcom.error as provider_error
|
|
15
|
+
import karrio.providers.freightcom.units as provider_units
|
|
16
|
+
import karrio.providers.freightcom.utils as provider_utils
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def parse_quote_reply(
|
|
20
|
+
_response: lib.Deserializable[lib.Element], settings: provider_utils.Settings
|
|
21
|
+
) -> typing.Tuple[typing.List[models.RateDetails], typing.List[models.Message]]:
|
|
22
|
+
response = _response.deserialize()
|
|
23
|
+
estimates = lib.find_element("Quote", response)
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
[_extract_rate(node, settings) for node in estimates],
|
|
27
|
+
provider_error.parse_error_response(response, settings),
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _extract_rate(
|
|
32
|
+
node: lib.Element,
|
|
33
|
+
settings: provider_utils.Settings,
|
|
34
|
+
) -> models.RateDetails:
|
|
35
|
+
quote = lib.to_object(QuoteType, node)
|
|
36
|
+
rate_provider, service, service_name = provider_units.ShippingService.info(
|
|
37
|
+
quote.serviceId,
|
|
38
|
+
quote.carrierId,
|
|
39
|
+
quote.serviceName,
|
|
40
|
+
quote.carrierName,
|
|
41
|
+
)
|
|
42
|
+
charges = [
|
|
43
|
+
("Base charge", quote.baseCharge),
|
|
44
|
+
("Fuel surcharge", quote.fuelSurcharge),
|
|
45
|
+
*((surcharge.name, surcharge.amount) for surcharge in quote.Surcharge),
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
return models.RateDetails(
|
|
49
|
+
carrier_name=settings.carrier_name,
|
|
50
|
+
carrier_id=settings.carrier_id,
|
|
51
|
+
currency=quote.currency,
|
|
52
|
+
service=service,
|
|
53
|
+
total_charge=lib.to_decimal(quote.totalCharge),
|
|
54
|
+
transit_days=quote.transitDays,
|
|
55
|
+
extra_charges=[
|
|
56
|
+
models.ChargeDetails(
|
|
57
|
+
name=name,
|
|
58
|
+
currency="CAD",
|
|
59
|
+
amount=lib.to_decimal(amount),
|
|
60
|
+
)
|
|
61
|
+
for name, amount in charges
|
|
62
|
+
if amount
|
|
63
|
+
],
|
|
64
|
+
meta=dict(rate_provider=rate_provider, service_name=service_name),
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def quote_request(
|
|
69
|
+
payload: models.RateRequest,
|
|
70
|
+
settings: provider_utils.Settings,
|
|
71
|
+
) -> lib.Serializable:
|
|
72
|
+
shipper = lib.to_address(payload.shipper)
|
|
73
|
+
recipient = lib.to_address(payload.recipient)
|
|
74
|
+
packages = lib.to_packages(
|
|
75
|
+
payload.parcels,
|
|
76
|
+
package_option_type=provider_units.ShippingOption,
|
|
77
|
+
required=["weight", "height", "width", "length"],
|
|
78
|
+
)
|
|
79
|
+
options = lib.to_shipping_options(
|
|
80
|
+
payload.options,
|
|
81
|
+
package_options=packages.options,
|
|
82
|
+
initializer=provider_units.shipping_options_initializer,
|
|
83
|
+
)
|
|
84
|
+
packaging_type = provider_units.FreightPackagingType[
|
|
85
|
+
packages.package_type or "small_box"
|
|
86
|
+
].value
|
|
87
|
+
packaging = (
|
|
88
|
+
"Pallet"
|
|
89
|
+
if packaging_type in [provider_units.FreightPackagingType.pallet.value]
|
|
90
|
+
else "Package"
|
|
91
|
+
)
|
|
92
|
+
service = (
|
|
93
|
+
lib.to_services(payload.services, provider_units.ShippingService).first
|
|
94
|
+
or provider_units.ShippingService.freightcom_all
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
request = Freightcom(
|
|
98
|
+
username=settings.username,
|
|
99
|
+
password=settings.password,
|
|
100
|
+
version="3.1.0",
|
|
101
|
+
QuoteRequest=QuoteRequestType(
|
|
102
|
+
saturdayPickupRequired=options.freightcom_saturday_pickup_required.state,
|
|
103
|
+
homelandSecurity=options.freightcom_homeland_security.state,
|
|
104
|
+
pierCharge=None,
|
|
105
|
+
exhibitionConventionSite=options.freightcom_exhibition_convention_site.state,
|
|
106
|
+
militaryBaseDelivery=options.freightcom_military_base_delivery.state,
|
|
107
|
+
customsIn_bondFreight=options.freightcom_customs_in_bond_freight.state,
|
|
108
|
+
limitedAccess=options.freightcom_limited_access.state,
|
|
109
|
+
excessLength=options.freightcom_excess_length.state,
|
|
110
|
+
tailgatePickup=options.freightcom_tailgate_pickup.state,
|
|
111
|
+
residentialPickup=options.freightcom_residential_pickup.state,
|
|
112
|
+
crossBorderFee=None,
|
|
113
|
+
notifyRecipient=options.freightcom_notify_recipient.state,
|
|
114
|
+
singleShipment=options.freightcom_single_shipment.state,
|
|
115
|
+
tailgateDelivery=options.freightcom_tailgate_delivery.state,
|
|
116
|
+
residentialDelivery=options.freightcom_residential_delivery.state,
|
|
117
|
+
insuranceType=options.insurance.state is not None,
|
|
118
|
+
scheduledShipDate=None,
|
|
119
|
+
insideDelivery=options.freightcom_inside_delivery.state,
|
|
120
|
+
isSaturdayService=options.freightcom_is_saturday_service.state,
|
|
121
|
+
dangerousGoodsType=options.freightcom_dangerous_goods_type.state,
|
|
122
|
+
serviceId=service.value,
|
|
123
|
+
stackable=options.freightcom_stackable.state,
|
|
124
|
+
From=FromType(
|
|
125
|
+
id=None,
|
|
126
|
+
company=shipper.company_name or " ",
|
|
127
|
+
instructions=None,
|
|
128
|
+
email=shipper.email,
|
|
129
|
+
attention=shipper.person_name,
|
|
130
|
+
phone=shipper.phone_number,
|
|
131
|
+
tailgateRequired=None,
|
|
132
|
+
residential=shipper.residential,
|
|
133
|
+
address1=shipper.street,
|
|
134
|
+
address2=lib.text(shipper.address_line2),
|
|
135
|
+
city=shipper.city,
|
|
136
|
+
state=shipper.state_code,
|
|
137
|
+
zip=shipper.postal_code,
|
|
138
|
+
country=shipper.country_code,
|
|
139
|
+
),
|
|
140
|
+
To=ToType(
|
|
141
|
+
id=None,
|
|
142
|
+
company=recipient.company_name or " ",
|
|
143
|
+
notifyRecipient=None,
|
|
144
|
+
instructions=None,
|
|
145
|
+
email=recipient.email,
|
|
146
|
+
attention=recipient.person_name,
|
|
147
|
+
phone=recipient.phone_number,
|
|
148
|
+
tailgateRequired=None,
|
|
149
|
+
residential=recipient.residential,
|
|
150
|
+
address1=recipient.street,
|
|
151
|
+
address2=lib.text(recipient.address_line2),
|
|
152
|
+
city=recipient.city,
|
|
153
|
+
state=recipient.state_code,
|
|
154
|
+
zip=recipient.postal_code,
|
|
155
|
+
country=recipient.country_code,
|
|
156
|
+
),
|
|
157
|
+
COD=None,
|
|
158
|
+
Packages=PackagesType(
|
|
159
|
+
Package=[
|
|
160
|
+
PackageType(
|
|
161
|
+
length=provider_utils.ceil(package.length.IN),
|
|
162
|
+
width=provider_utils.ceil(package.width.IN),
|
|
163
|
+
height=provider_utils.ceil(package.height.IN),
|
|
164
|
+
weight=provider_utils.ceil(package.weight.LB),
|
|
165
|
+
type_=packaging_type,
|
|
166
|
+
freightClass=package.parcel.freight_class,
|
|
167
|
+
nmfcCode=None,
|
|
168
|
+
insuranceAmount=package.options.insurance.state,
|
|
169
|
+
codAmount=package.options.cash_on_delivery.state,
|
|
170
|
+
description=package.parcel.description,
|
|
171
|
+
)
|
|
172
|
+
for package in packages
|
|
173
|
+
],
|
|
174
|
+
type_=packaging,
|
|
175
|
+
),
|
|
176
|
+
),
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
return lib.Serializable(request, provider_utils.standard_request_serializer)
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
from karrio.schemas.freightcom.shipping_request import (
|
|
2
|
+
Freightcom,
|
|
3
|
+
ShippingRequestType,
|
|
4
|
+
FromType,
|
|
5
|
+
ToType,
|
|
6
|
+
PackagesType,
|
|
7
|
+
PackageType,
|
|
8
|
+
PaymentType as RequestPaymentType,
|
|
9
|
+
CODType,
|
|
10
|
+
CODReturnAddressType,
|
|
11
|
+
ContactType,
|
|
12
|
+
ReferenceType,
|
|
13
|
+
CustomsInvoiceType,
|
|
14
|
+
ItemType,
|
|
15
|
+
BillToType,
|
|
16
|
+
)
|
|
17
|
+
from karrio.schemas.freightcom.shipping_reply import (
|
|
18
|
+
ShippingReplyType,
|
|
19
|
+
QuoteType,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
import typing
|
|
23
|
+
import karrio.lib as lib
|
|
24
|
+
import karrio.core.models as models
|
|
25
|
+
import karrio.providers.freightcom.error as provider_error
|
|
26
|
+
import karrio.providers.freightcom.units as provider_units
|
|
27
|
+
import karrio.providers.freightcom.utils as provider_utils
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def parse_shipping_reply(
|
|
31
|
+
_response: lib.Deserializable[lib.Element],
|
|
32
|
+
settings: provider_utils.Settings,
|
|
33
|
+
) -> typing.Tuple[models.ShipmentDetails, typing.List[models.Message]]:
|
|
34
|
+
response = _response.deserialize()
|
|
35
|
+
shipping_node = lib.find_element("ShippingReply", response, first=True)
|
|
36
|
+
shipment = (
|
|
37
|
+
_extract_shipment(shipping_node, settings)
|
|
38
|
+
if shipping_node is not None
|
|
39
|
+
else None
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
return shipment, provider_error.parse_error_response(response, settings)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _extract_shipment(
|
|
46
|
+
node: lib.Element,
|
|
47
|
+
settings: provider_utils.Settings,
|
|
48
|
+
) -> models.ShipmentDetails:
|
|
49
|
+
shipping = lib.to_object(ShippingReplyType, node)
|
|
50
|
+
quote: QuoteType = shipping.Quote or QuoteType()
|
|
51
|
+
|
|
52
|
+
tracking_number = getattr(
|
|
53
|
+
next(iter(shipping.Package), None), "trackingNumber", None
|
|
54
|
+
)
|
|
55
|
+
rate_provider, service, service_name = provider_units.ShippingService.info(
|
|
56
|
+
quote.serviceId, quote.carrierId, quote.serviceName, quote.carrierName
|
|
57
|
+
)
|
|
58
|
+
invoice = dict(invoice=shipping.CustomsInvoice) if shipping.CustomsInvoice else {}
|
|
59
|
+
charges = [
|
|
60
|
+
("Base charge", quote.baseCharge),
|
|
61
|
+
("Fuel surcharge", quote.fuelSurcharge),
|
|
62
|
+
*((surcharge.name, surcharge.amount) for surcharge in quote.Surcharge),
|
|
63
|
+
]
|
|
64
|
+
|
|
65
|
+
return models.ShipmentDetails(
|
|
66
|
+
carrier_name=settings.carrier_name,
|
|
67
|
+
carrier_id=settings.carrier_id,
|
|
68
|
+
tracking_number=tracking_number,
|
|
69
|
+
shipment_identifier=shipping.Order.id,
|
|
70
|
+
selected_rate=(
|
|
71
|
+
models.RateDetails(
|
|
72
|
+
carrier_name=settings.carrier_name,
|
|
73
|
+
carrier_id=settings.carrier_id,
|
|
74
|
+
service=service,
|
|
75
|
+
currency=quote.currency,
|
|
76
|
+
total_charge=lib.to_decimal(quote.totalCharge),
|
|
77
|
+
transit_days=quote.transitDays,
|
|
78
|
+
extra_charges=[
|
|
79
|
+
models.ChargeDetails(
|
|
80
|
+
name=name,
|
|
81
|
+
currency="CAD",
|
|
82
|
+
amount=lib.to_decimal(amount),
|
|
83
|
+
)
|
|
84
|
+
for name, amount in charges
|
|
85
|
+
if amount
|
|
86
|
+
],
|
|
87
|
+
meta=dict(rate_provider=rate_provider, service_name=service_name),
|
|
88
|
+
)
|
|
89
|
+
if shipping.Quote is not None
|
|
90
|
+
else None
|
|
91
|
+
),
|
|
92
|
+
docs=models.Documents(label=shipping.Labels, **invoice),
|
|
93
|
+
meta=dict(
|
|
94
|
+
rate_provider=rate_provider,
|
|
95
|
+
service_name=service_name,
|
|
96
|
+
tracking_url=shipping.TrackingURL,
|
|
97
|
+
),
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def shipping_request(
|
|
102
|
+
payload: models.ShipmentRequest,
|
|
103
|
+
settings: provider_utils.Settings,
|
|
104
|
+
) -> lib.Serializable:
|
|
105
|
+
shipper = lib.to_address(payload.shipper)
|
|
106
|
+
recipient = lib.to_address(payload.recipient)
|
|
107
|
+
service = provider_units.ShippingService.map(payload.service).value_or_key
|
|
108
|
+
packages = lib.to_packages(
|
|
109
|
+
payload.parcels,
|
|
110
|
+
package_option_type=provider_units.ShippingOption,
|
|
111
|
+
required=["weight", "height", "width", "length"],
|
|
112
|
+
)
|
|
113
|
+
options = lib.to_shipping_options(
|
|
114
|
+
payload.options,
|
|
115
|
+
package_options=packages.options,
|
|
116
|
+
initializer=provider_units.shipping_options_initializer,
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
is_intl = shipper.country_code != recipient.country_code
|
|
120
|
+
customs = lib.to_customs_info(
|
|
121
|
+
payload.customs,
|
|
122
|
+
shipper=payload.shipper,
|
|
123
|
+
recipient=payload.recipient,
|
|
124
|
+
weight_unit=packages.weight_unit,
|
|
125
|
+
default_to=(
|
|
126
|
+
models.Customs(
|
|
127
|
+
commodities=(
|
|
128
|
+
packages.items
|
|
129
|
+
if any(packages.items)
|
|
130
|
+
else [
|
|
131
|
+
models.Commodity(
|
|
132
|
+
quantity=1,
|
|
133
|
+
sku=f"000{index}",
|
|
134
|
+
weight=pkg.weight.value,
|
|
135
|
+
weight_unit=pkg.weight_unit.value,
|
|
136
|
+
description=pkg.parcel.content,
|
|
137
|
+
)
|
|
138
|
+
for index, pkg in enumerate(packages, start=1)
|
|
139
|
+
]
|
|
140
|
+
)
|
|
141
|
+
)
|
|
142
|
+
if is_intl
|
|
143
|
+
else None
|
|
144
|
+
),
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
packaging_type = provider_units.FreightPackagingType.map(
|
|
148
|
+
packages.package_type or "small_box"
|
|
149
|
+
).value
|
|
150
|
+
packaging = (
|
|
151
|
+
"Pallet"
|
|
152
|
+
if packaging_type in [provider_units.FreightPackagingType.pallet.value]
|
|
153
|
+
else "Package"
|
|
154
|
+
)
|
|
155
|
+
payment = payload.payment or models.Payment()
|
|
156
|
+
payment_type = (
|
|
157
|
+
provider_units.PaymentType[payment.paid_by] if payload.payment else None
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
request = Freightcom(
|
|
161
|
+
version="3.1.0",
|
|
162
|
+
username=settings.username,
|
|
163
|
+
password=settings.password,
|
|
164
|
+
ShippingRequest=ShippingRequestType(
|
|
165
|
+
saturdayPickupRequired=options.freightcom_saturday_pickup_required.state,
|
|
166
|
+
homelandSecurity=options.freightcom_homeland_security.state,
|
|
167
|
+
pierCharge=None,
|
|
168
|
+
exhibitionConventionSite=options.freightcom_exhibition_convention_site.state,
|
|
169
|
+
militaryBaseDelivery=options.freightcom_military_base_delivery.state,
|
|
170
|
+
customsIn_bondFreight=options.freightcom_customs_in_bond_freight.state,
|
|
171
|
+
limitedAccess=options.freightcom_limited_access.state,
|
|
172
|
+
excessLength=options.freightcom_excess_length.state,
|
|
173
|
+
tailgatePickup=options.freightcom_tailgate_pickup.state,
|
|
174
|
+
residentialPickup=options.freightcom_residential_pickup.state,
|
|
175
|
+
crossBorderFee=None,
|
|
176
|
+
notifyRecipient=options.freightcom_notify_recipient.state,
|
|
177
|
+
singleShipment=options.freightcom_single_shipment.state,
|
|
178
|
+
tailgateDelivery=options.freightcom_tailgate_delivery.state,
|
|
179
|
+
residentialDelivery=options.freightcom_residential_delivery.state,
|
|
180
|
+
insuranceType=(options.insurance.state is not None),
|
|
181
|
+
scheduledShipDate=None,
|
|
182
|
+
insideDelivery=options.freightcom_inside_delivery.state,
|
|
183
|
+
isSaturdayService=options.freightcom_is_saturday_service.state,
|
|
184
|
+
dangerousGoodsType=options.freightcom_dangerous_goods_type.state,
|
|
185
|
+
serviceId=service,
|
|
186
|
+
stackable=options.freightcom_stackable.state,
|
|
187
|
+
From=FromType(
|
|
188
|
+
id=None,
|
|
189
|
+
company=shipper.company_name,
|
|
190
|
+
instructions=None,
|
|
191
|
+
email=shipper.email,
|
|
192
|
+
attention=shipper.person_name,
|
|
193
|
+
phone=shipper.phone_number,
|
|
194
|
+
tailgateRequired=None,
|
|
195
|
+
residential=shipper.residential,
|
|
196
|
+
address1=shipper.street,
|
|
197
|
+
address2=lib.text(shipper.address_line2),
|
|
198
|
+
city=shipper.city,
|
|
199
|
+
state=shipper.state_code,
|
|
200
|
+
zip=shipper.postal_code,
|
|
201
|
+
country=shipper.country_code,
|
|
202
|
+
),
|
|
203
|
+
To=ToType(
|
|
204
|
+
id=None,
|
|
205
|
+
company=recipient.company_name,
|
|
206
|
+
notifyRecipient=None,
|
|
207
|
+
instructions=None,
|
|
208
|
+
email=recipient.email,
|
|
209
|
+
attention=recipient.person_name,
|
|
210
|
+
phone=recipient.phone_number,
|
|
211
|
+
tailgateRequired=None,
|
|
212
|
+
residential=recipient.residential,
|
|
213
|
+
address1=recipient.street,
|
|
214
|
+
address2=lib.text(recipient.address_line2),
|
|
215
|
+
city=recipient.city,
|
|
216
|
+
state=recipient.state_code,
|
|
217
|
+
zip=recipient.postal_code,
|
|
218
|
+
country=recipient.country_code,
|
|
219
|
+
),
|
|
220
|
+
COD=(
|
|
221
|
+
CODType(
|
|
222
|
+
paymentType=provider_units.PaymentType.recipient.value,
|
|
223
|
+
CODReturnAddress=CODReturnAddressType(
|
|
224
|
+
codCompany=recipient.company_name,
|
|
225
|
+
codName=recipient.person_name,
|
|
226
|
+
codAddress1=lib.text(recipient.address_line1),
|
|
227
|
+
codCity=recipient.city,
|
|
228
|
+
codStateCode=recipient.state_code,
|
|
229
|
+
codZip=recipient.postal_code,
|
|
230
|
+
codCountry=recipient.country_code,
|
|
231
|
+
),
|
|
232
|
+
)
|
|
233
|
+
if options.cash_on_delivery.state is not None
|
|
234
|
+
else None
|
|
235
|
+
),
|
|
236
|
+
Packages=PackagesType(
|
|
237
|
+
Package=[
|
|
238
|
+
PackageType(
|
|
239
|
+
length=provider_utils.ceil(package.length.IN),
|
|
240
|
+
width=provider_utils.ceil(package.width.IN),
|
|
241
|
+
height=provider_utils.ceil(package.height.IN),
|
|
242
|
+
weight=provider_utils.ceil(package.weight.LB),
|
|
243
|
+
type_=packaging_type,
|
|
244
|
+
freightClass=package.parcel.freight_class,
|
|
245
|
+
nmfcCode=None,
|
|
246
|
+
insuranceAmount=package.options.insurance.state,
|
|
247
|
+
codAmount=package.options.cash_on_delivery.state,
|
|
248
|
+
description=package.parcel.description,
|
|
249
|
+
)
|
|
250
|
+
for package in packages
|
|
251
|
+
],
|
|
252
|
+
type_=packaging,
|
|
253
|
+
),
|
|
254
|
+
Payment=(RequestPaymentType(type_=payment_type) if payment_type else None),
|
|
255
|
+
Reference=(
|
|
256
|
+
[ReferenceType(name="REF", code=payload.reference)]
|
|
257
|
+
if payload.reference != ""
|
|
258
|
+
else None
|
|
259
|
+
),
|
|
260
|
+
CustomsInvoice=(
|
|
261
|
+
CustomsInvoiceType(
|
|
262
|
+
BillTo=BillToType(
|
|
263
|
+
company=customs.duty_billing_address.company_name,
|
|
264
|
+
name=customs.duty_billing_address.person_name,
|
|
265
|
+
address1=customs.duty_billing_address.address_line,
|
|
266
|
+
city=customs.duty_billing_address.city,
|
|
267
|
+
state=customs.duty_billing_address.state_code,
|
|
268
|
+
zip=customs.duty_billing_address.postal_code,
|
|
269
|
+
country=customs.duty_billing_address.country_code,
|
|
270
|
+
),
|
|
271
|
+
Contact=ContactType(
|
|
272
|
+
name=customs.duty_billing_address.person_name,
|
|
273
|
+
phone=customs.duty_billing_address.phone_number,
|
|
274
|
+
),
|
|
275
|
+
Item=[
|
|
276
|
+
ItemType(
|
|
277
|
+
code=(item.hs_code or item.sku or "0000"),
|
|
278
|
+
description=lib.text(
|
|
279
|
+
item.description or item.title or "item"
|
|
280
|
+
),
|
|
281
|
+
originCountry=(item.origin_country or shipper.country_code),
|
|
282
|
+
unitPrice=item.value_amount,
|
|
283
|
+
quantity=(item.quantity or 1),
|
|
284
|
+
)
|
|
285
|
+
for item in customs.commodities
|
|
286
|
+
],
|
|
287
|
+
)
|
|
288
|
+
if payload.customs
|
|
289
|
+
else None
|
|
290
|
+
),
|
|
291
|
+
),
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
return lib.Serializable(request, provider_utils.standard_request_serializer)
|