Как получить данные из других справочников и регистров во внешней печатной форме (ВПФ) и избежать ошибок?

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

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

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

Анализ исходного кода с ошибками

Посмотрим на пример кода функции формирования печатной формы, в котором допущен ряд концептуальных ошибок:


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

Выясним причины, почему этот код не работает корректно.

Ошибка 1: Запрос без условий (выборка всей базы)

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

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


// В тексте запроса добавляем условие:
// ГДЕ ПриказОДвиженииКонтингента.Ссылка В (&МассивОбъектов)

Запрос.УстановитьПараметр("МассивОбъектов", МассивОбъектов);

Ошибка 2: Ошибка "Несоответствие типов" и пустой документ

В типовой процедуре печати на базе БСП используется метод УправлениеПечатью.ВывестиТабличныйДокументВКоллекцию. Этот метод ожидает, что последним параметром ему будет передан готовый сформированный объект типа ТабличныйДокумент.

В приведенном выше коде функция СформироватьПечатнуюФорму завершается без оператора Возврат. Если функция ничего не возвращает, 1С неявно возвращает значение Неопределено. БСП пытается принять Неопределено вместо табличного документа, и мы получаем ошибку "Несоответствие типов". Кроме того, в цикле обхода выборки нет команды вывода заполненной области в сам табличный документ.

Исправим это, добавив вывод области и возврат результата:


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

Возврат ТабДок;

Ошибка 3: Жесткая привязка к одному документу в массиве

В процедуре Печать часто можно встретить такой вызов: передается только МассивОбъектов[0]. В 1С пользователи часто выделяют в списке сразу несколько документов и нажимают кнопку печати. Если передавать только нулевой индекс, распечатается только первый выделенный документ. Правильный подход — передавать в функцию формирования весь МассивОбъектов, передавать его в параметр запроса и внутри цикла формировать листы для каждого документа подряд.

Ошибка 4: Неправильный вывод табличной части

В коде автора область макета называется просто "Основа", и туда пытаются вывести данные из табличной части списка студентов. Если в документе есть список (много строк), в макете создаются отдельные области: например, «Шапка» (для реквизитов документа) и «СтрокаТаблицы» (для студентов).

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

  1. Получаем области макета: Шапку и Строку.
  2. Выводим Шапку документа.
  3. В цикле обходим детальные записи запроса по студентам.
  4. Заполняем параметры области Строки и выводим её для каждого студента.

Итоговый правильный вариант кода

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


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

Как правильно отлаживать ВПФ в конфигураторе

Постоянно загружать обработку в справочник "Дополнительные отчеты и обработки" для проверки кода — очень долго. Чтобы быстро отлаживать ВПФ, рассмотрим удобный прием:

  1. Откройте конфигуратор и создайте форму для вашей внешней обработки (если ее нет).
  2. Добавьте на форму реквизит СсылкаНаДокумент (тип - ДокументСсылка...).
  3. Добавьте кнопку "Тест Печати". В обработчике команды на сервере создайте искусственный массив с этой ссылкой.
  4. Напрямую вызовите экспортную процедуру Печать из модуля объекта обработки:

&НаСервере
Процедура ТестПечатиНаСервере()
    ОбъектОбработки = РеквизитФормыВЗначение("Объект");
    МассивОбъектов = Новый Массив;
    МассивОбъектов.Добавить(СсылкаНаДокумент);
    КоллекцияПечатныхФорм = УправлениеПечатью.ПодготовитьКоллекциюПечатныхФорм("ОтчислениеВнешний");
    
    // Вызываем процедуру из модуля объекта обработки
    ОбъектОбработки.Печать(МассивОбъектов, КоллекцияПечатныхФорм, Неопределено, Неопределено);
    
    // Для просмотра результата можно получить ТабДок из коллекции
    Если КоллекцияПечатныхФорм.Количество() > 0 Тогда
        ТабДок = КоллекцияПечатныхФорм[0].ТабличныйДокумент;
        // вывести на форму или сохранить
    КонецЕсли;
КонецПроцедуры

Теперь вы можете открыть вашу внешнюю обработку через меню Файл -> Открыть прямо в режиме Предприятия, выбрать документ, нажать кнопку и спокойно использовать точки останова (F9) в модуле объекта для пошаговой отладки запросов и вывода областей.

← На главную