"""Реальный клиент exoCRM (`GetOrders`, `SetStatus`).""" from __future__ import annotations from typing import Any import httpx from app.core.config import Settings from app.schemas.orders import OrderOut from app.services.crm.checksum import compute_md5sum from app.services.crm.client import CrmError def _errors_text(data: dict[str, Any]) -> str: # Ключ бывает и `errors` (dict код → текст), и `Errors` (список строк). errors = data.get("errors") or data.get("Errors") or {} if isinstance(errors, dict): return "; ".join(f"{code}: {text}" for code, text in errors.items()) return "; ".join(str(error) for error in errors) class ExoCrmClient: def __init__(self, settings: Settings) -> None: self._base_url = settings.crm_base_url self._api_key = settings.crm_api_key self._secret_key = settings.crm_secret_key self._shop_key = settings.crm_shop_key self._sid = settings.crm_sid async def _post(self, method: str, params: dict[str, Any]) -> dict[str, Any]: body = {"apikey": self._api_key, "object": "Orders", "method": method, "params": params} body["md5sum"] = compute_md5sum(body, self._secret_key) async with httpx.AsyncClient(timeout=30) as client: response = await client.post(self._base_url, json=body) response.raise_for_status() try: data = response.json() except ValueError as exc: # PHP-notice'ы CRM перед JSON — признак неверных параметров. raise CrmError(f"CRM повернула не JSON: {response.text[:300]}") from exc if not isinstance(data, dict): raise CrmError(f"Неочікувана відповідь CRM: {str(data)[:300]}") return data async def get_orders(self, *, status: str) -> list[OrderOut]: data = await self._post( "GetOrders", { "sid": self._sid, "key": self._shop_key, "Status": status, "ReturnGoods": True, "ReturnTotals": True, }, ) if data.get("status") != "OK": raise CrmError(f"CRM повернула помилку: {_errors_text(data) or 'невідома помилка'}") return [OrderOut.model_validate(order) for order in data.get("result") or []] async def set_status(self, *, order_id: str, status: str) -> None: """`SetStatus`. Формат проверен на боевой CRM и расходится с документацией. Документация показывает `params: {"ID": ..., "Status": ...}`, но CRM на это отвечает «Undefined order list.». Рабочий вариант — список ID в `Orders`, а ответ — результат по каждому заказу без общего `status: OK`: {"123901": {"Status": "Success", "ChangeStatus": "Success"}} """ data = await self._post("SetStatus", {"Orders": [order_id], "Status": status}) result = data.get(order_id) if isinstance(result, dict) and result.get("Status") == "Success": return details = _errors_text(data) or ( str(result) if result is not None else "немає відповіді щодо замовлення" ) raise CrmError(f"CRM не змінила статус замовлення {order_id} на {status}: {details}")