На первый взгляд миграция из 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. Файлы — это не только вложения
Файлы в карточках оказались отдельной проблемой.
Наивная логика выглядит так:
-
Получить список файлов карточки.
-
Скачать файл из Kaiten.
-
Загрузить файл в Bitrix24.
-
Прикрепить к задаче.
Но в реальной карточке файлы могут быть не только во вложениях. Они могут быть ссылками внутри описания:
Смотрите документ: [ТЗ.pdf](https://.../files/...)
Если просто загрузить файл в Bitrix24, ссылка в описании всё равно будет вести в Kaiten. После отключения Kaiten или ограничения доступа такая ссылка станет бесполезной.
Поэтому пришлось делать обработку описания:
-
Найти Markdown-ссылки на файлы Kaiten.
-
Скачать исходный файл.
-
Загрузить его в Bitrix24.
-
Получить новую ссылку.
-
Заменить старую ссылку в описании задачи.
Упрощённый фрагмент:
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 методов. В облачной версии все пользовательские поля придется создать заранее.
Логика двухэтапная:
-
Локально получить поля Kaiten и их значения.
-
На сервере 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://github.com/vlikhobabin/kaiten-to-bitrix
-
Документация Kaiten API: https://developers.kaiten.ru/
-
Документация Bitrix24 REST API: https://apidocs.bitrix24.com/
ссылка на оригинал статьи https://habr.com/ru/articles/1033372/