Fixes: None; Extra: 1) Add `URLField` import in models.py; 2) Adjust `save` method signature in models.py to improve typing consistency; 3) Raise custom `CRMException` for CRM setup errors;
110 lines
4.3 KiB
Python
110 lines
4.3 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 AmoOrderGateway:
|
|
def __init__(self):
|
|
try:
|
|
self.instance = CustomerRelationshipManagementProvider.objects.get(name="amo")
|
|
except CustomerRelationshipManagementProvider.DoesNotExist:
|
|
logger.warning("AMO CRM provider not found")
|
|
raise CRMException("AMO CRM provider not found")
|
|
|
|
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")
|
|
|
|
if not all(
|
|
[
|
|
self.base,
|
|
self.client_id,
|
|
self.client_secret,
|
|
self.redirect_uri,
|
|
self.pipeline_id,
|
|
self.stage_map,
|
|
]
|
|
):
|
|
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():
|
|
link: Optional[OrderCrmLink] = OrderCrmLink.objects.filter(order=order).first()
|
|
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"])
|