@@ -236,9 +236,16 @@ class CSEClient:
|
|||||||
class CSEProvider(DeliveryProvider):
|
class CSEProvider(DeliveryProvider):
|
||||||
name = CSE_PROVIDER_NAME
|
name = CSE_PROVIDER_NAME
|
||||||
|
|
||||||
def __init__(self, client: CSEClient, *, cache_ttl_seconds: int = 900) -> None:
|
def __init__(
|
||||||
|
self,
|
||||||
|
client: CSEClient,
|
||||||
|
*,
|
||||||
|
cache_ttl_seconds: int = 900,
|
||||||
|
delivery_service_guids: tuple[str, ...] = (),
|
||||||
|
) -> None:
|
||||||
self._client = client
|
self._client = client
|
||||||
self.cache_ttl_seconds = cache_ttl_seconds
|
self.cache_ttl_seconds = cache_ttl_seconds
|
||||||
|
self._delivery_service_guids = delivery_service_guids
|
||||||
self._delivery_types: list[tuple[str, str]] | None = None
|
self._delivery_types: list[tuple[str, str]] | None = None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -262,7 +269,11 @@ class CSEProvider(DeliveryProvider):
|
|||||||
retry_attempts=config.retry_attempts,
|
retry_attempts=config.retry_attempts,
|
||||||
retry_backoff_seconds=config.retry_backoff_seconds,
|
retry_backoff_seconds=config.retry_backoff_seconds,
|
||||||
)
|
)
|
||||||
return cls(client=client, cache_ttl_seconds=config.cache_ttl_seconds)
|
return cls(
|
||||||
|
client=client,
|
||||||
|
cache_ttl_seconds=config.cache_ttl_seconds,
|
||||||
|
delivery_service_guids=tuple(config.delivery_service_guids),
|
||||||
|
)
|
||||||
|
|
||||||
async def get_prices(
|
async def get_prices(
|
||||||
self, request: DeliveryCalculationRequest
|
self, request: DeliveryCalculationRequest
|
||||||
@@ -271,8 +282,9 @@ class CSEProvider(DeliveryProvider):
|
|||||||
|
|
||||||
results = await asyncio.gather(
|
results = await asyncio.gather(
|
||||||
*(
|
*(
|
||||||
self._prices_for_delivery_type(request, name, label)
|
self._prices_for_delivery_type(request, name, label, service_guid)
|
||||||
for name, label in delivery_types
|
for name, label in delivery_types
|
||||||
|
for service_guid in self._service_guids_for_price_calculation()
|
||||||
),
|
),
|
||||||
return_exceptions=True,
|
return_exceptions=True,
|
||||||
)
|
)
|
||||||
@@ -294,18 +306,25 @@ class CSEProvider(DeliveryProvider):
|
|||||||
request: DeliveryCalculationRequest,
|
request: DeliveryCalculationRequest,
|
||||||
delivery_type: str,
|
delivery_type: str,
|
||||||
delivery_type_label: str,
|
delivery_type_label: str,
|
||||||
|
service_guid: str | None,
|
||||||
) -> list[DeliveryPrice]:
|
) -> list[DeliveryPrice]:
|
||||||
body = build_calc_body_for_calculation(request, delivery_type)
|
body = build_calc_body_for_calculation(request, delivery_type, service_guid)
|
||||||
root = await self._client.calc(body)
|
root = await self._client.calc(body)
|
||||||
try:
|
try:
|
||||||
return map_cse_calc_response(
|
return map_cse_calc_response(
|
||||||
root,
|
root,
|
||||||
delivery_type=delivery_type,
|
delivery_type=delivery_type,
|
||||||
delivery_type_label=delivery_type_label,
|
delivery_type_label=delivery_type_label,
|
||||||
|
service_guid=service_guid,
|
||||||
)
|
)
|
||||||
except CSEMappingError as exc:
|
except CSEMappingError as exc:
|
||||||
raise CSEClientError("CSE calc response payload is invalid.") from exc
|
raise CSEClientError("CSE calc response payload is invalid.") from exc
|
||||||
|
|
||||||
|
def _service_guids_for_price_calculation(self) -> tuple[str, ...]:
|
||||||
|
if not self._delivery_service_guids:
|
||||||
|
raise CSERequestError("CSE delivery service GUIDs are not configured.")
|
||||||
|
return self._delivery_service_guids
|
||||||
|
|
||||||
async def _resolve_delivery_types(self) -> list[tuple[str, str]]:
|
async def _resolve_delivery_types(self) -> list[tuple[str, str]]:
|
||||||
if self._delivery_types is None:
|
if self._delivery_types is None:
|
||||||
self._delivery_types = await self._client.get_delivery_types()
|
self._delivery_types = await self._client.get_delivery_types()
|
||||||
@@ -325,14 +344,16 @@ class CSEProvider(DeliveryProvider):
|
|||||||
request: InitPaymentRequest,
|
request: InitPaymentRequest,
|
||||||
) -> DeliveryPrice | None:
|
) -> DeliveryPrice | None:
|
||||||
tariff_code = request.system_data.tariff.tariff_code
|
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:
|
try:
|
||||||
|
delivery_type, service_guid, _ = split_tariff_code(tariff_code)
|
||||||
|
body = build_calc_body_for_payment(request, delivery_type, service_guid)
|
||||||
|
root = await self._client.calc(body)
|
||||||
return map_cse_calc_response_for_tariff_code(
|
return map_cse_calc_response_for_tariff_code(
|
||||||
root,
|
root,
|
||||||
tariff_code=tariff_code,
|
tariff_code=tariff_code,
|
||||||
)
|
)
|
||||||
|
except CSERequestError:
|
||||||
|
raise
|
||||||
except CSEMappingError as exc:
|
except CSEMappingError as exc:
|
||||||
raise CSEClientError(
|
raise CSEClientError(
|
||||||
"CSE payment price validation response payload is invalid."
|
"CSE payment price validation response payload is invalid."
|
||||||
|
|||||||
@@ -18,11 +18,14 @@ def map_cse_calc_response(
|
|||||||
*,
|
*,
|
||||||
delivery_type: str = "",
|
delivery_type: str = "",
|
||||||
delivery_type_label: str = "",
|
delivery_type_label: str = "",
|
||||||
|
service_guid: str | None = None,
|
||||||
) -> list[DeliveryPrice]:
|
) -> list[DeliveryPrice]:
|
||||||
"""Map a parsed ``Calc`` ``return`` Element into unified delivery prices."""
|
"""Map a parsed ``Calc`` ``return`` Element into unified delivery prices."""
|
||||||
|
|
||||||
prices: list[DeliveryPrice] = []
|
prices: list[DeliveryPrice] = []
|
||||||
for tariff in _iter_tariffs(root):
|
for tariff in _iter_tariffs(root):
|
||||||
|
if service_guid is not None and tariff.value != service_guid:
|
||||||
|
continue
|
||||||
price = _map_tariff(tariff, delivery_type, delivery_type_label)
|
price = _map_tariff(tariff, delivery_type, delivery_type_label)
|
||||||
if price is not None:
|
if price is not None:
|
||||||
prices.append(price)
|
prices.append(price)
|
||||||
@@ -35,11 +38,11 @@ def map_cse_calc_response_for_tariff_code(
|
|||||||
tariff_code: str,
|
tariff_code: str,
|
||||||
delivery_type_label: str = "",
|
delivery_type_label: str = "",
|
||||||
) -> DeliveryPrice | None:
|
) -> DeliveryPrice | None:
|
||||||
"""Return the tariff matching ``tariff_code`` (``"<DeliveryType>|<Urgency>"``)."""
|
"""Return tariff matching ``"<DeliveryType>|<ServiceGuid>|<Urgency>"``."""
|
||||||
|
|
||||||
delivery_type, _, urgency = tariff_code.partition("|")
|
delivery_type, tariff_guid, urgency = _split_tariff_code(tariff_code)
|
||||||
for tariff in _iter_tariffs(root):
|
for tariff in _iter_tariffs(root):
|
||||||
if _tariff_urgency(tariff) == urgency:
|
if _tariff_matches(tariff, tariff_guid, urgency):
|
||||||
price = _map_tariff(tariff, delivery_type, delivery_type_label)
|
price = _map_tariff(tariff, delivery_type, delivery_type_label)
|
||||||
if price is not None:
|
if price is not None:
|
||||||
return price
|
return price
|
||||||
@@ -52,6 +55,19 @@ def _tariff_urgency(tariff: Element) -> str | None:
|
|||||||
return tariff.field_value("Urgency") or tariff.value
|
return tariff.field_value("Urgency") or tariff.value
|
||||||
|
|
||||||
|
|
||||||
|
def _tariff_matches(tariff: Element, tariff_guid: str, urgency: str) -> bool:
|
||||||
|
if _tariff_urgency(tariff) != urgency:
|
||||||
|
return False
|
||||||
|
return tariff.value == tariff_guid
|
||||||
|
|
||||||
|
|
||||||
|
def _split_tariff_code(tariff_code: str) -> tuple[str, str, str]:
|
||||||
|
parts = tariff_code.split("|")
|
||||||
|
if len(parts) != 3 or not parts[1] or not parts[2]:
|
||||||
|
raise CSEMappingError("CSE tariff_code has invalid format.")
|
||||||
|
return parts[0], parts[1], parts[2]
|
||||||
|
|
||||||
|
|
||||||
def _iter_tariffs(root: Element):
|
def _iter_tariffs(root: Element):
|
||||||
for destination in root.items:
|
for destination in root.items:
|
||||||
for tariff in destination.items:
|
for tariff in destination.items:
|
||||||
@@ -70,9 +86,11 @@ def _map_tariff(
|
|||||||
urgency = _tariff_urgency(tariff)
|
urgency = _tariff_urgency(tariff)
|
||||||
if not urgency:
|
if not urgency:
|
||||||
return None
|
return None
|
||||||
# Unified tariff_code carries both the delivery scheme and the urgency so
|
if not tariff.value:
|
||||||
# registration (SaveDocuments) can set DeliveryOfCargo and Urgency.
|
return None
|
||||||
tariff_code = f"{delivery_type}|{urgency}"
|
# Unified tariff_code carries the delivery scheme, CSE service GUID and
|
||||||
|
# urgency so payment validation can recalculate the exact selected service.
|
||||||
|
tariff_code = f"{delivery_type}|{tariff.value}|{urgency}"
|
||||||
|
|
||||||
raw_total = tariff.field_value("Total")
|
raw_total = tariff.field_value("Total")
|
||||||
if raw_total is None:
|
if raw_total is None:
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ from app.adapters.delivery_providers.cse.constants import (
|
|||||||
resolve_cse_geography,
|
resolve_cse_geography,
|
||||||
resolve_type_of_cargo,
|
resolve_type_of_cargo,
|
||||||
)
|
)
|
||||||
from app.adapters.delivery_providers.cse.errors import CSEMappingError
|
from app.adapters.delivery_providers.cse.errors import CSEMappingError, CSERequestError
|
||||||
from app.adapters.delivery_providers.cse.soap import Element, make_field
|
from app.adapters.delivery_providers.cse.soap import Element, make_field
|
||||||
from app.schemas.payment import Address, InitPaymentRequest
|
from app.schemas.payment import Address, InitPaymentRequest
|
||||||
from app.schemas.request import DeliveryCalculationRequest
|
from app.schemas.request import DeliveryCalculationRequest
|
||||||
@@ -44,6 +44,7 @@ class CSEOrderInfo:
|
|||||||
def build_calc_body_for_calculation(
|
def build_calc_body_for_calculation(
|
||||||
request: DeliveryCalculationRequest,
|
request: DeliveryCalculationRequest,
|
||||||
delivery_type: str = "",
|
delivery_type: str = "",
|
||||||
|
service_guid: str | None = None,
|
||||||
) -> dict[str, Element]:
|
) -> dict[str, Element]:
|
||||||
fields = [
|
fields = [
|
||||||
make_field("SenderGeography", resolve_cse_geography(request.from_city)),
|
make_field("SenderGeography", resolve_cse_geography(request.from_city)),
|
||||||
@@ -53,12 +54,15 @@ def build_calc_body_for_calculation(
|
|||||||
make_field("Qty", "1", "int"),
|
make_field("Qty", "1", "int"),
|
||||||
]
|
]
|
||||||
_append_delivery_type(fields, delivery_type)
|
_append_delivery_type(fields, delivery_type)
|
||||||
|
if service_guid:
|
||||||
|
fields.append(make_field("Service", service_guid))
|
||||||
return _calc_body(Element(key="Destination", fields=fields))
|
return _calc_body(Element(key="Destination", fields=fields))
|
||||||
|
|
||||||
|
|
||||||
def build_calc_body_for_payment(
|
def build_calc_body_for_payment(
|
||||||
request: InitPaymentRequest,
|
request: InitPaymentRequest,
|
||||||
delivery_type: str = "",
|
delivery_type: str = "",
|
||||||
|
service_guid: str | None = None,
|
||||||
) -> dict[str, Element]:
|
) -> dict[str, Element]:
|
||||||
system_data = request.system_data
|
system_data = request.system_data
|
||||||
fields = [
|
fields = [
|
||||||
@@ -75,6 +79,8 @@ def build_calc_body_for_payment(
|
|||||||
make_field("Qty", "1", "int"),
|
make_field("Qty", "1", "int"),
|
||||||
]
|
]
|
||||||
_append_delivery_type(fields, delivery_type)
|
_append_delivery_type(fields, delivery_type)
|
||||||
|
if service_guid:
|
||||||
|
fields.append(make_field("Service", service_guid))
|
||||||
return _calc_body(Element(key="Destination", fields=fields))
|
return _calc_body(Element(key="Destination", fields=fields))
|
||||||
|
|
||||||
|
|
||||||
@@ -83,14 +89,17 @@ def _append_delivery_type(fields: list[Element], delivery_type: str) -> None:
|
|||||||
fields.append(make_field("DeliveryType", delivery_type))
|
fields.append(make_field("DeliveryType", delivery_type))
|
||||||
|
|
||||||
|
|
||||||
def split_tariff_code(tariff_code: str) -> tuple[str, str]:
|
def split_tariff_code(tariff_code: str) -> tuple[str, str, str]:
|
||||||
"""Split the CSE ``tariff_code`` into (delivery_type, urgency).
|
"""Split the CSE ``tariff_code`` into delivery type, service GUID, urgency.
|
||||||
|
|
||||||
The unified tariff_code encodes both dimensions as ``"<DeliveryType>|<Urgency>"``.
|
The expected format is ``"<DeliveryType>|<ServiceGuid>|<Urgency>"``.
|
||||||
|
``DeliveryType`` may be empty when CSE contract defaults are used.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
delivery_type, _, urgency = tariff_code.partition("|")
|
parts = tariff_code.split("|")
|
||||||
return delivery_type, urgency
|
if len(parts) != 3 or not parts[1] or not parts[2]:
|
||||||
|
raise CSERequestError("CSE tariff_code has invalid format.")
|
||||||
|
return parts[0], parts[1], parts[2]
|
||||||
|
|
||||||
|
|
||||||
def _calc_body(destination: Element) -> dict[str, Element]:
|
def _calc_body(destination: Element) -> dict[str, Element]:
|
||||||
@@ -110,7 +119,7 @@ def map_cse_save_order_request(
|
|||||||
) -> dict[str, Element]:
|
) -> dict[str, Element]:
|
||||||
system_data = request.system_data
|
system_data = request.system_data
|
||||||
take_date = request.pickup_date.strftime(_DATETIME_FORMAT)
|
take_date = request.pickup_date.strftime(_DATETIME_FORMAT)
|
||||||
delivery_type, urgency = split_tariff_code(system_data.tariff.tariff_code)
|
delivery_type, _, urgency = split_tariff_code(system_data.tariff.tariff_code)
|
||||||
|
|
||||||
fields = [
|
fields = [
|
||||||
make_field("TakeDate", take_date, "dateTime"),
|
make_field("TakeDate", take_date, "dateTime"),
|
||||||
|
|||||||
@@ -56,6 +56,10 @@ class CSEDeliveryProviderConfig(DeliveryProviderBaseConfig):
|
|||||||
base_url: str = "https://web.cse.ru/1c/ws/Web1C.1cws"
|
base_url: str = "https://web.cse.ru/1c/ws/Web1C.1cws"
|
||||||
login: str = ""
|
login: str = ""
|
||||||
password: str = ""
|
password: str = ""
|
||||||
|
# CSE service GUIDs from GetReferenceData: Services that are allowed to be
|
||||||
|
# shown as delivery tariffs. Additional/non-delivery services must not be
|
||||||
|
# included here.
|
||||||
|
delivery_service_guids: list[str] = Field(default_factory=list)
|
||||||
# Contract-specific required parameters for SaveDocuments (order registration).
|
# Contract-specific required parameters for SaveDocuments (order registration).
|
||||||
# Urgency is not here: it comes from the tariff selected by the client.
|
# Urgency is not here: it comes from the tariff selected by the client.
|
||||||
payer: str = ""
|
payer: str = ""
|
||||||
|
|||||||
@@ -33,6 +33,8 @@ delivery_providers:
|
|||||||
retry_backoff_seconds: 0.2
|
retry_backoff_seconds: 0.2
|
||||||
timeout_seconds: 10.0
|
timeout_seconds: 10.0
|
||||||
cache_ttl_seconds: 900
|
cache_ttl_seconds: 900
|
||||||
|
delivery_service_guids:
|
||||||
|
- "6da21fe8-4f13-11dc-bda1-0015170f8c09" # Россия доставка
|
||||||
payer: "0" # Заказчик
|
payer: "0" # Заказчик
|
||||||
payment_method: "1" # Безналичный расчёт
|
payment_method: "1" # Безналичный расчёт
|
||||||
shipping_method: "5052d0b3-5ea3-46f2-823f-1472686a51dd" # авто
|
shipping_method: "5052d0b3-5ea3-46f2-823f-1472686a51dd" # авто
|
||||||
|
|||||||
@@ -33,6 +33,8 @@ delivery_providers:
|
|||||||
retry_backoff_seconds: 0.2
|
retry_backoff_seconds: 0.2
|
||||||
timeout_seconds: 10.0
|
timeout_seconds: 10.0
|
||||||
cache_ttl_seconds: 900
|
cache_ttl_seconds: 900
|
||||||
|
delivery_service_guids:
|
||||||
|
- "tariff-guid-2"
|
||||||
payer: "0"
|
payer: "0"
|
||||||
payment_method: "1"
|
payment_method: "1"
|
||||||
shipping_method: "5052d0b3-5ea3-46f2-823f-1472686a51dd"
|
shipping_method: "5052d0b3-5ea3-46f2-823f-1472686a51dd"
|
||||||
|
|||||||
@@ -166,6 +166,43 @@ _CALC_RESPONSE_WITH_ADDITIONAL_SERVICE_FIRST = """<?xml version="1.0" encoding="
|
|||||||
</soap:Body>
|
</soap:Body>
|
||||||
</soap:Envelope>"""
|
</soap:Envelope>"""
|
||||||
|
|
||||||
|
_CALC_RESPONSE_WITH_BUYOUT_SERVICE_FIRST = """<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope">
|
||||||
|
<soap:Body>
|
||||||
|
<m:CalcResponse xmlns:m="http://www.cargo3.ru">
|
||||||
|
<m:return>
|
||||||
|
<m:Key>Calc</m:Key>
|
||||||
|
<m:List>
|
||||||
|
<m:Key>Destination</m:Key>
|
||||||
|
<m:List>
|
||||||
|
<m:Key>Tariff</m:Key>
|
||||||
|
<m:Value>buyout-service-guid</m:Value>
|
||||||
|
<m:Fields><m:Key>Total</m:Key><m:Value>100.00</m:Value></m:Fields>
|
||||||
|
<m:Fields><m:Key>CurrencyName</m:Key><m:Value>RUR</m:Value></m:Fields>
|
||||||
|
<m:Fields>
|
||||||
|
<m:Key>Service</m:Key><m:Value>Частичный выкуп</m:Value>
|
||||||
|
</m:Fields>
|
||||||
|
<m:Fields><m:Key>Urgency</m:Key><m:Value>urg-exp</m:Value></m:Fields>
|
||||||
|
</m:List>
|
||||||
|
<m:List>
|
||||||
|
<m:Key>Tariff</m:Key>
|
||||||
|
<m:Value>delivery-guid</m:Value>
|
||||||
|
<m:Fields><m:Key>Total</m:Key><m:Value>793.00</m:Value></m:Fields>
|
||||||
|
<m:Fields><m:Key>CurrencyName</m:Key><m:Value>RUR</m:Value></m:Fields>
|
||||||
|
<m:Fields><m:Key>Service</m:Key><m:Value>Экспресс</m:Value></m:Fields>
|
||||||
|
<m:Fields><m:Key>Urgency</m:Key><m:Value>urg-exp</m:Value></m:Fields>
|
||||||
|
<m:Fields>
|
||||||
|
<m:Key>AdditionalService</m:Key><m:Value>false</m:Value>
|
||||||
|
</m:Fields>
|
||||||
|
<m:Fields><m:Key>MinPeriod</m:Key><m:Value>1</m:Value></m:Fields>
|
||||||
|
<m:Fields><m:Key>MaxPeriod</m:Key><m:Value>2</m:Value></m:Fields>
|
||||||
|
</m:List>
|
||||||
|
</m:List>
|
||||||
|
</m:return>
|
||||||
|
</m:CalcResponse>
|
||||||
|
</soap:Body>
|
||||||
|
</soap:Envelope>"""
|
||||||
|
|
||||||
_ERROR_RESPONSE = """<?xml version="1.0" encoding="UTF-8"?>
|
_ERROR_RESPONSE = """<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope">
|
<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope">
|
||||||
<soap:Body>
|
<soap:Body>
|
||||||
@@ -288,19 +325,23 @@ def test_provider_get_prices_maps_all_tariffs() -> None:
|
|||||||
[
|
[
|
||||||
httpx.Response(200, text=_DELIVERY_TYPES_RESPONSE),
|
httpx.Response(200, text=_DELIVERY_TYPES_RESPONSE),
|
||||||
httpx.Response(200, text=_CALC_RESPONSE),
|
httpx.Response(200, text=_CALC_RESPONSE),
|
||||||
|
httpx.Response(200, text=_CALC_RESPONSE),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
provider = CSEProvider(client)
|
provider = CSEProvider(
|
||||||
|
client,
|
||||||
|
delivery_service_guids=("tariff-guid-1", "tariff-guid-2"),
|
||||||
|
)
|
||||||
|
|
||||||
prices = asyncio.run(provider.get_prices(_calc_request()))
|
prices = asyncio.run(provider.get_prices(_calc_request()))
|
||||||
|
|
||||||
# PVZ-requiring scheme (Склад-Дверь) is excluded: only delivery types + one
|
# PVZ-requiring scheme (Склад-Дверь) is excluded: only delivery types + one
|
||||||
# Calc (for door-to-door) are requested.
|
# Calc per configured CSE delivery service are requested.
|
||||||
assert len(http_client.calls) == 2
|
assert len(http_client.calls) == 3
|
||||||
assert [price.provider for price in prices] == ["cse", "cse"]
|
assert [price.provider for price in prices] == ["cse", "cse"]
|
||||||
assert [price.tariff_code for price in prices] == [
|
assert [price.tariff_code for price in prices] == [
|
||||||
"ДоставкаДоДверей|urg-std",
|
"ДоставкаДоДверей|tariff-guid-1|urg-std",
|
||||||
"ДоставкаДоДверей|urg-exp",
|
"ДоставкаДоДверей|tariff-guid-2|urg-exp",
|
||||||
]
|
]
|
||||||
assert prices[0].price == Decimal("1114.92")
|
assert prices[0].price == Decimal("1114.92")
|
||||||
assert prices[0].currency == "RUB"
|
assert prices[0].currency == "RUB"
|
||||||
@@ -311,7 +352,7 @@ def test_provider_get_prices_maps_all_tariffs() -> None:
|
|||||||
|
|
||||||
def test_provider_get_payment_price_selects_matching_tariff() -> None:
|
def test_provider_get_payment_price_selects_matching_tariff() -> None:
|
||||||
response = httpx.Response(200, text=_CALC_RESPONSE)
|
response = httpx.Response(200, text=_CALC_RESPONSE)
|
||||||
client, _ = _build_client([response])
|
client, http_client = _build_client([response])
|
||||||
provider = CSEProvider(client)
|
provider = CSEProvider(client)
|
||||||
request = make_init_payment_request(
|
request = make_init_payment_request(
|
||||||
systemData={
|
systemData={
|
||||||
@@ -321,7 +362,7 @@ def test_provider_get_payment_price_selects_matching_tariff() -> None:
|
|||||||
"price": 200000,
|
"price": 200000,
|
||||||
"deliveryDaysMin": 1,
|
"deliveryDaysMin": 1,
|
||||||
"deliveryDaysMax": 2,
|
"deliveryDaysMax": 2,
|
||||||
"tariffCode": "ДоставкаДоДверей|urg-exp",
|
"tariffCode": "ДоставкаДоДверей|tariff-guid-2|urg-exp",
|
||||||
},
|
},
|
||||||
"parcelType": "parcel",
|
"parcelType": "parcel",
|
||||||
"weight": "1.0",
|
"weight": "1.0",
|
||||||
@@ -332,8 +373,11 @@ def test_provider_get_payment_price_selects_matching_tariff() -> None:
|
|||||||
price = asyncio.run(provider.get_payment_price(request))
|
price = asyncio.run(provider.get_payment_price(request))
|
||||||
|
|
||||||
assert price is not None
|
assert price is not None
|
||||||
assert price.tariff_code == "ДоставкаДоДверей|urg-exp"
|
assert price.tariff_code == "ДоставкаДоДверей|tariff-guid-2|urg-exp"
|
||||||
assert price.price == Decimal("2000.00")
|
assert price.price == Decimal("2000.00")
|
||||||
|
content = http_client.calls[0]["content"].decode("utf-8")
|
||||||
|
assert "Service" in content
|
||||||
|
assert "tariff-guid-2" in content
|
||||||
|
|
||||||
|
|
||||||
def test_provider_get_payment_price_ignores_matching_additional_service() -> None:
|
def test_provider_get_payment_price_ignores_matching_additional_service() -> None:
|
||||||
@@ -348,7 +392,7 @@ def test_provider_get_payment_price_ignores_matching_additional_service() -> Non
|
|||||||
"price": 79300,
|
"price": 79300,
|
||||||
"deliveryDaysMin": 1,
|
"deliveryDaysMin": 1,
|
||||||
"deliveryDaysMax": 2,
|
"deliveryDaysMax": 2,
|
||||||
"tariffCode": "ДоставкаДоДверей|urg-exp",
|
"tariffCode": "ДоставкаДоДверей|delivery-guid|urg-exp",
|
||||||
},
|
},
|
||||||
"parcelType": "parcel",
|
"parcelType": "parcel",
|
||||||
"weight": "1.0",
|
"weight": "1.0",
|
||||||
@@ -359,7 +403,34 @@ def test_provider_get_payment_price_ignores_matching_additional_service() -> Non
|
|||||||
price = asyncio.run(provider.get_payment_price(request))
|
price = asyncio.run(provider.get_payment_price(request))
|
||||||
|
|
||||||
assert price is not None
|
assert price is not None
|
||||||
assert price.tariff_code == "ДоставкаДоДверей|urg-exp"
|
assert price.tariff_code == "ДоставкаДоДверей|delivery-guid|urg-exp"
|
||||||
|
assert price.price == Decimal("793.00")
|
||||||
|
|
||||||
|
|
||||||
|
def test_provider_get_payment_price_selects_matching_service_guid() -> None:
|
||||||
|
response = httpx.Response(200, text=_CALC_RESPONSE_WITH_BUYOUT_SERVICE_FIRST)
|
||||||
|
client, _ = _build_client([response])
|
||||||
|
provider = CSEProvider(client)
|
||||||
|
request = make_init_payment_request(
|
||||||
|
systemData={
|
||||||
|
"tariff": {
|
||||||
|
"provider": "cse",
|
||||||
|
"serviceName": "Экспресс",
|
||||||
|
"price": 79300,
|
||||||
|
"deliveryDaysMin": 1,
|
||||||
|
"deliveryDaysMax": 2,
|
||||||
|
"tariffCode": "ДоставкаДоДверей|delivery-guid|urg-exp",
|
||||||
|
},
|
||||||
|
"parcelType": "parcel",
|
||||||
|
"weight": "1.0",
|
||||||
|
"dimensions": {"length": "10", "width": "10", "height": "10"},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
price = asyncio.run(provider.get_payment_price(request))
|
||||||
|
|
||||||
|
assert price is not None
|
||||||
|
assert price.tariff_code == "ДоставкаДоДверей|delivery-guid|urg-exp"
|
||||||
assert price.price == Decimal("793.00")
|
assert price.price == Decimal("793.00")
|
||||||
|
|
||||||
|
|
||||||
@@ -370,7 +441,7 @@ def test_provider_get_prices_excludes_additional_service_tariffs() -> None:
|
|||||||
httpx.Response(200, text=_CALC_RESPONSE_WITH_ADDITIONAL_SERVICE_FIRST),
|
httpx.Response(200, text=_CALC_RESPONSE_WITH_ADDITIONAL_SERVICE_FIRST),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
provider = CSEProvider(client)
|
provider = CSEProvider(client, delivery_service_guids=("delivery-guid",))
|
||||||
|
|
||||||
prices = asyncio.run(provider.get_prices(_calc_request()))
|
prices = asyncio.run(provider.get_prices(_calc_request()))
|
||||||
|
|
||||||
@@ -380,13 +451,58 @@ def test_provider_get_prices_excludes_additional_service_tariffs() -> None:
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def test_provider_get_prices_uses_configured_service_guid_filter() -> None:
|
||||||
|
client, http_client = _build_client(
|
||||||
|
[
|
||||||
|
httpx.Response(200, text=_DELIVERY_TYPES_RESPONSE),
|
||||||
|
httpx.Response(200, text=_CALC_RESPONSE_WITH_BUYOUT_SERVICE_FIRST),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
provider = CSEProvider(client, delivery_service_guids=("delivery-guid",))
|
||||||
|
|
||||||
|
prices = asyncio.run(provider.get_prices(_calc_request()))
|
||||||
|
|
||||||
|
assert [price.price for price in prices] == [Decimal("793.00")]
|
||||||
|
assert [price.service_name for price in prices] == [
|
||||||
|
"Экспресс — Дверь-Дверь"
|
||||||
|
]
|
||||||
|
content = http_client.calls[1]["content"].decode("utf-8")
|
||||||
|
assert "Service" in content
|
||||||
|
assert "delivery-guid" in content
|
||||||
|
|
||||||
|
|
||||||
|
def test_provider_get_prices_requires_delivery_service_guid_configuration() -> None:
|
||||||
|
client, _ = _build_client([httpx.Response(200, text=_DELIVERY_TYPES_RESPONSE)])
|
||||||
|
provider = CSEProvider(client)
|
||||||
|
|
||||||
|
with pytest.raises(CSERequestError):
|
||||||
|
asyncio.run(provider.get_prices(_calc_request()))
|
||||||
|
|
||||||
|
|
||||||
def test_provider_register_order_returns_document_number() -> None:
|
def test_provider_register_order_returns_document_number() -> None:
|
||||||
response = httpx.Response(200, text=_SAVE_RESPONSE)
|
response = httpx.Response(200, text=_SAVE_RESPONSE)
|
||||||
client, http_client = _build_client([response])
|
client, http_client = _build_client([response])
|
||||||
provider = CSEProvider(client)
|
provider = CSEProvider(client)
|
||||||
|
|
||||||
result = asyncio.run(
|
result = asyncio.run(
|
||||||
provider.register_order(make_init_payment_request(), "order-uuid-1")
|
provider.register_order(
|
||||||
|
make_init_payment_request(
|
||||||
|
systemData={
|
||||||
|
"tariff": {
|
||||||
|
"provider": "cse",
|
||||||
|
"serviceName": "Экспресс",
|
||||||
|
"price": 200000,
|
||||||
|
"deliveryDaysMin": 1,
|
||||||
|
"deliveryDaysMax": 2,
|
||||||
|
"tariffCode": "ДоставкаДоДверей|tariff-guid-2|urg-exp",
|
||||||
|
},
|
||||||
|
"parcelType": "parcel",
|
||||||
|
"weight": "1.0",
|
||||||
|
"dimensions": {"length": "10", "width": "10", "height": "10"},
|
||||||
|
}
|
||||||
|
),
|
||||||
|
"order-uuid-1",
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
assert result.order_number == "CSE-000123"
|
assert result.order_number == "CSE-000123"
|
||||||
@@ -436,7 +552,7 @@ def test_save_order_request_uses_selected_tariff_urgency() -> None:
|
|||||||
"price": 200000,
|
"price": 200000,
|
||||||
"deliveryDaysMin": 1,
|
"deliveryDaysMin": 1,
|
||||||
"deliveryDaysMax": 2,
|
"deliveryDaysMax": 2,
|
||||||
"tariffCode": "ДоставкаДоДверей|urg-exp",
|
"tariffCode": "ДоставкаДоДверей|tariff-guid-2|urg-exp",
|
||||||
},
|
},
|
||||||
"parcelType": "parcel",
|
"parcelType": "parcel",
|
||||||
"weight": "1.0",
|
"weight": "1.0",
|
||||||
@@ -460,7 +576,7 @@ def test_application_error_in_response_raises_request_error() -> None:
|
|||||||
httpx.Response(200, text=_ERROR_RESPONSE),
|
httpx.Response(200, text=_ERROR_RESPONSE),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
provider = CSEProvider(client)
|
provider = CSEProvider(client, delivery_service_guids=("delivery-guid",))
|
||||||
|
|
||||||
with pytest.raises(CSERequestError):
|
with pytest.raises(CSERequestError):
|
||||||
asyncio.run(provider.get_prices(_calc_request()))
|
asyncio.run(provider.get_prices(_calc_request()))
|
||||||
|
|||||||
@@ -193,6 +193,7 @@ def test_configuration_sections_are_loaded_from_yaml_file(
|
|||||||
assert settings.delivery_providers.cse.retry_backoff_seconds == 0.2
|
assert settings.delivery_providers.cse.retry_backoff_seconds == 0.2
|
||||||
assert settings.delivery_providers.cse.timeout_seconds == 10.0
|
assert settings.delivery_providers.cse.timeout_seconds == 10.0
|
||||||
assert settings.delivery_providers.cse.cache_ttl_seconds == 900
|
assert settings.delivery_providers.cse.cache_ttl_seconds == 900
|
||||||
|
assert settings.delivery_providers.cse.delivery_service_guids == ["tariff-guid-2"]
|
||||||
assert settings.delivery_providers.cse.payer == "0"
|
assert settings.delivery_providers.cse.payer == "0"
|
||||||
assert settings.delivery_providers.cse.payment_method == "1"
|
assert settings.delivery_providers.cse.payment_method == "1"
|
||||||
assert (
|
assert (
|
||||||
|
|||||||
Reference in New Issue
Block a user