При разработке внешних отчетов на Системе Компоновки Данных (СКД) мы часто сталкиваемся с задачей программного изменения параметров перед формированием. Типичный сценарий: отчет должен запускаться по расписанию (через подсистему «Рассылка отчетов» из БСП) и автоматически сдвигать период формирования, если, например, рассылка выпадает на понедельник, а данные нужны за пятницу.
В этой статье мы разберем неочевидную ошибку, из-за которой отчет прекрасно работает при ручном запуске пользователем, но в журнале регистрации при рассылке появляется запись: «Рассылка отчетов не выполнена, так как отчеты пустые или не сформированы из-за ошибок».
Рассмотрим ситуацию, с которой столкнулся разработчик. Имеется внешний отчет, в модуле объекта которого переопределена процедура ПриКомпоновкеРезультата. Цель кода — проверить день недели и, если условия совпадают, изменить параметры НачалоПериода и КонецПериода.
Код выглядит логичным, но скрывает в себе ошибку типизации, которая может вести себя по-разному в тонком клиенте и в фоновом задании на сервере.
Вот исходный (проблемный) фрагмент кода:
Процедура ПриКомпоновкеРезультата(ДокументРезультат, ДанныеРасшифровки, СтандартнаяОбработка) Экспорт
СтандартнаяОбработка = Ложь;
НастройкиКД = КомпоновщикНастроек.ПолучитьНастройки();
// Получаем значения параметров
НачалоПериода = НастройкиКД.ПараметрыДанных.Элементы.Найти("НачалоПериода").Значение;
КонецПериода = НастройкиКД.ПараметрыДанных.Элементы.Найти("КонецПериода").Значение;
Если НачалоПериода <> Неопределено И КонецПериода <> Неопределено Тогда
// Проверка дней недели (например, сдвиг дат на выходных)
Если ДеньНедели(НачалоПериода) = 7 И ДеньНедели(КонецПериода) = 1 Тогда
// ОШИБКА ЗДЕСЬ: Обращение к свойству .Дата у переменной типа Дата
НастройкиКД.ПараметрыДанных.УстановитьЗначениеПараметра("НачалоПериода", НачалоПериода.Дата - (2*60*60*24));
НастройкиКД.ПараметрыДанных.УстановитьЗначениеПараметра("КонецПериода", КонецПериода.Дата - (2*60*60*24));
КонецЕсли;
КонецЕсли;
// Стандартный блок программного вывода СКД
КомпоновщикМакета = Новый КомпоновщикМакетаКомпоновкиДанных;
МакетКомпоновки = КомпоновщикМакета.Выполнить(СхемаКомпоновкиДанных, НастройкиКД, ДанныеРасшифровки);
// ... далее инициализация процессора и вывод в ДокументРезультат
КонецПроцедуры
При интерактивном запуске отчет формируется корректно. Однако при запуске через регламентное задание рассылки возникает ошибка, и отчет приходит пустым или не приходит вовсе. В Журнале регистрации можно увидеть сообщение об ошибке выполнения, но часто оно малоинформативно.
Давайте разберем, что именно происходит в строке изменения параметров. Мы пытаемся выполнить математическую операцию вычитания секунд из даты: НачалоПериода.Дата - (2*60*60*24).
Ключевой вопрос: какой тип данных хранится в переменной НачалоПериода?
.ДатаНачала и .ДатаОкончания.НачалоПериода уже содержит само значение даты.У типа Дата в платформе 1С:Предприятие нет свойства .Дата. Написание МояПеременнаяДата.Дата является синтаксической ошибкой.
Почему это работало интерактивно?
В некоторых случаях (в зависимости от настроек платформы и режима совместимости) или если переменная получалась неявно типизированной, ошибка могла маскироваться или не приводить к падению формирования формы. Однако в строгом контексте фонового задания сервера (где выполняется рассылка) любое необработанное исключение приводит к прерыванию формирования отчета.
Чтобы исправить ошибку, необходимо убрать лишнее обращение к несуществующему свойству. Давайте скорректируем код, предполагая, что параметры в СКД имеют тип Дата.
Процедура ПриКомпоновкеРезультата(ДокументРезультат, ДанныеРасшифровки, СтандартнаяОбработка) Экспорт
СтандартнаяОбработка = Ложь;
// 1. Получаем настройки
НастройкиКД = КомпоновщикНастроек.ПолучитьНастройки();
// 2. Находим значения параметров.
// Важно: Предполагаем, что тип значения параметров в СКД - "Дата"
ЗначениеНачала = НастройкиКД.ПараметрыДанных.Элементы.Найти("НачалоПериода").Значение;
ЗначениеКонца = НастройкиКД.ПараметрыДанных.Элементы.Найти("КонецПериода").Значение;
// 3. Проверяем на Неопределено перед использованием
Если ЗначениеНачала <> Неопределено И ЗначениеКонца <> Неопределено Тогда
// Пример логики: если воскресенье и понедельник
Если ДеньНедели(ЗначениеНачала) = 7 И ДеньНедели(ЗначениеКонца) = 1 Тогда
// Вычисляем смещение.
// Используем саму переменную ЗначениеНачала, так как это уже Дата.
// Используем НачалоДня и КонецДня для надежности.
НовоеНачало = НачалоДня(ЗначениеНачала) - (2 * 86400); // Сдвиг на 2 дня назад
НовыйКонец = КонецДня(ЗначениеКонца) - (2 * 86400);
// Устанавливаем новые значения
НастройкиКД.ПараметрыДанных.УстановитьЗначениеПараметра("НачалоПериода", НовоеНачало);
НастройкиКД.ПараметрыДанных.УстановитьЗначениеПараметра("КонецПериода", НовыйКонец);
КонецЕсли;
КонецЕсли;
// 4. Программная компоновка
КомпоновщикМакета = Новый КомпоновщикМакетаКомпоновкиДанных;
МакетКомпоновки = КомпоновщикМакета.Выполнить(СхемаКомпоновкиДанных, НастройкиКД, ДанныеРасшифровки);
ПроцессорКомпоновки = Новый ПроцессорКомпоновкиДанных;
ПроцессорКомпоновки.Инициализировать(МакетКомпоновки, , ДанныеРасшифровки, Ложь);
ПроцессорВывода = Новый ПроцессорВыводаРезультатаКомпоновкиДанныхВТабличныйДокумент;
ПроцессорВывода.УстановитьДокумент(ДокументРезультат);
ПроцессорВывода.Вывести(ПроцессорКомпоновки);
КонецПроцедуры
Если вы сталкиваетесь с ситуацией, когда отчет не формируется в фоне, используйте следующие приемы:
ЗаписьЖурналаРегистрации("ОтладкаОтчета", УровеньЖурналаРегистрации.Информация, , ,
"Тип НачалоПериода: " + ТипЗнч(ЗначениеНачала));
Попытка ... Исключение с обязательной записью ошибки в Журнал регистрации. Это покажет, на какой строке падает код.ДокументРезультат действительно содержит данные (например, проверьте ДокументРезультат.ВысотаТаблицы). Рассылка отчетов БСП имеет защиту: если отчет пустой (или содержит только шапку), письмо может не отправиться.Если бы в вашей СКД использовался один параметр Период с типом СтандартныйПериод, код выглядел бы иначе. В этом случае свойство .Значение вернуло бы объект типа СтандартныйПериод, и тогда обращение к датам выглядело бы так:
ПараметрПериод = НастройкиКД.ПараметрыДанных.Элементы.Найти("Период").Значение;
// ПараметрПериод.ДатаНачала
// ПараметрПериод.ДатаОкончания
Всегда проверяйте типы данных параметров с помощью отладчика или функции ТипЗнч(), чтобы избежать подобных ошибок при переходе от интерактивной работы к фоновым заданиям.