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

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

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

Постановка задачи

Нам необходимо создать отчет, который выводит текущие цены номенклатурных групп (или ценовых групп) в разрезе определенных типов цен (наценок). Данные должны быть представлены в виде таблицы, где строки — это категории (ценовые группы), а колонки — конкретные типы цен (например, VIP 0-250, VIP 250-500 и т.д.).

В процессе реализации часто возникают следующие трудности:

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

Рассмотрим каждую проблему подробно и напишем корректный код.

Ошибка 1: Обращение к элементам справочника в запросе

Одной из распространенных ошибок является попытка получить ссылку на элемент справочника по его коду непосредственно внутри текста запроса, используя конструкцию ЗНАЧЕНИЕ().

Неправильный вариант кода:


// ОШИБКА: Так писать нельзя, если элемент не является предопределенным в конфигураторе
ВЫБОР КОГДА Цены.ТипСкидкиНаценки = ЗНАЧЕНИЕ(Справочник.ТипыСкидокНаценок.00020) ...

Почему это не работает? Оператор ЗНАЧЕНИЕ() предназначен для работы с перечислениями (например, ЗНАЧЕНИЕ(Перечисление.ВидыНоменклатуры.Товар)) или предопределенными элементами справочников (созданными в конфигураторе и имеющими имя). Если элемент создан в пользовательском режиме и имеет просто код "00020", запрос не сможет его интерпретировать.

Решение: Ссылки на конкретные элементы необходимо найти в коде встроенного языка (через менеджер справочника) и передать в запрос в качестве параметров.


// В коде 1С перед выполнением запроса:
ТипЦены1 = Справочники.ТипыСкидокНаценок.НайтиПоКоду("00020");
Запрос.УстановитьПараметр("ТипЦеныVIP_0", ТипЦены1);

// В тексте запроса:
ВЫБОР КОГДА Цены.ТипСкидкиНаценки = &ТипЦеныVIP_0 ...

Ошибка 2: Логика "поворота" таблицы (Pivot)

Если мы хотим получить одну строку для одной ценовой группы, но вывести несколько цен в разные колонки, простого использования ВЫБОР КОГДА недостаточно. Если у группы есть три цены, запрос вернет три строки (в каждой будет заполнена только одна колонка с ценой, остальные — нули).

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

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


ВЫБРАТЬ
    Цены.ЦеноваяГруппа КАК Категория,
    МАКСИМУМ(ВЫБОР
        КОГДА Цены.ТипСкидкиНаценки = &Тип1
            ТОГДА Цены.Цена
        ИНАЧЕ 0
    КОНЕЦ) КАК ЦенаVIP_0,
    МАКСИМУМ(ВЫБОР
        КОГДА Цены.ТипСкидкиНаценки = &Тип2
            ТОГДА Цены.Цена
        ИНАЧЕ 0
    КОНЕЦ) КАК ЦенаVIP_250
ИЗ
    РегистрСведений.СофУТ_ЦеныНоменклатурыГр.СрезПоследних(&ДатаАктуальности, ) КАК Цены
СГРУППИРОВАТЬ ПО
    Цены.ЦеноваяГруппа

Ошибка 3: Архитектура вывода отчета на форму

В управляемом приложении (на управляемых формах) нельзя напрямую обращаться к элементам формы из модуля объекта. Код вида ЭтотОбъект.ПолучитьФорму(...).Поле... = ... создает новую копию формы в памяти сервера, записывает туда данные и сразу уничтожает её. Пользователь на экране ничего не увидит.

Правильный алгоритм действий:

  1. В модуле формы (НаКлиенте) пользователь нажимает кнопку "Сформировать".
  2. Вызывается функция (НаСервере), которая формирует и возвращает ТабличныйДокумент.
  3. Полученный документ присваивается реквизиту формы, который связан с элементом отображения.

Полный пример реализации

Разберем готовый код, объединяющий исправления всех ошибок.

1. Подготовка макета
В конфигураторе создаем макет типа "Табличный документ". Рисуем три области:

2. Код модуля формы
Вся логика будет сосредоточена в модуле формы отчета.


&НаКлиенте
Процедура Сформировать(Команда)
    // Вызываем серверную функцию для получения готового документа
    ТабДокРезультат = СформироватьНаСервере();
    
    // Выводим результат в реквизит формы "Результат" (тип ТабличныйДокумент)
    Результат = ТабДокРезультат;
КонецПроцедуры

&НаСервере
Функция СформироватьНаСервере()
    
    // 1. Инициализация макета
    ОтчетОбъект = РеквизитФормыВЗначение("Отчет");
    Макет = ОтчетОбъект.ПолучитьМакет("Макет");
    ТабДок = Новый ТабличныйДокумент;
    
    // 2. Вывод заголовков
    ОбластьЗаголовок = Макет.ПолучитьОбласть("Заголовок");
    ТабДок.Вывести(ОбластьЗаголовок);
    
    ОбластьШапка = Макет.ПолучитьОбласть("ШапкаТаблицы");
    ТабДок.Вывести(ОбластьШапка);
    
    // 3. Подготовка параметров для запроса (поиск типов цен)
    // Важно: здесь мы находим ссылки на справочники
    Тип0_250 = Справочники.ТипыСкидокНаценок.НайтиПоКоду("00020");
    Тип250_500 = Справочники.ТипыСкидокНаценок.НайтиПоКоду("00019");
    Тип500Plus = Справочники.ТипыСкидокНаценок.НайтиПоКоду("00021");
    
    // 4. Запрос с разворотом таблицы
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |   Цены.ЦеноваяГруппа КАК Категория,
        |   МАКСИМУМ(ВЫБОР
        |       КОГДА Цены.ТипСкидкиНаценки = &Тип1
        |           ТОГДА Цены.Цена
        |       ИНАЧЕ 0
        |   КОНЕЦ) КАК Цена1,
        |   МАКСИМУМ(ВЫБОР
        |       КОГДА Цены.ТипСкидкиНаценки = &Тип2
        |           ТОГДА Цены.Цена
        |       ИНАЧЕ 0
        |   КОНЕЦ) КАК Цена2,
        |   МАКСИМУМ(ВЫБОР
        |       КОГДА Цены.ТипСкидкиНаценки = &Тип3
        |           ТОГДА Цены.Цена
        |       ИНАЧЕ 0
        |   КОНЕЦ) КАК Цена3
        |ИЗ
        |   РегистрСведений.СофУТ_ЦеныНоменклатурыГр.СрезПоследних(КОНЕЦПЕРИОДА(&Дата, ДЕНЬ), ) КАК Цены
        |
        |СГРУППИРОВАТЬ ПО
        |   Цены.ЦеноваяГруппа";
        
    Запрос.УстановитьПараметр("Дата", ТекущаяДата());
    Запрос.УстановитьПараметр("Тип1", Тип0_250);
    Запрос.УстановитьПараметр("Тип2", Тип250_500);
    Запрос.УстановитьПараметр("Тип3", Тип500Plus);
    
    Выборка = Запрос.Выполнить().Выбрать();
    
    // 5. Обход выборки и заполнение строк
    ОбластьСтрока = Макет.ПолучитьОбласть("Строка");
    
    Пока Выборка.Следующий() Цикл
        // Заполняем параметры области данными из запроса
        // Имена параметров области должны совпадать со свойствами ниже
        ОбластьСтрока.Параметры.Категория = Выборка.Категория;
        ОбластьСтрока.Параметры.Цена1 = Выборка.Цена1;
        ОбластьСтрока.Параметры.Цена2 = Выборка.Цена2;
        ОбластьСтрока.Параметры.Цена3 = Выборка.Цена3;
        
        ТабДок.Вывести(ОбластьСтрока);
    КонецЦикла;
    
    Возврат ТабДок;
    
КонецФункции

Важные замечания

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

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

← На главную