intrabot/intradesk/reference.py
2026-02-24 15:15:23 +03:00

278 lines
10 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
Справочники Intradesk.
Содержит:
1. Статические константы (известные ID → имена)
2. Загрузку справочников из API Intradesk в локальную БД
"""
import logging
import requests
import config
import database
log = logging.getLogger(__name__)
# ── Статические справочники ────────────────────────────────────────────────────
# Используются как fallback если данные ещё не загружены из API,
# а также сидируются в БД при старте.
STATUSES: dict[int, str] = {
68046: "Открыта",
68048: "Требует уточнения",
68051: "В работе",
68045: "Выполнена",
}
PRIORITIES: dict[int, str] = {
29749: "Наивысший",
29598: "Критический",
29597: "Высокий",
29596: "Низкий",
}
USER_TYPES: dict[int, str] = {
10: "Клиент",
20: "Сотрудник",
}
# Все типы событий из lifecycle заявки
EVENT_TYPES: dict[int, str] = {
10: "Смена статуса",
20: "Смена приоритета",
25: "Смена оценки",
30: "Смена исполнителя",
40: "Смена группы исполнителей",
45: "Изменение доп. данных",
50: "Комментарий",
55: "Приватный комментарий",
60: "Добавлен наблюдатель",
61: "Добавлен координатор",
65: "Добавлена группа наблюдателей",
70: "Удалён наблюдатель",
75: "Удалена группа наблюдателей",
80: "Добавлен файл",
85: "Добавлен скрытый файл",
90: "Удалён файл",
95: "Удалён скрытый файл",
100: "Добавлен тег",
105: "Добавлен актив",
110: "Удалён тег",
115: "Удалён актив",
120: "Изменён плановый срок решения",
125: "Изменён фактический срок решения",
126: "Изменено оставшееся время",
130: "Изменён плановый срок реакции",
135: "Изменён фактический срок реакции",
136: "Истёк срок решения",
137: "Истёк срок реакции",
138: "Изменена дата закрытия",
140: "Изменён сервис",
150: "Изменён тип заявки",
160: "Изменён инициатор",
161: "Изменён клиент",
165: "Изменена группа инициатора",
170: "Изменено доп. поле",
180: "Изменена дата создания",
185: "Изменена дата обновления",
190: "Изменён создатель",
195: "Изменён последний редактор",
200: "Изменено название",
205: "Изменено описание",
210: "Изменён бизнес-процесс",
220: "Добавлен не ответственный исполнитель",
225: "Добавлена не ответственная группа исполнителей",
230: "Удалён не ответственный исполнитель",
235: "Удалена не ответственная группа исполнителей",
240: "Изменён SLA",
250: "Изменена история",
260: "Изменён канал создания",
270: "Изменён канал обновления",
280: "Архивировано",
290: "Добавлены координаты",
300: "Выполнен макрос",
310: "Изменён признак массового инцидента",
320: "Изменён массовый инцидент",
330: "Изменена координация",
340: "Изменена критичность приоритета",
350: "Изменено влияние приоритета",
400: "Эскалация заявки",
}
# ── API-справочники (загружаются из Intradesk) ─────────────────────────────────
#
# Статус эндпоинта:
# [OK] — проверено, работает
# [?] — нужно проверить, путь предположительный
SETTINGS_URL = "https://apigw.intradesk.ru/settings/odata"
ENTITIES = [
{
"key": "employees",
"url": f"{SETTINGS_URL}/Employees",
"params": {"$filter": "isArchived eq false"},
"id_field": "id",
"name_field": "name",
"status": "OK",
},
{
"key": "groups",
"url": f"{SETTINGS_URL}/UserGroups",
"params": {"$filter": "isArchived eq false"},
"id_field": "id",
"name_field": "name",
"status": "?",
},
{
"key": "statuses",
"url": f"{SETTINGS_URL}/Statuses",
"params": {},
"id_field": "id",
"name_field": "name",
"status": "?",
},
{
"key": "priorities",
"url": f"{SETTINGS_URL}/Priorities",
"params": {},
"id_field": "id",
"name_field": "name",
"status": "?",
},
{
"key": "services",
"url": f"{SETTINGS_URL}/ServiceCatalog",
"params": {"$filter": "isArchived eq false"},
"id_field": "id",
"name_field": "name",
"status": "?",
},
{
"key": "tasktypes",
"url": f"{SETTINGS_URL}/TaskTypes",
"params": {"$filter": "isArchived eq false"},
"id_field": "id",
"name_field": "name",
"status": "?",
},
{
"key": "tags",
"url": f"{SETTINGS_URL}/Tags",
"params": {},
"id_field": "id",
"name_field": "name",
"status": "?",
},
{
"key": "workflows",
"url": f"{SETTINGS_URL}/Workflows",
"params": {"$filter": "isArchived eq false"},
"id_field": "id",
"name_field": "name",
"status": "?",
},
{
"key": "sla",
"url": f"{SETTINGS_URL}/Sla",
"params": {},
"id_field": "id",
"name_field": "name",
"status": "?",
},
]
# ── вспомогательные ────────────────────────────────────────────────────────────
def resolve(entity: str, item_id: int | None) -> str | None:
"""
Резолвит ID в имя.
Сначала смотрит в БД (актуальные данные из API),
затем в статические словари-fallback.
"""
if item_id is None:
return None
# БД — приоритет
name = database.resolve(entity, item_id)
if name:
return name
# Fallback на статические константы
_fallbacks = {
"statuses": STATUSES,
"priorities": PRIORITIES,
}
fb = _fallbacks.get(entity, {})
return fb.get(item_id)
def event_name(event_type: int) -> str:
"""Возвращает человекочитаемое имя события по его числовому коду."""
return EVENT_TYPES.get(event_type, f"Событие {event_type}")
def user_type_name(user_type: int) -> str:
"""Возвращает тип пользователя (Клиент / Сотрудник)."""
return USER_TYPES.get(user_type, str(user_type))
# ── загрузка из API ────────────────────────────────────────────────────────────
def _fetch_all_pages(url: str, params: dict) -> list[dict]:
params = {**params, "ApiKey": config.API_KEY}
items = []
current_url = url
while current_url:
try:
resp = requests.get(current_url, params=params, timeout=30)
resp.raise_for_status()
except requests.exceptions.RequestException as e:
log.error(f"Request failed [{current_url}]: {e}")
break
data = resp.json()
items.extend(data.get("value", []))
current_url = data.get("@odata.nextLink")
params = None
return items
def _load_entity(entity: dict) -> int:
key = entity["key"]
id_field = entity["id_field"]
name_field = entity["name_field"]
items = _fetch_all_pages(entity["url"], entity.get("params", {}))
if not items:
log.warning(f"[{key}] empty or failed — skipped")
return 0
pairs = [
(item[id_field], str(item.get(name_field) or "").strip())
for item in items
if id_field in item
]
database.save_ref_data(key, pairs)
log.info(f"[{key}] loaded {len(pairs)} records")
return len(pairs)
def seed_static():
"""Сидирует в БД известные статические данные (статусы, приоритеты)."""
database.save_ref_data("statuses", list(STATUSES.items()))
database.save_ref_data("priorities", list(PRIORITIES.items()))
log.info("Static reference data seeded (statuses, priorities)")
def load_all():
"""Загружает все справочники из API. Неудачные — логирует и пропускает."""
log.info("Loading reference data from Intradesk API...")
for entity in ENTITIES:
_load_entity(entity)
log.info("Reference data loading complete.")