Skip to content
ShaerWare edited this page Mar 3, 2026 · 4 revisions

CRM (amoCRM)

Интеграция с amoCRM v4 API: OAuth2 аутентификация, синхронизация контактов, лидов и задач, автоматическое создание сделок.

Скриншот

CRM

Концепция

Интеграция с amoCRM v4 API через OAuth2 с автоматическим обновлением токенов. Система позволяет:

  • Двусторонняя синхронизация: импорт контактов и лидов из amoCRM → экспорт данных из бота в CRM
  • Автоматическое создание сделок: при обращении в Telegram бот или виджет
  • Webhook-интеграция: получение событий от amoCRM (обновление контактов, сделок, задач)
  • Управление воронками: выбор pipeline и статуса для новых лидов
  • Docker/VPN proxy: поддержка прокси для работы из контейнера

Страница CRM

Управление интеграцией через CrmView.vue с 4 вкладками: Settings (настройки), Kanban (доска сделок), Deals (таблица сделок), Inbox (сообщения).


Вкладка: Kanban (Доска сделок)

Визуальная доска сделок в стиле Trello/Kanban (CrmKanban.vue):

Функция Описание
Выбор воронки Выпадающий список всех pipelines из amoCRM
Колонки по статусам Горизонтальные колонки — каждая соответствует статусу воронки
Drag & Drop Перетаскивание сделок между статусами через vuedraggable@next (SortableJS)
Изменяемая ширина колонок Перетаскивание разделителя между колонками, ширина сохраняется в localStorage
Сворачивание колонок Кнопка ChevronLeft в заголовке сворачивает колонку в узкую вертикальную полоску (48px) с цветной точкой, счётчиком и вертикальным названием. Клик по свёрнутой полоске раскрывает обратно. Состояние сохраняется в localStorage (crm-kanban-collapsed). Разделители ресайза скрываются между свёрнутыми колонками
Сброс ширины Кнопка ↺ сбрасывает все колонки до ширины по умолчанию (288px)
Горизонтальная прокрутка Drag-to-scroll по горизонтали
Ленивая загрузка Карточки подгружаются порциями по 30 шт., IntersectionObserver автоматически загружает ещё при прокрутке
Неразобранные сделки Автоматически подгружаются в первую колонку воронки (статус «Неразобранное»)
Авто-обновление Каждые 30 секунд
Карточка сделки Название, сумма, ответственный, контакт

Вкладка: Deals (Таблица сделок)

Табличный вид всех сделок (CrmDeals.vue):

Функция Описание
Поиск Фильтрация сделок по названию
Пагинация Кнопки «назад/вперёд» в тулбаре (между поиском и кнопкой «Создать»)
Детали сделки Модальное окно: контакты, примечания, теги
Создание сделки Диалог с полями: название, сумма, воронка, статус, контакт

Вкладка: Inbox (Единый хаб диалогов)

Unified inbox с двумя подвкладками (CrmInbox.vue):

Подвкладка: AI Chats (по умолчанию)

Все клиентские диалоги с ИИ-ботами, сгруппированные по источнику:

Функция Описание
Группировка по источнику Сессии разделены на сворачиваемые группы: Telegram, WhatsApp, Widget
Сворачиваемые группы Chevron-toggle (▶/▼) на заголовке группы, со счётчиком сессий
Поиск Фильтрация сессий по названию и тексту последнего сообщения (по всем группам)
Иконки источника Каждый источник отображается своей иконкой: Send (Telegram), MessageCircle (WhatsApp), Globe (Widget)
Просмотр переписки Выбор сессии → сообщения с markdown-рендерингом (marked + DOMPurify)
Ответ через стриминг Ввод текста → chatApi.streamMessage() → streaming AI reply
Авто-обновление Каждые 15 сек — список сессий, каждые 10 сек — сообщения

Данные: используется GET /admin/chat/sessions?group_by=source (группы: telegram, whatsapp, widget; admin-чаты исключены).

Подвкладка: amoCRM Inbox

Мессенджер-интерфейс для переписки с контактами через Amojo API (CrmInboxAmoCRM.vue). Видна только если настроены Amojo credentials.

Функция Описание
Список контактов Поиск контактов из amoCRM
Разрешение чата Выбор контакта → автоматический запрос getContactChats() → получение UUID чата
История сообщений Загрузка переписки из Amojo API
Отправка сообщений Ввод текста → POST /admin/crm/chats/{chat_id}/messages
Авто-обновление Каждые 10 секунд

Требования: для amoCRM Inbox необходимо настроить Amojo credentials (Amojo Scope ID, Channel Secret) во вкладке Settings.


Вкладка: Settings (Настройки)

Connection Status (Статус подключения)

Индикатор Описание
🟢 Подключено OAuth2 токены активны, аккаунт доступен
🔴 Не подключено Нет токенов или истекли
🟡 Ошибка Ошибка при последнем запросе

Settings Form (Настройки)

Поле Описание
Subdomain Поддомен amoCRM (например, mycompany для mycompany.amocrm.ru)
Client ID OAuth2 Client ID из настроек интеграции
Client Secret OAuth2 Client Secret
Redirect URI URI для OAuth callback (автозаполняется: https://yourdomain.com/admin/crm/callback)
Sync Contacts Включить синхронизацию контактов
Sync Leads Включить синхронизацию лидов
Sync Tasks Включить синхронизацию задач
Auto Create Lead Автоматически создавать лид при первом обращении
Lead Pipeline ID ID воронки для новых лидов
Lead Status ID ID статуса для новых лидов

OAuth2 Flow

Шаг 1: Настройка интеграции в amoCRM

  1. Зайдите в Настройки → Интеграции → Создать интеграцию
  2. Выберите тип Private app (для приватного доступа) или OAuth2 (для публичной интеграции)
  3. Скопируйте Client ID и Client Secret
  4. Укажите Redirect URI: https://yourdomain.com/admin/crm/callback
  5. Выберите права доступа (scope): crm (контакты, сделки, задачи)

Шаг 2: Подключение в админ-панели

  1. Вставьте Subdomain, Client ID и Client Secret в форму
  2. Нажмите "Получить URL авторизации" → появится кнопка "Подключить amoCRM"
  3. Кликните на кнопку → откроется popup с OAuth-страницей amoCRM
  4. Подтвердите доступ в amoCRM
  5. После редиректа система получит токены и отобразит статус "Подключено"

Приватные интеграции (без OAuth redirect)

Для приватных интеграций amoCRM код авторизации (authorization_code) получается из настроек интеграции:

  1. Создайте приватную интеграцию в amoCRM
  2. Скопируйте код авторизации из раздела "Получить код"
  3. Используйте этот код вместо OAuth2 flow
  4. Система автоматически обменяет код на токены

Account Info (Информация об аккаунте)

После подключения отображается:

Поле Описание
Account Name Название аккаунта
Subdomain Поддомен
User Name Имя пользователя
User Email Email пользователя

Sync Controls (Управление синхронизацией)

Кнопка Действие
Синхронизировать Запустить синхронизацию контактов/лидов
Отключить Отозвать токены OAuth2

Sync Log (Лог синхронизации)

История операций синхронизации:

Поле Описание
Timestamp Дата и время
Operation Тип операции (sync_contacts, sync_leads, create_lead)
Status Статус (success, error)
Details Детали (количество синхронизированных записей, ошибки)

Статистика

Метрика Описание
Contacts Count Количество контактов в amoCRM
Leads Count Количество сделок
Last Sync Time Время последней успешной синхронизации

CRM Dataset (База знаний из amoCRM)

Синхронизация данных amoCRM в RAG-коллекцию для поиска по сделкам через LLM. Позволяет ИИ-секретарю отвечать на вопросы про конкретные сделки, клиентов, суммы и менеджеров.

Как работает

  1. Сбор данныхPOST /admin/crm/dataset-sync загружает все воронки, сделки, контакты и пользователей из amoCRM
  2. Обогащение — контакты дополняются телефонами/email (get_contacts_by_ids()), ответственные менеджеры резолвятся в имена (get_users())
  3. Генерация markdown — по каждой воронке создаётся crm-pipeline-{id}.md + общая сводка crm-summary.md
  4. Индексация — файлы пишутся в data/crm-dataset/, создаётся коллекция "amoCRM", BM25/embedding индекс обновляется

Содержимое документов

Каждая сделка записывается как секция с полными данными для поиска:

## Сделка #8847291: Разработка сайта — Переговоры (100 000 ₽)
- **ID сделки**: 8847291
- **Сумма**: 100 000 ₽ (100000)
- **Контакт**: Иванов Пётр | тел: +7 (999) 123-45-67 (79991234567) | email: ivanov@mail.ru
- **Тип продукта**: Корпоративный сайт
- **Источник**: Яндекс Директ
- **Теги**: VIP, срочно
- **Создана**: 15.01.2026
- **Обновлена**: 18.02.2026
- **Ответственный**: Алексей Сидоров

Оптимизация для поиска (BM25)

Проблема Решение
Форматированная цена 100 000 не матчит запрос 100000 Цена дублируется: 100 000 ₽ (100000)
Телефон +7 (999) 123-45-67 фрагментируется токенизатором Digits-only дубль: (79991234567)
responsible_user_id — числовой ID Резолвится в имя менеджера через get_users()
Кастомные поля (товар, источник) игнорировались custom_fields_values извлекаются в markdown
ID сделки не записывался Заголовок ## Сделка #8847291: + строка **ID сделки**

API эндпоинты

Метод Endpoint Описание
POST /admin/crm/dataset-sync Полная синхронизация: воронки + сделки + контакты + пользователи → markdown → RAG
GET /admin/crm/dataset-status Статус: количество документов, секций, дата последней синхронизации
DELETE /admin/crm/dataset Очистить датасет (только admin)

Ответ dataset-sync

{
  "status": "ok",
  "pipelines": 3,
  "leads_total": 150,
  "contacts_enriched": 89,
  "users_resolved": 5,
  "files_written": 4,
  "files_removed": 4,
  "collection_id": 2,
  "synced_at": "19.02.2026 14:30"
}

Архитектура

app/routers/amocrm.py (crm_dataset_sync)
  ├── get_pipelines()           → воронки + статусы
  ├── get_all_leads_paginated() → все сделки (250/стр)
  ├── get_contacts_by_ids()     → контакты с телефонами/email (50/батч)
  ├── get_users()               → пользователи → {id: имя}
  └── app/services/crm_dataset_service.py
        ├── build_pipeline_document()  → markdown по воронке
        └── build_summary_document()   → сводка + статистика

Файлы: data/crm-dataset/crm-pipeline-*.md, data/crm-dataset/crm-summary.md Коллекция: slug amocrm, base_dir="data/crm-dataset"


Docker/VPN Proxy

Если Docker-контейнер не может напрямую достучаться до amoCRM (например, VPN настроен на хосте), используйте прокси:

Шаг 1: Запустить прокси на хосте

python scripts/amocrm_proxy.py

Прокси запустится на порту 8888 и пробросит запросы к amoCRM.

Шаг 2: Указать прокси в Docker

Добавьте в .env:

AMOCRM_PROXY=http://172.17.0.1:8888

Где 172.17.0.1 — IP хост-машины из Docker-сети.

Webhook

Endpoint для получения событий от amoCRM: POST /webhooks/amocrm

Настройка вебхука в amoCRM

  1. Перейдите в Настройки → Интеграции → Вебхуки
  2. Укажите URL: https://yourdomain.com/webhooks/amocrm
  3. Выберите события: add, update, delete для контактов, сделок, задач
  4. Сохраните

Обрабатываемые события

Событие Описание
contacts:add Создан контакт
contacts:update Обновлён контакт
leads:add Создана сделка
leads:update Обновлена сделка
leads:status Изменён статус сделки
tasks:add Создана задача
tasks:update Обновлена задача

Автоматическое создание лидов

Если включено Auto Create Lead, при первом обращении пользователя из Telegram бота или виджета:

  1. Система проверяет, существует ли контакт с этим Telegram User ID
  2. Если нет — создаёт контакт
  3. Создаёт сделку (лид) в указанной воронке (Lead Pipeline ID) и статусе (Lead Status ID)
  4. Связывает сделку с контактом

API эндпоинты

Метод Endpoint Описание
GET /admin/crm/config Получить конфигурацию CRM
PUT /admin/crm/config Обновить конфигурацию
GET /admin/crm/auth-url Получить URL для OAuth2 авторизации
GET /admin/crm/callback OAuth2 callback (обмен кода на токены)
GET /admin/crm/status Статус подключения (токены активны?)
POST /admin/crm/disconnect Отключить amoCRM (отозвать токены)
GET /admin/crm/contacts Список контактов из amoCRM
GET /admin/crm/leads Список сделок из amoCRM
GET /admin/crm/pipelines Список воронок и статусов
POST /admin/crm/sync Запустить синхронизацию
GET /admin/crm/sync/log Получить лог синхронизации
GET /admin/crm/account Информация об аккаунте
GET /admin/crm/leads/{id} Детали сделки
PATCH /admin/crm/leads/{id} Обновить сделку (статус, pipeline)
GET /admin/crm/leads/by-pipeline/{pipeline_id} Сделки по воронке (для Kanban)
GET /admin/crm/events Лента событий
GET /admin/crm/contacts/{id}/chats Чаты контакта (Amojo)
GET /admin/crm/chats/{chat_id}/history История сообщений чата (Amojo)
POST /admin/crm/chats/{chat_id}/messages Отправить сообщение в чат (Amojo)
POST /admin/crm/dataset-sync Синхронизация данных в RAG-коллекцию
GET /admin/crm/dataset-status Статус CRM-датасета
DELETE /admin/crm/dataset Очистить CRM-датасет (admin only)

Пример запроса: Получить контакты

GET /admin/crm/contacts?limit=50&offset=0
Authorization: Bearer <jwt_token>

Пример ответа: Список контактов

{
  "_embedded": {
    "contacts": [
      {
        "id": 12345,
        "name": "Иван Иванов",
        "custom_fields_values": [
          {
            "field_code": "PHONE",
            "values": [{"value": "+79001234567"}]
          },
          {
            "field_code": "EMAIL",
            "values": [{"value": "ivan@example.com"}]
          }
        ]
      }
    ]
  }
}

Error Handling (Обработка ошибок)

AmoCRMAPIError

Общая ошибка API amoCRM:

raise AmoCRMAPIError(f"API error: {response.status_code}")

AmoCRMTokenExpired

Истёк токен доступа (401):

raise AmoCRMTokenExpired("Access token expired")

Система автоматически обновляет токен при получении 401 и повторяет запрос.

Rate Limiting (429)

При превышении лимита запросов (429 Too Many Requests):

  • Максимум повторов: 3 (MAX_429_RETRIES)
  • Задержка между повторами: 1.5 секунды (RETRY_DELAY_SECONDS)
  • Система использует экспоненциальный backoff
if response.status_code == 429 and retry_count < MAX_429_RETRIES:
    await asyncio.sleep(RETRY_DELAY_SECONDS * (retry_count + 1))
    return await self._request(...)

Redis-кэширование API

Для снижения нагрузки на amoCRM API (лимит ~7 req/s) используется Redis-кэширование ответов. Kanban-доска обновляется каждые 30 секунд, при этом каждый рефреш вызывает GET /leads/by-pipeline/{id}, который пагинирует все сделки (250/стр). Без кэша это создаёт большое количество запросов к amoCRM.

Кэшируемые эндпоинты

Эндпоинт TTL Ключ кэша
GET /pipelines 300с (5 мин) amocrm:pipelines:{subdomain}
GET /leads/by-pipeline/{id} 60с (1 мин) amocrm:pipeline_leads:{subdomain}:{pipeline_id}
GET /leads/unsorted 30с amocrm:unsorted:{subdomain}:{page}:{limit}
GET /leads/{id} 120с (2 мин) amocrm:lead:{subdomain}:{lead_id}

GET /leads (поиск с query) не кэшируется — динамические запросы, низкая частота.

Инвалидация кэша

Кэш автоматически сбрасывается при мутациях:

Мутация Что инвалидируется
PATCH /leads/{id} amocrm:lead:*:{lead_id} + amocrm:pipeline_leads:* + amocrm:unsorted:*
POST /leads amocrm:pipeline_leads:* + amocrm:unsorted:*
POST /webhooks/amocrm (events: leads[*]) amocrm:pipeline_leads:* + amocrm:unsorted:* + amocrm:lead:*
POST /disconnect amocrm:* (все ключи)

Graceful fallback

Redis опционален. Если Redis недоступен — все эндпоинты работают напрямую с amoCRM API, как и до внедрения кэша. При восстановлении Redis кэширование возобновляется автоматически.

Проверка

# Посмотреть ключи кэша
redis-cli keys "amocrm:*"

# Очистить весь CRM-кэш
redis-cli keys "amocrm:*" | xargs redis-cli del

Архитектура сервиса

app/routers/amocrm.py
  → app/services/amocrm_service.py (AmoCRMService)
    → AmoCRMService (modules/crm/service.py)
      → AmoCRMRepository (db/repositories/amocrm.py)
        → AmoCRMConfig, AmoCRMToken (modules/crm/models.py)

AmoCRMService

Чистый async HTTP-клиент без прямого DB-доступа. Методы:

  • get_contacts(limit, offset) — список контактов
  • get_contacts_by_ids(contact_ids, batch_size=50) — батч-запрос контактов по ID с телефонами/email
  • get_leads(limit, offset) — список сделок
  • get_all_leads_paginated() — все сделки (250/стр, пагинация до конца)
  • get_users() — все пользователи аккаунта → {id: имя}
  • create_lead(name, price, pipeline_id, status_id, contact_id) — создать сделку
  • get_pipelines() — список воронок и статусов
  • refresh_tokens() — обновить токены при 401
  • exchange_code(code) — обмен authorization_code на токены

AmoCRMService

Доменный сервис в modules/crm/service.py (ранее AsyncAmoCRMManager в db/integration.py) для работы с конфигурацией и токенами в БД:

  • get_config() — получить конфигурацию
  • save_config(config) — сохранить конфигурацию
  • get_tokens() — получить токены
  • save_tokens(tokens) — сохранить токены
  • delete_tokens() — удалить токены (отключение)

Proxy Support

Если установлена переменная окружения AMOCRM_PROXY, все запросы проксируются:

proxies = {"http://": os.getenv("AMOCRM_PROXY"), "https://": os.getenv("AMOCRM_PROXY")}
async with httpx.AsyncClient(proxies=proxies) as client:
    response = await client.get(url)

RBAC

  • Admin — полный доступ к CRM-интеграции
  • User — только своя конфигурация (по owner_id)
  • Guest — только чтение

Sales | Usage

Clone this wiki locally