Разработка отчетов и печатных форм в среде 1С:Предприятие 8.3 на управляемых формах часто вызывает трудности у начинающих программистов. Одной из наиболее типичных проблем является путаница между экранными элементами и физическими таблицами базы данных, а также непонимание специфики передачи данных между клиентом и сервером. Выясним причину популярных ошибок и разберем по шагам, как правильно реализовать вывод данных запроса по макету непосредственно в форму.
Прежде чем перейти к написанию кода, проанализируем терминологию, в которой часто путаются новички. В заголовках форумов нередко звучит фраза «вывод запроса в табличную часть по макету». Однако это принципиальное архитектурное заблуждение.
Табличная часть (ТЧ) — это жестко структурированный объект конфигурации (или реквизит формы типа ДанныеФормыКоллекция), состоящий из строго определенного набора колонок. Она предназначена для хранения табличных данных (например, перечня товаров в документе). В табличную часть нельзя произвольным образом вывести графический макет, объединить ячейки крест-накрест, раскрасить отдельные строки в разные цвета программным путем или установить произвольные шрифты для отдельных областей.
Табличный документ (ТабДок) — это специализированный объект платформы 1С (тип ТабличныйДокумент), который представляет собой аналог электронных таблиц Excel. Именно он используется для формирования отчетов, прайс-листов и печатных форм. Табличный документ состоит из ячеек, поддерживает группировки, вывод областей макета, сложные форматирования и условное оформление. На управляемой форме он отображается через элемент управления Поле табличного документа.
Поэтому, если перед нами стоит задача вывести структурированный отчет с шапкой, группировками контрагентов и подвалами по созданному макету, мы всегда работаем с Табличным документом, а не с табличной частью.
Рассмотрим ситуацию, с которой столкнулся автор темы при попытке присвоить значение на клиенте:
&НаКлиенте
Процедура Сформировать(Команда)
Табл = СформироватьНаСервере();
Отчет.ТабДок = Табл; // ОШИБКА! Нельзя изменять поле, содержащее данные объекта
КонецПроцедуры
Выясним причину этого поведения платформы. В управляемом приложении 1С разделение на клиент и сервер реализовано максимально строго. На стороне клиента реквизиты формы, связанные с данными объекта (в данном случае Отчет.ТабДок), представлены специальными прокси-объектами (оболочками), которые обеспечивают интерактивную связь с интерфейсом пользователя.
Платформа 1С запрещает прямую перезапись (замену ссылки) таких сложных прикладных объектов на клиенте. Когда мы пытаемся вернуть с сервера новый физический объект типа ТабличныйДокумент и присвоить его свойству Отчет.ТабДок, система генерирует исключение, так как эта операция нарушает синхронизацию данных формы.
Решить эту проблему можно двумя путями:
&НаСервере), где реквизит формы доступен напрямую и платформа сама синхронизирует изменения с клиентом.&НаСервереБезКонтекста, то возвращенный табличный документ нужно не переприсваивать целиком, а копировать его содержимое с помощью метода Вывести() или использовать другие механизмы загрузки данных на сервере.В процессе обсуждения на форумах часто всплывают устаревшие советы времен платформы 1С 8.1 или обычного интерфейса 8.2, например:
ЭлементыФормы.ПолеТабличногоДокумента1.Вывести(ТабДок);
Попытка использовать этот код в управляемом приложении приведет к ошибке компиляции по двум причинам:
ЭлементыФормы более не существует. В управляемых формах все элементы интерфейса доступны через коллекцию Элементы.Вывести(). Этот метод есть исключительно у прикладного объекта ТабличныйДокумент, который выступает источником данных (реквизитом) для этого элемента.Рассмотрим наиболее простой, надежный и производительный способ формирования отчета по макету на управляемой форме. Для этого мы будем использовать контекстный вызов сервера. В этом случае нам не нужно передавать тяжелые табличные документы между клиентом и сервером вручную — платформа сделает это автоматически в рамках стандартного вызова.
Предположим, у нас есть отчет. В реквизитах отчета (или самой формы) создан реквизит ТабДок с типом ТабличныйДокумент. На форме размещен элемент управления типа Поле табличного документа, путь к данным которого указывает на наш реквизит ТабДок.
Разберем по шагам правильный алгоритм формирования:
&НаКлиенте
Процедура Сформировать(Команда)
// Вызываем контекстную процедуру на сервере
СформироватьНаСервере();
КонецПроцедуры
&НаСервере
Процедура СформироватьНаСервере()
// Шаг 1. Очищаем наш реквизит формы ТабДок перед новым выводом
ТабДок.Очистить();
// Шаг 2. Получаем объект отчета, чтобы добраться до его макетов
ОтчетОбъект = РеквизитФормыВЗначение("Отчет");
// Шаг 3. Получаем макет по имени
Макет = ОтчетОбъект.ПолучитьМакет("МакетОтчета");
// Шаг 4. Получаем области макета для последующего заполнения
ОбластьШапка = Макет.ПолучитьОбласть("Шапка");
ОбластьДанныеКонтрагент = Макет.ПолучитьОбласть("СтрДанныеКонтрагент");
ОбластьДанныеДоговор = Макет.ПолучитьОбласть("СтрДанныеДоговорКонтрагента");
ОбластьПодвал = Макет.ПолучитьОбласть("Подвал");
// Шаг 5. Заполняем параметры шапки из реквизитов отчета (или элементов формы)
// Обратите внимание: обращаться к реквизитам отчета на сервере нужно через "Отчет"
ОбластьШапка.Параметры.НачалоПериода = Отчет.НачалоПериода;
ОбластьШапка.Параметры.КонецПериода = Отчет.КонецПериода;
// Выводим шапку в наш ТабличныйДокумент формы
ТабДок.Вывести(ОбластьШапка);
// Шаг 6. Создаем и настраиваем запрос для получения данных
Запрос = Новый Запрос;
Запрос.УстановитьПараметр("Счет62", ПланыСчетов.Хозрасчетный.НайтиПоКоду("62"));
Запрос.УстановитьПараметр("НачалоПериода", Отчет.НачалоПериода);
Запрос.УстановитьПараметр("КонецПериода", Отчет.КонецПериода);
Запрос.Текст =
"ВЫБРАТЬ
| ХозрасчетныйОбороты.Субконто1 КАК Контрагент,
| ХозрасчетныйОбороты.Субконто2 КАК ДоговорКонтрагента,
| ХозрасчетныйОбороты.СуммаОборотДт КАК Приход,
| ХозрасчетныйОбороты.СуммаОборотКт КАК Расход
|ИЗ
| РегистрБухгалтерии.Хозрасчетный.Обороты(&НачалоПериода, &КонецПериода, , Счет В ИЕРАРХИИ (&Счет62), , , , ) КАК ХозрасчетныйОбороты
|ИТОГИ
| СУММА(Приход),
| СУММА(Расход)
|ПО
| Контрагент,
| ДоговорКонтрагента";
РезультатЗапроса = Запрос.Выполнить();
// Шаг 7. Обход результата запроса по группировкам для построения иерархии
ВыборкаКонтрагент = РезультатЗапроса.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);
ИтогоПриход = 0;
ИтогоРасход = 0;
// Включаем автогруппировку строк для красивого сворачивания по "+"
ТабДок.НачатьАвтогруппировкуСтрок();
Пока ВыборкаКонтрагент.Следующий() Цикл
// Очищаем параметры перед заполнением
ОбластьДанныеКонтрагент.Параметры.Заполнить(Новый Структура("Приход, Расход", 0, 0));
// Заполняем параметры области данными из выборки контрагента
ЗаполнитьЗначенияСвойств(ОбластьДанныеКонтрагент.Параметры, ВыборкаКонтрагент);
// Выводим строку контрагента (уровень группировки 1)
ТабДок.Вывести(ОбластьДанныеКонтрагент, 1);
// Выбираем подчиненные договоры для текущего контрагента
ВыборкаДоговор = ВыборкаКонтрагент.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);
Пока ВыборкаДоговор.Следующий() Цикл
ОбластьДанныеДоговор.Параметры.Заполнить(Новый Структура("Приход, Расход", 0, 0));
ЗаполнитьЗначенияСвойств(ОбластьДанныеДоговор.Параметры, ВыборкаДоговор);
// Выводим строку договора (уровень группировки 2)
ТабДок.Вывести(ОбластьДанныеДоговор, 2);
// Накапливаем общие итоги
ИтогоПриход = ИтогоПриход + ВыборкаДоговор.Приход;
ИтогоРасход = ИтогоРасход + ВыборкаДоговор.Расход;
КонецЦикла;
КонецЦикла;
// Завершаем группировку строк
ТабДок.ЗакончитьАвтогруппировкуСтрок();
// Шаг 8. Вывод подвала с итогами
ОбластьПодвал.Параметры.ИтогоПриход = ИтогоПриход;
ОбластьПодвал.Параметры.ИтогоРасход = ИтогоРасход;
ТабДок.Вывести(ОбластьПодвал);
КонецПроцедуры
Проанализируем ключевые моменты представленного кода:
Очистить() у реквизита формы ТабДок. Это гарантирует, что при повторном нажатии на кнопку «Сформировать» старый отчет сотрется, уступив место новым актуальным данным.Загрузить(), предназначенного для работы со структурами файлов, мы используем метод Вывести(), который последовательно добавляет полученные из макета области в результирующий табличный документ.ЗаполнитьЗначенияСвойств(). Это существенно сокращает объем рутинного кода, автоматически сопоставляя имена полей выборки запроса с именами параметров в областях макета.НачатьАвтогруппировкуСтрок() и ЗакончитьАвтогруппировкуСтрок() совместно с указанием числового уровня в методе Вывести(Область, Уровень).В заголовке исходной темы упоминается аббревиатура СКД. Если для построения отчета вы используете Систему компоновки данных, то ручной обход выборок, создание запросов в коде и верстка макетов ячейка за ячейкой становятся избыточными операциями. СКД умеет самостоятельно генерировать структуру отчета по заданным настройкам.
Рассмотрим, как красиво и лаконично вывести отчет СКД в табличный документ на управляемой форме программным путем:
&НаСервере
Процедура СформироватьОтчетСКДНаСервере()
// 1. Очищаем результирующий табличный документ на форме
ТабДок.Очистить();
// 2. Получаем схему компоновки данных из макетов отчета
ОтчетОбъект = РеквизитФормыВЗначение("Отчет");
СхемаКомпоновкиДанных = ОтчетОбъект.ПолучитьМакет("ОсновнаяСхемаКомпоновкиДанных");
// 3. Получаем текущие настройки пользователя из компоновщика настроек
Настройки = Отчет.КомпоновщикНастроек.ПолучитьНастройки();
// 4. Выполняем компоновку макета с учетом настроек пользователя
КомпоновщикМакета = Новый КомпоновщикМакетаКомпоновкиДанных;
МакетКомпоновки = КомпоновщикМакета.Выполнить(СхемаКомпоновкиДанных, Настройки);
// 5. Инициализируем процессор компоновки данных полученным макетом
ПроцессорКомпоновкиДанных = Новый ПроцессорКомпоновкиДанных;
ПроцессорКомпоновкиДанных.Инициализировать(МакетКомпоновки);
// 6. Выводим результат в табличный документ формы через процессор вывода
ПроцессорВывода = Новый ПроцессорВыводаРезультатаКомпоновкиДанныхВТабличныйДокумент;
ПроцессорВывода.УстановитьДокумент(ТабДок);
ПроцессорВывода.Вывести(ПроцессорКомпоновкиДанных);
КонецПроцедуры
Использование СКД предоставляет разработчику колоссальные преимущества перед ручным выводом макетов:
Таким образом, для простых печатных форм документов (например, Торг-12 или счет на оплату), где структура жестко фиксирована стандартами, идеально подходит классический вывод по макету с ручным обходом запроса. Для построения многофункциональных аналитических отчетов (обороты по счетам, продажи по периодам, остатки товаров) золотым стандартом является использование механизмов СКД.