Более новая версия доступна в разделе документации.

Предисловие

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

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

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

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

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

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

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

Если у Вас имеются предложения по улучшению данного руководства, обратитесь пожалуйста в службу поддержки по адресу https://www.cuba-platform.ru/support/topics.

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

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

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

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

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

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

reporting
  • YARG - фреймворк, являющийся ядром генератора отчетов.

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

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

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

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

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

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

report structure

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1.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
Warning

В запросы на 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}

1.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 необходимо указать его имя.

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

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

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

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

    LoadContext<Book> loadContext = LoadContext.create(Book.class)
            .setId(bookId)
            .setView("book.edit")
    def book = dataManager.load(loadContext)
  • 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, позволяющий получать ссылку на EntityManager. Например:

    def em = persistence.getEntityManager()
    def query = em.createQuery('select g from sec$Group g')

    Для работы с дополнительным хранилищем данных, его имя нужно указать в параметре метода 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
Tip

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

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

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

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

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

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

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

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

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

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

1.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/.

1.2. Шаблон отчета

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

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

report template
  • 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

1.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

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

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

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

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

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

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

csv output
1.2.1.1. Перекрёстные отчёты

Перекрёстные, или матричные, отчёты создаются с помощью Microsoft Office или LibreOffice.

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

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

crosstab template

Вертикальная полоса DataHeader заполняет отчёт датами вправо:

select distinct c.dateOfCall as date from matrix$Call c order by c.dateOfCall

Горизонтальная полоса Operators выводит имена операторов сверху вниз:

select o.name as name, o.id as operator_id from matrix$Operator o order by o.createTs

Дочерняя вертикальная полоса Data использует id операторов из результатов родительской полосы как параметр для заполнения матрицы:

def result = []

transactional { em ->
    def query = em.createQuery('select distinct c.dateOfCall from matrix$Call c order by c.dateOfCall ')
    query.resultList.each { date ->
        def query2 = em.createQuery('select c from matrix$Call c where c.operator.id = ? 1 and c.dateOfCall = ? 2 ')
        query2.setParameter(1, parentBand.getParameterValue('operator_id'))
        query2.setParameter(2, date)
        result.add(['calls': query2.resultList.size()])
    }
}
return result

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

crosstab report

1.2.2. Шаблон CSV

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

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

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

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

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

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

1.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)-шаблоны.

Warning

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

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

report template doc
Warning

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

select e.year as "year"

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

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

1.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 (документация по 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>

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

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

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

html report editor
Рисунок 4. Встроенный редактор HTML
1.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>

1.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, доступен в разделе Примеры отчётов.

1.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

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

Шаблон-диаграмма доступен, если проект приложения включает компонент 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 формате. Если код цвета не предоставлен, он будет выбран автоматически.

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

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

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

report table output

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

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

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

show report table

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

Шаблон / Вывод 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.openoffice.docx.useOfficeForDocumentConversion вывод может осуществляться либо через OpenOffice/LibreOffice, либо без него. В последнем случае необходимо обеспечить наличие нужных шрифтов, как описано в Преобразование HTML в PDF.

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

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

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

report parameter

Вкладка 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

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

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

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

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

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

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

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

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

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

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

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

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

    cross parameter validation

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

Для любого поля, выводимого отчетом, можно задать форматирование на вкладке 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}.

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

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

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

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

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

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

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

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

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

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

2. Запуск отчетов

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

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

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

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

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

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

  • com.haulmont.reports.gui.actions.RunReportAction - действие, отображающее список всех доступных отчетов. При выборе пользователем отчета из списка отображается форма ввода параметров (если они заданы), и отчет запускается на исполнение.

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

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

      <layout>
          <table id="bookTable">
              ...
              <buttonsPanel id="buttonsPanel">
                  ...
                  <button id="reportButton"
                          icon="PRINT"/>
              </buttonsPanel>
          </table>
    • Контроллер

      @Inject
      private Button reportButton;
      
      @Override
      public void init(Map<String, Object> params) {
          reportButton.setAction(new RunReportAction("report"));
      }
    • messages.properties

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

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

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

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

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

    Пример использования с кнопкой и контекстным меню таблицы:

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

      <layout>
          <table id="bookTable">
              ...
              <buttonsPanel id="buttonsPanel">
                  ...
                  <button id="reportButton"
                          icon="PRINT"/>
              </buttonsPanel>
          </table>
    • Контроллер

      @Inject
      private Button reportButton;
      
      @Inject
      private Table bookTable;
      
      @Override
      public void init(Map<String, Object> params) {
          TablePrintFormAction action = new TablePrintFormAction("report", bookTable);
          bookTable.addAction(action);
          reportButton.setAction(action);
      }
    • messages.properties

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

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

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

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

<layout expand="windowActionsBox">
    ...
    <hbox id="windowActionsBox"
          spacing="true">
        <iframe id="windowActions"
                screen="editWindowActions"/>
        <button id="reportButton"/>
    </hbox>
</layout>
  • Контроллер

    @Inject
    private Button reportButton;
    
    @Override
    public void init(Map<String, Object> params) {
        reportButton.setAction(new EditorPrintFormAction("report", this, null));
    }
  • messages.properties

    report = Report

2.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());

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

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

Рассмотрим устройство одного отчета в приложении-примере Библиотека, исходный код которого доступен по адресу https://github.com/cuba-platform/sample-library.

Для использования отчетов в Вашем проекте, необходимо активировать элемент reports в списке App components на экране редактирования свойств проекта (секция Project properties, кнопка Edit) в CUBA Studio.

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

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

    sample1 structure

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

    • Полоса 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

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

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

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

    sample1 template
  4. Локализация названия отчета.

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

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

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

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

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

Чтобы использовать отчёты в этом проекте, необходимо активировать дополнение reports в списке App components на экране редактирования свойств проекта (секция Project properties) в CUBA Studio.

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

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

    sample jasper

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

    • Полоса 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.id = ?1 and bi.bookPublication.book.literatureType.id = ?2 ''')
                      .setParameter(1, params['library_department'])
                      .setParameter(2, lt)
                      .getSingleResult()
              def refCount = em.createQuery(''' select count(bi) from library$BookInstance bi where bi.libraryDepartment.id = ?1 and bi.bookPublication.book.literatureType.id = ?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

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

  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

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

sample jasper 4

3.3. Пример отчёта 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
  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

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

    example html 2

4. Мастер создания отчетов

Мастер создания отчетов - это визуальный инструмент, который позволяет быстро создавать структуру данных и шаблон отчета. Для вызова мастера в экране Reports нажмите CreateUsing wizard.

reports wizard main

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

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

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

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

Процесс создания любого отчета состоит из трех этапов:

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

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

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

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

Рассмотрим работу мастера на примере тестового приложения Библиотека, доступного для загрузки по нажатию на кнопку Samples в окне выбора проекта CUBA Studio.

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

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

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

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

  • Format of template file - формат шаблона, по которому будет создаваться отчет - DOCX. Доступны также форматы XSLX и PDF.

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

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

single entity step 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

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

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

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

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

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

single entity step 2

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

single entity test running

После настройки регионов можно переходить к третьему этапу - сохранению отчета. На этом этапе можно просмотреть готовый шаблон отчета, изменить название и формат файла вывода. Доступны три типа: DOCX, HTML, PDF.

single entity step 3

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

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

single entity reports list

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

<groupTable id="bookPublicationTable"
    ...
    <buttonsPanel>
        ...
        <button id="printDetails"
        caption="msg://printDetails"/>

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

@Inject
private Button printDetails;

@Override
public void init(Map<String, Object> params) {
    TablePrintFormAction action = new TablePrintFormAction("report", bookPublicationTable);
          bookPublicationTable.addAction(action);
          printDetails.setAction(action);
    }

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

single entity running

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

single entity result

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

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

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

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

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

На первом этапе необходимо указать детали отчета:

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

  • Format of template file - формат вывода отчета - XSLX.

  • Report name - имя отчета - Book items location.

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

list of entities step 1

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

list of entities attributes

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

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

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

list of entities editor

После сохранения отчет можно запускать из браузера отчетов.

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

      <table id="bookInstanceTable"
             multiselect="true">
             ...
                  <buttonsPanel>
                  ...
                      <button id="printList"
                      caption="msg://printList"/>

После этого инжектируем в контроллере компонент Button:

@Inject
private Button printList;

После этого внутри переопределенного метода init() добавим следующий код:

TablePrintFormAction action = new TablePrintFormAction("report", bookInstanceTable);
    bookInstanceTable.addAction(action);
    printList.setAction(action);

Теперь отчет можно запускать из браузера экземпляров книг, выбирая экземпляры для отчета в таблице и нажимая на кнопку Print list. Опция Print selected экспортирует выбранные экземпляры, опция Print all - все экземпляры, выбранные текущим фильтром.

list of entities running

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

list of entities result

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

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

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

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

  • Format of template file - формат вывода отчета - XSLX.

  • Report name - имя отчета - Recently added book items.

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

query step 1

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

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

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

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

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

query parameter

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

query step 2

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

query editor

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

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

query parameter rename

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

Для этого внесем в XML-дескриптор экрана librarydepartment-browse.xml реализацию кнопки:

<table id="libraryDepartmentTable"
    ...
    <buttonsPanel id="buttonsPanel">
        ...
        <button id="reportBtn"
         caption="msg://reportBtn"/>
     </buttonsPanel>
</table>

После чего в контроллере инжектируем компонент Button:

@Inject
private Button reportBtn;

и в переопределенном методе init() зададим для кнопки действие RunReportAction:

reportBtn.setAction(new RunReportAction("report"));

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

query running

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

query result

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

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

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

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

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

  • Укажите в свойстве приложения reporting.openoffice.path путь к установленному OpenOffice.org, например:

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

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

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

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

  • Укажите в свойстве приложения reporting.openoffice.path путь к установленному пакету LibreOffice, например:

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

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

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

    $ sudo apt-get install libreoffice`
  • Указать в свойстве приложения reporting.openoffice.path путь к установленному LibreOffice:

    reporting.openoffice.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.displayDeviceUnavailable:

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

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

Если в 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.openoffice.path = /Applications/LibreOffice.app

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

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

reporting.displayDeviceUnavailable

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

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

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

reporting.enableTabSymbolInDataSetEditor

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

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

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

reporting.fontsDir

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

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

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

reporting.openoffice.docx.useOfficeForDocumentConversion

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

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

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

reporting.openoffice.path

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

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

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

reporting.putEmptyRowIfNoDataSelected

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

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

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

. . .