Разработка новой CRM часто начинается с фразы:
Старую систему больше невозможно нормально поддерживать. Давайте сделаем новую.
Через некоторое время появляется интерфейс новой CRM, роли сотрудников, карточки клиентов, сделки, документы, статусы и отчёты.
И тут возникает вопрос, который иногда оказывается сложнее значительной части самой разработки:
что делать со всеми данными, накопленными за последние несколько лет?
У компании уже могут существовать десятки Excel-файлов, старая CRM, таблицы менеджеров, экспорт бухгалтерии, папки с договорами и отдельные списки клиентов.
И просто сказать:
Импортируем всё в новую базу,
недостаточно.
Потому что данные — это не только строки вроде:
Иван Петров
+7...
ivan@example.comВ старой системе может находиться история:
когда появился клиент;
кто с ним работал;
какие сделки создавались;
как менялись статусы;
какой была стоимость;
какие комментарии оставляли сотрудники;
какие документы прикладывались;
почему сделка была закрыта;
кто изменил данные.Если после миграции сохранить только текущее состояние, новая CRM технически будет работать.
Но несколько лет истории бизнеса исчезнут.
Поэтому хорошая миграция — это не:
Excel
↓
INSERT INTO clientsЭто отдельный инженерный проект со своими правилами, проверками и критериями приёмки.
Разберём, как мы бы проектировали такую миграцию.
Главное правило: сначала понять данные, потом писать импорт
Представим файл:
clients_final_2026.xlsxВ нём 37 колонок.
Кажется, что задача очевидна:
Excel column
↓
CRM fieldНапример:
ФИО
↓
client.nameТелефон
↓
client.phoneEmail
↓
client.emailНо уже через несколько строк обнаруживается:
Телефон:
+7 900 123-45-67
Телефон:
89001234567
Телефон:
8 (900) 1234567 / рабочий
Телефон:
нет
Телефон:
+7 900 123 45 67, МарияВ колонке:
Датаоказываются:
12.03.2024
2024-03-12
Март
весна 24
—А поле:
Менеджерсодержит:
Иван
Иван П.
Иван Петров
И.П.
Петров ИванХотя это один сотрудник.
Именно поэтому импорт начинается не с написания SQL.
Он начинается с профилирования исходных данных.
Сначала нужно провести инвентаризацию источников
В реальном бизнесе данные редко находятся в одном месте.
Например:
Старая CRM
+
clients.xlsx
+
sales-2024.xlsx
+
Google Sheets
+
папка Documents
+
экспорт бухгалтерииПри этом источники могут противоречить друг другу.
В старой CRM телефон:
+7 900 111-22-33В Excel:
+7 900 444-55-66Какой правильный?
Нельзя решить это универсальным:
берём последнее значение.Потому что нужно сначала определить источник истины для каждого типа данных.
Например:
| Данные | Приоритетный источник |
|---|---|
| Клиенты | старая CRM |
| Финансовые суммы | бухгалтерская система |
| Дополнительные телефоны | Excel менеджеров |
| Документы | файловый архив |
| Текущий ответственный | CRM |
| Исторические комментарии | CRM |
Это уже часть спецификации миграции.
Миграция — это преобразование модели, а не копирование таблиц
Старая CRM может иметь структуру:
customers
orders
commentsНовая:
organizations
contacts
projects
deals
messages
activities
documentsПоэтому операция редко выглядит:
old.customers
↓
new.customersЧаще:
Old customer row
↓
Organization
+
Contact
+
Membershipили наоборот.
Например старая система хранит:
Компания:
ООО Альфа
Контакт:
Иван Иванов
Телефон:
...
Последний заказ:
...в одной строке.
Новая CRM разделяет это:
Organization
│
├── Contact Ivan
├── Contact Maria
│
└── DealsТеперь миграция должна понять семантику данных и создать несколько связанных сущностей.
Полезно создать отдельную карту миграции
До написания importer можно составить таблицу:
| Старое поле | Новая сущность | Новое поле | Правило |
|---|---|---|---|
| company | Organization | name | trim |
| fio | Contact | fullName | normalize |
| phone | Contact | phone | normalize E.164 |
| manager | User relation | ownerId | mapping |
| status | Deal | status | status mapping |
| created | Deal | createdAt | preserve |
| comment | Activity | text | preserve original |
Такая таблица превращает миграцию из:
Разработчик примерно понял Excel
в явный технический контракт.
Старые ID лучше не выбрасывать
Представим старую CRM:
client_id = 1842В новой системе создаётся:
id = 9d7f...На первый взгляд старый ID больше не нужен.
Но через месяц бухгалтер пишет:
Посмотрите клиента 1842 из старой CRM.
Если связь потеряна, начинается ручной поиск.
Поэтому полезно сохранять:
legacy_source = old_crm
legacy_id = 1842или отдельную таблицу:
migration_id_map
source
entity
legacy_id
new_idНапример:
old_crm
client
1842
→
9d7f...Эта таблица невероятно полезна во время проверки миграции.
ID mapping нужен и для восстановления связей
Допустим старая система имеет:
Client #1842
↓
Deal #773
↓
Comment #9921В новой появляются другие IDs:
Client A
↓
Deal B
↓
Activity CЧтобы правильно перенести:
deal.client_idнужно знать:
old Client 1842
→
new Client AИменно поэтому сущности часто мигрируются по этапам.
Сначала:
Users
Organizations
Contactsпотом:
Deals
Projectsпосле:
Comments
Documents
ActivitiesСвязи восстанавливаются через mapping.
Самая серьёзная ошибка — потерять даты
Очень часто importer создаёт новые записи обычным способом.
Получается:
created_at = NOW()У клиента, который появился:
18.04.2019после миграции:
created_at = 28.09.2026Теперь новая CRM считает, что все клиенты появились сегодня.
История бизнеса уничтожена.
Исторические поля должны сохраняться отдельно от технических дат миграции
Например:
created_at
→ оригинальная дата создания
updated_at
→ исходная дата изменения,
если она достоверна
migrated_at
→ момент импортаТеперь можно отличить:
Когда сделка появилась в бизнесе?
от:
Когда мы перенесли её в новую CRM?
Особенно важно сохранить историю статусов
Представим сделку:
Сейчас:
ЗавершенаЕсли перенести только:
status = COMPLETEDмы знаем только результат.
Но старая CRM содержала:
12 марта
Новая
13 марта
Уточнение
15 марта
Оценка
18 марта
В работе
2 апреля
ЗавершенаЭта история позволяет рассчитывать:
скорость обработки;
время сделки;
длительность этапов;
работу менеджера.После миграции она должна выглядеть примерно так:
DealStatusHistory
deal_id
from_status
to_status
changed_at
actor_id
sourceИ важно сохранить реальные:
changed_atа не время выполнения importer.
Иногда старой истории нет
Это тоже нормальная ситуация.
Excel может содержать только:
Клиент
Текущий статусНельзя из этого придумать:
когда он переходил
через предыдущие статусы.Хорошая миграция не должна создавать фиктивную историю для красоты.
Правильнее сохранить:
Current status:
IN_PROGRESS
Historical transitions:
unknownи metadata:
imported_from = excelНе нужно «восстанавливать» факты, которых источник не знает
Это очень важный принцип.
Представим Excel:
Статус:
Завершено
Дата:
2022Нельзя автоматически решить:
Значит завершено 1 января 2022 года.
Такой timestamp выглядит точным, но является выдуманным.
Лучше сохранить:
legacy_year = 2022или пометить дату как неполную, если новая модель это поддерживает.
Миграция должна сохранять уровень достоверности данных, а не искусственно повышать его.
Отдельная проблема — пользователи и бывшие сотрудники
Старая история содержит:
Менеджер:
Алексей СмирновНо Алексей уволился три года назад.
В новой CRM активного аккаунта для него нет.
Если просто назначить все старые сделки текущему администратору, потеряется авторство.
Практичнее различать:
active userи:
historical actorНапример, можно создать:
User:
Алексей Смирнов
status:
INACTIVE
login:
disabledИстория сохранит:
createdBy = Alexeyно войти в систему он не сможет.
Авторство комментариев тоже является историей
Например:
14.05.2021
Алексей:
Клиент попросил перенести запуск.После миграции плохо получить:
28.09.2026
System:
Клиент попросил перенести запуск.Содержание сохранилось.
Контекст исчез.
Поэтому activity importer должен сохранять:
original author;
original timestamp;
source.Иногда можно сохранить оригинальное значение рядом с нормализованным
Например телефон:
original:
8 (900) 123-45-67 доб. 104после parsing:
phone:
+79001234567
extension:
104На этапе миграции полезно временно или постоянно иметь:
legacy_raw_valueособенно для неоднозначных полей.
Если что-то распарсилось неправильно, можно увидеть исходник.
Excel особенно опасен из-за неявной структуры
В одном столбце могут находиться:
Иван Иванов
ООО Альфа
+7...или:
Телефон 1 / Телефон 2или:
email; второй emailИногда цвет ячейки имеет смысл:
красный
→ проблемный клиента жирный шрифт:
→ VIPДля человека таблица выглядит структурированной.
Для программы значимая часть структуры вообще отсутствует.
Поэтому перед миграцией Excel нужно анализировать как пользовательский интерфейс
Важно выяснить:
Используются ли цвета?
Есть ли скрытые листы?
Что означает пустая строка?
Существуют ли формулы?
Объединены ли ячейки?
Есть ли комментарии Excel?
Как сотрудники обозначают удалённых клиентов?
Иногда оказывается, что бизнес-правила существуют только в привычках сотрудников.
Например:
Если строка серая — сделка закрыта.
В базе данных такого правила никогда не было.
Формулы нельзя импортировать как значения бездумно
Например Excel содержит:
Итого:
=SUM(...)На момент импорта значение:
580 000В новой CRM нужно решить:
580 000 — исторический факт?
или:
Это вычисляемое поле, которое новая система должна рассчитывать сама?
Это принципиально разные вещи.
В бизнес-системе лучше различать snapshot и derived value
Например:
current_project_totalможно рассчитывать.
Но:
invoice_amount_at_issueдолжна быть исторически зафиксирована.
Иначе изменение цен задним числом перепишет прошлое.
Миграция — хороший момент, чтобы впервые формально определить это различие.
Дедупликация — отдельный проект внутри миграции
Представим Excel:
ООО Альфа
+7 900...
info@alpha.ruДругая строка:
Альфа ООО
+7 900...Ещё одна:
ALFA
info@alpha.ruЭто одна организация?
Вероятно.
Но автоматическое:
похожее название
→ объединитьопасно.
Могут существовать реальные компании:
Строй-Сервис
Строй Сервис
СтройСервискоторые вообще не связаны.
Поэтому полезна многоуровневая deduplication
Самые надёжные признаки:
уникальный legacy ID;
ИНН;
официальный внешний идентификатор.Далее:
точный email;
нормализованный телефон.А:
похожее название;
похожее ФИО.лучше использовать для создания списка:
Возможные дубли.
Но не для безусловного автоматического объединения.
Confidence может помогать, но решение должно быть контролируемым
Например:
ИНН совпадает
→ 100%
→ merge автоматическиemail совпадает
→ высокий уровеньназвание похоже на 87%
→ manual reviewТак миграция не уничтожает две разные записи из-за слишком агрессивного fuzzy matching.
Лучше оставить два дубля, чем неправильно объединить двух клиентов
Дубликат можно устранить позднее.
После неправильного merge разделить данные назад намного сложнее.
Особенно если уже объединились:
сделки;
комментарии;
документы;
платежи.Поэтому автоматическая очистка данных должна быть консервативной.
Email тоже нельзя считать идеальным идентификатором
В B2B один адрес:
info@company.ruможет относиться к нескольким людям.
А один человек может иметь:
ivan@gmail.com
ivan@company.ruТо же касается телефона.
Именно поэтому deduplication — бизнес-задача, а не просто SQL:
GROUP BY emailСтатусы почти всегда требуют mapping
В старой CRM:
Новый
В процессе
Думает
Закрыто
Не интересноВ новой:
NEW
NEEDS_CLARIFICATION
ESTIMATION
OFFER_SENT
IN_PROGRESS
COMPLETED
CANCELLEDНельзя просто перенести текст.
Нужна таблица:
| Старый статус | Новый |
|---|---|
| Новый | NEW |
| В процессе | IN_PROGRESS |
| Закрыто | COMPLETED |
| Не интересно | CANCELLED |
| Думает | требует уточнения |
Последний случай нельзя автоматически решить без знания бизнеса.
Иногда одного старого статуса недостаточно для нового
Например:
Закрытов старой системе означает одновременно:
успешно завершенои:
клиент отказалсяНовая CRM различает:
COMPLETEDи:
CANCELLEDЗначит, одного status mapping недостаточно.
Нужно искать дополнительные признаки:
причина;
сумма;
комментарий;
результат.А записи, которые невозможно определить автоматически, отправлять в:
manual review.Manual review — нормальная часть миграции
Нередко хочется добиться:
100% полностью автоматический импорт.Но если 2% исходных данных неоднозначны, автоматическое угадывание может быть хуже ручной проверки.
Практичная схема:
100 000 записей
↓
98 400 импортируются автоматически
1 600
↓
migration review queueАдминистратор решает только спорные случаи.
Это намного эффективнее ручной обработки всех 100 000 строк.
Не нужно импортировать прямо в production-таблицы с первого прохода
Очень полезен промежуточный слой:
Source
↓
Staging
↓
Validation
↓
Transformation
↓
ProductionНапример:
staging_clients
staging_deals
staging_commentsТуда можно загрузить исходные данные почти без изменений.
После этого запускать анализ.
Зачем нужен staging
Он позволяет сохранить:
исходную строку;
номер файла;
номер листа;
номер строки;
raw values;
результат validation;
migration errors.Например:
source_file:
clients.xlsx
sheet:
Clients
row:
1843
error:
UNKNOWN_MANAGERТеперь ошибку можно расследовать.
Без staging debugging превращается в кошмар
Importer сообщает:
Ошибка foreign key.
Где?
В какой строке Excel?
Что было исходным значением?
Если importer уже преобразовал всё в runtime objects и выбросил исходный контекст, ответ получить сложно.
Staging превращает migration pipeline в наблюдаемую систему.
Хорошая миграция похожа на ETL
Классическая схема:
Extract
↓
Transform
↓
LoadExtract
Получаем данные из:
старой CRM;
CSV;
Excel;
API;
SQL dump.Transform
Нормализуем:
телефоны;
даты;
статусы;
ссылки;
пользователей;
связи.Load
Записываем в новую модель.
Но в реальной миграции между Transform и Load обычно нужна ещё серьёзная Validation.
Поэтому практический pipeline скорее такой
Extract
↓
Stage
↓
Profile
↓
Normalize
↓
Validate
↓
Map
↓
Load
↓
ReconcileПоследний этап — один из самых важных.
Что такое reconciliation после миграции
Импорт завершился без ошибок.
Это ещё не значит, что данные перенесены правильно.
Нужно сравнить старую и новую системы.
Например:
Старая CRM:
42 841 клиента
Новая CRM:
42 841 клиентаХороший первый сигнал.
Но недостаточный.
Нужно проверять не только количество строк
Например:
Старые сделки:
184 220
Новые:
184 220Количество совпадает.
Но все суммы оказались:
× 100из-за ошибки единиц.
Формально ни одна строка не потерялась.
Миграция провалилась.
Полезны контрольные агрегаты
Например сравнить:
количество клиентов;
количество сделок;
сумму продаж;
количество завершённых;
количество отменённых;
количество комментариев;
число документов.По периодам:
2019
2020
2021
...и по подразделениям.
Если старая система показывает:
Выручка 2024:
48 921 400 ₽а новая:
48 921 400 ₽это хороший признак.
Checksums полезны там, где данные можно нормализовать однозначно
Например, создаём canonical представление:
legacy_id
normalized_name
normalized_phone
created_atи считаем hash.
После миграции пересчитываем.
При несовпадении запись попадает в отчёт.
Это помогает проверять большие объёмы автоматически.
Но контрольные суммы не заменяют выборочную ручную проверку
Полезно выбрать:
нового клиента;
старого клиента;
клиента с несколькими телефонами;
сделку с 20 статусами;
закрытую сделку;
бывшего сотрудника;
запись с документами;
спорный дубль.И пройти историю вручную.
Автоматические тесты находят массовые ошибки.
Человеческая проверка — смысловые.
Особенно важно мигрировать файлы отдельно
Допустим старая CRM содержит:
Document recordи файлы лежат:
/uploads/...Недостаточно перенести metadata.
Нужно убедиться, что:
Document #184
↓
реальный PDFсуществует и открывается.
При большом архиве useful pipeline:
copy object
↓
verify size
↓
checksum
↓
create metadataили другая схема в зависимости от storage.
И checksum файлов действительно полезен
Например старый файл:
SHA-256:
abc...после копирования:
SHA-256:
abc...Мы знаем, что содержимое не изменилось при переносе.
Размер файла сам по себе даёт более слабую гарантию.
Не забывайте про attachments внутри истории
В старой CRM комментарий может выглядеть:
12.04.2022
Иван:
Согласован новый договор.
[contract-v3.pdf]Если мигрировать:
commentи отдельно:
fileно потерять связь:
comment ↔ fileистория уже не эквивалентна исходной.
Поэтому мигрируются не только сущности.
Мигрируются relations.
Именно связи чаще всего оказываются сложнее строк
Данные:
100 000 contactsможно загрузить быстро.
Но потом нужно восстановить:
contacts → organizations
deals → contacts
deals → owners
messages → deals
files → messages
activities → usersОдна неправильная mapping-table способна испортить тысячи связей.
Constraints полезно включать во время миграции, а не только после
Есть соблазн:
отключить все foreign keysи быстро вставить данные.
Иногда это действительно используется как технический приём.
Но тогда обязательна последующая строгая валидация.
Иначе новая БД может содержать:
deal.client_idссылающийся на несуществующего клиента.
Мы бы предпочитали по возможности загружать сущности в правильном порядке и позволять ограничениям БД ловить ошибки.
Новый ID не должен зависеть от порядка строк Excel
Например:
row 1 → client 1
row 2 → client 2После сортировки файла IDs меняются.
Намного лучше использовать стабильный:
legacy_idили создаваемую migration mapping.
Так повторный импорт остаётся предсказуемым.
Идемпотентность importer очень полезна
Первый импорт дошёл до:
72%и упал.
Что делать?
Плохой ответ:
Очистим всё и начнём заново.
Для небольшой базы это возможно.
Для сотен гигабайт и миллионов строк — неудобно.
Лучше, если importer умеет безопасно повторяться.
Например, через legacy key
Уникальность:
(source, legacy_id)позволяет понять:
эта запись уже импортирована.Importer может:
skipили:
updateв зависимости от режима.
Получаем resumable migration.
Но режим update должен быть определён заранее
Что если запись уже была импортирована, а пользователь успел изменить её в новой CRM?
Например:
Migration #1:
phone = AПосле запуска новой CRM сотрудник исправил:
phone = BЗатем migration rerun снова импортирует:
phone = Aи уничтожает новую правку.
Поэтому повторяемость миграции требует понимания:
До какого момента legacy-source имеет право переписывать новые данные?
Именно поэтому миграция должна иметь чёткий cutover
До определённого момента:
Old CRM
→ source of truthПосле:
New CRM
→ source of truthНельзя бесконечно разрешать обеим системам независимо изменять одни данные.
Иначе появляется задача постоянной двусторонней синхронизации, которая намного сложнее одноразовой миграции.
Самая простая миграция — с остановкой изменений
Например:
Пятница 20:00
↓
Старую CRM переводим
в read-only
↓
Final export
↓
Migration
↓
Validation
↓
Понедельник
Новая CRMЭто называется cutover window.
Для небольшого бизнеса такой подход часто самый надёжный.
Но не каждый бизнес может остановиться
Интернет-магазин или крупная CRM могут работать:
24/7.Тогда понадобится более сложная схема.
Например:
T0
полная миграция
↓
старая система продолжает работать
↓
delta changes
↓
финальная синхронизация
↓
cutoverDelta migration переносит только новые изменения
Допустим первая копия сделана:
1 сентября.Запуск новой CRM:
7 сентября.За шесть дней в старой системе появились новые:
клиенты;
сделки;
комментарии.Финальная миграция должна перенести только:
changed_since = 1 сентябряНо это работает лишь тогда, когда старая система действительно умеет надёжно определять изменения.
updated_at не всегда достаточно
Например старая система при добавлении комментария не обновляет:
deal.updated_atТогда delta query:
WHERE updated_at > ...пропустит изменение.
Для критичной миграции полезнее иметь:
change log;
CDC;
audit table;
monotonic revision.Если старая система этого не предоставляет, стратегию придётся адаптировать.
Иногда migration downtime дешевле сложной синхронизации
Бизнес может сказать:
Мы не можем отключить CRM даже на минуту.
Технически почти всё возможно.
Но continuous dual-write migration может потребовать значительной разработки.
Если реальный ущерб от:
2 часов read-onlyневелик, это может быть значительно безопаснее и дешевле.
Миграционный план должен учитывать экономику, а не только максимальную техническую сложность.
Очень опасен dual write
Идея:
пишем одновременно
в старую CRM
и новую CRMзвучит удобно.
Но теперь нужно отвечать:
Что если старая записалась, а новая нет?
Что если новая записалась, а старая нет?
Как повторить?
Кто source of truth?
Мы превращаем migration в distributed consistency problem.
Без необходимости такой путь лучше не выбирать.
Для крупных миграций полезен режим shadow
Например новая система получает копию production-данных и работает параллельно, но пользователи пока не используют её для реальных изменений.
Можно сравнивать:
отчёты;
поиск;
карточки клиентов;
агрегаты.до переключения.
Это позволяет обнаружить смысловые расхождения заранее.
Перед запуском новой CRM полезно сделать migration rehearsal
То есть выполнить полный перенос на тестовой инфраструктуре.
Например:
Production dump old CRM
↓
Staging migration
↓
New CRM stagingЗамерить:
сколько занимает extract;
transform;
load;
file copy;
validation.Теперь cutover планируется по реальным цифрам.
Первый полный прогон почти никогда не должен происходить в день переключения
Потому что именно тогда обнаруживается:
неизвестный статус;
битая дата;
неожиданный encoding;
пустой ID;
невалидный email;
потерянный документ.Гораздо лучше обнаружить это за неделю до production migration.
Migration report должен быть полноценным артефактом
После тестового прогона полезно получить примерно:
Source clients:
48 920
Imported:
48 712
Skipped:
71
Needs review:
137И далее причины:
UNKNOWN_MANAGER
INVALID_DATE
DUPLICATE_EXTERNAL_ID
MISSING_ORGANIZATIONТак заказчик и разработчик обсуждают конкретные проблемы.
Нельзя тихо игнорировать плохую строку
Очень опасный importer:
try {
import(row)
} catch {
continue
}В конце:
Import complete.
А 2 000 клиентов исчезли.
Каждая пропущенная запись должна иметь:
причину;
source;
row;
status.И общий отчёт.
Хороший критерий: каждая исходная запись должна получить результат
Например:
IMPORTED
SKIPPED_BY_RULE
MERGED
MANUAL_REVIEW
REJECTEDНо не:
неизвестно, куда делась.Это делает миграцию аудируемой.
Ошибки исходных данных не всегда нужно исправлять автоматически
Например:
email:
ivan@@mail.ruМожно:
исправить?Но откуда мы знаем правильный?
Лучше:
import original value
+
mark invalidили отправить на review.
Автоматически «угадывать» критичные пользовательские данные опасно.
Нормализация и исправление — разные вещи
Нормализация:
8 (900) 123-45-67
↓
+79001234567при достаточной информации.
Исправление:
ivan@@mail.ru
↓
ivan@mail.ruуже является предположением.
Миграция должна различать эти операции.
Особенно осторожно нужно обращаться с деньгами
Excel может содержать:
100000Что это?
100 000 рублей?
100 000 копеек?
$1000?Название колонки:
Суммане отвечает.
До импорта денежных данных нужно зафиксировать:
валюту;
единицу;
округление;
налоговую семантику.Ошибка ×100 в финансовой истории намного хуже пары неправильно нормализованных телефонов.
Timezone — ещё один скрытый источник ошибок
Старая CRM хранит:
2024-05-01 10:00В какой timezone?
Москва?
UTC?
локальный сервер?Если importer решит неправильно, все события сдвинутся.
Особенно опасно для:
платежей;
встреч;
SLA;
журналов действий.Поэтому temporal semantics нужно выяснить до миграции.
То же касается Excel serial dates
Excel может хранить дату не как:
2024-05-01а как числовое значение внутреннего формата.
Importer должен использовать корректный parser для конкретного типа файла.
Чтение:
число → timestampпо собственному предположению создаёт ошибки.
История переписки имеет отдельную сложность
Старая CRM может хранить:
Incoming
Outgoing
System noteНовая различает:
client message
manager message
internal note
system event.Если всё перенести в:
commentважное различие исчезнет.
Менеджер может случайно увидеть внутреннюю заметку как сообщение клиента.
При миграции нужно сохранить тип коммуникации.
Не каждый старый пользователь должен получить новый login
История может содержать сотни людей:
бывшие сотрудники;
тестовые аккаунты;
удалённые пользователи.Создавать активную учётную запись каждому опасно.
Полезнее:
historical account
active = falseили отдельное представление historical actor.
Пароли обычно нельзя «перенести» как обычное поле
Если старая CRM использует безопасный password hash, иногда технически возможна миграция hashes при совместимом authentication layer.
Но далеко не всегда это разумно или возможно.
Часто при переходе пользователям требуется:
reset password.Нельзя экспортировать plaintext passwords, если их вообще нет и быть не должно.
Персональные данные требуют отдельного внимания
Старые выгрузки часто содержат больше данных, чем реально требуется новой CRM.
Например:
паспорт;
домашний адрес;
старый телефон;
лишние комментарии.Миграция — хороший момент спросить:
Нужно ли вообще переносить это в новую систему?
Принцип:
раз есть в старой БД
→ обязательно переносимне всегда правильный.
Миграция может одновременно быть data cleanup
Но cleanup должен быть формализован.
Например:
пустые тестовые клиенты
→ не переносим
дубли
→ объединяем только
по подтверждённым правилам
удалённые temporary records
→ исключаемКаждое правило фиксируется до production migration.
Иначе разработчик незаметно начинает решать:
Какие данные бизнеса важны, а какие нет.
Старую систему нельзя удалять сразу после запуска новой
Даже если migration validation прошла идеально.
Практично оставить legacy CRM:
read-onlyна определённый период.
Например:
30 днейили другой срок по требованиям бизнеса.
Сотрудники могут проверить спорные исторические случаи.
Но read-only legacy не должна жить вечно
Иначе через пять лет компания продолжает оплачивать и поддерживать:
новую CRM
+
старую CRMтолько потому что:
Вдруг понадобится.
Нужно заранее определить:
validation period
↓
final archive
↓
decommissionФинальный архив старой системы тоже является частью миграции
Можно сохранить:
SQL dump;
экспорты;
файлы;
описание схемы;
migration mapping;
migration report.В будущем это намного полезнее, чем единственный:
oldcrm.zipбез инструкции, что внутри.
Rollback нужно продумать до переключения
Представим новую CRM запустили утром.
Через час обнаружили критичный дефект.
Что делать?
Если сотрудники уже начали создавать новые данные, просто:
вернуться в старуюне так просто.
Нужно определить cutover policy.
Например, первые часы новая система работает под усиленным контролем.
Если rollback действительно допускается, нужно понимать, как обрабатывать данные, созданные после переключения.
Иногда правильнее не rollback, а forward fix
Если новая система уже получила:
50 новых клиентов;
20 сделок;
100 сообщений,откатываться к старой CRM может быть опаснее, чем оперативно исправить дефект.
Поэтому migration rollback и application rollback — не одно и то же.
Data migration должна иметь собственные тесты
Например:
legacy client #1842после import должен иметь:
имя;
телефон;
организацию;
5 сделок;
18 комментариев;
3 файла.Тест может автоматически сравнить expected mapping.
Очень полезны fixture с «грязными» данными
Не только идеальная строка:
Иван
+7900...
ivan@example.comНо:
двойной телефон;
пустой email;
неизвестный менеджер;
сломанная дата;
duplicate company;
deleted user;
несуществующая relation.Importer должен пройти реальные странности legacy-системы.
Миграция должна быть версионируемой
Скрипт:
migration-final.jsкоторый постоянно редактируют вручную, плохо воспроизводим.
Лучше иметь:
migration-v1
migration-v2или Git history.
И конфигурацию mapping.
Тогда можно доказать:
Какой именно код перенёс production-данные?
Исходный экспорт тоже нужно фиксировать
Например:
crm-export-2026-09-28.csv
SHA-256: ...Или database dump checksum.
Если в процессе проверки исходный файл поменяли, результаты двух запусков уже нельзя сравнивать.
Хороший migration run должен быть воспроизводим
Имея:
тот же source snapshot
+
ту же migration version
+
ту же configurationмы должны получать:
тот же результат.Это сильно упрощает QA.
Секрет миграции — отделить техническое преобразование от бизнес-решений
Техническое:
trim spaces;
parse date;
convert encoding.Бизнес-решение:
статус "Думает"
→ какой новый статус?или:
два похожих клиента
→ один или два?Разработчик не должен самостоятельно угадывать вторую категорию.
Для спорных правил нужен migration specification
Например:
Rule M-017
Legacy:
status = "Закрыт"
If:
payment exists
Then:
COMPLETED
Else:
MANUAL_REVIEWТеперь решение документировано.
Его можно протестировать.
Это особенно полезно для приёмки
Без specification заказчик после migration говорит:
Мне кажется, некоторые статусы неправильные.
Разработчик:
Мы сделали как поняли.
С specification можно проверить:
ожидаемое правило
vs
фактический результат.Как мы бы организовали migration project
Общая схема выглядела бы так:
LEGACY SYSTEMS
│
┌───────────┼───────────┐
│ │ │
▼ ▼ ▼
Old CRM Excel Documents
│ │ │
└───────────┼───────────┘
▼
EXTRACT
│
▼
STAGING
│
┌────────┴────────┐
▼ ▼
Validation Profiling
│ │
└────────┬────────┘
▼
NORMALIZATION
│
▼
MAPPING
│
▼
NEW CRM DB
│
┌──────┴──────┐
▼ ▼
Reconciliation QA
│ │
└──────┬──────┘
▼
CUTOVERОбратите внимание: INSERT занимает в этой схеме совсем небольшое место.
Основная работа находится вокруг него.
Пример миграции клиента
Исходная Excel-строка:
ID:
1842
Компания:
ООО "Альфа "
Контакт:
Иван Петров
Телефон:
8 (900) 123-45-67
Менеджер:
Иван
Статус:
В работе
Создан:
12.03.2021После нормализации:
legacy_id:
1842
organization_name:
ООО «Альфа»
contact_name:
Иван Петров
phone:
+79001234567
legacy_manager:
Иван
legacy_status:
В работе
created_at:
2021-03-12Mapping:
Manager Иван
↓
User #42В работе
↓
IN_PROGRESSLoad:
Organization #A
Contact #B
Deal #Cи:
legacy map:
1842 → CНо импорт считается успешным только после проверки результата
Например:
Deal #C
status = IN_PROGRESS
createdAt = 2021-03-12
owner = User #42
contact = Ivan Petrov
organization = AlphaИ контрольной выборки.
Пример сложнее: историческая сделка
Legacy:
Deal #773
Client #1842
Manager #12
Created 2021-03-14
Closed 2021-04-02
Amount 480000History:
14.03 NEW
15.03 QUALIFICATION
20.03 PROPOSAL
23.03 IN_PROGRESS
02.04 WONПосле migration мы хотим получить не только:
status = COMPLETEDНо и:
Deal
createdAt = 2021-03-14
completedAt = 2021-04-02
amount = 480000плюс:
StatusHistoryсо всеми исходными датами.
Тогда новая аналитика сможет работать и с прошлым периодом.
Иначе отчёты после перехода начнут «жизнь с нуля»
Представим новую CRM запустили:
1 октября.Если история статусов не перенесена, dashboard может показать:
Среднее время сделки:
0 днейили учитывать только новые сделки.
Руководство сравнивает данные с прошлым годом и видит бессмысленные показатели.
Миграция должна учитывать будущую аналитику
До переноса полезно спросить:
Какие отчёты бизнес хочет строить после запуска?
Если нужен:
sales by manager 2020–2026нужно сохранить historical owner.
Если:
conversion by stageнужна status history.
Если:
revenue by monthнужны достоверные timestamps и суммы.
Бизнес-требования к аналитике определяют глубину миграции.
Иногда полную историю переносить невыгодно
Представим старой CRM:
15 лет.Первые десять лет данные крайне плохого качества.
Бизнес реально использует:
последние 3 года.Можно принять осознанное решение:
2023–2026
→ полноценная миграция
до 2023
→ read-only archiveЭто не потеря данных, если архив остаётся доступным и решение согласовано.
Это управление стоимостью migration.
Не обязательно превращать всё legacy в новую domain model
Например, старые записи содержат поле:
Параметр Xкоторого больше нет в бизнес-процессе.
Можно сохранить его:
legacy_metadataили в read-only archive, вместо добавления ненужного поля в современную CRM только ради истории.
Новая система не должна навсегда наследовать ошибки старой
Это одна из главных возможностей миграции.
Если legacy CRM имела:
17 почти одинаковых статусов;
одну гигантскую таблицу;
дублированные клиенты;
текстовые ID менеджеров,не нужно копировать эту архитектуру в новую.
Сохраняем информацию.
Не обязательно сохранять плохую модель.
История и старая структура — не одно и то же
Можно сохранить:
кто;
что;
когда;
с кем;
на какую сумму;
какой был результат.Но представить это уже через нормальную новую domain model.
Именно это делает migration полезнее простого переноса БД.
Что должен получить заказчик после миграции
Не просто сообщение:
Импорт завершён.
А понятный результат:
сколько сущностей было в источнике;
сколько перенесено;
сколько объединено;
сколько отклонено;
какие записи требуют проверки;
какие правила преобразования применены;
какие контрольные суммы и агрегаты совпали.Это делает миграцию проверяемой.
Когда можно считать миграцию завершённой
Не когда script закончил работу с:
exit code 0.А когда выполнены несколько условий.
Новая CRM содержит согласованный объём данных.
Связи восстановлены.
Исторические даты сохранены.
Файлы доступны.
Ключевые агрегаты совпадают.
Спорные записи разобраны или зафиксированы.
Сотрудники провели выборочную проверку.
Есть backup исходных данных.
Есть migration report.
И только после этого legacy-система перестаёт быть operational source of truth.
Почему «просто импортировать Excel» часто недооценивают
Потому что технически прочитать XLSX действительно несложно.
Самый трудный вопрос не:
Как открыть Excel из Node.js?А:
Что именно означает каждая строка и как сохранить её смысл в новой системе?
Например:
Красная строкаможет означать:
должник.Пустой менеджерможет означать:
общий клиент отдела.Закрытоможет означать два противоположных исхода.
Эти знания находятся не в API Excel.
Они находятся внутри бизнеса.
Поэтому миграция требует участия сотрудников компании
Разработчик понимает:
database;
ETL;
transactions;
constraints.Но менеджер понимает:
Что означало поле «Тип 2» в таблице 2021 года.
Без этого знания автоматическая миграция может быть технически безупречной и бизнесово неправильной.
Самый полезный вопрос на старте миграции
Мы бы спросили не:
Сколько строк в Excel?
А:
Какие факты из старой системы должны оставаться доказуемыми после запуска новой?
Например:
когда появился клиент;
кто вёл сделку;
какая цена была согласована;
какой статус был в конкретную дату;
какой документ был приложен;
кто оставил комментарий.От ответа зависит архитектура миграции.
Практический чек качества
Есть простой способ мысленно проверить результат.
Откройте случайную старую сделку в legacy CRM.
Затем ту же сделку в новой.
И спросите:
Могу ли я понять ту же историю?
Не обязательно интерфейсы должны совпадать.
Но факты должны.
Например:
клиент;
ответственный;
дата;
сумма;
статусы;
комментарии;
документы;
результат.Если новая система показывает только последний snapshot — данные перенесены, но история нет.
И это главное отличие миграции от импорта
Импорт отвечает:
Как записать данные в новую систему?
Миграция:
Как перевести бизнес на новую систему, сохранив смысл, историю и возможность доверять данным?
Это намного более широкая задача.
Вместо вывода
Переход со старой CRM или Excel часто воспринимают как заключительный технический этап:
новая CRM готова
↓
осталось загрузить данныеНа практике миграцию лучше проектировать ещё до завершения разработки.
Потому что именно старые данные показывают:
какие сущности существуют;
какие связи важны;
какую историю нужно хранить;
какие статусы реально использует бизнес;
какие отчёты должны работать после запуска.Надёжный процесс выглядит примерно так:
Инвентаризация источников
↓
Профилирование данных
↓
Migration specification
↓
Staging
↓
Нормализация
↓
Mapping
↓
Дедупликация
↓
Импорт
↓
Reconciliation
↓
Тестовый прогон
↓
Final migration
↓
Cutover
↓
Проверка
↓
Legacy read-onlyПри этом ключевая цель — не добиться красивого:
Imported: 100%Гораздо важнее другое:
после перехода сотрудники должны видеть тот же бизнес, который существовал до миграции, только уже в более качественно спроектированной системе.
Клиент, созданный пять лет назад, не должен внезапно стать сегодняшним.
Бывший менеджер не должен исчезнуть из старых сделок.
Завершённая сделка не должна потерять путь, которым она дошла до результата.
Документ не должен отделиться от комментария, к которому был приложен.
А неоднозначные данные не должны превращаться в выдуманные факты только ради того, чтобы importer завершился без предупреждений.
Хорошая миграция — это не максимальная скорость копирования строк.
Это способность после переключения новой CRM ответить на вопрос:
Можем ли мы доверять своей истории?
Если ответ уверенно «да», тогда переход действительно завершён.