При разработке интерфейсов в среде «1С:Предприятие 8.3» часто возникает задача автоматизации рутинных операций непосредственно из списков документов. Типичный сценарий: пользователь выбирает строку в динамическом списке, нажимает кастомную кнопку, открывается вспомогательная форма (например, для расчета скидок, кэшбэка или ввода параметров), и после её закрытия данные должны автоматически записаться в документ (чаще всего — в табличную часть ДополнительныеРеквизиты).
Однако при попытке реализовать этот алгоритм «в лоб» через получение объекта и его последующую запись разработчики регулярно сталкиваются с неприятной системной ошибкой:
{Документ.ПоступлениеНаСчет.Форма.ФормаСписка.Форма(419)}: Ошибка при вызове метода контекста (Записать) по причине: Ошибка при выполнении обработчика - 'ПриЗаписи' по причине: {Документ.ПоступлениеНаСчет.МодульОбъекта(3877)}: Поле объекта недоступно для записи (ДополнительныеСвойства)
В этой статье мы подробно разберем, почему возникает данная ошибка, изучим внутренние механизмы платформы и конфигураций на базе БСП (Библиотеки стандартных подсистем), а также рассмотрим три надежных способа решения этой проблемы в зависимости от ваших архитектурных задач.
---
Для начала проанализируем, что представляет собой свойство ДополнительныеСвойства. На уровне платформы «1С:Предприятие» это свойство любого объектного типа данных (такого как ДокументОбъект, СправочникОбъект и т.д.), которое инициализируется автоматически и имеет тип Структура. Оно предназначено для передачи временных параметров между различными обработчиками событий (например, из формы в модуль объекта, из модуля объекта в подписки на события) в рамках одной транзакции записи без изменения физической структуры базы данных.
Важная особенность платформы заключается в том, что само свойство ДополнительныеСвойства доступно только для чтения. Это означает, что мы не можем полностью перезаписать эту структуру новым объектом с помощью оператора присваивания, например, вот так:
ДополнительныеСвойства = Новый Структура; // Вызовет ошибку!
При этом мы имеем полное право работать с содержимым этой структуры: добавлять новые ключи, изменять их или удалять:
ДополнительныеСвойства.Вставить("МойПараметр", Истина); // Абсолютно корректно
Теперь посмотрим, что происходит при программной записи документа. Когда мы выполняем код:
Поступление = ТекущаяСтрокаСсылка.ПолучитьОбъект();
// ... заполнение дополнительных реквизитов ...
Поступление.Записать();
Платформа запускает транзакцию записи и передает управление в обработчик события ПриЗаписи модуля объекта. В типовых конфигурациях (таких как «1С:Управление нашей фирмой», «1С:ERP», «1С:Комплексная автоматизация» или «1С:Управление торговлей») в этом обработчике обычно содержатся вызовы процедур общих модулей. В нашем примере вызывается метод:
УправлениеНебольшойФирмойСервер.ПриЗаписиДокументаОснованияСчетаФактуры(Ссылка, ДополнительныеСвойства, Ложь);
Внутри этого или смежных общих модулей разработчики типового решения часто объявляют параметры процедур без явного указания ключевого слова Знач. Если внутри процедуры общего модуля происходит попытка переинициализировать переменную, переданную в качестве параметра (например, присвоить ей значение Неопределено, очистить или пересоздать структуру), платформа пытается выполнить запись в исходное свойство ДополнительныеСвойства объекта-источника. Так как свойство доступно только для чтения, транзакция аварийно завершается с ошибкой.
При интерактивной записи из формы документа этой ошибки не происходит, поскольку клиентская и серверная части формы предварительно подготавливают структуру дополнительных свойств, наполняя её служебными ключами. При программном же получении объекта через метод ПолучитьОбъект() структура ДополнительныеСвойства создается пустой, что и приводит к некорректной работе алгоритмов общего модуля.
---
Если в вашей конфигурации используется Библиотека стандартных подсистем (БСП), то ручное добавление строк в табличную часть ДополнительныеРеквизиты с последующей записью объекта — это не самый оптимальный путь. В БСП предусмотрен специальный программный интерфейс для работы со свойствами объектов, который позволяет вносить изменения напрямую, избегая блокировок и тяжелых транзакций перезаписи всего документа.
Рассмотрим, как правильно реализовать запись дополнительного реквизита с помощью общего модуля УправлениеСвойствами. Нам не потребуется получать объект документа — все действия выполняются на основе ссылки.
Разберем по шагам пример кода, который необходимо поместить в обработчик закрытия вашей формы:
// 1. Получаем ссылку на документ из текущей строки динамического списка
ТекущаяСтрокаСсылка = Элементы.Список.ТекущаяСтрока;
Если Не ЗначениеЗаполнено(ТекущаяСтрокаСсылка) Тогда
Возврат;
КонецЕсли;
// 2. Подготавливаем таблицу значений для передачи изменяемых свойств
ТаблицаСвойств = Новый ТаблицаЗначений;
ТаблицаСвойств.Колонки.Добавить("Свойство", Новый ОписаниеТипов("ПланВидовХарактеристикСсылка.ДополнительныеРеквизитыИСведения"));
ТаблицаСвойств.Колонки.Добавить("Значение");
// 3. Получаем ссылку на ПВХ дополнительного реквизита
// Примечание: Функция ПолучитьПВХ() должна возвращать ссылку на элемент ПВХ.ДополнительныеРеквизитыИСведения
СвойствоКэшбэк = ПолучитьПВХ("uds_Кэшбэк");
Если ЗначениеЗаполнено(СвойствоКэшбэк) Тогда
// 4. Формируем строку с новыми данными
НоваяСтрока = ТаблицаСвойств.Добавить();
НоваяСтрока.Свойство = СвойствоКэшбэк;
// Рассчитываем значение кэшбэка, используя реквизиты ссылки (без получения объекта документа)
Процент = РезультатОбработки.ПроцентКэшбэка;
НоваяСтрока.Значение = ТекущаяСтрокаСсылка.СуммаДокумента * Процент / 100;
// 5. Записываем свойства объекта через штатный механизм БСП
УправлениеСвойствами.ЗаписатьСвойстваУОбъекта(ТекущаяСтрокаСсылка, ТаблицаСвойств);
// 6. Обновляем строку в динамическом списке, чтобы пользователь сразу увидел изменения
ОповеститьОбИзменении(ТекущаяСтрокаСсылка);
КонецЕсли;
Преимущества данного метода:
---
Бывают ситуации, когда логика работы системы требует обязательного использования классического метода записи объекта Записать() (например, если помимо дополнительных реквизитов вам необходимо параллельно изменить стандартные реквизиты документа или запустить специфические процедуры пересчета).
В этом случае мы можем воспользоваться свойством объекта ОбменДанными.Загрузка. Установка этого флага в значение Истина сообщает платформе и конфигурации, что документ записывается в режиме обмена (синхронизации), и большинство стандартных проверок и тяжелых обработчиков событий в модуле объекта должны быть пропущены.
Проанализируем пример реализации данного подхода:
ТекущаяСтрокаСсылка = Элементы.Список.ТекущаяСтрока;
Если ЗначениеЗаполнено(ТекущаяСтрокаСсылка) Тогда
// Получаем объект документа для редактирования
ПоступлениеОбъект = ТекущаяСтрокаСсылка.ПолучитьОбъект();
СвойствоКэшбэк = ПолучитьПВХ("uds_Кэшбэк");
// Проверим, существует ли уже строка с данным свойством в табличной части
СтрокаСвойства = Неопределено;
Для Каждого СтрокаТЧ Из ПоступлениеОбъект.ДополнительныеРеквизиты Цикл
Если СтрокаТЧ.Свойство = СвойствоКэшбэк Тогда
СтрокаСвойства = СтрокаТЧ;
Прервать;
КонецЕсли;
КонецЦикла;
// Если строка не найдена, добавляем новую
Если СтрокаСвойства = Неопределено Тогда
СтрокаСвойства = ПоступлениеОбъект.ДополнительныеРеквизиты.Добавить();
СтрокаСвойства.Sвойство = СвойствоКэшбэк;
КонецЕсли;
// Заполняем значение реквизита
СтрокаСвойства.Значение = ПоступлениеОбъект.СуммаДокумента * РезультатОбработки.ПроцентКэшбэка / 100;
// Включаем режим загрузки данных для обхода обработчика ПриЗаписи()
ПоступлениеОбъект.ОбменДанными.Загрузка = Истина;
Попытка
ПоступлениеОбъект.Записать();
ОповеститьОбИзменении(ТекущаяСтрокаСсылка);
Исключение
ВызватьИсключение "Не удалось записать документ: " + КраткоеПредставлениеОшибки(ИнформацияОбОшибке());
КонецПопытки;
КонецЕсли;
Как это работает? В начале процедуры ПриЗаписи(Отказ) в модуле любого типового документа всегда стоит стандартная проверка:
Если ОбменДанными.Загрузка Тогда
Возврат;
КонецЕсли;
Благодаря этому принудительно установленный флаг мгновенно прерывает выполнение обработчика ПриЗаписи еще до того, как управление дойдет до проблемного вызова процедуры общего модуля. Документ успешно сохраняется в базе данных.
Важное предупреждение: Использование режима ОбменДанными.Загрузка = Истина отключает не только проблемный обработчик, но и все остальные бизнес-проверки конфигурации. Документ запишется в любом состоянии (даже если нарушена логика проведения). Используйте этот метод осознанно, только если вы уверены, что пропускаемые алгоритмы не нарушат целостность данных системы.
---
Если вам критически важно, чтобы при программной записи документа отработали абсолютно все встроенные алгоритмы проведения, проверки и связанные подписки на события (то есть способ с ОбменДанными.Загрузка не подходит), необходимо пойти по пути явного наполнения структуры ДополнительныеСвойства.
Для этого мы должны сымитировать поведение интерактивной формы и передать в структуру те ключи и значения, которые ожидают встретить процедуры общих модулей при проведении и записи.
Посмотрим на практическую реализацию этого метода:
ТекущаяСтрокаСсылка = Элементы.Список.ТекущаяСтрока;
Если ЗначениеЗаполнено(ТекущаяСтрокаСсылка) Тогда
ПоступлениеОбъект = ТекущаяСтрокаСсылка.ПолучитьОбъект();
// Проверяем инициализацию структуры ДополнительныеСвойства
// Саму структуру пересоздавать нельзя, но мы можем наполнить ее свойствами
Если ПоступлениеОбъект.ДополнительныеСвойства.Свойство("Модифицированность") = Ложь Тогда
ПоступлениеОбъект.ДополнительныеСвойства.Вставить("Модифицированность", Истина);
КонецЕсли;
// Добавляем служебные ключи, которые часто запрашивают общие модули БСП
ПоступлениеОбъект.ДополнительныеСвойства.Вставить("ПерезаписатьПроведенный", Истина);
// Заполняем нужный дополнительный реквизит
СвойствоКэшбэк = ПолучитьПВХ("uds_Кэшбэк");
СтрокаСвойства = ПоступлениеОбъект.ДополнительныеРеквизиты.Добавить();
СтрокаСвойства.Свойство = СвойствоКэшбэк;
СтрокаСвойства.Значение = ПоступлениеОбъект.СуммаДокумента * РезультатОбработки.ПроцентКэшбэка / 100;
// Записываем документ в стандартном режиме с вызовом всех обработчиков
Попытка
ПоступлениеОбъект.Записать(РежимЗаписиДокумента.Запись);
ОповеститьОбИзменении(ТекущаяСтрокаСсылка);
Исключение
ЗаписьЖурналаРегистрации("ПрограммнаяЗапись", УровеньЖурналаРегистрации.Ошибка,,, КраткоеПредставлениеОшибки(ИнформацияОбОшибке()));
ВызватьИсключение "Ошибка при сохранении изменений документа.";
КонецПопытки;
КонецЕсли;
Этот способ требует от разработчика хорошего понимания кода модулей записываемого документа. Рекомендуется предварительно запустить отладчик, поставить точку останова в процедуре ПриЗаписи и посмотреть, к каким именно ключам структуры ДополнительныеСвойства обращается система, чтобы заблаговременно добавить их перед вызовом метода Записать().
---
Мы подробно проанализировали природу ошибки блокировки записи свойства ДополнительныеСвойства и рассмотрели варианты её устранения.
Подведем краткий итог:
УправлениеСвойствами.ЗаписатьСвойстваУОбъекта). Это самый быстрый, надежный и архитектурно верный вариант.ОбменДанными.Загрузка = Истина).