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