Оглавление
Введение
СКД предназначена для создания отчетов на основе декларативного описания. Модель СКД это DSL для программного создания декларативного описания и использования дополнительных возможностей, которые недоступны при интерактивной настройки схемы. Технология позволяет многое сделать «руками» в конструкторе схемы компоновки данных. Однако программное формирование схемы позволяет использовать больше возможностей для вывода отчетов: нестандартные макеты, фиксированные макеты без потери гибкости в настройке, универсальные отчеты по динамическим источникам данных, вывод диаграмм в ячейке, формирование дополнительных параметров расшифровки и т.д.
Идея создания DSL для СКД у меня зародилась после прочтения вот этой статьи Программная корректировка при выводе отчета СКД. В ней очень хорошо представлена структура и этапы процесса формирования отчета через СКД. После её прочтения у меня появилось понимание дополнительных возможностей СКД, недоступных при интерактивной настройке. Демонстрируемый подход в примере с отчетом, где выводится диаграмма остатков, мне показался сложным и это подвигло меня искать более простые способы.
Задача вывода таблицы значений в отчет уже давно является рутиной. Однако всякий раз, когда я сталкивался с ней, мне приходилось либо заново ее решать, либо пользоваться для вывода старым проверенным способом через Построитель отчетов. Да, где то я уже много раз писал код вывода таблицы значений в отчет, но каждый раз какие-то новые нюансы не позволяли просто переиспользовать его. В своих прошлых алгоритмах я использовал и прямой вывод в табличный документ, и построитель отчетов и даже СКД.
И в очередной раз я столкнулся с задачей вывода таблицы значений в отчет. И снова мне пришлось писать алгоритм вывода. На этот раз требования к выводу оказались особенно экзотическими. Мне потребовалось ввести структуру таблицы: определить колонки - измерения, колонки - ресурсы и колонки - реквизиты. Также у меня появились группировки и итоги. Все это требовалось красиво оформить. В общем в какой-то момент я понял, что пытаюсь написать что-то типа СКД. Когда количество кода для такой простой задачи превысило все разумные пределы я решил остановиться и посмотреть в сторону СКД более пристально.
Первое с чего я начал - создал тестовый стенд для исследования структуры объектов СКД. Для своего исследования я использовал ИР и генераторы кода СКД по схеме. Также я пытался генерировать собственные структуры, отражающие структуру объектов СКД в более простой форме. Так у меня появилась структура вывода макета СКД и массив группировок - выжимка из структуры тел макета компоновки, которые соответствуют выводу областей отчета.
Из анализа внутренней структуры макета СКД мне стал понятен механизм вывода отчета. Стал понятен принцип формирования такой структуры и соответственно понятно как такую структуру сформировать самому для изменения этого вывода. Также стали понятны сложности внутреннего представления, которые приводят к сложному коду программной обработки таких объектов (см. сравнение кода генератором и моим конструктором - разница в 5 раз). Кроме того код в подобных генераторах плохо отражает структурность объектов СКД: описание групп полей и их вложенность, описание отборов с группами "И" и "ИЛИ", структура группировок и их полей.
Сравнение объема кода при использовании генератора и модели
Проведя комплексный анализ механизмов СКД (см. информацию на ИТС Система компоновки данных и в этом видео Программная работа с СКД в 1С) от создания схемы компоновки до вывода макета в отчет, я стал синтезировать полученные знания в некий язык. Целью такого языка было скрыть сложности внутреннего представления схемы, настройки, макета СКД и убрать избыточность. Технология DSL с использованием текучего интерфейса очень хорошо легла на структуру иерархических объектов СКД. Также в языке я структурировал поля набора данных: выделил отдельно описание для полей Измерений, Реквизитов и Ресурсов - это позволило развести настройку таких полей и уменьшить количество аргументов операторов. Ввел предопределенные поля: Счет, Период, НачальныйОстаток, Сумма, КонечныйОстаток - это позволило еще больше сократить количество аргументов в связи со специализацией каждого поля. Настройки отделил от схемы: отдельно модель Схемы, отдельно модель Настроек. Чтобы не помнить все эти нюансы я также разработал, уже традиционный в моих решениях, конструктор модели отчета СКД.
Еще одна существенная причина разработки моделей СКД – поиск границ технологии. Это исследование позволило понять что действительно может технология. Познав границы технологии, можно создавать более эффективные и эффектные решения. Если до этого исследования в моем представлении СКД больше представлялось как черный ящик, то теперь это вполне себе гибкая и богатая среда, в которой можно воплощать самые безумные решения =514;. По результату технология меня не разочаровала!
Созданные модели соответствуют объектам СКД: Схема, Настройка, Макет компоновки данных, Макет области компоновки данных. Соответствующие модели DSL:
- Модель схемы
- Модель настройки
- Модель макета
- Модель макета области
Схема позволяет описать источники данных, их связи, параметры и варианты настроек. В модели совмещены описания полей источника данных и полей компоновки. Так в одном операторе сразу указывается и поле источника и его роль в компоновке данных. Такой подход позволяет с одной стороны описать поле один раз, а с другой, использовать для поля свой набор параметров, в соответствии с ролью. Например, поля с ролью начального/конечного остатков требуют названия ресурса. Поле с ролью периода настраивается в соответствии с порядком описания (можно переопределить). Поля в разделе Измерения сразу настраиваются на роль Измерение, а поля в разделе Ресурсы сразу попадают в итоги.
Пример 1. Описание схемы с ролями полей
МодельСхемыКомпоновки.НаборДанныхЗапрос(ТекстЗапроса, "НаборДанных")
.Измерения()
.Поле("Номенклатура")
.Поле("МестоХранения")
.Поле("Организация")
.Реквизиты()
.Период("НомерСтроки")
.Период("ПериодДень")
.Период("Регистратор")
.Период("ПериодСекунда")
.Ресурсы()
.НачальныйОстаток("КоличествоНачальныйОстаток", , "Количество")
.Сумма("КоличествоПриход")
.Сумма("КоличествоРасход")
.КонечныйОстаток("КоличествоКонечныйОстаток", , "Количество")
.Параметры()
.Параметр("НачалоПериода")
.Параметр("КонецПериода")
;
В схеме также можно описать и настройки отчета, однако в модели я исхожу из того, что при программном формировании отчета настройки будут описаны в настройке компоновщика настроек. Для работы с настройками реализована модель Настройки.
В модели есть разделы: описание полей отчета, Условия, Структура, Порядок, Параметры вывода. Поля могут объединяться в группы с неограниченной вложенностью. Условия отборов также могут объединяться в группы И, ИЛИ. Структура представляет собой вложенные группировки с описанием полей группировки и полей вывода. Порядок – перечень полей и сортировка. Параметры вывода – предопределенные параметры отчета.
Пример 2. Описание настройки в модели Настройки
МодельНастройкиКомпоновки = Общий.МодельНастройкиКомпоновкиДанных(МодельСхемыКомпоновки.СхемаКомпоновкиДанных)
.Поле("МестоХранения")
.Поле("Номенклатура")
.Поле("ПериодДень")
.Поле("Регистратор")
.Поле("КоличествоНачальныйОстаток")
.ГруппаНачать("Обороты")
.Поле("КоличествоПриход")
.Поле("КоличествоРасход")
.ГруппаЗавершить()
.Поле("КоличествоКонечныйОстаток")
.Условия()
.ОтборГруппаИНачать()
.Отбор("МестоХранения", "", ВидСравненияКомпоновкиДанных.НеРавно)
.Отбор("Номенклатура.Наименование", "Стул")
.ОтборГруппаЗавершить()
.Структура()
.ГруппировкаНачать()
.ПолеГруппировки("Организация")
.Поле("*")
.ГруппировкаНачать()
.ПолеГруппировки("МестоХранения")
.Поле("*")
.ГруппировкаНачать()
.ПолеГруппировки("Номенклатура")
.Поле("*")
.ГруппировкаНачать()
.ПолеГруппировки("*")
.Поле("*")
.ГруппировкаЗавершить()
.ГруппировкаЗавершить()
.ГруппировкаЗавершить()
.ГруппировкаЗавершить()
.Порядок("ПериодДень")
.Порядок("Регистратор")
.ПараметрыВывода()
.Параметр("МакетОформления", "Арктика")
.Параметр("ВыводитьЗаголовок", "Авто")
.Параметр("Заголовок", "Тест")
;
Макет является результатом преобразования схемы и настройки в макет, подготовленный для вывода. Модель макета не имеет настроек и по факту принимает на вход схему и настройки. Модель позволяет сделать вывод отчета в табличный документ. Однако роль модели не ограничивается только выводом макета. Основное назначение модели - извлечение информации из полученного макета компоновки данных с целью его модификации или использования при выводе.
Пример 3. Вывод отчета в табличный документ с использованием модели макета
МодельМакетаКомпоновки = Общий.МодельМакетаКомпоновкиДанных()
// Формирование макета и вывод отчета
.Схема(МодельСхемыКомпоновки.СхемаКомпоновкиДанных)
.Настройки(МодельНастройкиКомпоновки.Настройки)
.Скомпоновать(ДанныеРасшифровки)
.ВывестиВТабличныйДокумент(ДокументРезультат)
;
Если модель схемы и модель настройки больше подойдут для динамического программного формирования отчета, то модель макета позволяет модифицировать вывод отчета. Модель макета может использоваться и самостоятельно как дополнительный механизм контроля вывода.
В следующем примере с помощью информации о структуре макета в коде модифицируется макет шапки отчета «Демо модель макета области».
Пример 4. Использования структуры макета для модификации вывода шапки таблицы отчета. Добавление нумерации колонок
СтруктураМакета = МодельМакетаКомпоновки.СтруктураМакета();
///////////////////////////////////////////////////////
// Оформление шапки таблицы отчета, добавление нумерации колонок
// Структура макета представляет собой иерархическое дерево, где с последнего узла начинается
// "спуск" в глубину иерархии группировок отчета. Предпоследний узел описывается вывод макета шапки таблицы.
// Существует и другой способ поиска макета шапки - через сопоставление группировок и их макетов:
// - макет Группировки
// -- Заголовок
// -- Подвал
// -- Заголовок иерархии
// -- Подвал иерархии
// -- Общий итог заголовок
// -- Общий итог подвал
// - макет Заголовка группировки
// -- Заголовок
// -- Подвал
// -- Заголовок иерархии
// -- Подвал иерархии
// -- Общий итог заголовок
// -- Общий итог подвал
// - макет Таблицы
// -- макет Поля
// -- макет Ресурса
МакетШапка = Макеты[СтруктураМакета[СтруктураМакета.ВГраница() - 1].Макет];
ЯчейкиТаблицы = МакетШапка.Макет[0].Ячейки;
МодельМакетаОбласти = Общий.МодельМакетаОбластиКомпоновкиДанных(МакетШапка.Макет)
.СтрокаТаблицы()
;
Для Сч = 1 По ЯчейкиТаблицы.Количество() Цикл
МодельМакетаОбласти
.ЯчейкаТаблицы()
.ПолеОбласти(Строка(Сч))
.Оформление("ГоризонтальноеПоложение", ГоризонтальноеПоложение.Центр)
;
КонецЦикла;
В следующем примере демонстрируется поиск описания макета по имени группировки. Из найденного описания извлекается макет области и находится ячейка с параметром ОстаткиПоДням. Выражение найденного параметра изменено СКД и поэтому необходимо программно его вернуть в исходное состояние. Далее добавляется оформление ячейки в виде картинки, а для вывода картинки добавляется чисто программный параметр компоновки. В этот параметр будет помещена диаграмма по данным остатков по дням. Сформировать такую диаграмму можно уже при выводе, когда все данные будут подготовлены в параметре макета Остатки по дням для группировки по дням.
Еще в этом примере продемонстрирован прием создания нового макета ячеек, который затем можно будет использовать для вывода сообщения о наличии отрицательных остатков по дням.
Пример 5. Изменение существующего макета группировки и добавление нового макета для вывода сообщения об отрицательных остатках
///////////////////////////////////////////////////////
// Поиск макета области и макета описания группировки МестаХранения
// Группировку можно найти либо по имени, либо по составу полей. В нашем случае для группировки установлено
// имя МестоХранения.
ГруппировкиМакета = МодельМакетаКомпоновки.ГруппировкиМакета();
ГруппировкаМестоХранения = РаботаСМассивом.НайтиЭлемент(ГруппировкиМакета, "Элемент.Имя = 'МестоХранения'");
Если ГруппировкаМестоХранения <> Неопределено Тогда
// Если такая группировка есть в отчете, то добваить настройку
МакетОбластиМестоХранения = РаботаСМассивом.НайтиЭлемент(ГруппировкаМестоХранения.Макет.ПодчиненныеЭлементы, "Элемент.Тип = 'МакетОбластиМакетаКомпоновкиДанных'");
МакетМестоХранения = Макеты[МакетОбластиМестоХранения.Макет];
// Поиск имени параметра вывода остатков по дням. Имя параметра закодировано в виде П#, найти можно либо по порядку, либо по выражению
ИмяПараметраОстаткиПоДням = МакетОбластиМестоХранения.Параметры[1].Имя;// 1-й параметр - это представление места хранения, 2-й - остатки по дням
ПараметрМакетаОстаткиПоДням = МакетМестоХранения.Параметры[ИмяПараметраОстаткиПоДням];
// Переопределение выражения параметра остатки по дням. СКД добавляет к выражению функцию Представление(...), а здесь этого не нужно
ПараметрМакетаОстаткиПоДням.Выражение = "ВычислитьВыражениеСГруппировкойТаблицаЗначений(""НаборДанных.ПериодДень КАК ПериодДень, Сумма(НаборДанных.КоличествоКонечныйОстаток) КАК Остаток"", ""НаборДанных.ПериодДень"")";
ЯчейкиТаблицы = МакетМестоХранения.Макет[0].Ячейки;
НайденнаяЯчейкаОстаткиПоДням = Неопределено;
ПараметрКомпоновкиОстаткиПоДням = Новый ПараметрКомпоновкиДанных(ИмяПараметраОстаткиПоДням);
Для Каждого Ячейка Из ЯчейкиТаблицы Цикл
Для Каждого ПолеОбласти Из Ячейка.Элементы Цикл
Если ПолеОбласти.Значение = ПараметрКомпоновкиОстаткиПоДням Тогда
НайденнаяЯчейкаОстаткиПоДням = Ячейка;
Прервать;
КонецЕсли;
КонецЦикла;
Если НайденнаяЯчейкаОстаткиПоДням <> Неопределено Тогда
Прервать;
КонецЕсли;
КонецЦикла;
НайденнаяЯчейкаОстаткиПоДням.Элементы.Очистить();
НайденнаяЯчейкаОстаткиПоДням.Оформление.УстановитьЗначениеПараметра(Новый ПараметрКомпоновкиДанных("Картинка"), ПараметрКомпоновкиОстаткиПоДням);
// Добавление нового макета для вывода сообщения об отрицательных остатках
ОписаниеМакетаОбластиМакетаКомпоновкиДанных = Макеты.Добавить();
ОписаниеМакетаОбластиМакетаКомпоновкиДанных.Имя = "МакетСообщениеОбОтрицательныхОстатках";
МодельМакетаОбласти = Общий.МодельМакетаОбластиКомпоновкиДанных()
.СтрокаТаблицы()
.ЯчейкаТаблицы()
.ПолеОбласти(Новый ПараметрКомпоновкиДанных("СообщениеОбОтрицательныхОстатках"))
.Оформление("Отступ", 1)
.Оформление("ЦветТекста", WebЦвета.Белый)
.Оформление("ЦветФона", WebЦвета.ОранжевоКрасный)
.ЯчейкаТаблицы()
.ЯчейкаТаблицы()
.ЯчейкаТаблицы()
.ЯчейкаТаблицы()
.ЯчейкаТаблицы()
.Объединять(1, 1, 1, 6)
;
ОписаниеМакетаОбластиМакетаКомпоновкиДанных.Макет = МодельМакетаОбласти.МакетОбласти;
КонецЕсли;
Отчет доступен в демо базе «Демо остатки по местам хранения по дням». Весь программный код помещен в обработчик ПриКомпоновкеРезультата.
Демо остатки по местам хранения по дням
В СКД макеты области имеют собственный тип, отличный от используемых для описания табличного документа. Порядок работы с макетами областей существенно отличается от работы с табличным документом, ячейками табличного документа и областями табличного документа.
Макет области состоит из строк, ячеек, полей, оформления ячейки, оформления поля (используется только свойство формат).
Модель макета области отражает структуру макета: строки, ячейки, поля.
Пример 6. Описание области макета: две строки по 3 ячейки объединенные в одну прямоугольную область
МодельМакетаОбласти
.СтрокаТаблицы()
.ЯчейкаТаблицы()
.ПолеОбласти(Новый ПараметрКомпоновкиДанных("П1"))
.Оформление("ВертикальныйУровень", Уровень)
.Оформление("Отступ", Уровень)
.Ширина(10)
.ЯчейкаТаблицы()
.Ширина(5)
.ЯчейкаТаблицы()
.СтрокаТаблицы()
.ЯчейкаТаблицы()
.ЯчейкаТаблицы()
.ЯчейкаТаблицы()
.Объединять(1, 1, 2, 3)
;
Для вывода макета используется Процессор вывода. В следующем материале подробно рассмотрено устройство макета компоновки данных и его вывод: 1С СКД. Программное изменение макетов. На вход процессору необходимо подавать элементы результата, где передаются макеты областей с определенными параметрами вывода и расшифровки. Элементы результата формирует Процессор компоновки. Однако элементы результата вполне можно формировать и программно без процессора компоновки. Последнее позволяет вмешиваться в процесс вывода результата: дополнять его или изменять перед подачей в процессор вывода.
Пример 7. Изменение вывода: подмена макета вывода для сообщения об отрицательных остатках, формирование картинки для вывода диаграммы остатков
// Вывод результата в отчет
ПроцессорКомпоновки = Новый ПроцессорКомпоновкиДанных;
ПроцессорКомпоновки.Инициализировать(МодельМакетаКомпоновки.МакетКомпоновкиДанных, , ДанныеРасшифровки);
ПроцессорВывода = Новый ПроцессорВыводаРезультатаКомпоновкиДанныхВТабличныйДокумент;
ПроцессорВывода.УстановитьДокумент(ДокументРезультат);
ПроцессорВывода.НачатьВывод();
Пока Истина Цикл
ЭлементРезультата = ПроцессорКомпоновки.Следующий();
Если ЭлементРезультата = Неопределено Тогда
Прервать;
КонецЕсли;
Если ЗначениеЗаполнено(МакетОбластиМестоХранения) И ЭлементРезультата.Макет = МакетОбластиМестоХранения.Макет Тогда
// Переопределение значения параметра в картинку диаграммы остатков по дням
ПараметрМакетаОстаткиПоДням = ЭлементРезультата.ЗначенияПараметров[ИмяПараметраОстаткиПоДням];
ТаблицаОстатков = ПараметрМакетаОстаткиПоДням.Значение;
ПараметрМакетаОстаткиПоДням.Значение = ПолучитьКартинкуДиаграммы(ТаблицаОстатков);
ПроцессорВывода.ВывестиЭлемент(ЭлементРезультата);
ОтрицательныеОстатки = РаботаСМассивом.Отобрать(ТаблицаОстатков, "Элемент.Остаток < 0");
Если ЗначениеЗаполнено(ОтрицательныеОстатки) Тогда
// Вывод макета с ошибкой об отрицательных остатках
ЭлементРезультатаСообщениеОбОтрицательныхОстатках = Новый ЭлементРезультатаКомпоновкиДанных;
ЭлементРезультатаСообщениеОбОтрицательныхОстатках.Макет = "МакетСообщениеОбОтрицательныхОстатках";
ЭлементРезультатаСообщениеОбОтрицательныхОстатках.ТипЭлемента = ТипЭлементаРезультатаКомпоновкиДанных.НачалоИКонец;
Дни = РаботаСМассивом.АТДМассив(ОтрицательныеОстатки)
.Отобразить("Формат(Элемент.ПериодДень, 'ДФ=dd.MM.yyyy;')")
.ВМассив()
;
Параметр = ЭлементРезультатаСообщениеОбОтрицательныхОстатках.ЗначенияПараметров.Добавить();
Параметр.Имя = "СообщениеОбОтрицательныхОстатках";
Параметр.Значение = СтрШаблон("Есть отрицательные остатки по дням: %1", СтрСоединить(Дни, ", "));
ПроцессорВывода.ВывестиЭлемент(ЭлементРезультатаСообщениеОбОтрицательныхОстатках);
КонецЕсли;
Продолжить;
КонецЕсли;
ПроцессорВывода.ВывестиЭлемент(ЭлементРезультата);
КонецЦикла;
ПроцессорВывода.ЗакончитьВывод();
Можно полностью результаты вывода генерировать программно. Пример такой обработки смотрите в обработке «_ДемоМодельМакетаОбласти». В обработке результат вывода создается программно. Макеты компоновки формируются для каждого результата отдельно.
Особенность макетов компоновки в том, что в элементе результата вывода, который подается на вход процессора вывода, можно ссылаться на имя выводимого макета. Само описание макета может быть как в составе текущего результата вывода, либо, как это делает Процессор компоновки, в первом результате для последующего указания только имени.
Как уже было написано выше, макет области не является приемником табличного документа. Подход работы с макетом области сильно отличается от работы с табличным документом. Фактически ваши знания по работе с табличным документом никак не пригодятся для работы с макетом области компоновки данных.
В тоже время описание макета области и табличного документа имеет много общего и почему здесь нет преемственности - для меня загадка. Большое сходство позволит в будущем расширить созданную модель макета области также и на табличный документ. Т.е. одна модель позволит описать одинаковым образом как макет области, так и табличный документ. Это позволит как упростить работу с этими похожими объектами, так и взаимно их дополнить (тут можно пофантазировать на тему вариантов использования табличный макетов для формирования макетов областей СКД или обратно - смоделировать работу процессора вывода СКД).
Пример работы с макетом области можно смотреть в обработке «Демо модель макета области».
Демо модель макета области
Основное назначение конструктора - по схеме сгенерировать код с использованием представленных моделей СКД, проверить выполнение, получить обратно схему (реверс), получить структуру макета в собранном виде с параметрами вывода и расшифровки.
Внешний вид конструктора модели отчета
Разработка проекта ведется в EDT с использованием платформы 8.3.23.
Подсистемы: KASL.Модели.МодельОтчета, KASLДемо.ДемоМодельОтчета
Зависимости: АТДМассив, МодельТекста, KASL.ОбщегоНазначения, БСП 3.1
Проект выложен на github: report-model, report-model-constructor.