import logging import time from typing import Optional import requests from django.core.cache import cache from django.db import transaction from core.crm.exceptions import CRMException from core.models import CustomerRelationshipManagementProvider, Order, OrderCrmLink logger = logging.getLogger("django") class AmoCRM: def __init__(self): try: self.instance = CustomerRelationshipManagementProvider.objects.get(name="amo") except CustomerRelationshipManagementProvider.DoesNotExist as dne: logger.warning("AMO CRM provider not found") raise CRMException("AMO CRM provider not found") from dne self.base = f"https://{self.instance.integration_url}" self.client_id = self.instance.authentication.get("client_id") self.client_secret = self.instance.authentication.get("client_secret") self.refresh_token = cache.get("amo_refresh_token") self.redirect_uri = self.instance.attributes.get("redirect_uri") self.pipeline_id = self.instance.attributes.get("pipeline_id") self.stage_map = self.instance.attributes.get("stage_map") 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.redirect_uri, self.pipeline_id, self.stage_map, self.fns_api_key, ] ): raise CRMException("AMO CRM provider not configured") def _token(self) -> str: cached = getattr(self, "_cached_token", None) expiry = getattr(self, "_cached_expiry", 0) if cached and time.time() < expiry - 15: return cached payload = { "client_id": self.client_id, "client_secret": self.client_secret, "grant_type": "refresh_token", "refresh_token": self.refresh_token, "redirect_uri": self.redirect_uri, } r = requests.post(f"{self.base}/oauth2/access_token", json=payload, timeout=15) r.raise_for_status() data = r.json() self._cached_token = data["access_token"] self._cached_expiry = time.time() + int(data.get("expires_in", 900)) self.refresh_token = data.get("refresh_token", self.refresh_token) return self._cached_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 #{order.human_readable_id}" price = int(round(order.total_price)) stage_id = self.stage_map.get(order.status) payload = {"name": name, "price": price, "pipeline_id": self.pipeline_id} if stage_id: payload["status_id"] = stage_id if self.responsible_user_id: payload["responsible_user_id"] = self.responsible_user_id return payload def upsert_order(self, order: Order) -> str: with transaction.atomic(): try: link: Optional[OrderCrmLink] = 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_uuid=order.uuid, crm_lead_id=lead_id) return lead_id def update_order_status(self, crm_lead_id: str, new_status_code: str) -> None: link = OrderCrmLink.objects.filter(crm_lead_id=crm_lead_id).first() if not link: return from core.models import Order order = Order.objects.get(uuid=link.order_uuid) if order.status == new_status_code: return order.status = new_status_code order.save(update_fields=["status"])