Почему возникает синтаксическая ошибка при вызове функции общего модуля в вычисляемом поле СКД при программном формировании отчета?

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

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

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

Анализ проблемы и текста ошибки

Рассмотрим пример: в схеме компоновки данных создано вычисляемое поле, где объединяются две даты, а между ними вставляется перенос строки с помощью функции общего модуля:

Строка(Формат(ГоденОт, "ДФ = дд.ММ.гг")) + КДВНашеВсяко.СимволыПС() + Строка(Формат(ГоденДо, "ДФ = дд.ММ.гг"))

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


Ошибка исполнения отчета
по причине:
Ошибка при вызове метода контекста (Следующий)
по причине:
Ошибка компоновки данных
по причине:
Ошибка получения данных
по причине:
Ошибка в выражении
по причине:
Синтаксическая ошибка "КДВНашеВсяко.СимволыПС"

Выясним причину: почему платформа воспринимает имя общего модуля как синтаксическую ошибку? Дело в том, что в целях безопасности вызов внешних функций встроенного языка из выражений СКД по умолчанию заблокирован. Когда отчет строится платформой автоматически (стандартная обработка не отключена), система компоновки данных неявно инициализирует процессор компоновки с разрешением на вызов внешних функций. Но когда разработчик создает объект ПроцессорКомпоновкиДанных вручную, вступает в силу значение по умолчанию соответствующего параметра инициализации.

Основное решение: явное разрешение использования внешних функций

Разберем синтаксис метода Инициализировать объекта ПроцессорКомпоновкиДанных. Данный метод принимает следующие параметры:

  1. МакетКомпоновки — макет компоновки данных, подготовленный компоновщиком макета.
  2. ВнешниеНаборыДанных — коллекция внешних наборов данных (если используются).
  3. ДанныеРасшифровки — объект данных расшифровки.
  4. ВозможностьИспользованияВнешнихФункций — булево значение. По умолчанию установлено в Ложь.

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

Посмотрим на исправленный фрагмент кода процедуры ПриКомпоновкеРезультата:


Процедура ПриКомпоновкеРезультата(ДокументРезультат, ДанныеРасшифровки, СтандартнаяОбработка)
	
	СтандартнаяОбработка = Ложь;

	Настройки = КомпоновщикНастроек.ПолучитьНастройки();

	КомпоновщикМакета = Новый КомпоновщикМакетаКомпоновкиДанных;
	МакетКомпоновки = КомпоновщикМакета.Выполнить(СхемаКомпоновкиДанных, Настройки, ДанныеРасшифровки);

	ПроцессорКомпоновки = Новый ПроцессорКомпоновкиДанных;
	// Явно передаем четвертый параметр Истина:
	ПроцессорКомпоновки.Инициализировать(МакетКомпоновки, , ДанныеРасшифровки, Истина);

	ПроцессорВывода = Новый ПроцессорВыводаРезультатаКомпоновкиДанныхВТабличныйДокумент;
	ПроцессорВывода.УстановитьДокумент(ДокументРезультат);

	ПроцессорВывода.НачатьВывод();

	ЭлементРезультата = ПроцессорКомпоновки.Следующий();
	Пока ЭлементРезультата <> Неопределено Цикл
		Если ЭлементРезультата.ЗначенияПараметров.Количество() = 1 Тогда
			Если ЭлементРезультата.ЗначенияПараметров[0].Значение = "Разорвать" Тогда
				ДокументРезультат.ВывестиГоризонтальныйРазделительСтраниц();
			КонецЕсли;
		КонецЕсли;
		ПроцессорВывода.ВывестиЭлемент(ЭлементРезультата);
		ЭлементРезультата = ПроцессорКомпоновки.Следующий();
	КонецЦикла;
	ПроцессорВывода.ЗакончитьВывод();
	
	ДокументРезультат.АвтоМасштаб = Истина;
	
КонецПроцедуры

Обратите внимание: даже если в коде используется пакетный вывод через метод ПроцессорВывода.Вывести(ПроцессорКомпоновки), параметр ВозможностьИспользованияВнешнихФункций всё равно необходимо передавать при инициализации процессора компоновки.

Обязательные требования к общим модулям в СКД

Для корректного вызова функции из схемы компоновки данных необходимо соблюдать ряд системных требований к самому общему модулю:

  1. Директива экспорта: функция обязательно должна иметь ключевое слово Экспорт.
  2. Серверное выполнение: компоновка данных и расчет выражений СКД происходят на стороне сервера, поэтому у общего модуля должен быть установлен флаг Сервер. Если отчет может формироваться в фоновом задании, регламентном задании или через COM-соединение, может потребоваться флаг Внешнее соединение.
  3. Неглобальный контекст: общий модуль не должен быть глобальным. Обращение из СКД всегда выполняется строго по шаблону ИмяМодуля.ИмяФункции(Параметры).
  4. Безопасный режим: если отчет выполняется в безопасном режиме (или в расширении при включенной защите от опасных действий), выполнение внешнего серверного кода может блокироваться платформой в целях безопасности.

Альтернативные варианты вставки переноса строки без вызова модулей

Вызов процедур и функций конфигурации из СКД снижает общую производительность отчета, так как требует переключения контекстов и исключает оптимизацию на уровне СУБД. Вставить перевод строки можно стандартными возможностями самой схемы компоновки:

  1. Многострочный литерал в строке: в окне редактирования формулы СКД можно просто нажать клавишу Enter внутри строковых кавычек. Платформа воспринимает перенос строки внутри литерала как полноценный символ перевода каретки:
    
    Строка(Формат(ГоденОт, "ДФ = дд.ММ.гг")) + "
    " + Строка(Формат(ГоденДо, "ДФ = дд.ММ.гг"))
    
    Такой способ отрабатывает максимально быстро и не требует вызова общего модуля или разрешения внешних функций.
  2. Использование предопределенного параметра: можно добавить на вкладке «Параметры» СКД параметр СимволПС, отключить его видимость для пользователя, а в колонке «Выражение» указать Символы.ПС. После этого в выражении вычисляемого поля достаточно написать:

    Строка(Формат(ГоденОт, "ДФ = дд.ММ.гг")) + &СимволПС + Строка(Формат(ГоденДо, "ДФ = дд.ММ.гг"))

  3. Пользовательский макет поля: на вкладке «Макеты» схемы компоновки можно назначить индивидуальный макет оформления для выводимого поля и разместить значения двух полей в ячейках друг под другом, что избавит от необходимости конкатенации строк.

Альтернатива программному разрыву страниц

В исходном примере программный вывод использовался ради вставки горизонтального разделителя страниц. Однако во многих случаях в СКД это можно настроить декларативно без написания кода в ПриКомпоновкеРезультата:

  1. Перейдем в схеме на вкладку Настройки.
  2. Выберем группировку, перед которой или после которой требуется начинать новую страницу.
  3. В окне Другие настройки для выбранной группировки найдем свойство Новая страница (или Разрыв страниц) и выберем значение Перед группировкой или После группировки.

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

← На главную