При разработке в конфигурациях, построенных на базе Библиотеки стандартных подсистем (БСП) (к ним относятся практически все современные типовые решения: ERP, КА, УТ, ЗУП, БП), программисты часто сталкиваются с неприятной особенностью. При попытке программно изменить значение дополнительного реквизита в модуле управляемой формы документа или справочника, данные успешно меняются в отладчике, но после записи объекта в базе данных остаются старые значения. Выясним, почему так происходит, и разберем по шагам правильные и надежные способы решения этой задачи.
Для того чтобы понять суть проблемы, проанализируем, как устроена работа подсистемы «Свойства» в БСП. Дополнительные реквизиты физически хранятся в табличной части ДополнительныеРеквизиты вашего документа или справочника. Однако на управляемую форму они выводятся не напрямую из этой табличной части.
Когда управляемая форма открывается, программные механизмы БСП выполняют следующие действия:
ДополнительныйРеквизитЗначение_<GUID_Набора>_<GUID_Свойства>.Свойства_ОписаниеДополнительныхРеквизитов, в которой хранится карта соответствия между реальными свойствами (из плана видов характеристик) и динамическими реквизитами формы.Главная ловушка кроется в механизме сохранения: когда пользователь нажимает кнопку «Записать», или когда в коде формы вызывается метод записи, БСП запускает служебный обработчик (метод УправлениеСвойствамиСлужебный.ПеренестиЗначенияИзРеквизитовФормыВОбъект). Этот обработчик берет значения из динамических реквизитов формы и принудительно перезаписывает ими строки в табличной части ДополнительныеРеквизиты объекта непосредственно перед отправкой данных в базу данных.
Если в коде формы вы получаете объект через РеквизитФормыВЗначение("Объект"), вручную меняете строку в его табличной части и возвращаете объект на форму через ЗначениеВРеквизитФормы, то в момент записи ваши изменения просто затираются «старыми» значениями, которые всё это время продолжали храниться в динамических реквизитах формы. В обработчике формы ПередЗаписьюНаСервере вы еще можете видеть измененное значение, но уже в модуле объекта при проведении данные гарантированно будут затерты старыми значениями из формы.
Ниже мы подробно рассмотрим три правильных способа изменения дополнительных реквизитов, которые исключают эту проблему.
Этот способ является наиболее красивым и правильным при работе в контексте управляемой формы. Вместо того чтобы трогать табличную часть документа, мы найдем на форме динамический реквизит, созданный БСП для нашего свойства, и запишем новое значение напрямую в него. В этом случае пользователь сразу увидит изменения на экране, а при записи документа БСП штатно перенесет новое значение в табличную часть объекта.
Рассмотрим пример кода, реализующий этот алгоритм:
&НаСервере
Процедура ИзменитьДопРеквизитНаФорме(НовыйСтатус)
// 1. Находим элемент плана видов характеристик (наше свойство)
Свойство = ПланыВидовХарактеристик.ДополнительныеРеквизитыИСведения.НайтиПоНаименованию("Статус согласования", Истина);
Если ЗначениеЗаполнено(Свойство) Тогда
// 2. Отыскиваем строку с описанием этого свойства в служебной коллекции формы
ПараметрыОтбора = Новый Структура("Свойство", Свойство);
НайденныеСтроки = ЭтаФорма.Свойства_ОписаниеДополнительныхРеквизитов.НайтиСтроки(ПараметрыОтбора);
Если НайденныеСтроки.Количество() > 0 Тогда
// 3. Извлекаем имя динамического реквизита формы
ИмяРеквизитаНаФорме = НайденныеСтроки[0].ИмяРеквизитаЗначение;
// 4. Устанавливаем новое значение напрямую в реквизит формы
ЭтаФорма[ИмяРеквизитаНаФорме] = НовыйСтатус;
КонецЕсли;
КонецЕсли;
КонецПроцедуры
Преимущества способа:
РеквизитФормыВЗначение и обратно.Иногда возникают ситуации, когда вам по архитектурным соображениям удобнее работать непосредственно с табличной частью документа (например, если у вас уже написан сложный универсальный алгоритм заполнения реквизитов объекта, или изменения вносятся в большую группу реквизитов). В таком случае, после изменения табличной части ДополнительныеРеквизиты на форме, вам необходимо дать команду БСП обновить динамические элементы формы.
Проанализируем пример кода, который решает эту задачу:
&НаСервере
Процедура ИзменитьДопРеквизитИОбновитьФорму(НовыйСтатус)
// 1. Преобразуем данные формы в прикладной объект
ДокументОбъект = РеквизитФормыВЗначение("Объект");
// 2. Находим нужное свойство
СвойствоПВХ = ПланыВидовХарактеристик.ДополнительныеРеквизитыИСведения.НайтиПоНаименованию("Статус согласования", Истина);
Если ЗначениеЗаполнено(СвойствоПВХ) Тогда
// 3. Ищем строку свойства в табличной части объекта (по колонке "Свойство")
СтрокаДопРеквизита = ДокументОбъект.ДополнительныеРеквизиты.Найти(СвойствоПВХ, "Свойство");
Если СтрокаДопРеквизита <> Неопределено Тогда
СтрокаДопРеквизита.Значение = НовыйСтатус;
Иначе
// Если строки не было, создаем новую
НоваяСтрока = ДокументОбъект.ДополнительныеРеквизиты.Добавить();
НоваяСтрока.Свойство = СвойствоПВХ;
НоваяСтрока.Значение = НовыйСтатус;
КонецЕсли;
КонецЕсли;
// 4. Возвращаем измененный объект обратно в данные формы
ЗначениеВРеквизитФормы(ДокументОбъект, "Объект");
// 5. Ключевой шаг: принудительно синхронизируем реквизиты формы с табличной частью объекта
УправлениеСвойствами.ЗаполнитьДополнительныеРеквизитыВФорме(ЭтаФорма, Объект);
КонецПроцедуры
Важное примечание: метод УправлениеСвойствами.ЗаполнитьДополнительныеРеквизитыВФорме берет актуальные данные из табличной части переданного объекта и перезаписывает динамические реквизиты формы, благодаря чему при сохранении ничего не затрется.
Если бизнес-логика изменения дополнительного реквизита не требует мгновенного отображения пользователю на экране в процессе интерактивного редактирования (например, статус меняется автоматически при проведении документа, или заполняется служебная информация при сохранении), то правильнее всего полностью отказаться от выполнения этого кода в модуле формы.
Перенесите этот функционал в Модуль объекта вашего документа в обработчик события ПередЗаписью. В модуле объекта нет никаких динамических реквизитов формы, поэтому стандартное изменение табличной части отработает корректно и данные гарантированно запишутся в базу.
Посмотрим на пример реализации в модуле объекта:
Процедура ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения)
// Системная проверка, чтобы не выполнять код при обмене данными
Если ОбменДанными.Загрузка Тогда
Возврат;
КонецЕсли;
// Находим наше свойство
СвойствоПВХ = ПланыВидовХарактеристик.ДополнительныеРеквизитыИСведения.НайтиПоНаименованию("Статус согласования", Истина);
Если ЗначениеЗаполнено(СвойствоПВХ) Тогда
СтрокаДопРеквизита = ЭтотОбъект.ДополнительныеРеквизиты.Найти(СвойствоПВХ, "Свойство");
// Определяем новое значение по вашей бизнес-логике
НовоеЗначение = Справочники.ЗначенияСвойствОбъектов.НайтиПоНаименованию("Согласовано");
Если СтрокаДопРеквизита <> Неопределено Тогда
СтрокаДопРеквизита.Значение = НовоеЗначение;
Иначе
НоваяСтрока = ЭтотОбъект.ДополнительныеРеквизиты.Добавить();
НоваяСтрока.Свойство = СвойствоПВХ;
НоваяСтрока.Значение = НовоеЗначение;
КонецЕсли;
КонецЕсли;
КонецПроцедуры
Преимущества способа:
При работе со свойствами БСП всегда помните о приоритете реквизитов формы над реквизитами объекта при интерактивной работе. Используйте Способ 1 для интерактивных изменений на форме «на лету» и Способ 3 для реализации фоновой автоматической бизнес-логики при записи документа.