Как получить значение из сложной функции общего модуля для вычисляемого поля СКД

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

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

Разбор технической проблемы и причины ошибки

Проанализируем ситуацию, описанную автором темы. Имеется функция ОбновитьСрокиПоставки, которая выполняет сложные расчеты внутри цикла по товарам документа, заполняет таблицу мТовары и в конце помещает некое значение во временное хранилище, возвращая адрес. Ошибка «Значение не является значением объектного типа (НайтиСтроки)» возникает в функции-обертке по двум причинам:

  1. Несоответствие типов данных: Функция помещает в хранилище переменную СтрокаТовара.Фирма_Поставка, которая является обычной строкой (например, «Все в наличии»). В функции-обертке программист пытается применить метод НайтиСтроки() к результату, полученному из хранилища. Поскольку у типа Строка нет метода НайтиСтроки (он есть только у ТаблицыЗначений), система выдает ошибку.
  2. Логическая ошибка итератора: В исходном коде возврат значения через ПоместитьВоВременноеХранилище происходит вне цикла, но обращается к переменной СтрокаТовара, которая была итератором цикла. Это означает, что в хранилище попадет результат только для самой последней строки документа, а не для всей таблицы.

Вариант 1: Исправление работы с временным хранилищем

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

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


// В конце функции ОбновитьСрокиПоставки
// Вместо возврата одной строки возвращаем всю таблицу товаров
Возврат ПоместитьВоВременноеХранилище(мТовары);
КонецФункции

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


Функция ОпределитьСрокПоставки(ДокументЗаказ, КодСтроки) Экспорт
    // Получаем адрес хранилища, вызвав основную функцию
    АдресХранилища = ОбновитьСрокиПоставки(ДокументЗаказ);
    
    // Извлекаем таблицу значений
    ТаблицаСрокПоставки = ПолучитьИзВременногоХранилища(АдресХранилища);
    
    Если ТипЗнч(ТаблицаСрокПоставки) = Тип("ТаблицаЗначений") Тогда
        Отбор = Новый Структура("КодСтроки", КодСтроки);
        НайденныеСтроки = ТаблицаСрокПоставки.НайтиСтроки(Отбор);
        
        Если НайденныеСтроки.Количество() > 0 Тогда
            Возврат НайденныеСтроки[0].Фирма_Поставка;
        КонецЕсли;
    КонецЕсли;
    
    Возврат "";
КонецФункции

Вариант 2: Оптимизация вызовов из СКД

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

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

Посмотрим, как реализовать это по шагам:

  1. Сформируйте основной запрос в СКД.
  2. В коде вызова отчета получите все ссылки на документы ЗаказКлиента, которые попали в результат.
  3. Передайте этот массив в функцию, которая одним запросом (а не в цикле) рассчитает данные для всех строк всех заказов.
  4. Заполните ТаблицуЗначений и передайте её в СКД как «Набор данных - объект».

Важные нюансы работы с контекстом (Объект vs Ссылка)

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

Если вы работаете в СКД, лучше передавать ссылку и внутри функции явно получать объект, если это необходимо для модификации, или использовать запросы для получения данных табличной части:


// Пример безопасного получения таблицы товаров из ссылки
Запрос = Новый Запрос;
Запрос.Текст = "ВЫБРАТЬ * ИЗ Документ.ЗаказКлиента.Товары ГДЕ Ссылка = &Ссылка";
Запрос.УстановитьПараметр("Ссылка", ДокументЗаказ);
мТовары = Запрос.Выполнить().Выгрузить();

Итоги решения

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

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

← На главную