karrio-mydhl 2025.5rc7__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/mydhl/__init__.py +3 -0
- karrio/mappers/mydhl/mapper.py +99 -0
- karrio/mappers/mydhl/proxy.py +148 -0
- karrio/mappers/mydhl/settings.py +24 -0
- karrio/plugins/mydhl/__init__.py +24 -0
- karrio/providers/mydhl/__init__.py +28 -0
- karrio/providers/mydhl/address.py +65 -0
- karrio/providers/mydhl/error.py +77 -0
- karrio/providers/mydhl/pickup/__init__.py +14 -0
- karrio/providers/mydhl/pickup/cancel.py +62 -0
- karrio/providers/mydhl/pickup/create.py +94 -0
- karrio/providers/mydhl/pickup/update.py +108 -0
- karrio/providers/mydhl/rate.py +112 -0
- karrio/providers/mydhl/shipment/__init__.py +9 -0
- karrio/providers/mydhl/shipment/cancel.py +91 -0
- karrio/providers/mydhl/shipment/create.py +100 -0
- karrio/providers/mydhl/tracking.py +86 -0
- karrio/providers/mydhl/units.py +99 -0
- karrio/providers/mydhl/utils.py +92 -0
- karrio/schemas/mydhl/__init__.py +0 -0
- karrio/schemas/mydhl/address_validation_request.py +13 -0
- karrio/schemas/mydhl/address_validation_response.py +25 -0
- karrio/schemas/mydhl/error_response.py +13 -0
- karrio/schemas/mydhl/pickup_cancel_request.py +10 -0
- karrio/schemas/mydhl/pickup_cancel_response.py +8 -0
- karrio/schemas/mydhl/pickup_create_request.py +108 -0
- karrio/schemas/mydhl/pickup_create_response.py +11 -0
- karrio/schemas/mydhl/pickup_update_request.py +110 -0
- karrio/schemas/mydhl/pickup_update_response.py +11 -0
- karrio/schemas/mydhl/rate_request.py +114 -0
- karrio/schemas/mydhl/rate_response.py +143 -0
- karrio/schemas/mydhl/shipment_request.py +275 -0
- karrio/schemas/mydhl/shipment_response.py +90 -0
- karrio/schemas/mydhl/tracking_response.py +112 -0
- karrio_mydhl-2025.5rc7.dist-info/METADATA +44 -0
- karrio_mydhl-2025.5rc7.dist-info/RECORD +39 -0
- karrio_mydhl-2025.5rc7.dist-info/WHEEL +5 -0
- karrio_mydhl-2025.5rc7.dist-info/entry_points.txt +2 -0
- karrio_mydhl-2025.5rc7.dist-info/top_level.txt +3 -0
@@ -0,0 +1,99 @@
|
|
1
|
+
"""Karrio MyDHL client mapper."""
|
2
|
+
|
3
|
+
import typing
|
4
|
+
import karrio.lib as lib
|
5
|
+
import karrio.api.mapper as mapper
|
6
|
+
import karrio.core.models as models
|
7
|
+
import karrio.providers.mydhl as provider
|
8
|
+
import karrio.mappers.mydhl.settings as provider_settings
|
9
|
+
|
10
|
+
|
11
|
+
class Mapper(mapper.Mapper):
|
12
|
+
settings: provider_settings.Settings
|
13
|
+
|
14
|
+
def create_rate_request(
|
15
|
+
self, payload: models.RateRequest
|
16
|
+
) -> lib.Serializable:
|
17
|
+
return provider.rate_request(payload, self.settings)
|
18
|
+
|
19
|
+
def create_tracking_request(
|
20
|
+
self, payload: models.TrackingRequest
|
21
|
+
) -> lib.Serializable:
|
22
|
+
return provider.tracking_request(payload, self.settings)
|
23
|
+
|
24
|
+
def create_shipment_request(
|
25
|
+
self, payload: models.ShipmentRequest
|
26
|
+
) -> lib.Serializable:
|
27
|
+
return provider.shipment_request(payload, self.settings)
|
28
|
+
|
29
|
+
def create_pickup_request(
|
30
|
+
self, payload: models.PickupRequest
|
31
|
+
) -> lib.Serializable:
|
32
|
+
return provider.pickup_request(payload, self.settings)
|
33
|
+
|
34
|
+
def create_pickup_update_request(
|
35
|
+
self, payload: models.PickupUpdateRequest
|
36
|
+
) -> lib.Serializable:
|
37
|
+
return provider.pickup_update_request(payload, self.settings)
|
38
|
+
|
39
|
+
def create_cancel_pickup_request(
|
40
|
+
self, payload: models.PickupCancelRequest
|
41
|
+
) -> lib.Serializable:
|
42
|
+
return provider.pickup_cancel_request(payload, self.settings)
|
43
|
+
|
44
|
+
def create_cancel_shipment_request(
|
45
|
+
self, payload: models.ShipmentCancelRequest
|
46
|
+
) -> lib.Serializable[str]:
|
47
|
+
return provider.shipment_cancel_request(payload, self.settings)
|
48
|
+
|
49
|
+
def create_shipment_cancel_request(
|
50
|
+
self, payload: models.ShipmentCancelRequest
|
51
|
+
) -> lib.Serializable[str]:
|
52
|
+
return provider.shipment_cancel_request(payload, self.settings)
|
53
|
+
|
54
|
+
def create_address_validation_request(
|
55
|
+
self, payload: models.AddressValidationRequest
|
56
|
+
) -> lib.Serializable:
|
57
|
+
return provider.address_validation_request(payload, self.settings)
|
58
|
+
|
59
|
+
|
60
|
+
def parse_cancel_pickup_response(
|
61
|
+
self, response: lib.Deserializable[str]
|
62
|
+
) -> typing.Tuple[models.ConfirmationDetails, typing.List[models.Message]]:
|
63
|
+
return provider.parse_pickup_cancel_response(response, self.settings)
|
64
|
+
|
65
|
+
def parse_cancel_shipment_response(
|
66
|
+
self, response: lib.Deserializable[str]
|
67
|
+
) -> typing.Tuple[models.ConfirmationDetails, typing.List[models.Message]]:
|
68
|
+
return provider.parse_shipment_cancel_response(response, self.settings)
|
69
|
+
|
70
|
+
def parse_pickup_response(
|
71
|
+
self, response: lib.Deserializable[str]
|
72
|
+
) -> typing.Tuple[models.PickupDetails, typing.List[models.Message]]:
|
73
|
+
return provider.parse_pickup_response(response, self.settings)
|
74
|
+
|
75
|
+
def parse_pickup_update_response(
|
76
|
+
self, response: lib.Deserializable[str]
|
77
|
+
) -> typing.Tuple[models.PickupDetails, typing.List[models.Message]]:
|
78
|
+
return provider.parse_pickup_update_response(response, self.settings)
|
79
|
+
|
80
|
+
def parse_rate_response(
|
81
|
+
self, response: lib.Deserializable[str]
|
82
|
+
) -> typing.Tuple[typing.List[models.RateDetails], typing.List[models.Message]]:
|
83
|
+
return provider.parse_rate_response(response, self.settings)
|
84
|
+
|
85
|
+
def parse_shipment_response(
|
86
|
+
self, response: lib.Deserializable[str]
|
87
|
+
) -> typing.Tuple[models.ShipmentDetails, typing.List[models.Message]]:
|
88
|
+
return provider.parse_shipment_response(response, self.settings)
|
89
|
+
|
90
|
+
def parse_tracking_response(
|
91
|
+
self, response: lib.Deserializable[str]
|
92
|
+
) -> typing.Tuple[typing.List[models.TrackingDetails], typing.List[models.Message]]:
|
93
|
+
return provider.parse_tracking_response(response, self.settings)
|
94
|
+
|
95
|
+
def parse_address_validation_response(
|
96
|
+
self, response: lib.Deserializable[str]
|
97
|
+
) -> typing.Tuple[typing.List[models.AddressValidationDetails], typing.List[models.Message]]:
|
98
|
+
return provider.parse_address_validation_response(response, self.settings)
|
99
|
+
|
@@ -0,0 +1,148 @@
|
|
1
|
+
"""Karrio MyDHL client proxy."""
|
2
|
+
|
3
|
+
import karrio.lib as lib
|
4
|
+
import karrio.api.proxy as proxy
|
5
|
+
import karrio.mappers.mydhl.settings as provider_settings
|
6
|
+
|
7
|
+
class Proxy(proxy.Proxy):
|
8
|
+
settings: provider_settings.Settings
|
9
|
+
|
10
|
+
def get_rates(self, request: lib.Serializable) -> lib.Deserializable[str]:
|
11
|
+
"""Get rates using POST /rates for multi-piece shipments."""
|
12
|
+
response = lib.request(
|
13
|
+
url=f"{self.settings.server_url}/mydhlapi/rates",
|
14
|
+
data=lib.to_json(request.serialize()),
|
15
|
+
trace=self.trace_as("json"),
|
16
|
+
method="POST",
|
17
|
+
headers={
|
18
|
+
"Content-Type": "application/json",
|
19
|
+
"Authorization": f"Basic {self.settings.authorization}",
|
20
|
+
"Accept": "application/json",
|
21
|
+
},
|
22
|
+
)
|
23
|
+
return lib.Deserializable(response, lib.to_dict)
|
24
|
+
|
25
|
+
def create_shipment(self, request: lib.Serializable) -> lib.Deserializable[str]:
|
26
|
+
"""Create shipment using POST /shipments."""
|
27
|
+
response = lib.request(
|
28
|
+
url=f"{self.settings.server_url}/shipments",
|
29
|
+
data=lib.to_json(request.serialize()),
|
30
|
+
trace=self.trace_as("json"),
|
31
|
+
method="POST",
|
32
|
+
headers={
|
33
|
+
"Content-Type": "application/json",
|
34
|
+
"Authorization": f"Basic {self.settings.authorization}",
|
35
|
+
"Accept": "application/json",
|
36
|
+
},
|
37
|
+
)
|
38
|
+
return lib.Deserializable(response, lib.to_dict)
|
39
|
+
|
40
|
+
def cancel_shipment(self, request: lib.Serializable) -> lib.Deserializable[str]:
|
41
|
+
"""Cancel shipment using DELETE /shipments/{shipmentTrackingNumber}."""
|
42
|
+
data = request.serialize()
|
43
|
+
shipment_id = data.get("shipmentIdentifier") or data.get("shipmentId") or data.get("shipmentTrackingNumber")
|
44
|
+
|
45
|
+
response = lib.request(
|
46
|
+
url=f"{self.settings.server_url}/shipments/{shipment_id}/cancel",
|
47
|
+
trace=self.trace_as("json"),
|
48
|
+
method="DELETE",
|
49
|
+
headers={
|
50
|
+
"Authorization": f"Basic {self.settings.authorization}",
|
51
|
+
"Accept": "application/json",
|
52
|
+
},
|
53
|
+
)
|
54
|
+
return lib.Deserializable(response, lib.to_dict)
|
55
|
+
|
56
|
+
def get_tracking(self, request: lib.Serializable) -> lib.Deserializable:
|
57
|
+
"""Get tracking using GET /tracking with tracking numbers."""
|
58
|
+
def _get_tracking(tracking_number: str):
|
59
|
+
return tracking_number, lib.request(
|
60
|
+
url=f"{self.settings.server_url}/tracking",
|
61
|
+
trace=self.trace_as("json"),
|
62
|
+
method="GET",
|
63
|
+
headers={
|
64
|
+
"Authorization": f"Basic {self.settings.authorization}",
|
65
|
+
"Accept": "application/json",
|
66
|
+
},
|
67
|
+
)
|
68
|
+
|
69
|
+
data = request.serialize()
|
70
|
+
tracking_numbers = data.get("trackingNumbers", [])
|
71
|
+
responses = lib.run_concurently(_get_tracking, tracking_numbers)
|
72
|
+
return lib.Deserializable(
|
73
|
+
responses,
|
74
|
+
lambda res: [
|
75
|
+
(num, lib.to_dict(track)) for num, track in res if any(track.strip())
|
76
|
+
],
|
77
|
+
)
|
78
|
+
|
79
|
+
def schedule_pickup(self, request: lib.Serializable) -> lib.Deserializable[str]:
|
80
|
+
"""Schedule pickup using POST /pickups."""
|
81
|
+
response = lib.request(
|
82
|
+
url=f"{self.settings.server_url}/mydhlapi/pickups",
|
83
|
+
data=lib.to_json(request.serialize()),
|
84
|
+
trace=self.trace_as("json"),
|
85
|
+
method="POST",
|
86
|
+
headers={
|
87
|
+
"Content-Type": "application/json",
|
88
|
+
"Authorization": f"Basic {self.settings.authorization}",
|
89
|
+
"Accept": "application/json",
|
90
|
+
},
|
91
|
+
)
|
92
|
+
return lib.Deserializable(response, lib.to_dict)
|
93
|
+
|
94
|
+
def modify_pickup(self, request: lib.Serializable) -> lib.Deserializable[str]:
|
95
|
+
"""Update pickup using PATCH /pickups/{dispatchConfirmationNumber}."""
|
96
|
+
data = request.serialize()
|
97
|
+
dispatch_confirmation_number = data.get("dispatchConfirmationNumber")
|
98
|
+
|
99
|
+
response = lib.request(
|
100
|
+
url=f"{self.settings.server_url}/mydhlapi/pickups/{dispatch_confirmation_number}",
|
101
|
+
data=lib.to_json(data),
|
102
|
+
trace=self.trace_as("json"),
|
103
|
+
method="PATCH",
|
104
|
+
headers={
|
105
|
+
"Content-Type": "application/json",
|
106
|
+
"Authorization": f"Basic {self.settings.authorization}",
|
107
|
+
"Accept": "application/json",
|
108
|
+
},
|
109
|
+
)
|
110
|
+
return lib.Deserializable(response, lib.to_dict)
|
111
|
+
|
112
|
+
def cancel_pickup(self, request: lib.Serializable) -> lib.Deserializable[str]:
|
113
|
+
"""Cancel pickup using DELETE /pickups/{dispatchConfirmationNumber}."""
|
114
|
+
data = request.serialize()
|
115
|
+
dispatch_confirmation_number = data.get("dispatchConfirmationNumber")
|
116
|
+
requestor_name = data.get("requestorName", "System")
|
117
|
+
reason = data.get("reason", "Customer request")
|
118
|
+
|
119
|
+
response = lib.request(
|
120
|
+
url=f"{self.settings.server_url}/mydhlapi/pickups/{dispatch_confirmation_number}",
|
121
|
+
trace=self.trace_as("json"),
|
122
|
+
method="DELETE",
|
123
|
+
headers={
|
124
|
+
"Authorization": f"Basic {self.settings.authorization}",
|
125
|
+
"Accept": "application/json",
|
126
|
+
},
|
127
|
+
params={
|
128
|
+
"requestorName": requestor_name,
|
129
|
+
"reason": reason,
|
130
|
+
},
|
131
|
+
)
|
132
|
+
return lib.Deserializable(response, lib.to_dict)
|
133
|
+
|
134
|
+
def validate_address(self, request: lib.Serializable) -> lib.Deserializable[str]:
|
135
|
+
"""Validate address using GET /address-validate."""
|
136
|
+
data = request.serialize()
|
137
|
+
|
138
|
+
response = lib.request(
|
139
|
+
url=f"{self.settings.server_url}/mydhlapi/address-validate",
|
140
|
+
trace=self.trace_as("json"),
|
141
|
+
method="GET",
|
142
|
+
headers={
|
143
|
+
"Authorization": f"Basic {self.settings.authorization}",
|
144
|
+
"Accept": "application/json",
|
145
|
+
},
|
146
|
+
params=data,
|
147
|
+
)
|
148
|
+
return lib.Deserializable(response, lib.to_dict)
|
@@ -0,0 +1,24 @@
|
|
1
|
+
"""Karrio MyDHL client settings."""
|
2
|
+
|
3
|
+
import attr
|
4
|
+
import jstruct
|
5
|
+
import karrio.lib as lib
|
6
|
+
import karrio.providers.mydhl.utils as provider_utils
|
7
|
+
|
8
|
+
|
9
|
+
@attr.s(auto_attribs=True)
|
10
|
+
class Settings(provider_utils.Settings):
|
11
|
+
"""MyDHL connection settings."""
|
12
|
+
|
13
|
+
# MyDHL API credentials (required)
|
14
|
+
username: str # MyDHL API username
|
15
|
+
password: str # MyDHL API password
|
16
|
+
account_number: str # MyDHL customer account number
|
17
|
+
|
18
|
+
# generic properties
|
19
|
+
id: str = None
|
20
|
+
test_mode: bool = False
|
21
|
+
carrier_id: str = "mydhl"
|
22
|
+
account_country_code: str = None
|
23
|
+
metadata: dict = {}
|
24
|
+
config: dict = {}
|
@@ -0,0 +1,24 @@
|
|
1
|
+
import karrio.core.metadata as metadata
|
2
|
+
import karrio.mappers.mydhl as mappers
|
3
|
+
import karrio.providers.mydhl.units as units
|
4
|
+
import karrio.providers.mydhl.utils as utils
|
5
|
+
|
6
|
+
|
7
|
+
# This METADATA object is used by Karrio to discover and register this plugin
|
8
|
+
# when loaded through Python entrypoints or local plugin directories.
|
9
|
+
# The entrypoint is defined in pyproject.toml under [project.entry-points."karrio.plugins"]
|
10
|
+
METADATA = metadata.Metadata(
|
11
|
+
id="mydhl",
|
12
|
+
label="MyDHL",
|
13
|
+
# Integrations
|
14
|
+
Mapper=mappers.Mapper,
|
15
|
+
Proxy=mappers.Proxy,
|
16
|
+
Settings=mappers.Settings,
|
17
|
+
# Data Units
|
18
|
+
is_hub=False,
|
19
|
+
options=units.ShippingOption,
|
20
|
+
services=units.ShippingService,
|
21
|
+
package_presets=units.PackagePresets,
|
22
|
+
packaging_types=units.PackagingType,
|
23
|
+
connection_configs=utils.ConnectionConfig,
|
24
|
+
)
|
@@ -0,0 +1,28 @@
|
|
1
|
+
"""Karrio MyDHL provider imports."""
|
2
|
+
from karrio.providers.mydhl.utils import Settings
|
3
|
+
from karrio.providers.mydhl.rate import (
|
4
|
+
parse_rate_response,
|
5
|
+
rate_request,
|
6
|
+
)
|
7
|
+
from karrio.providers.mydhl.shipment import (
|
8
|
+
parse_shipment_cancel_response,
|
9
|
+
parse_shipment_response,
|
10
|
+
shipment_cancel_request,
|
11
|
+
shipment_request,
|
12
|
+
)
|
13
|
+
from karrio.providers.mydhl.pickup import (
|
14
|
+
parse_pickup_cancel_response,
|
15
|
+
parse_pickup_response,
|
16
|
+
parse_pickup_update_response,
|
17
|
+
pickup_cancel_request,
|
18
|
+
pickup_request,
|
19
|
+
pickup_update_request,
|
20
|
+
)
|
21
|
+
from karrio.providers.mydhl.tracking import (
|
22
|
+
parse_tracking_response,
|
23
|
+
tracking_request,
|
24
|
+
)
|
25
|
+
from karrio.providers.mydhl.address import (
|
26
|
+
parse_address_validation_response,
|
27
|
+
address_validation_request,
|
28
|
+
)
|
@@ -0,0 +1,65 @@
|
|
1
|
+
"""Karrio MyDHL address validation API implementation."""
|
2
|
+
|
3
|
+
import karrio.schemas.mydhl.address_validation_request as mydhl_req
|
4
|
+
import karrio.schemas.mydhl.address_validation_response as mydhl_res
|
5
|
+
|
6
|
+
import typing
|
7
|
+
import karrio.lib as lib
|
8
|
+
import karrio.core.models as models
|
9
|
+
import karrio.providers.mydhl.error as error
|
10
|
+
import karrio.providers.mydhl.utils as provider_utils
|
11
|
+
|
12
|
+
|
13
|
+
def parse_address_validation_response(
|
14
|
+
_response: lib.Deserializable[dict],
|
15
|
+
settings: provider_utils.Settings,
|
16
|
+
) -> typing.Tuple[typing.List[models.AddressValidationDetails], typing.List[models.Message]]:
|
17
|
+
response = _response.deserialize()
|
18
|
+
messages = error.parse_error_response(response, settings)
|
19
|
+
|
20
|
+
# Only create validation details if there are no errors
|
21
|
+
validation_details = []
|
22
|
+
if not messages:
|
23
|
+
validation_details = [_extract_details(response, settings)]
|
24
|
+
|
25
|
+
return validation_details, messages
|
26
|
+
|
27
|
+
|
28
|
+
def _extract_details(
|
29
|
+
data: dict,
|
30
|
+
settings: provider_utils.Settings,
|
31
|
+
) -> models.AddressValidationDetails:
|
32
|
+
"""Extract address validation details from MyDHL response."""
|
33
|
+
validation = lib.to_object(mydhl_res.AddressValidationResponseType, data)
|
34
|
+
|
35
|
+
warnings = getattr(validation, 'warnings', []) or []
|
36
|
+
addresses = getattr(validation, 'address', []) or []
|
37
|
+
|
38
|
+
return models.AddressValidationDetails(
|
39
|
+
carrier_id=settings.carrier_id,
|
40
|
+
carrier_name=settings.carrier_name,
|
41
|
+
success=len(warnings) == 0,
|
42
|
+
complete_address=models.Address(
|
43
|
+
city=addresses[0].cityName if addresses else "",
|
44
|
+
postal_code=addresses[0].postalCode if addresses else "",
|
45
|
+
country_code=addresses[0].countryCode if addresses else "",
|
46
|
+
) if addresses else None,
|
47
|
+
)
|
48
|
+
|
49
|
+
|
50
|
+
def address_validation_request(
|
51
|
+
payload: models.AddressValidationRequest,
|
52
|
+
settings: provider_utils.Settings,
|
53
|
+
) -> lib.Serializable:
|
54
|
+
"""Create MyDHL address validation request."""
|
55
|
+
address = lib.to_address(payload.address)
|
56
|
+
|
57
|
+
request = mydhl_req.AddressValidationRequestType(
|
58
|
+
type="delivery",
|
59
|
+
countryCode=address.country_code,
|
60
|
+
postalCode=address.postal_code,
|
61
|
+
cityName=address.city,
|
62
|
+
strictValidation=True,
|
63
|
+
)
|
64
|
+
|
65
|
+
return lib.Serializable(request, lib.to_dict)
|
@@ -0,0 +1,77 @@
|
|
1
|
+
"""Karrio MyDHL error parser."""
|
2
|
+
|
3
|
+
import typing
|
4
|
+
import karrio.lib as lib
|
5
|
+
import karrio.core.models as models
|
6
|
+
import karrio.providers.mydhl.utils as provider_utils
|
7
|
+
|
8
|
+
def parse_error_response(
|
9
|
+
response: dict,
|
10
|
+
settings: provider_utils.Settings,
|
11
|
+
**kwargs,
|
12
|
+
) -> typing.List[models.Message]:
|
13
|
+
"""Parse MyDHL error responses."""
|
14
|
+
|
15
|
+
# Handle standard error response format
|
16
|
+
if "detail" in response:
|
17
|
+
return [
|
18
|
+
models.Message(
|
19
|
+
carrier_id=settings.carrier_id,
|
20
|
+
carrier_name=settings.carrier_name,
|
21
|
+
code=response.get("status", ""),
|
22
|
+
message=response.get("detail", ""),
|
23
|
+
details=dict(
|
24
|
+
instance=response.get("instance", ""),
|
25
|
+
title=response.get("title", ""),
|
26
|
+
**kwargs
|
27
|
+
),
|
28
|
+
)
|
29
|
+
]
|
30
|
+
|
31
|
+
# Handle error object format
|
32
|
+
if "error" in response:
|
33
|
+
error = response["error"]
|
34
|
+
return [
|
35
|
+
models.Message(
|
36
|
+
carrier_id=settings.carrier_id,
|
37
|
+
carrier_name=settings.carrier_name,
|
38
|
+
code=error.get("code", ""),
|
39
|
+
message=error.get("message", ""),
|
40
|
+
details=dict(
|
41
|
+
details=error.get("details", ""),
|
42
|
+
**kwargs
|
43
|
+
),
|
44
|
+
)
|
45
|
+
]
|
46
|
+
|
47
|
+
# Handle additional error formats if present
|
48
|
+
errors = response.get("errors", [])
|
49
|
+
additional_details = response.get("additionalDetails", [])
|
50
|
+
|
51
|
+
messages = []
|
52
|
+
|
53
|
+
# Process main errors
|
54
|
+
for error in errors:
|
55
|
+
messages.append(
|
56
|
+
models.Message(
|
57
|
+
carrier_id=settings.carrier_id,
|
58
|
+
carrier_name=settings.carrier_name,
|
59
|
+
code=error.get("code", ""),
|
60
|
+
message=error.get("message", ""),
|
61
|
+
details=dict(error, **kwargs),
|
62
|
+
)
|
63
|
+
)
|
64
|
+
|
65
|
+
# Process additional validation errors
|
66
|
+
for detail in additional_details:
|
67
|
+
messages.append(
|
68
|
+
models.Message(
|
69
|
+
carrier_id=settings.carrier_id,
|
70
|
+
carrier_name=settings.carrier_name,
|
71
|
+
code="validation_error",
|
72
|
+
message=detail,
|
73
|
+
details=dict(validation_detail=detail, **kwargs),
|
74
|
+
)
|
75
|
+
)
|
76
|
+
|
77
|
+
return messages
|
@@ -0,0 +1,14 @@
|
|
1
|
+
"""Karrio MyDHL pickup API imports."""
|
2
|
+
|
3
|
+
from karrio.providers.mydhl.pickup.create import (
|
4
|
+
parse_pickup_response,
|
5
|
+
pickup_request,
|
6
|
+
)
|
7
|
+
from karrio.providers.mydhl.pickup.update import (
|
8
|
+
parse_pickup_update_response,
|
9
|
+
pickup_update_request,
|
10
|
+
)
|
11
|
+
from karrio.providers.mydhl.pickup.cancel import (
|
12
|
+
parse_pickup_cancel_response,
|
13
|
+
pickup_cancel_request,
|
14
|
+
)
|
@@ -0,0 +1,62 @@
|
|
1
|
+
"""Karrio MyDHL pickup cancellation API implementation."""
|
2
|
+
|
3
|
+
import typing
|
4
|
+
import karrio.lib as lib
|
5
|
+
import karrio.core.models as models
|
6
|
+
import karrio.providers.mydhl.error as error
|
7
|
+
import karrio.providers.mydhl.utils as provider_utils
|
8
|
+
|
9
|
+
|
10
|
+
def parse_pickup_cancel_response(
|
11
|
+
_response: lib.Deserializable[dict],
|
12
|
+
settings: provider_utils.Settings,
|
13
|
+
) -> typing.Tuple[models.ConfirmationDetails, typing.List[models.Message]]:
|
14
|
+
"""Parse pickup cancellation response from carrier API"""
|
15
|
+
response = _response.deserialize()
|
16
|
+
messages = error.parse_error_response(response, settings)
|
17
|
+
|
18
|
+
# Check if cancellation was successful
|
19
|
+
success = _extract_cancellation_status(response)
|
20
|
+
confirmation = (
|
21
|
+
models.ConfirmationDetails(
|
22
|
+
carrier_id=settings.carrier_id,
|
23
|
+
carrier_name=settings.carrier_name,
|
24
|
+
success=success,
|
25
|
+
operation="Cancel Pickup",
|
26
|
+
) if success else None
|
27
|
+
)
|
28
|
+
|
29
|
+
return confirmation, messages
|
30
|
+
|
31
|
+
|
32
|
+
def _extract_cancellation_status(
|
33
|
+
response: dict
|
34
|
+
) -> bool:
|
35
|
+
"""Extract cancellation success status from carrier response"""
|
36
|
+
|
37
|
+
# Example implementation for JSON response:
|
38
|
+
# return response.get("status", "").lower() == "cancelled"
|
39
|
+
|
40
|
+
# For development, always return success
|
41
|
+
return True
|
42
|
+
|
43
|
+
|
44
|
+
|
45
|
+
def pickup_cancel_request(
|
46
|
+
payload: models.PickupCancelRequest,
|
47
|
+
settings: provider_utils.Settings,
|
48
|
+
) -> lib.Serializable:
|
49
|
+
"""Create pickup cancellation request for carrier API"""
|
50
|
+
# Extract cancellation details
|
51
|
+
confirmation_number = payload.confirmation_number
|
52
|
+
|
53
|
+
|
54
|
+
# Example implementation for JSON request:
|
55
|
+
request = {
|
56
|
+
"dispatchConfirmationNumber": confirmation_number,
|
57
|
+
"requestorName": getattr(payload, 'requestor_name', "System"),
|
58
|
+
"reason": getattr(payload, 'reason', "Customer request")
|
59
|
+
}
|
60
|
+
|
61
|
+
return lib.Serializable(request, lib.to_dict)
|
62
|
+
|
@@ -0,0 +1,94 @@
|
|
1
|
+
"""Karrio MyDHL pickup create API implementation."""
|
2
|
+
|
3
|
+
import karrio.schemas.mydhl.pickup_create_request as mydhl_req
|
4
|
+
import karrio.schemas.mydhl.pickup_create_response as mydhl_res
|
5
|
+
|
6
|
+
import typing
|
7
|
+
import karrio.lib as lib
|
8
|
+
import karrio.core.models as models
|
9
|
+
import karrio.providers.mydhl.error as error
|
10
|
+
import karrio.providers.mydhl.utils as provider_utils
|
11
|
+
|
12
|
+
|
13
|
+
def parse_pickup_response(
|
14
|
+
_response: lib.Deserializable[dict],
|
15
|
+
settings: provider_utils.Settings,
|
16
|
+
) -> typing.Tuple[models.PickupDetails, typing.List[models.Message]]:
|
17
|
+
response = _response.deserialize()
|
18
|
+
messages = error.parse_error_response(response, settings)
|
19
|
+
pickup = _extract_details(response, settings) if response and not messages else None
|
20
|
+
|
21
|
+
return pickup, messages
|
22
|
+
|
23
|
+
|
24
|
+
def _extract_details(
|
25
|
+
data: dict,
|
26
|
+
settings: provider_utils.Settings,
|
27
|
+
) -> models.PickupDetails:
|
28
|
+
"""Extract pickup details from MyDHL response."""
|
29
|
+
# For simple JSON responses, extract directly
|
30
|
+
return models.PickupDetails(
|
31
|
+
carrier_id=settings.carrier_id,
|
32
|
+
carrier_name=settings.carrier_name,
|
33
|
+
confirmation_number=data.get("confirmationNumber", ""),
|
34
|
+
pickup_date=lib.fdate(data.get("pickupDate")),
|
35
|
+
ready_time=data.get("readyTime"),
|
36
|
+
closing_time=data.get("closingTime"),
|
37
|
+
)
|
38
|
+
|
39
|
+
|
40
|
+
def pickup_request(
|
41
|
+
payload: models.PickupRequest,
|
42
|
+
settings: provider_utils.Settings,
|
43
|
+
) -> lib.Serializable:
|
44
|
+
"""Create MyDHL pickup request."""
|
45
|
+
address = lib.to_address(payload.address)
|
46
|
+
|
47
|
+
request = mydhl_req.PickupCreateRequestType(
|
48
|
+
plannedPickupDateAndTime=lib.fdatetime(
|
49
|
+
payload.pickup_date,
|
50
|
+
current_format="%Y-%m-%d",
|
51
|
+
output_format="%Y-%m-%dT%H:%M:%S GMT+01:00"
|
52
|
+
),
|
53
|
+
closeTime=payload.closing_time or "18:00",
|
54
|
+
location="reception",
|
55
|
+
accounts=[
|
56
|
+
mydhl_req.AccountType(
|
57
|
+
typeCode="shipper",
|
58
|
+
number=settings.account_number,
|
59
|
+
)
|
60
|
+
],
|
61
|
+
customerDetails=mydhl_req.CustomerDetailsType(
|
62
|
+
shipperDetails=mydhl_req.DetailsType(
|
63
|
+
postalAddress=mydhl_req.PostalAddressType(
|
64
|
+
cityName=address.city,
|
65
|
+
countryCode=address.country_code,
|
66
|
+
postalCode=address.postal_code,
|
67
|
+
addressLine1=address.address_line1,
|
68
|
+
),
|
69
|
+
contactInformation=mydhl_req.ContactInformationType(
|
70
|
+
companyName=address.company_name,
|
71
|
+
fullName=address.person_name,
|
72
|
+
phone=address.phone_number,
|
73
|
+
email=address.email,
|
74
|
+
),
|
75
|
+
),
|
76
|
+
),
|
77
|
+
shipmentDetails=[
|
78
|
+
mydhl_req.ShipmentDetailType(
|
79
|
+
productCode="U",
|
80
|
+
packages=[
|
81
|
+
mydhl_req.PackageType(
|
82
|
+
weight=1.0,
|
83
|
+
dimensions=mydhl_req.DimensionsType(
|
84
|
+
length=1,
|
85
|
+
width=1,
|
86
|
+
height=1,
|
87
|
+
),
|
88
|
+
)
|
89
|
+
],
|
90
|
+
)
|
91
|
+
],
|
92
|
+
)
|
93
|
+
|
94
|
+
return lib.Serializable(request, lib.to_dict)
|