This commit is contained in:
@@ -7,7 +7,10 @@ import httpx
|
||||
import structlog
|
||||
|
||||
from app.adapters.delivery_providers.base import DeliveryProvider
|
||||
from app.adapters.delivery_providers.cse.constants import CSE_PROVIDER_NAME
|
||||
from app.adapters.delivery_providers.cse.constants import (
|
||||
CSE_PROVIDER_NAME,
|
||||
delivery_type_requires_pvz,
|
||||
)
|
||||
from app.adapters.delivery_providers.cse.errors import (
|
||||
CSEClientError,
|
||||
CSEMappingError,
|
||||
@@ -24,10 +27,12 @@ from app.adapters.delivery_providers.cse.order_mapper import (
|
||||
build_calc_body_for_payment,
|
||||
map_cse_save_order_request,
|
||||
map_cse_save_order_response,
|
||||
split_tariff_code,
|
||||
)
|
||||
from app.adapters.delivery_providers.cse.soap import (
|
||||
Element,
|
||||
build_envelope,
|
||||
make_field,
|
||||
parse_response,
|
||||
)
|
||||
from app.config import AdapterConfig
|
||||
@@ -71,6 +76,27 @@ class CSEClient:
|
||||
request_error_message="CSE calc request was rejected with status",
|
||||
)
|
||||
|
||||
async def get_delivery_types(self) -> list[tuple[str, str]]:
|
||||
"""Return available delivery schemes as (name, human label) pairs."""
|
||||
|
||||
body = {
|
||||
"parameters": Element(
|
||||
key="parameters",
|
||||
items=[make_field("Reference", "DeliveryType")],
|
||||
)
|
||||
}
|
||||
root = await self._post(
|
||||
"GetReferenceData",
|
||||
body,
|
||||
request_error_message="CSE delivery types request was rejected with status",
|
||||
)
|
||||
result: list[tuple[str, str]] = []
|
||||
for entry in root.items:
|
||||
name = entry.value
|
||||
if name:
|
||||
result.append((name, entry.field_value("Information") or name))
|
||||
return result
|
||||
|
||||
async def save_order(
|
||||
self, request: InitPaymentRequest, order_uuid: str
|
||||
) -> CSEOrderRegistrationResult:
|
||||
@@ -183,6 +209,7 @@ class CSEProvider(DeliveryProvider):
|
||||
def __init__(self, client: CSEClient, *, cache_ttl_seconds: int = 900) -> None:
|
||||
self._client = client
|
||||
self.cache_ttl_seconds = cache_ttl_seconds
|
||||
self._delivery_types: list[tuple[str, str]] | None = None
|
||||
|
||||
@classmethod
|
||||
def from_adapter_config(
|
||||
@@ -200,7 +227,6 @@ class CSEProvider(DeliveryProvider):
|
||||
payer=adapter_config.cse_payer,
|
||||
payment_method=adapter_config.cse_payment_method,
|
||||
shipping_method=adapter_config.cse_shipping_method,
|
||||
urgency=adapter_config.cse_urgency,
|
||||
),
|
||||
timeout_seconds=adapter_config.cse_timeout_seconds,
|
||||
retry_attempts=adapter_config.cse_retry_attempts,
|
||||
@@ -211,23 +237,71 @@ class CSEProvider(DeliveryProvider):
|
||||
async def get_prices(
|
||||
self, request: DeliveryCalculationRequest
|
||||
) -> list[DeliveryPrice]:
|
||||
body = build_calc_body_for_calculation(request)
|
||||
delivery_types = await self._resolve_delivery_types()
|
||||
|
||||
results = await asyncio.gather(
|
||||
*(
|
||||
self._prices_for_delivery_type(request, name, label)
|
||||
for name, label in delivery_types
|
||||
),
|
||||
return_exceptions=True,
|
||||
)
|
||||
|
||||
prices: list[DeliveryPrice] = []
|
||||
errors: list[BaseException] = []
|
||||
for result in results:
|
||||
if isinstance(result, BaseException):
|
||||
errors.append(result)
|
||||
else:
|
||||
prices.extend(result)
|
||||
|
||||
if not prices and errors:
|
||||
raise errors[0]
|
||||
return prices
|
||||
|
||||
async def _prices_for_delivery_type(
|
||||
self,
|
||||
request: DeliveryCalculationRequest,
|
||||
delivery_type: str,
|
||||
delivery_type_label: str,
|
||||
) -> list[DeliveryPrice]:
|
||||
body = build_calc_body_for_calculation(request, delivery_type)
|
||||
root = await self._client.calc(body)
|
||||
try:
|
||||
return map_cse_calc_response(root)
|
||||
return map_cse_calc_response(
|
||||
root,
|
||||
delivery_type=delivery_type,
|
||||
delivery_type_label=delivery_type_label,
|
||||
)
|
||||
except CSEMappingError as exc:
|
||||
raise CSEClientError("CSE calc response payload is invalid.") from exc
|
||||
|
||||
async def _resolve_delivery_types(self) -> list[tuple[str, str]]:
|
||||
if self._delivery_types is None:
|
||||
self._delivery_types = await self._client.get_delivery_types()
|
||||
# Exclude schemes that require a pickup point (PVZ) at registration,
|
||||
# since PVZ is not yet supported (only door-to-door is fulfillable).
|
||||
usable = [
|
||||
(name, label)
|
||||
for name, label in self._delivery_types
|
||||
if not delivery_type_requires_pvz(name, label)
|
||||
]
|
||||
# Fall back to the contract-default scheme (empty delivery_type) so
|
||||
# calculation still works if no usable schemes are returned.
|
||||
return usable or [("", "")]
|
||||
|
||||
async def get_payment_price(
|
||||
self,
|
||||
request: InitPaymentRequest,
|
||||
) -> DeliveryPrice | None:
|
||||
body = build_calc_body_for_payment(request)
|
||||
tariff_code = request.system_data.tariff.tariff_code
|
||||
delivery_type, _ = split_tariff_code(tariff_code)
|
||||
body = build_calc_body_for_payment(request, delivery_type)
|
||||
root = await self._client.calc(body)
|
||||
try:
|
||||
return map_cse_calc_response_for_tariff_code(
|
||||
root,
|
||||
tariff_code=request.system_data.tariff.tariff_code,
|
||||
tariff_code=tariff_code,
|
||||
)
|
||||
except CSEMappingError as exc:
|
||||
raise CSEClientError(
|
||||
|
||||
Reference in New Issue
Block a user