Как при обновлении 1С принудительно объединить вручную добавленный объект с новым объектом поставщика без потери данных?

Программист 1С v8.3 (Управляемые формы) 1С:Управление нашей фирмой Управленческий учет
← На главную

При работе с конфигурациями 1С нередко возникают ситуации, когда для оперативного решения задач или добавления новой функциональности разработчики вручную добавляют или копируют объекты метаданных, которые еще отсутствуют в типовой конфигурации поставщика, но планируются к появлению в будущих релизах. Такой подход, хоть и позволяет быстро внедрить необходимые механизмы, создает серьезные сложности при последующих обновлениях. Система 1С, при попытке обновить конфигурацию, может не распознать эти вручную добавленные объекты как аналоги новых объектов поставщика, предлагая удалить старые и добавить новые, что грозит потерей данных и необходимостью ручной доработки.

Рассмотрим подробно, как мы можем решить проблему сопоставления вручную добавленного перечисления СтатусыОтчетаОРозничныхПродажах с появившимся в новом релизе поставщика перечислением СтатусыКассовойСмены, которое по сути является тем же объектом, но с другим названием и внутренним идентификатором.

Основная причина конфликтов при обновлении

Прежде чем перейти к решениям, давайте выясним, почему возникает данная проблема. Система 1С для идентификации объектов метаданных использует не только их имя, но и внутренний уникальный идентификатор (UUID). Когда мы вручную добавляем новый объект (например, создавая перечисление с нуля) или копируем его из другой конфигурации через буфер обмена, этому объекту присваивается новый, уникальный UUID. Даже если мы назовем его так же, как будущий объект поставщика, или даже если система поставщика в итоге введет объект с таким же названием, их UUID будут различаться.

При обновлении конфигурации 1С сравнивает метаданные текущей конфигурации с метаданными конфигурации поставщика. Если она видит объект с новым UUID в конфигурации поставщика и не находит соответствующего объекта в нашей конфигурации (или находит объект с таким же именем, но другим UUID), она предлагает:

  1. Удалить наш "несопоставленный" объект (если он есть).
  2. Добавить новый объект поставщика.

В результате, все данные, ссылавшиеся на вручную добавленный объект, могут быть утеряны или станут некорректными, если мы просто удалим старый объект и добавим новый.

Решение 1: Интерактивное сопоставление объектов в режиме сравнения/объединения

Система 1С предоставляет механизм для ручного сопоставления объектов метаданных в процессе сравнения и объединения конфигураций. Этот подход может быть полезен, когда мы хотим "подсказать" системе, что наш существующий объект соответствует новому объекту поставщика, несмотря на различия в именах или UUID.

  1. Запускаем процесс обновления. Применяем файл обновления поставщика (.cfu) или сравниваем конфигурацию с конфигурацией поставщика.
  2. Открываем окно сравнения и объединения. В этом окне мы увидим список всех объектов метаданных и их состояния (добавлены, изменены, удалены).
  3. Находим несопоставленные объекты. В вашем случае, вы увидите ваше вручную добавленное перечисление СтатусыОтчетаОРозничныхПродажах без "желтого бочонка" (показывает, что это не объект поставщика) и новое перечисление СтатусыКассовойСмены от поставщика, которое предлагается добавить как новое.
  4. Выполняем ручное сопоставление. Для этого мы выбираем одно из несопоставленных перечислений (например, ваше старое СтатусыОтчетаОРозничныхПродажах), нажимаем на него правой кнопкой мыши и выбираем пункт контекстного меню "Установить соответствие объектов...".
  5. Выбираем соответствующий объект. В появившемся диалоговом окне система 1С попытается предложить объекты, которые, по ее мнению, могут быть сопоставлены. Однако, как вы уже заметили в исходной ситуации, она может предлагать совершенно другие, нерелевантные объекты. Это происходит из-за значительных различий в UUID и, возможно, даже в структуре. Крайне важно убедиться, что вы выбираете именно тот объект поставщика, который является функциональным аналогом вашего вручную добавленного объекта (в нашем случае, СтатусыКассовойСмены).
  6. Подтверждаем сопоставление. После выбора соответствующего объекта, система 1С попытается выполнить сопоставление. Если сопоставление успешно, тогда система будет рассматривать ваш объект как объект поставщика, и в дальнейшем при обновлениях он будет вести себя как типовой объект.

Важные замечания по этому методу:

Решение 2: Программная миграция данных с использованием обработчиков обновления

Этот подход является более надежным и управляемым, особенно в сложных случаях или когда интерактивное сопоставление невозможно. Он включает в себя программный перенос всех существующих данных из старого, вручную добавленного объекта в новый объект, который мы примем от поставщика.

2.1. Подготовка к миграции

  1. Выполняем резервное копирование. Перед любыми манипуляциями с конфигурацией и данными, всегда создаем полную резервную копию информационной базы. Это наша страховка на случай непредвиденных проблем.
  2. Принимаем новый объект поставщика. При обновлении конфигурации мы соглашаемся на добавление нового перечисления СтатусыКассовойСмены от поставщика.
  3. Сохраняем старый объект и избегаем конфликтов. Мы не удаляем вручную добавленное перечисление СтатусыОтчетаОРозничныхПродажах сразу. Вместо этого, чтобы избежать возможных конфликтов имен с будущими версиями конфигурации (особенно если объект поставщика позже получит такое же имя), мы можем временно изменить имя нашего старого перечисления, добавив к нему префикс, например, _Удалить_СтатусыОтчетаОРозничныхПродажах или СтатусыОтчетаОРозничныхПродажах_Удалить. Это позволит нашему обработчику обновления получить к нему доступ.

2.2. Разработка обработчика обновления

Нам потребуется создать специальный обработчик обновления конфигурации, который будет выполняться один раз после обновления информационной базы до новой версии. Этот обработчик будет отвечать за перенос данных.

  1. Место размещения обработчика. Мы можем разместить код обработчика в одном из общих модулей, предназначенных для обновления конфигурации. Часто для этого используется общий модуль, входящий в состав подсистемы "СтандартныеПодсистемы" (например, ОбновлениеИнформационнойБазыБСП или аналогичный), или создаем свой специализированный общий модуль, например, ОбработчикиОбновленияКонфигурации_Расширенная, если мы работаем в модифицированной конфигурации.

    Наш обработчик должен быть вызван в определенный момент обновления. Обычно для этого используется процедура в таком модуле, которая вызывается из подсистемы обновления при достижении определенной версии конфигурации.

    
    // Пример общего модуля для обработчиков обновления (например, ОбновлениеИнформационнойБазы)
    // В модуле объекта Подсистема.СтандартныеПодсистемы.ОбработчикиОбновления (или аналогичном)
    // или в вашем кастомном общем модуле, предназначенном для таких целей.
    
    Процедура ПриОбновленииКонфигурации(ПараметрыОбновления) Экспорт
        // Пример использования для конкретной версии
        // Допустим, ваше обновление, где появилось СтатусыКассовойСмены, имеет версию 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;
    КонецФункции;
    
  2. Принцип сопоставления значений. Внутри обработчика мы должны создать "карту" соответствия между значениями старого перечисления и значениями нового перечисления. Это может быть реализовано с помощью объекта Соответствие.
    
    // В том же общем модуле, где и ПриОбновленииКонфигурации
    
    Процедура ВыполнитьМиграциюСтатусовОтчетаОРозничныхПродажах()
        Сообщить("Начало миграции перечисления 'СтатусыОтчетаОРозничныхПродажах_Удалить' на 'СтатусыКассовойСмены'.");
    
        // Создаем соответствие старых и новых значений перечислений
        // Предполагаем, что старое перечисление было переименовано в СтатусыОтчетаОРозничныхПродажах_Удалить
        КартаСопоставленияСтатусов = Новый Соответствие();
    
        // Пример сопоставления значений. Мы должны вручную определить, какое старое значение
        // соответствует какому новому значению.
        // Если в старом перечислении были значения, не имеющие аналогов в новом,
        // нам нужно решить, что с ними делать (например, присвоить значение по умолчанию или игнорировать).
    
        // Старые значения (из СтатусыОтчетаОРозничныхПродажах_Удалить) : Новые значения (из СтатусыКассовойСмены)
        КартаСопоставленияСтатусов.Вставить(Перечисления.СтатусыОтчетаОРозничныхПродажах_Удалить.Принят,                 Перечисления.СтатусыКассовойСмены.Открыта);
        КартаСопоставленияСтатусов.Вставить(Перечисления.СтатусыОтчетаОРозничныхПродажах_Удалить.Отправлен,              Перечисления.СтатусыКассовойСмены.Закрыта);
        КартаСопоставленияСтатусов.Вставить(Перечисления.СтатусыОтчетаОРозничныхПродажах_Удалить.К_Оформлению,          Перечисления.СтатусыКассовойСмены.ПодготовленаКЗакрытию);
        // Добавляем все остальные значения, которые были в старом перечислении.
        // Если какие-то старые значения не имеют прямого аналога, нам нужно решить,
        // к какому новому статусу их привести или оставить пустыми (если это допустимо).
    
        // Продолжение ниже, в разделе поиска и замены ссылок
    КонецПроцедуры;
    
  3. Поиск и замена ссылок. Это самый трудоемкий, но и самый важный шаг. Мы должны найти все места в информационной базе, где использовалось наше старое перечисление СтатусыОтчетаОРозничныхПродажах_Удалить, и заменить его на соответствующее значение из нового перечисления СтатусыКассовойСмены.

    Для поиска мест использования старого перечисления мы можем:

    1. Использовать глобальный поиск по конфигурации. Открываем конфигуратор, выбираем "Конфигурация" -> "Поиск по всей конфигурации..." (Ctrl+Shift+F). Ищем упоминания старого перечисления или его реквизитов. Это поможет нам определить, в каких объектах (документах, регистрах, константах, справочниках) оно используется.
    2. Выполнять запросы к метаданным. Программно мы можем получить список всех объектов, у которых есть реквизит типа нашего старого перечисления.

    После того, как мы определили все места использования, нам нужно написать код для их обновления. Рассмотрим пример для документов, где использовался старый статус, и для регистров сведений.

    Пример кода миграции для документов

    Предположим, старый статус использовался в документе ОтчетОРозничныхПродажах, и новый статус должен быть записан в новый реквизит этого же документа или в аналогичный реквизит, но уже нового типа.

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

    Пример кода миграции для регистров сведений

    Если старое перечисление использовалось как измерение или ресурс в каком-либо регистре сведений, нам также потребуется его обновить.

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

    Важные моменты при написании кода миграции:

    • Итерации по данным: Для больших объемов данных используем пакетные запросы или итерации с ограничением выборки, чтобы не перегружать память.
    • Транзакции: Если изменения касаются большого количества объектов, оберните операции в транзакции, чтобы обеспечить атомарность. Однако для обработчиков обновления обычно избегают больших транзакций, чтобы не блокировать базу надолго. Лучше делать небольшие транзакции или записывать каждый объект отдельно.
    • Обработка ошибок: Используйте блоки Попытка...Исключение для корректной обработки возможных ошибок и логирования проблемных записей.
    • Производительность: Запросы должны быть оптимизированы. Избегайте получения всех объектов в память сразу.
    • Ссылки на таблицы значений: Если старое перечисление хранилось в табличных частях документов, нам нужно будет получать объект документа, изменять его табличную часть и записывать документ.

2.3. Тестирование и завершение

  1. Тщательное тестирование. После выполнения обработчика обновления мы должны тщательно протестировать систему. Открываем документы, где использовались старые статусы, проверяем регистры, отчеты. Убеждаемся, что все данные корректно перенесены и система работает без ошибок.
  2. Удаление старого объекта. После того как мы убедились в успешности миграции и отсутствии проблем, мы можем окончательно удалить переименованное перечисление СтатусыОтчетаОРозничныхПродажах_Удалить из конфигурации.

Решение 3: Применение "Конвертации данных" для комплексных сценариев

Для очень сложных случаев, когда изменения метаданных глубокие, затрагивают большое количество объектов и требуют сложных правил преобразования, стандартные обработчики обновления могут быть недостаточными. В таких ситуациях мы можем рассмотреть использование инструмента "Конвертация данных". Этот инструмент позволяет нам гибко настраивать правила переноса данных между конфигурациями, сопоставлять объекты, реквизиты и значения по сложным алгоритмам. Однако, его применение требует дополнительных знаний и трудозатрат на настройку правил.

Общие рекомендации и лучшие практики

  1. Всегда делайте резервное копирование. Это самое важное правило перед любыми обновлениями или изменениями конфигурации.
  2. Избегайте ручного копирования метаданных. По возможности, старайтесь не добавлять объекты вручную без использования штатных механизмов сравнения и объединения. Если необходимо добавить новую функциональность, которая впоследствии появится в типовой конфигурации, рассмотрите следующие варианты:
    • Используйте расширения конфигурации. Расширения позволяют добавлять новую функциональность или модифицировать существующую без изменения основной конфигурации. Это значительно упрощает обновления. Однако, если объект, который вы модифицируете через расширение, меняется в конфигурации поставщика, все равно могут возникнуть конфликты.
    • Добавляйте объекты через сравнение/объединение. Даже если вы копируете объект из другой конфигурации, делайте это через сохранение объекта в файл .cf и последующее сравнение с текущей конфигурацией, чтобы система могла корректно присвоить ему ИД и отслеживать его.
  3. Планируйте обновления. Если вы знаете, что в будущих релизах ожидаются изменения, которые могут конфликтовать с вашими доработками, заранее спланируйте стратегию миграции данных.
  4. Документируйте доработки. Ведите подробный учет всех ручных изменений, добавленных объектов и их предполагаемых аналогов в типовой конфигурации. Это значительно облегчит работу при будущих обновлениях.

Таким образом, проблема с сопоставлением вручную добавленных объектов и объектов поставщика при обновлении 1С является распространенной и решаемой. Предпочтительным и наиболее надежным подходом является программная миграция данных с использованием обработчиков обновления, которая позволяет нам полностью контролировать процесс переноса данных и гарантировать их целостность.

← На главную