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

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

При разработке внешних печатных форм (ВПФ) в конфигурациях «1С:Предприятие 8.3», использующих Библиотеку стандартных подсистем (например, 1С:Управление торговлей 11, 1С:ERP или 1С:Комплексная автоматизация 2), разработчики часто сталкиваются с задачей красивого вывода номенклатуры и её характеристик. По умолчанию система может выводить характеристики отдельным столбцом или не выводить детальные свойства вовсе. Часто бизнес-заказчики требуют вывести номенклатуру, а рядом (или в этой же ячейке) отобразить все её индивидуальные свойства (размер, цвет, материал и т. д.) списком через запятую или с новой строки.

В этой статье мы подробно разберем, почему стандартное левое соединение в запросе приводит к ошибкам, как правильно спроектировать пакетный запрос для получения дополнительных реквизитов и как красиво объединить свойства в одну строку на уровне кода печатной формы без потери производительности системы.

В чем сложность: почему «наивное» левое соединение не работает?

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


// ВНИМАНИЕ: Этот код содержит архитектурную ошибку задвоения строк!
ВЫБРАТЬ
    Товары.Номенклатура КАК Номенклатура,
    Товары.Характеристика КАК Характеристика,
    ДопРеквизиты.Свойство.Заголовок КАК Свойство,
    ДопРеквизиты.Значение КАК Значение
ИЗ
    Документ.ЗаказПоставщику.Товары КАК Товары
    ЛЕВОЕ СОЕДИНЕНИЕ Справочник.ХарактеристикиНоменклатуры.ДополнительныеРеквизиты КАК ДопРеквизиты
        ПО Товары.Характеристика = ДопРеквизиты.Ссылка

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

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

---

Решение 1: Пакетный запрос и программная склейка (Оптимальный способ)

Этот способ является наиболее гибким, производительным и безопасным. Мы разделим получение данных на два независимых шага в рамках одного пакетного запроса:

  1. Получим основные данные табличной части товаров (без соединения с доп. реквизитами характеристик).
  2. Получим плоскую таблицу всех свойств и значений характеристик, которые фигурируют в этом документе, установив жесткий отбор по массиву печатаемых объектов.

Рассмотрим подробнее реализацию этого алгоритма в кодовой базе.

Шаг 1. Модернизация запроса

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


// Текст запроса для получения свойств характеристик в пакете
ТекстЗапроса = "ВЫБРАТЬ
               |    Товары.Ссылка КАК Ссылка,
               |    Товары.НомерСтроки КАК НомерСтроки,
               |    Товары.Номенклатура КАК Номенклатура,
               |    Товары.Характеристика КАК Характеристика,
               |    Товары.КоличествоУпаковок КАК Количество,
               |    Товары.Сумма КАК Сумма
               |ИЗ
               |    Документ.ЗаказПоставщику.Товары КАК Товары
               |ГДЕ
               |    Товары.Ссылка В(&МассивОбъектов) И Товары.Отменено = ЛОЖЬ
               |;
               |
               |////////////////////////////////////////////////////////////////////////////////
               |// ВЫБИРАЕМ СВОЙСТВА ХАРАКТЕРИСТИК ДЛЯ НАШИХ ТОВАРОВ
               |ВЫБРАТЬ РАЗЛИЧНЫЕ
               |    ХарДопРеквизиты.Ссылка КАК Характеристика,
               |    ХарДопРеквизиты.Свойство.Заголовок КАК Свойство,
               |    ХарДопРеквизиты.Значение КАК Значение
               |ИЗ
               |    Справочник.ХарактеристикиНоменклатуры.ДополнительныеРеквизиты КАК ХарДопРеквизиты
               |ГДЕ
               |    ХарДопРеквизиты.Ссылка В (
               |        ВЫБРАТЬ РАЗЛИЧНЫЕ
               |            Т.Характеристика
               |        ИЗ
               |            Документ.ЗаказПоставщику.Товары КАК Т
               |        ГДЕ
               |            Т.Ссылка В(&МассивОбъектов)
               |    )";

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

Шаг 2. Передача таблиц в структуру данных

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


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

СтруктураДанныхДляПечати = Новый Структура;
СтруктураДанныхДляПечати.Вставить("РезультатПоШапке", ПакетРезультатовЗапроса[0]);
// Допустим, под индексом 2 у нас табличная часть товаров
СтруктураДанныхДляПечати.Вставить("РезультатПоТабличнойЧасти", ПакетРезультатовЗапроса[2]);
// Добавляем таблицу доп. характеристик под индексом 3
СтруктураДанныхДляПечати.Вставить("РезультатПоДопХарактеристикам", ПакетРезультатовЗапроса[3]);

Возврат СтруктураДанныхДляПечати;

Шаг 3. Быстрый поиск свойств в цикле вывода строк

Теперь перейдем в процедуру заполнения табличного документа (обычно это ЗаполнитьТабличныйДокумент...). Чтобы избежать медленного обращения к базе данных для каждой строки товара, мы один раз выгрузим свойства в Таблицу Значений и проиндексируем её. Это обеспечит мгновенный поиск в памяти сервера.

Посмотрим на пример кода обработки данных:


// Выгружаем товары и дополнительные реквизиты характеристик в таблицы значений
Товары = ДанныеДляПечати.РезультатПоТабличнойЧасти.Выгрузить();
ТаблицаДопХарактеристик = ДанныеДляПечати.РезультатПоДопХарактеристикам.Выгрузить();

// ИНДЕКСИРУЕМ таблицу характеристик для мгновенного поиска по ссылке характеристики
ТаблицаДопХарактеристик.Индексы.Добавить("Характеристика");

Для Каждого СтрокаТовары Из ТаблицаТовары Цикл
    
    // Получаем область строки макета
    ОбластьТоварСтроки = Макет.ПолучитьОбласть("СтрокаТаблицы|Товар");
    
    // Сборка дополнительных реквизитов характеристики для текущей строки
    ТекстХарактеристики = "";
    Если ЗначениеЗаполнено(СтрокаТовары.Характеристика) Тогда
        
        // Быстрый отбор строк свойств по текущей характеристике
        ПараметрыОтбора = Новый Структура("Характеристика", СтрокаТовары.Характеристика);
        НайденныеСвойства = ТаблицаДопХарактеристик.НайтиСтроки(ПараметрыОтбора);
        
        МассивСвойств = Новый Массив;
        Для Каждого СтрокаСвойства Из НайденныеСвойства Цикл
            СтрокаСвойстваЗначение = Строка(СтрокаСвойства.Значение);
            Если ЗначениеЗаполнено(СтрокаСвойстваЗначение) Тогда
                МассивСвойств.Добавить(СтрокаСвойства.Свойство + ": " + СтрокаСвойстваЗначение);
            КонецЕсли;
        КонецЦикла;
        
        // Объединяем свойства через перенос строки (Символы.ПС) или через запятую
        ТекстХарактеристики = СтрСоединить(МассивСвойств, Символы.ПС);
        
    КонецЕсли;
    
    // Используем типовую функцию БСП для получения наименования номенклатуры с характеристикой
    ДополнительныеПараметрыПолученияНаименованияДляПечати = НоменклатураКлиентСервер.ДополнительныеПараметрыПредставлениеНоменклатурыДляПечати();
    ДополнительныеПараметрыПолученияНаименованияДляПечати.ВозвратнаяТара = СтрокаТовары.ЭтоВозвратнаяТара;
    ДополнительныеПараметрыПолученияНаименованияДляПечати.Содержание = СтрокаТовары.Содержание;
    
    // Передаем нашу собранную строку ТекстХарактеристики вместо ссылки характеристики!
    ПредставлениеНоменклатурыДляПечати = НоменклатураКлиентСервер.ПредставлениеНоменклатурыДляПечати(
        СтрокаТовары.НаименованиеПолное,
        ТекстХарактеристики,
        ,
        ,
        ДополнительныеПараметрыПолученияНаименованияДляПечати
    );
    
    // Выводим итоговую строку в параметр макета
    УстановитьПараметр(ОбластьТоварСтроки, "Товар", ПредставлениеНоменклатурыДляПечати);
    ТабличныйДокумент.Присоединить(ОбластьТоварСтроки);
    
КонецЦикла;

Использование метода СтрСоединить(МассивСвойств, Символы.ПС) склеит все свойства в одну многострочную строку. В результате в одной ячейке «Товар» под названием номенклатуры красиво отобразится список характеристик:

Ботинки мужские демисезонные
Размер: 42
Цвет: Черный
Материал: Натуральная кожа

---

Решение 2: Использование шаблонов наименований в конфигурации (Без кодинга)

Если вы работаете в конфигурации 1С:ERP, КА 2 или УТ 11, то можете решить задачу еще проще — с помощью стандартного функционала системы, не меняя код внешней печатной формы.

Разберем этот вариант по шагам:

  1. Перейдем в раздел НСИ и администрирование -> Классификаторы номенклатуры -> Виды номенклатуры.
  2. Откроем нужный вид номенклатуры и перейдем на вкладку Шаблоны наименований.
  3. Найдем пункт Шаблон наименования характеристики для печати (или рабочее наименование).
  4. Нажмем Изменить и в конструкторе формул укажем вывод нужных доп. реквизитов. Пример формулы:
    [Размер] + ", " + [Цвет] + ", " + [Материал]
  5. Сохраним настройки. При необходимости выполним групповое переформирование наименований существующих характеристик.

После этого платформа 1С будет автоматически генерировать поле НаименованиеПолное (или просто Наименование) у характеристик в готовом строковом виде. В тексте запроса вашей печатной формы вам достаточно будет написать:

Товары.Характеристика.НаименованиеПолное КАК ХарактеристикаПредставление

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

---

Критический антипаттерн: Получение объектов в цикле

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


// ВНИМАНИЕ: Крайне медленный код! Никогда так не пишите!
Пока ВыбДок.Следующий() Цикл
    Для Каждого Стр Из ВыбДок.Товары Цикл
        Хар = Стр.Характеристика;
        Для Каждого ДопРекв Из Хар.ДополнительныеРеквизиты Цикл
            // обращение к Свойству и Значению через точку вызывает неявные SQL-запросы в цикле
            ТекстХарактеристики = ТекстХарактеристики + ДопРекв.Свойство.Заголовок + ": " + ДопРекв.Значение;
        КонецЦикла;
    КонецЦикла;
КонецЦикла;

Проанализируем этот код: обращение через конструкцию вида ДопРекв.Свойство.Заголовок внутри вложенного цикла заставляет систему выполнять отдельные SQL-запросы к базе данных на каждую итерацию каждого свойства каждой строки товара. Если в документе содержится 100 строк, а у каждой характеристики по 3 свойства, система выполнит порядка 300–500 мелких запросов к СУБД. Это приведет к «зависанию» формирования печатной формы на 10–15 секунд и создаст колоссальную нагрузку на сервер.

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

Резюме

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

← На главную