Как обеспечить обязательный отбор по дате в форме динамического списка и предотвратить перегрузку сервера из-за пользовательских настроек?

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

Мы рассмотрим актуальную проблему, с которой часто сталкиваются пользователи и разработчики 1С: замедление работы системы или даже «падение» сервера при активном использовании форм списков документов. В частности, речь идет о ситуации, когда пользователи в форме динамического списка, такой как «Реализация товаров услуг», отключают обязательный отбор по дате и дополнительно устанавливают сложные группировки, что приводит к огромной нагрузке на базу данных и сервер.

Наша цель – выяснить, как мы можем программно запретить открытие формы без установленного отбора по дате, или как автоматически установить этот отбор, если он отсутствует, а также как ограничить возможности пользователей по созданию ресурсоемких группировок. Мы проанализируем различные подходы и методы, доступные в платформе 1С:Предприятие 8.3.

Разбираемся в механизмах отбора динамического списка

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

Почему Список.Отбор.Элементы может быть пустым?

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

Таким образом, если мы хотим проверить, установил ли пользователь отбор по дате, или хотим принудительно его установить, нам необходимо работать именно с объектом КомпоновщикНастроек и его коллекциями.

Подход 1: Программная проверка и установка обязательного отбора по дате при открытии формы

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

Шаг 1: Получаем доступ к компоновщику настроек

В процедуре ПриСозданииНаСервере мы имеем доступ к динамическому списку через свойство Элементы.Список. Компоновщик настроек списка доступен через Элементы.Список.КомпоновщикНастроек.

Мы будем работать с текущими (активными) настройками отбора, которые хранятся в КомпоновщикНастроек.Настройки.Отбор.Элементы. Это коллекция элементов отбора, которые фактически применяются к динамическому списку.

Шаг 2: Проверяем наличие и корректность отбора по дате

Наша задача – убедиться, что отбор по дате установлен и соответствует необходимым условиям (например, "ТекущийМесяц"). Мы можем пройтись по существующим элементам отбора и найти нужный.


&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
    // Получаем компоновщик настроек динамического списка
    КомпоновщикНастроекСписка = Элементы.Список.КомпоновщикНастроек;
    
    // Получаем текущие настройки отбора
    НастройкиОтбора = КомпоновщикНастроекСписка.Настройки.Отбор.Элементы;
    
    // Флаг, указывающий, найден ли подходящий отбор по дате
    ОтборПоДатеНайден = Ложь;
    
    // Ищем элемент отбора по дате
    Для Каждого ЭлементОтбора Из НастройкиОтбора Цикл
        Если ЭлементОтбора.Использование И ТипЗнч(ЭлементОтбора) = Тип("ЭлементОтбораКомпоновкиДанных") Тогда
            Если ЭлементОтбора.Поле.Имя = "Дата" Тогда // Или "Период" в зависимости от поля в запросе
                // Мы нашли отбор по дате. Теперь проверяем его вид сравнения
                // Для "ТекущийМесяц" обычно используется ВидСравненияКомпоновкиДанных.Интервал
                // или несколько условий: > НачалоМесяца и < КонецМесяца
                Если ЭлементОтбора.ВидСравнения = ВидСравненияКомпоновкиДанных.Интервал Тогда
                    // Здесь можно дополнительно проверить Значение.ДатаНачала и Значение.ДатаОкончания
                    // если они заданы как СтандартныйПериод, или просто проверить, что интервал не пустой.
                    // Для примера, считаем, что любой интервальный отбор по дате подходит
                    ОтборПоДатеНайден = Истина;
                    Прервать;
                Иначе
                    // Если отбор по дате есть, но он не интервальный,
                    // можно решить его удалить или модифицировать
                    // В данном случае, мы ищем конкретный интервальный отбор
                    // или же просто его наличие как критерий
                КонецЕсли;
            КонецЕсли;
        КонецЕсли;
    КонецЦикла;
    
    // Если отбор по дате не найден или не соответствует нашим требованиям, устанавливаем его
    Если Не ОтборПоДатеНайден Тогда
        // 1. Сначала удалим все существующие элементы отбора, которые могут конфликтовать
        // Или только те, которые относятся к "Дате"
        Для Индекс = НастройкиОтбора.Количество() - 1 По 0 Шаг -1 Цикл
            ЭлементОтбора = НастройкиОтбора[Индекс];
            Если ТипЗнч(ЭлементОтбора) = Тип("ЭлементОтбораКомпоновкиДанных") Тогда
                Если ЭлементОтбора.Поле.Имя = "Дата" Тогда // Учитываем поле
                    НастройкиОтбора.Удалить(ЭлементОтбора);
                КонецЕсли;
            КонецЕсли;
        КонецЦикла;
        
        // 2. Создаем новый элемент отбора по дате "ТекущийМесяц"
        НовыйЭлементОтбора = НастройкиОтбора.Добавить(Тип("ЭлементОтбораКомпоновкиДанных"));
        НовыйЭлементОтбора.ЛевоеЗначение = Новый ПолеКомпоновкиДанных("Дата");
        НовыйЭлементОтбора.ВидСравнения = ВидСравненияКомпоновкиДанных.Интервал;
        
        // Устанавливаем стандартный период
        СтандартныйПериод = Новый СтандартныйПериод;
        СтандартныйПериод.Установить(СтандартныйПериодТекущийМесяц()); // Метод для установки текущего месяца
        
        НовыйЭлементОтбора.ПравоеЗначение = СтандартныйПериод;
        НовыйЭлементОтбора.Использование = Истина;
        
        // Для удобства пользователя, можно сделать его невидимым или недоступным для изменения
        // (хотя это не всегда возможно для элементов, добавленных программно таким образом,
        // лучше использовать ограничения ниже)
        
        // Дополнительно можно обновить форму, если это необходимо
        // (для того, чтобы пользователь сразу увидел примененный отбор)
        // Но при ПриСозданииНаСервере это происходит автоматически после отрисовки формы
    КонецЕсли;
КонецПроцедуры

// Вспомогательная функция для получения стандартного периода "ТекущийМесяц"
&НаСервере
Функция СтандартныйПериодТекущийМесяц()
    Результат = Новый СтандартныйПериод;
    Результат.ДатаНачала = НачалоМесяца(ТекущаяДата());
    Результат.ДатаОкончания = КонецДня(ТекущаяДата());
    Возврат Результат;
КонецФункции

В этом примере мы проверяем наличие отбора по полю "Дата" с видом сравнения "Интервал". Если такой отбор не найден, или он не соответствует нашим требованиям, мы удаляем все старые отборы по дате и добавляем новый, устанавливающий "ТекущийМесяц".

Важные замечания:

  1. Если поле в запросе динамического списка называется иначе (например, "ДатаДокумента"), используйте соответствующее имя поля: Новый ПолеКомпоновкиДанных("ДатаДокумента").
  2. Проверка значения отбора по стандартному периоду может быть более сложной, так как ПравоеЗначение для ВидСравненияКомпоновкиДанных.Интервал может быть как объектом СтандартныйПериод, так и структурой с полями ДатаНачала и ДатаОкончания, или даже просто двумя датами. В нашем примере мы предполагаем использование объекта СтандартныйПериод.
  3. Для того чтобы пользователь видел этот отбор на форме и мог его отключить/включить (если это допустимо), но не мог изменить его значения, можно использовать различные подходы. Однако, наша главная цель — принудительная установка.

Шаг 3: Как убедиться, что отбор установлен через Элементы.Список.Период?

В сообщении 1 приводится пример СП = Элементы.Список.Период; СП.ДатаНачала = НачалоМесяца(ТекущаяДата); СП.ДатаОкончания = КонецДня(ТекущаяДата);. Это свойство Период доступно только для динамических списков, у которых в запросе есть стандартный период. Если ваш динамический список построен так, что поддерживает это свойство, то его использование может быть более простым способом установить период. Однако, оно не всегда доступно и не всегда достаточно для полной кастомизации отбора. Наш предыдущий подход через КомпоновщикНастроек более универсален.


&НаСервере
Процедура УстановитьОтборЧерезСвойствоПериод()
    Если Элементы.Список.Период.Использование Тогда
        // Проверяем, соответствует ли текущий период нашим требованиям
        Если Элементы.Список.Период.ДатаНачала <> НачалоМесяца(ТекущаяДата()) ИЛИ 
           Элементы.Список.Период.ДатаОкончания <> КонецДня(ТекущаяДата()) Тогда
            // Если не соответствует, устанавливаем нужный
            Элементы.Список.Период.ДатаНачала = НачалоМесяца(ТекущаяДата());
            Элементы.Список.Период.ДатаОкончания = КонецДня(ТекущаяДата());
        КонецЕсли;
    Иначе
        // Если отбор по периоду отключен, включаем и устанавливаем
        Элементы.Список.Период.Использование = Истина;
        Элементы.Список.Период.ДатаНачала = НачалоМесяца(ТекущаяДата());
        Элементы.Список.Период.ДатаОкончания = КонецДня(ТекущаяДата());
    КонецЕсли;
КонецПроцедуры

Этот подход проще, если свойство Период доступно и достаточного для вашей задачи. Рекомендуем сначала попробовать его, и если не подойдет — перейти к работе с КомпоновщикНастроек.

Подход 2: Ограничение пользовательских возможностей для предотвращения перегрузки сервера

Помимо принудительной установки отбора по дате, крайне важно рассмотреть способы ограничения пользовательских действий, которые приводят к высокой нагрузке на сервер (таких как произвольные группировки или отключение ключевых отборов). Это более системный подход, который предотвращает проблему в корне.

1. Использование фиксированного отбора в конфигураторе

Мы можем настроить отбор непосредственно в запросе динамического списка в конфигураторе. Этот отбор пользователь не сможет отключить или изменить.

  1. Откройте конфигуратор, перейдите к форме списка, затем к динамическому списку.
  2. Откройте редактор запроса списка.
  3. В запросе добавьте условие ГДЕ Документ.Дата МЕЖДУ &НачалоМесяца И &КонецМесяца (или аналогичное).
  4. Объявите параметры &НачалоМесяца и &КонецМесяца и установите их значения в процедуре ПриСозданииНаСервере через Элементы.Список.Параметры.УстановитьЗначение().

Это делает отбор абсолютно обязательным, но и негибким для пользователя.

2. Передача отбора через параметры открытия формы

Если вы открываете форму списка программно, вы можете передать отбор через параметры открытия формы. Для этого необходимо, чтобы имя свойства структуры в параметре Отбор совпадало с именем поля отбора динамического списка, и у динамического списка должен быть установлен флаг "основной реквизит".

Пример:


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

Этот способ позволяет передать начальные значения отбора, но пользователю может быть разрешено их изменить, если не использовать дополнительные ограничения.

3. Запрет изменения формы и ее элементов

Мы можем ограничить пользователя в изменении самой формы или отдельных ее элементов. Свойство РазрешитьИзменятьФорму для всей формы или РазрешитьИзменять для отдельных элементов формы (например, для поля даты отбора) может быть установлено в "Ложь".

Например, в процедуре ПриСозданииНаСервере:


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

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

4. Ограничение использования полей в отборах, группировках и сортировках (платформа 8.3.10+)

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

Методы для ограничения:

Эти методы позволяют нам указать, какие поля (или их дочерние поля) нельзя использовать в пользовательских отборах, группировках или сортировках. Это особенно полезно для предотвращения использования неиндексированных или ресурсоемких полей, которые могут "положить" сервер при попытке их сгруппировать.

Пример использования УстановитьОграниченияИспользованияВГруппировке():

Предположим, в вашем списке есть поле "Контрагент.ПолноеНаименование", группировка по которому очень ресурсоемка. Вы можете запретить его использование для группировки.


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

Эти ограничения будут действовать для пользовательских настроек, то есть пользователи не смогут добавить эти поля в свои группировки через стандартное окно настроек списка. Это мощный инструмент для контроля производительности.

Важно отметить, что если пользователь имеет полные права (администратор), он может обходить некоторые ограничения. Однако для большинства пользователей этот подход будет очень эффективен.

Оптимизация производительности динамических списков и группировок

Давайте также вспомним основные правила оптимизации, которые помогут снизить нагрузку даже при частичной свободе пользователя:

  1. Группировки только для небольшого числа записей: Группировки в динамических списках рекомендуется использовать только тогда, когда количество записей после применения отборов невелико (не более нескольких сотен). Если ожидается большой объем данных, группировки лучше выносить в отдельные отчеты.
  2. Группировка по проиндексированным полям: Все поля, по которым планируется осуществлять группировку, должны быть проиндексированы в базе данных. Это существенно ускоряет выполнение запросов. Поле, по которому осуществляется первая группировка, должно обладать высокой селективностью (т.е., для каждого значения этого поля должно соответствовать небольшое количество записей).
  3. Избегайте сложных запросов: Запросы динамических списков должны быть максимально простыми. Обновление списка происходит в режиме реального времени, и каждый сложный запрос может значительно замедлить работу. Все сложные вычисления, объединения с множеством таблиц или виртуальными таблицами лучше переносить в отчеты или предварительно агрегировать данные.
  4. Отбор в запросе динамического списка: Применяйте наиболее строгие отборы непосредственно в запросе динамического списка в конфигураторе. Это уменьшает объем данных, загружаемых из базы данных изначально, до применения пользовательских отборов.
  5. Использование серверного кэша: Если в вашей конфигурации есть возможность кэширования данных или результатов запросов, это может помочь снизить нагрузку при частом обращении к одним и тем же данным.

Объединяем подходы

Мы предлагаем комбинированный подход для решения нашей задачи:

  1. Программная проверка и установка отбора по дате: В процедуре ПриСозданииНаСервере формы списка, мы проверяем, установлен ли отбор по дате. Если нет или он некорректен, мы принудительно устанавливаем его на "ТекущийМесяц". Это гарантирует, что список всегда будет открываться с минимальным необходимым отбором. Мы используем для этого работу с КомпоновщикНастроек.Настройки.Отбор.Элементы или свойство Элементы.Список.Период, если оно применимо.
  2. Ограничение пользовательских группировок: Используем метод УстановитьОграниченияИспользованияВГруппировке() (и при необходимости УстановитьОграниченияИспользованияВОтборе()) для тех полей, которые потенциально могут привести к зависанию сервера при попытке их сгруппировать. Это предотвратит создание "тяжелых" запросов самими пользователями.

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

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

← На главную