Как настроить динамический отбор по реквизиту формы, зависимому от другого реквизита через регистр сведений, в 1С:Предприятии 8.3?

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

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

Допустим, у нас есть следующая структура: справочник Контрагенты и перечисление СтавкиНДС. Связь между ними осуществляется через регистр сведений, где для каждого контрагента указываются все допустимые для него ставки НДС. На форме документа или элемента справочника присутствуют реквизиты Контрагент и СтавкаНДС. Наша задача — при изменении реквизита Контрагент автоматически фильтровать список выбора для реквизита СтавкаНДС, чтобы пользователь мог выбрать только разрешенные ставки.

Особенности работы с параметрами выбора и клиент-серверным взаимодействием

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

Важно понимать: В обработчике события НачалоВыбора нельзя использовать серверные методы формы с директивой компиляции &НаСервере, а также изменять свойства, которые могут привести к серверному вызову. Это обусловлено архитектурой управляемых форм и клиент-серверного взаимодействия. Событие НачалоВыбора выполняется на клиенте, и прямой синхронный серверный вызов из него запрещен, чтобы избежать блокировок пользовательского интерфейса и непредсказуемого поведения. Если нам все же требуется серверные данные, то для этого существуют другие подходы, которые мы рассмотрим ниже.

Решение 1: Декларативный подход через Связи параметров выбора

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

Как это работает:

  1. Мы привязываем реквизит формы, от которого зависит выбор, к полю, по которому нужно отбирать.
  2. Если настройки заданы для реквизита объекта метаданных, они будут применяться во всех формах, где используется этот реквизит.
  3. Мы можем переопределить эти связи в свойствах конкретного элемента формы, если требуется специфичное поведение.

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

Решение 2: Программное управление параметрами выбора (Рекомендуемый подход для сложной логики)

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

Ключевой момент: Вместо НачалоВыбора мы будем использовать другие обработчики событий, которые позволяют взаимодействовать с сервером.

Пошаговая реализация:

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

    На клиентской стороне формы для элемента Контрагент создадим обработчик события ПриИзменении:

    
    // В модуле формы, на клиенте
    &НаКлиенте
    Процедура КонтрагентПриИзменении(Элемент)
        // При изменении контрагента, нам нужно обновить параметры выбора для ставки НДС.
        // Вызываем серверную процедуру для получения данных.
        ОбновитьПараметрыВыбораСтавкиНДСНаСервере();
    КонецПроцедуры
    
  2. Серверная процедура для получения данных: Из клиентского обработчика мы вызовем серверную процедуру (с директивой &НаСервере) для выполнения запроса к регистру сведений. Эта процедура получит список допустимых ставок НДС.

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

    
    // В модуле формы, на сервере
    &НаСервере
    Процедура ПриЧтенииНаСервере(ТекущийОбъект)
        // При чтении объекта также инициализируем параметры выбора для ставки НДС
        ОбновитьПараметрыВыбораСтавкиНДСНаСервере();
    КонецПроцедуры
    

Разберем подробнее структуру ПараметрыВыбора:

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

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


ПараметрОтборПоВиду = Новый ПараметрВыбора("ВидСравнения", ВидСравнения.Равно);
ПараметрОтборПоЗначению = Новый ПараметрВыбора("Значение", МоеЗначениеОтбора);

Элементы.МойРеквизит.ПараметрыВыбора.Добавить(ПараметрОтборПоВиду);
Элементы.МойРеквизит.ПараметрыВыбора.Добавить(ПараметрОтборПоЗначению);

Таким образом, коллекция ПараметрыВыбора может включать несколько параметров, которые будут применяться совместно.

Решение 3: Открытие формы выбора с отбором

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

Пример реализации:

Для этого мы можем использовать метод ОткрытьФорму() и передать в него структуру Отбор. Мы можем перехватить событие НачалоВыбора (на клиенте) и, установив СтандартнаяОбработка = Ложь, вызвать нашу функцию открытия формы.


// В модуле формы, на клиенте
&НаКлиенте
Процедура СтавкаНДСНачалоВыбора(Элемент, СтандартнаяОбработка)
    СтандартнаяОбработка = Ложь; // Отменяем стандартное открытие формы выбора

    Если Объект.Контрагент.Пустая() Тогда
        ПоказатьПредупреждение(,"Сначала выберите контрагента!");
        Возврат;
    КонецЕсли;

    // Формируем структуру отбора для регистра сведений
    СтруктураОтбора = Новый Структура();
    СтруктураОтбора.Вставить("Контрагент", Объект.Контрагент);

    // Определяем имя формы списка регистра сведений
    ИмяФормыРегистра = "РегистрСведений.КонтрагентыСтавкиНДС.ФормаСписка"; // Пример

    // Создаем структуру параметров для открытия формы
    ПараметрыФормы = Новый Структура("Отбор", СтруктураОтбора);

    // Открываем форму списка регистра сведений с отбором
    ОткрытьФорму(ИмяФормыРегистра, ПараметрыФормы, Элемент);
КонецПроцедуры

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

Более корректный подход, если мы хотим выбрать непосредственно перечисление, — это открыть форму выбора перечисления, но с предварительно полученным списком допустимых значений (как в Решении 2), и затем использовать метод ВыбратьИзСписка().

Решение 4: Работа с динамическим списком и его настройками

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

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


// Предположим, что "СписокСтавок" - это динамический список на форме
ЭлементОтбора = СписокСтавок.Отбор.Элементы.Добавить(Тип("ЭлементОтбораКомпоновкиДанных"));
ЭлементОтбора.ЛевоеЗначение = Новый ПолеКомпоновкиДанных("Контрагент");
ЭлементОтбора.ВидСравнения = ВидСравненияКомпоновкиДанных.Равно;
ЭлементОтбора.ПравоеЗначение = Объект.Контрагент;
ЭлементОтбора.Использование = Истина;

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

Решение 5: Использование метода ВыбратьИзСписка()

Если нам нужно предоставить пользователю выбор из заранее сформированного списка значений, который не обязательно соответствует справочнику или перечислению целиком, мы можем использовать клиентский метод ВыбратьИзСписка(). Этот метод позволяет открыть выпадающий список со значениями, сформированными программно в виде СписокЗначений.

Мы бы использовали этот метод в событии НачалоВыбора (на клиенте), предварительно получив список допустимых ставок НДС с сервера (например, при помощи асинхронного вызова, если это критично для производительности, но чаще всего достаточно получить его в ПриИзменении, как описано в Решении 2). Если ПараметрыВыбора уже настроены, то платформа сама применит их для формы выбора перечисления. Если мы хотим полностью контролировать выбор, то:


// В модуле формы, на клиенте
&НаКлиенте
Процедура СтавкаНДСНачалоВыбора(Элемент, СтандартнаяОбработка)
    СтандартнаяОбработка = Ложь; // Отменяем стандартное открытие формы выбора

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

    СписокДляВыбора = ПолучитьСписокДопустимыхСтавокНДСКлиент(); // Пример клиентской функции, которая вернет СписокЗначений

    Если СписокДляВыбора.Количество() > 0 Тогда
        ВыбранноеЗначение = Неопределено;
        Если ВыбратьИзСписка(СписокДляВыбора, ВыбранноеЗначение, Элемент.Имя, Элемент) Тогда
            Элемент.УстановитьЗначение(ВыбранноеЗначение);
        КонецЕсли;
    Иначе
        ПоказатьПредупреждение(, "Для текущего контрагента нет доступных ставок НДС.");
    КонецЕсли;
КонецПроцедуры

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

    Возврат ПолучитьСписокДопустимыхСтавокНДССерверныйВызов(); // Асинхронный или синхронный вызов на сервер

КонецФункции

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

    Выборка = Запрос.Выполнить().Выбрать();

    Пока Выборка.Следующий() Цикл
        СписокСтавок.Добавить(Выборка.СтавкаНДС);
    КонецЦикла;

    Возврат СписокСтавок;
КонецФункции;

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

Работа с регистром сведений: общие принципы

Для получения данных из регистра сведений с отбором мы обычно используем объект НаборЗаписей регистра или прямой запрос. Установка отбора по измерениям регистра позволяет эффективно извлекать только нужные данные. Например, для регистра КонтрагентыСтавкиНДС с измерением Контрагент, мы можем установить отбор:


НаборЗаписей = РегистрыСведений.КонтрагентыСтавкиНДС.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.Контрагент.Установить(МойКонтрагент);
НаборЗаписей.Прочитать(); // Записи будут прочитаны по установленному отбору

Полученный НаборЗаписей можно затем перебрать, чтобы сформировать список значений для отбора.

Дополнительные моменты:

Таким образом, для задачи отбора ставок НДС по контрагенту, связанных через регистр сведений, наиболее чистым и гибким подходом является программное заполнение ПараметровВыбора элемента формы в серверных обработчиках ПриИзменении (для зависимого реквизита) и ПриЧтенииНаСервере формы. Этот метод позволяет нам реализовать любую требуемую логику фильтрации, соблюдая при этом правила клиент-серверного взаимодействия в управляемых формах 1С:Предприятия 8.3.

← На главную