При интерактивном копировании элементов справочника Номенклатура разработчики часто сталкиваются с ситуацией, когда данные самого блюда копируются успешно, а список подчиненных элементов (например, рецептуры, спецификации или единицы измерения) остается пустым. Разберем архитектурные особенности платформы 1С, выясним причину такого поведения и реализуем надежный программный алгоритм дублирования связанных данных.
Рассмотрим механизм интерактивного копирования объектов на уровне платформы 1С:
Ссылка пустая).РецептурыБлюд) жестко привязан к владельцу через обязательный реквизит Владелец, который требует валидную ссылку на родительский элемент.Проанализируем частую ошибку, которая возникает при попытке выполнить копирование в обработчике ПриОткрытии или ПриКопировании. Попытка создать подчиненную рецептуру до записи самого блюда приводит к логическим сбоям:
Справочники.РецептурыБлюд.СоздатьЭлемент() переносит только наименование и шапку, оставляя пустой табличную часть с ингредиентами, потерями при обработке и нормами закладки.Владелец ссылку на копируемый объект-источник, новая рецептура будет привязана к старому блюду, а не к новому.Разберем наиболее надежный и безопасный способ реализации, при котором подчиненные рецептуры переносятся только после того, как новая номенклатура гарантированно сохранена в базе данных.
Номенклатура перехватываем событие ПриКопировании и сохраняем ссылку на исходный элемент в переменную модуля.ПослеЗаписи проверяем факт копирования, считываем все подчиненные рецептуры исходного блюда и дублируем их методом Скопировать(), подставляя нового владельца.ОсновнаяРецептураБлюда номенклатуры.Посмотрим на пример реализации в модуле объекта справочника Номенклатура:
Перем мОбъектКопирования;
Процедура ПриКопировании(ОбъектКопирования)
// Запоминаем исходный элемент, с которого создается копия
мОбъектКопирования = ОбъектКопирования.Ссылка;
КонецПроцедуры
Процедура ПослеЗаписи(Отказ)
// Если объект создан не копированием или перенос уже выполнен — выходим
Если Не ЗначениеЗаполнено(мОбъектКопирования) Тогда
Возврат;
КонецЕсли;
// Выбираем все рецептуры, принадлежащие исходному блюду
ВыборкаРецептур = Справочники.РецептурыБлюд.Выбрать(, мОбъектКопирования);
НоваяОсновнаяРецептура = Неопределено;
Пока ВыборкаРецептур.Следующий() Цикл
// Метод Скопировать() переносит все реквизиты и табличные части состава блюда
ОбъектРецептуры = ВыборкаРецептур.ПолучитьОбъект();
НоваяРецептура = ОбъектРецептуры.Скопировать();
// Переназначаем владельца на текущий записанный элемент
НоваяРецептура.Владелец = ЭтотОбъект.Ссылка;
НоваяРецептура.Записать();
// Проверяем, являлась ли эта рецептура основной для старого блюда
Если ВыборкаРецептур.Ссылка = мОбъектКопирования.ОсновнаяРецептураБлюда Тогда
НоваяОсновнаяРецептура = НоваяРецептура.Ссылка;
КонецЕсли;
КонецЦикла;
// Если нашли основную рецептуру — обновляем ссылку в текущей номенклатуре
Если ЗначениеЗаполнено(НоваяОсновнаяРецептура) Тогда
ЭтотОбъектНоменклатуры = ЭтотОбъект.Ссылка.ПолучитьОбъект();
ЭтотОбъектНоменклатуры.ОсновнаяРецептураБлюда = НоваяОсновнаяРецептура;
ЭтотОбъектНоменклатуры.Записать();
КонецЕсли;
// Очищаем переменную, чтобы исключить повторное дублирование при следующих записях
мОбъектКопирования = Неопределено;
КонецПроцедуры
Рассмотрим еще один подход — создание отдельной кнопки в форме списка номенклатуры (например, «Клонировать блюдо со спецификацией»). При этом подходе все действия выполняются в рамках одной транзакции:
Процедура КлонироватьБлюдо(СсылкаНаИсточника)
НачатьТранзакцию();
Попытка
// 1. Создаем и записываем копию номенклатуры
НовоеБлюдоОбъект = СсылкаНаИсточника.ПолучитьОбъект().Скопировать();
НовоеБлюдоОбъект.Наименование = НовоеБлюдоОбъект.Наименование + " (копия)";
НовоеБлюдоОбъект.Записать();
// 2. Копируем подчиненные рецептуры
Выборка = Справочники.РецептурыБлюд.Выбрать(, СсылкаНаИсточника);
Пока Выборка.Следующий() Цикл
РецептураОбъект = Выборка.ПолучитьОбъект().Скопировать();
РецептураОбъект.Владелец = НовоеБлюдоОбъект.Ссылка;
РецептураОбъект.Записать();
Если Выборка.Ссылка = СсылкаНаИсточника.ОсновнаяРецептураБлюда Тогда
НовоеБлюдоОбъект.ОсновнаяРецептураБлюда = РецептураОбъект.Ссылка;
КонецЕсли;
КонецЦикла;
НовоеБлюдоОбъект.Записать();
ЗафиксироватьТранзакцию();
// Открываем форму созданного элемента пользователю
НовоеБлюдоОбъект.ПолучитьФорму().Открыть();
Исключение
ОтменитьТранзакцию();
Предупреждение("Ошибка при клонировании блюда: " + ОписаниеОшибки());
КонецПопытки;
КонецПроцедуры
Использование метода Скопировать() в связке с обработчиком ПослеЗаписи или единой транзакцией гарантирует полное сохранение состава рецептур и целостность ссылочных связей в информационной базе.