Как установить проверку и отбор данных при программном выводе отчета в макет 1С

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

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

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

Постановка задачи и анализ исходной проблемы

Представим сценарий: нам необходимо сформировать отчет по взаиморасчетам с клиентами. В отчете должна выводиться информация по платежам только тех контрагентов, у которых сумма долга со сроком более 30 дней строго больше нуля (Сумма30 > 0). Все остальные строки, не удовлетворяющие этому условию, выводить в табличный документ не нужно.

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

Проанализируем фрагмент исходного кода, который часто вызывает затруднения у разработчиков:


// Фрагмент кода внутри цикла обхода выборки
Если ВсегоВсего <> СуммаТекущая Тогда
    ПрошлыйМесяц2 = ДобавитьМесяц(ПрошлыйМесяц, -1);
    Сумма30 = ПосчитатьСколькоСуммыПлатежаЗаПериод(ВыборкаДок.Регистратор, НачалоМесяца(ПрошлыйМесяц2), КонецМесяца(ПрошлыйМесяц2));
    Сумма30 = Мин(Сумма30, ВсегоВсего - СуммаТекущая);
КонецЕсли;

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

Типичная ошибка №1: Эффект «залипания» значений переменных

Обратите внимание на то, что переменная Сумма30 рассчитывается строго внутри условной конструкции Если ВсегоВсего <> СуммаТекущая Тогда. Если на определенной итерации цикла это условие не выполняется (например, для текущего клиента показатели равны), ветка расчетов пропускается.

В результате переменная Сумма30 сохраняет свое значение, полученное на предыдущей итерации цикла от совершенно другого контрагента! Программа проверит старое значение, посчитает его верным и выведет строку текущего клиента, у которого на самом деле долга нет. Это классическая ошибка инициализации данных в цикле.

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

Типичная ошибка №2: Неверное использование оператора Продолжить

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


Если Сумма30 > 0 Тогда 
    Продолжить;
КонецЕсли;

Данный код работает с точностью до наоборот. Оператор Продолжить прерывает выполнение текущей итерации и возвращает указатель в начало цикла. Таким образом, написав такое условие, мы пропустим вывод именно тех строк, где долг от 30 дней присутствует, и выведем пустые строки или строки с нулевым долгом.

Правильное отрицание условия должно выглядеть следующим образом:


Если Сумма30 <= 0 Тогда 
    Продолжить; // Пропускаем итерацию, если долга нет
КонецЕсли;

Если условие ложно (то есть Сумма30 больше нуля), управление передается коду вывода области в табличный документ.

Вариант решения №1: Прямая проверка перед выводом (Базовый метод)

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

  1. Организуем цикл обхода выборки данных.
  2. В начале цикла инициализируем переменную Сумма30 нулевым значением.
  3. Производим расчет показателей по алгоритму.
  4. Проверяем значение переменной условным оператором Если...Тогда.
  5. Выводим область макета только при успешном прохождении проверки.

Проанализируем пример кода, реализующий эту логику:


ТабДок = Новый ТабличныйДокумент;
Макет = ПолучитьМакет("МакетОтчета");
ОбластьСтрока = Макет.ПолучитьОбласть("Строка");

Выборка = РезультатЗапроса.Выбрать();

Пока Выборка.Следующий() Цикл
    
    // 1. Обязательный сброс значения перед расчетами
    Сумма30 = 0; 
    
    // 2. Расчет показателей (алгоритм разработчика)
    Если Выборка.ВсегоВсего <> Выборка.СуммаТекущая Тогда
        ПрошлыйМесяц2 = ДобавитьМесяц(ПрошлыйМесяц, -1);
        Сумма30 = ПосчитатьСколькоСуммыПлатежаЗаПериод(Выборка.Регистратор, НачалоМесяца(ПрошлыйМесяц2), КонецМесяца(ПрошлыйМесяц2));
        Сумма30 = Мин(Сумма30, Выборка.ВсегоВсего - Выборка.СуммаТекущая);
    КонецЕсли;
    
    // 3. Проверка условия вывода
    Если Сумма30 > 0 Тогда
        // Заполняем параметры области макета данными из выборки и расчетов
        ОбластьСтрока.Параметры.Заполнить(Выборка);
        ОбластьСтрока.Параметры.СуммаДолга30 = Сумма30;
        
        // Выводим только проверенную строку
        ТабДок.Вывести(ОбластьСтрока);
    КонецЕсли;
    
КонецЦикла;

Этот способ прост в реализации, однако он имеет существенные недостатки при работе со сложными иерархическими отчетами или при больших объемах данных.

Вариант решения №2: Буферизация данных через Таблицу Значений (Рекомендуемый метод)

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

  1. Выгружаем результат запроса в таблицу значений с помощью метода Выгрузить().
  2. Добавляем в таблицу новую колонку для расчетного показателя (например, Сумма30).
  3. Проходим по таблице циклом, рассчитываем показатель и записываем его в каждую строку.
  4. Удаляем из таблицы значений строки, не проходящие критерий отбора.
  5. Выводим готовую, очищенную таблицу в макет без каких-либо условий внутри цикла вывода.

Рассмотрим детальный пример реализации этого паттерна:


// Шаг 1: Получение исходной таблицы значений
ТаблицаДанных = РезультатЗапроса.Выгрузить();
ТаблицаДанных.Колонки.Добавить("Сумма30", Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15, 2)));

// Шаг 2: Расчет данных в строках таблицы
Для Каждого СтрокаДанных Из ТаблицаДанных Цикл
    
    РасчетнаяСумма = 0;
    Если СтрокаДанных.ВсегоВсего <> СтрокаДанных.СуммаТекущая Тогда
        ПрошлыйМесяц2 = ДобавитьМесяц(ПрошлыйМесяц, -1);
        РасчетнаяСумма = ПосчитатьСколькоСуммыПлатежаЗаПериод(СтрокаДанных.Регистратор, НачалоМесяца(ПрошлыйМесяц2), КонецМесяца(ПрошлыйМесяц2));
        РасчетнаяСумма = Мин(РасчетнаяСумма, СтрокаДанных.ВсегоВсего - СтрокаДанных.СуммаТекущая);
    КонецЕсли;
    
    СтрокаДанных.Сумма30 = РасчетнаяСумма;
    
КонецЦикла;

// Шаг 3: Фильтрация таблицы (удаление неподходящих строк)
ИндексСтроки = ТаблицаДанных.Количество() - 1;
Пока ИндексСтроки >= 0 Цикл
    СтрокаДляПроверки = ТаблицаДанных[ИндексСтроки];
    Если СтрокаДляПроверки.Сумма30 <= 0 Тогда
        ТаблицаДанных.Удалить(СтрокаДляПроверки);
    КонецЕсли;
    ИндексСтроки = ИндексСтроки - 1;
КонецЦикла;

// Шаг 4: Вывод подготовленных данных в макет
ТабДок = Новый ТабличныйДокумент;
Макет = ПолучитьМакет("МакетОтчета");
ОбластьСтрока = Макет.ПолучитьОбласть("Строка");

Для Каждого СтрокаВывода Из ТаблицаДанных Цикл
    ОбластьСтрока.Параметры.Заполнить(СтрокаВывода);
    ТабДок.Вывести(ОбластьСтрока);
КонецЦикла;

Преимущество метода: Мы полностью разделили расчетную часть и построение визуальной формы. Код стал чище, его проще отлаживать. Кроме того, перед выводом мы всегда можем точно узнать количество строк, которые действительно попадут в финальный отчет.

Вариант решения №3: Проблема отложенного вывода в иерархических отчетах

Если ваш отчет содержит группировки (например, Контрагент -> Договоры -> Накладные), простое использование оператора Продолжить на детальных строках приведет к серьезному визуальному дефекту. Программа выведет заголовок группировки (Контрагента), начнет обходить его накладные, поймет, что долга старше 30 дней по ним нет, и пропустит детальные строки. В итоге в табличном документе отобразятся «пустые» контрагенты без подчиненных строк.

Для решения этой проблемы мы применим паттерн отложенного вывода (буферизации областей):

  1. При начале обхода новой группировки (Контрагента) мы НЕ выводим его шапку сразу в табличный документ.
  2. Создаем массив для накопления подготовленных детальных строк (буфер).
  3. Обходим подчиненные строки, рассчитываем показатель Сумма30.
  4. Если строка проходит отбор, мы заполняем область макета детальной строки и добавляем ее в наш массив-буфер.
  5. После завершения обхода детальных записей контрагента проверяем размер буфера. Если в нем есть хотя бы одна строка, мы сначала выводим область шапки контрагента, а затем циклом выводим все накопленные детальные области из буфера. Если буфер пуст — полностью игнорируем данную группу контрагента.

Рассмотрим схематичный пример реализации отложенного вывода:


ВыборкаКонтрагенты = РезультатЗапроса.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам, "Контрагент");

Пока ВыборкаКонтрагенты.Следующий() Цикл
    
    // Получаем область шапки группы, но пока НЕ выводим ее
    ОбластьШапкаГруппы = Макет.ПолучитьОбласть("ШапкаГруппы");
    ОбластьШапкаГруппы.Параметры.Заполнить(ВыборкаКонтрагенты);
    
    БуферДетальныхСтрок = Новый Массив;
    ВыборкаДетали = ВыборкаКонтрагенты.Выбрать();
    
    Пока ВыборкаДетали.Следующий() Цикл
        
        Сумма30 = 0;
        // ... Расчет показателя Сумма30 по детальной строке ...
        
        Если Сумма30 > 0 Тогда
            ОбластьСтрока = Макет.ПолучитьОбласть("ДетальнаяСтрока");
            ОбластьСтрока.Параметры.Заполнить(ВыборкаДетали);
            ОбластьСтрока.Параметры.Сумма30 = Сумма30;
            
            // Сохраняем готовую область в буфер
            БуферДетальныхСтрок.Добавить(ОбластьСтрока);
        КонецЕсли;
        
    КонецЦикла;
    
    // Если в буфере есть хотя бы одна подошедшая строка, выводим группу целиком
    Если БуферДетальныхСтрок.Количество() > 0 Тогда
        ТабДок.Вывести(ОбластьШапкаГруппы); // Вывод родителя
        
        Для Каждого ОбластьИзБуфера Из БуферДетальныхСтрок Цикл
            ТабДок.Вывести(ОбластьИзБуфера); // Вывод деталей
        КонецЦикла;
    КонецЕсли;
    
КонецЦикла;

Архитектурная рекомендация: Избавление от проблемы N+1

Анализируя строку кода Сумма30 = ПосчитатьСколькоСуммыПлатежаЗаПериод(...), выполняющуюся внутри цикла, необходимо указать на серьезную архитектурную проблему производительности. Данный вызов порождает так называемую проблему N+1 запроса. Если в выборке отчета будет 10 000 клиентов, программа выполнит 10 000 отдельных мелких запросов к базе данных для получения платежей, что приведет к катастрофическому замедлению работы системы и перегрузке СУБД.

Решение проблемы:

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

← На главную