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

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

При разработке обработок в системе 1С:Предприятие часто возникает задача не просто переместить элементы (что делается простым изменением родителя), а именно скопировать целую ветку справочника. Основная сложность здесь заключается в сохранении структуры подчиненности: если мы просто скопируем папки и элементы, их «родители» в копиях будут по-прежнему указывать на старые папки-источники. Чтобы копия стала независимым деревом, нам необходимо правильно перепривязать каждый вложенный объект к его новому родителю.

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

Метод 1: Иерархический обход с использованием Соответствия

Проанализируем самый надежный способ копирования структуры без использования сложной рекурсии. Этот метод основан на двух проходах или одном последовательном проходе с использованием объекта Соответствие (Mapping). Суть заключается в том, чтобы хранить связь между «старой» ссылкой и «новой» созданной ссылкой.

Разберем алгоритм по шагам:

  1. Выбираем все элементы и группы из папки-источника иерархически.
  2. Для каждого объекта создаем копию методом Скопировать().
  3. Если у объекта родитель — это наша исходная папка, меняем его на целевую папку-приемник.
  4. Если у объекта родитель — это одна из уже скопированных папок внутри иерархии, мы находим новую ссылку на эту папку в нашем Соответствии и подставляем ее.

Посмотрим на пример реализации такого кода:


// Источник - ссылка на папку, которую копируем
// Приемник - ссылка на папку, КУДА копируем

СоответствиеСсылок = Новый Соответствие;
// Помещаем в соответствие корень, чтобы алгоритм корректно обработал первый уровень
СоответствиеСсылок.Вставить(Источник, Приемник);

Выборка = Справочники.Номенклатура.ВыбратьИерархически(Источник);

// Пропускаем саму папку-источник в выборке, если она там есть
Пока Выборка.Следующий() Цикл
    Если Выборка.Ссылка = Источник Тогда
        Продолжить;
    КонецЕсли;

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

    // Важный нюанс: очистка кода для автогенерации нового
    НовыйОбъект.УстановитьНовыйКод();
    
    НовыйОбъект.Записать();
    
    // Если это была группа, запоминаем связь "СтараяСсылка -> НоваяСсылка"
    Если Выборка.ЭтоГруппа Тогда
        СоответствиеСсылок.Вставить(Выборка.Ссылка, НовыйОбъект.Ссылка);
    КонецЕсли;
КонецЦикла;

Метод 2: Рекурсивный обход справочника

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

Разберем структуру рекурсивной функции:


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

Этот метод интуитивно понятен, однако стоит помнить, что при очень большой вложенности папок (сотни уровней) теоретически можно столкнуться с переполнением стека, хотя в реальных базах 1С такая иерархия встречается крайне редко.

Важные нюансы при копировании справочников

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

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

Использование запроса для оптимизации

Если задача стоит максимально быстро получить список всех объектов для копирования, лучше использовать Запрос с условием В ИЕРАРХИИ (&Источник). Это позволит одним обращением к БД получить все данные, отсортировать их по уровню иерархии и приступить к созданию копий.

Посмотрим, как выглядит подготовка данных через запрос:


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |   Номенклатура.Ссылка КАК Ссылка,
    |   Номенклатура.Родитель КАК Родитель,
    |   Номенклатура.ЭтоГруппа КАК ЭтоГруппа,
    |   Номенклатура.Уровень КАК Уровень
    |ИЗ
    |   Справочник.Номенклатура КАК Номенклатура
    |ГДЕ
    |   Номенклатура.Ссылка В ИЕРАРХИИ(&Источник)
    |   И Номенклатура.Ссылка <> &Источник
    |УПОРЯДОЧИТЬ ПО
    |   Уровень ВОЗР"; // Важно: сначала создаем верхние уровни
    
Запрос.УстановитьПараметр("Источник", Источник);
РезультатЗапроса = Запрос.Выполнить();

Использование сортировки по Уровню гарантирует, что к моменту копирования любого дочернего элемента его новый родитель (папка) уже будет создан и записан в базу, что крайне важно для корректного заполнения Соответствия.

Подводя итог, можно сказать, что выбор метода зависит от объема данных. Для небольших структур подойдет рекурсия, для массового копирования номенклатуры с вложениями лучше использовать плоский список (запрос) с сортировкой по уровням и Соответствие для подмены ссылок.

← На главную