Как переместить элемент справочника 1С в подгруппу и исправить ошибку «Поле объекта не доступно для записи (Родитель)»

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

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

В этой статье мы подробно разберем, почему возникает данная ошибка, как устроена иерархия в платформе 1С на физическом уровне, и рассмотрим три надежных способа переноса элементов — от простого кода до высокопроизводительной обработки больших массивов данных (более 30 000 позиций) и готовых платформенных инструментов.

Выясняем причину: почему возникает ошибка «Поле объекта не доступно для записи»?

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


Ном = Справочники.Номенклатура.НайтиПоНаименованию("Бумага А4");
НоменклатураОбъект = Ном.ПолучитьОбъект();

// Попытка записать нового родителя через цепочку
НоменклатураОбъект.Родитель.Родитель = ГруппаЦелевая; 
НоменклатураОбъект.Записать();

При выполнении этой инструкции платформа мгновенно выдает ошибку записи реквизита Родитель. Давайте проанализируем, почему это происходит:

  1. Разница между Ссылкой и Объектом. В системе 1С существует жесткое разделение типов данных на ссылочные (СправочникСсылка) и объектные (СправочникОбъект). Изменять реквизиты базы данных (такие как наименование, код, родитель, дополнительные свойства) можно только у объектов, полученных с помощью метода ПолучитьОбъект(). Ссылки предназначены исключительно для чтения и отображения данных.
  2. Мутация через ссылку. В коде выше мы успешно получили объект самого элемента номенклатуры — переменную НоменклатураОбъект. Но когда мы пишем НоменклатураОбъект.Родитель, система возвращает нам ссылку на родительскую группу (тип СправочникСсылка). Пытаясь обратиться к Родитель.Родитель, мы совершаем попытку записи нового значения в свойство ссылки, которая по своей природе доступна только для чтения.
  3. Ошибочная логика обхода дерева. Разработчик интуитивно пытается «пройти» вверх или вниз по иерархическому дереву. Но в архитектуре 1С нет необходимости «прокладывать маршрут» через промежуточные папки. Чтобы переместить элемент на абсолютно любой уровень вложенности, достаточно работать только с самим элементом.

Как устроена иерархия в СУБД под управлением 1С

В отличие от файловой системы вашего компьютера, где папки физически содержат внутри себя файлы, база данных 1С хранит справочники в виде «плоских» таблиц. Каждый элемент или группа — это просто отдельная строка в таблице СУБД.

Связь между папками и элементами реализуется через единственный реквизит — Родитель. В этой колонке для каждого элемента хранится уникальный идентификатор (GUID) той группы, в которой он непосредственно находится. Если элемент лежит в корне справочника, его реквизит Родитель содержит пустую ссылку (Справочники.Номенклатура.ПустаяСсылка()).

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

Способ 1: Программный перенос одного элемента (классический вариант)

Рассмотрим правильный шаблон кода для переноса одного элемента номенклатуры в другую группу. Проанализируем пример:


// 1. Находим элемент номенклатуры, который требуется переместить
СсылкаНаТовар = Справочники.Номенклатура.НайтиПоНаименованию("Альбом для рисования");

Если Не СсылкаНаТовар.Пустая() Тогда
    
    // 2. Получаем объект для изменения данных
    ТоварОбъект = СсылкаНаТовар.ПолучитьОбъект();
    
    // 3. Находим целевую группу-подгруппу (получаем именно ССЫЛКУ, объект получать не нужно)
    СсылкаНаГруппу = Справочники.Номенклатура.НайтиПоНаименованию("Канцелярские товары");
    
    Если Не СсылкаНаГруппу.Пустая() Тогда
        // 4. Напрямую переопределяем родителя у объекта товара
        ТоварОбъект.Родитель = СсылкаНаГруппу;
        
        // 5. Записываем изменения в базу данных
        Попытка
            ТоварОбъект.Записать();
            Сообщить("Элемент успешно перемещен!");
        Исключение
            Сообщить("Не удалось записать элемент. Ошибка: " + ОписаниеОшибки());
        КонецПопытки;
    Иначе
        Сообщить("Целевая подгруппа не найдена в информационной базе!");
    КонецЕсли;
    
КонецЕсли;

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

Способ 2: Оптимизированный пакетный перенос данных (для объемов от 30 000 позиций)

Если вам необходимо сделать пересортировку огромного каталога товаров (например, перенести 37 000 позиций из старых папок в новые на основе данных из файла Excel), классический код, описанный в первом способе, приведет к критическому падению производительности или зависанию системы. Поиск по наименованию в цикле и одиночная запись каждого объекта займут долгие часы.

Давайте разберем основные правила оптимизации таких операций:

Рассмотрим пример профессионального кода для оптимизированного переноса данных:


// Предположим, у нас есть ТаблицаНоменклатуры, полученная при чтении Excel, 
// с колонками "КодТовара" и "КодНовойГруппы".

Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |	ВременнаяТаблица.КодТовара КАК КодТовара,
    |	ВременнаяТаблица.КодНовойГруппы КАК КодНовойГруппы,
    |	СпрНоменклатура.Ссылка КАК ТоварСсылка,
    |	СпрГруппы.Ссылка КАК ГруппаСсылка
    |ИЗ
    |	&ВременныйИсточник КАК ВременнаяТаблица
    |	ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Номенклатура КАК СпрНоменклатура
    |		ПО ВременнаяТаблица.КодТовара = СпрНоменклатура.Код
    |		И НЕ СпрНоменклатура.ЭтоГруппа
    |	ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Номенклатура КАК СпрГруппы
    |		ПО ВременнаяТаблица.КодНовойГруппы = СпрГруппы.Код
    |		И СпрГруппы.ЭтоГруппа";

Запрос.УстановитьПараметр("ВременныйИсточник", ТаблицаНоменклатуры);
РезультатЗапроса = Запрос.Выполнить();
Выборка = РезультатЗапроса.Выбрать();

// Начинаем пакетную обработку в транзакции
Счетчик = 0;
РазмерПорции = 1000;

НачатьТранзакцию();

Пока Выборка.Следующий() Цикл
    
    Если ЗначениеЗаполнено(Выборка.ТоварСсылка) И ЗначениеЗаполнено(Выборка.ГруппаСсылка) Тогда
        
        // Получаем объект номенклатуры
        ТоварОбъект = Выборка.ТоварСсылка.ПолучитьОбъект();
        
        Если ТоварОбъект <> Неопределено Тогда
            
            // Назначаем нового родителя
            ТоварОбъект.Родитель = Выборка.ГруппаСсылка;
            
            // Отключаем лишние контроли платформы для ускорения записи
            ТоварОбъект.ОбменДанными.Загрузка = Истина;
            
            Попытка
                ТоварОбъект.Записать();
                Счетчик = Счетчик + 1;
            Исключение
                // Логируем ошибки записи отдельных элементов, не прерывая весь процесс
                ЗаписьЖурналаРегистрации("ПереносНоменклатуры", 
                    УровеньЖурналаРегистрации.Предупреждение, , , 
                    "Ошибка переноса товара с кодом: " + Выборка.КодТовара + ". Описание: " + ОписаниеОшибки());
            КонецПопытки;
            
        КонецЕсли;
        
    КонецЕсли;
    
    // Периодически фиксируем транзакцию, чтобы не переполнять лог транзакций СУБД
    Если Счетчик % РазмерПорции = 0 И Счетчик > 0 Тогда
        ЗафиксироватьТранзакцию();
        НачатьТранзакцию();
    КонецЕсли;
    
КонецЦикла;

// Закрываем финальную открытую транзакцию
Если ТранзакцияАктивна() Тогда
    ЗафиксироватьТранзакцию();
КонецЕсли;

Сообщить("Успешно перенесено элементов: " + Счетчик);

Способ 3: Групповое изменение реквизитов (Решение без программирования)

Если перед вами стоит задача разовой пересортировки товаров внутри базы данных, писать собственный программный код часто вовсе не обязательно. Во все типовые решения 1С (ERP, Управление торговлей, Комплексная автоматизация, Бухгалтерия предприятия), созданные на базе Библиотеки Стандартных Подсистем (БСП), встроен мощный универсальный инструмент.

Рассмотрим по шагам, как выполнить перенос элементов в подгруппу с его помощью:

  1. Перейдите в раздел НСИ и администрирование (или Администрирование) -> Обслуживание.
  2. Найдите и откройте пункт Групповое изменение реквизитов (в некоторых версиях он находится в подразделе «Корректировка данных»).
  3. В поле «Выбирать элементы» укажите справочник Номенклатура.
  4. Нажмите гиперссылку «Все элементы» рядом, чтобы настроить жесткий отбор. Добавьте условие, например: Родитель В группе "Канцтовары" и дополнительно отфильтруйте нужные позиции (по бренду, артикулу или признаку).
  5. В основном списке доступных реквизитов найдите поле Родитель (в зависимости от локализации конфигурации оно может называться «Группа» или «Группа номенклатуры»).
  6. В колонке «Новое значение» выберите из выпадающего списка нужную целевую подгруппу (например, папка «Альбомы»).
  7. Нажмите кнопку Дополнительные параметры внизу формы. Установите флаг «Выполнять в режиме загрузки» — это позволит платформе обработать тысячи элементов за несколько секунд в обход тяжелых проверок бизнес-логики.
  8. Нажмите кнопку Изменить реквизиты и дождитесь окончания выполнения процесса.

Важное предостережение при переносе групп: избегаем рекурсии

В этой теме мы подробно рассмотрели перенос конечных элементов (товаров). Однако, если ваша задача включает перенос самих групп (папок), вы можете случайно столкнуться с опасной логической ошибкой — попыткой переместить родительскую группу внутрь одной из своих же дочерних подгрупп (зацикливание иерархии). При попытке сделать это программно база данных вернет критическую ошибку.

Чтобы защитить базу данных от циклической рекурсии, перед записью измененного объекта группы обязательно делайте проверку с помощью встроенного метода ПринадлежитЭлементу():


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

Соблюдение этих правил гарантирует безопасность данных, высокую скорость выполнения обработки и полное отсутствие ошибок несовместимости типов в коде конфигурации.

← На главную