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