153 lines
5.9 KiB
Python
153 lines
5.9 KiB
Python
"""
|
||
Intradesk REST API: создание заявок и чтение истории/комментариев.
|
||
|
||
Эндпоинты:
|
||
POST /changes/v3/tasks — создать заявку
|
||
GET /taskhistory/api/v2.0/lifetime/{id}/full — история (комментарии, смена статуса...)
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
import json
|
||
import logging
|
||
|
||
import httpx
|
||
|
||
import config
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
|
||
async def get_task_last_comment(task_id: int) -> dict | None:
|
||
"""
|
||
Получить последний комментарий к заявке из Intradesk.
|
||
|
||
Возвращает dict: {text, author, date} или None если нет комментариев.
|
||
"""
|
||
key = config.INTRADESK_TASKS_API_KEY
|
||
if not key:
|
||
return None
|
||
|
||
url = (
|
||
f"{config.INTRADESK_BASE_URL.rstrip('/')}"
|
||
f"/taskhistory/api/v2.0/lifetime/{task_id}/full"
|
||
f"?ApiKey={key}&sortDirection=Desc&top=10&events=50"
|
||
)
|
||
try:
|
||
async with httpx.AsyncClient() as client:
|
||
resp = await client.get(url, timeout=15.0)
|
||
if resp.status_code != 200:
|
||
logger.warning(
|
||
"get_task_last_comment task=%s → HTTP %s", task_id, resp.status_code
|
||
)
|
||
return None
|
||
data = resp.json()
|
||
except Exception as exc:
|
||
logger.warning("get_task_last_comment error: %s", exc)
|
||
return None
|
||
|
||
# Ищем последнее событие с blockname="comment" (type=50)
|
||
for entry in data.get("data") or []:
|
||
for ev in (entry.get("events") or {}).get("data") or []:
|
||
if ev.get("blockname") == "comment" and ev.get("type") == 50:
|
||
raw_date = entry.get("eventat") or ""
|
||
# "2021-03-15T11:54:36.92Z" → "2021-03-15 11:54"
|
||
short_date = raw_date[:16].replace("T", " ")
|
||
return {
|
||
"text": ev.get("stringvalue") or "",
|
||
"author": entry.get("username") or "—",
|
||
"date": short_date,
|
||
}
|
||
return None
|
||
|
||
|
||
async def create_intradesk_task(
|
||
name: str,
|
||
description: str,
|
||
client_intradesk_id: int | None = None,
|
||
contact_person_id: int | None = None,
|
||
attachment_urls: list[str] | None = None,
|
||
) -> dict | None:
|
||
"""
|
||
Создать заявку в Intradesk.
|
||
|
||
Возвращает ответ API (dict с полями id, tasknumber и др.) или None при ошибке.
|
||
Вложения — пока добавляются как ссылки в конец описания (до появления upload API).
|
||
В dry-run режиме запрос печатается в консоль и возвращается None.
|
||
"""
|
||
key = config.INTRADESK_CHANGES_API_KEY
|
||
if not key:
|
||
logger.warning("create_intradesk_task: INTRADESK_CHANGES_API_KEY не задан")
|
||
return None
|
||
|
||
# Вложения — ссылки в конце описания
|
||
full_desc = description
|
||
if attachment_urls:
|
||
full_desc += "\n\nВложения:\n" + "\n".join(attachment_urls)
|
||
|
||
blocks: dict[str, str] = {
|
||
"name": json.dumps({"value": name}, ensure_ascii=False),
|
||
"description": json.dumps({"value": full_desc}, ensure_ascii=False),
|
||
}
|
||
# initiator: вариант 3 из документации — клиент + пользователь клиента.
|
||
# clientid заполняется автоматически из initiatorclient, отдельно не нужен.
|
||
if client_intradesk_id is not None and contact_person_id is not None:
|
||
blocks["initiator"] = json.dumps(
|
||
{"value": {"groupid": client_intradesk_id, "userid": contact_person_id}},
|
||
ensure_ascii=False,
|
||
)
|
||
elif client_intradesk_id is not None:
|
||
# только клиент (нет конкретного контактного лица)
|
||
blocks["initiator"] = json.dumps(
|
||
{"value": {"groupid": client_intradesk_id}}, ensure_ascii=False
|
||
)
|
||
if config.INTRADESK_DEFAULT_SERVICE:
|
||
blocks["service"] = json.dumps(
|
||
{"value": config.INTRADESK_DEFAULT_SERVICE}, ensure_ascii=False
|
||
)
|
||
if config.INTRADESK_DEFAULT_TASK_TYPE:
|
||
blocks["tasktype"] = json.dumps(
|
||
{"value": int(config.INTRADESK_DEFAULT_TASK_TYPE)}, ensure_ascii=False
|
||
)
|
||
|
||
body = {"blocks": blocks, "Channel": "web"}
|
||
url = f"{config.INTRADESK_BASE_URL.rstrip('/')}/changes/v3/tasks?ApiKey={key}"
|
||
if contact_person_id is not None:
|
||
url += f"&userid={contact_person_id}"
|
||
|
||
if config.DRY_RUN:
|
||
print(
|
||
"\n[DRY-RUN] create_intradesk_task — запрос НЕ отправлен\n"
|
||
f" POST {url}\n"
|
||
f" Body: {json.dumps(body, ensure_ascii=False, indent=2)}\n"
|
||
)
|
||
return None
|
||
|
||
try:
|
||
async with httpx.AsyncClient() as client:
|
||
resp = await client.post(
|
||
url,
|
||
json=body,
|
||
headers={"Content-Type": "application/json"},
|
||
timeout=20.0,
|
||
)
|
||
if resp.status_code not in (200, 201):
|
||
logger.error(
|
||
"create_intradesk_task → HTTP %s: %s",
|
||
resp.status_code,
|
||
resp.text[:500],
|
||
)
|
||
return None
|
||
result = resp.json()
|
||
# API может вернуть как "Number"/"Id" (PascalCase), так и "number"/"id"
|
||
task_id = result.get("id") or result.get("Id") or result.get("ID")
|
||
task_number = (
|
||
result.get("tasknumber")
|
||
or result.get("number")
|
||
or result.get("Number")
|
||
or result.get("TaskNumber")
|
||
)
|
||
logger.info("create_intradesk_task ok: id=%s number=%s", task_id, task_number)
|
||
return result
|
||
except Exception as exc:
|
||
logger.exception("create_intradesk_task error: %s", exc)
|
||
return None
|