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