При работе с конфигурациями 1С нередко возникают ситуации, когда для оперативного решения задач или добавления новой функциональности разработчики вручную добавляют или копируют объекты метаданных, которые еще отсутствуют в типовой конфигурации поставщика, но планируются к появлению в будущих релизах. Такой подход, хоть и позволяет быстро внедрить необходимые механизмы, создает серьезные сложности при последующих обновлениях. Система 1С, при попытке обновить конфигурацию, может не распознать эти вручную добавленные объекты как аналоги новых объектов поставщика, предлагая удалить старые и добавить новые, что грозит потерей данных и необходимостью ручной доработки.
Рассмотрим подробно, как мы можем решить проблему сопоставления вручную добавленного перечисления СтатусыОтчетаОРозничныхПродажах с появившимся в новом релизе поставщика перечислением СтатусыКассовойСмены, которое по сути является тем же объектом, но с другим названием и внутренним идентификатором.
Прежде чем перейти к решениям, давайте выясним, почему возникает данная проблема. Система 1С для идентификации объектов метаданных использует не только их имя, но и внутренний уникальный идентификатор (UUID). Когда мы вручную добавляем новый объект (например, создавая перечисление с нуля) или копируем его из другой конфигурации через буфер обмена, этому объекту присваивается новый, уникальный UUID. Даже если мы назовем его так же, как будущий объект поставщика, или даже если система поставщика в итоге введет объект с таким же названием, их UUID будут различаться.
При обновлении конфигурации 1С сравнивает метаданные текущей конфигурации с метаданными конфигурации поставщика. Если она видит объект с новым UUID в конфигурации поставщика и не находит соответствующего объекта в нашей конфигурации (или находит объект с таким же именем, но другим UUID), она предлагает:
В результате, все данные, ссылавшиеся на вручную добавленный объект, могут быть утеряны или станут некорректными, если мы просто удалим старый объект и добавим новый.
Система 1С предоставляет механизм для ручного сопоставления объектов метаданных в процессе сравнения и объединения конфигураций. Этот подход может быть полезен, когда мы хотим "подсказать" системе, что наш существующий объект соответствует новому объекту поставщика, несмотря на различия в именах или UUID.
.cfu) или сравниваем конфигурацию с конфигурацией поставщика.СтатусыОтчетаОРозничныхПродажах без "желтого бочонка" (показывает, что это не объект поставщика) и новое перечисление СтатусыКассовойСмены от поставщика, которое предлагается добавить как новое.СтатусыОтчетаОРозничныхПродажах), нажимаем на него правой кнопкой мыши и выбираем пункт контекстного меню "Установить соответствие объектов...".СтатусыКассовойСмены).Важные замечания по этому методу:
Этот подход является более надежным и управляемым, особенно в сложных случаях или когда интерактивное сопоставление невозможно. Он включает в себя программный перенос всех существующих данных из старого, вручную добавленного объекта в новый объект, который мы примем от поставщика.
СтатусыКассовойСмены от поставщика.СтатусыОтчетаОРозничныхПродажах сразу. Вместо этого, чтобы избежать возможных конфликтов имен с будущими версиями конфигурации (особенно если объект поставщика позже получит такое же имя), мы можем временно изменить имя нашего старого перечисления, добавив к нему префикс, например, _Удалить_СтатусыОтчетаОРозничныхПродажах или СтатусыОтчетаОРозничныхПродажах_Удалить. Это позволит нашему обработчику обновления получить к нему доступ.Нам потребуется создать специальный обработчик обновления конфигурации, который будет выполняться один раз после обновления информационной базы до новой версии. Этот обработчик будет отвечать за перенос данных.
ОбновлениеИнформационнойБазыБСП или аналогичный), или создаем свой специализированный общий модуль, например, ОбработчикиОбновленияКонфигурации_Расширенная, если мы работаем в модифицированной конфигурации.
Наш обработчик должен быть вызван в определенный момент обновления. Обычно для этого используется процедура в таком модуле, которая вызывается из подсистемы обновления при достижении определенной версии конфигурации.
// Пример общего модуля для обработчиков обновления (например, ОбновлениеИнформационнойБазы)
// В модуле объекта Подсистема.СтандартныеПодсистемы.ОбработчикиОбновления (или аналогичном)
// или в вашем кастомном общем модуле, предназначенном для таких целей.
Процедура ПриОбновленииКонфигурации(ПараметрыОбновления) Экспорт
// Пример использования для конкретной версии
// Допустим, ваше обновление, где появилось СтатусыКассовойСмены, имеет версию 1.1.2.5.
// И вы хотите выполнить миграцию, если база обновилась с более ранней версии
Если ВерсияКонфигурацииИнформационнойБазыМенее(ПараметрыОбновления.ВерсияКонфигурацииИнформационнойБазы, "1.1.2.5") ИЛИ НЕ ПараметрыОбновления.ВерсияКонфигурацииИнформационнойБазы = "1.1.2.5" Тогда
// Проверяем, что миграция еще не выполнялась
Если ОбновлениеИнформационнойБазы.ТребуетсяВыполнитьОтложенноеОбновление("МиграцияСтатусовОтчета") Тогда
ВыполнитьМиграциюСтатусовОтчетаОРозничныхПродажах();
ОбновлениеИнформационнойБазы.ЗарегистрироватьОтложенноеОбновлениеВыполнено("МиграцияСтатусовОтчета");
КонецЕсли;
КонецЕсли;
// Здесь могут быть и другие обработчики для других версий
КонецПроцедуры;
// Вспомогательная функция (если не существует в типовых модулях)
Функция ВерсияКонфигурацииИнформационнойБазыМенее(ВерсияТекущая, ВерсияСравнения)
Возврат СравнитьВерсии(ВерсияТекущая, ВерсияСравнения) < 0;
КонецФункции;
Функция СравнитьВерсии(Версия1, Версия2)
Массив1 = СтрРазделить(Версия1, ".", Ложь);
Массив2 = СтрРазделить(Версия2, ".", Ложь);
Для Индекс = 0 По Макс(Массив1.Количество(), Массив2.Количество()) - 1 Цикл
Значение1 = 0;
Если Индекс < Массив1.Количество() Тогда
Значение1 = Число(Массив1[Индекс]);
КонецЕсли;
Значение2 = 0;
Если Индекс < Массив2.Количество() Тогда
Значение2 = Число(Массив2[Индекс]);
КонецЕсли;
Если Значение1 < Значение2 Тогда
Возврат -1;
ИначеЕсли Значение1 > Значение2 Тогда
Возврат 1;
КонецЕсли;
КонецЦикла;
Возврат 0;
КонецФункции;
Соответствие.
// В том же общем модуле, где и ПриОбновленииКонфигурации
Процедура ВыполнитьМиграциюСтатусовОтчетаОРозничныхПродажах()
Сообщить("Начало миграции перечисления 'СтатусыОтчетаОРозничныхПродажах_Удалить' на 'СтатусыКассовойСмены'.");
// Создаем соответствие старых и новых значений перечислений
// Предполагаем, что старое перечисление было переименовано в СтатусыОтчетаОРозничныхПродажах_Удалить
КартаСопоставленияСтатусов = Новый Соответствие();
// Пример сопоставления значений. Мы должны вручную определить, какое старое значение
// соответствует какому новому значению.
// Если в старом перечислении были значения, не имеющие аналогов в новом,
// нам нужно решить, что с ними делать (например, присвоить значение по умолчанию или игнорировать).
// Старые значения (из СтатусыОтчетаОРозничныхПродажах_Удалить) : Новые значения (из СтатусыКассовойСмены)
КартаСопоставленияСтатусов.Вставить(Перечисления.СтатусыОтчетаОРозничныхПродажах_Удалить.Принят, Перечисления.СтатусыКассовойСмены.Открыта);
КартаСопоставленияСтатусов.Вставить(Перечисления.СтатусыОтчетаОРозничныхПродажах_Удалить.Отправлен, Перечисления.СтатусыКассовойСмены.Закрыта);
КартаСопоставленияСтатусов.Вставить(Перечисления.СтатусыОтчетаОРозничныхПродажах_Удалить.К_Оформлению, Перечисления.СтатусыКассовойСмены.ПодготовленаКЗакрытию);
// Добавляем все остальные значения, которые были в старом перечислении.
// Если какие-то старые значения не имеют прямого аналога, нам нужно решить,
// к какому новому статусу их привести или оставить пустыми (если это допустимо).
// Продолжение ниже, в разделе поиска и замены ссылок
КонецПроцедуры;
СтатусыОтчетаОРозничныхПродажах_Удалить, и заменить его на соответствующее значение из нового перечисления СтатусыКассовойСмены.
Для поиска мест использования старого перечисления мы можем:
После того, как мы определили все места использования, нам нужно написать код для их обновления. Рассмотрим пример для документов, где использовался старый статус, и для регистров сведений.
Предположим, старый статус использовался в документе ОтчетОРозничныхПродажах, и новый статус должен быть записан в новый реквизит этого же документа или в аналогичный реквизит, но уже нового типа.
// Продолжение процедуры ВыполнитьМиграциюСтатусовОтчетаОРозничныхПродажах()
// Перебираем объекты, в которых используется старое перечисление.
// В данном примере - документ 'ОтчетОРозничныхПродажах'.
// Предполагаем, что в этом документе был реквизит 'СтатусОтчета' типа СтатусыОтчетаОРозничныхПродажах_Удалить.
// И в новом релизе появился реквизит 'СтатусКассовойСмены' типа СтатусыКассовойСмены.
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Документ.ОтчетОРозничныхПродажах.Ссылка КАК Ссылка,
| Документ.ОтчетОРозничныхПродажах.СтатусОтчета КАК СтарыйСтатус
|ИЗ
| Документ.ОтчетОРозничныхПродажах КАК Документ.ОтчетОРозничныхПродажах
|ГДЕ
| Документ.ОтчетОРозничныхПродажах.СтатусОтчета ССЫЛКА Перечисление.СтатусыОтчетаОРозничныхПродажах_Удалить";
Выборка = Запрос.Выполнить().Выбрать();
СчетчикОбновленных = 0;
Пока Выборка.Следующий() Цикл
СтарыйСтатус = Выборка.СтарыйСтатус;
// Находим соответствующее новое значение
Если КартаСопоставленияСтатусов.Ключи().Найти(СтарыйСтатус) Тогда
НовыйСтатус = КартаСопоставленияСтатусов[СтарыйСтатус];
Попытка
// Получаем объект для изменения
ОбъектДокумента = Выборка.Ссылка.ПолучитьОбъект();
// Здесь мы записываем новое значение в новый реквизит.
// Если в новом релизе реквизит получил другое имя,
// или если мы хотим использовать его для других целей,
// это нужно учесть.
// Важно: убедиться, что реквизит 'СтатусКассовойСмены' существует в документе
// после принятия обновлений от поставщика.
ОбъектДокумента.СтатусКассовойСмены = НовыйСтатус; // Имя нового реквизита в документе
// Также, если старый реквизит не удален, его можно очистить или установить в NULL,
// чтобы избежать путаницы.
// ОбъектДокумента.СтатусОтчета = NULL;
ОбъектДокумента.Записать();
СчетчикОбновленных = СчетчикОбновленных + 1;
Исключение Как Искл
Сообщить("Ошибка при обновлении документа " + Выборка.Ссылка + " (Статус: " + СтарыйСтатус + "): " + Искл.Описание, СтатусСообщения.Важное);
КонецПопытки;
Иначе
Сообщить("Внимание: для документа " + Выборка.Ссылка + " не найдено соответствие для старого статуса '" + СтарыйСтатус + "'. Требуется ручная проверка.", СтатусСообщения.Предупреждение);
КонецЕсли;
КонецЦикла;
Сообщить("Завершено обновление статусов в документах 'ОтчетОРозничныхПродажах'. Обновлено: " + СчетчикОбновленных + " документов.");
Если старое перечисление использовалось как измерение или ресурс в каком-либо регистре сведений, нам также потребуется его обновить.
// Продолжение процедуры ВыполнитьМиграциюСтатусовОтчетаОРозничныхПродажах()
// Пример для регистра сведений 'СостояниеОбъектов'
// Предположим, в регистре 'СостояниеОбъектов' было измерение 'Статус'
// типа СтатусыОтчетаОРозничныхПродажах_Удалить.
// И теперь мы хотим его заменить на измерение 'СтатусОбъекта' типа СтатусыКассовойСмены.
ЗапросРС = Новый Запрос;
ЗапросРС.Текст =
"ВЫБРАТЬ
| РегистрСведений.СостояниеОбъектов.Статус КАК СтарыйСтатус,
| РегистрСведений.СостояниеОбъектов.ОбъектСсылка КАК ОбъектСсылка,
| РегистрСведений.СостояниеОбъектов.Период КАК Период,
| РегистрСведений.СостояниеОбъектов.Значение КАК Значение
|ИЗ
| РегистрСведений.СостояниеОбъектов КАК РегистрСведений.СостояниеОбъектов
|ГДЕ
| РегистрСведений.СостояниеОбъектов.Статус ССЫЛКА Перечисление.СтатусыОтчетаОРозничныхПродажах_Удалить";
ВыборкаРС = ЗапросРС.Выполнить().Выбрать();
СчетчикОбновленныхРС = 0;
// Сначала соберем изменения, потом будем записывать,
// чтобы избежать проблем с блокировками или чтением измененных данных.
// Или же используем менеджер записи для каждого набора записей.
МенеджерЗаписи = РегистрыСведений.СостояниеОбъектов.СоздатьМенеджерЗаписи();
НаборЗаписейДляЗаписи = Новый Массив(); // Для групповой записи
Пока ВыборкаРС.Следующий() Цикл
СтарыйСтатусРС = ВыборкаРС.СтарыйСтатус;
Если КартаСопоставленияСтатусов.Ключи().Найти(СтарыйСтатусРС) Тогда
НовыйСтатусРС = КартаСопоставленияСтатусов[СтарыйСтатусРС];
Попытка
// Удаляем старую запись
НаборЗаписей = РегистрыСведений.СостояниеОбъектов.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.ОбъектСсылка.Установить(ВыборкаРС.ОбъектСсылка);
НаборЗаписей.Отбор.Период.Установить(ВыборкаРС.Период);
НаборЗаписей.Отбор.Статус.Установить(СтарыйСтатусРС);
НаборЗаписей.Загрузить();
НаборЗаписей.Очистить();
НаборЗаписей.Записать(); // Удаляем записи по старому статусу
// Создаем новую запись с новым статусом
НоваяЗапись = РегистрыСведений.СостояниеОбъектов.СоздатьЗапись();
НоваяЗапись.ОбъектСсылка = ВыборкаРС.ОбъектСсылка;
НоваяЗапись.Период = ВыборкаРС.Период;
НоваяЗапись.СтатусОбъекта = НовыйСтатусРС; // Предполагаем, что новое измерение называется 'СтатусОбъекта'
НоваяЗапись.Значение = ВыборкаРС.Значение;
НоваяЗапись.Записать();
СчетчикОбновленныхРС = СчетчикОбновленныхРС + 1;
Исключение Как Искл
Сообщить("Ошибка при обновлении записи регистра сведений 'СостояниеОбъектов' для объекта " + ВыборкаРС.ОбъектСсылка + " (Статус: " + СтарыйСтатусРС + "): " + Искл.Описание, СтатусСообщения.Важное);
КонецПопытки;
Иначе
Сообщить("Внимание: для записи регистра сведений 'СостояниеОбъектов' (объект: " + ВыборкаРС.ОбъектСсылка + ") не найдено соответствие для старого статуса '" + СтарыйСтатусРС + "'. Требуется ручная проверка.", СтатусСообщения.Предупреждение);
КонецЕсли;
КонецЦикла;
Сообщить("Завершено обновление статусов в регистре сведений 'СостояниеОбъектов'. Обновлено: " + СчетчикОбновленныхРС + " записей.");
Сообщить("Миграция перечисления 'СтатусыОтчетаОРозничныхПродажах_Удалить' завершена.");
КонецПроцедуры;
Важные моменты при написании кода миграции:
Попытка...Исключение для корректной обработки возможных ошибок и логирования проблемных записей.СтатусыОтчетаОРозничныхПродажах_Удалить из конфигурации.Для очень сложных случаев, когда изменения метаданных глубокие, затрагивают большое количество объектов и требуют сложных правил преобразования, стандартные обработчики обновления могут быть недостаточными. В таких ситуациях мы можем рассмотреть использование инструмента "Конвертация данных". Этот инструмент позволяет нам гибко настраивать правила переноса данных между конфигурациями, сопоставлять объекты, реквизиты и значения по сложным алгоритмам. Однако, его применение требует дополнительных знаний и трудозатрат на настройку правил.
.cf и последующее сравнение с текущей конфигурацией, чтобы система могла корректно присвоить ему ИД и отслеживать его.Таким образом, проблема с сопоставлением вручную добавленных объектов и объектов поставщика при обновлении 1С является распространенной и решаемой. Предпочтительным и наиболее надежным подходом является программная миграция данных с использованием обработчиков обновления, которая позволяет нам полностью контролировать процесс переноса данных и гарантировать их целостность.