Как правильно реализовать копирование связанных данных из регистра сведений при копировании элемента справочника

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

При разработке в системе 1С:Предприятие 8.3 часто возникает задача: необходимо при интерактивном копировании элемента справочника (например, «Контрагенты» или «Номенклатура») автоматически скопировать данные из связанного с ним независимого регистра сведений. Типичная ошибка начинающих разработчиков — попытка записать новый объект непосредственно в обработчике ПриКопировании, что приводит к нарушению логики работы экранных форм и ошибкам вроде «Код не уникален».

В этой статье мы подробно разберем, почему стандартный подход с немедленной записью не работает, и рассмотрим правильную методику реализации этой задачи с использованием коллекции ДополнительныеСвойства и транзакционных событий модуля объекта.

Анализ проблемы: почему нельзя вызывать Записать() в событии ПриКопировании

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

Если мы вызываем ЭтотОбъект.Записать() внутри этого события, происходит следующее:

  1. Система принудительно фиксирует объект в базе данных.
  2. Объекту присваивается код (или номер) и ссылка (GUID).
  3. После завершения обработчика 1С открывает форму нового элемента.

Однако форма «думает», что она работает с новым, еще не созданным объектом. Когда пользователь нажимает в форме кнопку «Записать и закрыть», платформа пытается снова записать объект как новый. Поскольку объект с таким кодом или ссылкой уже существует в базе (вы записали его программно в ПриКопировании), возникает конфликт уникальности. Кроме того, пользователь теряет возможность полноценно редактировать объект до его первой «официальной» записи через интерфейс.

Решение №1: Использование коллекции «ДополнительныеСвойства» (Рекомендуемое)

Наиболее надежный и архитектурно верный способ — передать информацию о том, что объект является копией, из события ПриКопировании в событие ПриЗаписи. Для этого идеально подходит коллекция ДополнительныеСвойства, которая «живет» вместе с экземпляром объекта в памяти, пока он открыт.

Рассмотрим пошагово реализацию в Модуле объекта справочника:

Шаг 1. Передача ссылки на оригинал. В обработчике ПриКопировании мы не производим никаких действий с базой данных, а просто запоминаем, из какого объекта создается копия.


Процедура ПриКопировании(ОбъектКопирования)
    // Сохраняем ссылку на оригинал в структуру ДополнительныеСвойства
    ДополнительныеСвойства.Вставить("ИсточникКопирования", ОбъектКопирования);
КонецПроцедуры

Шаг 2. Копирование записей регистра. Теперь в обработчике ПриЗаписи, который выполняется в одной транзакции с сохранением основного объекта, мы проверяем наличие нашего свойства и переносим данные из регистра.


Процедура ПриЗаписи(Отказ)
    // Проверяем, не является ли это первой записью после копирования
    Если ДополнительныеСвойства.Свойство("ИсточникКопирования") Тогда
        Источник = ДополнительныеСвойства.ИсточникКопирования;
        
        // Выполняем копирование данных из РС
        КопироватьЗаписиРегистра(Источник, ЭтотОбъект.Ссылка);
        
        // Удаляем свойство, чтобы при последующих изменениях записи не дублировались
        ДополнительныеСвойства.Удалить("ИсточникКопирования");
    КонецЕсли;
КонецПроцедуры

Процедура КопироватьЗаписиРегистра(Источник, НовыйВладелец)
    // Предположим, РС называется "РС1", а ведущее измерение "Объект"
    НаборЗаписейОригинал = РегистрыСведений.РС1.СоздатьНаборЗаписей();
    НаборЗаписейОригинал.Отбор.Объект.Установить(Источник);
    НаборЗаписейОригинал.Прочитать();
    
    Если НаборЗаписейОригинал.Количество() > 0 Тогда
        НаборЗаписейНовый = РегистрыСведений.РС1.СоздатьНаборЗаписей();
        НаборЗаписейНовый.Отбор.Объект.Установить(НовыйВладелец);
        
        Для Каждого ЗаписьИзОригинала Из НаборЗаписейОригинал Цикл
            НоваяЗапись = НаборЗаписейНовый.Добавить();
            ЗаполнитьЗначенияСвойств(НоваяЗапись, ЗаписьИзОригинала);
            // Устанавливаем привязку к новому элементу справочника
            НоваяЗапись.Объект = НовыйВладелец;
        КонецЦикла;
        
        НаборЗаписейНовый.Записать();
    КонецЕсли;
КонецПроцедуры

Этот метод гарантирует транзакционную целостность: если запись справочника сорвется, записи в регистре также не будут созданы.

Решение №2: Использование «УстановитьСсылкуНового()»

Если специфика вашей задачи требует, чтобы записи в регистре были созданы до момента записи основного объекта (что бывает крайне редко), можно воспользоваться методом УстановитьСсылкуНового(). Разберем, как это работает.

Функция УстановитьСсылкуНового(Ссылка) позволяет программисту заранее определить, какой уникальный идентификатор (GUID) будет у объекта после его записи. Мы можем создать эту ссылку в обработчике ПриКопировании и использовать её как ключ для записей в регистре.


Процедура ПриКопировании(ОбъектКопирования)
    // Генерируем новую ссылку программно
    НоваяСсылка = Справочники.Спр1.ПолучитьСсылку(Новый УникальныйИдентификатор);
    ЭтотОбъект.УстановитьСсылкуНового(НоваяСсылка);
    
    // Теперь мы можем использовать НоваяСсылка для создания записей в РС
    // ВАЖНО: записи в независимом РС появятся сразу, даже если пользователь нажмет "Отмена"
    КопироватьДанныеВРегистрСразу(ОбъектКопирования, НоваяСсылка);
КонецПроцедуры

Внимание: Этот метод потенциально опасен. Если пользователь закроет форму справочника без сохранения, записи в независимом регистре сведений останутся «висеть» в базе, ссылаясь на несуществующий элемент справочника (так называемые «битые ссылки»). Поэтому данный подход крайне не рекомендуется для независимых регистров, если только вы не планируете реализовывать сложную систему очистки «мусора».

Решение №3: Работа на уровне формы (Управляемое приложение)

Проанализируем ситуацию, когда нам нужно показать пользователю скопированные данные в интерфейсе еще до нажатия кнопки «Записать». В этом случае логику следует перенести в Модуль формы.

В управляемых формах существует параметр ПараметрКопирования. Мы можем перехватить его в процедуре ПриСозданииНаСервере.

  1. В процедуре ПриСозданииНаСервере проверяем Параметры.ЗначениеЗаполнения (или ПараметрКопирования в зависимости от версии платформы и способа вызова).
  2. Считываем данные из регистра сведений оригинала.
  3. Заполняем ими временную таблицу на форме (реквизит формы типа «ТаблицаЗначений»).
  4. В процедуре ПослеЗаписиНаСервере считываем данные из этой таблицы и физически записываем их в регистр сведений, используя уже готовую ссылку Объект.Ссылка.

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

Важные особенности ведущего измерения

Вы упоминали, что в вашем регистре сведений РС1 измерение, ссылающееся на справочник, является ведущим. Давайте выясним, на что это влияет:

Резюме и рекомендации

Подводя итог, выясним лучший алгоритм действий для вашей задачи:

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

Применяя данные методики, вы избавитесь от ошибки «Код не уникален» и обеспечите стабильную работу вашей конфигурации при интерактивных действиях пользователей.

← На главную