В процессе разработки автоматизированных систем на платформе 1С:Предприятие часто возникает задача автоматической рассылки или сохранения отчетов во внешние файлы (Excel, PDF, Word). При этом крайне важно избежать отправки «пустых» файлов, содержащих исключительно шапку, заголовки таблиц и пустые строки итогов. Отправка таких отчетов пользователям снижает качество сервиса и несет лишнюю нагрузку на почтовые серверы.
Определить визуально, пуст ли отчет, для пользователя не составляет труда. Однако программно сделать это в системе компоновки данных (СКД) не так просто, как кажется на первый взгляд. В этой статье мы подробно разберем, почему стандартные подходы могут возвращать неверный результат, проанализируем различные методы решения этой проблемы и выберем наиболее быстрый и надежный способ проверки.
Первая идея, которая приходит в голову большинству разработчиков, — выгрузить результат компоновки данных в коллекцию (например, в ТаблицаЗначений или ДеревоЗначений) и проверить количество строк через метод Количество(). Рассмотрим стандартный код такой проверки:
КомпоновщикМакета = Новый КомпоновщикМакетаКомпоновкиДанных;
МакетКомпоновки = КомпоновщикМакета.Выполнить(СхемаКомпоновкиДанных, КомпоновщикНастроек.ПолучитьНастройки(), , , Тип("ГенераторМакетаКомпоновкиДанныхДляКоллекцииЗначений"));
ПроцессорКомпоновки = Новый ПроцессорКомпоновкиДанных;
ПроцессорКомпоновки.Инициализировать(МакетКомпоновки);
ПроцессорВывода = Новый ПроцессорВыводаРезультатаКомпоновкиДанныхВКоллекциюЗначений;
ТаблицаЗначений = Новый ТаблицаЗначений;
ПроцессорВывода.УстановитьОбъект(ТаблицаЗначений);
ПроцессорВывода.Вывести(ПроцессорКомпоновки);
Если ТаблицаЗначений.Количество() = 0 Тогда
ВызватьИсключение "Отчет пустой!";
КонецЕсли;
Протестировав данный код на пустой базе данных, мы с удивлением обнаружим, что метод ТаблицаЗначений.Количество() возвращает значение больше нуля (обычно 1 или более строк), хотя реальных данных в информационной базе нет. Выясним причину такого поведения платформы.
Дело в том, что по умолчанию генератор макета компоновки для коллекции значений переносит в результат всю структуру отчета. Если в настройках компоновщика включен вывод общих итогов или присутствуют родительские группировки, то СКД сформирует строки под эти итоги и группировки. В результате в полученной таблице значений окажется одна строка, все колонки ресурсов которой будут заполнены значениями NULL или 0. Проверка на Количество() > 0 в данном случае не сработает.
Если мы попытаемся применить условие Количество() > 1, то столкнемся с другой проблемой: если отчет содержит ровно одну легитимную запись, система посчитает его пустым и не отправит получателю.
Чтобы метод выгрузки в ТаблицаЗначений работал корректно и действительно возвращал пустую коллекцию при отсутствии данных, необходимо предварительно подготовить настройки отчета перед компоновкой макета. Нам нужно принудительно отключить вывод общих итогов и оставить в структуре только детальные записи.
Рассмотрим, как это сделать программно:
// Создаем копию настроек, чтобы не испортить исходные настройки пользователя
НастройкиДляПроверки = КомпоновщикНастроек.ПолучитьНастройки();
// Отключаем вывод общих итогов по вертикали и горизонтали
ПараметрВыводаИтоги = НастройкиДляПроверки.ПараметрыВывода.НайтиЗначениеПараметра(Новый ПараметрКомпоновкиДанных("HorizontalOverallPlacement"));
Если ПараметрВыводаИтоги <> Неопределено Тогда
ПараметрВыводаИтоги.Значение = РасположениеИтоговКомпоновкиДанных.Нет;
ПараметрВыводаИтоги.Использование = Истина;
КонецЕсли;
ПараметрВыводаИтогиВерт = НастройкиДляПроверки.ПараметрыВывода.НайтиЗначениеПараметра(Новый ПараметрКомпоновкиДанных("VerticalOverallPlacement"));
Если ПараметрВыводаИтогиВерт <> Неопределено Тогда
ПараметрВыводаИтогиВерт.Значение = РасположениеИтоговКомпоновкиДанных.Нет;
ПараметрВыводаИтогиВерт.Использование = Истина;
КонецЕсли;
// Оставляем только детальные записи в структуре, удаляя лишние группировки
НастройкиДляПроверки.Структура.Очистить();
НоваяГруппировка = НастройкиДляПроверки.Структура.Добавить(Тип("ГруппировкаКомпоновкиДанных"));
НоваяГруппировка.Использование = Истина;
// Теперь генерируем макет и выгружаем в ТЗ
КомпоновщикМакета = Новый КомпоновщикМакетаКомпоновкиДанных;
МакетКомпоновки = КомпоновщикМакета.Выполнить(СхемаКомпоновкиДанных, НастройкиДляПроверки, , , Тип("ГенераторМакетаКомпоновкиДанныхДляКоллекцииЗначений"));
ПроцессорКомпоновки = Новый ПроцессорКомпоновкиДанных;
ПроцессорКомпоновки.Инициализировать(МакетКомпоновки);
ПроцессорВывода = Новый ПроцессорВыводаРезультатаКомпоновкиДанныхВКоллекциюЗначений;
ДанныеТЗ = Новый ТаблицаЗначений;
ПроцессорВывода.УстановитьОбъект(ДанныеТЗ);
ПроцессорВывода.Вывести(ПроцессорКомпоновки);
Если ДанныеТЗ.Количество() = 0 Тогда
Сообщить("Отчет не содержит данных!");
КонецЕсли;
При таком подходе, если запрос СКД вернет пустой результат, полученная таблица значений гарантированно будет иметь 0 строк, так как группировочные и итоговые строки мы предварительно отключили.
Существует еще один популярный способ, который часто предлагают на форумах: получить текст запроса из сформированного макета компоновки и выполнить его стандартным объектом Запрос базы данных.
Давайте посмотрим, как выглядит этот программный код:
КомпоновщикМакета = Новый КомпоновщикМакетаКомпоновкиДанных;
МакетКомпоновки = КомпоновщикМакета.Выполнить(СхемаКомпоновкиДанных, КомпоновщикНастроек.Настройки);
// Предположим, что у нас один набор данных с именем НаборДанных1
Если МакетКомпоновки.НаборыДанных.Найти("НаборДанных1") <> Неопределено Тогда
ТекстЗапроса = МакетКомпоновки.НаборыДанных.НаборДанных1.Запрос;
Запрос = Новый Запрос(ТекстЗапроса);
// Переносим параметры из макета в запрос
Для Каждого ПараметрЗапроса Из МакетКомпоновки.ЗначенияПараметров Цикл
Запрос.УстановитьПараметр(ПараметрЗапроса.Имя, ПараметрЗапроса.Значение);
КонецЦикла;
Если Запрос.Выполнить().Пустой() Тогда
Сообщить("В отчете нет данных!");
КонецЕсли;
КонецЕсли;
Проанализируем этот способ подробнее и выделим его критические недостатки:
Запрос. Код аварийно завершится с ошибкой.Наиболее производительным, надежным и универсальным методом является поэлементный вывод результата компоновки данных. Этот же подход используется в стандартных библиотеках фирмы 1С (в частности, в Библиотеке Стандартных Подсистем — БСП, общий модуль ОтчетыСервер, функция ОтчетПустой).
Суть метода заключается в том, что мы не выводим весь отчет целиком в память. Вместо этого мы инициализируем стандартный ПроцессорКомпоновкиДанных и начинаем пошагово считывать элементы результата с помощью метода Следующий(). Как только мы находим первый элемент, содержащий реальное заполненное значение параметра макета, мы немедленно прерываем цикл. Это означает, что в отчете есть как минимум одна строка с данными.
Посмотрим на пример реализации данного алгоритма:
// Инициализируем макет и процессоры как обычно
КомпоновщикМакета = Новый КомпоновщикМакетаКомпоновкиДанных;
МакетКомпоновки = КомпоновщикМакета.Выполнить(СхемаКомпоновкиДанных, КомпоновщикНастроек.ПолучитьНастройки(), ДанныеРасшифровки);
ПроцессорКомпоновкиДанных = Новый ПроцессорКомпоновкиДанных;
ПроцессорКомпоновкиДанных.Инициализировать(МакетКомпоновки, , ДанныеРасшифровки, Истина);
ПустойРезультат = Истина;
// Начинаем поэлементный обход без полного вывода в документ
Пока Истина Цикл
ЭлементРезультата = ПроцессорКомпоновкиДанных.Следующий();
Если ЭлементРезультата = Неопределено Тогда
Прервать; // Достигнут конец отчета
КонецЕсли;
// Проверяем элементы макета на наличие заполненных значений
Для Каждого ПараметрЭлемента Из ЭлементРезультата.ЗначенияПараметров Цикл
// Исключаем системные параметры и проверяем значимые данные
Если ЗначениеЗаполнено(ПараметрЭлемента.Значение) Тогда
ПустойРезультат = Ложь;
Прервать; // Найдена первая строка с данными, прекращаем внутренний цикл
КонецЕсли;
КонецЦикла;
Если Не ПустойРезультат Тогда
Прервать; // Прекращаем внешний обход, отчет точно не пуст!
КонецЕсли;
КонецЦикла;
Если ПустойРезультат Тогда
Сообщить("Отчет пустой! Запись и отправка отменены.");
Иначе
Сообщить("В отчете есть данные, продолжаем формирование...");
// Здесь выполняем полноценный вывод в ТабличныйДокумент
КонецЕсли;
Преимущества поэлементного обхода:
Иногда возникают ситуации, когда отчет уже сформирован в объект ТабличныйДокумент (например, пользователь нажал кнопку «Сформировать» на форме), и нам нужно проверить его состояние перед сохранением на диск. Простая проверка свойства ТабДок.ВысотаТаблицы здесь не поможет, так как шапка отчета и заголовки колонок всегда заполняют строки, делая высоту больше нуля.
В этом случае мы можем применить алгоритм выборочной проверки ячеек. Мы знаем, что шапка отчета обычно занимает фиксированное количество строк сверху (например, первые 5-10 строк). Следовательно, мы можем проигнорировать эту область и проверить только ячейки, идущие ниже.
Разберем пример функции проверки готового табличного документа:
Функция ТабличныйДокументПустой(ТабДок, СтрокаНачалаДанных = 7) Экспорт
// Если высота документа меньше строки начала данных, значит кроме шапки ничего нет
Если ТабДок.ВысотаТаблицы < СтрокаНачалаДанных Тогда
Возврат Истина;
КонецЕсли;
// Обходим ячейки в поисках любого текстового наполнения
Для НомерСтроки = СтрокаНачалаДанных По ТабДок.ВысотаТаблицы Цикл
Для НомерКолонки = 1 По ТабДок.ШиринаТаблицы Цикл
ТекущаяОбласть = ТабДок.Область(НомерСтроки, НомерКолонки);
Если ЗначениеЗаполнено(ТекущаяОбласть.Текст) Тогда
// Нашли заполненную ячейку
Возврат Ложь;
КонецЕсли;
КонецЦикла;
КонецЦикла;
Возврат Истина;
КонецФункции
Этот способ уступает в производительности поэлементному обходу процессора компоновки, но он незаменим, когда мы работаем с уже готовым, сформированным табличным документом и не имеем прямого доступа к процессору вывода СКД.
Подведем итоги нашего анализа:
ТаблицаЗначений, не забудьте программно отключить общие итоги и группировки в настройках перед компоновкой макета, чтобы избежать «ложных» строк.