|
|
|
Professor Seleznov
|
На первый взгляд миграция из 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.json mappings/space_mapping.json mappings/card_mapping.json mappings/custom_fields_mapping.json mappings/custom_properties_cache.json mappings/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», а как к памяти организации. Именно её и нужно переносить. Полезные ссылки
-Источник
|
|
|
|