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