Многие не понаслышке знают, что создание отчетов в Directum RX это одна из самых трудоемких задач.
Зачастую, тривиальная задача может превратиться в целое испытание из-за неверно выбранного подхода к заполнению отчета данными.
И тут резонно возникает вопрос "А какие подходы вообще есть!?"
Прежде чем ответить этот вопрос, давайте коротко разберем источники данных для отчета:
Справка Directum RX 3.5 по основным механизмам отчетов.
Подходят как для передачи данных в дизайнер отчета, так и в события "До выполнения", "После выполнения".
Поскольку параметры в основном используются для передачи примитивов и изредка сущностей, считается что с ними каши не сварить. Вероятно по это причине, в построении сложных отчетов они не используются, т.к. в базовых решениях нет ни одного отчета с выборкой полученной из параметров, а не из источников данных (Sungero_Connections или тип сущность).
Пример: Отчет "BarcodePageReport" расположенный в Sungero.Docflow.OfficialDocument.
Тут все просто, это обычный LINQ по типам сущностей.
Получили по нужным условиям сущности, подключили их к DataSource в отчете, вывели данные. Данные для фильтрации как раз можно передать в параметрах отчета, если это необходимо.
Загрузку всех связанных с сущностью полей можно считать как минусом, так и плюсом, но то что это влияет на скорость построения отчета это факт.
Несомненным плюсом является скорость разработки отчета. Подключили к примеру справочник сотрудников, отфильтровали и работаем. Красота!
Пример: Отчет "OutgoingDocumentsReport" расположенный в Sungero.RecordManagement.
Классические запросы к БД.
Тут и связи таблиц через JOIN, и объединения UNION, и прочие прелести SQL (MS и Postge).
Выборка данных для отчета ограничена лишь фантазией и навыками разработчика.
Ну и несомненный плюс в виде быстродействия, т.к. получить большой массив данных запросом намного быстрее, чем прикладным кодом (кривые запросы и индексы в расчет не берем).
Пример: Отчет "ApprovalSheetReport" расположенный в Sungero.Docflow.OfficialDocument (95% отчетов в Sungero как раз используют Sungero_Connection).
Казалось бы!?
Вот он идеальный кандидат. Подключай таблицы в Sungero_Connections и пользуйся, но дьявол как всегда кроется в деталях!
Есть ли вариант как-то упростить себе жизнь!? Ну хотя бы изредка!?
FastReport ведь весьма гибкий генератор отчетов и одну и ту же задачу можно решить даже не двумя разными подходами, а больше!
Давайте разберем один из таких примеров, а заодно немного затронем тему коллекций в параметрах, т.к. данных по ним на Club почти нет.
Требуется разработать отчет по исполнению поручения (форма отчета ниже).
Вызов отчета поместить на ленту карточки задачи "Задача на исполнение поручения" в группу "Отчеты".
Дополнительные требования:
- Формат страниц отчета А6;
- Поля: нижний отступ - 10 мм, остальные отступы - 5мм;
- Шрифт: Arial;
- Размер шрифта: 12 пунктов (рег. номер и рег. дата - 9 пунктов).
Все имена и роли изменены, любые совпадения с реальным отчетом случайны!
И так, перед нами постановка задачи на реализацию отчета по поручению для руководителя.
Если блоки 2, 5, 6 вопросов реализации не вызывают, то по остальным блокам есть разные варианты реализации.
Б) Скрывать логотип в зависимости от условия.
Думаю не нужно объяснять, что вариант А является далеко не самым оптимальным, поэтому без вопросов выбираем Б.
А) Использовать простой строковый параметр отчета, записать туда форматированный текст с HTML тегами и вывести в отчете;
Б) Подключить источник Sungero_Connections, написать запрос который будет выбирать нужных исполнителей;
В) Подключить источник типа сущность IEmployee и фильтровать его по заданным параметрам;
Г) Использовать строковый параметр отчета со свойством "IsCollection".
Вариант А ограничен длинной строки и в данном случае его лучше не применять, вариант Б довольно трудоемкий по реализации, а при варианте Г не понятно, как без бубнов подставлять (отв.) у основных исполнителей. В данном случае коллекция наше все, на мой взгляд.
Тут, в теории, по сути те же варианты, что и в блоке 3, за исключением варианта А, как самого неоптимального из-за возможных объемов текста. Вообще передача больших кусков данных в параметрах отчета это крайне сомнительное и плохое решение на мой взгляд.
Вариант Г отпадает по тем же причинам что и А, т.к. некоторые могут и поэму написать в тексте поручения (правда ограниченную 1000 символами) и когда отчет сломается это лишь вопрос времени.
Вариант Б мы в данном примере не используем, т.к. долго и трудоемко.
Остается подключить коллекцию поручения с исполнителями ActionItemParts, как источник данных. В случае необходимости, отчет можно будет быстро поправить, если на этапе проектирования что-то упустили.
A) Создать новый скрытый модуль и создать отчет в нем, без привязки к сущности задачи;
Б) Перекрыть задачу ActionItemExecutionTask (Задача на исполнение поручения) и создать отчет в ней.
Т.к. отчет логически относится к исполнению поручения и будет вызываться только из задачи, то правильнее выбрать вариант Б, да и создание лишних модулей ради одного отчета можно будет избежать.
Плюсом в отчете автоматически появится параметр Entity, который будет иметь интерфейс Sungero.RecordManagement.IActionItemExecutionTask.
Анализ вариантов реализации провели, можно выполнять поставленную задачу.
1) Перекроем задачу ActionItemExecutionTask и создадим отчет ResolutionReport.
2) Займемся параметрами отчета и источниками данных.
Остальные данные поручения мы будем получать из параметра Entity.
3) Займемся заполнением параметров отчета данными в событии отчета "До выполнения":
public override void BeforeExecute(Sungero.Reporting.Server.BeforeExecuteEventArgs e)
{
var task = ResolutionReport.Entity;
//Заполним рег № и дату регистрации, если в поручении есть документ
if (task.DocumentsGroup.OfficialDocuments.Any())
{
var document = task.DocumentsGroup.OfficialDocuments.First();
ResolutionReport.RegistrationNumber = string.Format("№ {0}", document.RegistrationNumber);
ResolutionReport.RegistrationDate = document.RegistrationDate;
}
//Заполним исполнителя или исполнителей поручения, если оно составное
if (task.IsCompoundActionItem.HasValue && task.IsCompoundActionItem.Value)
{
foreach (var item in task.ActionItemParts)
ResolutionReport.AssigneesName.Add(string.Format("{0} (отв.)", item.Assignee.Person.ShortName));
}
else
ResolutionReport.AssigneesName.Add(string.Format("{0} (отв.)", task.Assignee.Person.ShortName));
//Заполним соисполнителей поручения
foreach (var item in task.CoAssignees)
ResolutionReport.AssigneesName.Add(item.Assignee.Person.ShortName);
//Сотрудник выдавший поручение входит в роль Аудиторы ?
ResolutionReport.IsAuditors = task.AssignedBy.IncludedIn(Roles.Auditors);
}
Как видим код тут совсем не сложный.
Коллекция отчета ResolutionReport.AssigneesName без проблем заполняется, через Add().
Если при заполнении возникают ошибки, то используйте AddRange().
4) Для получения составных получений в источнике данных ActionItemParts перейдем к коду и напишем условие выборки данных:
public virtual IQueryable<finex.SolutionsForTests.IActionItemExecutionTaskActionItemParts> GetActionItemParts()
{
return finex.SolutionsForTests.ActionItemExecutionTasks
.GetAll(t => t.Equals(ResolutionReport.Entity))
.SelectMany(_ => _.ActionItemParts)
.Cast<finex.SolutionsForTests.IActionItemExecutionTaskActionItemParts>();
}
Т.к. при создании источника я "намеренно" выбрал тип из модуля finex.SolutionsForTests, а результат имеет интерфейс Sungero.RecordManagement, то нужно привести данные к перекрытой сущности через Cast<>().
5) Данные в отчете есть, можно приступать к визуальной составляющей. Настроим размер страниц и поля.
6) Настроим бэнды отчета.
Нам точно понадобятся бэнды "Заголовок отчета" и "Подвал отчета" для блоков 1, 2 и 6.
Для блоков 3, 4, 5 добавим 3 источника данных.
7) Для удобства дальнейшего повествования я накидал и подписал элементы отчета, чтобы можно было ссылаться на имена элементов в дереве отчета.
Так же добавил логотип в элемент LogoPicture.
8) Подключим источник данных ActionItemParts к бэнду ActionItemPartsData (бэнд данных тот что посередине).
Остальные бэнды данных останутся без источников.
9) Займемся наполнением элементов отчета кодом.
JobTitle - заполняем должностью сотрудника
[[Entity].AssignedBy.JobTitle]
ShortName - заполняем фамилией и инициалами из персоны
[[Entity].AssignedBy.Person.ShortName]
RegNum и RegDate - заполняем данными из соответствующих параметров
AssigneesText - заполняем данными из параметра AssigneesName. Для этого преобразуем коллекцию CollectionAdapter<string> в массив, чтобы потом преобразовать ее в строку с разделителями используя string.Join().
[string.Join("\n", [AssigneesName].ToArray())]
ActionItemPartsText - заполним пункты поручения из источника ActionItemParts.
Формат: <Фамилия И.О.> <текст личного поручения> до <инд. срок>
Если индивидуальный срок установлен, то выведем "до <инд. срок>", иначе ничего не выводим.
[ActionItemParts.Assignee.Person.ShortName] [ActionItemParts.ActionItemPart] [[ActionItemParts.Deadline].HasValue ? "до " + Format("{0:dd/MM/yyyy}", [ActionItemParts.Deadline]) : ""]
ActionItemText - тест поручения возьмем из самого поручения.
[[Entity].ActionItem]
DeadlineText - Если поручение составное, то возьмем "Общий срок" поручения, иначе возьмем "Срок".
[[Entity].IsCompoundActionItem == true ? [Entity].FinalDeadline : [Entity].Deadline]
LowerText - В подвал отчета выведем дату создания поручения в формате __.Месяц.Год
[Format("{0:__/MM/yyyy}", [Entity].Created)]
Отметки исполнителя ________________________________________________________________________________________________________________________________________
Итоговый результат выглядит так:
10) Скроем логотип, если автор поручения не входит в заданную роль.
Многие наверное замечали вкладку "Код" в нижней части дизайнера отчетов.
Т.к. не все знают как этой вкладкой пользоваться, мы будем этот пробел устранять.
Выделяем наш элемент LogoPicture с логотипом и в свойствах элемента решительно жмем на значок с молнией.
Нам нужно событие BeforePrint (До печати), жмем в него двойным кликом мыши и в классе ReportScript у нас появляемся пустой метод LogoPicture_BeforePrint
namespace FastReport
{
public class ReportScript
{
private void LogoPicture_BeforePrint(object sender, EventArgs e)
{
}
}
}
Для выполнения задуманного нам нужно:
1) Получить параметр отчета IsAuditors в методе LogoPicture_BeforePrint
2) Если он True, то скрыть логотип (LogoPicture) и пустой разделитель (SeparatorLogo), а остальные элементы заголовка сдвинуть вверх к началу страницы.
Для реализации напишем код:
private void LogoPicture_BeforePrint(object sender, EventArgs e)
{
//Получаем параметр IsAuditors из отчета
//если он True, то скроем логитип и сдвинем элементы формы вверх
if (!(Boolean)Report.GetParameterValue("IsAuditors"))
{
float logoHeight = LogoPicture.Height + SeparatorLogo.Height;
LogoPicture.Visible = false;
SeparatorLogo.Visible = false;
JobTitle.Top = (float)(JobTitle.Top - logoHeight);
ShortName.Top = (float)(ShortName.Top - logoHeight);
Line1.Top = (float)(Line1.Top - logoHeight);
Line2.Top = (float)(Line2.Top - logoHeight);
RegNum.Top = (float)(RegNum.Top - logoHeight);
RegDate.Top = (float)(RegDate.Top - logoHeight);
SeparatorHeader.Top = (float)(SeparatorHeader.Top - logoHeight);
}
}
Дело осталось за малым.
11) Настроим свойства бэндов и элементов.
ReportTitleLogo (заголовок отчета) - может расти, может сжиматься.
AssigneesData (Данные) - может расти, может сжиматься, может разрываться.
ActionItemPartsData (Данные) - может расти, может сжиматься, может разрываться.
ActionItemExecutionData (Данные) - может расти, может сжиматься, может разрываться.
ReportSummary (Подвал отчета) - Печать внизу страницы.
Элементы на странице настраиваем похожим образом:
- где текста может быть много ставим "Может расти";
- где текста может и не быть, ставим "Может сжиматься";
- где текст может перенестись на другую страницу, ставим "Может разрываться".
12) Создаем новое действие в поручении и выносим кнопку на форму задачи.
public virtual void ResolutionReportfinex(Sungero.Domain.Client.ExecuteActionArgs e)
{
var report = Reports.GetResolutionReport();
report.Entity = _obj;
report.Open();
}
Готово, можно проверять результат трудов!
Формируем тестовое поручение для проверки корректности работы отчета:
Добавляем автора поручения в роль Аудиторы и переформировываем отчет для проверки отображения логотипа:
Отлично, создадим еще одно поручение, но уже с соисполнителями:
Все работает как и ожидалось.
Логотип скрывается, бэнд с пунктами поручения сжимается при отсутствии данных.
С одной стороны пример отчета был не сложным в реализации, но это только с одной стороны.
Если опыта в FastReport мало или отчеты приходится делать редко, то даже такая разработка может превратиться в целый квест.
В данном примере я постарался затронуть некоторые интересные, на мой взгляд, моменты разработки отчетов, которые возможно, помогут сократить временные затраты или упростить логику.
В крайнем случае всегда можно воспользоваться Sungero_Connections и накидать пару тройку запросов к базе.
Приведенный пример не является единственно верным вариантом реализации и если у Вас есть замечания или дополнения, то пишите об этом в комментариях. Будет интересно.
Добрый день.
как в запросе корректно передать параметр для in
and wf.performer in @performerIds где @performerIds - (12,147,458) набор ИД - в таком варианте не работает, как и не работает если (@performerIds) где @performerIds - 12,147,458 набор ИД
Mikhail, Он и не будет работать, т.к. @performerIds это строковый параметр и вставляется он в запрос как строка.
На Вашем месте я бы просто заменил источник данных с Sungero_Connection на тип сущности.
Mikhail, вот здесь разбирался данный вопрос
https://club.directum.ru/question/364899
Авторизуйтесь, чтобы написать комментарий