278 lines
10 KiB
Python
278 lines
10 KiB
Python
"""
|
||
Справочники 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.")
|