Предисловие

Данный документ является руководством по созданию отчётов в системах, разработанных на платформе CUBA.

Целевая аудитория

Руководство предназначено для разработчиков и администраторов CUBA-приложений. Для успешной работы с генератором отчётов требуется как минимум понимание принципов работы реляционных баз данных и умение писать запросы на языке SQL. В некоторых случаях данные для отчётов удобнее извлекать с помощью языков JPQL или Groovy, поэтому знакомство с ними является полезным.

Дополнительные материалы

Настоящее Руководство, а также другая документация по платформе CUBA доступны по адресу https://www.cuba-platform.ru/documentation.

Ядром генератора отчётов является фреймворк YARG, распространяемый по свободной лицензии Apache 2.0. Более подробно о фреймворке YARG читайте в нашем блоге. Документация по фреймворку доступна по адресу https://github.com/cuba-platform/yarg/wiki.

Обратная связь

Если у Вас имеются предложения по улучшению данного руководства, мы будем рады принять ваши pull request’ы и issues в исходниках документации на GitHub. Если вы увидели ошибку или несоответствие в документе, пожалуйста, форкните репозиторий и исправьте проблему. Заранее спасибо!

1. Общие сведения

Генератор отчётов позволяет создавать шаблоны в привычных и доступных всем средах (Microsoft Office и LibreOffice/OpenOffice) и описывать источники данных в интерфейсе приложения, используя модель данных приложения, SQL, JPQL или исполняемые скрипты.

С помощью генератора отчётов вы сможете:

  • Быстро создавать шаблоны отчётов непосредственно из приложения с помощью пошагового мастера;

  • Формировать отчёты в DOC, DOCX, ODT, XLS, XLSX, HTML и произвольных текcтовых форматах;

  • Создавать XLS(X) отчёты сложной структуры – многоуровневые, с агрегацией данных или перекрестными таблицами;

  • Использовать графики и формулы в XLS(X) отчётах;

  • Конвертировать отчёты из офисных форматов или HTML в PDF.

2. Установка

Для добавления аддона в проект выполните следующие действия:

  1. Кликните дважды Add-ons в дереве проекта CUBA.

    addons
  2. Перейдите на вкладку Marketplace и найдите аддон Reporting.

    reporting addon
  3. Кликните Install и затем Apply & Close.

    addon install
  4. Кликните Continue в появившемся диалоговом окне.

    addon continue

Аддон, соответствующий используемой версии платформы, будет добавлен в проект.

3. Быстрый старт

В этой главе мы рассмотрим простые и наглядные примеры использования генератора отчётов. Большинство примеров основаны на тестовом приложении Библиотека, исходный код которого доступен на GitHub.

Смотрите также руководство Introduction to Reporting and Document generation, которое демонстрирует, как использовать возможности генератора отчетов в приложениях.

3.1. Настройка проекта

  1. Загрузите и распакуйте исходный репозиторий приложения Библиотека или клонируйте проект, используя git:

      git clone https://github.com/cuba-platform/sample-library-cuba7
  2. Откройте проект Библиотека, как описано в разделе Создание нового проекта в Руководстве пользователя CUBA Studio.

  3. Добавьте в проект аддон, как это описано в разделе Установка.

  4. Создайте базу данных на локальном сервере HyperSQL. В главном меню выберите CUBACreate database.

  5. Для запуска приложения кликните кнопку run_button рядом с выбранной конфигурацией CUBA Application на главной панели инструментов.

  6. В дереве проекта в секции Runs at…​ перейдите по ссылке.

  7. Авторизуйтесь в приложении Библиотека с помощью логина и пароля пользователя admin / admin.

Чтобы легко начать работу с генератором отчётов, воспользуйтесь Мастером – визуальным инструментом, который позволяет быстро создавать структуру данных и шаблон отчёта. Создав отчёт с помощью мастера, вы можете изнутри рассмотреть структуру наборов данных, понять, как задаются параметры отчёта, попробовать изменить шаблон или тип вывода, следуя инструкциям в последующих разделах руководства.

Для вызова мастера в экране Reports нажмите CreateUsing wizard.

reports wizard main
Рисунок 1. Вызов мастера отчёта

Мастер позволяет создавать отчёты трех типов:

  1. Отчёт по отдельному экземпляру сущности.

  2. Отчёт по списку экземпляров сущности.

  3. Отчёт по списку экземпляров сущности, выбранных при помощи запроса.

Создание отчёта состоит из трех этапов:

  1. Выбор основных свойств отчёта.

  2. Редактирование регионов отчёта.

  3. Сохранение отчёта.

Созданный отчёт можно доработать в редакторе и запустить либо через общий список отчётов, либо с помощью действий (ListPrintFormAction, EditorPrintFormAction и так далее).

3.2. Отчёт по экземпляру сущности

Допустим, что мы хотим получить сведения об отдельно взятом издании книги, то есть экземпляре сущности library$BookPublication.

Для этого запустите мастер создания отчётов и на первом этапе укажите детали отчёта:

  • Entity – сущность, по экземпляру которой будет создаваться отчёт – library$BookPublication.

  • Template type – формат шаблона, по которому будет создаваться отчёт – DOCX. Доступны также форматы XSLX, HTML, CSV и Chart.

  • Report name – имя, под которым будет сохранен отчёт – Publication details.

Затем выберите тип построения отчёта – Report for single entity. Этот тип построения означает, что отчёт будет создаваться по одной сущности.

single entity step 1
Рисунок 2. Создание отчёта по экземпляру сущности в мастере: шаг 1

После этого нажмите на кнопку Next и в отобразившемся окне выберите атрибуты сущности BookPublication и связанных с ней сущностей, которые будут включены в отчёт (Publication.Book.Name, Publication.Publisher.Name, Publication.Year и Publication.City.Name). Для этого выделите их в левой колонке и перенесите в правую колонку нажатием на кнопку attributes_selection_arrow или двойным кликом.

Атрибуты будут отображаться в отчёте в том порядке, в котором они выбраны на этом этапе. Для того чтобы изменить порядок отображения, перемещайте атрибуты при помощи кнопок attributes_selection_up/attributes_selection_down.

single entity attributes
Рисунок 3. Создание отчёта по экземпляру сущности в мастере: выбор атрибутов

После нажатия на кнопку ОК произойдет переход ко второму этапу – редактированию регионов отчёта.

Отобразившийся экран содержит список именованных регионов – полос шаблона отчёта, в которых будут отображаться данные. Мастер позволяет добавить в шаблон несколько регионов, позволяющих отображать разные наборы данных в виде простого текста.

При необходимости набор атрибутов сущности, загружаемых в регион шаблона отчёта, можно отредактировать, нажав на ссылку со списком атрибутов в описании региона. Также можно добавить новый регион, нажав на кнопку Add simple region.

Если в сущности есть атрибуты-коллекции, появится также кнопка Add tabulated region, позволяющая добавить регион для отображения данных в виде таблицы.

В обоих случаях отобразится окно выбора атрибутов сущности library$BookPublication, позволяющее добавить или удалить атрибуты из набора.

single entity step 2
Рисунок 4. Создание отчёта по экземпляру сущности в мастере: шаг 2

На этом этапе посмотрите, как отчёт будет выглядеть с текущим набором данных, нажав на кнопку Run и выбрав экземпляр сущности library$BookPublication.

single entity test running
Рисунок 5. Окно предварительного запуска отчёта с текущим набором данных

После настройки регионов перейдите к третьему этапу – сохранению отчёта. На этом этапе просмотрите готовый шаблон отчёта, измените название и формат файла вывода, выбрав один из доступных типов.

single entity step 3
Рисунок 6. Создание отчёта по экзмепляру сущности в мастере: шаг 3

После нажатия на кнопку Save откроется стандартный редактор отчёта, в котором при необходимости произведите более тонкую настройку шаблона и структуры данных. После завершения редактирования нажмите Save and close в редакторе отчёта.

Отчёт будет добавлен в группу отчётов General в браузере отчётов, откуда его можно запустить кнопкой Run.

single entity reports list
Рисунок 7. Созданный отчёт в списке отчётов системы

Дополнительно мы можем сделать так, чтобы отчёт о деталях публикации запускался из браузера публикаций. Для этого в XML-дескрипторе bookpublication-browse.xml определите для таблицы публикаций стандартное действие ListPrintFormAction:

<groupTable id="bookPublicationsTable"
            width="100%"
            dataContainer="bookPublicationsDc">
    <actions>
        <action id="create" type="create"/>
                <!-- -->
        <action id="list" type="listPrintForm" caption="msg://printDetails"/>
    </actions>
    <columns>
        <!-- -->
    </columns>
    <rowsCount/>
    <buttonsPanel id="buttonsPanel"
                  alwaysVisible="true">
        <button id="createBtn" action="bookPublicationsTable.create"/>
        <!-- -->
        <button id="listBtn" action="bookPublicationsTable.list"/>
    </buttonsPanel>
</groupTable>

Далее свяжите отчёт с экраном просмотра сущности BookPublication. Откройте отчёт на редактирование, перейдите на вкладку Roles and Screens, выберите экран library$BookPublication.browse из выпадающего списка и добавьте его в таблицу ниже:

single entity screens
Рисунок 8. Окно редактирования отчёта: связывание отчёта с экраном

После этого запустите отчёт по любой публикации, выбирая ее в таблице и нажимая на кнопку Print details.

single entity running
Рисунок 9. Запуск отчёта из браузера публикаций

Готовый отчёт выглядит следующим образом:

single entity result
Рисунок 10. Пример выполненного отчёта

3.3. Отчёт по списку экземпляров сущности

Мастер отчётов позволяет создавать два вида отчётов по списку экземпляров сущности:

  1. Отчёт по вручную выбранным экземплярам определенной сущности;

  2. Отчёт по экземплярам сущности, удовлетворяющим некоторому запросу.

Рассмотрим первый тип отчёта. Допустим, что нам необходимо получить список экземпляров книг, находящихся в библиотеке (сущность library$BookInstance) с их названиями и отделами библиотеки, в которых они находятся.

На первом этапе укажите детали отчёта:

  • Entity – сущность, по списку экземпляров которой будет создаваться отчёт – library$BookInstance.

  • Template type – формат вывода отчёта – XSLX.

  • Report name – имя отчёта – Book items location.

Затем нужно выбрать тип построения отчёта – Report for list of entities и нажать Next.

list of entities step 1
Рисунок 11. Создание отчёта по списку экземпляров сущности в мастере: шаг 1

В соответствии с условием задачи, в окне выбора атрибутов необходимо выбрать BookItem.Publication.Book.Name, BookItem.LibraryDepartment.Name.

list of entities attributes
Рисунок 12. Создание отчёта по списку экзмепляров сущности в мастере: выбор атрибутов

Нажмите ОК для перехода ко второму этапу – редактированию регионов отчёта.

Шаблон отчёта по списку сущностей содержит только один регион, выводящий данные в виде таблицы. Добавлять новые регионы нельзя, но можно отредактировать набор данных в существующем, нажав на ссылку со списком атрибутов, либо удалить существующий регион и создать его заново, для чего наверху станет активной кнопка Add tabulated region.

В данном случае, менять ничего не нужно. Нажмите NextSave для сохранения отчёта. В редакторе отчётов отчёт будет выглядеть следующим образом:

list of entities editor
Рисунок 13. Редактор отчётов

После сохранения запустите отчёт через общий список отчётов.

Добавьте кнопку запуска отчёта в экран просмотра экземпляров книг, открывающийся из браузера публикаций по кнопке Show items. Для этого в XML-дескрипторе экрана bookinstance-browse.xml установите атрибут multiselect="true" у таблицы экземпляров книг (bookInstancesTable) и добавьте стандартное действие ListPrintFormAction:

<groupTable id="bookInstancesTable"
            multiselect="true"
                        ...>
    <actions>
        <action id="create" type="create"/>
                <!-- -->
        <action id="list" type="listPrintForm" caption="msg://printList"/>
    <columns>
        <!-- -->
    </columns>
    <rowsCount/>
    <buttonsPanel id="buttonsPanel"
                  alwaysVisible="true">
        <button id="createBtn" action="bookInstancesTable.create"/>
        <!-- -->
        <button id="listBtn" action="bookInstancesTable.list"/>
    </buttonsPanel>
</groupTable>

Далее свяжите отчёт Book items location с экраном просмотра сущности BookInstance. Откройте отчёт на редактирование, перейдите на вкладку Roles and Screens, выберите экран library$BookInstance.browse из выпадающего списка и добавьте его в таблицу ниже:

list of entities screens
Рисунок 14. Редактор отчётов: связывание отчёта с экраном

Теперь запустите отчёт из браузера экземпляров книг. Чтобы добавить экземпляры в отчет, сначала перейдите в экран Library → Accession Register и сгенерируйте экземпляры для одной из книг в выпадающем списке. Затем, перейдите в экран Library → Publications, выберите книгу, для которой сгенерировали экземпляры ранее, и нажмите кнопку Show items. В появившемся экране BookInstances выберите экземпляры для отчета в таблице и нажмите кнопку Print list. Опция Print selected экспортирует выбранные экземпляры, опция Print all – все экземпляры, выбранные текущим фильтром.

list of entities running
Рисунок 15. Окно выбора количества экземпляров сущности для отчёта

Готовый отчёт будет выглядеть следующим образом:

list of entities result
Рисунок 16. Пример выполненного отчёта

3.4. Отчёт по экземплярам сущности, отобранными при помощи запроса

Теперь рассмотрим второй тип отчёта – отчёт по списку сущностей, выбранных с помощью запроса. Для этого усложним задачу: отчёт должен содержать в себе список экземпляров книг (с именами и названиями отделов), добавленных после определенной даты.

Как и в предыдущем случае, начните с задания деталей отчёта:

  • Entity – сущность, по списку экземпляров которой будет создаваться отчёт – library$BookInstance.

  • Template type – формат вывода отчёта – XSLX.

  • Report name – имя отчёта – Recently added book items.

Затем выберите тип построения отчёта – Report for list of entities, selected by query.

query step 1
Рисунок 17. Создание отчёта по экземплярам сущности, отобранными при помощи запроса, в мастере: шаг 1

Выбранный тип отчёта позволит автоматически отобрать список сущностей, соответствующих запросу. Для того чтобы задать этот запрос, нажмите на ссылку Set query, появившуюся внизу.

Отобразится окно выбора условий запроса, которое во многом аналогично соответствующему окну универсального фильтра. Оно позволяет добавлять условия, объединять их в группы AND/OR и настраивать их параметры.

Для добавления нового условия запроса нажмите на кнопку Add. Отобразится окно выбора атрибутов сущности library$BookInstance, в котором выберите атрибут Created at. Атрибут будет добавлен в дерево условий запроса, и в панели справа отобразятся его свойства.

В панели свойств можно установить значение параметра по умолчанию. Если не планируется изменять логику отчёта, сделайте этот параметр скрытым условием, для этого установите флажок Hidden. В этом случае пользователям не будет предлагаться ввести этот параметр при запуске отчёта.

Выберите оператор запроса (>=).

query parameter
Рисунок 18. Окно выбора условий запроса

После сохранения запроса нажмите Next и перейдите к выбору атрибутов сущности library$BookInstance, которые будут включены в отчёт. В соответствии с условием задачи, перенесите в правую колонку атрибуты BookItem.Publication.Book.Name, BookItem.LibraryDepartment.Name. Нажмите ОК для перехода ко второму этапу.

query step 2
Рисунок 19. Создание отчёта по экземплярам сущности, отобранными при помощи запроса, в мастере: шаг 2

Нажмите NextSave для сохранения отчёта. В отобразившемся редакторе готовый отчёт будет выглядеть следующим образом:

query editor
Рисунок 20. Редактор отчётов

В редакторе можно усложнить структуру отчёта, добавив новые полосы и наборы данных, а также настроить дизайн шаблона отчёта, сделать локализацию отчёта или определить настройки прав доступа.

К примеру, перейдите на вкладку Parameters and Formats. В списке Parameters выберите и измените имя параметра запроса: Date вместо стандартного CreateTs1. Сохраните изменения и закройте редактор отчёта.

query parameter rename
Рисунок 21. Редактор параметра

Наконец, добавьте в экран просмотра списка отделов библиотеки кнопку Run report, позволяющую запустить данный отчёт.

Для этого добавьте новое стандартное действие RunReportAction в элементе actions и кнопку на панели buttonsPanel в XML-дескрипторе экрана librarydepartment-browse.xml:

<groupTable id="libraryDepartmentsTable"
            width="100%"
            dataContainer="libraryDepartmentsDc">
    <actions>
        <action id="create" type="create"/>
        <action id="edit" type="edit"/>
        <action id="remove" type="remove"/>
        <action id="run" type="runReport"/>
    </actions>
    <!-- -->
    <buttonsPanel id="buttonsPanel"
                  alwaysVisible="true">
        <!-- -->
        <button id="runReportBtn" action="libraryDepartmentsTable.run"/>
    </buttonsPanel>
</groupTable>

Для всех отчётов добавьте экран library$LibraryDepartment.browse к списку экранов на вкладке Roles and Screens редактора отчётов, как мы делали в предыдущих примерах.

В браузере отделов библиотеки появится кнопка Run report, по нажатию на которую открывается список доступных в системе отчётов. Для того чтобы запустить наш отчёт, выберите в списке Recently added book items, укажите дату и нажмите Run report.

query running
Рисунок 22. Окно выбора отчёта

Готовый отчёт выглядит следующим образом:

query result
Рисунок 23. Пример выполненного отчёта

3.5. Создание отчёта с шаблоном-диаграммой

С помощью мастера можно создавать отчёты с выводом в виде диаграммы так же, как и с другим шаблоном. Единственная разница заключается в том, что придется дополнительно настраивать конфигурацию диаграммы после завершения всех шагов в мастере.

Этот пример основан на приложении petclinic, исходный код которого доступен на GitHub.

  1. Добавьте в проект аддон Charts в окне CUBA Add-Ons, как это описано в документации по Studio.

  2. Начните создавать отчёт так же, как описано в предыдущих главах данного раздела.

    chart wizard
    Рисунок 24. Создание отчёта с шаблоном-диаграммой в мастере: шаг 1
  3. Для шаблонов-диаграмм выберите один или несколько исчисляемых атрибутов сущностей, они потребуются для оси значений диаграммы.

    chart wizard 2
    Рисунок 25. Создание отчёта с шаблоном-диаграммой в мастере: выбор атрибутов
  4. Выполните следующий шаг в мастере.

    chart wizard 3
    Рисунок 26. Создание отчёта с шаблоном-диаграммой в мастере: шаг 2
  5. На последнем шаге мастера выберите тип диаграммы – Pie или Serial – и сохраните отчёт.

    chart wizard 4
    Рисунок 27. Создание отчёта с шаблоном-диаграммой в мастере: шаг 3
  6. В завершение, настройте конфигурацию диаграммы на вкладке Templates редактора отчёта.

    chart wizard 5
    Рисунок 28. Настройка конфигурации диаграммы

    Более подробно о создании и настройке диаграмм см. в Документации к дополнению Charts.

4. Создание отчётов

Создание отчёта в системе заключается в создании двух взаимосвязанных элементов: шаблона визуального представления и описания извлекаемых для отчёта данных. Шаблон создается в формате XLS(X), DOC(X), HTML внешними средствами, а описание данных отчёта производится в экране дизайнера отчётов.

Сгенерированный отчёт в зависимости от заданных при описании параметров исходного шаблона может быть выдан в форматах PDF, XLS(X), CSV, DOC(X), HTML, Chart, Table или Pivot table.

Структура данных, выводимых отчётом, может быть либо описана в дизайнере отчёта путем создания полос, запросов и других элементов, либо запрограммирована в классе Java, реализующем специальный интерфейс. Отчёт принимает параметры от пользователя или из вызывающего кода. Для отчёта можно задать, каким пользователям он доступен и в каких экранах системы он должен появляться.

Основные компоненты генератора отчётов приведены на следующей диаграмме:

reporting
Рисунок 29. Диаграмма компонентов генератора отчётов
  • YARG – фреймворк, являющийся ядром генератора отчётов.

  • Report Engine интегрирует фреймворк YARG в фреймворк CUBA и предоставляет дополнительную функциональность, такую как права доступа к отчётам и связь с экранами.

  • Report Designer – средство описания и хранения отчётов. Включает в себя инфраструктуру хранения описаний и шаблонов отчётов, экраны создания и управления отчётами.

  • Report – описатель структуры данных отчёта, включающий в себя Bands – полосы отчёта, и Datasets – наборы данных, выводимые в полосах.

  • Report Template – шаблон визуального представления отчёта.

4.1. Структура данных отчёта

Рассмотрим вкладку Report structure редактора отчёта.

report structure
Рисунок 30. Редактор отчётов: структура данных отчёта

В верхней части находятся поля ввода общих свойств отчёта:

  • Name – имя отчёта. Имя может быть локализовано на вкладке Localization.

  • Group – группа отчётов, применяется для группировки в общем списке браузера отчётов.

  • Default templateшаблон, по которому будет выводиться отчёт.

  • System code – необязательный код отчёта, по которому его можно идентифицировать в программном коде системы.

Основным элементом структуры данных отчёта является иерархия полос – Report bands.

Полоса отчёта характеризуется следующими параметрами:

  • Band name – уникальное в рамках отчёта имя полосы. Должно содержать только латинские буквы, цифры или символ подчеркивания. Кроме того, если имя полосы начинается с символов header, данные из этой полосы не будут отображаться при выводе в виде таблицы.

  • Orientation – ориентация полосы: Horizontal, Vertical или Crosstab. Горизонтальные полосы в выводимом отчёте копируются вниз, вертикальные – вправо, перекрестные – вправо и вниз в виде матрицы. Горизонтальные полосы могут содержать вложенные полосы.

  • Parent band – родительская полоса.

Каждая полоса включает в себя один или несколько наборов данных – Datasets. Наборы данных при выполнении отчёта представляют собой списки строк, а каждая строка – набор пар имя-значение. Полоса выводится в отчёт столько раз, сколько строк в ее самом длинном наборе данных. Имена полей указываются в шаблоне отчёта, и при выводе полосы имена заменяются на соответствующие значения. При описании наборов данных используются внешние параметры отчёта, а также поля других полос – это позволяет делать полосы связанными.

В каждом отчёте присутствует корневая полоса Root. В ней можно создавать наборы данных и ссылаться на их поля из других полос, однако использовать полосу Root в шаблоне нельзя.

Имя набора данных в колонке Dataset name не имеет значения и служит только для удобства пользователя.

Поле Link field объединяет данные из нескольких наборов внутри одной полосы. Его используют, когда невозможно получить полный набор данных в одном запросе или скрипте Groovy.

Далее рассмотрены возможные типы наборов данных.

4.1.1. Набор данных SQL

SQL – набор данных формируется выполнением SQL-запроса к базе данных. Поля результирующего набора запроса желательно снабдить алиасами с помощью оператора as. Для исключения возможного преобразования базой данных регистра символов алиасы желательно заключить в двойные кавычки:

select u.name as "userName", u.login as "userLogin"
from sec_user u

В запросе можно использовать входные параметры отчёта и поля родительских полос. К параметрам нужно обращаться по имени, заключенному в конструкцию ${}, например ${dateFrom}. К полям родительской полосы нужно обращаться аналогично, добавляя имя полосы перед именем поля: ${band1.field1}.

Пример SQL-запроса с параметром groupId, полученным из родительской полосы group, и внешним параметром active:

select u.name as "userName", u.login as "userLogin"
from sec_user u
where u.group_id = ${group.groupId}
    and u.active = ${active}
    and u.delete_ts is null

В запросы на SQL необходимо вручную включать условия фильтрации мягко удаленных записей.

По умолчанию SQL-запросы выполняются в основной базе данных. Если необходимо выполнить запрос в дополнительном хранилище данных, укажите его имя в поле Data store.

Препроцессор запросов

Если вам нужно изменять запросы SQL/JPQL динамически в зависимости от значений параметров отчёта, используйте предварительную обработку SQL. Препроцессор позволяет модифицировать SQL/JPQL-запросы с помощью Groovy. Для активации препроцессора установите флажок Preprocess query as Groovy template под редактором полосы. Результирующий запрос будет обработан шаблонизатором GStringTemplateEngine, в котором доступны:

  • параметры отчёта: ${<parameter_name>},

  • значения из родительских полос: ${<band_name>.<parameter_name>}.

Например, в зависимости от того, передан параметр запроса createTs2 или нет, вам необходимо выбирать, какое из условий использовать в запросе: e.create_ts < ${createTs2} или e.create_ts < current_timestamp.

В этом случае запрос выглядит так:

select e.create_ts, e.id, e.vin from ref_car e
where
e.create_ts >= \${createTs1}
and
<% out << (createTs2 != null  ? 'e.create_ts < ${createTs2}' : 'e.create_ts < current_timestamp')%>

Теперь, если параметр createTs2 не передан, исходный запрос преобразуется в следующий:

select e.create_ts, e.id, e.vin from ref_car e
where
e.create_ts >= \${createTs1}
and
e.create_ts < current_timestamp

Если же createTs2 передан, для формирования полосы используется следующий результирующий запрос:

select e.create_ts, e.id, e.vin from ref_car e
where
e.create_ts >= \${createTs1}
and
e.create_ts < ${createTs2}

4.1.2. Набор данных JPQL

JPQL – набор данных формируется выполнением JPQL-запроса к базе данных. Поля результирующего набора запроса необходимо снабдить алиасами с помощью оператора as. В JPQL-запросе можно использовать входные параметры отчёта и поля родительских полос аналогично описанному для SQL-запроса.

Пример JPQL-запроса с параметром groupId, полученным из родительской полосы group, и внешним параметром active:

select u.name as userName, u.login as userLogin
from sec$User u
where u.group.id = ${group.groupId}
    and u.active = ${active}

Запросы на JPQL автоматически поддерживают мягкое удаление и возвращают только неудаленные записи.

Также вы можете использовать препроцессор запросов, установив флажок Preprocess query as Groovy template под редактором полосы.

По умолчанию JPQL-запросы выполняются по сущностям основной базы данных. Если необходимо выполнить запрос к сущностям дополнительного хранилища, укажите его имя в поле Data store.

4.1.3. Набор данных Groovy

Groovy – набор данных формируется выполнением Groovy-скрипта. Скрипт возвращает объект типа List<Map<String, Object>>. Элемент этого списка, то есть объект типа Map<String, Object> соответствует одной записи набора данных.

В скрипт передаются следующие объекты:

  • dataManager – объект типа com.haulmont.cuba.core.global.DataManager, предоставляющий CRUD-функциональность для работы с персистентными хранилищами данных. Например:

    def book = dataManager.load(Book.class).id(bookId).view("book.edit").one;
  • metadata – объект типа com.haulmont.cuba.core.global.Metadata, позволяющий обращаться к метаданным приложения, например:

    def metaClass = metadata.getClassNN('sec$User')
  • params – мэп внешних параметров отчёта. Пример получения значения параметра:

    def active = params['active']
  • parentBand – родительская полоса в виде объекта типа com.haulmont.yarg.structure.BandData. Через этот объект методом getParameterValue() можно получить значение поля родительской полосы, например:

    def groupId = parentBand.getParameterValue('groupId')
  • persistence – объект типа com.haulmont.cuba.core.Persistence, позволяющий получать источники данных (datasources).

    По умолчанию используется основное хранилище данных. Для работы с дополнительным хранилищем данных, его имя нужно указать в параметре метода getDataSource():

    def sql = new Sql(persistence.getDataSource('myStore'))
    def rows = sql.rows('select e.name from SEC_GROUP e')
  • security – объект типа com.haulmont.cuba.core.global.Security, используемый для проверки прав пользователя на доступ к различным объектам системы. Пример использования:

    if (security.isEntityOpPermitted(Book.class, EntityOp.READ) {
        ...
    }
  • timeSource – объект типа com.haulmont.cuba.core.global.TimeSource, используемый для получения текущего времени, например:

    def currentDate = timeSource.currentTimestamp()
  • transactional – метод, принимающий на вход замыкание, которое нужно выполнить в новой транзакции. Параметром замыкания становится текущий EntityManager. Пример использования:

    transactional { em ->
        def query = em.createQuery('select g from sec$Group g')
        ...
    }

    Пример Groovy-скрипта извлечения пользователей по группе, выводимой в родительской полосе и по внешнему параметру active:

    def result = []
    transactional { em ->
        def query = em.createQuery('select u from sec$User u where u.group.id = ?1 and u.active = ?2')
        query.setParameter(1, parentBand.getParameterValue('groupId'))
        query.setParameter(2, params['active'])
        query.resultList.each { user ->
            result.add(['userLogin': user.login, 'userName': user.name])
        }
    }
    return result
  • userSession – объект типа com.haulmont.cuba.security.global.UserSession, связанный с текущим пользователем системы, например:

    def user = userSession.currentOrSubstitutedUser
  • userSessionSource – объект типа com.haulmont.cuba.core.global.UserSessionSource, используемый для получения текущей сессии пользователя. Пример использования:

    def locale = userSessionSource.locale

Для обращения к любым бинам Spring среднего слоя используйте статические методы класса AppBeans, например:

def myService = com.haulmont.cuba.core.global.AppBeans.get('sample_MyService')

4.1.4. Набор данных Entity

Entity – набор данных состоит из одной строки и формируется по атрибутам одного экземпляра сущности и связанных с ним сущностей.

Источником данных является внешний параметр типа Entity, который должен быть описан на вкладке Parameters and Formats. Значение в поле Entity parameter name должно соответствовать имени параметра.

Шаблон отчёта должен содержать поля с именами атрибутов сущности. Атрибуты, используемые в шаблоне, необходимо указать в специальном окне, вызываемом кнопкой Entity attributes.

4.1.5. Набор данных List of entities

List of entities – набор данных формируется по списку экземпляров сущности.

Источником данных является внешний параметр типа List of entities, который должен быть описан на вкладке Parameters and Formats. Значение в поле Entity parameter name должно соответствовать имени параметра.

Шаблон отчёта должен содержать поля с именами атрибутов сущности. Атрибуты, используемые в шаблоне, необходимо указать в специальном окне, вызываемом кнопкой Select entity attributes.

4.1.6. Набор данных JSON

JSON – набор данных формируется из данных в формате JSON, которые могут быть получены из следующих источников:

  1. Groovy script

    Скрипт задается пользователем и должен возвращать JSON как строку, например:

    return '''
            {
              "items": [
                {
                  "name": "Java Concurrency in practice",
                  "price": 15000
                },
                {
                  "name": "Clear code",
                  "price": 13000
                },
                {
                  "name": "Scala in action",
                  "price": 12000
                }
              ]
            }
            '''
  2. URL

    Генератор отчётов запрашивает данные через URL с помощью GET HTTP-запроса, например:

    https://jsonplaceholder.typicode.com/users
  3. Parameter

    Внешний параметр отчёта с типом String, содержащий данные JSON, можно создать на вкладке Parameters and Formats редактора отчётов.

Извлечь необходимые данные из полученного дерева JSON можно запросом JsonPath. К примеру, выражение $.store.book[*] вернет все книги из дерева JSON в следующем примере:

{
    "store": {
    "book": [
            {
                "category": "reference",
                "author": "Nigel Rees",
                "title": "Sayings of the Century",
                "price": 8.95
            },
            {
                "category": "fiction",
                "author": "Evelyn Waugh",
                "title": "Sword of Honour",
                "price": 12.99,
                "isbn": "0-553-21311-3"
            }
    ],
    "bicycle": {
        "color": "red",
        "price": 19.95
    }
}
}

Более подробно о выражениях JsonPath можно посмотреть на http://goessner.net/articles/JsonPath/.

Поля, выводимые отчётом, имеющие типы данных Date, DateTime, Time, не поддерживают формат, определённый по правилам java.text.SimpleDateFormat. Для задания корректного формата вам следует написать скрипт на Groovy.

Для этого перейдите на вкладку Parameters and Formats редактора отчёта и откройте форму добавления формата. Например, для поля отчёта bookPublication.dateTime Groovy-скрипт будет иметь следующий вид:

import java.text.SimpleDateFormat

def simpleOldDateFormat = new SimpleDateFormat('yyyy-MM-dd HH:mm')
def simpleNewDateFormat = new SimpleDateFormat('dd/MM/yyyy HH:mm')
def oldDate = simpleOldDateFormat.parse(value)

return simpleNewDateFormat.format(oldDate)

4.2. Шаблон отчёта

Для одного отчёта на вкладке Templates редактора отчёта может быть создано несколько шаблонов, и один из них должен быть выбран как шаблон по умолчанию на вкладке Report structure.

Рассмотрим форму добавления шаблона:

report template
Рисунок 31. Редактор шаблона
  • Template code – код шаблона для его идентификации.

  • Template file – файл шаблона, который загружается из файловой системы и сохраняется в базе данных вместе с описанием структуры отчёта.

  • Output type – тип вывода отчёта. Должен быть согласован с типом файла шаблона по правилам, описанным в секции Соответствие типа шаблона типу вывода.

  • Output name pattern – необязательное имя файла, которое будет использоваться для выгрузки готового отчёта. Паттерн имени файла может быть как постоянной строкой, так и содержать параметры отчёта в качестве переменных, например, ${header.authorName}.xlsx. Более сложные паттерны с несколькими параметрами и конкатенацией строк можно также создать скриптом в любой полосе в структуре отчёта, например, ${Root.title}.xlsx, где title является результатом следующего скрипта:

    [['title' : ('Report for '+params['author'].firstName+' '+params['author'].lastName)]]
  • Is custom – признак использования шаблона, определяемого собственной логикой, не предусмотренной стандартным форматтером.

  • Defined by – способ создания собственного шаблона: определяемый классом, скриптом или URL.

  • Custom definition – имя Java-класса с полным именем пакета, либо путь к скрипту Groovy, расположенному в модуле core, либо URL, используемый для создания собственного шаблона.

  • Is alterable output – признак, позволяющий выбирать тип вывода отчёта во время его выполнения.

    Если флажок установлен, во время выполнения отчёта в диалоговом окне появится меню выбора типа вывода. Если для данного отчёта загружено более одного шаблона, нужный шаблон можно также выбрать в диалоговом окне.

    report template alterable
    Рисунок 32. Окно ввода параметров отчёта

4.2.1. Шаблоны XLSX и XLS

Шаблоны XLSX и XLS создаются с помощью Microsoft Office или LibreOffice.

Для каждой полосы отчёта в шаблоне должен быть определен регион с именем полосы. К примеру, если отчёт содержит две полосы – Header и Data, то шаблон отчёта также должен содержать именованные регионы Header и Data. Именованные регионы создаются путем выделения нужного диапазона ячеек и ввода имени в поле в левом верхнем углу приложения. Для редактирования уже созданных именованных регионов в Microsoft Office используется команда меню FormulasName Manager, а в OpenOffice команда InsertNamesManage. И, напротив, каждый элемент шаблона, который необходимо отобразить, должен быть создан как полоса в структуре отчёта, даже если эта полоса будет пустой.

Полосы выводятся в том порядке, в котором заданы в структуре отчёта.

Полосы могут быть горизонтальными и вертикальными. В горизонтальных полосах именованные регионы будут заполняться вниз, в вертикальных – вправо. Горизонтальные полосы могут быть организованы в древовидную структуру и содержать вложенные (или дочерние) полосы. Поэтому для вложенных полос необходимо создавать именованные регионы непосредственно под регионами, соответствующими родительским полосам. Отрисовка полос в XLSX происходит по следующему алгоритму:

  • Создание первого ряда родительской полосы →

  • Создание всех дочерних полос первого ряда →

  • Создание следующего ряда родительской полосы.

Поля наборов данных полосы размечаются в шаблоне с помощью строк вида ${field_name}, где field_name – имя поля. Например:

report template xls
Рисунок 33. Пример отчёта XLS

В шаблон отчёта также можно включать переменные. Переменные вставляются в имя листа XLSX или его заголовки/подписи в следующем формате: ${<BandName>.<variableName>}.

Ячейки могут содержать форматирование и сразу несколько полей внутри. Для вывода картинок или формул их нужно целиком поместить в соответствующий именованный регион.

Формулы могут ссылаться на свою полосу или другие полосы в шаблоне. Чтобы формулы обрабатывались генератором отчётов, они должны принимать диапазон ячеек полосы или явные координаты ячеек, например, (A1*B1) или ($B:$B).

Для создания диаграммы в шаблоне Excel создайте пустую полосу в структуре отчёта и именованный регион с тем же названием в шаблоне отчёта. Далее создайте диаграмму в этом именованном регионе, затем выберите в контекстном меню диаграммы пункт "Выбрать данные", где укажите ссылку на необходимые полосы отчёта. Если данные для диаграммы находятся в непрерывном диапазоне ячеек, можно выбрать любую ячейку в этом диапазоне, так в диаграмму будут включены все данные в диапазоне. Если данные находятся в прерывном диапазоне, выберите несмежные ячейки или диапазоны (при этом область выделения должна быть в виде прямоугольника).

Конвертация XLSX в PDF и CSV

Для отчётов в формате XLSX поддерживается автоматическая конвертация в форматы CSV и PDF. Чтобы выбрать выходной тип файла PDF, установите OpenOffice/LibreOffice.

csv output
Рисунок 34. Редактор шаблона: конвертация XLSX в CSV

4.2.2. Шаблон CSV

Шаблоны CSV создаются с помощью Microsoft Office или LibreOffice.

Полосы в CSV-шаблоне должны быть горизонтальными, таким образом, именованные регионы будут заполняться вниз. Также полосы должны располагаться на первом уровне данных, то есть быть дочерними для Root. Во всем остальном при создании шаблонов CSV применяются те же принципы, что и для шаблонов XLS/XLSX.

csv template
Рисунок 35. Пример шаблона CSV
csv report
Рисунок 36. Результат отчёта CSV

Встроенный редактор

Вы можете редактировать шаблоны CSV на лету. Встроенный текстовый редактор в окне Template editor позволяет вносить изменения в отчёт без необходимости заново загружать файл шаблона.

csv report editor
Рисунок 37. Встроенный редактор CSV

4.2.3. Шаблоны DOCX, DOC и ODT

Шаблоны DOC и DOCX создаются с помощью Microsoft Office или OpenOffice / LibreOffice.

Шаблон данного типа включает в себя текст документа и опционально одну или несколько таблиц. В тексте документа выводятся данные из первых строк произвольных полос. В таблицу можно выводить произвольное количество строк некоторой полосы.

Для вывода поля в тексте документа необходимо использовать строку вида ${band_name.field_name}, где band_name – имя полосы, field_name – имя поля.

Для вывода данных в таблицу она должна быть привязана к некоторой полосе. Это делается путем указания в первой ячейке таблицы строки вида ##band=band_name, где band_name – имя полосы. Поля в таблице размечаются строками вида ${field_name}, где field_name – имя поля связанной с таблицей полосы. Для обращения к полям других полос в таблице используйте префикс с именем полосы, как это делается в полях текста документа. В одной ячейке таблицы можно выводить несколько полей.

Вложенные горизонтальные полосы в формате DOCX и DOC не поддерживаются. Если вложенные полосы необходимы, используйте XLS(X)-шаблоны.

Таблица должна содержать либо одну, либо две строки. Если строк в таблице две, то поля связанной полосы должны находиться во второй строке. В первую заносится маркер с именем связанной полосы и, при необходимости, статический текст и поля других полос.

Например, для вывода отчёта, состоящего из двух полос: Book и Authors, первая из которых выводит название и жанр книги, а вторая – список авторов этой книги, шаблон выглядит следующим образом:

report template doc
Рисунок 38. Пример отчёта DOC

Формат ячеек для шаблонов DOCX и DOC не поддерживается. Рекомендуется приводить данные к строке в запросе. Например, для значения

select e.year as "year"

можно выполнить следующее приведение:

select cast(e.year as varchar(4)) as "year"

4.2.4. Шаблон HTML

Шаблон HTML задается в файле c расширением .html в кодировке UTF-8 (без BOM). При создании HTML/CSS шаблона вы можете пользоваться библиотекой Flying Saucer, руководство по ее использованию находится по адресу http://flyingsaucerproject.github.io/flyingsaucer/r8/guide/users-guide-R8.html

Размер страницы, наличие и расположение хэдеров и футеров настраиваются с помощью CSS. Пример отчёта с повторяющимися блоками хэдера и футера доступен в разделе Примеры отчётов.

Существует два способа для размещения данных в шаблоне:

  • Использовать тэги FreeMarker.

  • Использовать шаблонизатор Groovy.

По умолчанию мастер отчётов создает HTML-шаблон с тэгами FreeMarker.

Для переключения между способами используйте переключатель Template type в редакторе шаблонов.

html template editor
Рисунок 39. Редактор HTML шаблона
Шаблонизатор Groovy

Шаблон отчёта HTML может быть предварительно обработан как шаблон Groovy с помощью шаблонизатора GStringTemplateEngine.

Шаблонизатор использует стиль JSP скриптов <% %> и синтаксис выражений <%= %>, или стиль выражений GString. Переменная out осуществляет вывод в файл, для которого используется шаблон. Таким образом, шаблон может использовать любой код на Groovy, если он правильно определен. GStringTemplateEngine имеет доступ к:

  • внешним параметрам отчёта: BandName.fields.ParamName;

  • полосам отчёта: BandName.bands.ChildBandName;

  • полям: BandName.fields.FieldName.

Для удобства можно использовать переменные:

<% def headerRow = Root.bands.HeaderRow %>
<p>Date: ${headerRow.fields.reportDate}</p>

Ниже приведен пример шаблона, который выводит отчёт для выбранного пользователя.

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ru">
    <head>
        <title> Report User </title>
        <style type="text/css">
 body {font-family: 'Charis SIL', sans-serif;} tbody tr {height:20px; min-height:20px}
        </style>
    </head>
    <body>
        <% def user = Root.bands.User %>
        <p>Login: ${user.fields.login.first()}</p>
        <p>Active: ${user.fields.active.first()}</p>
    </body>
</html>

Пример отчёта с использованием Groovy шаблонизатора доступен в разделе Примеры отчётов.

FreeMarker

Документация по FreeMarker находится по адресу https://freemarker.apache.org/docs/.

Модель документа FreeMarker имеет следующую структуру:

Band {
      bands [ bandName : [ band, .. ], .. ]
      fields [ fieldName : fieldValue, .. ]
}

Например, для доступа к полю name в полосе band в нулевой строке выборки используйте следующее выражение:

Root.bands.band[0].fields.name

Для удобства можно использовать переменные:

<#assign headerRow = Root.bands.Header[0]>
<p>Date: ${headerRow.fields.reportDate}</p>

Для получения локализованных значений можно использовать функции getMessage() и getMainMessage() :

  • getMessage() используется для получения локализаций enum или, если передано два строковых параметра (msgPack, key), для получения по ключу сообщения из указанного пакета сообщений:

    ${getMessage(order.status)}
  • getMainMessage(), в свою очередь, принимает один строковый параметр key и ищет по данному ключу сообщение в главном пакете сообщений:

    ${getMessage(outOfStockMessage)}

Пример шаблона для вывода отчёта, состоящего из двух полос: Book и Authors, первая из которых выводит название и жанр книги, а вторая – список авторов этой книги:

<!doctype html>
<html>
<head></head>
<body>
    <#assign book = Root.bands.Book[0] />
    <#assign authors = Root.bands.Authors />

    <p>Name: ${book.fields.name}</p>
    <p>Genre: ${book.fields.literatureType.name}</p>
    <table border="1" cellpadding="5" cellspacing="0" width="200">
        <thead>
        <tr>
            <td>First name</td>
            <td>Last name</td>
        </tr>
        </thead>
        <tbody>
        <#list authors as author>
            <tr>
                <td>${author.fields.firstName}</td>
                <td>${author.fields.lastName}</td>
            </tr>
        </#list>
        </tbody>
    </table>
</body>
</html>

Более сложный пример. Имеем структуру полос следующего вида:

Root {
    HeaderBand {
        query = return [[ "name" : "Column1" ],[ "name" : "Column2" ]]
    }
    Band1 {
        query = return [
            ["field1" : "Value 11", "field2" : "Value 12"],
            ["field1" : "Value 21" , "field2" : "Value 22"]
        ]
    }
    Band2 {
        query = return [[ "header" : "Header1" ], [ "header" : "Header2" ]]
        SubBand1 {
            query = return [["header" : 'SubHeader1'] , [ "header" : 'SubHeader2' ]]
        }
    }
}
  • Обращение к полю:

    <!doctype html>
    <html>
        <head>
            <title> Simple template </title>
        </head>
        <body>
        <#assign Tree1 = Root.bands.Band2>
            <h1> Header </h1>
            <p>
                ${Tree1[1].bands.SubBand1[0].fields.header}
            </p>
        </body>
    </html>
  • Список:

    <!doctype html>
    <html>
        <head>
            <title> List </title>
        </head>
        <body>
        <#assign Table1Header = Root.bands.HeaderBand>
    
            <#if Table1Header?has_content>
                <ol>
                    <#list Table1Header as header>
                        <li> ${header.fields.name} </li>
                    </#list>
                </ol>
            </#if>
        </body>
    </html>
  • Таблица:

    <!doctype html>
    <html>
    <head>
        <title> Table </title>
    </head>
    <body>
    <#assign Table1Header = Root.bands.HeaderBand>
        <#assign Table1 = Root.bands.Band1>
            <table border="1" cellpadding="5" cellspacing="0" width="200">
                <thead>
                <tr>
                    <#list Table1Header as header>
                        <td> ${header.fields.name} </td>
                    </#list>
                </tr>
                </thead>
                <tbody>
                <#list Table1 as row>
                    <tr>
                        <td>
                            ${row.fields.field1}
                        </td>
                        <td>
                            ${row.fields.field2}
                        </td>
                    </tr>
                </#list>
                </tbody>
            </table>
    </body>
    </html>
  • Многоуровневый список:

    <!doctype html>
    <html>
    <head>
        <title> Multi-level list </title>
    </head>
    <body>
    <#assign Tree1 = Root.bands.Band2>
        <ul>
            <#list Tree1 as item>
                <li>
                    <h2> ${item.fields.header} </h2>
                    <#if item.bands.SubBand1?has_content>
                        <ul>
                            <#list item.bands.SubBand1 as subitem>
                                <li>
                                    <h3> ${subitem.fields.header} </h3>
                                </li>
                            </#list>
                        </ul>
                    </#if>
                </li>
            </#list>
        </ul>
    </body>
    </html>
Добавление изображений

В настоящий момент генератор отчётов CUBA не позволяет вставлять изображения в HTML-отчёты так же, как это реализовано для шаблонов DOCX/XLSX. Тем не менее, изображения можно добавить, используя тег img со ссылкой на изображение в атрибуте src. Вставить изображения в HTML-шаблон можно одним из перечисленных ниже способов.

  • по URL

    Изображения хранятся на сервере Tomcat или другом хостинге. К примеру, для вставки изображения, хранящегося в deploy\tomcat\webapps\ROOT\images, используется следующий код:

<img src="http://localhost:8080/images/SomeImage.jpg" height="68" width="199" border="0" align="right"/>
  • с использованием Bitmap

    Ссылка на изображение в виде байтового массива вставляется в атрибут src. В этом случае удобно использовать переменные для атрибутов сущностей, являющихся ссылкой на FileDescriptor. Строку ByteArray можно вставить в атрибут и напрямую, однако мы не рекомендуем этот подход к использованию:

<img alt="SomePicture.png" src="data:image/png;base64,iVBORw0K ..... AcEP9PwxD0hNKK1FCAAAAAElFTkSuQmCC"/>
  • с использованием специальных префиксов:

    • С помощью префиксов core:// и web:// можно ссылаться на ресурсы изображений из web или core модулей без необходимости жестко кодировать webPort и webContextName в шаблоне. Например, изображение, размещенное в каталоге deploy\tomcat\webapps\app\VAADIN\images может быть вставлено как:

      <img src="web://VAADIN/images/SomeImage.jpg" height="68" width="199" border="0" align="right"/>
    • Префикс fs:// используется для вставки изображения по идентификатору объекта типа FileDescriptor. Например:

      <img src="fs://fede432a-4f5d-3bab-71a0-b98133759b0f" height="68" width="199" border="0" align="right"/>
Встроенный редактор

Вы можете редактировать шаблоны HTML на лету. Встроенный текстовый редактор в окне Template editor позволяет вносить изменения в отчёт без необходимости заново загружать файл шаблона.

html report editor
Рисунок 40. Встроенный редактор HTML
4.2.4.1. Преобразование HTML в PDF

Отчёты, имеющие формат шаблона HTML и формат вывода PDF, не всегда корректно отображают шрифты. Для решения этой проблемы добавьте в конфигурационный каталог блока Middleware (в стандартном варианте развертывания tomcat/conf/app-core) подкаталог cuba/fonts с необходимыми .ttf-шрифтами. Кроме того, можно использовать шрифты из операционной системы путем указания пути к ним в свойстве приложения reporting.fontsDir.

Для решения проблемы со шрифтами на сервере Ubuntu выполните следующее:

  • Установите пакет ttf-mscorefonts-installer:

    $ sudo apt-get install ttf-mscorefonts-installer

  • Установите свойство приложения reporting.fontsDir:

    reporting.fontsDir = /usr/share/fonts/truetype/msttcorefonts
  • В HTML-шаблонах используйте явное указание шрифтов, например так:

    <html>
    <head>
        <style type="text/css">
     * { font-family: Times New Roman; }
        </style>

Стоит также позаботиться об экранировании спецсимволов. Чтобы избежать ошибок при конвертации HTML в PDF, рекомендуется обернуть поля в HTML шаблоне в конструкцию <![CDATA[ ]]>:

<tr>
    <td> <![CDATA[${(row.fields('book_name'))!?string!}]]> </td>
    <td> <![CDATA[${(row.fields('author'))!?string!}]]> </td>
</tr>

4.2.5. Шаблон JasperReports

Генератор отчётов позволяет извлекать информацию из приложения и использовать для ее вывода шаблоны отчётов JasperReports. Язык разметки JasperReports будет обработан генератором отчётов, преобразуя шаблон в отчёт с выбранным типом вывода.

Создавайте шаблоны JRXML, как используя специальные утилиты JasperReports (такие, как Jaspersoft Studio), так и в текстовом редакторе. Каждой полосе отчёта, указанной в его структуре, должен соответствовать свой элемент band в файле шаблона, который, в свою очередь, должен быть помещен в одну из стандартных секций отчёта JasperReports (в терминологии JasperReports эти секции также называются bands): title, pageHeader, columnHeader, detail, и т.д.

Из данных со всех полос отчёта генератор формирует один источник данных: JRBandDataDataSource, содержащий данные в виде дерева с корневой полосой Root, и передает экземпляр CubaJRFunction в отчёт в качестве основного источника данных. Обратиться к источнику данных можно с помощью параметра REPORTING. Этот параметр объявлять в шаблоне не обязательно, он будет добавлен автоматически, однако если вы хотите компилировать отчёты в JasperReports IDE, объявите параметр явно.

Для параметра REPORTING доступны следующие два метода:

  • dataset – получает вложенный источник данных из основного источника, который можно использовать, например, в таблицах или подотчётах в качестве subDataset. Метод ищет среди наследников полосы Root полосу с указанным именем и создает новый источник данных только из этой полосы. Например:

    <subDataset name="Product">
            <field name="name" class="java.lang.String"/>
            <field name="price" class="java.lang.Long"/>
    </subDataset>
    ...
    <dataSourceExpression><![CDATA[$P{REPORTING}.dataset("Product")]]></dataSourceExpression>
  • bitmap – преобразует переданный массив байтов в ByteArrayInputStream, который можно использовать для вставки изображений в отчёт. Например:

<field name="Main.image" class="java.lang.Object"/> //image from DB as byte array
...
<imageExpression><![CDATA[$P{REPORTING}.bitmap($F{Main.image})]]></imageExpression>

К каждой полосе отчёта генератор обращается только один раз, поэтому если вам необходимо представить одни и те же данные в разных формах (к примеру, в виде таблицы и в виде графика) в одном отчёте, вам необходимо создать столько полос, сколько элементов band вы используете в шаблоне. Вложенные полосы не поддерживаются, для корректного отображения используйте только полосы, вложенные непосредственно в Root.

Вставить значения из источника данных в поле можно, используя следующий синтаксис: $F{<field name>}. Например:

<textField>
    <textFieldExpression><![CDATA[$F{library_department_name}]]></textFieldExpression>
</textField>

Пример отчёта, использующего шаблон JasperReports, доступен в разделе Примеры отчётов.

Если ваше приложение развернуто в the UberJAR, чтобы запустить JasperReports из UberJAR, сделайте следующее:

  • Скопируйте файлы jasperreports-.jar и yarg-.jar в каталог uber JAR;

  • Создайте файл jasperreports.properties в каталоге uber JAR;

  • Добавьте в него свойство net.sf.jasperreports.compiler.classpath с именами скопированных JAR-файлов в качестве значения, к примеру:

net.sf.jasperreports.compiler.classpath = jasperreports-6.9.0.jar;yarg-2.0-SNAPSHOT.jar

4.2.6. Шаблон, определяемый классом

Шаблоны, определяемые классом, используются в тех случаях, когда выбирать данные с помощью SQL, JPQL или Groovy слишком сложно или невозможно. Например, в случаях, когда отчёт представляет собой результат объединения нескольких других отчётов.

Расположите класс, определяющий шаблон, в модуле core и реализуйте интерфейс com.haulmont.yarg.formatters.CustomReport. В классе определите метод createReport(), возвращающий массив байтов и принимающий на вход параметры:

  • report – описатель отчёта типа com.haulmont.yarg.structure.Report.

  • rootBand – данные корневой полосы типа com.haulmont.yarg.structure.BandData.

  • params – мэп внешних параметров отчёта.

Ниже приведен пример шаблона, определяемого классом. Он формирует HTML-документ с названием выбранной в параметре отчёта книги:

package com.sample.library.report;

import com.haulmont.yarg.formatters.CustomReport;
import com.haulmont.yarg.structure.BandData;
import com.haulmont.yarg.structure.Report;
import com.sample.library.entity.Book;
import java.util.Map;

public class BookReport implements CustomReport {
    @Override
    public byte[] createReport(Report report, BandData rootBand, Map<String, Object> params) {
        Book book = (Book) params.get("book");
        String html = "<html><body>";
        html += "<p>Name: " + book.getName() + "</p>";
        html += "</body></html>";
        return html.getBytes();
    }
}

В редакторе шаблона установите флажок Is custom, затем выберите Class в поле Defined by и укажите имя Java-класса с полным именем пакета в поле Custom definition:

class defined template
Рисунок 41. Редактор шаблона, определяемого классом

4.2.7. Шаблон-диаграмма

Шаблон-диаграмма доступен, если проект приложения включает компонент charts. Результирующая диаграмма выводится в экране ReportsShow Charts веб-приложения.

Поддерживаются два типа диаграмм: круговая и серийная. Каждый тип имеет свой набор параметров, настраиваемый в экране редактирования шаблона.

chart template pie
Рисунок 42. Круговая диаграмма
  • Band name – полоса, предоставляющая данные для диаграммы.

  • Title field – поле, из которого будут взяты названия сегментов.

  • Value field – поле, из которого будут взяты значения сегментов.

  • Color field – поле, из которого будут взяты коды цветов сегментов. Код цвета должен быть в web формате. Если код цвета не предоставлен, он будет выбран автоматически.

  • Units – данный текст будет добавлен к значениям в легенде.

chart template serial
Рисунок 43. Серийная диаграмма
  • Band name – полоса, предоставляющая данные для диаграммы.

  • Category field – поле, из которого будут взяты названия категорий.

  • Category axis caption – заголовок для горизонтальной оси.

  • Value axis caption – заголовок для вертикальной оси.

  • Value axis units – данный текст будет добавлен к значениям.

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

  • Value field – поле, из которого будут взяты значения.

  • Type – вид отображения ряда.

  • Color field – поле, из которого будут взяты коды цветов сегментов. Код цвета должен быть в web формате. Если код цвета не предоставлен, он будет выбран автоматически.

4.2.8. Вывод отчёта в виде Pivot Table

Шаблон Pivot Table доступен, если проект приложения включает компонент charts. Больше информации о компоненте Pivot Table ищите в документации по Charts.

Шаблон Pivot Table используется только как дополнительный шаблон отчёта, поэтому он недоступен в мастере. Для использования этого шаблона перейдите на вкладку Templates готового отчёта, нажмите Create и выберите Pivot Table в поле формата вывода в редакторе нового шаблона отчёта. После этого настройте конфигурацию Pivot Table, как описано ниже.

Результирующая таблица выводится в экране ReportsShow Pivot Tables веб-приложения.

pivot template result
Рисунок 44. Пример отчёта в виде Pivot Table

При использовании шаблона Pivot Table генератор отчётов получает данные из полосы и представляет их в виде сводной таблицы с поддержкой функциональности drag-and-drop, агрегации и вывода итоговых значений. Для построения отчёта используется только одна полоса, вложенные полосы не поддерживаются.

Настройки отрисовки

На вкладке Renderer options укажите один или несколько рендереров (отрисовщиков), которые будут отображаться в списке доступных для выбора рендереров в UI, а также укажите рендерер по умолчанию.

pivot template renderer
Рисунок 45. Редактор шаблона для отчёта в виде Pivot Table
Настройки агрегации данных

На вкладке Aggregation options задается список агрегаторов для таблицы. Для агрегаторов доступны следующие атрибуты:

  • Mode – выбор из списка предопределенных функций;

  • Caption – локализуемое значение, которое будет отображаться в UI;

  • Custom function – если поле не пустое, вместо выбранного режима для агрегации будет использована заданная в поле функция JavaScript.

pivot template aggregation
Рисунок 46. Редактор шаблона для отчёта в виде Pivot Table: настройка агрегации данных
Используемые поля

Сводная таблица может отображать свойства из наборов данных всех типов. Для корректной обработки алиасы ссылочных атрибутов из наборов SQL, JPQL и Groovy не должны содержать точек, например, select u.name as "userName". Выбранные поля из этих наборов укажите в качестве атрибутов сводной таблицы на вкладке Properties options редактора шаблона:

  • Row, Column – коллекция ключ-значение атрибутов таблицы, которые будут использоваться в качестве строк и столбцов, где ключ – имя атрибута в источнике данных, а значение – его локализованное значение, используемое при отображении;

  • Aggregation – настройки для функции, которая будет использоваться для агрегирования значений в каждой ячейке;

  • Derived property – используется для добавления новых генерированных атрибутов к источнику данных таблицы. Представляет собой коллекцию ключ-значение, где ключ – имя генерированного атрибута, а значение – JavaScript функция для генерации значения этого атрибута.

pivot template properties
Рисунок 47. Редактор шаблона для отчёта в виде Pivot Table: настройка атрибутов
Пользовательские настройки
  • Filter function – JavaScript код, который будет использоваться в качестве функции фильтрации;

  • Sorters function – JavaScript код, который будет использоваться в качестве функции сортировки заголовков строк и столбцов;

  • Переопределяемые функции отрисовки, в зависимости от выбранного рендерера. В данный момент только два типа рендереров могут быть кастомизированы:

    • все виды heatmap. Можно задать JavaScript-функцию генерации цвета для ячеек;

    • все виды графиков. Можно задать размер графика.

pivot template custom properties
Рисунок 48. Редактор шаблона для отчёта в виде Pivot Table: пользовательские настройки

4.2.9. Вывод отчёта в виде таблицы

При выборе табличного вывода отчёта в мастере система автоматически создаёт шаблон. После запуска отчёта данные выводятся в таблицу на отдельном экране внутри самого приложения.

Для существующего отчёта шаблон можно создать и вручную. Для этого выберите Table в поле формата вывода в редакторе шаблона отчёта.

report table output
Рисунок 49. Редактор шаблона: формат вывода в виде таблицы

В колонке Band добавьте названия полос, которые вы хотите отобразить в результирующей таблице. Для каждой полосы задайте коллекцию ключ-значение, где ключ – имя атрибута в источнике данных, а значение – его локализованное название.

Генератор отчётов получит данные из полос и создаст для каждой из них, начиная с первого уровня иерархии, сортируемую таблицу.

Если в отчёте использованы наборы данных SQL или JPQL, в таблице будут созданы колонки для всех полей. Для наборов данных Entity/List of Entities в таблице будут отображаться только колонки для выбранных в отчёте атрибутов.

Результирующая таблица будет отображаться на экране приложения Reports → Show Report Table. Кнопка Excel позволяет скачать таблицу с отчётом в формате файла Excel.

show report table
Рисунок 50. Пример выполненного отчёта в виде таблицы

4.2.10. Соответствие типа шаблона типу вывода

Шаблон / Вывод XLSX XLS CSV DOCX DOC PDF HTML Chart

XLSX

+

+

+ 1

+ 1

XLS

+

+ 1

CSV

+

DOCX

+

+ 2

+ 2

DOC

+

+ 1

HTML

+

+

Chart

+

JRXML

+

+

+

+

+

+

+

1 – для вывода требуется установка OpenOffice/LibreOffice.

2 – в зависимости от значения свойства приложения reporting.office.docx.useOfficeForDocumentConversion вывод осуществляется либо через OpenOffice/LibreOffice, либо без него. В последнем случае обеспечьте наличие нужных шрифтов, как описано в Преобразование HTML в PDF.

4.2.11. Выполнение отчета на внешнем сервере

Платформа предоставляет возможность выполнить отчет на внешнем сервере, например, Microsoft Reporting Services, по URL. Для подключения такой возможности выполните следующие шаги:

  • В редакторе шаблона установите флажок Is custom.

  • Выберите URL в поле Defined by.

  • В поле Custom definition укажите URL для отчета в MS Reporting Services. Параметры отчета можно передавать в URL в виде ${paramAlias}.

    exec report on external server
    Рисунок 51. Редактор шаблона
  • Укажите в свойстве приложения reporting.curl.path в файле app.properties модуля core путь до консольной утилиты curl.

4.3. Внешние параметры отчёта

Внешние параметры передаются извне при запуске отчёта и используются в наборах данных в качестве условий. Все внешние параметры становятся полями каждой полосы отчёта, поэтому их можно непосредственно использовать в шаблоне как поля наборов данных. Если какой-либо набор данных выводит одноименное поле, оно маскирует внешний параметр в данной полосе, и в отчёте используется значение поля набора данных.

Для описания внешних параметров служит вкладка Parameters and Formats редактора отчёта. Рассмотрим форму добавления параметра:

report parameter
Рисунок 52. Редактор отчётов: описание внешних параметров

Вкладка Properties:

  • Caption – имя параметра, как оно будет отображено в форме ввода параметров при старте отчёта.

  • Parameter alias – алиас параметра, который используется для обращения к нему в наборах данных.

  • Parameter type – тип параметра.

  • Hidden – признак того, что параметр является условием, скрытым от пользователей.

  • Required parameter? – признак того, что параметр должен быть обязательно передан в отчёт.

  • Entity – если указан тип параметра Entity или List of entities, то в данном поле выбирается тип сущности.

  • Entity selection screen – необязательный идентификатор экрана, который используется для выбора экземпляров сущности. Если экран не указан, выбор осуществляется через специальный общий для всех сущностей экран.

  • Enumeration – если указан тип параметра Enumeration, то в данном поле выбирается тип перечисления.

  • Default value – значение параметра, которое будет использовано по умолчанию, если пользователь не указал другого значения.

  • Default date(time) is current – если указан временной тип параметра (Date, Time или Date and time), этот флажок позволяет использовать текущее значение времени в качестве значения по умолчанию.

На вкладке Localization определяются названия параметра для различных локалей. Для этого в отдельных строках текстового поля введите пары имя_локали = имя_параметра, например:

ru = Книга
Трансформация входных параметров

На вкладке Transformation задаётся скрипт Groovy для преобразования параметра перед использованием его в отчёте.

Скрипт Groovy возвращает новое значение параметра. Текущее значение доступно по алиасу paramValue, мэп параметров можно получить с помощью алиаса params. Например:

return "%" + paramValue + "%"

Также используйте готовые трансформации, которые добавляют специальные условия к текстовым параметрам типа String:

  • Starts with.

  • Ends with.

  • Contains.

report parameter transformation
Рисунок 53. Редактор параметров: трансформация

На вкладке Validation задаётся Groovy-скрипт с некоторым условием для валидации значения параметра, см. ниже.

Валидация внешних параметров

Вы можете настроить как валидацию отдельных параметров, так и перекрестную валидацию параметров относительно друг друга.

  1. Разрешите валидацию одного параметра, установив флажок Validate на вкладке Validation редактора параметров. Логика валидации задается в скрипте Groovy. Скрипт проверяет значение параметра и вызывать метод invalid(), если введено некорректное значение. Метод отобразит пользователю уведомление с переданным сообщением об ошибке валидации.

    В скрипте доступны следующие переменные:

    • value – значение параметра, введенное пользователем.

    • dataManager – объект типа DataManager, предоставляющий CRUD-функциональность для работы с персистентными хранилищами данных.

    • metadata – объект типа Metadata, позволяющий обращаться к метаданным приложения.

    • security – объект типа Security, используемый для проверки прав пользователя на доступ к различным объектам системы.

    • userSession – объект типа UserSession, связанный с текущим пользователем системы.

      report parameter validation
      Рисунок 54. Редактор параметров: валидация
  2. Включите перекрестную валидацию флажком Validate в разделе Cross parameters validation на вкладке Parameters and Formats. Логика валидации задается в скрипте Groovy. Скрипт проверяет, не противоречат ли друг другу значения введенных параметров, и вызывает метод invalid(), если набор значений некорректен. Метод отобразит пользователю уведомление с переданным сообщением об ошибке валидации.

    Кроме переменных, перечисленных выше, в скрипт передается переменная params, которая хранит набор значений параметров, введенный пользователем.

    cross parameter validation
    Рисунок 55. Редактор параметров: перекрестная валидация

4.4. Форматы значений полей

Для любого поля, выводимого отчётом, можно задать форматирование на вкладке Parameters and Formats редактора отчёта. Рассмотрим форму добавления формата:

report formatter
Рисунок 56. Форма добавления формата значений полей
  • Value name – имя поля отчёта с префиксом полосы, например Book.year.

  • Format string – формат поля. Для числовых значений формат задаётся по правилам java.text.DecimalFormat, для дат – java.text.SimpleDateFormat.

  • Флажок Groovy script – позволяет указать скрипт Groovy для форматирования параметра. С помощью алиаса value в скрипт передаётся текущее значение параметра, которое может быть отформатировано или преобразовано в нужный формат. Скрипт Groovy должен возвращать новое значение в виде строки.

Форматы позволяют вставлять в документ изображения и HTML-блоки.

  • Для вставки изображения значение поля должно быть строкой URL для доступа к нему, а в формате значения строка форматирования должна иметь вид: ${image:<Width>x<Height>}, например ${image:200x300}.

    Для изображений в формате FileDescriptor можно использовать формат ${imageFileId:WxH}, который принимает как FileDescriptor id, так и ссылку на экземпляр самого FileDecriptor.

  • Для вставки HTML-блока необходимо в поле возвращать HTML-разметку, а в формате значения выбрать ${html} в качестве строки форматирования. В выходном значении тэги верхнего уровня до <body> включительно могут быть опущены. При необходимости произойдет автоматическое дополнение недостающих тегов верхнего уровня. Кодировка всех блоков UTF-8. CSS и атрибут style не поддерживаются.

Также есть возможность задать свой формат значений. Для этого, не открывая список, введите свое значение и нажмите Enter. Также можно выбрать один формат из существующих, отредактировать его и нажать Enter. В обоих случаях значение сохранится.

4.5. Разграничение прав доступа к отчётам

На вкладке Roles and Screens редактора отчётов определяются права пользователей на доступ к отчёту, а также принадлежность отчёта экранам системы.

Если в списке ролей для отчёта указана хотя бы одна роль, то данный отчёт будет доступен только пользователям с этой ролью. Если ни одна роль не указана, отчёт доступен всем.

Имейте в виду, что в экране списка отчётов (пункт меню Reports→Reports) видны все отчёты системы, поскольку данный экран считается административным.

Список экранов позволяет определить, в каких экранах данный отчёт доступен при использовании действий RunReportAction, ListPrintFormAction, EditorPrintFormAction или ExecutionHistoryAction. Если ни один экран не указан, отчёт недоступен из всех экранов.

4.6. Локализация названия отчёта

Название отчёта можно локализовать, то есть в списке отчётов для запуска отображать название на языке, с которым пользователь вошел в систему. Для этого в редакторе отчёта перейдите на вкладку Localization и в отдельных строках текстового поля введите пары имя_локали = название_отчёта, например:

en = Books by author
ru = Книги по автору

5. Запуск отчётов

В данном разделе рассматриваются способы запуска созданных отчётов на выполнение.

5.1. Запуск из общего списка отчётов

Простейший способ запуска отчётов – из общего списка, доступного в экране ReportsRun Reports. Для этого у пользователя должно быть право на открытие данного экрана. В списке будут присутствовать все отчёты, доступные пользователю в соответствии с его ролью. Если для отчёта заданы внешние параметры, они будут запрошены при запуске отчёта в специальной форме.

5.2. Запуск отчётов из экранов

Отчёты можно запускать из произвольных экранов системы, используя специальные действия и связанные с ними кнопки или элементы контекстного меню компонентов. При этом кроме доступности отчёта в соответствии с ролью пользователя проверяется также его принадлежность данному экрану.

Рассмотрим типы действий и примеры их использования.

  • com.haulmont.reports.gui.actions.list.RunReportActionстандартное действие, отображающее список всех доступных отчётов. Действие определяется для компонента Button или для компонента-списка (Table, DataGrid и так далее).

    Пример декларативного объявления действия для GroupTable:

    <groupTable id="booksTable"
                width="100%"
                dataContainer="booksDc">
        <actions>
            <action id="create" type="create"/>
            <action id="edit" type="edit"/>
            <action id="remove" type="remove"/>
            <action id="run" type="runReport"/> (1)
        </actions>
        <columns>
            <!-- -->
        </columns>
        <rowsCount/>
        <buttonsPanel id="buttonsPanel"
                      alwaysVisible="true">
            <!-- -->
            <button id="runBtn" action="booksTable.run"/>
        </buttonsPanel>
    </groupTable>
    1 - в атрибуте type задаётся тип действия runReport, предоставляемый фреймворком.

    Пример программного создания действия для кнопки, объявленной в XML-дескрипторе экрана:

    @Inject
    protected Actions actions;
    
    @Inject
    protected Button runReportBtn;
    
    @Subscribe
    public void onInit(InitEvent event) {
        Action runReportAction = actions.create(RunReportAction.class, "runReport");
        runReportBtn.setAction(runReportAction);
    }

    При выполнении действия RunReportAction откроется модальное окно Run Reports, в котором будут отображаться отчёты, доступные для текущего экрана. При выборе пользователем отчёта из списка отображается форма ввода параметров (если они заданы), и отчёт запускается на исполнение.

  • com.haulmont.reports.gui.actions.list.ListPrintFormActionстандартное действие для печати отчётов для экземпляров сущностей, определённое для компонента-списка (Table, DataGrid и так далее).

    Действие отбирает только те отчёты, которые имеют внешний параметр типа Entity или List of entities, и тип сущности параметра совпадает с типом сущности, отображаемой компонентом-списком. Если в результате отбора доступен только один отчёт, он сразу запускается на исполнение. Если доступно несколько отчётов, их список предлагается пользователю.

    В отчёт передается значение внешнего параметра по следующим правилам:

    • Если параметр типа List of entities, то в него передаются выбранные в данный момент в компоненте-списке экземпляры сущности.

    • Если параметр типа Entity, и в компоненте-списке выбран один экземпляр (выделена одна строка), то в отчёт передается этот экземпляр.

    • Если параметр типа Entity, а в компоненте-списке выделено несколько строк, то отчёт исполняется несколько раз по числу выбранных экземпляров. После выполнения пользователю возвращается один ZIP архив, в котором находятся все сформированные отчёты.

    Пример декларативного объявления действия для GroupTable:

    <groupTable id="authorsTable"
                width="100%"
                dataContainer="authorsDc">
        <actions>
            <action id="create" type="create"/>
            <action id="edit" type="edit"/>
            <action id="remove" type="remove"/>
            <action id="list" type="listPrintForm"/> (1)
        </actions>
        <columns>
            <!-- -->
        </columns>
        <rowsCount/>
        <buttonsPanel id="buttonsPanel"
                      alwaysVisible="true">
            <!-- -->
            <button id="listBtn" action="authorsTable.list"/>
        </buttonsPanel>
    </groupTable>
    1 - в атрибуте type задаётся тип действия listPrintForm, предоставляемый фреймворком.

    Пример программного создания действия для кнопки, объявленной в XML-дескрипторе экрана:

    @Inject
    protected Actions actions;
    
    @Inject
    protected Button listPrintBtn;
    
    @Inject
    private GroupTable<Author> authorsTable;
    
    @Subscribe
    public void onInit(InitEvent event) {
        Action listPrintFormAction = actions.create(ListPrintFormAction.class, "listPrintFormAction");
        authorsTable.addAction(listPrintFormAction);
        listPrintBtn.setAction(listPrintFormAction);
    }

    Если в компоненте-списке не было выбрано ни одной сущности, то при выполнении действия ListPrintFormAction появится окно подтверждения:

    run actions listPrint confirmation
    Рисунок 57. Окно подтверждения

    После этого откроется модальное окно Run reports, в котором отображаются отчёты, относящиеся к текущему экрану. С этого экрана пользователь может запустить некоторый отчёт для выбранной сущности.

  • com.haulmont.reports.gui.actions.EditorPrintFormAction – действие, связанное с экраном редактирования экземпляра сущности. Действие отбирает только те отчёты, которые имеют внешний параметр типа Entity или List of entities, и тип сущности параметра совпадает с типом редактируемой сущности. Если в результате отбора доступен только один отчёт, он сразу запускается на выполнение. Если доступно несколько отчётов, их список предлагается пользователю.

    В отчёт передается значение внешнего параметра – экземпляр редактируемой сущности. Если тип параметра – List of entities, то передается список из одного элемента.

    Пример использования с кнопкой, размещенной рядом со стандартными кнопками OK и Cancel:

    • XML-дескриптор

      <layout expand="editActions" spacing="true">
          <!--...-->
          <hbox id="editActions" spacing="true">
              <button action="windowCommitAndClose"/>
              <button action="windowClose"/>
              <button id="reportButton" icon="PRINT"/>
          </hbox>
      </layout>
    • Контроллер

      @Inject
      private Button reportButton;
      
      @Subscribe
      public void onInit(InitEvent event) {
          reportButton.setAction(new EditorPrintFormAction(this,null));
      }
  • com.haulmont.reports.gui.actions.list.ExecutionHistoryActionстандартное действие для отображения истории исполнения отчёта. Действие определяется для компонента Button или для компонента-списка (Table, DataGrid и так далее).

    Для просмотра истории задайте свойству reporting.executionHistory.enabled значение true в экране Administration > Application Properties.

    Пример декларативного объявления действия для GroupTable:

    <groupTable id="authorsTable"
                width="100%"
                dataContainer="authorsDc">
        <actions>
            <action id="create" type="create"/>
            <action id="edit" type="edit"/>
            <action id="remove" type="remove"/>
            <action id="history" type="executionHistory"/> (1)
        </actions>
        <columns>
            <!-- -->
        </columns>
        <rowsCount/>
        <buttonsPanel id="buttonsPanel"
                      alwaysVisible="true">
            <!-- -->
            <button id="historyBtn" action="authorsTable.history"/>
        </buttonsPanel>
    </groupTable>
    1 - в атрибуте type задаётся тип действия executionHistory, предоставляемый фреймворком.

    Пример программного создания действия для кнопки, объявленной в XML-дескрипторе экрана:

    @Inject
    protected Actions actions;
    
    @Inject
    protected Button execHistoryBtn;
    
    @Subscribe
    public void onInit(InitEvent event) {
        Action execHistoryAction = actions.create(ExecutionHistoryAction.class, "execHistoryReport");
        execHistoryBtn.setAction(execHistoryAction);
    }

    При выполнении действия ExecutionHistoryAction откроется модальное окно Execution history, в котором будут отображаться отчёты, доступные для текущего экрана. При выборе пользователем отчёта из списка отображается экран просмотра истории исполнения выбранного отчёта. Если в списке отчётов ни один отчёт не был выбран, то в экране просмотра будет отображена история исполнения всех отчётов, связанных с экраном.

5.3. Отмена выполнения отчёта

Если процесс генерации отчёта выполняется как фоновая задача, он может быть прерван пользователем.

Чтобы добавить возможность отмены, определите свойство reporting.useBackgroundReportProcessing в экране Administration → Application Properties:

reporting.useBackgroundReportProcessing = true

Теперь при выполнении будет отображаться окно с progress bar и кнопкой Cancel:

run cancel

Также можно установить таймаут выполнения с помощью свойства reporting.backgroundReportProcessingTimeoutMs:

reporting.backgroundReportProcessingTimeoutMs = 30000

По истечении указанного таймаута задача будет прервана независимо от этапа ее выполнения, и пользователь получит сообщение об ошибке:

run cancel 2

Чтобы настроить отмену выполнения отчёта программно, используйте метод cancelReportExecution() интерфейса ReportService, который принимает идентификатор сессии пользователя и идентификатор выполняемого отчёта:

reportService.cancelReportExecution(userSessionId, report.getId());

6. История исполнения отчётов

Платформа предоставляет механизм управления историей исполнения отчётов со следующими возможностями:

  1. Сохранение истории исполнения для каждого отчёта. В истории администратор может найти информацию, как часто исполняется тот или иной отчёт, какая длительность исполнения отчёта, каким пользователем и когда был запущен отчёт, какие ошибки возникли при исполнении.

  2. Удаление устаревшей истории исполнения отчётов.

По умолчанию механизм сохранения истории не используется, но его можно включить, установив свойство reporting.executionHistory.enabled в значение true. Это можно сделать в экране Administration > Application Properties.

Экран просмотра истории считается административным, поэтому не добавлен в главное меню. Для просмотра истории перейдите в экран списка отчётов (пункт меню Reports>Reports) и нажмите на кнопку Execution history.

Если в списке отчётов был выбран какой-либо отчёт, то таблица в экране просмотра истории будет отфильтрована по выбранному отчёту. Если в списке отчётов ни один отчёт не был выбран, то в экране просмотра будет отображена история исполнения всех отчётов системы.

report execution history
Рисунок 58. The execution history screen

Флаг "Cancelled" означает, что пользователь запустил отчёт в фоновом режиме, а затем отменил его.

История исполнения записывается также для отчётов, которые еще не сохранены в БД, но запущены из редактора отчётов (по нажатию на кнопку Run).

Выходные документы

Механизм предусматривает возможность сохранения выходных документов – файлов результатов отчётов – в хранилище файлов. Эта функция использует дисковое пространство для хранения файлов; она настраивается отдельно и по умолчанию отключена. Для ее включения определите свойство reporting.executionHistory.saveOutputDocument в экране Administration > Application Properties:

reporting.executionHistory.saveOutputDocument = true

Теперь, если в таблице просмотра истории выбрана запись, кнопка Download document становится доступной. При нажатии на кнопку скачивается документ, представляющий собой файл результата отчёта.

Отчёты с типом вывода chart, pivot table и table не имеют результирующих файлов, поэтому история исполнения таких отчётов не сохраняет никаких документов.

Если вы вызываете запуск отчёта программно с помощью метода createAndSaveReport(), он сохраняет другую копию того же выходного документа в хранилище файлов. Эти два файла помещаются в хранилище независимо друг от друга.

Удаление истории

Чтобы регулярно удалять ненужные данные, вы можете настроить процедуру очистки истории исполнения.

  • Создайте и активируйте назначенное задание. Перейдите в экран Administration > Scheduled Tasks вашего приложения. Создайте новую задачу и установите ей следующие параметры:

    • Bean Name = reporting_ExecutionHistoryRecorder

    • Method Name = cleanupHistory()

    • Cron = nightly, например, 0 0 1 * * *

    • Singleton – да (этот параметр важен только при эксплуатации кластера серверов middleware)

      Сохраните задачу и нажмите на ней Activate.

    Если вы ранее не настраивали выполнение назначенных заданий для данного приложения, то на данном этапе ничего не произойдет – новая задача не начнет выполняться, пока вы не запустите весь механизм назначенных заданий с помощью свойства cuba.schedulingActive.

  • Настройте конфигурационные параметры:

    Когда история исполнения очищается, связанные выходные документы удаляются из хранилища файлов.

7. Примеры отчётов

Смотрите также руководство Introduction to Reporting and Document generation, которое демонстрирует, как использовать возможности генератора отчетов в приложениях.

7.1. Пример отчёта XLS

Рассмотрим устройство одного отчёта в приложении-примере Библиотека, исходный код которого доступен на GitHub.

Для использования отчётов в проекте, активируйте элемент reports в списке App components на экране редактирования свойств проекта (меню CUBAProject Properties) в CUBA Studio.

Для импорта отчёта откройте экран ReportsReports и нажмите кнопку Import. Выберите файл Reports.zip в корневом каталоге проекта. В таблице появятся два отчёта, один из которых – Books by author. Данный отчёт выводит список публикаций книг по автору, группируя их по названию книги и издателю. Формат вывода – XLS.

  1. Структура данных отчёта.

    sample1 structure
    Рисунок 59. Структура данных отчёта Books by author

    Рассмотрим полосы отчёта.

    • Полоса header – заголовок отчёта. Содержит набор данных с Groovy-скриптом, выводящим значения внешних параметров отчёта:

      [['authorName' : (params['author'].firstName + ' ' + params['author'].lastName)]]
    • Полоса book выводит книги путем выполнения следующего SQL-запроса:

      select b.name as book_name, b.id as book_id
      from library_book b
          join library_book_author_link ba on ba.book_id = b.id
          join library_author a on a.id = ba.author_id
      where a.id = ${author}

      В данном запросе используется внешний параметр отчёта – author. Параметр имеет тип Entity, однако в SQL-запросах его можно напрямую сравнивать с полями-идентификаторами сущностей, преобразование будет выполнено автоматически.

    • Вложенная в book полоса publisher выводит издателей книги путем выполнения следующего SQL-запроса:

      select p.name as publisher, bp.year, p.id as publisher_id
      from library_book_publication bp
          join library_publisher p on p.id = bp.publisher_id
      where bp.book_id = ${book.book_id}

      В данном запросе в качестве параметра используется поле родительской полосы – book_id. Таким образом осуществляется связь между родительской и дочерней полосами.

    • Вложенная в publisher полоса publication выводит издания книги путем выполнения следующего SQL-запроса:

      select ld.name as department, count(bi.id) as amount
      from library_book_instance bi
          join library_book_publication bp on bp.id = bi.book_publication_id
          join library_library_department ld on ld.id = bi.library_department_id
      where bp.publisher_id = ${publisher.publisher_id} and bp.book_id = ${book.book_id}
      group by ld.name

      В данном запросе в качестве параметров используются поля обоих родительских полос – book_id и publisher_id.

  2. Параметры отчёта.

    На вкладке Parameters and Formats объявлен один внешний параметр отчёта – Author:

    sample1 param
    Рисунок 60. Редактор внешнего параметра Author

    Этот параметр запрашивается у пользователя при запуске отчёта. Выбор автора производится через экран library$Author.browse, имеющийся в приложении.

  3. Шаблоны отчёта.

    На вкладке Templates определен один шаблон формата XLS, загруженный из файла BooksByAuthor.xls:

    sample1 template
    Рисунок 61. Редактор шаблона
  4. Локализация названия отчёта.

    На вкладке Localization задано название отчёта для русской локали:

    ru = Книги по автору

Вызовите отчёт на исполнение из общего списка в экране ReportsRun Reports.

7.2. Пример перекрестного отчёта

Для создания перекрестного, или матричного, отчёта выберите ориентацию полосы Crosstab на вкладке Report structure редактора отчётов. При выборе этой ориентации к полосе автоматически добавляются три набора данных:

  1. <band_name>_dynamic_header – данные из этого набора заполняют отчёт значениями слева направо, то есть он ведет себя, как вертикальная полоса с заголовками столбцов матрицы.

  2. <band_name>_master_data – данные из этого набора заполняют отчёт значениями сверху внизу, то есть он ведет себя, как горизонтальная полоса с заголовками строк матрицы.

  3. <band_name> – набор данных, названный так же, как полоса, в которой он создан. Этот набор содержит данные для заполнения ячеек матрицы.

Для этих наборов данных вы можете выбрать любой из доступных типов: SQL, JPQL, Groovy, и т.д.

Для примера создадим матричный отчёт для сущности Order из демо-приложения Sales со следующей структурой:

crosstab structure
  1. Структура данных отчёта.

    Рассмотрим полосы отчёта.

    • набор данных orders_dynamic_header возвращает список названий месяцев:

      orders_dynamic_header dataset
      import java.text.DateFormatSymbols
      
      List result = new ArrayList()
      DateFormatSymbols dateFormatSymbols = DateFormatSymbols.getInstance(Locale.ENGLISH)
      for (i in 0..dateFormatSymbols.months.length - 1) {
          result.add(["header_id" : i + 1, "month_name" : dateFormatSymbols.months[i]])
      }
      return result
    • набор данных orders_master_data возвращает имена и идентификаторы покупателей, выбранных пользователем в качестве внешнего параметра отчёта:

      orders_master_data dataset
      select name as name, id as customer_id
      from SALES_CUSTOMER
      where id in (${selected_customers})
    • набор данных orders генерирует данные для заполнения ячеек матрицы, то есть сумму всех заказов, сделанных конкретным покупателем в конкретном месяце. Он использует orders_master_data@customer_id (идентификатор покупателя) как вертикальную координату ячейки и orders_dynamic_header@header_id (название месяца) как горизонтальную координату, а затем заполняет ячейку суммой значений amount.

      В примере ниже мы использовали два дополнительных внешних параметра: start_date и end_date, которые определяют временной диапазон заказов. Мы рекомендуем использовать перекрестную валидацию значений введенных параметров, чтобы избежать ошибок, вызванных неправильным диапазоном дат.

      orders dataset
      select
              o.customer_id as orders_master_data@customer_id,
              month(o.date_) as orders_dynamic_header@header_id,
              sum(o.amount) as "amount"
      from sales_order o
      where o.date_ >= ${start_date} and o.date_ <= ${end_date}
      and o.customer_id in (${orders_master_data@customer_id})
      and month(o.date_) in (${orders_dynamic_header@header_id})
      group by o.customer_id, month(o.date_)
      order by o.customer_id, month(o.date_)
  2. Параметры отчёта.

    На вкладке Parameters and Formats объявлены внешние параметры отчёта – selected_customers, start_date, end_date:

    crosstab external params
    Рисунок 62. Внешние параметры отчёта

    Эти параметры запрашиваются у пользователя при запуске отчёта. Выбор покупателей производится через экран sales_Customer.browse, имеющийся в приложении.

  3. Шаблон отчёта.

    Теперь создадим шаблон отчёта, используя Microsoft Office или LibreOffice.

    Создайте в шаблоне именованные регионы для всех трех наборов данных полосы orders, а также, дополнительно, регион для заголовка столбца: <band_name>_header. В нашем случае это orders_header.

    Вот так будет выглядеть шаблон отчёта, выводящего по вертикали список имен Customers и горизонтально сумму Orders, сгруппированных по месяцам:

    crosstab template 2
    crosstab names regions

В результате, отчёт заполняется как вниз по вертикали, так и вправо по горизонтали агрегированными значениями суммы заказа для каждого покупателя в каждом месяце:

crosstab result

Если вы хотите добавить к получившейся таблице итоговые суммы, это необходимо делать в отдельных полосах и получать для них данные в отдельном запросе.

7.3. Пример отчёта JRXML

Этот пример также основан на тестовом приложении Library, исходный код которого доступен на GitHub.

Для использования отчётов в проекте активируйте элемент reports в списке App components на экране редактирования свойств проекта (меню CUBAProject Properties) в CUBA Studio.

Для импорта отчёта откройте экран ReportsReports и нажмите кнопку Import. Выберите файл Reports.zip в корневом каталоге проекта. В таблице появятся два отчёта, один из которых – Book availability in department. Данный отчёт выводит список публикаций книг, доступных в выбранном отделе; формат вывода по умолчанию – XLS. Теперь создадим новый шаблон JasperReports для этого отчёта.

  1. Структура данных отчёта.

    sample jasper
    Рисунок 63. Редактор отчёта: структура данных

    Рассмотрим полосы отчёта.

    • Полоса Header – заголовок отчёта. Она содержит набор данных с Groovy-скриптом, выводящим значение внешнего параметра отчёта:

      [['library_department_name' : params['library_department'].name]]
    • Полоса Data выводит список книг в выбранном отделе путем выполнения следующего Groovy-скрипта:

      import com.haulmont.cuba.core.global.AppBeans
      
      def persistence = AppBeans.get('cuba_Persistence')
      def tx = persistence.createTransaction()
      try {
          def result = []
      
          def em = persistence.getEntityManager()
          def ltList = em.createQuery('select lt from library$LiteratureType lt').getResultList()
          ltList.each { lt ->
              def count = em.createQuery(''' select count(bi) from library$BookInstance bi where bi.libraryDepartment = ?1 and bi.bookPublication.book.literatureType = ?2 ''')
                  .setParameter(1, params['library_department'])
                  .setParameter(2, lt)
                  .getSingleResult()
              def refCount = em.createQuery(''' select count(bi) from library$BookInstance bi where bi.libraryDepartment = ?1 and bi.bookPublication.book.literatureType = ?2 and bi.isReference = true''')
                  .setParameter(1, params['library_department'])
                  .setParameter(2, lt)
                  .getSingleResult()
      
              result.add(['literature_type_name': lt.name,
                  'books_instances_amount': count,
                  'reference_books_instances_amount': refCount])
          }
          return result
      } finally {
          tx.end()
      }

      В данном запросе используется внешний параметр отчёта – library_department. Параметр имеет тип Entity, однако его можно напрямую сравнивать с полями-идентификаторами сущностей, преобразование будет выполнено автоматически.

  2. Параметры отчёта.

    На вкладке Parameters and Formats объявлен один внешний параметр отчёта – Department:

    sample jasper 2
    Рисунок 64. Редактор параметра

    Этот параметр запрашивается у пользователя при запуске отчёта. Выбор автора производится через экран library$LibraryDepartment.browse, имеющийся в приложении.

  3. Шаблон отчёта.

    На вкладке Templates определен один шаблон формата XLS, загруженный из файла BookAvailability.xls.

    Создайте новый файл JRXML со следующим содержимым:

    BookAvailability.jrxml
    <?xml version="1.0" encoding="UTF-8"?>
    <!-- Created with Jaspersoft Studio version 6.4.0.final using JasperReports Library version 6.4.1 -->
    <jasperReport xmlns="http://jasperreports.sourceforge.net/jasperreports" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://jasperreports.sourceforge.net/jasperreports http://jasperreports.sourceforge.net/xsd/jasperreport.xsd" name="books" pageWidth="595" pageHeight="842" whenNoDataType="AllSectionsNoDetail" columnWidth="535" leftMargin="20" rightMargin="20" topMargin="20" bottomMargin="20">
        <property name="template.engine" value="tabular_template"/>
        <property name="com.jaspersoft.studio.data.defaultdataadapter" value="One Empty Record"/>
        <style name="Table_TH" mode="Opaque" backcolor="#066990">
     <box> <topPen lineWidth="0.5" lineColor="#000000"/> <bottomPen lineWidth="0.5" lineColor="#000000"/> </box>
        </style>
        <style name="Table_CH" mode="Opaque" forecolor="#FFFFFF" backcolor="#06618F" hTextAlign="Center" fontSize="12">
     <box> <topPen lineWidth="0.5" lineColor="#000000"/> <bottomPen lineWidth="0.5" lineColor="#000000"/> </box>
        </style>
        <style name="Table_TD" mode="Opaque" backcolor="#FFFFFF" hTextAlign="Center">
     <box> <topPen lineWidth="0.5" lineColor="#000000"/> <bottomPen lineWidth="0.5" lineColor="#000000"/> </box>
        </style>
        <subDataset name="Data">
            <field name="literature_type_name" class="java.lang.String"/>
            <field name="books_instances_amount" class="java.lang.Long"/>
            <field name="reference_books_instances_amount" class="java.lang.Long"/>
        </subDataset>
        <field name="library_department_name" class="java.lang.String"/>
        <title>
            <band height="72">
                <frame>
                    <reportElement mode="Opaque" x="-20" y="-20" width="595" height="92" backcolor="#006699"/>
                    <staticText>
                        <reportElement x="20" y="10" width="555" height="30" forecolor="#FFFFFF"/>
                        <textElement textAlignment="Center">
                            <font size="20" isBold="true"/>
                        </textElement>
                        <text><![CDATA[Book availability in department]]></text>
                    </staticText>
                    <textField>
                        <reportElement x="20" y="50" width="555" height="30" forecolor="#FFFFFF"/>
                        <box>
                            <pen lineWidth="1.0" lineColor="#FFFFFF"/>
                            <topPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
                            <leftPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
                            <bottomPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
                            <rightPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
                        </box>
                        <textElement textAlignment="Center" verticalAlignment="Middle">
                            <font fontName="SansSerif" size="20" isBold="true"/>
                        </textElement>
                        <textFieldExpression><![CDATA[$F{library_department_name}]]></textFieldExpression>
                    </textField>
                </frame>
            </band>
        </title>
        <detail>
            <band height="204">
                <componentElement>
                    <reportElement x="0" y="4" width="555" height="200" forecolor="#FFFFFF">
                        <property name="com.jaspersoft.studio.layout" value="com.jaspersoft.studio.editor.layout.VerticalRowLayout"/>
                        <property name="com.jaspersoft.studio.table.style.table_header" value="Table_TH"/>
                        <property name="com.jaspersoft.studio.table.style.column_header" value="Table_CH"/>
                        <property name="com.jaspersoft.studio.table.style.detail" value="Table_TD"/>
                        <property name="net.sf.jasperreports.export.headertoolbar.table.name" value=""/>
                        <property name="com.jaspersoft.studio.components.autoresize.proportional" value="true"/>
                    </reportElement>
                    <jr:table xmlns:jr="http://jasperreports.sourceforge.net/jasperreports/components" xsi:schemaLocation="http://jasperreports.sourceforge.net/jasperreports/components http://jasperreports.sourceforge.net/xsd/components.xsd">
                        <datasetRun subDataset="Data">
                            <dataSourceExpression><![CDATA[$P{REPORTING}.dataset("Data")]]></dataSourceExpression>
                        </datasetRun>
                        <jr:column width="188">
                            <jr:columnHeader style="Table_CH" height="30">
                                <staticText>
                                    <reportElement x="0" y="0" width="188" height="30" forecolor="#FFFFFF"/>
                                    <box>
                                        <pen lineColor="#FFFFFF"/>
                                    </box>
                                    <textElement textAlignment="Center" verticalAlignment="Middle">
                                        <font fontName="SansSerif" size="12" isBold="true"/>
                                    </textElement>
                                    <text><![CDATA[Literature Type]]></text>
                                </staticText>
                            </jr:columnHeader>
                            <jr:detailCell style="Table_TD" height="30">
                                <textField>
                                    <reportElement x="0" y="0" width="188" height="30"/>
                                    <textElement textAlignment="Center" verticalAlignment="Middle">
                                        <font fontName="SansSerif" size="12"/>
                                    </textElement>
                                    <textFieldExpression><![CDATA[$F{literature_type_name}]]></textFieldExpression>
                                </textField>
                            </jr:detailCell>
                        </jr:column>
                        <jr:column width="186">
                            <jr:columnHeader style="Table_CH" height="30">
                                <staticText>
                                    <reportElement x="0" y="0" width="186" height="30" forecolor="#FFFFFF"/>
                                    <textElement textAlignment="Center" verticalAlignment="Middle">
                                        <font fontName="SansSerif" size="12" isBold="true"/>
                                    </textElement>
                                    <text><![CDATA[Book Amount]]></text>
                                </staticText>
                            </jr:columnHeader>
                            <jr:detailCell style="Table_TD" height="30">
                                <textField>
                                    <reportElement x="0" y="0" width="186" height="30"/>
                                    <textElement textAlignment="Center" verticalAlignment="Middle">
                                        <font size="12"/>
                                    </textElement>
                                    <textFieldExpression><![CDATA[$F{books_instances_amount}]]></textFieldExpression>
                                </textField>
                            </jr:detailCell>
                        </jr:column>
                        <jr:column width="181">
                            <jr:columnHeader style="Table_CH" height="30">
                                <staticText>
                                    <reportElement x="0" y="0" width="181" height="30" forecolor="#FFFFFF"/>
                                    <textElement textAlignment="Center" verticalAlignment="Middle">
                                        <font fontName="SansSerif" size="12" isBold="true"/>
                                    </textElement>
                                    <text><![CDATA[Reference Book Amount]]></text>
                                </staticText>
                            </jr:columnHeader>
                            <jr:detailCell style="Table_TD" height="30">
                                <textField isBlankWhenNull="false">
                                    <reportElement x="0" y="0" width="181" height="30" forecolor="#000000"/>
                                    <textElement textAlignment="Center" verticalAlignment="Middle">
                                        <font size="12"/>
                                    </textElement>
                                    <textFieldExpression><![CDATA[$F{reference_books_instances_amount}]]></textFieldExpression>
                                </textField>
                            </jr:detailCell>
                        </jr:column>
                    </jr:table>
                </componentElement>
            </band>
        </detail>
        <pageFooter>
            <band height="17">
                <textField>
                    <reportElement mode="Opaque" x="0" y="4" width="515" height="13" backcolor="#E6E6E6"/>
                    <textElement textAlignment="Right"/>
                    <textFieldExpression><![CDATA["Page "+$V{PAGE_NUMBER}+" of"]]></textFieldExpression>
                </textField>
                <textField evaluationTime="Report">
                    <reportElement mode="Opaque" x="515" y="4" width="40" height="13" backcolor="#E6E6E6"/>
                    <textFieldExpression><![CDATA[" " + $V{PAGE_NUMBER}]]></textFieldExpression>
                </textField>
                <textField pattern="M/d/yy">
                    <reportElement x="0" y="4" width="280" height="13"/>
                    <textFieldExpression><![CDATA[new java.util.Date()]]></textFieldExpression>
                </textField>
            </band>
        </pageFooter>
    </jasperReport>

    Таблица в этом шаблоне привязана к дочернему источнику данных subDataset. Элемент title обращается к данным полосы Header напрямую. Вы можете заранее посмотреть, как будет выглядеть отчёт, открыв шаблон в визуальном редакторе JasperReports.

    Загрузите новый шаблон в приложение, выбрав любой тип вывода, и сделайте его шаблоном по умолчанию:

    sample jasper 3
    Рисунок 65. Редактор шаблона

Выполните отчёт, чтобы убедиться в его работоспособности:

sample jasper 4
Рисунок 66. Пример выполненного отчёта

7.4. Пример отчёта HTML/PDF

Создадим отчёт с альбомной ориентацией страниц, нумерацией, а также фиксированными заголовком и подвалом на каждой странице, которые мы настроим через правила и свойства CSS. Формат вывода отчёта – HTML с конвертацией в PDF.

Готовый пример этого отчёта вместе с тестовым проектом можно скачать с CUBA GitHub.

  1. Модель данных

    Отчёт будет отображать информацию о сущности Client. Она содержит два строковых атрибута, title и summary, которые мы используем в структуре отчёта.

    public class Client extends StandardEntity {
    
        @NotNull
        @Column(name = "TITLE", nullable = false)
        protected String title;
    
        @Lob
        @Column(name = "SUMMARY")
        protected String summary;
    
        ...
    }
  2. Создание отчёта

    Создайте простой отчёт без параметров. Запрос JPQL возвращает список всех клиентов с их локальными атрибутами: title и summary.

    example html 1
    Рисунок 67. Создание отчёта HTML/PDF в редакторе
  3. Шаблон отчёта.

    Теперь создайте файл шаблона. Определите в нем блоки заголовка и подвала, которые должны выводиться на каждой странице итогового документа PDF. Также используйте свойство CSS page-break-before: always, которое будет создавать разрыв страницы перед каждым новым блоком информации о клиенте.

    Используйте теги FreeMarker для вставки данных в тело отчёта. Подробное руководство по FreeMarker находится здесь: https://freemarker.apache.org/docs/.

    <body>
    <h1>Clients report</h1>
    
    <!-- Custom HTML header -->
    <div class="header">
        Annual Report of our Company
    </div>
    
    <!-- Custom HTML footer -->
    <div class="footer">
        Address: William Road
    </div>
    
    <#assign clients = Root.bands.Clients />
    
    <#list clients as client>
    <div class="custom-page-start" style="page-break-before: always;">
        <h2>Client</h2>
    
        <p>Name: ${client.fields.title}</p>
        <p>Summary: ${client.fields.summary}</p>
    </div>
    </#list>
    </body>
  4. Правила CSS

    Используйте следующий код CSS для разметки страницы PDF:

    body {
        font: 12pt Georgia, "Times New Roman", Times, serif;
        line-height: 1.3;
    }
    @page {
        /* switch to landscape */
        size: landscape;
        /* set page margins */
        margin: 0.5cm;
        @top-center {
            content: element(header);
        }
        @bottom-center {
            content: element(footer);
        }
        @bottom-right{
            content: counter(page) " of " counter(pages);
        }
    }

    Далее определите положение заголовка и подвала:

    div.header {
        display: block;
        text-align: center;
        position: running(header);
        width: 100%;
    }
    
    div.footer {
        display: block;
        text-align: center;
        position: running(footer);
        width: 100%;
    }

    Также настройте отступы для основного содержимого отчёта, чтобы избежать наложения с заголовком и подвалом:

    /* Fix overflow of headers and content */
    body {
        padding-top: 50px;
    }
    .custom-page-start {
        margin-top: 50px;
    }

    В итоге получился файл paging-template.html со следующим содержанием:

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <title>Invoice</title>
        <style type="text/css">
     body { font: 12pt Georgia, "Times New Roman", Times, serif; line-height: 1.3; padding-top: 50px; } div.header { display: block; text-align: center; position: running(header); width: 100%; } div.footer { display: block; text-align: center; position: running(footer); width: 100%; } @page { /* switch to landscape */ size: landscape; /* set page margins */ margin: 0.5cm; @top-center { content: element(header); } @bottom-center { content: element(footer); } @bottom-right { content: counter(page) " of " counter(pages); } } .custom-page-start { margin-top: 50px; }
      </style>
    </head>
    <body>
    <h1>Clients report</h1>
    
    <!-- Custom HTML header -->
    <div class="header">
        Annual Report of our Company
    </div>
    
    <!-- Custom HTML footer -->
    <div class="footer">
        Address: William Road
    </div>
    
    <#assign clients = Root.bands.Clients />
    
    <#list clients as client>
    <div class="custom-page-start" style="page-break-before: always;">
        <h2>Client</h2>
    
        <p>Name: ${client.fields.title}</p>
        <p>Summary: ${client.fields.summary}</p>
    </div>
    </#list>
    </body>
    </html>
  5. Загрузите шаблон и запустите отчёт.

    example html 3
    Рисунок 68. Загрузка шаблона в редакторе

    Как мы видим, отчёт содержит титульную страницу и разрывы перед каждой страницей с информацией о клиенте, а также заголовок и подвал на каждой странице:

    example html 2
    Рисунок 69. Пример выполненного отчёта

7.5. HTML отчёт с шаблонизатором Groovy

Этот пример основан на приложении Library, исходный код которого доступен на GitHub. Создадим отчёт, который выводит список книжных публикаций для выбранного города. Формат вывода по умолчанию – HTML.

  1. Создайте отчёт с набором данных JPQL

    html groovy template structure
    Рисунок 70. Структура данных отчёта

    Полоса BookPublications выводит список публикаций путем выполнения следующего JPQL запроса:

    Набор данных BookPublications
    select
    book.name as "book",
    publisher.name as "publisher"
    from library$BookPublication e
    left join e.book book
    left join e.publisher publisher
     where e.town.id = ${city}

    В запросе используется внешний параметр отчёта – city. Параметр имеет тип Entity, однако его можно напрямую сравнивать с полями-идентификаторами сущностей, преобразование будет выполнено автоматически.

  2. Задайте параметр отчёта:

    На вкладке Parameters and Formats объявлен один внешний параметр отчёта – City:

    html groovy template parameter
    Рисунок 71. Редактор параметра

    Этот параметр запрашивается у пользователя при запуске отчёта. Выбор города производится через экран library$Town.browse, имеющийся в приложении.

  3. Создайте шаблон отчёта

    На вкладке Templates определен один шаблон формата HTML, сгенерированный по умолчанию с тэгами FreeMarker.

    Создайте новый файл HTML со следующим содержимым:

    PublicationsTemplate
    <?xml version="1.0" encoding="utf-8"?>
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ru">
       <head>
          <title> Publications by city </title>
          <style type="text/css">
     body {font: 12pt Georgia, "Times New Roman", Times, serif; line-height: 1.3; padding-top: 30px;} tbody tr {height:40px; min-height:20px}
          </style>
       </head>
       <body>
          <h1>Publications, published in <% out << "${Root.fields.city.name}"%></h1>
              <% def bookPublications = Root.bands.BookPublications.fields %>
          <table class="report-table" border="1" cellspacing="2" >
             <thead>
             <tr>
                <th>Book</th>
                <th>Publisher</th>
             </tr>
             </thead>
             <tbody>
                <% bookPublications.title.eachWithIndex{elem, index -> out << "<tr><td> ${bookPublications.book[index]} </td><td> ${bookPublications.publisher[index]} </td></tr>"}%>
             </tbody>
          </table>
       </body>
    </html>

    Для формирования заголовка отчёта используется значение входного параметра: ${Root.fields.city.name}.

    Ниже определена переменная bookPublications:

    <% def bookPublications = Root.bands.BookPublications.fields %>

    Эта переменная используется в теле таблицы для вывода полей отчёта.

    <% bookPublications.title.eachWithIndex{elem, index -> out << "<tr><td> ${bookPublications.book[index]} </td><td> ${bookPublications.publisher[index]} </td></tr>"}%>

    Загрузите новый шаблон в приложение, выбрав формат вывода HTML, установите переключатель Template type в значение Groovy template и сделайте его шаблоном по умолчанию:

    publicationsTemplate editor
    Рисунок 72. Редактор шаблона отчёта

Выполните отчёт, чтобы убедиться в его работоспособности:

publications report
Рисунок 73. Пример выполненного отчёта

8. REST API генератора отчётов

Универсальный REST API предоставляет следующую функциональность для генератора отчётов:

  • Получение списка отчётов.

  • Получение информации об отдельном отчёте.

  • Запуск отчёта и скачивание результата.

  • Получение документации OpenAPI (Swagger).

REST API использует протокол OAuth2 для аутентификации и поддерживает анонимный доступ.

Чтобы отчёт был доступен в REST API, установите флаг Visible for REST API на вкладке Roles and Screens редактора отчёта:

visible for rest
Рисунок 74. Флаг Visible for REST API

Ниже приведено формальное описание некоторых особенностей REST API для генератора отчётов. Больше информации о REST API и получении OAuth-токена можно найти в документации по аддону REST API.

Получение списка отчётов

Чтобы получить список всех существующих отчётов, выполните GET запрос по адресу:

/rest/reports/v1/report

Например:

GET http://localhost:8080/app/rest/reports/v1/report HTTP/1.1

Authorization: Bearer f5a2b4b1-a121-4563-9519-dd3c0b116689
Content-Type: application/json

Ответ будет содержать краткую информацию обо всех отчётах с установленным флагом Visible for REST API:

{
    "id": "2dd27fbf-8830-416a-899f-339543f8f27a",
    "name": "Books by author"
},
{
    "id": "2f07c9fe-5d6d-48cf-876f-8c02ac1f6c3c",
    "name": "Book availability in department"
}
Получение информации об отчёте

Для получения детальной информации об отчёте выполните GET запрос по адресу:

/rest/reports/v1/report/{id}

Последняя часть запроса здесь – это идентификатор нужного отчёта:

GET http://localhost:8080/app/rest/reports/v1/report/2dd27fbf-8830-416a-899f-339543f8f27a HTTP/1.1

Возвращаемый объект JSON будет содержать следующую информацию об отчёте:

{
    "id": "2dd27fbf-8830-416a-899f-339543f8f27a",
    "name": "Books by author",
    "templates": [
        {
            "code": "DEFAULT",
            "outputType": "XLS"
        }
    ],
    "inputParameters": [
        {
            "name": "Author",
            "alias": "author",
            "type": "ENTITY",
            "required": true,
            "hidden": false,
            "entityMetaClass": "library$Author"
        }
    ]
}
Запуск отчёта

Чтобы выполнить отчёт, выполните POST запрос по адресу:

/rest/reports/v1/run/{id}

Последняя часть запроса здесь – это идентификатор нужного отчёта:

POST http://localhost:8080/app/rest/reports/v1/run/2dd27fbf-8830-416a-899f-339543f8f27a HTTP/1.1

Параметры отчёта можно передать в теле запроса:

{parameters: [{name: 'author',value: '4b3a21b0-d6b7-4161-b0b6-55f118fbaac5'}]}

Если необходимо запустить отчёт с шаблоном, отличным от шаблона по умолчанию, передайте код шаблона в теле запроса:

{template: 'Template_1', parameters: [{name: 'author',value: '4b3a21b0-d6b7-4161-b0b6-55f118fbaac5'}]}
Получение документации Swagger

Полная документация Swagger для дополнения Генератор отчётов доступна с помощью GET запроса по адресу:

http://localhost:8080/app/rest/reports/v1/docs/swagger.json

Приложение A: Установка и настройка OpenOffice

Генератор отчетов использует пакет OpenOffice / LibreOffice для вывода отчетов в форматах PDF и DOC. Ниже рассмотрена установка и настройка данного пакета на компьютере, содержащем сервер приложения.

Установка и настройка OpenOffice для Microsoft Windows

  • Скачайте дистрибутив программы по адресу http://openoffice.org.

  • Произведите установку программы.

  • Укажите в свойстве приложения reporting.office.path в файле app.properties модуля core путь к установленному OpenOffice.org, например:

reporting.office.path = C:/Program Files (x86)/OpenOffice.org 3/program

Установка и настройка LibreOffice для Microsoft Windows

  • Скачайте дистрибутив программы по адресу http://www.libreoffice.org/download/download/.

  • Произведите установку программы.

  • Укажите в свойстве приложения reporting.office.path в файле app.properties модуля core путь к установленному пакету LibreOffice, например:

reporting.office.path = C:/Program Files (x86)/LibreOffice 5/program

Установка и настройка LibreOffice на сервере Ubuntu

  • Установите пакет libreoffice, например, командой

    $ sudo apt-get install libreoffice
  • Укажите в свойстве приложения reporting.office.path в файле app.properties модуля core путь к установленному LibreOffice:

    reporting.office.path = /usr/lib/libreoffice/program
  • Если на сервере не установлен оконный интерфейс, то LibreOffice при старте будет выдавать ошибку вида Caused by: java.awt.HeadlessException: No X11 DISPLAY variable was set, but this program performed an operation which requires it, или же просто завершаться без сообщений об ошибках. Для устранения проблемы установите свойство приложения reporting.displayDeviceAvailable:

    reporting.displayDeviceAvailable = false
  • Для диагностики ошибок при старте LibreOffice выполните следующую команду:

    $ strace -e trace=signal /usr/lib/libreoffice/program/soffice.bin --headless --accept="socket,host=localhost,port=8100;urp" --nologo --nolockcheck

Если в Ubuntu вы установили tomcat с помощью пакетного менеджера apt, вам необходимо также скопировать каталог ~/.config/libreoffice в $CATALINA_HOME. Для tomcat8, это /usr/share/tomcat8.

Затем измените пользователя этого каталога:

sudo mkdir /usr/share/tomcat8/.config
sudo cp -pr ~/.config/libreoffice /usr/share/tomcat8/.config/
sudo chown -R tomcat8.tomcat8 /usr/share/tomcat8/.config/

Установка и настройка LibreOffice для macOS

reporting.office.path = /Applications/LibreOffice.app/Contents/MacOS

Приложение B: Свойства приложения

В данном разделе в алфавитном порядке описаны свойства приложения, имеющие отношение к генератору отчётов.

reporting.backgroundReportProcessingTimeoutMs

Определяет таймаут для выполнения отчета в миллисекундах, если свойство reporting.useBackgroundReportProcessing имеет значение true.

Значение по умолчанию: 10000.

Хранится в базе данных.

Используется в блоке Middleware.

reporting.curl.path

Генератор отчетов использует инструмент cURL для создания отчетов по URL-адресу. Значение свойства представляет собой системный путь к инструменту.

Значение по умолчанию: curl.

Используется в блоке Middleware.

reporting.displayDeviceAvailable

Значение false позволяет запускать OpenOffice / LibreOffice в серверной операционной системе без оконного интерфейса.

Значение по умолчанию: false

Используется в блоке Middleware.

reporting.enableTabSymbolInDataSetEditor

Указывает, должно ли нажатие клавиши TAB в полях Script редактора отчёта обрабатываться как символ табуляции вместо смены фокуса для навигации по экрану.

Значение по умолчанию: false

Используется в клиентских блоках.

reporting.executionHistory.cleanup.days

Назначенное задание удаляет все записи истории выполнения отчётов старше указанного количества дней. Если значение свойства = 0, то назначенное задание не учитывает это свойство при удалении записей. Подробнее про настройку назначенного задания см. Удаление истории.

Значение по умолчанию: 730.

Хранится в базе данных.

Используется в блоке Middleware.

reporting.executionHistory.cleanup.itemsPerReport

Назначенное задание оставляет то количество записей в истории выполнения для каждого отчёта, которое указано в этом свойстве, остальные удаляет. Не рекомендуется использовать в качестве значения число, больше 1000. Если значение = 0, то назначенное задание не учитывает это свойство при удалении записей. Это полезно для часто запускаемых отчётов, таких как шаблоны писем, выставление счетов, шаблоны документов и т. д. Подробнее про настройку назначенного задания см. Удаление истории.

Значение по умолчанию: 1000.

Хранится в базе данных.

Используется в блоке Middleware.

reporting.executionHistory.enabled

Включает механизм управления историей выполнения отчётов.

Значение по умолчанию: false.

Хранится в базе данных.

Используется в блоке Middleware.

reporting.executionHistory.saveOutputDocument

При установке в true файлы результатов отчётов сохраняются в хранилище файлов, если свойство reporting.executionHistory.enabled включено. См. также Выходные документы.

Значение по умолчанию: false.

Хранится в базе данных.

Используется в блоке Middleware.

reporting.fontsDir

Путь к каталогу со шрифтами для конвертации HTML в PDF.

Например: reporting.fontsDir = C:/Windows/Fonts

Используется в блоке Middleware.

reporting.docFormatterTimeout

Задаёт время ожидания LibreOffice в секундах для преобразования DOCX/XLSX в HTML/PDF.

По истечении указанного таймаута пользователь получит сообщение об ошибке.

Значение по умолчанию: 20

Используется в блоке Middleware.

reporting.office.docx.useOfficeForDocumentConversion

Включает использование OpenOffice для вывода отчёта с DOCX шаблоном в HTML/PDF, что значительно увеличивает точность конвертации.

Значение по умолчанию: false

Используется в блоке Middleware.

reporting.office.path

Задает путь к установленному пакету OpenOffice.

Значение по умолчанию: /

Используется в блоке Middleware.

reporting.office.ports

Задает список портов для OpenOffice/LibreOffice, разделенный запятыми или вертикальной чертой.

Например: reporting.office.ports = 8100|8101|8102|8103|8104|8105.

Значение по умолчанию: 8100, 8101, 8102, 8103.

Используется в блоке Middleware.

reporting.putEmptyRowIfNoDataSelected

Задает режим, в котором полосы, наборы данных которых не вернули ни одной записи, все равно выводятся один раз.

Значение по умолчанию: true

Используется в блоке Middleware.

reporting.useBackgroundReportProcessing

Позволяет задать выполнение отчёта в качестве фоновой задачи. Это свойство используется для добавления возможности отмены отчёта.

Значение по умолчанию: false.

Хранится в базе данных.

Используется в блоке Middleware.

. . .