karrio-hermes 2026.1__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/hermes/__init__.py +3 -0
- karrio/mappers/hermes/mapper.py +59 -0
- karrio/mappers/hermes/proxy.py +78 -0
- karrio/mappers/hermes/settings.py +36 -0
- karrio/plugins/hermes/__init__.py +29 -0
- karrio/providers/hermes/__init__.py +16 -0
- karrio/providers/hermes/error.py +94 -0
- karrio/providers/hermes/pickup/__init__.py +12 -0
- karrio/providers/hermes/pickup/cancel.py +49 -0
- karrio/providers/hermes/pickup/create.py +98 -0
- karrio/providers/hermes/shipment/__init__.py +8 -0
- karrio/providers/hermes/shipment/create.py +336 -0
- karrio/providers/hermes/units.py +242 -0
- karrio/providers/hermes/utils.py +102 -0
- karrio/schemas/hermes/__init__.py +43 -0
- karrio/schemas/hermes/error_response.py +14 -0
- karrio/schemas/hermes/pickup_cancel_request.py +8 -0
- karrio/schemas/hermes/pickup_cancel_response.py +15 -0
- karrio/schemas/hermes/pickup_create_request.py +41 -0
- karrio/schemas/hermes/pickup_create_response.py +15 -0
- karrio/schemas/hermes/shipment_request.py +195 -0
- karrio/schemas/hermes/shipment_response.py +97 -0
- karrio_hermes-2026.1.dist-info/METADATA +44 -0
- karrio_hermes-2026.1.dist-info/RECORD +27 -0
- karrio_hermes-2026.1.dist-info/WHEEL +5 -0
- karrio_hermes-2026.1.dist-info/entry_points.txt +2 -0
- karrio_hermes-2026.1.dist-info/top_level.txt +4 -0
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""Karrio Hermes 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.hermes as provider
|
|
8
|
+
import karrio.mappers.hermes.settings as provider_settings
|
|
9
|
+
import karrio.universal.providers.rating as universal_provider
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Mapper(mapper.Mapper):
|
|
13
|
+
settings: provider_settings.Settings
|
|
14
|
+
|
|
15
|
+
# Rating operations (using rate sheets)
|
|
16
|
+
def create_rate_request(self, payload: models.RateRequest) -> lib.Serializable:
|
|
17
|
+
return universal_provider.rate_request(payload, self.settings)
|
|
18
|
+
|
|
19
|
+
def parse_rate_response(
|
|
20
|
+
self, response: lib.Deserializable[str]
|
|
21
|
+
) -> typing.Tuple[typing.List[models.RateDetails], typing.List[models.Message]]:
|
|
22
|
+
return universal_provider.parse_rate_response(response, self.settings)
|
|
23
|
+
|
|
24
|
+
# Shipment operations
|
|
25
|
+
def create_shipment_request(
|
|
26
|
+
self, payload: models.ShipmentRequest
|
|
27
|
+
) -> lib.Serializable:
|
|
28
|
+
return provider.shipment_request(payload, self.settings)
|
|
29
|
+
|
|
30
|
+
def parse_shipment_response(
|
|
31
|
+
self, response: lib.Deserializable[str]
|
|
32
|
+
) -> typing.Tuple[models.ShipmentDetails, typing.List[models.Message]]:
|
|
33
|
+
return provider.parse_shipment_response(response, self.settings)
|
|
34
|
+
|
|
35
|
+
# Pickup operations
|
|
36
|
+
def create_pickup_request(
|
|
37
|
+
self, payload: models.PickupRequest
|
|
38
|
+
) -> lib.Serializable:
|
|
39
|
+
return provider.pickup_request(payload, self.settings)
|
|
40
|
+
|
|
41
|
+
def parse_pickup_response(
|
|
42
|
+
self, response: lib.Deserializable[str]
|
|
43
|
+
) -> typing.Tuple[models.PickupDetails, typing.List[models.Message]]:
|
|
44
|
+
return provider.parse_pickup_response(response, self.settings)
|
|
45
|
+
|
|
46
|
+
def create_cancel_pickup_request(
|
|
47
|
+
self, payload: models.PickupCancelRequest
|
|
48
|
+
) -> lib.Serializable:
|
|
49
|
+
return provider.pickup_cancel_request(payload, self.settings)
|
|
50
|
+
|
|
51
|
+
def parse_cancel_pickup_response(
|
|
52
|
+
self, response: lib.Deserializable[str]
|
|
53
|
+
) -> typing.Tuple[models.ConfirmationDetails, typing.List[models.Message]]:
|
|
54
|
+
return provider.parse_pickup_cancel_response(response, self.settings)
|
|
55
|
+
|
|
56
|
+
# Note: Hermes API does not support:
|
|
57
|
+
# - cancel_shipment (no DELETE endpoint for shipments)
|
|
58
|
+
# - pickup_update (no PUT endpoint for pickups)
|
|
59
|
+
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"""Karrio Hermes client proxy."""
|
|
2
|
+
|
|
3
|
+
import karrio.lib as lib
|
|
4
|
+
import karrio.api.proxy as proxy
|
|
5
|
+
import karrio.mappers.hermes.settings as provider_settings
|
|
6
|
+
import karrio.universal.mappers.rating_proxy as rating_proxy
|
|
7
|
+
from karrio.providers.hermes.units import LabelType
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Proxy(rating_proxy.RatingMixinProxy, proxy.Proxy):
|
|
11
|
+
settings: provider_settings.Settings
|
|
12
|
+
|
|
13
|
+
def get_rates(self, request: lib.Serializable) -> lib.Deserializable[str]:
|
|
14
|
+
return super().get_rates(request)
|
|
15
|
+
|
|
16
|
+
def _get_headers(self, accept: str = "application/json") -> dict:
|
|
17
|
+
"""Get common headers for Hermes API requests."""
|
|
18
|
+
token_data = self.settings.access_token
|
|
19
|
+
# Handle case where token_data might not be a dict
|
|
20
|
+
access_token = token_data.get("access_token") if isinstance(token_data, dict) else token_data
|
|
21
|
+
language = self.settings.connection_config.language.state or "DE"
|
|
22
|
+
|
|
23
|
+
return {
|
|
24
|
+
"Content-Type": "application/json",
|
|
25
|
+
"Accept": accept,
|
|
26
|
+
"Accept-Language": language,
|
|
27
|
+
"Authorization": f"Bearer {access_token}",
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
def create_shipment(self, request: lib.Serializable) -> lib.Deserializable[str]:
|
|
31
|
+
"""Create a shipment order with label.
|
|
32
|
+
|
|
33
|
+
Endpoint: POST /shipmentorders/labels
|
|
34
|
+
"""
|
|
35
|
+
label_type = self.settings.connection_config.label_type.state or "PDF"
|
|
36
|
+
accept_header = LabelType.map(label_type).value or "application/pdf"
|
|
37
|
+
|
|
38
|
+
response = lib.request(
|
|
39
|
+
url=f"{self.settings.server_url}/shipmentorders/labels",
|
|
40
|
+
data=lib.to_json(request.serialize()),
|
|
41
|
+
trace=self.trace_as("json"),
|
|
42
|
+
method="POST",
|
|
43
|
+
headers=self._get_headers(accept=accept_header),
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
return lib.Deserializable(response, lib.to_dict)
|
|
47
|
+
|
|
48
|
+
def schedule_pickup(self, request: lib.Serializable) -> lib.Deserializable[str]:
|
|
49
|
+
"""Create a pickup order.
|
|
50
|
+
|
|
51
|
+
Endpoint: POST /pickuporders
|
|
52
|
+
"""
|
|
53
|
+
response = lib.request(
|
|
54
|
+
url=f"{self.settings.server_url}/pickuporders",
|
|
55
|
+
data=lib.to_json(request.serialize()),
|
|
56
|
+
trace=self.trace_as("json"),
|
|
57
|
+
method="POST",
|
|
58
|
+
headers=self._get_headers(),
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
return lib.Deserializable(response, lib.to_dict)
|
|
62
|
+
|
|
63
|
+
def cancel_pickup(self, request: lib.Serializable) -> lib.Deserializable[str]:
|
|
64
|
+
"""Cancel a pickup order.
|
|
65
|
+
|
|
66
|
+
Endpoint: DELETE /pickuporders/{pickupOrderID}
|
|
67
|
+
"""
|
|
68
|
+
payload = request.serialize()
|
|
69
|
+
pickup_order_id = payload.get("pickupOrderID")
|
|
70
|
+
|
|
71
|
+
response = lib.request(
|
|
72
|
+
url=f"{self.settings.server_url}/pickuporders/{pickup_order_id}",
|
|
73
|
+
trace=self.trace_as("json"),
|
|
74
|
+
method="DELETE",
|
|
75
|
+
headers=self._get_headers(),
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
return lib.Deserializable(response, lib.to_dict)
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""Karrio Hermes client settings."""
|
|
2
|
+
|
|
3
|
+
import attr
|
|
4
|
+
import typing
|
|
5
|
+
import jstruct
|
|
6
|
+
import karrio.core.models as models
|
|
7
|
+
import karrio.providers.hermes.units as provider_units
|
|
8
|
+
import karrio.providers.hermes.utils as provider_utils
|
|
9
|
+
import karrio.universal.mappers.rating_proxy as rating_proxy
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@attr.s(auto_attribs=True)
|
|
13
|
+
class Settings(provider_utils.Settings, rating_proxy.RatingMixinSettings):
|
|
14
|
+
"""Hermes connection settings."""
|
|
15
|
+
|
|
16
|
+
# OAuth2 credentials (password flow)
|
|
17
|
+
username: str # type:ignore
|
|
18
|
+
password: str # type:ignore
|
|
19
|
+
client_id: str # type:ignore
|
|
20
|
+
client_secret: str # type:ignore
|
|
21
|
+
|
|
22
|
+
# generic properties
|
|
23
|
+
id: str = None
|
|
24
|
+
test_mode: bool = False
|
|
25
|
+
carrier_id: str = "hermes"
|
|
26
|
+
services: typing.List[models.ServiceLevel] = jstruct.JList[models.ServiceLevel, False, dict(default=provider_units.DEFAULT_SERVICES)] # type: ignore
|
|
27
|
+
account_country_code: str = "DE"
|
|
28
|
+
metadata: dict = {}
|
|
29
|
+
config: dict = {}
|
|
30
|
+
|
|
31
|
+
@property
|
|
32
|
+
def shipping_services(self) -> typing.List[models.ServiceLevel]:
|
|
33
|
+
if any(self.services or []):
|
|
34
|
+
return self.services
|
|
35
|
+
|
|
36
|
+
return provider_units.DEFAULT_SERVICES
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from karrio.core.metadata import PluginMetadata
|
|
2
|
+
|
|
3
|
+
from karrio.mappers.hermes.mapper import Mapper
|
|
4
|
+
from karrio.mappers.hermes.proxy import Proxy
|
|
5
|
+
from karrio.mappers.hermes.settings import Settings
|
|
6
|
+
import karrio.providers.hermes.units as units
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
# This METADATA object is used by Karrio to discover and register this plugin
|
|
10
|
+
# when loaded through Python entrypoints or local plugin directories.
|
|
11
|
+
# The entrypoint is defined in pyproject.toml under [project.entry-points."karrio.plugins"]
|
|
12
|
+
METADATA = PluginMetadata(
|
|
13
|
+
id="hermes",
|
|
14
|
+
label="Hermes",
|
|
15
|
+
description="Hermes shipping integration for Karrio",
|
|
16
|
+
# Integrations
|
|
17
|
+
Mapper=Mapper,
|
|
18
|
+
Proxy=Proxy,
|
|
19
|
+
Settings=Settings,
|
|
20
|
+
# Data Units
|
|
21
|
+
is_hub=False,
|
|
22
|
+
options=units.ShippingOption,
|
|
23
|
+
services=units.ShippingService,
|
|
24
|
+
service_levels=units.DEFAULT_SERVICES,
|
|
25
|
+
connection_configs=units.ConnectionConfig,
|
|
26
|
+
# Extra info
|
|
27
|
+
website="https://www.hermesworld.com",
|
|
28
|
+
documentation="https://de-api-int.hermesworld.com/docs/applications/order",
|
|
29
|
+
)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""Karrio Hermes provider imports."""
|
|
2
|
+
from karrio.providers.hermes.utils import Settings
|
|
3
|
+
from karrio.providers.hermes.shipment import (
|
|
4
|
+
parse_shipment_response,
|
|
5
|
+
shipment_request,
|
|
6
|
+
)
|
|
7
|
+
from karrio.providers.hermes.pickup import (
|
|
8
|
+
parse_pickup_cancel_response,
|
|
9
|
+
parse_pickup_response,
|
|
10
|
+
pickup_cancel_request,
|
|
11
|
+
pickup_request,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
# Note: Hermes API does not support:
|
|
15
|
+
# - shipment cancellation (no DELETE endpoint for shipments)
|
|
16
|
+
# - pickup update (no PUT endpoint for pickups)
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"""Karrio Hermes error parser."""
|
|
2
|
+
|
|
3
|
+
import typing
|
|
4
|
+
import karrio.lib as lib
|
|
5
|
+
import karrio.core.models as models
|
|
6
|
+
import karrio.providers.hermes.utils as provider_utils
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def parse_error_response(
|
|
10
|
+
response: typing.Union[dict, str, None],
|
|
11
|
+
settings: provider_utils.Settings,
|
|
12
|
+
**kwargs,
|
|
13
|
+
) -> typing.List[models.Message]:
|
|
14
|
+
"""Parse Hermes error response into karrio messages."""
|
|
15
|
+
messages: typing.List[models.Message] = []
|
|
16
|
+
|
|
17
|
+
if response is None:
|
|
18
|
+
return messages
|
|
19
|
+
|
|
20
|
+
# Handle case where response is not a dict (e.g., raw string response)
|
|
21
|
+
if not isinstance(response, dict):
|
|
22
|
+
messages.append(
|
|
23
|
+
models.Message(
|
|
24
|
+
carrier_id=settings.carrier_id,
|
|
25
|
+
carrier_name=settings.carrier_name,
|
|
26
|
+
code="PARSE_ERROR",
|
|
27
|
+
message=f"Unexpected response format: {str(response)[:200]}",
|
|
28
|
+
details={**kwargs},
|
|
29
|
+
)
|
|
30
|
+
)
|
|
31
|
+
return messages
|
|
32
|
+
|
|
33
|
+
# Handle OAuth2 error response
|
|
34
|
+
if "error" in response:
|
|
35
|
+
messages.append(
|
|
36
|
+
models.Message(
|
|
37
|
+
carrier_id=settings.carrier_id,
|
|
38
|
+
carrier_name=settings.carrier_name,
|
|
39
|
+
code=response.get("error", "AUTH_ERROR"),
|
|
40
|
+
message=response.get("error_description", "Authentication failed"),
|
|
41
|
+
details={**kwargs},
|
|
42
|
+
)
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
# Handle Hermes API error response with listOfResultCodes
|
|
46
|
+
result_codes = response.get("listOfResultCodes") or []
|
|
47
|
+
for error in result_codes:
|
|
48
|
+
code = error.get("code", "")
|
|
49
|
+
message = error.get("message", "")
|
|
50
|
+
|
|
51
|
+
# Only include actual errors (codes starting with 'e')
|
|
52
|
+
if code.startswith("e") or (code and not code.startswith("w")):
|
|
53
|
+
messages.append(
|
|
54
|
+
models.Message(
|
|
55
|
+
carrier_id=settings.carrier_id,
|
|
56
|
+
carrier_name=settings.carrier_name,
|
|
57
|
+
code=code,
|
|
58
|
+
message=message,
|
|
59
|
+
details={**kwargs},
|
|
60
|
+
)
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
return messages
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def parse_warning_response(
|
|
67
|
+
response: dict,
|
|
68
|
+
settings: provider_utils.Settings,
|
|
69
|
+
**kwargs,
|
|
70
|
+
) -> typing.List[models.Message]:
|
|
71
|
+
"""Parse Hermes warning response into karrio messages."""
|
|
72
|
+
messages: typing.List[models.Message] = []
|
|
73
|
+
|
|
74
|
+
if response is None:
|
|
75
|
+
return messages
|
|
76
|
+
|
|
77
|
+
# Handle warnings from listOfResultCodes (codes starting with 'w')
|
|
78
|
+
result_codes = response.get("listOfResultCodes") or []
|
|
79
|
+
for warning in result_codes:
|
|
80
|
+
code = warning.get("code", "")
|
|
81
|
+
message = warning.get("message", "")
|
|
82
|
+
|
|
83
|
+
if code.startswith("w"):
|
|
84
|
+
messages.append(
|
|
85
|
+
models.Message(
|
|
86
|
+
carrier_id=settings.carrier_id,
|
|
87
|
+
carrier_name=settings.carrier_name,
|
|
88
|
+
code=code,
|
|
89
|
+
message=message,
|
|
90
|
+
details={**kwargs},
|
|
91
|
+
)
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
return messages
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""Karrio Hermes pickup API imports."""
|
|
2
|
+
|
|
3
|
+
from karrio.providers.hermes.pickup.create import (
|
|
4
|
+
parse_pickup_response,
|
|
5
|
+
pickup_request,
|
|
6
|
+
)
|
|
7
|
+
from karrio.providers.hermes.pickup.cancel import (
|
|
8
|
+
parse_pickup_cancel_response,
|
|
9
|
+
pickup_cancel_request,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
# Note: Hermes API does not support pickup update
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""Karrio Hermes 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.hermes.error as error
|
|
7
|
+
import karrio.providers.hermes.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[typing.Optional[models.ConfirmationDetails], typing.List[models.Message]]:
|
|
14
|
+
"""Parse Hermes pickup cancellation response."""
|
|
15
|
+
response = _response.deserialize()
|
|
16
|
+
messages = error.parse_error_response(response, settings)
|
|
17
|
+
|
|
18
|
+
# Check if cancellation was successful (no errors means success)
|
|
19
|
+
success = len(messages) == 0
|
|
20
|
+
|
|
21
|
+
confirmation = None
|
|
22
|
+
if success:
|
|
23
|
+
confirmation = models.ConfirmationDetails(
|
|
24
|
+
carrier_id=settings.carrier_id,
|
|
25
|
+
carrier_name=settings.carrier_name,
|
|
26
|
+
success=True,
|
|
27
|
+
operation="Cancel Pickup",
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
return confirmation, messages
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def pickup_cancel_request(
|
|
34
|
+
payload: models.PickupCancelRequest,
|
|
35
|
+
settings: provider_utils.Settings,
|
|
36
|
+
) -> lib.Serializable:
|
|
37
|
+
"""Create Hermes pickup cancellation request.
|
|
38
|
+
|
|
39
|
+
Note: The pickup order ID is passed in the URL path, not in the body.
|
|
40
|
+
The proxy.cancel_pickup method extracts pickupOrderID from the payload.
|
|
41
|
+
"""
|
|
42
|
+
# Hermes uses DELETE /pickuporders/{pickupOrderID}
|
|
43
|
+
# The confirmation_number from Karrio is the pickupOrderID
|
|
44
|
+
request = {
|
|
45
|
+
"pickupOrderID": payload.confirmation_number,
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return lib.Serializable(request, lib.to_dict)
|
|
49
|
+
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"""Karrio Hermes pickup scheduling implementation."""
|
|
2
|
+
|
|
3
|
+
import typing
|
|
4
|
+
import karrio.lib as lib
|
|
5
|
+
import karrio.core.models as models
|
|
6
|
+
import karrio.providers.hermes.error as error
|
|
7
|
+
import karrio.providers.hermes.utils as provider_utils
|
|
8
|
+
import karrio.schemas.hermes.pickup_create_request as hermes_req
|
|
9
|
+
import karrio.schemas.hermes.pickup_create_response as hermes_res
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def parse_pickup_response(
|
|
13
|
+
_response: lib.Deserializable[dict],
|
|
14
|
+
settings: provider_utils.Settings,
|
|
15
|
+
) -> typing.Tuple[typing.Optional[models.PickupDetails], typing.List[models.Message]]:
|
|
16
|
+
"""Parse Hermes pickup response."""
|
|
17
|
+
response = _response.deserialize()
|
|
18
|
+
messages = error.parse_error_response(response, settings)
|
|
19
|
+
|
|
20
|
+
# Check if we have a valid pickup order ID
|
|
21
|
+
pickup = None
|
|
22
|
+
if response.get("pickupOrderID"):
|
|
23
|
+
pickup = _extract_details(response, settings)
|
|
24
|
+
|
|
25
|
+
return pickup, messages
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _extract_details(
|
|
29
|
+
data: dict,
|
|
30
|
+
settings: provider_utils.Settings,
|
|
31
|
+
) -> models.PickupDetails:
|
|
32
|
+
"""Extract pickup details from Hermes response."""
|
|
33
|
+
response = lib.to_object(hermes_res.PickupCreateResponseType, data)
|
|
34
|
+
|
|
35
|
+
# Hermes returns pickupOrderID as the confirmation number
|
|
36
|
+
confirmation_number = response.pickupOrderID or ""
|
|
37
|
+
|
|
38
|
+
return models.PickupDetails(
|
|
39
|
+
carrier_id=settings.carrier_id,
|
|
40
|
+
carrier_name=settings.carrier_name,
|
|
41
|
+
confirmation_number=confirmation_number,
|
|
42
|
+
pickup_date=None, # Not returned in response
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def pickup_request(
|
|
47
|
+
payload: models.PickupRequest,
|
|
48
|
+
settings: provider_utils.Settings,
|
|
49
|
+
) -> lib.Serializable:
|
|
50
|
+
"""Create a Hermes pickup request."""
|
|
51
|
+
address = lib.to_address(payload.address)
|
|
52
|
+
|
|
53
|
+
# Parse parcel counts by size (XS, S, M, L, XL)
|
|
54
|
+
# Default to counting all parcels as M (medium) if not specified
|
|
55
|
+
parcel_count = hermes_req.ParcelCountType(
|
|
56
|
+
pickupParcelCountXS=0,
|
|
57
|
+
pickupParcelCountS=0,
|
|
58
|
+
pickupParcelCountM=len(payload.parcels) if payload.parcels else 1,
|
|
59
|
+
pickupParcelCountL=0,
|
|
60
|
+
pickupParcelCountXL=0,
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
# Map time slot from ready_time/closing_time
|
|
64
|
+
# Valid values per OpenAPI: BETWEEN_10_AND_13, BETWEEN_12_AND_15, BETWEEN_14_AND_17
|
|
65
|
+
time_slot = None
|
|
66
|
+
if payload.ready_time:
|
|
67
|
+
hour = int(payload.ready_time.split(":")[0]) if ":" in payload.ready_time else 12
|
|
68
|
+
if hour < 12:
|
|
69
|
+
time_slot = "BETWEEN_10_AND_13"
|
|
70
|
+
elif hour < 14:
|
|
71
|
+
time_slot = "BETWEEN_12_AND_15"
|
|
72
|
+
else:
|
|
73
|
+
time_slot = "BETWEEN_14_AND_17"
|
|
74
|
+
|
|
75
|
+
# Create the request using generated schema types
|
|
76
|
+
request = hermes_req.PickupCreateRequestType(
|
|
77
|
+
pickupAddress=hermes_req.PickupAddressType(
|
|
78
|
+
street=address.street_name,
|
|
79
|
+
houseNumber=address.street_number or "",
|
|
80
|
+
zipCode=address.postal_code,
|
|
81
|
+
town=address.city,
|
|
82
|
+
countryCode=address.country_code,
|
|
83
|
+
addressAddition=address.address_line2 or address.company_name or None,
|
|
84
|
+
),
|
|
85
|
+
pickupName=hermes_req.PickupNameType(
|
|
86
|
+
title=None,
|
|
87
|
+
gender=None,
|
|
88
|
+
firstname=address.person_name.split()[0] if address.person_name else None,
|
|
89
|
+
middlename=None,
|
|
90
|
+
lastname=" ".join(address.person_name.split()[1:]) if address.person_name and len(address.person_name.split()) > 1 else address.person_name,
|
|
91
|
+
),
|
|
92
|
+
phone=address.phone_number or None,
|
|
93
|
+
pickupDate=payload.pickup_date,
|
|
94
|
+
pickupTimeSlot=time_slot,
|
|
95
|
+
parcelCount=parcel_count,
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
return lib.Serializable(request, lib.to_dict)
|