karrio-tnt 2025.5rc1__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/tnt/__init__.py +3 -0
- karrio/mappers/tnt/mapper.py +40 -0
- karrio/mappers/tnt/proxy.py +97 -0
- karrio/mappers/tnt/settings.py +22 -0
- karrio/plugins/tnt/__init__.py +24 -0
- karrio/providers/tnt/__init__.py +10 -0
- karrio/providers/tnt/error.py +129 -0
- karrio/providers/tnt/rate.py +184 -0
- karrio/providers/tnt/shipment/__init__.py +4 -0
- karrio/providers/tnt/shipment/create.py +226 -0
- karrio/providers/tnt/tracking.py +71 -0
- karrio/providers/tnt/units.py +166 -0
- karrio/providers/tnt/utils.py +162 -0
- karrio/schemas/tnt/__init__.py +0 -0
- karrio/schemas/tnt/label_common_definitions.py +3499 -0
- karrio/schemas/tnt/label_request.py +4409 -0
- karrio/schemas/tnt/label_response.py +5744 -0
- karrio/schemas/tnt/rating_common_definitions.py +1610 -0
- karrio/schemas/tnt/rating_request.py +2886 -0
- karrio/schemas/tnt/rating_response.py +3077 -0
- karrio/schemas/tnt/shipping_common_definitions.py +1216 -0
- karrio/schemas/tnt/shipping_request.py +6114 -0
- karrio/schemas/tnt/shipping_response.py +3404 -0
- karrio/schemas/tnt/tracking_request.py +2742 -0
- karrio/schemas/tnt/tracking_response.py +3729 -0
- karrio_tnt-2025.5rc1.dist-info/METADATA +44 -0
- karrio_tnt-2025.5rc1.dist-info/RECORD +30 -0
- karrio_tnt-2025.5rc1.dist-info/WHEEL +5 -0
- karrio_tnt-2025.5rc1.dist-info/entry_points.txt +2 -0
- karrio_tnt-2025.5rc1.dist-info/top_level.txt +3 -0
@@ -0,0 +1,226 @@
|
|
1
|
+
import karrio.schemas.tnt.shipping_request as tnt
|
2
|
+
import karrio.schemas.tnt.shipping_response as shipping
|
3
|
+
import uuid
|
4
|
+
import typing
|
5
|
+
import karrio.lib as lib
|
6
|
+
import karrio.core.units as units
|
7
|
+
import karrio.core.models as models
|
8
|
+
import karrio.providers.tnt.error as provider_error
|
9
|
+
import karrio.providers.tnt.utils as provider_utils
|
10
|
+
import karrio.providers.tnt.units as provider_units
|
11
|
+
|
12
|
+
|
13
|
+
def parse_shipment_response(
|
14
|
+
_response: lib.Deserializable[lib.Element],
|
15
|
+
settings: provider_utils.Settings,
|
16
|
+
) -> typing.Tuple[models.ShipmentDetails, typing.List[models.Message]]:
|
17
|
+
response = _response.deserialize()
|
18
|
+
|
19
|
+
messages = provider_error.parse_error_response(response, settings)
|
20
|
+
create: shipping.CREATE = lib.find_element(
|
21
|
+
"CREATE", response, element_type=shipping.CREATE, first=True
|
22
|
+
)
|
23
|
+
shipment = (
|
24
|
+
_extract_detail(create, settings, ctx=_response.ctx)
|
25
|
+
if getattr(create, "SUCCESS", None) == "Y"
|
26
|
+
else None
|
27
|
+
)
|
28
|
+
|
29
|
+
return shipment, messages
|
30
|
+
|
31
|
+
|
32
|
+
def _extract_detail(
|
33
|
+
detail: shipping.CREATE,
|
34
|
+
settings: provider_utils.Settings,
|
35
|
+
ctx: dict,
|
36
|
+
) -> typing.Optional[models.ShipmentDetails]:
|
37
|
+
label = ctx.get("label")
|
38
|
+
|
39
|
+
return models.ShipmentDetails(
|
40
|
+
carrier_name=settings.carrier_name,
|
41
|
+
carrier_id=settings.carrier_id,
|
42
|
+
tracking_number=detail.CONNUMBER,
|
43
|
+
shipment_identifier=detail.CONREF,
|
44
|
+
docs=models.Documents(label=label),
|
45
|
+
)
|
46
|
+
|
47
|
+
|
48
|
+
def shipment_request(
|
49
|
+
payload: models.ShipmentRequest,
|
50
|
+
settings: provider_utils.Settings,
|
51
|
+
) -> lib.Serializable:
|
52
|
+
shipper = lib.to_address(payload.shipper)
|
53
|
+
recipient = lib.to_address(payload.recipient)
|
54
|
+
packages = lib.to_packages(payload.parcels)
|
55
|
+
service = provider_units.ShippingService.map(payload.service).value_or_key
|
56
|
+
payment = payload.payment or models.Payment(paid_by="sender")
|
57
|
+
is_document = all([parcel.is_document for parcel in payload.parcels])
|
58
|
+
options = lib.to_shipping_options(
|
59
|
+
payload.options,
|
60
|
+
package_options=packages.options,
|
61
|
+
shipper_country_code=payload.shipper.country_code,
|
62
|
+
recipient_country_code=payload.recipient.country_code,
|
63
|
+
is_international=(
|
64
|
+
payload.shipper.country_code != payload.recipient.country_code
|
65
|
+
),
|
66
|
+
initializer=provider_units.shipping_options_initializer,
|
67
|
+
)
|
68
|
+
ref = payload.parcels[0].reference_number or f"ref_{uuid.uuid4()}"
|
69
|
+
insurance = options.tnt_insurance.state
|
70
|
+
|
71
|
+
request = tnt.ESHIPPER(
|
72
|
+
LOGIN=tnt.LOGIN(
|
73
|
+
COMPANY=settings.username,
|
74
|
+
PASSWORD=settings.password,
|
75
|
+
APPID=settings.connection_config.app_id.state or "EC",
|
76
|
+
APPVERSION="3.1",
|
77
|
+
),
|
78
|
+
CONSIGNMENTBATCH=tnt.CONSIGNMENTBATCH(
|
79
|
+
GROUPCODE=None,
|
80
|
+
SENDER=tnt.SENDER(
|
81
|
+
COMPANYNAME=shipper.company_name,
|
82
|
+
STREETADDRESS1=shipper.address_line1,
|
83
|
+
STREETADDRESS2=shipper.address_line2,
|
84
|
+
STREETADDRESS3=None,
|
85
|
+
CITY=shipper.city,
|
86
|
+
PROVINCE=shipper.state_code,
|
87
|
+
POSTCODE=shipper.postal_code,
|
88
|
+
COUNTRY=shipper.country_code,
|
89
|
+
ACCOUNT=settings.account_number,
|
90
|
+
VAT=shipper.tax_id,
|
91
|
+
CONTACTNAME=shipper.person_name,
|
92
|
+
CONTACTDIALCODE=None,
|
93
|
+
CONTACTTELEPHONE=shipper.phone_number,
|
94
|
+
CONTACTEMAIL=shipper.email,
|
95
|
+
COLLECTION=tnt.COLLECTION(
|
96
|
+
COLLECTIONADDRESS=None,
|
97
|
+
SHIPDATE=lib.fdatetime(
|
98
|
+
options.shipment_date.state,
|
99
|
+
current_format="%Y-%m-%d",
|
100
|
+
output_format="%d/%m/%Y",
|
101
|
+
),
|
102
|
+
PREFCOLLECTTIME=None,
|
103
|
+
ALTCOLLECTTIME=None,
|
104
|
+
COLLINSTRUCTIONS=None,
|
105
|
+
CONFIRMATIONEMAILADDRESS=None,
|
106
|
+
),
|
107
|
+
),
|
108
|
+
CONSIGNMENT=[
|
109
|
+
tnt.CONSIGNMENT(
|
110
|
+
CONREF=ref,
|
111
|
+
DETAILS=tnt.DETAILS(
|
112
|
+
RECEIVER=tnt.RECEIVER(
|
113
|
+
COMPANYNAME=recipient.company_name,
|
114
|
+
STREETADDRESS1=recipient.address_line1,
|
115
|
+
STREETADDRESS2=recipient.address_line2,
|
116
|
+
STREETADDRESS3=None,
|
117
|
+
CITY=recipient.city,
|
118
|
+
PROVINCE=recipient.state_code,
|
119
|
+
POSTCODE=recipient.postal_code,
|
120
|
+
COUNTRY=recipient.country_code,
|
121
|
+
VAT=recipient.tax_id,
|
122
|
+
CONTACTNAME=recipient.person_name,
|
123
|
+
CONTACTDIALCODE=None,
|
124
|
+
CONTACTTELEPHONE=recipient.phone_number,
|
125
|
+
CONTACTEMAIL=recipient.email,
|
126
|
+
ACCOUNT=None,
|
127
|
+
ACCOUNTCOUNTRY=None,
|
128
|
+
),
|
129
|
+
DELIVERY=None,
|
130
|
+
CONNUMBER=None,
|
131
|
+
CUSTOMERREF=payload.reference,
|
132
|
+
CONTYPE=("D" if is_document else "N"),
|
133
|
+
PAYMENTIND=provider_units.PaymentType.map(
|
134
|
+
payment.paid_by
|
135
|
+
).value,
|
136
|
+
ITEMS=len(packages),
|
137
|
+
TOTALWEIGHT=packages.weight.KG,
|
138
|
+
TOTALVOLUME=packages.volume.m3,
|
139
|
+
CURRENCY=options.currency.state,
|
140
|
+
GOODSVALUE=options.declared_value.state,
|
141
|
+
INSURANCEVALUE=insurance,
|
142
|
+
INSURANCECURRENCY=options.currency.state,
|
143
|
+
DIVISION=None,
|
144
|
+
SERVICE=service,
|
145
|
+
OPTION=[
|
146
|
+
option.code
|
147
|
+
for _, option in options.items()
|
148
|
+
if "division" not in _
|
149
|
+
],
|
150
|
+
DESCRIPTION=None,
|
151
|
+
DELIVERYINST=None,
|
152
|
+
CUSTOMCONTROLIN=None,
|
153
|
+
HAZARDOUS=None,
|
154
|
+
UNNUMBER=None,
|
155
|
+
PACKINGGROUP=None,
|
156
|
+
PACKAGE=[
|
157
|
+
tnt.PACKAGE(
|
158
|
+
ITEMS=package.items.quantity,
|
159
|
+
DESCRIPTION=package.parcel.description,
|
160
|
+
LENGTH=package.length.M,
|
161
|
+
HEIGHT=package.height.M,
|
162
|
+
WIDTH=package.width.M,
|
163
|
+
WEIGHT=package.weight.KG,
|
164
|
+
ARTICLE=(
|
165
|
+
[
|
166
|
+
tnt.ARTICLE(
|
167
|
+
ITEMS=item.quantity,
|
168
|
+
DESCRIPTION=lib.text(
|
169
|
+
item.title or item.description or "N/A",
|
170
|
+
max=35,
|
171
|
+
),
|
172
|
+
WEIGHT=units.Weight(
|
173
|
+
item.weight,
|
174
|
+
units.WeightUnit[item.weight_unit],
|
175
|
+
).KG,
|
176
|
+
INVOICEVALUE=item.value_amount,
|
177
|
+
INVOICEDESC=lib.text(
|
178
|
+
item.description or item.title,
|
179
|
+
max=35,
|
180
|
+
),
|
181
|
+
HTS=item.hs_code or item.sku,
|
182
|
+
COUNTRY=item.origin_country,
|
183
|
+
)
|
184
|
+
for item in package.items
|
185
|
+
]
|
186
|
+
if len(package.items) > 0
|
187
|
+
else None
|
188
|
+
),
|
189
|
+
)
|
190
|
+
for package in packages
|
191
|
+
],
|
192
|
+
),
|
193
|
+
)
|
194
|
+
],
|
195
|
+
),
|
196
|
+
ACTIVITY=tnt.ACTIVITY(
|
197
|
+
CREATE=tnt.CREATE(CONREF=[ref]),
|
198
|
+
RATE=tnt.RATE(CONREF=[ref]),
|
199
|
+
BOOK=tnt.BOOK(CONREF=[ref]),
|
200
|
+
SHIP=tnt.SHIP(CONREF=[ref]),
|
201
|
+
PRINT=tnt.PRINT(
|
202
|
+
REQUIRED=tnt.REQUIRED(CONREF=[ref]),
|
203
|
+
CONNOTE=tnt.CONNOTE(CONREF=[ref]),
|
204
|
+
LABEL=tnt.LABEL(CONREF=[ref]),
|
205
|
+
MANIFEST=tnt.MANIFEST(CONREF=[ref]),
|
206
|
+
INVOICE=tnt.INVOICE(CONREF=[ref]),
|
207
|
+
EMAILTO=(
|
208
|
+
tnt.EMAILTO(
|
209
|
+
type_=None,
|
210
|
+
valueOf_=(
|
211
|
+
options.email_notification_to.state or recipient.email
|
212
|
+
),
|
213
|
+
)
|
214
|
+
if (
|
215
|
+
options.email_notification.state
|
216
|
+
and any([options.email_notification_to.state, recipient.email])
|
217
|
+
)
|
218
|
+
else None
|
219
|
+
),
|
220
|
+
EMAILFROM=settings.connection_config.email_from.state,
|
221
|
+
),
|
222
|
+
SHOW_GROUPCODE=tnt.SHOW_GROUPCODE(),
|
223
|
+
),
|
224
|
+
)
|
225
|
+
|
226
|
+
return lib.Serializable(request, lib.to_xml, dict(payload=payload))
|
@@ -0,0 +1,71 @@
|
|
1
|
+
import karrio.schemas.tnt.tracking_request as tnt
|
2
|
+
import karrio.schemas.tnt.tracking_response as tracking
|
3
|
+
import typing
|
4
|
+
import karrio.lib as lib
|
5
|
+
import karrio.core.models as models
|
6
|
+
import karrio.providers.tnt.error as provider_error
|
7
|
+
import karrio.providers.tnt.utils as provider_utils
|
8
|
+
import karrio.providers.tnt.units as provider_units
|
9
|
+
|
10
|
+
|
11
|
+
def parse_tracking_response(
|
12
|
+
_response: lib.Deserializable[lib.Element],
|
13
|
+
settings: provider_utils.Settings,
|
14
|
+
) -> typing.Tuple[typing.List[models.TrackingDetails], typing.List[models.Message]]:
|
15
|
+
response = _response.deserialize()
|
16
|
+
messages = provider_error.parse_error_response(response, settings)
|
17
|
+
tracking_details = [
|
18
|
+
_extract_detail(node, settings)
|
19
|
+
for node in lib.find_element("Consignment", response)
|
20
|
+
]
|
21
|
+
|
22
|
+
return tracking_details, messages
|
23
|
+
|
24
|
+
|
25
|
+
def _extract_detail(
|
26
|
+
node: dict, settings: provider_utils.Settings
|
27
|
+
) -> models.TrackingDetails:
|
28
|
+
detail = lib.to_object(tracking.ConsignmentType, node)
|
29
|
+
events: typing.List[tracking.StatusStructure] = detail.StatusData
|
30
|
+
|
31
|
+
return models.TrackingDetails(
|
32
|
+
carrier_name=settings.carrier_name,
|
33
|
+
carrier_id=settings.carrier_id,
|
34
|
+
tracking_number=detail.ConsignmentNumber,
|
35
|
+
events=[
|
36
|
+
models.TrackingEvent(
|
37
|
+
date=lib.fdate(status.LocalEventDate.valueOf_, "%Y%m%d"),
|
38
|
+
description=status.StatusDescription,
|
39
|
+
location=lib.join(
|
40
|
+
status.Depot, status.DepotName, join=True, separator="-"
|
41
|
+
),
|
42
|
+
code=status.StatusCode,
|
43
|
+
time=lib.flocaltime(status.LocalEventTime.valueOf_, "%H%M"),
|
44
|
+
)
|
45
|
+
for status in events
|
46
|
+
],
|
47
|
+
delivered=(detail.SummaryCode == "DEL"),
|
48
|
+
)
|
49
|
+
|
50
|
+
|
51
|
+
def tracking_request(
|
52
|
+
payload: models.TrackingRequest,
|
53
|
+
settings: provider_utils.Settings,
|
54
|
+
) -> lib.Serializable:
|
55
|
+
request = tnt.TrackRequest(
|
56
|
+
locale=settings.connection_config.locale.state or "en_US",
|
57
|
+
version="3.1",
|
58
|
+
SearchCriteria=tnt.SearchCriteriaType(
|
59
|
+
marketType="INTERNATIONAL",
|
60
|
+
originCountry=(settings.account_country_code or "US"),
|
61
|
+
ConsignmentNumber=payload.tracking_numbers,
|
62
|
+
),
|
63
|
+
LevelOfDetail=tnt.LevelOfDetailType(
|
64
|
+
Complete=tnt.CompleteType(
|
65
|
+
originAddress=True,
|
66
|
+
destinationAddress=True,
|
67
|
+
)
|
68
|
+
),
|
69
|
+
)
|
70
|
+
|
71
|
+
return lib.Serializable(request, lib.to_xml)
|
@@ -0,0 +1,166 @@
|
|
1
|
+
""" TNT Native Types """
|
2
|
+
|
3
|
+
import karrio.lib as lib
|
4
|
+
|
5
|
+
PRESET_DEFAULTS = dict(dimension_unit="CM", weight_unit="KG")
|
6
|
+
|
7
|
+
|
8
|
+
class PackagePresets(lib.Enum):
|
9
|
+
tnt_envelope_doc = lib.units.PackagePreset(
|
10
|
+
**dict(width=35.0, height=1.0, length=27.5, packaging_type="envelope"),
|
11
|
+
**PRESET_DEFAULTS
|
12
|
+
)
|
13
|
+
tnt_satchel_bag1 = lib.units.PackagePreset(
|
14
|
+
**dict(weight=2.0, width=40.0, height=1.0, length=30.0, packaging_type="pak"),
|
15
|
+
**PRESET_DEFAULTS
|
16
|
+
)
|
17
|
+
tnt_satchel_bag2 = lib.units.PackagePreset(
|
18
|
+
**dict(weight=4.0, width=47.5, height=1.0, length=38.0, packaging_type="pak"),
|
19
|
+
**PRESET_DEFAULTS
|
20
|
+
)
|
21
|
+
tnt_box_B = lib.units.PackagePreset(
|
22
|
+
**dict(
|
23
|
+
weight=4.0,
|
24
|
+
width=29.5,
|
25
|
+
height=19.0,
|
26
|
+
length=40.0,
|
27
|
+
packaging_type="medium_box",
|
28
|
+
),
|
29
|
+
**PRESET_DEFAULTS
|
30
|
+
)
|
31
|
+
tnt_box_C = lib.units.PackagePreset(
|
32
|
+
**dict(
|
33
|
+
weight=6.0,
|
34
|
+
width=29.5,
|
35
|
+
height=29.0,
|
36
|
+
length=40.0,
|
37
|
+
packaging_type="medium_box",
|
38
|
+
),
|
39
|
+
**PRESET_DEFAULTS
|
40
|
+
)
|
41
|
+
tnt_box_D = lib.units.PackagePreset(
|
42
|
+
**dict(
|
43
|
+
weight=10.0,
|
44
|
+
width=39.5,
|
45
|
+
height=29.0,
|
46
|
+
length=50.0,
|
47
|
+
packaging_type="medium_box",
|
48
|
+
),
|
49
|
+
**PRESET_DEFAULTS
|
50
|
+
)
|
51
|
+
tnt_box_E = lib.units.PackagePreset(
|
52
|
+
**dict(
|
53
|
+
weight=15.0,
|
54
|
+
width=39.5,
|
55
|
+
height=49.5,
|
56
|
+
length=44.0,
|
57
|
+
packaging_type="medium_box",
|
58
|
+
),
|
59
|
+
**PRESET_DEFAULTS
|
60
|
+
)
|
61
|
+
tnt_medpack_ambient = lib.units.PackagePreset(
|
62
|
+
**dict(width=18.0, height=12.0, length=23.0, packaging_type="medium_box"),
|
63
|
+
**PRESET_DEFAULTS
|
64
|
+
)
|
65
|
+
tnt_medpack_fronzen_10 = lib.units.PackagePreset(
|
66
|
+
**dict(width=37.0, height=35.5, length=40.0, packaging_type="large_box"),
|
67
|
+
**PRESET_DEFAULTS
|
68
|
+
)
|
69
|
+
|
70
|
+
|
71
|
+
class PackageType(lib.StrEnum):
|
72
|
+
tnt_envelope = "envelope"
|
73
|
+
tnt_satchel = "satchel"
|
74
|
+
tnt_box = "box"
|
75
|
+
tnt_cylinder = "cylinder"
|
76
|
+
tnt_pallet = "pallet"
|
77
|
+
|
78
|
+
""" Unified Packaging type mapping """
|
79
|
+
envelope = tnt_envelope
|
80
|
+
pak = tnt_satchel
|
81
|
+
tube = tnt_cylinder
|
82
|
+
pallet = tnt_pallet
|
83
|
+
small_box = tnt_box
|
84
|
+
medium_box = tnt_box
|
85
|
+
large_box = tnt_box
|
86
|
+
your_packaging = "your_packaging"
|
87
|
+
|
88
|
+
|
89
|
+
class PaymentType(lib.StrEnum):
|
90
|
+
sender = "S"
|
91
|
+
recipient = "R"
|
92
|
+
third_party = recipient
|
93
|
+
|
94
|
+
|
95
|
+
class ConnectionConfig(lib.Enum):
|
96
|
+
app_id = lib.OptionEnum("app_id")
|
97
|
+
email_from = lib.OptionEnum("email_from")
|
98
|
+
|
99
|
+
|
100
|
+
class ShippingService(lib.StrEnum):
|
101
|
+
tnt_special_express = "1N"
|
102
|
+
tnt_9_00_express = "09N"
|
103
|
+
tnt_10_00_express = "10N"
|
104
|
+
tnt_12_00_express = "12N"
|
105
|
+
tnt_express = "EX"
|
106
|
+
tnt_economy_express = "48N"
|
107
|
+
tnt_global_express = "15N"
|
108
|
+
|
109
|
+
|
110
|
+
class ShippingOption(lib.Enum):
|
111
|
+
tnt_priority = lib.OptionEnum("PR")
|
112
|
+
tnt_insurance = lib.OptionEnum("IN", lib.to_money)
|
113
|
+
tnt_enhanced_liability = lib.OptionEnum("EL")
|
114
|
+
tnt_dangerous_goods_fully_regulated = lib.OptionEnum("HZ")
|
115
|
+
tnt_dangerous_goods_in_limited_quantities = lib.OptionEnum("LQ")
|
116
|
+
tnt_dry_ice_shipments = lib.OptionEnum("DI")
|
117
|
+
tnt_biological_substances = lib.OptionEnum("BB")
|
118
|
+
tnt_lithium_batteries = lib.OptionEnum("LB")
|
119
|
+
tnt_dangerous_goods_in_excepted_quantities = lib.OptionEnum("EQ")
|
120
|
+
tnt_radioactive_materials_in_excepted_packages = lib.OptionEnum("XP")
|
121
|
+
tnt_pre_delivery_notification = lib.OptionEnum("SMS")
|
122
|
+
|
123
|
+
tnt_division_international_shipments = lib.OptionEnum("G", bool)
|
124
|
+
tnt_division_global_link_domestic = lib.OptionEnum("D", bool)
|
125
|
+
tnt_division_german_domestic = lib.OptionEnum("H", bool)
|
126
|
+
tnt_division_uk_domestic = lib.OptionEnum("010", bool)
|
127
|
+
|
128
|
+
insurance = tnt_insurance
|
129
|
+
|
130
|
+
|
131
|
+
def shipping_options_initializer(
|
132
|
+
options: dict,
|
133
|
+
package_options: lib.units.Options = None,
|
134
|
+
is_international: bool = None,
|
135
|
+
shipper_country_code: str = None,
|
136
|
+
recipient_country_code: str = None,
|
137
|
+
) -> lib.units.Options:
|
138
|
+
"""
|
139
|
+
Apply default values to the given options.
|
140
|
+
"""
|
141
|
+
_options = options.copy()
|
142
|
+
|
143
|
+
if package_options is not None:
|
144
|
+
_options.update(package_options.content)
|
145
|
+
|
146
|
+
if is_international:
|
147
|
+
_options.update(tnt_division_international_shipments=True)
|
148
|
+
|
149
|
+
if shipper_country_code == "DE" and recipient_country_code == "DE":
|
150
|
+
_options.update(tnt_division_german_domestic=True)
|
151
|
+
|
152
|
+
if shipper_country_code == "GB" and recipient_country_code == "GB":
|
153
|
+
_options.update(tnt_division_uk_domestic=True)
|
154
|
+
|
155
|
+
if shipper_country_code == recipient_country_code and shipper_country_code not in [
|
156
|
+
"DE",
|
157
|
+
"GB",
|
158
|
+
]:
|
159
|
+
_options.update(tnt_division_global_link_domestic=True)
|
160
|
+
|
161
|
+
def items_filter(key: str) -> bool:
|
162
|
+
return key in ShippingOption # type: ignore
|
163
|
+
|
164
|
+
return lib.units.ShippingOptions(
|
165
|
+
_options, ShippingOption, items_filter=items_filter
|
166
|
+
)
|
@@ -0,0 +1,162 @@
|
|
1
|
+
import karrio.schemas.tnt.label_request as tnt
|
2
|
+
import karrio.schemas.tnt.shipping_response as shipping
|
3
|
+
import typing
|
4
|
+
import base64
|
5
|
+
import karrio.lib as lib
|
6
|
+
import karrio.core as core
|
7
|
+
import karrio.core.models as models
|
8
|
+
|
9
|
+
|
10
|
+
class Settings(core.Settings):
|
11
|
+
"""TNT connection settings."""
|
12
|
+
|
13
|
+
username: str
|
14
|
+
password: str
|
15
|
+
|
16
|
+
account_number: str = None
|
17
|
+
account_country_code: str = None
|
18
|
+
metadata: dict = {}
|
19
|
+
config: dict = {}
|
20
|
+
|
21
|
+
@property
|
22
|
+
def carrier_name(self):
|
23
|
+
return "tnt"
|
24
|
+
|
25
|
+
@property
|
26
|
+
def server_url(self):
|
27
|
+
return "https://express.tnt.com"
|
28
|
+
|
29
|
+
@property
|
30
|
+
def authorization(self):
|
31
|
+
pair = "%s:%s" % (self.username, self.password)
|
32
|
+
return base64.b64encode(pair.encode("utf-8")).decode("ascii")
|
33
|
+
|
34
|
+
@property
|
35
|
+
def connection_config(self) -> lib.units.Options:
|
36
|
+
from karrio.providers.tnt.units import ConnectionConfig
|
37
|
+
|
38
|
+
return lib.to_connection_config(
|
39
|
+
self.config or {},
|
40
|
+
option_type=ConnectionConfig,
|
41
|
+
)
|
42
|
+
|
43
|
+
|
44
|
+
def create_label_request(
|
45
|
+
shipment_response: str,
|
46
|
+
settings: Settings,
|
47
|
+
ctx: dict,
|
48
|
+
) -> typing.Optional[lib.Serializable]:
|
49
|
+
import karrio.providers.tnt.units as provider_units
|
50
|
+
|
51
|
+
payload: models.ShipmentRequest = ctx.get("payload")
|
52
|
+
response = lib.to_element(shipment_response)
|
53
|
+
consignment = lib.find_element("CONNUMBER", response, first=True)
|
54
|
+
groupcode = lib.find_element("GROUPCODE", response, first=True)
|
55
|
+
price: shipping.document = lib.find_element(
|
56
|
+
"PRICE", response, shipping.PRICE, first=True
|
57
|
+
)
|
58
|
+
|
59
|
+
if consignment is None or groupcode is None or price is None:
|
60
|
+
return None
|
61
|
+
|
62
|
+
shipper = lib.to_address(payload.shipper)
|
63
|
+
recipient = lib.to_address(payload.recipient)
|
64
|
+
packages = lib.to_packages(payload.parcels)
|
65
|
+
customs = lib.to_customs_info(payload.customs)
|
66
|
+
|
67
|
+
request = tnt.labelRequest(
|
68
|
+
consignment=[
|
69
|
+
tnt.labelConsignmentsType(
|
70
|
+
key="1",
|
71
|
+
consignmentIdentity=tnt.consignmentIdentityType(
|
72
|
+
consignmentNumber=getattr(consignment, "text", None),
|
73
|
+
customerReference=payload.reference,
|
74
|
+
),
|
75
|
+
collectionDateTime=None,
|
76
|
+
sender=tnt.nameAndAddressRequestType(
|
77
|
+
name=recipient.contact,
|
78
|
+
addressLine1=shipper.street,
|
79
|
+
addressLine2=shipper.address_line2,
|
80
|
+
addressLine3=None,
|
81
|
+
town=shipper.city,
|
82
|
+
exactMatch=None,
|
83
|
+
province=shipper.state_code,
|
84
|
+
postcode=shipper.postal_code,
|
85
|
+
country=shipper.country_code,
|
86
|
+
),
|
87
|
+
delivery=tnt.nameAndAddressRequestType(
|
88
|
+
name=recipient.contact,
|
89
|
+
addressLine1=recipient.street,
|
90
|
+
addressLine2=recipient.address_line2,
|
91
|
+
addressLine3=None,
|
92
|
+
town=recipient.city,
|
93
|
+
exactMatch=None,
|
94
|
+
province=recipient.state_code,
|
95
|
+
postcode=recipient.postal_code,
|
96
|
+
country=recipient.country_code,
|
97
|
+
),
|
98
|
+
contact=tnt.contactType(
|
99
|
+
name=shipper.person_name,
|
100
|
+
telephoneNumber=shipper.phone_number,
|
101
|
+
emailAddress=shipper.email,
|
102
|
+
),
|
103
|
+
product=tnt.productType(
|
104
|
+
lineOfBusiness=None,
|
105
|
+
groupId=getattr(groupcode, "text", None),
|
106
|
+
subGroupId=None,
|
107
|
+
id=price.SERVICE,
|
108
|
+
type_=price.SERVICEDESC,
|
109
|
+
option=price.OPTION,
|
110
|
+
),
|
111
|
+
account=tnt.accountType(
|
112
|
+
accountNumber=settings.account_number,
|
113
|
+
accountCountry=settings.account_country_code,
|
114
|
+
),
|
115
|
+
cashAmount=price.RATE,
|
116
|
+
cashCurrency=price.CURRENCY,
|
117
|
+
cashType=None,
|
118
|
+
ncolNumber=None,
|
119
|
+
specialInstructions=None,
|
120
|
+
bulkShipment="N",
|
121
|
+
customControlled=("N" if payload.customs is None else "Y"),
|
122
|
+
termsOfPayment=provider_units.PaymentType.map(
|
123
|
+
getattr(payload.payment, "paidby", "sender")
|
124
|
+
).value,
|
125
|
+
totalNumberOfPieces=len(packages),
|
126
|
+
pieceLine=[
|
127
|
+
tnt.pieceLineType(
|
128
|
+
identifier=1,
|
129
|
+
goodsDescription=package.parcel.description,
|
130
|
+
barcodeForCustomer="Y",
|
131
|
+
pieceMeasurements=tnt.measurementsType(
|
132
|
+
length=package.length.M,
|
133
|
+
width=package.width.M,
|
134
|
+
height=package.height.M,
|
135
|
+
weight=package.weight.KG,
|
136
|
+
),
|
137
|
+
pieces=(
|
138
|
+
[
|
139
|
+
tnt.pieceType(
|
140
|
+
sequenceNumbers=(index + 1),
|
141
|
+
pieceReference=piece.sku or piece.hs_code,
|
142
|
+
)
|
143
|
+
for index, piece in enumerate(
|
144
|
+
(
|
145
|
+
package.items
|
146
|
+
if len(package.items) > 0
|
147
|
+
else customs.commodities
|
148
|
+
),
|
149
|
+
start=1,
|
150
|
+
)
|
151
|
+
]
|
152
|
+
if len(package.items) > 0 or len(customs.commodities) > 0
|
153
|
+
else None
|
154
|
+
),
|
155
|
+
)
|
156
|
+
for package in packages
|
157
|
+
],
|
158
|
+
)
|
159
|
+
]
|
160
|
+
)
|
161
|
+
|
162
|
+
return lib.Serializable(request, lib.to_xml)
|
File without changes
|