При разработке отчетов на базе системы компоновки данных (СКД) разработчики часто выносят сложные расчеты сроков задолженности и календарных дней в отдельные функции общих модулей. Однако при формировании отчета может возникнуть критическая ошибка выполнения:
{ОбщийМодуль.ФункцииСКД.Модуль(59)}: Значение не является значением объектного типа (ДопустимоеЧислоДнейЗадолженности)
Разберем детально, почему возникает данная ситуация, как быстро локализовать проблему при отладке и какие архитектурные решения помогут избежать падения отчета.
Проанализируем строку кода, на которой происходит сбой:
Если ДоговорКонтрагента.ДопустимоеЧислоДнейЗадолженности = 0 тогда
Возврат Дата;
КонецЕсли;
Ошибка означает, что переменная ДоговорКонтрагента не содержит ссылку на объект справочника ДоговорыКонтрагентов. В момент обращения через точку переменная имеет тип Неопределено или NULL. В контексте работы отчета СКД это происходит по следующим причинам:
ДоговорКонтрагента не определено и принимает значение Неопределено.ПОЛНОЕ СОЕДИНЕНИЕ таблиц остатков и оборотов, для строк без соответствия поле договора возвращает значение NULL.ДоговорКонтрагента (например, после ручных корректировок записей регистров).Выясним, как найти строку или группировку, на которой падает отчет, если стандартная пошаговая отладка сразу останавливается на строке с ошибкой:
Самое быстрое и надежное исправление на уровне программного кода — защитить функцию от передачи некорректных типов данных. Добавим проверку перед обращением к реквизитам через точку:
Если Не ЗначениеЗаполнено(ДоговорКонтрагента)
Или ТипЗнч(ДоговорКонтрагента) <> Тип("СправочникСсылка.ДоговорыКонтрагентов") Тогда
Возврат Дата;
КонецЕсли;
Если ДоговорКонтрагента.ДопустимоеЧислоДнейЗадолженности = 0 Тогда
Возврат Дата;
КонецЕсли;
Такая проверка гарантирует, что при обработке итоговых строк группировок или пустых записей функция вернет значение по умолчанию (исходную дату) и не прервет формирование отчета.
Если функция должна рассчитываться строго для детальных записей или определенной группировки, настроим правила расчета на уровне СКД:
ДоговорКонтрагента и детальные записи.Сумма(...) или Максимум(...)), исключив прямой вызов экспортной функции без переданного договора.Посмотрим на исходный код функции, выполняющей запрос к производственному календарю:
Запрос = Новый Запрос();
Запрос.Текст = "ВЫБРАТЬ ПЕРВЫЕ " + ДоговорКонтрагента.ДопустимоеЧислоДнейЗадолженности + "
| РегламентированныйПроизводственныйКалендарь.ДатаКалендаря
|ПОМЕСТИТЬ Дни
|ИЗ
| РегистрСведений.РегламентированныйПроизводственныйКалендарь КАК РегламентированныйПроизводственныйКалендарь
|ГДЕ
| РегламентированныйПроизводственныйКалендарь.ВидДня В(&ВидДня)
| И РегламентированныйПроизводственныйКалендарь.ДатаКалендаря < НАЧАЛОПЕРИОДА(&Дата, ДЕНЬ)
|УПОРЯДОЧИТЬ ПО
| РегламентированныйПроизводственныйКалендарь.ДатаКалендаря УБЫВ
|;
|ВЫБРАТЬ
| МИНИМУМ(Дни.ДатаКалендаря) КАК ДатаКалендаря
|ИЗ
| Дни КАК Дни";
Выполнение отдельного запроса в цикле компоновщика для каждой строки отчета приводит к проблеме N+1 и существенно замедляет формирование отчета на больших базах. Рекомендуется перенести получение реквизита ДопустимоеЧислоДнейЗадолженности непосредственно в основной набор данных СКД:
ВЫБРАТЬ
ЕСТЬNULL(ВзаиморасчетыОстатки.ДоговорКонтрагента, ВзаиморасчетыОбороты.ДоговорКонтрагента) КАК ДоговорКонтрагента,
ЕСТЬNULL(ВзаиморасчетыОстатки.ДоговорКонтрагента.ДопустимоеЧислоДнейЗадолженности, 0) КАК ДопустимоеЧислоДней
ИЗ
РегистрНакопления.ВзаиморасчетыСКонтрагентами.Остатки(&Дата, ) КАК ВзаиморасчетыОстатки
ПОЛНОЕ СОЕДИНЕНИЕ РегистрНакопления.ВзаиморасчетыСКонтрагентами.Обороты(&Дата, , , ) КАК ВзаиморасчетыОбороты
ПО ВзаиморасчетыОстатки.ДоговорКонтрагента = ВзаиморасчетыОбороты.ДоговорКонтрагента
Использование конструкции ЕСТЬNULL для поля соединения гарантирует корректное получение ссылки на договор независимо от того, в какой из соединяемых таблиц присутствуют данные.