schon/core/crm/amo/gateway.py
Egor fureunoir Gorbunov d203095c65 Features: 1) Integrated CRM trigger functionality for Order model, allowing CRM updates during state changes; 2) Added handling for multiple or missing OrderCrmLink entries within upsert_order; 3) Introduced fns_api_key handling in CRM configurations.
Fixes: 1) Resolved malformed phone number issue in serializer when `instance` is unavailable; 2) Corrected relation field naming for `OrderCrmLink` (`order_uuid` to `order`); 3) Ensured only one default CRM provider can exist.

Extra: 1) Refactored `is_business` logic with error handling; 2) Improved logging for CRM integration errors; 3) Added `integration_location` and `default` fields to CRM configuration model.
2025-09-07 00:09:29 +03:00

118 lines
4.6 KiB
Python

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"])