Как передать ссылку на документ во внешнюю печатную форму и настроить многоуровневую группировку в 1С

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

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

Передача ссылки на документ в запрос внешней печатной формы

Если код выполняется в модуле самого документа, то стандартный реквизит Ссылка доступен напрямую в контексте объекта. Однако во внешней обработке переменная Ссылка не определена, и попытка написать Запрос.УстановитьПараметр("Ссылка", Ссылка); приводит к ошибке компиляции платформы 1С: Переменная не определена (Ссылка).

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

  1. Откроем внешнюю обработку в конфигураторе и добавим новый реквизит объекта с именем СсылкаНаОбъект.
  2. В качестве типа созданного реквизита укажем ссылку на нужный документ, например ДокументСсылка.ОтчетОРозничныхПродажах (либо составной тип, если форма разрабатывается сразу для нескольких документов).
  3. В коде функции печати передадим данный реквизит в параметр запроса: Запрос.УстановитьПараметр("Ссылка", СсылкаНаОбъект);.

При подключении обработки через справочник «Внешние обработки» типовые механизмы конфигурации автоматически заполняют реквизит СсылкаНаОбъект ссылкой на документ, из формы которого вызывается команда печати. При отладке через пункт меню «Файл — Открыть» этот реквизит можно вынести на форму обработки и заполнять вручную для тестирования выборки данных.

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


ГДЕ
    ОтчетОРозничныхПродажахТовары.Ссылка В (&Ссылка)

Платформа 1С корректно отрабатывает оператор В как для одиночной ссылки, так и для переданной коллекции ссылок.

Организация многоуровневых группировок в запросе и вывод макета

Часто в печатных формах требуется разделять номенклатурные позиции по категориям, видам партий или товарным группам, чтобы наименование группы не дублировалось в каждой строке, а выводилось отдельным заголовком. Для этого во встроенном языке запросов 1С используется секция ИТОГИ ПО, а вывод строк в табличный документ осуществляется через вложенные циклы обхода результатов запроса по группировкам.

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


Запрос = Новый Запрос;
Запрос.Текст = 
"ВЫБРАТЬ
|   ОтчетОРозничныхПродажахТовары.Цена КАК Цена,
|   ОтчетОРозничныхПродажахТовары.Сумма КАК Сумма,
|   ОтчетОРозничныхПродажахТовары.Номенклатура.НоменклатурнаяГруппа КАК НоменклатурнаяГруппа,
|   ОтчетОРозничныхПродажахТовары.Номенклатура КАК Номенклатура,
|   ЕСТЬNULL(ПартииТоваровНаСкладах.СтатусПартии, ЗНАЧЕНИЕ(Перечисление.СтатусыПартийСертификатов.ПустаяСсылка)) КАК СтатусПартии,
|   ЕСТЬNULL(ПартииТоваровНаСкладах.Количество, 0) КАК Количество1
|ИЗ
|   Документ.ОтчетОРозничныхПродажах.Товары КАК ОтчетОРозничныхПродажахТовары
|   ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ПартииТоваровНаСкладах КАК ПартииТоваровНаСкладах
|       ПО ОтчетОРозничныхПродажахТовары.Ссылка = ПартииТоваровНаСкладах.Регистратор
|       И ОтчетОРозничныхПродажахТовары.Номенклатура = ПартииТоваровНаСкладах.Номенклатура
|       И ПартииТоваровНаСкладах.Регистратор В (&Ссылка)
|ГДЕ
|   ОтчетОРозничныхПродажахТовары.Ссылка В (&Ссылка)
|ИТОГИ
|   СУММА(Сумма),
|   СУММА(Количество1)
|ПО
|   СтатусПартии,
|   НоменклатурнаяГруппа";

Запрос.УстановитьПараметр("Ссылка", СсылкаНаОбъект);
РезультатЗапроса = Запрос.Выполнить();

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

Устранение ошибки «Преобразование значения к типу Число не может быть выполнено»

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

На уровне итоговых записей (в выборке по группировкам) поля, которые не были перечислены с агрегатными функциями (например, СУММА(...)) в секции ИТОГИ запроса, принимают значение NULL. В частности, поле Цена на уровне группы не имеет агрегации, поэтому при попытке умножения на него платформа 1С пытается выполнить арифметическую операцию со значением NULL, что и приводит к ошибке.

Правильный подход заключается в переносе агрегации данных на уровень самого запроса. В секции ИТОГИ объявляем суммирование необходимых ресурсов: СУММА(Сумма) и СУММА(Количество1). Тогда в выборке группировки будут сразу содержаться готовые числовые значения.

Полный пример алгоритма печати с тремя областями макета

Для корректного вывода данных создадим в макете три отдельные горизонтальные области: Шапка (для статуса партии), Группа (для заголовка номенклатурной группы и итогов по ней) и Данные (для детальных строк товаров). Посмотрим на реализацию модуля:


Функция Печать() Экспорт

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

    Запрос = Новый Запрос;
    Запрос.Текст = 
    "ВЫБРАТЬ
    |   ОтчетОРозничныхПродажахТовары.Цена КАК Цена,
    |   ОтчетОРозничныхПродажахТовары.Сумма КАК Сумма,
    |   ОтчетОРозничныхПродажахТовары.Номенклатура.НоменклатурнаяГруппа КАК НоменклатурнаяГруппа,
    |   ОтчетОРозничныхПродажахТовары.Номенклатура КАК Номенклатура,
    |   ЕСТЬNULL(ПартииТоваровНаСкладах.СтатусПартии, ЗНАЧЕНИЕ(Перечисление.СтатусыПартийСертификатов.ПустаяСсылка)) КАК СтатусПартии,
    |   ЕСТЬNULL(ПартииТоваровНаСкладах.Количество, 0) КАК Количество1
    |ИЗ
    |   Документ.ОтчетОРозничныхПродажах.Товары КАК ОтчетОРозничныхПродажахТовары
    |   ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ПартииТоваровНаСкладах КАК ПартииТоваровНаСкладах
    |       ПО ОтчетОРозничныхПродажахТовары.Ссылка = ПартииТоваровНаСкладах.Регистратор
    |       И ОтчетОРозничныхПродажахТовары.Номенклатура = ПартииТоваровНаСкладах.Номенклатура
    |       И ПартииТоваровНаСкладах.Регистратор В (&Ссылка)
    |ГДЕ
    |   ОтчетОРозничныхПродажахТовары.Ссылка В (&Ссылка)
    |ИТОГИ
    |   СУММА(Сумма),
    |   СУММА(Количество1)
    |ПО
    |   СтатусПартии,
    |   НоменклатурнаяГруппа";

    Запрос.УстановитьПараметр("Ссылка", СсылкаНаОбъект);
    РезультатЗапроса = Запрос.Выполнить();

    // 1-й уровень: Группировка по статусу партии
    ВыборкаСтатус = РезультатЗапроса.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам, "СтатусПартии");
    Пока ВыборкаСтатус.Следующий() Цикл
        
        ОбластьШапка = Макет.ПолучитьОбласть("Шапка");
        ЗначениеСтатуса = ВыборкаСтатус.СтатусПартии;
        
        // Преобразуем представление для пользователя при необходимости
        ОбластьШапка.Параметры.СтатусПартии = ?(ЗначениеСтатуса = "Продажи фирмы", "Купленный", 
            ?(ЗначениеСтатуса = "На комиссию", "Комиссионные продажи", ЗначениеСтатуса));
        ТабДокумент.Вывести(ОбластьШапка);

        // 2-й уровень: Группировка по номенклатурной группе
        ВыборкаГруппа = ВыборкаСтатус.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам, "НоменклатурнаяГруппа");
        Пока ВыборкаГруппа.Следующий() Цикл
            
            ОбластьГруппа = Макет.ПолучитьОбласть("Группа");
            ОбластьГруппа.Параметры.НоменклатурнаяГруппа = ВыборкаГруппа.НоменклатурнаяГруппа;
            
            // Заполняем агрегированные итоги по группе напрямую из полей выборки
            ОбластьГруппа.Параметры.Итог = ВыборкаГруппа.Сумма;
            ОбластьГруппа.Параметры.КолИтог = ВыборкаГруппа.Количество1;
            ТабДокумент.Вывести(ОбластьГруппа);

            // 3-й уровень: Детальные записи строк
            ВыборкаДетальныеЗаписи = ВыборкаГруппа.Выбрать();
            Пока ВыборкаДетальныеЗаписи.Следующий() Цикл
                
                ОбластьДанные = Макет.ПолучитьОбласть("Данные");
                // Метод Заполнить автоматически сопоставляет имена параметров области с полями выборки
                ОбластьДанные.Параметры.Заполнить(ВыборкаДетальныеЗаписи);
                ТабДокумент.Вывести(ОбластьДанные);
                
            КонецЦикла;
            
        КонецЦикла;
        
    КонецЦикла;

    Возврат ТабДокумент;

КонецФункции

Использование метода Параметры.Заполнить(ВыборкаДетальныеЗаписи) позволяет сократить объем кода и избежать ручного присвоения каждого реквизита при совпадении имен параметров макета и полей в запросе.

← На главную