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

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

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

Выясняем причину возникновения ошибки

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


ТекКонтрагент = ВыборкаДетальныеЗаписи.Ссылка.ПолучитьОбъект();
ТекКонтрагент.ДоговорПоУмолчанию.НомерДоговора = "1"; // Ошибка возникает здесь!
ТекКонтрагент.ДоговорПоУмолчанию.ДатаДоговора = ТекущаяДата();
ТекКонтрагент.Записать();

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

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

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

Разберем по шагам правильный алгоритм решения

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

  1. Создание нового объекта договора: Используем метод менеджера справочника договоров СоздатьЭлемент(). Это инициализирует в оперативной памяти новый объект типа СправочникОбъект.
  2. Указание владельца договора: Поскольку справочник договоров является подчиненным по отношению к справочнику контрагентов, нам обязательно нужно заполнить стандартный реквизит Владелец. В него мы передаем ссылку на контрагента.
  3. Заполнение реквизитов договора: Заполняем номер, дату, наименование и любые другие необходимые реквизиты создаваемого договора.
  4. Запись нового договора: Вызываем метод Записать() для объекта договора. В этот момент запись физически создается в базе данных, и у договора появляется уникальная ссылка.
  5. Привязка договора к контрагенту: Получаем объект контрагента через метод ПолучитьОбъект() (если у нас в цикле обхода выборки доступна только ссылка), записываем полученную ссылку на новый договор в реквизит контрагента ДоговорПоУмолчанию.
  6. Запись контрагента: Вызываем метод Записать() для объекта контрагента, чтобы зафиксировать изменения в базе данных.

Вариант 1. Классическая реализация алгоритма

Рассмотрим простую и понятную программную реализацию описанного алгоритма в цикле обработки выборки данных:


Пока ВыборкаДетальныеЗаписи.Следующий() Цикл
    
    СсылкаКонтрагента = ВыборкаДетальныеЗаписи.Ссылка;
    
    // Шаг 1. Создаем новый элемент в справочнике договоров
    НовыйДоговор = Справочники._ДемоДоговорыКонтрагентов.СоздатьЭлемент();
    
    // Шаг 2. Задаем владельца (контрагента)
    НовыйДоговор.Владелец = СсылкаКонтрагента;
    
    // Шаг 3. Заполняем реквизиты договора
    НовыйДоговор.НомерДоговора = "1";
    НовыйДоговор.ДатаДоговора  = ТекущаяДата();
    НовыйДоговор.Наименование  = "Основной договор № 1 от " + Формат(ТекущаяДата(), "ДФ=dd.MM.yyyy");
    
    // Шаг 4. Записываем договор в базу данных
    НовыйДоговор.Записать();
    
    // Шаг 5. Получаем объект контрагента и устанавливаем созданный договор по умолчанию
    ОбъектКонтрагент = СсылкаКонтрагента.ПолучитьОбъект();
    ОбъектКонтрагент.ДоговорПоУмолчанию = НовыйДоговор.Ссылка;
    
    // Шаг 6. Сохраняем изменения контрагента
    ОбъектКонтрагент.Записать();
    
КонецЦикла;

Этот код полностью работоспособен и решает поставленную задачу без вызова ошибок. Однако при работе с реальными информационными базами, где количество контрагентов может исчисляться тысячами, такой подход будет работать крайне медленно. Каждая запись объекта производит обращение к диску и запускает многочисленные подписки на события и проверки. Перейдем к профессиональной оптимизации этого процесса. В ситуациях, когда задачу необходимо выполнить непосредственно в пользовательском интерфейсе без написания программного кода, применяется платформа для создания программных роботов One RPA.

Вариант 2. Оптимизированное решение для больших баз данных

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

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

Посмотрим на оптимизированный программный код:


Процедура ВыполнитьКоманду(ИдентификаторКоманды, ОбъектыНазначения, ПараметрыВыполненияКоманды) Экспорт
    
    // Шаг 1. Выбираем контрагентов без договоров по умолчанию,
    // а также попутно пытаемся найти любой уже существующий договор этого контрагента
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |    Контрагенты.Ссылка КАК Ссылка,
        |    МИНИМУМ(СуществующиеДоговоры.Ссылка) КАК СуществующийДоговор
        |ИЗ
        |    Справочник._ДемоКонтрагенты КАК Контрагенты
        |    ЛЕВОЕ СОЕДИНЕНИЕ Справочник._ДемоДоговорыКонтрагентов КАК СуществующиеДоговоры
        |        ПО СуществующиеДоговоры.Владелец = Контрагенты.Ссылка
        |        И НЕ СуществующиеДоговоры.ПометкаУдаления
        |ГДЕ
        |    НЕ Контрагенты.ЭтоГруппа
        |    И Контрагенты.ДоговорПоУмолчанию = ЗНАЧЕНИЕ(Справочник._ДемоДоговорыКонтрагентов.ПустаяСсылка)
        |ГРУППИРОВАТЬ ПО
        |    Контрагенты.Ссылка";
    
    РезультатЗапроса = Запрос.Run();
    Выборка = РезультатЗапроса.Выбрать();
    
    // Использование транзакции ускоряет запись в десятки раз
    НачатьТранзакцию();
    
    Попытка
        Пока Выборка.Следующий() Цикл
            
            СсылкаКонтрагента = Выборка.Ссылка;
            СсылкаНаДоговор = ЗначениеЗаполнено(Выборка.СуществующийДоговор) ? Выборка.СуществующийДоговор : Неопределено;
            
            // Если у контрагента вообще нет ни одного договора — создаем новый
            Если СсылкаНаДоговор = Неопределено Тогда
                
                НовыйДоговор = Справочники._ДемоДоговорыКонтрагентов.СоздатьЭлемент();
                НовыйДоговор.Владелец = СсылкаКонтрагента;
                НовыйДоговор.НомерДоговора = "1";
                НовыйДоговор.ДатаДоговора  = ТекущаяДата();
                НовыйДоговор.Наименование  = "Основной договор № 1 от " + Формат(ТекущаяДата(), "ДФ=dd.MM.yyyy");
                
                // Свойство ОбменДанными.Загрузка = Истина отключает тяжелые прикладные проверки при записи
                НовыйДоговор.ОбменДанными.Загрузка = Истина;
                НовыйДоговор.Записать();
                
                СсылкаНаДоговор = НовыйДоговор.Ссылка;
                
            КонецЕсли;
            
            // Получаем объект контрагента для обновления ссылки на договор
            ОбъектКонтрагент = СсылкаКонтрагента.ПолучитьОбъект();
            ОбъектКонтрагент.ДоговорПоУмолчанию = СсылкаНаДоговор;
            
            // Отключаем лишние обработчики событий для ускорения выполнения групповой операции
            ОбъектКонтрагент.ОбменДанными.Загрузка = Истина;
            ОбъектКонтрагент.Записать();
            
        КонецЦикла;
        
        // Фиксируем все изменения в базе данных одним пакетом
        ЗафиксироватьТранзакцию();
        Сообщить("Заполнение договоров по умолчанию успешно завершено!");
        
    Исключение
        // В случае любой непредвиденной ошибки откатываем все изменения назад
        ОтменитьТранзакцию();
        ЗаписатьЛогСобытий(ОписаниеОшибки()); // Процедура логирования разработчика
        ВызватьИсключение "Не удалось выполнить обработку. Причина: " + ОписаниеОшибки();
    КонецПопытки;
    
КонецПроцедуры

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

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

  1. Использование механизма транзакций (НачатьТранзакцию() / ЗафиксироватьТранзакцию()): По умолчанию каждая запись любого объекта в 1С открывает свою внутреннюю неявную транзакцию. Если у вас 1000 контрагентов, база данных выполнит 2000 транзакций (запись договора и запись контрагента). Это порождает колоссальную нагрузку на жесткий диск и СУБД из-за постоянных операций подтверждения записи (commit). Обернув весь цикл в одну общую явную транзакцию, мы заставляем СУБД выполнить запись всей пачки данных в оперативной памяти и зафиксировать ее на диск единовременно. Скорость работы увеличивается в 10–50 раз.
  2. Использование свойства ОбменДанными.Загрузка = Истина: Это важнейший инструмент при написании сервисных обработок по массовому изменению данных. Установка этого свойства сообщает платформе, что объект записывается в режиме импорта данных. При этом отключаются все тяжелые платформенные проверки заполнения реквизитов, кастомные обработчики событий в модулях объектов (например, процедуры ПередЗаписью или ПриЗаписи), а также подписки на события конфигурации (например, регистрация изменений для обменов). Это не только ускоряет процесс, но и предотвращает появление случайных ошибок, связанных с бизнес-логикой конфигурации, которая не рассчитана на подобные массовые программные корректировки.
  3. Проверка на существование договоров в запросе: Мы использовали конструкцию ЛЕВОЕ СОЕДИНЕНИЕ со справочником договоров в запросе. Благодаря этому мы сразу видим, есть ли у конкретного контрагента хоть один живой договор. Если он найден, мы просто устанавливаем его по умолчанию в поле ДоговорПоУмолчанию без создания нового элемента. Это защищает базу данных от накопления «мусорных» дублирующихся записей.

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

← На главную