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