schon/engine/core/crm/amo/gateway.py

230 lines
8.4 KiB
Python

import logging
import requests
from django.conf import settings
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.{settings.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"])