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