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