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