Глава 1. Создание отчетов

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

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

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

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

reporting
  • YARG - фреймворк, являющийся ядром генератора отчетов.
  • Report Engine интегрирует фреймворк YARG в платформу CUBA и предоставляет дополнительную функциональность, такую как права доступа к отчетам и связь с экранами.
  • Report Designer - средство описания и хранения отчетов. Включает в себя инфраструктуру хранения описаний и шаблонов отчетов, экраны создания и управления отчетами.
  • Report - описатель структуры данных отчета, включающий в себя Bands - полосы отчета, и Datasets - наборы данных, выводимые в полосах.
  • Report Template - шаблон визуального представления отчета.

=== Структура данных отчета

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

report structure

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

  • Name - имя отчета. Имя может быть локализовано на вкладке Localization.
  • Group - группа отчетов, применяется для группировки в общем списке браузера отчетов.
  • Default template - шаблон, по которому будет выводиться отчет.
  • System code - необязательный код отчета, по которому его можно при необходимости идентифицировать в программном коде системы.

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

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

  • Band name - уникальное в рамках отчета имя полосы. Должно содержать только латинские буквы, цифры или символ подчеркивания.
  • Orientation - ориентация полосы: Horizontal или Vertical. Горизонтальные полосы в выводимом отчете копируются вниз, вертикальные - вправо. Горизонтальные полосы могут содержать вложенные полосы.
  • Parent band - родительская полоса.

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

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

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

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

  1. 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 необходимо вручную включать условия фильтрации мягко удаленных записей.

  2. 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 автоматически поддерживают мягкое удаление и возвращают только неудаленные записи.

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

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

    • params - мэп внешних параметров отчета. Пример получения значения параметра:

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

      def groupId = parentBand.getParameterValue('groupId')
    • persistence - объект типа com.haulmont.cuba.core.Persistence, позволяющий управлять транзакциями и получать ссылку на EntityManager. Например:

      def tx = persistence.createTransaction()
      try {
          def em = persistence.getEntityManager()
          def query = em.createQuery('select g from sec$Group g')
      ...
          tx.commit()
      } finally {
          tx.end()
      }
    • metadata - объект типа com.haulmont.cuba.core.global.Metadata, позволяющий обращаться к метаданным приложения. Например:

      def metaClass = metadata.getClassNN('sec$User')
    • transactional - метод, принимающий на вход замыкание, которое нужно выполнить в новой транзакции. Параметром замыкания становится текущий EntityManager. Пример использования:

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

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

    def dataWorker = com.haulmont.cuba.core.global.AppBeans.get('cuba_DataWorker')

    Пример 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
  4. Entity - набор данных состоит из одной строки и формируется по атрибутам одного экземпляра сущности и связанных с ним сущностей.

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

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

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

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

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

=== Шаблон отчета

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

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

report template
  • Template code - код шаблона для его идентификации.
  • Output type - тип вывода отчета. Должен быть согласован с типом файла шаблона по правилам, описанным в .
  • Template file - файл шаблона, который загружается из файловой системы и сохраняется в базе данных вместе с описанием структуры отчета.
  • Output name pattern - необязательное имя файла, которое будет использоваться для выгрузки готового отчета.
  • Defined with class - признак использования шаблона, определяемого классом.
  • Custom class name - имя класса шаблона, если установлен признак Defined with class.

==== Шаблоны XLSX и XLS

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

Для каждой полосы отчета в шаблоне должен быть определен регион с именем полосы. Именованные регионы создаются путем выделения нужного диапазона ячеек и ввода имени в поле в левом верхнем углу приложения. Для редактирования уже созданных именованных регионов в Microsoft Office используется команда меню Formulas -> Name Manager, а в OpenOffice команда Insert -> Names -> Manage.

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

Горизонтальные полосы могут содержать вложенные полосы. Поэтому для вложенных полос необходимо создавать именованные регионы непосредственно под регионами, соответствующими родительским полосам.

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

report template xls

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

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

Шаблон ODT создается с помощью OpenOffice / LibreOffice, для создания шаблонов DOC и DOCX можно использовать как OpenOffice, так и Microsoft Office.

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

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

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

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

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

report template doc

==== Шаблон HTML

Шаблон HTML задается в файле c расширением .html в кодировке UTF-8 (без BOM). Для размещения данных необходимо использовать тэги FreeMarker (документация по FreeMarker находится по адресу freemarker.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>

Пример шаблона для вывода отчета, состоящего из двух полос: 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>

===== Преобразование HTML в PDF

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

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

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

    $ sudo apt-get install ttf-mscorefonts-installer

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

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

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

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

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

Класс, определяющий шаблон должен реализовывать интерфейс 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();
    }
}

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

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

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

Круговая диаграмма:

chart template pie
  • Band name - полоса, предоставляющая данные для диаграммы.
  • Title field - поле, из которого будут взяты названия сегментов.
  • Value field - поле, из которого будут взяты значения сегментов.
  • Color field - поле, из которого будут взяты коды цветов сегментов. Код цвета должен быть в web формате. Если код цвета не предоставлен, он будет выбран автоматически.
  • Units - данный текст будет добавлен к значениям в легенде.

Серийная диаграмма:

chart template serial
  • Band name - полоса, предоставляющая данные для диаграммы.
  • Category field - поле, из которого будут взяты названия категорий.
  • Category axis caption - заголовок для горизонтальной оси.
  • Value axis caption - заголовок для вертикальной оси.
  • Value axis units - данный текст будет добавлен к значениям.

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

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

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

Шаблон / Вывод

XLSX

XLS

DOCX

DOC

ODT

PDF

HTML

Chart

XLSX

+

       

+ 1

   

XLS

 

+

     

+ 1

   

DOCX

   

+

   

+ 2

+

 

DOC

     

+

 

+ 1

   

ODT

       

+

+ 1

   

HTML

         

+

+

 

Chart

             

+

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

2 - в зависимости от значения свойства приложения cuba.reporting.openoffice.docx.useOfficeForPdfConversion вывод может осуществляться либо через OpenOffice, либо без него. В последнем случае необходимо обеспечить наличие нужных шрифтов, как описано в .

=== Внешние параметры отчета

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

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

report parameter

Вкладка Properties:

  • Parameter name - имя параметра, как оно будет отображено в форме ввода параметров при старте отчета.
  • Parameter alias - алиас параметра, который нужно использовать для обращения к нему в наборах данных.
  • Parameter type - тип параметра.
  • Required parameter - признак того, что параметр должен быть обязательно передан в отчет.
  • Entity - если указан тип параметра Entity или List of entities, то в данном поле необходимо выбрать тип сущности.
  • Enumeration - если указан тип параметра Enumeration, то в данном поле необходимо выбрать тип перечисления.
  • Entity selection screen - необязательный идентификатор экрана, который будет использоваться для выбора экземпляров сущности. Если экран не указан, выбор будет осуществляться через специальный общий для всех сущностей экран.

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

ru = Книга

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

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

report formatter
  • Value name - имя поля отчета с префиксом полосы, например Book.name.
  • Format string - формат поля. Для числовых значений формат должен быть задан по правилам java.text.DecimalFormat, для дат - java.text.SimpleDateFormat.

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

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

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

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

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

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

=== Локализация названия отчета

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

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