При работе с обычными формами в платформе 1С:Предприятие 8 разработчики часто сталкиваются с задачей отображения дополнительных расчетных данных в списках документов или справочников. Типичный сценарий: нужно вывести статус выполнения заказа, процент оплаты или состояние отгрузки непосредственно в форму списка, не сохраняя эти данные напрямую в самом документе.
Для реализации такой задачи традиционно используется событие табличного поля ПриПолученииДанных. Однако некорректное использование этого механизма приводит к двум серьезным проблемам: «обнулению» значений в добавленной колонке при действиях пользователя и критическому снижению производительности (зависаниям и фризам интерфейса). Проанализируем, почему возникают эти проблемы, и рассмотрим три профессиональных способа их решения.
Для начала разберем физику процесса работы табличного поля. В отличие от табличной части, динамический список документов не хранит все строки в оперативной памяти клиента. Он работает по принципу «виртуального окна»: на клиенте отображается и кэшируется только то количество строк, которое физически помещается на экране пользователя (обычно от 15 до 45 строк).
Событие ПриПолученииДанных срабатывает каждый раз, когда платформа запрашивает у сервера очередную порцию отображаемых данных. Это происходит при:
В ситуации, описанной автором темы, форма списка совершает «прыжки» в процедуру КонтрольРежимаЗавершенияРаботыПользователей(). В типовых конфигурациях (например, УПП 1.3 или КА 1.1) данная процедура вызывается глобальным обработчиком ожидания. Каждый раз при ее срабатывании система выполняет обращения к серверу (например, проверяя наличие активных сессий или установку блокировки базы данных). Любое подобное фоновое серверное событие инициирует принудительное обновление активных окон. Платформа перерисовывает интерфейс, заново инициирует событие ПриПолученииДанных и очищает все «оформления» строк, которые не были жестко связаны с источником данных (базой данных).
Если значение колонки «Выполнено» рассчитывалось «на лету» в процедуре ПриПолученииДанных и нигде не сохранялось, при перерисовке оно сбрасывается в значение по умолчанию (для типа Булево — в Ложь). Пользователь видит это как мерцание и очистку колонки.
Вторая проблема — производительность. Если алгоритм определения «выполненности» заказа сложный (включает проверку связанных заказов покупателей, анализ выполнения по цехам, проверку оплат), то выполнение таких расчетов непосредственно в момент отрисовки интерфейса парализует работу пользователей. Каждое движение мыши по списку заставляет сервер заново выполнять тяжелые транзакционные запросы.
Если бизнес-логика не позволяет изменить структуру метаданных информационной базы, лучшим решением для устранения «обнуления» колонок и снижения нагрузки на СУБД является создание клиентского кэша в модуле формы. Мы выполним расчет статуса документа только один раз и сохраним его в оперативной памяти до момента закрытия формы.
Разберем реализацию этого метода по шагам:
Соответствие при открытии формы.ПриПолученииДанных будем собирать только те ссылки документов, которых еще нет в нашем кэше.Рассмотрим пример программного кода, реализующего этот паттерн:
Перем КэшСтатусовВыполнения; // Переменная модуля формы
Процедура ПриОткрытии()
// Инициализируем кэш при открытии формы
КэшСтатусовВыполнения = Новый Соответствие;
КонецПроцедуры
Процедура ДокументСписокПриПолученииДанных(Элемент, ОформленияСтрок)
МассивНовыхСсылок = Новый Массив;
// Шаг 1. Отбираем только те документы, которые еще не рассчитывались
Для Каждого СтрокаОформления Из ОформленияСтрок Цикл
СсылкаНаДокумент = СтрокаОформления.ДанныеСтроки.Ссылка;
Если КэшСтатусовВыполнения[СсылкаНаДокумент] = Неопределено Тогда
МассивНовыхСсылок.Добавить(СсылкаНаДокумент);
КонецЕсли;
КонецЦикла;
// Шаг 2. Если появились новые документы на экране, рассчитываем их одним запросом
Если МассивНовыхСсылок.Количество() > 0 Тогда
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ЗаказНаПроизводство.Ссылка КАК Ссылка,
| ВЫБОР
| КОГДА ХозяйственнаяОперация.Выполнена Тогда ИСТИНА
| ИНАЧЕ ЛОЖЬ
| КОНЕЦ КАК Выполнен
|ИЗ
| Документ.ЗаказНаПроизводство КАК ЗаказНаПроизводство
| // Здесь подключается ваша сложная бизнес-логика соединений
|ГДЕ
| ЗаказНаПроизводство.Ссылка В (&МассивНовыхСсылок)";
Запрос.УстановитьПараметр("МассивНовыхСсылок", МассивНовыхСсылок);
Выборка = Запрос.Выполнить().Выбрать();
// Заполняем кэш полученными значениями
Пока Выборка.Следующий() Цикл
КэшСтатусовВыполнения.Вставить(Выборка.Ссылка, Выборка.Выполнен);
КонецЦикла;
// Для документов, по которым запрос не вернул данных, запишем значение по умолчанию
Для Каждого СсылкаИзМассива Из МассивНовыхСсылок Цикл
Если КэшСтатусовВыполнения[СсылкаИзМассива] = Неопределено Тогда
КэшСтатусовВыполнения.Вставить(СсылкаИзМассива, Ложь);
КонецЕсли;
КонецЦикла;
КонецЕсли;
// Шаг 3. Заполняем оформление строк данными из быстрого кэша в памяти
Для Каждого СтрокаОформления Из ОформленияСтрок Цикл
СсылкаНаДокумент = СтрокаОформления.ДанныеСтроки.Ссылка;
СтатусВыполнения = КэшСтатусовВыполнения[СсылкаНаДокумент];
Если СтатусВыполнения = Истина Тогда
СтрокаОформления.Ячейки.Выполнено.Значение = Истина;
СтрокаОформления.Ячейки.Выполнено.ОтображатьФлажок = Истина;
Иначе
СтрокаОформления.Ячейки.Выполнено.Значение = Ложь;
СтрокаОформления.Ячейки.Выполнено.ОтображатьФлажок = Ложь;
КонецЕсли;
КонецЦикла;
КонецПроцедуры
Преимущества метода: Полностью исчезает проблема «обнуления» колонки при фоновых процессах контроля сессий пользователей. Запрос к СУБД по каждому документу выполняется строго один раз. При повторном возникновении события ПриПолученииДанных (например, при скроллинге вверх-вниз) данные мгновенно считываются из оперативной памяти клиента за долю миллисекунды.
Хотя клиентское кэширование решает проблемы интерфейса, оно не избавляет от первоначальной нагрузки на систему, когда пользователь впервые открывает форму списка за большой период времени. Если алгоритм расчета «Выполнено» включает десятки левых соединений, СУБД все равно будет испытывать нагрузку при первичном заполнении кэша.
Профессиональный архитектурный подход 1С заключается в переходе от динамического расчета к событийной модели хранения статусов.
Реализуем этот подход по шагам:
СтатусыВыполненияЗаказов. Измерение: Заказ (тип ДокументСсылка.ЗаказНаПроизводство), Ресурс: Выполнен (тип Булево).ОтчетПроизводстваЗаСмену или при изменении статуса оплаты в платежных поручениях мы программно рассчитываем статус заказа и записываем его в регистр.Пример запроса для динамического списка (свойства ДокументСписок):
ВЫБРАТЬ
ДокументЗаказ.Ссылка КАК Ссылка,
ДокументЗаказ.Дата КАК Дата,
ДокументЗаказ.Номер КАК Номер,
ЕСТЬNULL(Статусы.Выполнен, ЛОЖЬ) КАК Выполнено
ИЗ
Документ.ЗаказНаПроизводство КАК ДокументЗаказ
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.СтатусыВыполненияЗаказов КАК Статусы
ПО ДокументЗаказ.Ссылка = Статусы.Заказ
Преимущества метода: Это абсолютно надежное и быстрое решение, соответствующее стандартам разработки фирмы «1С». Чтение признака происходит мгновенно прямо из СУБД без каких-либо вычислений в интерфейсе. Динамическое обновление списка работает безупречно, интерфейс не тормозит, а нагрузка на сервер распределяется равномерно в течение рабочего дня (в моменты проведения документов, а не при просмотре списков).
Бывают ситуации, когда логика расчета настолько динамична, что хранить ее в регистре невозможно, а при быстром скроллинге списка расчет даже для кэша вызывает микро-задержки (фризы). В таких случаях применяют паттерн «отложенного расчета» с использованием обработчика ожидания.
Идея состоит в том, чтобы не производить никаких тяжелых расчетов во время непрерывной прокрутки списка пользователем. Мы ждем, пока пользователь остановит скроллинг на конкретном экране на 0.3-0.5 секунды, и только тогда рассчитываем информацию для видимых строк.
Рассмотрим пример реализации отложенного вычисления:
Перем ОчередьНаРасчет; // Массив документов для отложенного расчета
Процедура ДокументСписокПриПолученииДанных(Элемент, ОформленияСтрок)
// Сбрасываем старое подключение ожидания
ОтключитьОбработчикОжидания("ВыполнитьОтложенныйРасчет");
ОчередьНаРасчет = Новый Массив;
Для Каждого СтрокаОформления Из ОформленияСтрок Цикл
СсылкаНаДокумент = СтрокаОформления.ДанныеСтроки.Ссылка;
// Запоминаем видимые строки
ОчередьНаРасчет.Добавить(СсылкаНаДокумент);
КонецЦикла;
// Подключаем однократный обработчик ожидания через 0.4 секунды
ПодключитьОбработчикОжидания("ВыполнитьОтложенныйРасчет", 0.4, Истина);
КонецПроцедуры
Процедура ВыполнитьОтложенныйРасчет() Экспорт
Если ОчередьНаРасчет = Неопределено Или ОчередьНаРасчет.Количество() = 0 Тогда
Возврат;
КонецЕсли;
// Выполняем один легкий запрос по накопленной очереди документов
// и программно обновляем только нужные ячейки табличного поля.
// ... (код расчета аналогичен первому методу) ...
ЭлементыФормы.ДокументСписок.ОбновитьСтроки();
КонецПроцедуры
Преимущества метода: Исключительная плавность работы интерфейса. Пользователь может прокручивать список со скоростью тысячи строк в минуту, и система не зависнет ни на секунду. Как только пользователь остановился, чтобы изучить строки на экране, статус «Выполнено» отобразится с едва заметной приятной глазу задержкой.
Выбор оптимального метода зависит от архитектуры вашего решения и требований к оперативности данных: