Как правильно скопировать номенклатуру вместе с подчиненным справочником рецептур в 1С

Программист 1С v8.3 (Обычные формы) Сфера услуг, туризм и социальный сектор
← На главную

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

Почему подчиненный справочник не копируется автоматически

Рассмотрим механизм интерактивного копирования объектов на уровне платформы 1С:

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

Типичные ошибки при самостоятельной доработке

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

  1. Создание «осиротевших» записей: если мы сгенерируем ссылку заранее или попытаемся записать рецептуру сразу, а пользователь решит закрыть форму без сохранения, в базе останутся рецептуры, ссылающиеся на несуществующую номенклатуру.
  2. Потеря табличных частей: прямое создание объекта через Справочники.РецептурыБлюд.СоздатьЭлемент() переносит только наименование и шапку, оставляя пустой табличную часть с ингредиентами, потерями при обработке и нормами закладки.
  3. Ошибки назначения владельца: если попытаться присвоить в Владелец ссылку на копируемый объект-источник, новая рецептура будет привязана к старому блюду, а не к новому.

Пошаговый алгоритм решения через событие «ПослеЗаписи»

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

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

Посмотрим на пример реализации в модуле объекта справочника Номенклатура:


Перем мОбъектКопирования;

Процедура ПриКопировании(ОбъектКопирования)
    // Запоминаем исходный элемент, с которого создается копия
    мОбъектКопирования = ОбъектКопирования.Ссылка;
КонецПроцедуры

Процедура ПослеЗаписи(Отказ)
    // Если объект создан не копированием или перенос уже выполнен — выходим
    Если Не ЗначениеЗаполнено(мОбъектКопирования) Тогда
        Возврат;
    КонецЕсли;

    // Выбираем все рецептуры, принадлежащие исходному блюду
    ВыборкаРецептур = Справочники.РецептурыБлюд.Выбрать(, мОбъектКопирования);
    НоваяОсновнаяРецептура = Неопределено;

    Пока ВыборкаРецептур.Следующий() Цикл
        // Метод Скопировать() переносит все реквизиты и табличные части состава блюда
        ОбъектРецептуры = ВыборкаРецептур.ПолучитьОбъект();
        НоваяРецептура = ОбъектРецептуры.Скопировать();
        
        // Переназначаем владельца на текущий записанный элемент
        НоваяРецептура.Владелец = ЭтотОбъект.Ссылка;
        НоваяРецептура.Записать();

        // Проверяем, являлась ли эта рецептура основной для старого блюда
        Если ВыборкаРецептур.Ссылка = мОбъектКопирования.ОсновнаяРецептураБлюда Тогда
            НоваяОсновнаяРецептура = НоваяРецептура.Ссылка;
        КонецЕсли;
    КонецЦикла;

    // Если нашли основную рецептуру — обновляем ссылку в текущей номенклатуре
    Если ЗначениеЗаполнено(НоваяОсновнаяРецептура) Тогда
        ЭтотОбъектНоменклатуры = ЭтотОбъект.Ссылка.ПолучитьОбъект();
        ЭтотОбъектНоменклатуры.ОсновнаяРецептураБлюда = НоваяОсновнаяРецептура;
        ЭтотОбъектНоменклатуры.Записать();
    КонецЕсли;

    // Очищаем переменную, чтобы исключить повторное дублирование при следующих записях
    мОбъектКопирования = Неопределено;
КонецПроцедуры

Альтернативный вариант: копирование в единой транзакции

Рассмотрим еще один подход — создание отдельной кнопки в форме списка номенклатуры (например, «Клонировать блюдо со спецификацией»). При этом подходе все действия выполняются в рамках одной транзакции:


Процедура КлонироватьБлюдо(СсылкаНаИсточника)
    НачатьТранзакцию();
    Попытка
        // 1. Создаем и записываем копию номенклатуры
        НовоеБлюдоОбъект = СсылкаНаИсточника.ПолучитьОбъект().Скопировать();
        НовоеБлюдоОбъект.Наименование = НовоеБлюдоОбъект.Наименование + " (копия)";
        НовоеБлюдоОбъект.Записать();

        // 2. Копируем подчиненные рецептуры
        Выборка = Справочники.РецептурыБлюд.Выбрать(, СсылкаНаИсточника);
        Пока Выборка.Следующий() Цикл
            РецептураОбъект = Выборка.ПолучитьОбъект().Скопировать();
            РецептураОбъект.Владелец = НовоеБлюдоОбъект.Ссылка;
            РецептураОбъект.Записать();

            Если Выборка.Ссылка = СсылкаНаИсточника.ОсновнаяРецептураБлюда Тогда
                НовоеБлюдоОбъект.ОсновнаяРецептураБлюда = РецептураОбъект.Ссылка;
            КонецЕсли;
        КонецЦикла;

        НовоеБлюдоОбъект.Записать();
        ЗафиксироватьТранзакцию();

        // Открываем форму созданного элемента пользователю
        НовоеБлюдоОбъект.ПолучитьФорму().Открыть();

    Исключение
        ОтменитьТранзакцию();
        Предупреждение("Ошибка при клонировании блюда: " + ОписаниеОшибки());
    КонецПопытки;
КонецПроцедуры

Использование метода Скопировать() в связке с обработчиком ПослеЗаписи или единой транзакцией гарантирует полное сохранение состава рецептур и целостность ссылочных связей в информационной базе.

← На главную