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

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

При работе с иерархическими справочниками в платформе «1С:Предприятие» разработчики часто сталкиваются с необходимостью программного изменения структуры папок или копирования элементов из одной группы в другую. Типичная ошибка новичков — попытка изменить реквизиты связанного объекта «через точку» от текущего. В результате платформа генерирует системное исключение: «Поле объекта недоступно для записи».

В этой статье мы подробно разберем, почему возникает эта ошибка, в чем разница между ссылочными и объектными типами данных в 1С, а также рассмотрим два практических сценария: корректное перемещение элементов по иерархии и их копирование с созданием дубликатов.

---

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

Давайте проанализируем код, который часто приводит к ошибке:


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

На первый взгляд код кажется логичным: мы получили объект ЭлементСправочника, который доступен для изменения, и пытаемся перезаписать его структуру. Однако здесь кроется фундаментальная особенность объектной техники 1С.

Свойство Родитель у любого элемента справочника возвращает тип СправочникСсылка. Ссылка — это всего лишь неизменяемый уникальный идентификатор (GUID) записи в базе данных. Она предназначена исключительно для чтения и получения данных. Когда мы пишем ЭлементСправочника.Родитель.Родитель, мы обращаемся к реквизиту ссылки. Изменить реквизиты ссылки напрямую в базе данных невозможно. Для любого изменения данных в 1С необходимо явным образом получить объект базы данных с помощью метода ПолучитьОбъект().

Таким образом, действует жесткое правило платформы 1С: запись реквизитов допускается только «через одну точку» от переменной, содержащей сам Объект (тип СправочникОбъект). Обращение через две и более точки всегда вернет ошибку записи.

---

Решение сценария №1: Правильное перемещение элементов по иерархии

Представим ситуацию: у нас есть элемент справочника, который находится в папке «Склад2», а саму папку «Склад2» мы хотим переместить внутрь группы «Москва» (ГруппаПриемник). При этом подчиненность элемента группе «Склад2» должна сохраниться.

Если нам нужно просто переместить группу «Склад2» со всем её содержимым, нет необходимости перебирать каждый элемент внутри этой группы и менять его родителя. В 1С иерархия строится динамически. Достаточно изменить родителя у самой папки «Склад2», и все вложенные элементы автоматически «переедут» в новое место.

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


// Получаем ссылку на родителя текущего элемента (например, это папка "Склад2")
РодительСсылка = ТекСпр.Родитель; 

Если ЗначениеЗаполнено(РодительСсылка) Тогда
    // Получаем изменяемый ОБЪЕКТ для родительской папки
    РодительОбъект = РодительСсылка.ПолучитьОбъект();
    
    // Меняем родителя этой папки на "Москва" (ГруппаПриемник)
    РодительОбъект.Родитель = ГруппаПриемник;
    
    // Записываем изменения родительской папки в базу данных
    РодительОбъект.Записать();
    
    Информировать("Папка успешно перемещена вместе со всем содержимым!");
КонецЕсли;

Если же стоит задача переместить конкретный элемент из одной папки в другую (например, убрать его из «Склад2» и перенести напрямую в «Москва»), код будет еще проще:


// Получаем объект самого элемента
ЭлементОбъект = ТекСпр.Ссылка.ПолучитьОбъект();

// Меняем родителя непосредственно у объекта
ЭлементОбъект.Родитель = ГруппаПриемник;

// Сохраняем изменения в базе данных
ЭлементОбъект.Записать();

---

Решение сценария №2: Копирование (дублирование) элементов

Иногда требуется не переносить оригинальные элементы, а создать их точные копии (дубликаты) в другой папке. Для этих целей в 1С используется метод Скопировать(). Данный метод доступен у объекта ссылки и возвращает новый, еще не записанный в базу данных объект типа СправочникОбъект со скопированными реквизитами.

Посмотрим на пример копирования одиночного элемента:


// Создаем копию элемента в оперативной памяти
НовыйЭлементОбъект = ТекСпр.Ссылка.Скопировать();

// Указываем для новой копии другого родителя (папку-приемник)
НовыйЭлементОбъект.Родитель = ГруппаПриемник;

// Если в справочнике настроена автонумерация, очистим код, чтобы платформа присвоила новый
НовыйЭлементОбъект.Код = ""; 

// Записываем новый элемент в базу данных
НовыйЭлементОбъект.Записать();

Важное предупреждение при копировании папок (групп)

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

Если перед вами стоит задача скопировать всю ветку иерархии (папку «Склад2» со всеми товарами внутри нее в папку «Москва»), необходимо реализовать рекурсивный обход. Давайте разберем готовый алгоритм для решения этой задачи:


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

---

Рекомендации по оптимизации работы со справочниками

Для того чтобы ваши обработки работали быстро и без сбоев, особенно на больших базах данных, рекомендуем придерживаться следующих правил:

  1. Используйте транзакции. Если вы массово переносите или копируете элементы, оборачивайте процесс записи в транзакцию с помощью операторов НачатьТранзакцию() и ЗафиксироватьТранзакцию(). Это ускорит запись в базу данных в несколько раз.
  2. Проверяйте заполненность родителя. Перед вызовом метода ПолучитьОбъект() у родителя всегда делайте проверку ЗначениеЗаполнено(Ссылка.Родитель). У элементов верхнего уровня родитель равен пустой ссылке, и попытка получить объект от пустой ссылки вызовет аварийное завершение программы.
  3. Отключайте лишние проверки при обмене данными. Если вы пишете техническую обработку переноса данных и уверены в корректности заполнения реквизитов, используйте свойство объекта ОбменДанными.Загрузка = Истина. Это отключит выполнение тяжелых проверок в модуле объекта при записи и существенно сократит время выполнения операции.
← На главную