Как использовать событие ПриПолученииДанныхНаСервере в динамическом списке 1С 8.3?

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

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

Начиная с версии платформы 1С:Предприятие 8.3.10, разработчики получили мощный и производительный инструмент — событие ПриПолученииДанныхНаСервере для динамических списков. Давайте подробно разберем, как устроено это событие, в каких сценариях его нужно применять, как правильно обходить строки для получения реквизитов и как решить специфические задачи, такие как обход ограничений доступа (RLS) в привилегированном режиме.

Постановка задачи: выбор между запросом и программной обработкой

Проанализируем классическую задачу, описанную на форуме. Имеется список документов, в котором есть два реквизита одинакового типа (например, РеквизитА и РеквизитБ), а также булевый флаг. Нам необходимо вывести в отдельную колонку значение РеквизитА, если флаг равен Ложь, и РеквизитБ, если флаг равен Истина.

Выясним, какой инструмент лучше всего подходит для решения этой задачи. Существует два основных подхода:

  1. Модификация запроса динамического списка. Это наиболее производительный метод, когда все расчеты выполняются на уровне СУБД.
  2. Использование обработчика ПриПолученииДанныхНаСервере. Данный метод незаменим, когда данные невозможно или крайне сложно получить стандартным запросом (например, требуется обращение к внешним системам, кэшированным файлам или расчет по сложным встроенным алгоритмам).

Рассмотрим детально оба варианта реализации.

Способ 1. Решение задачи через запрос динамического списка

Если логика выбора проста (как в случае с выбором между двумя реквизитами в зависимости от булева флага), лучшим решением будет использование произвольного запроса в динамическом списке. Это позволит СУБД самостоятельно подготовить данные, а платформе — корректно отрабатывать пользовательские сортировки, группировки и отборы по этой колонке.

Разберем по шагам, как настроить такой список:

  1. Откроем форму списка документов и перейдем в свойства динамического списка Список.
  2. Установим флаг «Произвольный запрос» и перейдем в конструктор запроса.
  3. В тексте запроса напишем условие с использованием конструкции ВЫБОР КОГДА.

Проанализируем пример такого запроса:


ВЫБРАТЬ
    ДокументРеализация.Ссылка КАК Ссылка,
    ДокументРеализация.ПометкаУдаления КАК ПометкаУдаления,
    ДокументРеализация.Дата КАК Дата,
    ДокументРеализация.Номер КАК Номер,
    ДокументРеализация.Проведен КАК Проведен,
    ДокументРеализация.ИспользоватьВариантБ КАК ИспользоватьВариантБ,
    ДокументРеализация.РеквизитА КАК РеквизитА,
    ДокументРеализация.РеквизитБ КАК РеквизитБ,
    // Вычисляем значение динамической колонки на уровне СУБД:
    ВЫБОР
        КОГДА ДокументРеализация.ИспользоватьВариантБ = ИСТИНА 
            ТОГДА ДокументРеализация.РеквизитБ
        ИНАЧЕ ДокументРеализация.РеквизитА
    КОНЕЦ КАК ОтображаемыйРеквизит
ИЗ
    Документ.РеализацияТоваровУслуг КАК ДокументРеализация

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

Способ 2. Использование события ПриПолученииДанныхНаСервере

Событие ПриПолученииДанныхНаСервере выполняется на сервере без контекста формы (директива &НаСервереБезКонтекста). Это означает, что процедура работает максимально быстро, так как серверу не нужно передавать туда и обратно данные всей формы. Ему передается только порция отображаемых в данный момент на экране строк (обычно от 20 до 40 строк в зависимости от высоты монитора пользователя).

Рассмотрим параметры, которые принимает этот обработчик:

Посмотрим на пример реализации, где мы программно заполняем колонку количества и динамически меняем оформление строк в зависимости от условий:


&НаСервереБезКонтекста
Процедура СписокПриПолученииДанныхНаСервере(ИмяЭлемента, Настройки, Строки)
    
    // Шаг 1. Собираем ключи (ссылки документов) отображаемых строк.
    // Это критически важно для исключения запросов в цикле!
    МассивСсылок = Новый Массив;
    Для Каждого СтрокаСписка Из Строки Цикл
        // Ключом строки динамического списка документов является Ссылка
        МассивСсылок.Добавить(СтрокаСписка.Ключ);
    КонецЦикла;
    
    Если МассивСсылок.Количество() = 0 Тогда
        Возврат;
    КонецЕсли;
    
    // Шаг 2. Получаем нужные данные одним запросом к базе данных
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |	РеализацияТоваровУслуг.Ссылка КАК Ссылка,
        |	РеализацияТоваровУслуг.КоличествоДокумента КАК Количество
        |ИЗ
        |	Документ.РеализацияТоваровУслуг КАК РеализацияТоваровУслуг
        |ГДЕ
        |	РеализацияТоваровУслуг.Ссылка В(&МассивСсылок)";
    
    Запрос.УстановитьПараметр("МассивСсылок", МассивСсылок);
    ТаблицаДанных = Запрос.Выполнить().Выгрузить();
    
    // Шаг 3. Распределяем полученные данные по строкам списка
    Для Каждого СтрокаСписка Из Строки Цикл
        
        // Ищем данные по ключу строки
        СтрокаДанных = ТаблицаДанных.Найти(СтрокаСписка.Ключ, "Ссылка");
        
        Если СтрокаДанных <> Неопределено Тогда
            
            // Записываем данные в виртуальную колонку
            СтрокаСписка.Данные.Количество = СтрокаДанных.Количество;
            
            // Настраиваем условное оформление ячейки на лету
            Если СтрокаДанных.Количество > 100 Тогда
                СтрокаСписка.Оформление.Количество.ЦветФона = WebЦвета.СветлоЗеленый;
                СтрокаСписка.Оформление.Количество.Шрифт = Новый Шрифт(,,Истина); // Жирный
            КонецЕсли;
            
        КонецЕсли;
        
    КонецЦикла;
    
КонецПроцедуры

Важное правило оптимизации: Никогда не выполняйте запросы к базе данных внутри цикла по строкам! Сначала соберите все ключи в массив, сделайте один пакетный запрос, выгрузите результат в таблицу значений и затем быстро сопоставьте данные методом Найти() или индексированной таблицей.

Лайфхак: Как передать параметры из формы в процедуру БезКонтекста?

Поскольку процедура ПриПолученииДанныхНаСервере выполняется вне контекста формы (&НаСервереБезКонтекста), мы не можем напрямую обратиться к реквизитам формы (например, к флагу на форме «Показывать остатки»). Как быть в этой ситуации?

Для передачи параметров мы можем использовать механизм ДополнительныеСвойства компоновщика настроек нашего динамического списка. Этот объект доступен как в контексте формы, так и в неконтекстном вызове.

Посмотрим, как это реализовать на практике. В модуле формы при изменении элемента управления на клиенте запишем параметр в настройки списка:


&НаКлиенте
Процедура ПоказыватьТолькоДоступныеПриИзменении(Элемент)
    // Передаем управление на сервер для изменения настроек компоновщика
    УстановитьПараметрОтображенияНаСервере(Объект.ПоказыватьТолькоДоступные);
    // Принудительно обновляем список на форме для вызова события получения данных
    Элементы.Список.Обновить();
КонецПроцедуры

&НаСервере
Процедура УстановитьПараметрОтображенияНаСервере(ЗначениеФлага)
    // Записываем значение флага формы в ДополнительныеСвойства компоновщика
    Список.КомпоновщикНастроек.Настройки.ДополнительныеСвойства.Вставить("ПоказыватьТолькоДоступные", ЗначениеФлага);
КонецПроцедуры

Теперь в обработчике события мы сможем легко прочитать этот параметр из переданных настроек компоновщика:


&НаСервереБезКонтекста
Процедура СписокПриПолученииДанныхНаСервере(ИмяЭлемента, Настройки, Строки)
    
    ПоказыватьТолькоДоступные = Ложь;
    // Безопасно извлекаем свойство из настроек компоновщика
    Настройки.ДополнительныеСвойства.Свойство("ПоказыватьТолькоДоступные", ПоказыватьТолькоДоступные);
    
    Если ПоказыватьТолькоДоступные Тогда
        // Выполняем логику фильтрации или расчета данных...
    КонецЕсли;
    
КонецПроцедуры

Получение данных в привилегированном режиме в обход RLS

Рассмотрим специфический сценарий, поднятый в сообщении 8: как вывести в динамический список данные (например, реальный остаток товара на складе), к которым у текущего пользователя нет прямого доступа из-за настроенных ограничений RLS (ограничение доступа на уровне записей)?

Событие ПриПолученииДанныхНаСервере идеально решает эту задачу при интеграции с привилегированным общим модулем. Алгоритм действий выглядит следующим образом:

  1. Создадим общий модуль (например, ПривилегированныйВызовПолныеПрава).
  2. В свойствах этого модуля установим флаги «Сервер» и «Привилегированный». Флаг «Привилегированный» позволяет выполнять код модуля без проверки прав доступа пользователя.
  3. Напишем экспортную функцию получения остатков по массиву номенклатуры.

Проанализируем код привилегированного общего модуля:


Функция ПолучитьОстаткиНоменклатурыНаСервере(МассивНоменклатуры) Экспорт
    
    Запрос = Новый Запрос;
    // Запрос будет выполнен на сервере без применения правил RLS для текущего пользователя:
    Запрос.Текст = 
        "ВЫБРАТЬ
        |	ТоварыНаСкладахОстатки.Номенклатура КАК Номенклатура,
        |	ТоварыНаСкладахОстатки.ВНаличииОстаток КАК Остаток
        |ИЗ
        |	РегистрНакопления.ТоварыНаСкладах.Остатки(, Номенклатура В (&МассивНоменклатуры)) КАК ТоварыНаСкладахОстатки";
        
    Запрос.УстановитьПараметр("МассивНоменклатуры", МассивНоменклатуры);
    
    Возврат Запрос.Выполнить().Выгрузить();
    
КонецФункции

Теперь в модуле формы в обработчике получения данных вызовем этот модуль:


&НаСервереБезКонтекста
Процедура СписокПриПолученииДанныхНаСервере(ИмяЭлемента, Настройки, Строки)
    
    МассивНоменклатуры = Новый Массив;
    Для Каждого СтрокаСписка Из Строки Цикл
        МассивНоменклатуры.Добавить(СтрокаСписка.Ключ);
    КонецЦикла;
    
    Если МассивНоменклатуры.Количество() = 0 Тогда
        Возврат;
    КонецЕсли;
    
    // Получаем реальные остатки в обход RLS
    ТаблицаОстатков = ПривилегированныйВызовПолныеПрава.ПолучитьОстаткиНоменклатурыНаСервере(МассивНоменклатуры);
    
    // Заполняем данные на форме
    Для Каждого СтрокаСписка Из Строки Цикл
        СтрокаОстатка = ТаблицаОстатков.Найти(СтрокаСписка.Ключ, "Номенклатура");
        Если СтрокаОстатка <> Неопределено Тогда
            СтрокаСписка.Данные.ОстатокЦС = СтрокаОстатка.Остаток;
        КонецЕсли;
    КонецЦикла;
    
КонецПроцедуры

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

Важные ограничения метода и проблемы сортировки

Несмотря на все преимущества использования ПриПолученииДанныхНаСервере, важно помнить о ключевом архитектурном ограничении динамического списка:

Поиск, сортировка, группировка и отборы по колонкам, заполненным программно в этом событии, не поддерживаются платформой!

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

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

← На главную