При разработке управляемых форм часто возникает задача отобразить в табличной части расчетные данные: остатки на складах, задолженность, плановую себестоимость или показатели предыдущих документов. Чтобы не перегружать метаданные документа лишней информацией, разработчик добавляет реквизит не в сам объект базы данных, а непосредственно в реквизиты формы, расширяя коллекцию Объект.МояТЧ.
На форме такие колонки рассчитываются корректно, однако сразу после нажатия кнопки Записать или Провести и закрыть значения в них исчезают или сбрасываются в значения по умолчанию (например, 0). Разберем по шагам внутреннюю логику работы платформы «1С:Предприятие 8.3», выясним причину такого поведения и рассмотрим надежные варианты решения этой задачи.
Проанализируем, что происходит на сервере в момент сохранения документа в управляемом интерфейсе:
ДанныеФормыВЗначение). В этот момент все пользовательские реквизиты формы, которых нет в структуре метаданных документа, просто отсекаются. Объект ничего о них не знает.ПередЗаписью, ОбработкаПроверкиЗаполнения, ПриЗаписи) и данные фиксируются в базе данных.ЗначениеВДанныеФормы). Так как в записанном объекте базы данных добавленных колонок нет, платформа инициализирует их исходными пустыми значениями.Таким образом, очистка вычисляемой колонки — это штатный механизм синхронизации данных формы с объектом базы данных. Стандартными свойствами элементов формы отменить эту очистку нельзя.
Многие разработчики пытаются поместить код восстановления расчетов в обработчик ПриЧтенииНаСервере, однако при записи документа этот обработчик не вызывается. Рассмотрим разницу между ключевыми серверными событиями формы:
ПриСозданииНаСервере — выполняется один раз при первичной инициализации формы при ее открытии (как для нового объекта, так и для существующего).ПриЧтенииНаСервере — вызывается только тогда, когда форма считывает существующий объект непосредственно из базы данных (при открытии существующего документа или при явном вызове метода формы Прочитать()).ПередЗаписьюНаСервере, ПриЗаписиНаСервере и ПослеЗаписиНаСервере. Событие ПриЧтенииНаСервере в эту цепочку не входит.Если вычисления выполняются быстро и не требуют тяжелых обращений к базе данных, наиболее простым и правильным решением будет вызов перерасчета в серверном событии ПослеЗаписиНаСервере. В этой точке объект уже зафиксирован в базе, форма перезаполнена платформой, и любые значения, которые мы установим в реквизиты формы, благополучно вернутся на клиента.
Посмотрим на пример организации кода в модуле формы:
&НаСервере
Процедура ПерерасчетИтогов()
Для Каждого СтрокаТЧ Из Объект.МояТЧ Цикл
СтрокаТЧ.Реквизит1 = СтрокаТЧ.Реквизит2 + СтрокаТЧ.Реквизит3;
КонецЦикла;
КонецПроцедуры
&НаСервере
Процедура ПриЧтенииНаСервере(ТекущийОбъект)
// Заполняем расчетную колонку при открытии существующего документа
ПерерасчетИтогов();
КонецПроцедуры
&НаСервере
Процедура ПослеЗаписиНаСервере(ТекущийОбъект, ПараметрыЗаписи)
// Восстанавливаем расчетную колонку после перезаполнения формы объектом
ПерерасчетИтогов();
КонецПроцедуры
Важное замечание: Не следует выполнять обход табличной части в клиентском событии ПослеЗаписи. Обход коллекции строк на клиенте приводит к множественным неявным обращениям к серверу для порционного считывания данных таблицы, что существенно замедляет работу интерфейса при большом количестве строк.
Рассмотрим ситуацию, когда колонка заполняется результатами сложных запросов (например, остатки, курсы валют или данные прошлых периодов). Повторно запускать тяжелый алгоритм в ПослеЗаписиНаСервере неэффективно. В этом случае мы можем сохранить уже рассчитанные значения перед записью и вернуть их обратно в строки сразу после обновления формы.
Разберем по шагам, как реализовать сохранение через временное соответствие с использованием свойства НомерСтроки или свойства ПолучитьИдентификатор():
&НаСервере
Процедура ПередЗаписьюНаСервере(Отказ, ТекущийОбъект, ПараметрыЗаписи)
// Сохраняем вычисленные значения в соответствие перед тем, как платформа обнулит форму
КэшКолонки = Новый Соответствие;
Для Каждого СтрокаТЧ Из Объект.МояТЧ Цикл
КэшКолонки.Вставить(СтрокаТЧ.НомерСтроки, СтрокаТЧ.Реквизит1);
КонецЦикла;
// Передаем кэш в параметры записи, доступные в других обработчиках записи
ПараметрыЗаписи.Вставить("КэшКолонки", КэшКолонки);
КонецПроцедуры
&НаСервере
Процедура ПослеЗаписиНаСервере(ТекущийОбъект, ПараметрыЗаписи)
// Проверяем наличие сохраненных данных в параметрах записи
Если ПараметрыЗаписи.Свойство("КэшКолонки") Тогда
КэшКолонки = ПараметрыЗаписи.КэшКолонки;
Для Каждого СтрокаТЧ Из Объект.МояТЧ Цикл
ЗначениеИзКэша = КэшКолонки[СтрокаТЧ.НомерСтроки];
Если ЗначениеИзКэша <> Неопределено Тогда
СтрокаТЧ.Реквизит1 = ЗначениеИзКэша;
КонецЕсли;
КонецЦикла;
КонецЕсли;
КонецПроцедуры
Такой подход полностью исключает повторные запросы к базе данных: данные переносятся в оперативной памяти сервера между этапами записи формы.
Еще один архитектурный вариант — не связывать отображаемую таблицу напрямую с реквизитом Объект.МояТЧ. Если на форме создать отдельный реквизит формы с типом ТаблицаЗначений и связать элемент управления таблицы именно с ним, платформа вообще не будет затрагивать эти данные при записи документа. В таком случае синхронизацию между таблицей формы и реальной табличной частью документа программист берет под полный ручной контроль.