Kaiten → коробочный Bitrix24: как мы переносили не задачи, а память команды

от автора

На первый взгляд миграция из Kaiten в Bitrix24 выглядит как обычная интеграционная задача: прочитать данные из одного REST API и записать в другой REST API.

Но это впечатление быстро проходит, когда начинаешь переносить не демо-доску, а живую проектную систему.

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

В нашем случае нужно было перенести данные из облачного Kaiten в коробочный Bitrix24 так, чтобы команда смогла продолжить работу уже в новом контуре: с группами, задачами, файлами, комментариями, правами доступа и понятной структурой.

В этой статье расскажу, как мы построили мигратор на Python, где помог асинхронный подход, почему маппинг ID оказался центральной частью архитектуры, какие ограничения обнаружились в коробочном Bitrix24 и почему часть задач пришлось решать не только через REST API, но и через отдельные серверные скрипты.

Репозиторий с кодом: https://github.com/vlikhobabin/kaiten-to-bitrix

Контекст: нужно было перенести не доску, а рабочую историю

В нашей компании большая часть операционной работы велась в Kaiten. Там были пространства, карточки, файлы, обсуждения, пользовательские поля, связи между задачами и привычная структура доступа.

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

На уровне бизнеса задача звучала просто:

перенести все проектные данные из Kaiten в Bitrix24.

На уровне реализации это быстро превратилось в другой список:

  • перенести пользователей и сопоставить их по email;

  • перенести пространства Kaiten в группы Bitrix24;

  • сохранить участников и права доступа;

  • перенести карточки в задачи;

  • разложить задачи по стадиям;

  • перенести комментарии;

  • сохранить исторические даты комментариев;

  • скачать файлы из Kaiten;

  • загрузить файлы в Bitrix24;

  • заменить ссылки в описаниях карточек;

  • обработать пользовательские поля;

  • учесть архивные и завершённые карточки;

  • перенести связи Parent/Child и связанные карточки;

  • сделать так, чтобы миграцию можно было повторять, тестировать и докатывать частями.

В этот момент становится понятно: это не «экспорт-импорт». Это восстановление рабочей памяти команды в другой системе.

Что именно переносили

В итоговой версии мигратор поддерживал несколько основных типов объектов.

Kaiten                         Bitrix24------                         --------Пользователи              →    ПользователиПространства              →    Группы / проектыКарточки                  →    ЗадачиКомментарии               →    Комментарии / сообщения задачиФайлы                     →    Файлы Bitrix24.ДискПользовательские поля     →    UF-поля и/или блок в описанииParent/Child связи        →    Подзадачи / зависимости / связанные задачиГруппы доступа Kaiten     →    Участники групп Bitrix24

Сразу оговорюсь: это не универсальный коробочный продукт «нажмите кнопку и мигрируйте любой Kaiten в любой Bitrix24». Это миграционный инструмент, написанный под реальный переход, с учётом конкретной структуры данных, конкретного Bitrix24 и конкретных компромиссов.

Но именно поэтому в нём много практических деталей, которые редко видны в красивых интеграционных схемах.

Архитектура мигратора

Проект получился достаточно простым по идее, но модульным по устройству.

kaiten-to-bitrix/├── config/          # настройки и конфигурация├── connectors/      # API-клиенты Kaiten и Bitrix24├── migrators/       # миграторы по типам объектов├── models/          # Pydantic-модели данных├── transformers/    # преобразование форматов├── utils/           # вспомогательные модули├── scripts/         # сценарии запуска миграции│   └── vps/         # серверные скрипты для коробочного Bitrix24├── mappings/        # соответствия ID, создаются во время миграции└── logs/            # журналы выполнения

Стек достаточно обычный:

  • Python;

  • httpx для HTTP-запросов;

  • asyncio для параллельной обработки;

  • pydantic / pydantic-settings для моделей и настроек;

  • .env для токенов и параметров подключения;

  • отдельные Python-скрипты для операций на сервере Bitrix24;

Ключевая идея была в разделении ответственности:

Connector     → знает, как говорить с API конкретной системыModel         → описывает структуру данныхTransformer   → преобразует Kaiten-объект в Bitrix24-форматMigrator      → управляет процессом переноса конкретного типа данныхScript        → даёт понятную точку запускаMapping       → хранит соответствие старых и новых ID

Такой подход оказался особенно важен из-за зависимостей между объектами. Нельзя нормально перенести карточку, пока не понятно, в какую группу Bitrix24 она должна попасть. Нельзя корректно перенести комментарий, пока не создана задача. Нельзя восстановить связи между карточками, пока не создан маппинг карточек.

Главный артефакт миграции — не задача, а mapping

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

В нашем случае это были JSON-маппинги:

mappings/user_mapping.jsonmappings/space_mapping.jsonmappings/card_mapping.jsonmappings/custom_fields_mapping.jsonmappings/custom_properties_cache.jsonmappings/groups_cache.json

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

  • не создавать дубли;

  • докатывать миграцию частями;

  • повторно запускать обработку конкретного пространства;

  • восстанавливать связи между карточками после массового переноса;

  • переносить комментарии и файлы уже к существующим задачам;

  • анализировать ошибки после выполнения;

  • делать инкрементальную миграцию.

На практике маппинг — это не техническая мелочь, а позвоночник всего процесса.

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

Пространства Kaiten → группы Bitrix24

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

Мы приняли решение переносить не каждую доску, а именно пространства.

Логика была примерно такой:

  • конечные пространства без дочерних пространств превращались в группы Bitrix24;

  • пространства второго уровня тоже могли превращаться в группы;

  • технические или ненужные пространства исключались через конфигурацию;

  • участники пространства переносились в участники группы;

  • дополнительно учитывались пользователи через группы доступа Kaiten;

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

Именно здесь обнаружился первый важный организационный момент: структура Kaiten и структура Bitrix24 не совпадают один к одному.

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

В нашем случае пространство Kaiten становилось группой Bitrix24, а карточки внутри пространства — задачами этой группы.

Карточки Kaiten → задачи Bitrix24

Карточки — самая объёмная и самая «грязная» часть миграции. Для карточек нужно было решить сразу несколько задач.

Во-первых, определить, в какую группу Bitrix24 попадёт задача. Для этого использовался space_mapping.json. Во-вторых, сопоставить стадии. В Kaiten у колонок есть типы. В нашем сценарии они маппились так:

type: 1              → стадия «Новые»type: 2 и другие     → стадия «Выполняются»type: 3              → по умолчанию пропускаемархивные карточки    → по умолчанию пропускаем

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

В-третьих, нужно было перенести не только заголовок и описание, но и всё окружение карточки:

  • автора;

  • ответственного;

  • участников;

  • сроки;

  • описание;

  • комментарии;

  • файлы;

  • пользовательские поля;

  • связи с другими карточками;

  • информацию об исходном пространстве.

И вот здесь обычная миграция превращается в серию маленьких компромиссов.

Грабля 1. Производительность API и контроль конкурентности

Если переносить карточки последовательно, миграция получается слишком медленной. Особенно когда у карточек есть файлы, комментарии и пользовательские поля. Но если просто запустить сотни запросов параллельно, можно получить другую проблему: таймауты, ошибки API, нестабильные ответы, превышение лимитов и сложную отладку. Поэтому мы использовали асинхронный подход с ограничением конкурентности.

Упрощённо логика выглядела так:
semaphore = asyncio.Semaphore(10)async def process_card(card):    async with semaphore:        for attempt in range(3):            try:                return await migrate_single_card(card)            except Exception:                if attempt == 2:                    raise                await asyncio.sleep(1)await asyncio.gather(*(process_card(card) for card in cards))

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

В нашем проекте ориентировочные показатели были такими:

Тип данных

Количество

Ориентировочное время

Пользователи

89

около 25 секунд

Пространства

34

около 3 минут

Карточки

около 1200

около 30 минут

Файлы

около 450

около 15 минут

Комментарии

около 3800

около 20 минут

Это не бенчмарк библиотеки. Это практический замер на конкретной миграции.

Грабля 2. Файлы — это не только вложения

Файлы в карточках оказались отдельной проблемой.

Наивная логика выглядит так:

  1. Получить список файлов карточки.

  2. Скачать файл из Kaiten.

  3. Загрузить файл в Bitrix24.

  4. Прикрепить к задаче.

Но в реальной карточке файлы могут быть не только во вложениях. Они могут быть ссылками внутри описания:

Смотрите документ: [ТЗ.pdf](https://.../files/...)

Если просто загрузить файл в Bitrix24, ссылка в описании всё равно будет вести в Kaiten. После отключения Kaiten или ограничения доступа такая ссылка станет бесполезной.

Поэтому пришлось делать обработку описания:

  1. Найти Markdown-ссылки на файлы Kaiten.

  2. Скачать исходный файл.

  3. Загрузить его в Bitrix24.

  4. Получить новую ссылку.

  5. Заменить старую ссылку в описании задачи.

Упрощённый фрагмент:
file_pattern = r'\[([^\]]+)\]\((https?://[^)]+/files/[^)]+)\)'for match in re.finditer(file_pattern, description):    file_name = match.group(1)    old_url = match.group(2)    content = await kaiten.download_file(old_url)    bitrix_file_id = await bitrix.upload_task_file(task_id, file_name, content)    new_url = await bitrix.get_file_url(bitrix_file_id)    description = description.replace(match.group(0), f'[{file_name}]({new_url})')

Именно такие детали отличают «перенесли данные» от «пользователи действительно могут продолжить работу».

Грабля 3. Пользовательские поля Kaiten богаче, чем кажется

Пользовательские поля — один из самых неприятных участков миграции.

В Kaiten набор типов достаточно широкий: строки, числа, даты, email, телефон, checkbox, select, multi-select, URL, formula, user, attachment, голосования, каталоги и другие варианты.

В Bitrix24 у задач тоже есть пользовательские поля, но модель другая. На момент актуализации статьи официальный REST Bitrix24 умеет создавать пользовательские поля задач через task.item.userfield.add, но поддерживаемые типы для задач ограничены: строка, число, дата/время и boolean.

То есть формально API для пользовательских полей есть. Но для полной миграции Kaiten-полей этого недостаточно.

Например, если в Kaiten есть select или multi_select с набором значений, цветами, сортировкой и исторически накопленными значениями, то простое создание строкового поля в Bitrix24 теряет часть смысла. А если поле вообще специфическое — вроде голосования, каталога или вложения — прямого соответствия может не быть.

В проекте использовались два подхода.

Подход 1. Создавать пользовательские поля в Bitrix24

Для части полей мы пытались создавать соответствующие UF-поля в Bitrix24. В коробочной версии для этого использовались серверные скрипты с прямым доступом к базе, так как у Битрикса для этого нет штатных API методов. В облачной версии все пользовательские поля придется создать заранее.

Логика двухэтапная:

  1. Локально получить поля Kaiten и их значения.

  2. На сервере Bitrix24 создать нужные записи и маппинги.

Это позволяло точнее контролировать создаваемую структуру, но добавляло риск: мы уже не просто используем публичный REST API, а вмешиваемся в внутреннюю структуру коробочного продукта.

Подход 2. Дублировать пользовательские поля в описании задачи

Для сохранения читаемого контекста часть пользовательских полей форматировалась прямо в описание задачи:

<hr><h3>Пользовательские поля</h3><table>  <tr><th>Название</th><th>Значение</th></tr>  <tr><td>Приоритет клиента</td><td>Высокий</td></tr>  <tr><td>Плановая дата</td><td>15.04.2025</td></tr></table>

Это не всегда красиво как модель данных, зато надёжно как миграция смысла.

Пользователь открывает задачу в Bitrix24 и видит важные поля, даже если они не идеально легли в нативную модель Bitrix24.

Сейчас я бы рассматривал такую стратегию как нормальный компромисс:

  • критичные поля переносить в нативные UF-поля;

  • сложные или плохо сопоставимые поля сохранять в описании;

  • обязательно формировать отчёт о том, какие поля были перенесены нативно, а какие — как информационный блок.

Грабля 4. Комментарии и историческая хронология

Перенести комментарии — не значит просто добавить новые сообщения к задаче.

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

В старой логике Bitrix24 для комментариев использовались методы семейства task.commentitem.*. В новых версиях Bitrix24 комментарии в новой карточке задачи переехали в чат задачи, а старые методы для комментариев помечены как deprecated, хотя часть операций ещё может работать в целях совместимости.

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

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

python3 /root/kaiten-vps-scripts/update_comment_dates.py '{"601": "2025-07-08 14:22:00"}'

Скрипт обновлял POST_DATE в таблице b_forum_message.

В облачном Битрикс24, я полагаю, сохранить исходную дату комментария нет никакой технической возможности.

В идеальном мире такие вещи должны делаться только через официальный API. Но миграции в коробочные системы часто живут не в идеальном мире. Там есть исторические данные, внутренние таблицы, отличия версий, ограничения методов и требование бизнеса: «после перехода пользователи должны видеть нормальную историю».

Поэтому главное правило здесь такое: если вы идёте в базу напрямую, это должно быть осознанное миграционное действие, а не постоянная архитектура интеграции.

Грабля 5. Дочерние пространства и связи между карточками

В первом приближении кажется, что можно мигрировать пространство за пространством.

Но в Kaiten структура может быть глубже:

  • есть дочерние пространства;

  • карточки могут жить в подчинённой структуре;

  • доступ может наследоваться или задаваться через группы;

  • карточки могут быть связаны друг с другом;

  • у карточек могут быть родительские и дочерние отношения.

В репозитории для этого появилась отдельная логика:

  • автоматический поиск карточек из дочерних пространств;

  • рекурсивный поиск родительского пространства, для которого уже есть группа Bitrix24;

  • обработка parent_entity_uid, если нет прямого parent_id;

  • сохранение информации об исходном пространстве в описании задачи;

  • перенос Parent/Child отношений;

  • перенос связанных карточек;

  • установка связей уже после создания карточек, когда card_mapping.json заполнен.

Почему связи лучше устанавливать в конце?

Потому что при переносе карточки A связанная карточка B может ещё не существовать в Bitrix24. Если пытаться установить связь сразу, часть связей неизбежно потеряется. Поэтому надёжнее сначала создать все задачи, сохранить mapping, а затем вторым проходом восстановить отношения.

Это хороший пример общей закономерности миграций: некоторые данные нельзя корректно перенести за один проход.

Результаты миграции

В результате удалось перенести рабочие данные из Kaiten в коробочный Bitrix24 с сохранением основной структуры и контекста.

Что получилось хорошо:

  • пользователи сопоставлены по email;

  • пространства перенесены в группы Bitrix24;

  • участники добавлены в группы;

  • карточки перенесены в задачи;

  • активные карточки попали в рабочие стадии;

  • архивные и завершённые можно было переносить отдельным режимом;

  • комментарии перенесены;

  • файлы скачаны из Kaiten и загружены в Bitrix24;

  • ссылки в описаниях заменены;

  • пользовательские поля сохранены как данные и/или как читаемый блок;

  • связи между карточками восстановлены отдельным проходом;

  • миграцию можно было тестировать и запускать частями.

Ориентировочная статистика проекта:

Объект

Объём

Пользователи

89

Пространства

34

Карточки

около 1200

Файлы

около 450

Комментарии

около 3800

Пользовательские поля

десятки полей и значений

Главный результат даже не в том, что объекты появились в Bitrix24. Главный результат — команда не потеряла рабочий контекст.

Что я бы сделал иначе сейчас

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

1. Сначала сделал бы reconciliation report

То есть отдельный отчёт сверки:

В Kaiten найдено пользователей: 100Перенесено в Bitrix24: 89Пропущено: 11Причины: нет email, архивный пользователь, дубльВ Kaiten найдено карточек: 1400Перенесено: 1200Пропущено: 200Причины: архивные, финальная колонка, нет маппинга пространстваФайлов найдено: 470Файлов перенесено: 450Ошибок скачивания: 20

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

2. Жёстче разделил бы «данные» и «историю»

Перенести текущие карточки — одна задача.

Перенести историю обсуждений, даты, авторов, файлы, ссылки и связи — другая.

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

Я бы прямо разделил режимы:

current-only       # только активная рабочая структураwith-history       # комментарии, даты, файлыarchive-full       # полная историческая миграция

3. Перепроверил бы актуальные методы Bitrix24 REST

За время жизни проекта API меняется.

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

Поэтому старое утверждение «это невозможно через API» сегодня лучше заменить на более точное:

часть операций можно делать через официальный REST, но не вся семантика Kaiten-полей и исторических комментариев переносится один к одному. Для конкретной коробочной версии и конкретного набора данных нужно отдельно проверять, какие операции делать через REST, какие через D7/Bitrix Framework, а какие остаются миграционными SQL-операциями.

4. Добавил бы слой D7 вместо части прямого SQL

Для коробочного Bitrix24 прямой SQL работает, но это самый жёсткий вариант.

Более аккуратный путь — писать серверные скрипты не только на уровне SQL, а по возможности использовать внутренние API Bitrix Framework / D7. Это всё равно требует доступа к серверу, но меньше завязывает мигратор на конкретные таблицы.

Прямой SQL я бы оставил как последний уровень: когда REST и D7 не дают нужного результата.

5. Сделал бы мигратор более продуктовым

Сейчас это инженерный инструмент. Для повторного использования я бы добавил:

  • веб-интерфейс настройки миграции;

  • экран предварительного анализа;

  • отчёт расхождений;

  • прогресс по этапам;

  • очередь заданий;

  • безопасный restart;

  • режим rollback для созданных объектов;

  • настройку правил маппинга через UI;

  • экспорт итогового отчёта в PDF/Excel;

  • уведомления в Telegram или Bitrix24.

Но это уже превращает одноразовый мигратор в отдельный продукт.

Выводы

Миграция проектной системы — это не перенос таблицы задач.

Это перенос рабочей памяти команды.

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

REST API решает только часть задачи. Вторая часть — это проектирование соответствий, контроль целостности, повторяемость, проверка результата и честное принятие компромиссов.

В нашем случае Python-мигратор позволил перенести данные из Kaiten в коробочный Bitrix24 и сохранить основной рабочий контекст. Но главный урок был не в выборе httpx, asyncio или Pydantic.

Главный урок такой: если система целый год была местом, где команда думала, спорила, принимала решения и прикладывала файлы, то миграция такой системы должна относиться к этим данным не как к «записям в API», а как к памяти организации.

Именно её и нужно переносить.

Полезные ссылки

ссылка на оригинал статьи https://habr.com/ru/articles/1033372/