import logging import requests from constance import config from django.core.cache import cache from django.db import transaction from engine.core.crm.exceptions import CRMException from engine.core.models import CustomerRelationshipManagementProvider, Order, OrderCrmLink from engine.core.utils import is_status_code_success logger = logging.getLogger(__name__) class AmoCRM: STATUS_MAP: dict[str, str] = {} def __init__(self): try: self.instance = CustomerRelationshipManagementProvider.objects.get(name="AmoCRM") except CustomerRelationshipManagementProvider.DoesNotExist as dne: logger.warning("AMO CRM provider not found") raise CRMException("AMO CRM provider not found") from dne except CustomerRelationshipManagementProvider.MultipleObjectsReturned as mre: logger.warning("Multiple AMO CRM providers found") raise CRMException("Multiple AMO CRM providers found") from mre self.base = f"{self.instance.integration_url}" self.client_id = self.instance.authentication.get("client_id") self.client_secret = self.instance.authentication.get("client_secret") self.authorization_code = self.instance.authentication.get("authorization_code") self.refresh_token = cache.get("amo_refresh_token") self.responsible_user_id = self.instance.attributes.get("responsible_user_id") self.fns_api_key = self.instance.attributes.get("fns_api_key") if not all( [ self.base, self.client_id, self.client_secret, self.authorization_code, self.fns_api_key, ] ): raise CRMException("AMO CRM provider not configured") def _token(self) -> str: payload = { "client_id": self.client_id, "client_secret": self.client_secret, "redirect_uri": f"https://api.{config.BASE_DOMAIN}/", } if self.refresh_token: payload["grant_type"] = "refresh_token" payload["refresh_token"] = self.refresh_token else: payload["grant_type"] = "authorization_code" payload["code"] = self.authorization_code r = requests.post(f"{self.base}/oauth2/access_token", json=payload, timeout=15) if not is_status_code_success(r.status_code): logger.error("Unable to get AMO access token: %s %s", r.status_code, r.text) raise CRMException("Unable to get AMO access token") data = r.json() self.access_token = data["access_token"] cache.set("amo_refresh_token", data["refresh_token"], 604800) self.refresh_token = data["refresh_token"] return self.access_token def _headers(self) -> dict: return {"Authorization": f"Bearer {self._token()}", "Content-Type": "application/json"} def _build_lead_payload(self, order: Order) -> dict: name = f"Заказ #{order.human_readable_id}" price = int(round(order.total_price)) payload: dict = {"name": name, "price": price} contact_id = self._create_contact(order) if contact_id: payload["_embedded"] = {"contacts": [{"id": contact_id}]} if self.responsible_user_id: payload["responsible_user_id"] = self.responsible_user_id return payload def _get_customer_name(self, order: Order) -> str: if type(order.attributes) is not dict: raise ValueError("order.attributes must be a dict") if order.user: if type(order.user.attributes) is not dict: order.user.attributes = {} order.user.save() if not order.business_identificator: return ( order.user.get_full_name() if order.user else None or ( f"{order.attributes.get('customer_name')} | " f"{order.attributes.get('customer_phone_number') or order.attributes.get('customer_email')}" ) ) try: r = requests.get( f"https://api-fns.ru/api/egr?req={order.business_identificator}&key={self.fns_api_key}", timeout=15 ) r.raise_for_status() body = r.json() except requests.exceptions.RequestException as rex: logger.error("Unable to get company info with FNS: %s", rex, exc_info=True) return "" ul = body.get("ЮЛ") ip = body.get("ИП") if ul and not ip: return f"{ul.get('НаимСокрЮЛ')} | {order.business_identificator}" if ip and not ul: return f"ИП {ip.get('ФИОПолн')} | {order.business_identificator}" return "" def _create_contact(self, order: Order) -> int | None: try: customer_name = self._get_customer_name(order) if customer_name: r = requests.get( f"{self.base}/api/v4/contacts", headers=self._headers().update({"filter[name]": customer_name, "limit": 1}), timeout=15, ) if r.status_code == 200: body = r.json() return body.get("_embedded", {}).get("contacts", [{}])[0].get("id", None) create_contact_payload = {"name": customer_name} if self.responsible_user_id: create_contact_payload["responsible_user_id"] = self.responsible_user_id if order.user: create_contact_payload["first_name"] = order.user.first_name or "" create_contact_payload["last_name"] = order.user.last_name or "" r = requests.post( f"{self.base}/api/v4/contacts", json={"name": customer_name}, headers=self._headers(), timeout=15 ) if r.status_code == 200: body = r.json() return body.get("_embedded", {}).get("contacts", [{}])[0].get("id", None) return None else: return None except requests.exceptions.RequestException as rex: logger.error("Unable to create a company in AmoCRM: %s", rex, exc_info=True) raise CRMException("Unable to create a company in AmoCRM") from rex def process_order_changes(self, order: Order) -> str: with transaction.atomic(): try: link: OrderCrmLink | None = OrderCrmLink.objects.get(order=order) except OrderCrmLink.MultipleObjectsReturned: link = OrderCrmLink.objects.filter(order=order).first() except OrderCrmLink.DoesNotExist: link = None if link: lead_id = link.crm_lead_id payload = self._build_lead_payload(order) r = requests.patch( f"{self.base}/api/v4/leads/{lead_id}", json=payload, headers=self._headers(), timeout=15 ) if r.status_code not in (200, 204): r.raise_for_status() return lead_id payload = self._build_lead_payload(order) r = requests.post(f"{self.base}/api/v4/leads", json=[payload], headers=self._headers(), timeout=15) r.raise_for_status() body = r.json() lead_id = str(body["_embedded"]["leads"][0]["id"]) OrderCrmLink.objects.create(order=order, crm_lead_id=lead_id, crm=self.instance) return lead_id def update_order_status(self, crm_lead_id: str, new_status: str) -> None: link = OrderCrmLink.objects.get(crm_lead_id=crm_lead_id) if link.order.status == new_status: return link.order.status = self.STATUS_MAP[new_status] link.order.save(update_fields=["status"])