В данной статье мы разберем задачу создания внешнего отчета в 1С:Предприятие, используя классический подход вывода данных в ТабличныйДокумент через макет (без использования СКД). На примере реальной проблемы проанализируем типичные ошибки начинающих разработчиков при работе с языком запросов, агрегатными функциями и архитектурой управляемых форм.
Нам необходимо создать отчет, который выводит текущие цены номенклатурных групп (или ценовых групп) в разрезе определенных типов цен (наценок). Данные должны быть представлены в виде таблицы, где строки — это категории (ценовые группы), а колонки — конкретные типы цен (например, VIP 0-250, VIP 250-500 и т.д.).
В процессе реализации часто возникают следующие трудности:
Рассмотрим каждую проблему подробно и напишем корректный код.
Одной из распространенных ошибок является попытка получить ссылку на элемент справочника по его коду непосредственно внутри текста запроса, используя конструкцию ЗНАЧЕНИЕ().
Неправильный вариант кода:
// ОШИБКА: Так писать нельзя, если элемент не является предопределенным в конфигураторе
ВЫБОР КОГДА Цены.ТипСкидкиНаценки = ЗНАЧЕНИЕ(Справочник.ТипыСкидокНаценок.00020) ...
Почему это не работает? Оператор ЗНАЧЕНИЕ() предназначен для работы с перечислениями (например, ЗНАЧЕНИЕ(Перечисление.ВидыНоменклатуры.Товар)) или предопределенными элементами справочников (созданными в конфигураторе и имеющими имя). Если элемент создан в пользовательском режиме и имеет просто код "00020", запрос не сможет его интерпретировать.
Решение: Ссылки на конкретные элементы необходимо найти в коде встроенного языка (через менеджер справочника) и передать в запрос в качестве параметров.
// В коде 1С перед выполнением запроса:
ТипЦены1 = Справочники.ТипыСкидокНаценок.НайтиПоКоду("00020");
Запрос.УстановитьПараметр("ТипЦеныVIP_0", ТипЦены1);
// В тексте запроса:
ВЫБОР КОГДА Цены.ТипСкидкиНаценки = &ТипЦеныVIP_0 ...
Если мы хотим получить одну строку для одной ценовой группы, но вывести несколько цен в разные колонки, простого использования ВЫБОР КОГДА недостаточно. Если у группы есть три цены, запрос вернет три строки (в каждой будет заполнена только одна колонка с ценой, остальные — нули).
Для "схлопывания" строк необходимо использовать группировку СГРУППИРОВАТЬ ПО и агрегатную функцию, например, МАКСИМУМ (так как мы выбираем между ценой и нулем, максимум вернет цену).
Правильный текст запроса будет выглядеть так:
ВЫБРАТЬ
Цены.ЦеноваяГруппа КАК Категория,
МАКСИМУМ(ВЫБОР
КОГДА Цены.ТипСкидкиНаценки = &Тип1
ТОГДА Цены.Цена
ИНАЧЕ 0
КОНЕЦ) КАК ЦенаVIP_0,
МАКСИМУМ(ВЫБОР
КОГДА Цены.ТипСкидкиНаценки = &Тип2
ТОГДА Цены.Цена
ИНАЧЕ 0
КОНЕЦ) КАК ЦенаVIP_250
ИЗ
РегистрСведений.СофУТ_ЦеныНоменклатурыГр.СрезПоследних(&ДатаАктуальности, ) КАК Цены
СГРУППИРОВАТЬ ПО
Цены.ЦеноваяГруппа
В управляемом приложении (на управляемых формах) нельзя напрямую обращаться к элементам формы из модуля объекта. Код вида ЭтотОбъект.ПолучитьФорму(...).Поле... = ... создает новую копию формы в памяти сервера, записывает туда данные и сразу уничтожает её. Пользователь на экране ничего не увидит.
Правильный алгоритм действий:
ТабличныйДокумент.Разберем готовый код, объединяющий исправления всех ошибок.
1. Подготовка макета
В конфигураторе создаем макет типа "Табличный документ". Рисуем три области:
Заголовок (название отчета).ШапкаТаблицы (названия колонок: Категория, VIP 0-250, VIP 250-500 и т.д.).Строка (ячейки с параметрами: Категория, Цена1, Цена2, Цена3).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;
ТабДок.Вывести(ОбластьСтрока);
КонецЦикла;
Возврат ТабДок;
КонецФункции
Обратите внимание на использование виртуальной таблицы СрезПоследних. Если в регистре СофУТ_ЦеныНоменклатурыГр есть другие измерения, кроме "ЦеноваяГруппа" и "ТипСкидкиНаценки", то без явного отбора в параметрах виртуальной таблицы запрос может вернуть некорректные данные (лишние строки или неактуальные цены). В приведенном примере мы полагаем, что измерения регистра соответствуют задаче.
Также важно помнить, что псевдонимы полей в запросе, заданные через оператор КАК (например, КАК Категория), становятся именами свойств выборки. При обходе результата запроса мы обращаемся именно к ним: Выборка.Категория.