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