Как передать номенклатуру из документа в запрос получения цен для внешней печатной формы в 1С

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

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

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

Выясняем причину: почему простая передача табличной части не работает?

Начинающие разработчики часто пытаются передать реквизиты табличной части документа напрямую в параметры запроса. Рассмотрим пример типичного ошибочного кода:


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |	ЦеныНоменклатурыСрезПоследних.Цена КАК Цена
    |ИЗ
    |	РегистрСведений.ЦеныНоменклатуры.СрезПоследних(, ВидЦены = &ВидЦены И Номенклатура = &Номенклатура) КАК ЦеныНоменклатурыСрезПоследних";

Запрос.УстановитьПараметр("ВидЦены", СсылкаНаОбъект.ВидЦены);
// ОШИБКА: Попытка передать коллекцию вместо конкретной ссылки
Запрос.УстановитьПараметр("Номенклатура", СсылкаНаОбъект.Товары.Номенклатура); 

Данный подход не работает по двум причинам:

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

Давайте разберем по шагам, как правильно решить эту задачу без выполнения запросов в цикле (что категорически запрещено стандартами разработки 1С).

Шаг 1. Передача списка значений через оператор «В»

Чтобы запросить цены сразу для всего списка товаров из документа, нам необходимо использовать оператор В вместо = в условиях запроса. А сам список номенклатуры нужно предварительно выгрузить в массив.

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


// Выгружаем все уникальные ссылки на номенклатуру из документа в массив
МассивНоменклатуры = СсылкаНаОбъект.Товары.ВыгрузитьКолонку("Номенклатура");

Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |	Цены.Номенклатура КАК Номенклатура,
    |	Цены.Цена КАК Цена
    |ИЗ
    |	РегистрСведений.ЦеныНоменклатуры.СрезПоследних(
    |		&Период,
    |		Номенклатура В (&МассивНоменклатуры) И ВидЦены = &ВидЦены
    |	) КАК Цены";

Запрос.УстановитьПараметр("ВидЦены", СсылкаНаОбъект.ВидЦены);
Запрос.УстановитьПараметр("МассивНоменклатуры", МассивНоменклатуры);
// Важно: всегда передавайте дату документа в СрезПоследних!
Запрос.УстановитьПараметр("Период", СсылкаНаОбъект.Дата); 

РезультатЗапроса = Запрос.Выполнить();

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

Шаг 2. Сопоставление полученных цен со строками документа

После выполнения запроса мы получаем плоскую таблицу с ценами только для тех позиций, по которым эти цены были установлены. Теперь нам нужно вывести строки документа в макет печатной формы, подставив цены из результата запроса.

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

Чтобы избежать подобных багов и сделать алгоритм максимально надежным, мы применим выгрузку результатов запроса в индексированную Таблицу Значений.

Проанализируем оптимальный код заполнения макета:


// Выгружаем результат запроса в Таблицу Значений
ТаблицаЦен = РезультатЗапроса.Выгрузить();

// ОПТИМИЗАЦИЯ: строим индекс по ключевому полю поиска.
// Это увеличивает скорость работы метода Найти() в сотни раз на больших документах!
ТаблицаЦен.Индексы.Добавить("Номенклатура");

ТабличныйДокумент = Новый ТабличныйДокумент;
МакетПечФормы = ПолучитьМакет("ПФ_MXL_МБ8");
ОбластьСтроки = МакетПечФормы.ПолучитьОбласть("Строка");

СуммаИтог = 0;

Для Каждого ТекущаяСтрока Из СсылкаНаОбъект.Товары Цикл
    
    // Заполняем базовые параметры строки из документа
    ОбластьСтроки.Параметры.МатериалНаименование = ТекущаяСтрока.Номенклатура;
    ОбластьСтроки.Параметры.НоменклатурныйНомер  = ТекущаяСтрока.Номенклатура.Код;
    ОбластьСтроки.Параметры.Количество           = ТекущаяСтрока.Количество;
    
    КоличествоСтроки = Число(ТекущаяСтрока.Количество);
    
    // Ищем цену в нашей индексированной таблице цен
    СтрокаЦены = ТаблицаЦен.Найти(ТекущаяСтрока.Номенклатура, "Номенклатура");
    
    Если СтрокаЦены <> Неопределено Тогда
        // Если цена найдена, выводим её и считаем сумму
        ОбластьСтроки.Параметры.Цена = СтрокаЦены.Цена;
        ТекущаяСумма = КоличествоСтроки * СтрокаЦены.Price;
        ОбластьСтроки.Параметры.Сумма = ТекущаяСумма;
        СуммаИтог = СуммаИтог + ТекущаяСумма;
    Иначе
        // КРИТИЧЕСКИ ВАЖНО: Явно очищаем параметры для позиций без цен!
        ОбластьСтроки.Параметры.Price = 0;
        ОбластьСтроки.Параметры.Сумма = 0;
    КонецЕсли;
    
    // Выводим подготовленную область в итоговый документ
    ТабличныйДокумент.Вывести(ОбластьСтроки);
    
КонецЦикла;

Продвинутый уровень: Полная универсальность для любых документов

Если ваша печатная форма регистрируется во многих документах системы, вы неизбежно столкнетесь с тем, что в некоторых документах табличная часть может называться не Товары, а, например, Материалы или Услуги. Обращение напрямую к свойству Товары вызовет ошибку работы программы.

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


Функция ПолучитьТаблицуТоваровДокумента(ДокументСсылка) Экспорт
    
    МетаданныеДокумента = ДокументСсылка.Метаданные();
    ИмяНайденнойТЧ = "";
    
    // Ищем среди табличных частей ту, которая содержит реквизит с именем "Номенклатура"
    Для Каждого ТЧ Из МетаданныеДокумента.ТабличныеЧасти Цикл
        Если ТЧ.Реквизиты.Найти("Номенклатура") <> Неопределено Тогда
            ИмяНайденнойТЧ = ТЧ.Имя;
            Прервать;
        КонецЕсли;
    КонецЦикла;
    
    Если ЗначениеЗаполнено(ИмяНайденнойТЧ) Тогда
        Возврат ДокументСсылка[ИмяНайденнойТЧ].Выгрузить();
    Иначе
        ВызватьИсключение "В документе " + МетаданныеДокумента.Имя + " не найдена подходящая табличная часть с номенклатурой!";
    КонецЕсли;
    
КонецФункции

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

← На главную