При разработке внешних печатных форм часто возникает классическая задача: необходимо получить дополнительные данные (например, цены, штрихкоды или остатки) из регистров для всех товаров, содержащихся в документе-основании. Ситуация осложняется, когда печатная форма должна быть универсальной и подходить сразу к десятку разных документов (например, «Реализация товаров и услуг», «Внутреннее потребление», «Перемещение товаров» и т.д.).
В этой статье мы подробно разберем ошибки, с которыми сталкиваются разработчики при передаче параметров в запрос из текущего выбранного документа, рассмотрим рабочие решения и изучим лучшие практики оптимизации этого процесса.
Начинающие разработчики часто пытаются передать реквизиты табличной части документа напрямую в параметры запроса. Рассмотрим пример типичного ошибочного кода:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ЦеныНоменклатурыСрезПоследних.Цена КАК Цена
|ИЗ
| РегистрСведений.ЦеныНоменклатуры.СрезПоследних(, ВидЦены = &ВидЦены И Номенклатура = &Номенклатура) КАК ЦеныНоменклатурыСрезПоследних";
Запрос.УстановитьПараметр("ВидЦены", СсылкаНаОбъект.ВидЦены);
// ОШИБКА: Попытка передать коллекцию вместо конкретной ссылки
Запрос.УстановитьПараметр("Номенклатура", СсылкаНаОбъект.Товары.Номенклатура);
Данный подход не работает по двум причинам:
СсылкаНаОбъект.Товары.Номенклатура не возвращает конкретную ссылку. По сути, это обращение к колонке табличной части, которая на платформе 1С представляет собой коллекцию значений, а не одиночный элемент.= ожидает получить одиночную ссылку (тип СправочникСсылка.Номенклатура), а не массив или таблицу.Давайте разберем по шагам, как правильно решить эту задачу без выполнения запросов в цикле (что категорически запрещено стандартами разработки 1С).
Чтобы запросить цены сразу для всего списка товаров из документа, нам необходимо использовать оператор В вместо = в условиях запроса. А сам список номенклатуры нужно предварительно выгрузить в массив.
Для этого у табличных частей документов (и у таблиц значений в целом) существует штатный и очень быстрый метод ВыгрузитьКолонку(). Рассмотрим исправленный вариант запроса:
// Выгружаем все уникальные ссылки на номенклатуру из документа в массив
МассивНоменклатуры = СсылкаНаОбъект.Товары.ВыгрузитьКолонку("Номенклатура");
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Цены.Номенклатура КАК Номенклатура,
| Цены.Цена КАК Цена
|ИЗ
| РегистрСведений.ЦеныНоменклатуры.СрезПоследних(
| &Период,
| Номенклатура В (&МассивНоменклатуры) И ВидЦены = &ВидЦены
| ) КАК Цены";
Запрос.УстановитьПараметр("ВидЦены", СсылкаНаОбъект.ВидЦены);
Запрос.УстановитьПараметр("МассивНоменклатуры", МассивНоменклатуры);
// Важно: всегда передавайте дату документа в СрезПоследних!
Запрос.УстановитьПараметр("Период", СсылкаНаОбъект.Дата);
РезультатЗапроса = Запрос.Выполнить();
Важное архитектурное замечание: Обратите внимание, что в первом параметре виртуальной таблицы СрезПоследних мы передали &Период. Если оставить этот параметр пустым, система вернет цены на текущую дату компьютера. Если вы будете печатать документ месячной давности, цены в печатной форме окажутся неактуальными на момент совершения сделки. Передача даты документа (или объекта МоментВремени) решает эту проблему.
После выполнения запроса мы получаем плоскую таблицу с ценами только для тех позиций, по которым эти цены были установлены. Теперь нам нужно вывести строки документа в макет печатной формы, подставив цены из результата запроса.
Если использовать последовательный обход выборки методом НайтиСледующий(), можно столкнуться с логической ошибкой: если для какого-то товара в регистре нет цены, то в печатной форме для него может продублироваться цена предыдущего успешно обработанного товара. Это происходит из-за того, что параметры области макета не очищаются принудительно перед выводом новой строки.
Чтобы избежать подобных багов и сделать алгоритм максимально надежным, мы применим выгрузку результатов запроса в индексированную Таблицу Значений.
Проанализируем оптимальный код заполнения макета:
// Выгружаем результат запроса в Таблицу Значений
ТаблицаЦен = РезультатЗапроса.Выгрузить();
// ОПТИМИЗАЦИЯ: строим индекс по ключевому полю поиска.
// Это увеличивает скорость работы метода Найти() в сотни раз на больших документах!
ТаблицаЦен.Индексы.Добавить("Номенклатура");
ТабличныйДокумент = Новый ТабличныйДокумент;
МакетПечФормы = ПолучитьМакет("ПФ_MXL_МБ8");
ОбластьСтроки = МакетПечФормы.ПолучитьОбласть("Строка");
СуммаИтог = 0;
Для Каждого ТекущаяСтрока Из СсылкаНаОбъект.Товары Цикл
// Заполняем базовые параметры строки из документа
ОбластьСтроки.Параметры.МатериалНаименование = ТекущаяСтрока.Номенклатура;
ОбластьСтроки.Параметры.НоменклатурныйНомер = ТекущаяСтрока.Номенклатура.Код;
ОбластьСтроки.Параметры.Количество = ТекущаяСтрока.Количество;
КоличествоСтроки = Число(ТекущаяСтрока.Количество);
// Ищем цену в нашей индексированной таблице цен
СтрокаЦены = ТаблицаЦен.Найти(ТекущаяСтрока.Номенклатура, "Номенклатура");
Если СтрокаЦены <> Неопределено Тогда
// Если цена найдена, выводим её и считаем сумму
ОбластьСтроки.Параметры.Цена = СтрокаЦены.Цена;
ТекущаяСумма = КоличествоСтроки * СтрокаЦены.Price;
ОбластьСтроки.Параметры.Сумма = ТекущаяСумма;
СуммаИтог = СуммаИтог + ТекущаяСумма;
Иначе
// КРИТИЧЕСКИ ВАЖНО: Явно очищаем параметры для позиций без цен!
ОбластьСтроки.Параметры.Price = 0;
ОбластьСтроки.Параметры.Сумма = 0;
КонецЕсли;
// Выводим подготовленную область в итоговый документ
ТабличныйДокумент.Вывести(ОбластьСтроки);
КонецЦикла;
Если ваша печатная форма регистрируется во многих документах системы, вы неизбежно столкнетесь с тем, что в некоторых документах табличная часть может называться не Товары, а, например, Материалы или Услуги. Обращение напрямую к свойству Товары вызовет ошибку работы программы.
Рассмотрим, как решить эту проблему с помощью анализа метаданных объекта. Мы напишем универсальную функцию получения таблицы товаров из любого переданного документа:
Функция ПолучитьТаблицуТоваровДокумента(ДокументСсылка) Экспорт
МетаданныеДокумента = ДокументСсылка.Метаданные();
ИмяНайденнойТЧ = "";
// Ищем среди табличных частей ту, которая содержит реквизит с именем "Номенклатура"
Для Каждого ТЧ Из МетаданныеДокумента.ТабличныеЧасти Цикл
Если ТЧ.Реквизиты.Найти("Номенклатура") <> Неопределено Тогда
ИмяНайденнойТЧ = ТЧ.Имя;
Прервать;
КонецЕсли;
КонецЦикла;
Если ЗначениеЗаполнено(ИмяНайденнойТЧ) Тогда
Возврат ДокументСсылка[ИмяНайденнойТЧ].Выгрузить();
Иначе
ВызватьИсключение "В документе " + МетаданныеДокумента.Имя + " не найдена подходящая табличная часть с номенклатурой!";
КонецЕсли;
КонецФункции
Используя этот универсальный подход, мы гарантируем стабильную работу внешней печатной формы в любой типовой или кастомной конфигурации 1С, независимо от структуры метаданных конкретного документа-основания.