Модернизация CRM

Замена критичной для бизнеса 1С-CRM люксового ювелирного бренда на модульный монолит на Django. Самым сложным была не разработка, а нормализация и миграция грязных данных из старой системы.

Подключил телефонию через АТС MangoНастроил интеграцию с Яндекс.Метрикой для анализа спамаУспешно мигрировал данные из легаси

Контекст

Разрабатывал корпоративную CRM-систему, которая заменила легаси-решение на базе 1С.

Система автоматизировала работу отдела продаж и являлась бизнес-критичной: любые ошибки в данных или простои напрямую влияли на операционную деятельность компании.

Помимо разработки новой функциональности, участвовал в проектировании архитектуры, миграции данных, внедрении системы и сопровождении после запуска.

Изначальный каркас фронтенда создал фронтенд-разработчик, но дальше полностью дорабатываю и развиваю его самостоятельно.

Моя роль в проекте

Выступал как основной backend-разработчик и автор архитектурных решений со стороны разработки. Отвечал не только за реализацию отдельных задач, но и за проработку бизнес-логики, интеграций, миграции данных и дальнейшее развитие системы.

Работал напрямую с представителями бизнеса: руководителем отдела маркетинга и пользователями CRM. Совместно уточняли, какие проблемы нужно решить, как менеджеры работают с клиентами на практике и какие сценарии должны быть удобными в новой системе. Я не только реализовывал поставленные задачи, но и предлагал изменения и улучшения, если видел более эффективный вариант решения.

Например, при работе с интеграцией Яндекс.Метрики уточнял бизнес-цели использования данных: какие показатели действительно важны для маркетинга, как отличать качественные обращения от спама и какие данные помогут контролировать корректность работы менеджеров.

В команде также участвовали:

Frontend-разработчик, который создал первоначальный каркас интерфейса. После этого я самостоятельно развивал frontend-часть: добавлял новые сценарии, дорабатывал существующие экраны и при необходимости консультировался по архитектурным решениям.

Команда 1С, которая помогала с пониманием старой CRM и подготовкой данных для миграции. Они предоставляли техническую информацию о структуре легаси-системы, помогали разобраться с особенностями хранения данных и доработали выгрузку, добавив необходимые идентификаторы для корректного переноса.

Таким образом, моя зона ответственности находилась на стыке разработки и продукта: от проектирования архитектуры и написания кода до выяснения требований, поиска решений вместе с бизнесом и обеспечения корректного перехода со старой системы.

Архитектура

С самого начала было понятно, что микросервисная архитектура для этого проекта была бы избыточной. Система не предполагала высокой нагрузки, в ней работала небольшая команда, а требования не диктовали необходимость независимого масштабирования или отдельных циклов развёртывания сервисов. Поэтому был выбран модульный монолит на Django: одна кодовая база, одна база MySQL, а бизнес-логика разделена по приложениям — CRM, авторизация, интеграции и другим модулям.

Фоновые задачи вынесены в Celery, потому что там быстро начали появляться интеграции, отложенные операции, синки с 1С, уведомления и прочие фоновые процессы. Плюс был слой WebSocket для работы в реальном времени. Redis использовался как брокер очередей и как слой для WebSocket.

Если упрощать, CRM состоит из трёх больших блоков:

  • Клиенты и продажи — клиенты, лиды, взаимодействия, статусы, товары, сделки.
  • Коммуникации — звонки через Mango, уведомления в Telegram и Max, WebSocket-события.
  • Интеграции — 1С, сайт, Яндекс.Метрика.

Важный момент: 1С использовалась только как источник товаров. Всё, что касается клиентов и продаж, жило в CRM. Это было принципиально — чтобы не получить двусторонний хаос в данных.

Потоки данных

Данные приходили по трём основным каналам:

Сайт      →  API  →  Celery  →  создание клиента + лида (автоматически)
Менеджер  →  ручное создание лида в системе
Mango     →  вебхук звонка  →  автосоздание/привязка интереса по правилам владения

Ключевое: лиды приходят «ничейными» — они не привязаны к менеджеру в момент создания, а попадают в общий пул и оттуда распределяются. Это было наиболее подходящим решением, потому что создание заявки и назначение ответственного — разные бизнес-события.

При прямом назначении система должна сразу принять решение, кому отдать заявку, но формально свободный менеджер не всегда означает, что он действительно готов принять новую нагрузку. Например, обращение может прийти ночью, вне рабочего времени или в момент, когда менеджер уже занят обработкой предыдущего клиента.

В модели с общим пулом распределение выполняется отдельно и может учитывать текущую доступность менеджеров, их загрузку, правила очередности и другие бизнес-условия. Это также позволяет изменять алгоритм распределения без изменения процесса создания лидов.

Дополнительно такой подход упрощает контроль и аналитику: можно отдельно видеть факт поступления обращения, время ожидания назначения и результат распределения, а не смешивать эти этапы в одной операции.

Конкурентная обработка данных

Одной из наиболее сложных задач стала реализация операций, которые одновременно выполнялись несколькими пользователями и фоновыми процессами.

Для предотвращения гонок данных активно использовались:

  • transaction.atomic;
  • блокировки строк (select_for_update);
  • transaction.on_commit.

Это позволило гарантировать корректность бизнес-инвариантов даже при высокой степени конкурентного доступа.

Телефония (Mango Office, ВАТС/VPBX)

Двусторонняя интеграция с телефонией — одна из самых проработанных подсистем.

  • Исходящие — click-to-call. Менеджер жмёт «Позвонить» в карточке клиента: АТС сначала дозванивается самому менеджеру, он берёт трубку — и только потом соединяется с клиентом. Звонок сразу фиксируется в CRM и попадает в историю клиента.
  • Входящие — вебхуки. Mango шлёт события о звонках, из которых система собирает запись разговора, статус и привязку к клиенту.
  • «Звонит твой клиент — звонит тебе». На входящем звонке система по номеру находит клиента и, если он закреплён за менеджером, автоматически переводит звонок именно на него.
  • Автосоздание обращения из звонка. Входящий звонок превращается в интерес по понятным правилам владения (закреплён за менеджером → на него; никто не взял → в общую очередь распределения). Неизвестный номер заводит нового потенциального клиента.
  • Записи разговоров привязываются к клиенту и обращению и доступны прямо в хронологии.

Отдельная сложность — гонки: события по одному звонку приходят несколько раз, поэтому перевод и привязка к обращению защищены так, чтобы на один звонок не создавалось два интереса и звонок не переводился дважды.

Уведомления

Отдельный контур оповещений через Telegram и Max: система реагирует на бизнес-события (новые обращения, значимые изменения по клиентам/сделкам) и доставляет уведомления менеджерам.

Отправка Telegram-уведомлений была реализована через прокси в связи с блокировками.

Вебсокет

Слой WebSocket (daphne + Redis) обеспечивал работу в реальном времени: события распределения и появления новых обращений, уведомления по звонкам Mango, изменения по клиентам и статусам доходили до интерфейса без перезагрузки страницы. Для работы в общем пуле лидов это важно — менеджер видит актуальное состояние, а не устаревший снимок.

Интеграции

  • 1С — только источник товаров, односторонний поток. Всё про клиентов и продажи живёт в CRM. Это осознанно исключало двусторонний хаос данных.
  • Сайт — заявки с сайта приходят по API и через Celery автоматически создают клиента и лида.
  • Яндекс.Метрика — использовалась не для «веб-аналитики ради аналитики», а как сигнал о поведении клиента, в первую очередь для выявления спама. Это решало сразу две задачи:
    • Маркетинговая статистика. Отдел маркетинга получал данные о качестве трафика и источников — какие каналы приводят реальных клиентов, а какие спам.
    • Контроль злоупотреблений менеджеров. У менеджеров была мотивация ставить статус «Спам» клиентам, которым просто не смогли продать, — чтобы не портить себе статистику продаж. Данные Метрики позволяли увидеть реальную картину поведения клиента и поймать такое злоупотребление.

Второй пункт — хороший пример того, как интеграция из «аналитики» превратилась в инструмент контроля данных и честности статистики.

Проблема: данные, а не код

Самым сложным была не разработка, а миграция данных из старой CRM. Данные — это самая критичная часть бизнеса, а цена ошибки при их потере или повреждении чрезвычайно высока, поэтому на мне была большая ответственность.

В старой 1С-системе была довольно проблемная модель:

  • Клиент мог принадлежать нескольким менеджерам, при этом дубли клиентов создавались постоянно и никак не контролировались — хотя изначальное бизнес-правило было обратным: у клиента должен быть один ответственный менеджер.
  • Сама модель клиентов была раздвоена: потенциальный и постоянный клиент были разными сущностями. Потенциальный становился постоянным только после первой покупки, и при этом данные не «схлопывались», а продолжали жить параллельно — это создавало дубли.
  • При выгрузке не было нормальных идентификаторов связей: «интересы» или «взаимодействия» нельзя было однозначно привязать к клиенту или менеджеру.

В какой-то момент корректно собрать модель уже не получалось, и началось обсуждение с 1С-разработчиками. Мы пришли к тому, что без уникальных идентификаторов на уровне сущностей нормальную миграцию сделать невозможно. Договорились так: они добавляют уникальные ID и нормальную структуру связей, а нормализация и корректное объединение постоянных и потенциальных клиентов — на моей стороне.

Миграция данных

Даже после этого данные оставались грязными. Например, один и тот же клиент мог быть задублирован несколько раз и при этом иметь разных менеджеров.

Решение — через бизнес-правило: «последняя продажа определяет основного менеджера». Оно было согласовано с руководством, потому что идеальной истины в данных уже не существовало — её пришлось восстановить из поведения системы.

Отдельная сложность — раздвоенная модель клиента: часть обращений висела на потенциальной сущности, а часть — на постоянной, но по факту это был один и тот же человек. Здесь правило такое: если у клиента есть постоянная модель, все интересы потенциальной переносятся на неё. А если дублировалась сама постоянная модель, оставалась только одна сущность — по дате последнего обращения.

Внедрение

  • Сначала менеджерам выдали тестовую версию, чтобы они просто попробовали систему и дали обратную связь.
  • Затем — обучающий вебинар и видео-инструкции, без которых переход был бы очень болезненным.
  • После запуска старая CRM ещё какое-то время оставалась как резервная система, но основная работа уже шла в новой.

Ключевые архитектурные решения

  1. Транзакционность. Активно использовались atomic, блокировки и on_commit, потому что в системе было много гонок, особенно вокруг распределения лидов и обработки событий телефонии.
  2. Celery как ядро асинхронности. Центральный механизм всей асинхронной логики — от лидов до интеграций.
  3. Сознательный отказ от микросервисов. Домен сильно связанный; скорость разработки и изменения системы были важнее распределённой архитектуры.
  4. Ничейные лиды + слотовое распределение. Принадлежность клиента определяется правилами распределения, а не тем, кто первый успел, — это снижало «перехваты» и перекосы.
  5. Разделение HTTP и WebSocket. Синхронный трафик — через gunicorn (WSGI), real-time — через daphne (ASGI) отдельным сервисом.

Права доступа

Была строгая ролевая модель:

  • Менеджер — видит только своих клиентов и сделки, а также клиентов своего салона; не видит телефоны и детали чужих клиентов и не может с ними взаимодействовать.
  • Управляющий салоном — видит всё внутри салона, управляет всеми клиентами и сделками своего подразделения.
  • Управляющий отделением — видит всё внутри региона, управляет всеми салонами.
  • Администратор — полный доступ ко всей системе, может действовать от имени пользователя через impersonation.

Поскольку наша CRM — это закрытая система только для нашей компании, админ мог по нажатию кнопки залогиниться любым пользователем. Это очень удобно для отладки: админ мог работать «глазами пользователя», при этом все действия логировались как «админ под пользователем», что давало полный аудит.

Инфраструктура и деплой

Когда стало понятно, что проекту потребуются Redis и Celery, было принято решение использовать Docker. Без использования контейнеризации настройка и совместный запуск компонентов потребовали бы значительно больше времени и усилий, особенно при переносе проекта между различными окружениями. Он позволил быстро развернуть и объединить все компоненты системы в единую инфраструктуру.

Всё разворачивалось через Docker Compose — пять сервисов, каждый со своей ролью:

  • db — база MySQL, единый источник данных.
  • redis — брокер очередей Celery, слой для WebSocket и хранилище служебных ключей для маршрутизации звонков.
  • web — само Django-приложение, обрабатывает обычные HTTP-запросы.
  • celery — фоновый воркер: периодические задачи, таймауты офферов, распределение лидов.
  • ws — отдельный сервис под WebSocket, отвечает за real-time (например, телефонию).

Тестирование

Тесты писались в первую очередь на самую рискованную часть — конкурентное распределение лидов, где цена ошибки максимальна:

  • работу со слотами: поиск, назначение лида менеджеру, откат при неудаче;
  • механизм офферов: таймауты и приём лида;
  • изоляцию чужих данных — менеджер не должен видеть чужих клиентов;
  • конкурентную выдачу номеров под реальными транзакциями (гонки на счётчике);
  • ключевые бизнес-инварианты — например, продажа не может быть оформлена без суммы.

Тесты запускались и без поднятой инфраструктуры (in-memory база, синхронный Celery), чтобы быстро прогонять именно логику, а не окружение.

Эксплуатация и мониторинг

Система работала в закрытой инфраструктуре, доступ был только через прокси, поэтому часть проблем решалась совместно с системным администратором.

Мониторинг и логирование включали:

  • подробное логирование всех действий в CRM;
  • аудит клиентов, лидов, взаимодействий;
  • хранение логов около 7 дней;
  • стек мониторинга Prometheus / Grafana / Loki.

На практике основным инструментом диагностики часто были именно бизнес-логи — они показывали контекст действий.

Фактически на связи при инцидентах был всегда: подключался к проблемам, разбирал причины и выкатывал фиксы; системный администратор помогал с инфраструктурой (сеть, прокси, сервер).

Выводы

  • Самая дорогая часть переезда с легаси — не переписывание кода, а восстановление смысла в данных. Без уникальных идентификаторов на стороне источника миграция нормализуется только «по поведению системы».
  • Для сильно связанного домена модульный монолит оказался правильным выбором: скорость изменений важнее распределённости.
  • Конкурентную бизнес-логику (распределение лидов, слоты, обработку событий телефонии) нельзя писать без транзакционных гарантий — гонки здесь стоят двойных назначений и потерянных обращений.
← все кейсы