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