Проблема миграции Bitrix-поля проявляется после успешного ответа API. Запись получила ID, но телефон оказался пустым, email изменил регистр, а повторный запуск создал второе значение. Цена ошибки — не только испорченная строка. Дальше ломается поиск, уведомление или связь с внешней системой, а восстановить исходное состояние трудно, потому что команда сохранила только факт «update вернул успех».
Полевой разбор должен проверять три операции: сохранить mapping, прочитать результат тем же смыслом и повторить вход без дубля. Для каждого поля нужно различить отсутствующее значение и явную очистку. Это особенно важно при переходе от массивов Bitrix к canonical объекту: старое имя можно удалить из кода, но нельзя удалить смысл значения до окончания проверки.
Сначала таблица mapping
Документация CUser перечисляет поля пользователя, включая PERSONAL_PHONE, EMAIL, идентификатор и время изменения. Для проекта это отправная точка, а не готовая схема: рядом могут быть пользовательские поля, обработчики события и внешний XML_ID. Запишите для каждого значения источник, формат, пустое состояние и обратное представление. Если поле не переносится, причина должна быть явной.
Нормализация должна быть идемпотентной: одинаковый вход при повторном запуске даёт одинаковый canonical результат. Для телефона это может быть trim без изменения номера, для email — lowercase, если бизнес-правило считает регистр незначимым. Нельзя применять общую нормализацию ко всем полям: комментарий пользователя, парольный хэш и XML_ID имеют разные правила.
| Поле | Legacy-значение | Canonical-значение | Проверка |
|---|---|---|---|
| Телефон | PERSONAL_PHONE, пробелы допустимы | phone, trimmed string | read-back и формат |
| EMAIL, исходный регистр | email, lower-case | валидность и отсутствие дубля | |
| ID | ID пользователя | externalId | одинаковая запись при retry |
| Пустота | нет ключа или пустая строка | missing или clear | два разных теста |
| Связь | XML_ID | externalId | двусторонний mapping |
Учебная нормализация без Bitrix-сервера
Ниже запускается локальная функция преобразования одного объекта. Она сохраняет legacy-копию, создаёт canonical поля и возвращает признак обратимости. Это не подмена миграционного запуска: результат показывает, как тестировать mapping до подключения API. В интеграционном коде следующая проверка должна сравнить canonical объект с read-back из Bitrix.
import { migrateUserFields } from './upgrade-2027-02.mjs';
const input = {
ID: 17,
PERSONAL_PHONE: ' +7 900 000-00-00 ',
EMAIL: 'User@Example.TEST',
XML_ID: 'crm-17',
};
console.log(migrateUserFields(input));
// canonical.phone === '+7 900 000-00-00'
// canonical.email === 'user@example.test'
// reversible === trueОжидаемый результат показывает две отдельные операции: пробелы убраны у телефона, регистр email нормализован, а исходные поля остаются в legacy snapshot. Snapshot нужен для теста и отката преобразования, но не должен случайно отправляться обратно в новый API. В production-модуле его заменит журнал безопасного mapping без персональных значений.
Пустое поле и отсутствующее поле
Разница между {} и { PERSONAL_PHONE: "" } часто теряется в универсальном merge. Первый объект может означать «не менять телефон», второй — «очистить телефон». Если миграция смешивает эти случаи, повторный запуск удалит данные, которые не должны были меняться. В тестовой матрице должны быть оба входа и ожидаемое действие на стороне Bitrix.
Внешняя валидация тоже не должна менять значение молча. PHP filter_var может помочь проверить email, но решение о допустимости адреса принадлежит контракту приложения. Неверный email лучше остановить до update, чем сохранить пустую строку и потом считать ответ API доказательством успеха. Для телефона нужны отдельные правила: длина, допустимые символы и локальный формат.
Действия по порядку
- Составить mapping table и отдельно назвать missing, empty, invalid и unchanged для каждого поля.
- Запустить нормализацию на локальном наборе с пробелами, разным регистром, пустым и неверным значением.
- В тестовой среде записать одну запись по стабильному ID или XML_ID, затем выполнить read-back.
- Сравнить смысловые поля, время изменения, события и внешний идентификатор; не ограничиваться HTTP/API success.
- Повторить тот же запуск и убедиться, что новая запись не появилась и canonical результат не изменился.
Ограничения и следующий шаг
Локальная функция не знает о правах, событиях и особенностях конкретной версии Bitrix. Read-back может вернуть представление, отличное от входа: формат телефона, timezone или пустое значение иногда нормализуются сервером. Восстановление должно учитывать транзакцию и сохранённую связь, а не просто повторно отправлять старый объект.
Следующий шаг — взять одно поле, для которого есть внешний XML_ID, и прогнать полный цикл на небольшой выборке: mapping, запись, read-back, повтор и отчёт по расхождениям. Пока расхождения не классифицированы, расширять миграцию на весь набор рискованно. Проверяемость одного поля ценнее широкого запуска с неясным результатом.
Проверяемые источники
- Главный модуль: CUser — документация 1С-Битрикс — версия и дата: документация API, проверена 31 July 2026; класс доступен с версии 3.0.6. Применение: Официальный список полей CUser используется для примера PERSONAL_PHONE, EMAIL, ID и XML_ID. Граница: Документация не знает пользовательские поля, события и фактические данные проекта.
- PHP Manual: filter_var — версия и дата: PHP Manual, актуальная страница, проверена 31 July 2026. Применение: PHP Manual используется для проверки допустимости входного email перед записью. Граница: filter_var не определяет бизнес-правила, Bitrix-формат и гарантию сохранения поля.
- CModule::IncludeModule — документация 1С-Битрикс — версия и дата: документация API, проверена 31 July 2026; CModule с версии 3.0.1. Применение: Подключение модуля упомянуто как проверка интеграционной границы до записи. Граница: Страница не подтверждает права и настройки конкретной среды.