Как программно настроить сложный отбор по нескольким полям с условиями И / ИЛИ в динамическом списке 1С

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

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

Постановка задачи и разбор логики отбора

Допустим, у нас есть динамический список документов СписокЗаявокУИиС. Нам нужно наложить на него программный фильтр. Логическое выражение выглядит следующим образом:

(Подготовил = ТекущийПользователь ИЛИ Исполнитель = ТекущийПользователь ИЛИ Адресат = ТекущийПользователь) И (СтатусЗаявки <> СтатусЗавершен)

Для реализации этого условия в системе компоновки данных (СКД), на которой построены динамические списки управляемых форм, нам необходимо использовать объекты типа ЭлементОтбораКомпоновкиДанных и ГруппаЭлементовОтбораКомпоновкиДанных. Разберем детально, как устроена эта иерархия объектов.

Анализ структуры отборов в 1С

Любой динамический список имеет свойство Отбор, представляющее собой коллекцию элементов отбора. По умолчанию все элементы, добавленные непосредственно в корень коллекции Отбор.Элементы, объединяются по логическому правилу И (AND). Если мы хотим использовать условие ИЛИ (OR), нам обязательно нужно создать специальную группу.

Рассмотрим ключевые объекты, которые мы будем использовать:

Разбор типичных ошибок разработчиков

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


ГруппаОтбора1 = ГруппаОтбора.Отбор.Элементы.Добавить(Тип("ГруппаЭлементовОтбораКомпоновкиДанных"));

При выполнении этой строки система выдает ошибку «Поле объекта не обнаружено (Отбор)». Выясним причину этой ошибки. Объект ГруппаОтбора сам по себе является элементом структуры компоновки данных. У него нет свойства Отбор. Чтобы добавить вложенный элемент или подчиненную группу внутрь существующей группы, необходимо обращаться напрямую к ее свойству Элементы. Правильный синтаксис выглядит так:


ГруппаОтбора1 = ГруппаОтбора.Элементы.Добавить(Тип("ГруппаЭлементовОтбораКомпоновкиДанных"));

Вторая распространенная ошибка — создание избыточной структуры. Часто разработчики пытаются обернуть абсолютно все условия в отдельные группы на верхнем уровне. Например, создают дополнительную группу «И» на самом верху и помещают все остальные условия внутрь нее. Поскольку корневая коллекция Отбор.Элементы по умолчанию уже работает по принципу логического «И», создавать дополнительную корневую группу «И» избыточно. Это усложняет код и снижает его читаемость.

Вариант 1. Классическая программная установка отборов в СКД

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

Ниже представлен чистый и оптимизированный код, реализующий требуемую логику:


// Очищаем существующие элементы отбора, чтобы избежать дублирования
СписокЗаявокУИиС.Отбор.Элементы.Очистить();

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

// 2. Создаем корневую группу ИЛИ для объединения условий по пользователям
ГруппаПользователи = СписокЗаявокУИиС.Отбор.Элементы.Добавить(Тип("ГруппаЭлементовОтбораКомпоновкиДанных"));
ГруппаПользователи.ТипГруппы = ТипГруппыЭлементовОтбораКомпоновкиДанных.ГруппаИли;
ГруппаПользователи.Представление = "Фильтр по ролям пользователя";
ГруппаПользователи.Использование = Истина;

// 3. Добавляем условие по создателю (Подготовил)
ЭлементПодготовил = ГруппаПользователи.Элементы.Добавить(Тип("ЭлементОтбораКомпоновкиДанных"));
ЭлементПодготовил.ЛевоеЗначение  = Новый ПолеКомпоновкиДанных("Подготовил");
ЭлементПодготовил.ВидСравнения   = ВидСравненияКомпоновкиДанных.Равно;
ЭлементПодготовил.ПравоеЗначение = _ТекущийПользователь;
ЭлементПодготовил.Использование  = Истина;

// 4. Добавляем условие по исполнителю (Исполнитель)
ЭлементИсполнитель = ГруппаПользователи.Элементы.Добавить(Тип("ЭлементОтбораКомпоновкиДанных"));
ЭлементИсполнитель.ЛевоеЗначение  = Новый ПолеКомпоновкиДанных("Исполнитель");
ЭлементИсполнитель.ВидСравнения   = ВидСравненияКомпоновкиДанных.Равно;
ЭлементИсполнитель.ПравоеЗначение = _ТекущийПользователь;
ЭлементИсполнитель.Использование  = Истина;

// 5. Добавляем условие по адресату (Адресат)
ЭлементАдресат = ГруппаПользователи.Элементы.Добавить(Тип("ЭлементОтбораКомпоновкиДанных"));
ЭлементАдресат.ЛевоеЗначение  = Новый ПолеКомпоновкиДанных("Адресат");
ЭлементАдресат.ВидСравнения   = ВидСравненияКомпоновкиДанных.Равно;
ЭлементАдресат.ПравоеЗначение = _ТекущийПользователь;
ЭлементАдресат.Использование  = Истина;

Этот алгоритм полностью закрывает потребность в гибкой фильтрации. Давайте разберем по шагам, как он работает на стороне СУБД. Платформа трансформирует эту структуру настроек компоновки данных в SQL-запрос с конструкцией WHERE (СтатусЗаявки <> @СтатусЗавершен) AND (Подготовил = @ТекущийПользователь OR Исполнитель = @ТекущийПользователь OR Адресат = @ТекущийПользователь). Это именно то, что нам требовалось получить.

Вариант 2. Использование механизмов БСП для чистоты кода

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

Посмотрим на пример реализации аналогичного отбора с применением функций БСП:


// Очищаем текущие отборы списка
СписокЗаявокУИиС.Отбор.Элементы.Очистить();

// Устанавливаем отбор по статусу в корень коллекции отборов динамического списка
ОбщегоНазначенияКлиентСервер.УстановитьЭлементОтбораДинамическогоСписка(
    СписокЗаявокУИиС,
    "СтатусЗаявки",
    СтатусЗавершен(),
    ВидСравненияКомпоновкиДанных.НеРавно,,
    Истина
);

// Создаем группу ИЛИ программно
ГруппаИли = СписокЗаявокУИиС.Отбор.Элементы.Добавить(Тип("ГруппаЭлементовОтбораКомпоновкиДанных"));
ГруппаИли.ТипГруппы = ТипГруппыЭлементовОтбораКомпоновкиДанных.ГруппаИли;
ГруппаИли.Использование = Истина;

// Наполняем созданную группу условиями по пользователям
ОбщегоНазначенияКлиентСервер.УстановитьЭлементОтбора(
    ГруппаИли,
    "Подготовил",
    _ТекущийПользователь,
    ВидСравненияКомпоновкиДанных.Равно,,
    Истина
);

ОбщегоНазначенияКлиентСервер.УстановитьЭлементОтбора(
    ГруппаИли,
    "Исполнитель",
    _ТекущийПользователь,
    ВидСравненияКомпоновкиДанных.Равно,,
    Истина
);

ОбщегоНазначенияКлиентСервер.УстановитьЭлементОтбора(
    ГруппаИли,
    "Адресат",
    _ТекущийПользователь,
    ВидСравненияКомпоновкиДанных.Равно,,
    Истина
);

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

Реализация интерактивного переключения фильтров кнопками

В сообщении темы автор упоминает, что на форме списка необходимо разместить три кнопки для управления видимостью документов:

  1. ФильтрНеЗакрытые — показывает незавершенные заявки текущего пользователя.
  2. ФильтрВсе — показывает все документы, где текущий пользователь является автором, исполнителем или адресатом (без ограничения по статусу).
  3. ОтключитьОтбор — полностью сбрасывает фильтры и выводит весь список документов.

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

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


// Процедура устанавливает отборы в зависимости от выбранного режима
&НаСервере
Процедура УстановитьПрограммныеФильтры(РежимФильтрации)
    
    // Очищаем текущие настройки
    СписокЗаявокУИиС.Отбор.Элементы.Очистить();
    
    Если РежимФильтрации = "Отключить" Тогда
        Возврат; // Выходим, оставляя список без отборов
    КонецЕсли;
    
    // Если режим не "Отключить", в любом случае накладываем ограничение по правам текущего пользователя
    ГруппаИли = СписокЗаявокУИиС.Отбор.Элементы.Добавить(Тип("ГруппаЭлементовОтбораКомпоновкиДанных"));
    ГруппаИли.ТипГруппы = ТипГруппыЭлементовОтбораКомпоновкиДанных.ГруппаИли;
    ГруппаИли.Использование = Истина;
    
    // Заполняем условия группы ИЛИ через БСП
    ОбщегоНазначенияКлиентСервер.УстановитьЭлементОтбора(ГруппаИли, "Подготовил", _ТекущийПользователь);
    ОбщегоНазначенияКлиентСервер.УстановитьЭлементОтбора(ГруппаИли, "Исполнитель", _ТекущийПользователь);
    ОбщегоНазначенияКлиентСервер.УстановитьЭлементОтбора(ГруппаИли, "Адресат",    _ТекущийПользователь);
    
    // Если выбран режим "НеЗакрытые", добавляем дополнительное условие "И" по статусу
    Если РежимФильтрации = "НеЗакрытые" Тогда
        ОбщегоНазначенияКлиентСервер.УстановитьЭлементОтбораДинамическогоСписка(
            СписокЗаявокУИиС,
            "СтатусЗаявки",
            СтатусЗавершен(),
            ВидСравненияКомпоновкиДанных.НеРавно
        );
    КонецЕсли;
    
КонецПроцедуры

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


&НаКлиенте
Процедура КомандаФильтрНеЗакрытыеНажатие(Элемент)
    УстановитьПрограммныеФильтры("НеЗакрытые");
КонецПроцедуры

&НаКлиенте
Процедура КомандаФильтрВсеНажатие(Элемент)
    УстановитьПрограммныеФильтры("Все");
КонецПроцедуры

&НаКлиенте
Процедура КомандаОтключитьОтборНажатие(Элемент)
    УстановитьПрограммныеФильтры("Отключить");
КонецПроцедуры

Такой подход обеспечивает централизованное управление логикой отборов. Код легко поддерживать, модифицировать и расширять новыми фильтрами при необходимости.

Обычный отбор против фиксированных настроек

Важно понимать разницу между свойствами Список.Отбор и Список.КомпоновщикНастроек.ФиксированныеНастройки.Отбор.

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

Если ваша задача — жестко разграничить видимость данных на уровне бизнес-логики и безопасности (например, рядовой сотрудник ни при каких обстоятельствах не должен видеть чужие заявки), то программные отборы необходимо добавлять в Фиксированные настройки динамического списка. Такие отборы не отображаются в стандартном окне настроек формы, и пользователь не сможет обойти их стандартными механизмами платформы. Однако в нашей задаче присутствует кнопка «Отключить отбор», подразумевающая полное снятие ограничений, поэтому использование стандартной коллекции отборов является абсолютно корректным решением. Для реализации подобных ограничений без ручного написания кода подойдёт расширение для настройки видимости объектов в списках.

Проблемы производительности при использовании условий ИЛИ

С точки зрения работы СУБД (MS SQL Server, PostgreSQL и др.), наложение условий с использованием логического оператора ИЛИ (OR) по разным индексированным полям часто является ресурсоемкой операцией. Оптимизатор запросов СУБД в таких случаях может отказаться от использования индексов и выполнить полное сканирование таблицы (Full Table Scan), что приведет к падению скорости работы формы списка при увеличении объема данных.

Чтобы предотвратить зависания интерфейса, рекомендуем выполнить следующие настройки:

  1. Индексирование полей в конфигураторе. Перейдите в свойства метаданных вашего документа на вкладку «Данные». Для реквизитов Подготовил, Исполнитель и Адресат установите свойство «Индексировать» или «Индексировать с дополнительным упорядочиванием». Это позволит СУБД быстрее строить выборку по этим полям.
  2. Использование параметров запроса динамического списка. В качестве альтернативы сложным программным отборам вы можете переписать сам текст запроса динамического списка. Например, использовать конструкцию ГДЕ (Подготовил = &ТекущийПользователь ИЛИ Исполнитель = &ТекущийПользователь ИЛИ Адресат = &ТекущийПользователь). Программная передача параметра выполняется одной строкой: Список.Параметры.УстановитьЗначениеПараметра("ТекущийПользователь", ПараметрыСеанса.ТекущийПользователь);. Этот способ часто работает быстрее, так как структура запроса остается жесткой и понятной для СУБД.

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

← На главную