Берём данные, JasperReports и заполняем шаблон в iReport
Эта статья посвящена не столько, как это сделать красиво с точки зрения дизайна, а как с помощью имеющихся средств JasperReports сделать отчёты чёткими для восприятия пользователем и удобными для дальнейшего использования интегратором. Так как JasperReports, это в первую очередь не самостоятельное приложение, а библиотека расширения для программ написанных на Java, то в качестве источника информации для построения отчётов будем использовать базу данных от NORD POS(подробнее об этом моём проекте в отдельной статье). При этом изложенный «под катом» материал, я надеюсь, будет интересен не только пользователям моей POS, но и всем тем, кто хочет больше узнать о мощном инструменте формирования отчётов для своего приложения, а на русском языке материалов посвящённых JasperReports не так и много.
Загружаем основу
Основой построения любого отчёта средствами JasperReports является шаблон с расширением jrxml. Этот файл XML-формата можно создать с нуля, как в текстовом редакторе, так и скомпоновать из элементов(а по сути XML-тегов) в визуальной оболочке iReport. Приложение iReport есть в виде плагина для NetBeans или в виде самостоятельной программы. Для данной статьи я выбрал второй вариант, как более наглядный с моей точки зрения.
Первое, что необходимо сделать после установки и запуска iReport, это создать чистый лист шаблона для последующего заполнения элементами отчёта(команда File -> New...). Я выбрал шаблон формата A4, расположенный портретно.
В дальнейшем вы можете поменять, как формат листа, так и поля отступов на нём(команда Format -> Page format...). И здесь вы встретитесь с первой проблемой, которую придётся учитывать по ходу всей работы с JasperReports, невозможностью точно выставить значения в привычных нам миллиметрах. Точно можно выставить только значение в пикселях, при этом на один дюйм приходится 72 пикселя, а вот в миллиметрах это всегда будет дробное значение 25,4 мм. В обычном отчёте из столбцов это не сильно существенно, но если делать шаблон этикетки, то такой подход к расчётам вам попортит значительно нервы, так как под рукой только миллиметровая линейка и необходимо будет экспериментально подобрать, то количество пикселей которое будет вмещаться при печати на заготовку этикет ленты. Вот как у меня получилось:
<?xml version="1.0" encoding="UTF-8"?>
<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="report" language="groovy" pageWidth="595" pageHeight="842" columnWidth="525" leftMargin="42" rightMargin="28" topMargin="32" bottomMargin="32" uuid="fd614d3e-f49f-4d37-848f-ffffd7b3d536">
<property name="ireport.zoom" value="1.0"/>
<property name="ireport.x" value="0"/>
<property name="ireport.y" value="0"/>
<background>
<band splitType="Stretch"/>
</background>
<title>
<band height="79" splitType="Stretch"/>
</title>
<pageHeader>
<band height="35" splitType="Stretch"/>
</pageHeader>
<columnHeader>
<band height="61" splitType="Stretch"/>
</columnHeader>
<detail>
<band height="125" splitType="Stretch"/>
</detail>
<columnFooter>
<band height="45" splitType="Stretch"/>
</columnFooter>
<pageFooter>
<band height="54" splitType="Stretch"/>
</pageFooter>
<summary>
<band height="42" splitType="Stretch"/>
</summary>
</jasperReport>
И эти размеры будут примерно соответствовать отступам сверху и снизу по 11 мм, слева 15 мм, а справа 10 мм.
Подключаем источник
Если шаблон формы отчёта, это основная визуальная составляющая JasperReports, то источники данных, это внутрення основа механизма обработки и построения отчёта. В качестве источника данных, могут служить, как базы или хранилища данных, так и например просто табличные или текстовые файлы. Но в моём случае с NORD POS в качестве источника данных выступит база данных Apache Derby, подключаемая через JDBC-драйвер. Так как библиотека драйвера клиента Apache Derby по-умолчанию не добавлена в окружение iReport, предварительно нужно указать к ней путь в параметрах(команда Tools -> Options) и перезагрузить iReport.
Изначально источник выбранный по-умолчанию пуст.
Но войдя в параметры можно будет его добавить, сделав источником информации для заполнения полей отчёта.
Указав параметры JDBC-драйвера и запустив предварительно NORD POS, можно проверить соединение. А для того, чтобы убедиться, что поля базы данных доступны JasperReports, сделаем простой запрос.
<queryString>
<![CDATA[SELECT
REFERENCE
, CODE
, NAME
, PRICEBUY
, PRICESELL
FROM
PRODUCTS]]>
</queryString>
Наш источник готов к использованию.
Пишем запрос
Это был очень простой SQL-запрос, в результате его выполнения мы получим только список товаров хранящийся в базе данных NORD POS. Но обычно в отчётах приходится использовать более сложные конструкции. Кроме названий самих товаров хорошо было-бы получить названия категорий товаров для последующий группировки товаров в нашем отчёте. А, так как цена продажи товаров у нас не содержит налога, то ещё необходимо получить ставку налога по каждой позиции товара в справочнике. В итоге, для дальнейшего построения отчёта, будем использовать SQL-запрос такого вида:
SELECT
PRODUCTS.NAME AS PRODUCT_NAME
, PRODUCTS.REFERENCE AS PRODUCT_REFERENCE
, PRODUCTS.PRICESELL AS PRODUCT_PRICESELL
, PRODUCTS.CATEGORY AS CATEGORY_ID
, CATEGORIES.NAME AS CATEGORY_NAME
, TAXES.RATE AS TAX_RATE
FROM
PRODUCTS
LEFT OUTER JOIN CATEGORIES ON PRODUCTS.CATEGORY = CATEGORIES.ID
LEFT OUTER JOIN TAXCATEGORIES ON PRODUCTS.TAXCAT = TAXCATEGORIES.ID
LEFT OUTER JOIN TAXES ON TAXCATEGORIES.ID = TAXES.CATEGORY
ORDER BY CATEGORIES.NAME, PRODUCTS.NAME
При этом сразу хочу предупредить, при построении SQL-запросов постарайтесь избегать в них конструкций для группировки данных непосредственно на уровне источника, но не забывайте предварительно данные отсортировать. Лучше доверить все операции связанные с консолидацией информации непосредственно JasperReports, так ваш отчёт будет более универсальным для различных СУБД, а вы в дальнейшем более гибко сможете менять последовательность алгоритмов обработки по представлению данных в различной форме.
В итоге на панели структуры отчёта станут доступны поля из источника данных.
<field name="PRODUCT_NAME" class="java.lang.String"/>
<field name="PRODUCT_REFERENCE" class="java.lang.String"/>
<field name="PRODUCT_PRICESELL" class="java.lang.Double"/>
<field name="CATEGORY_ID" class="java.lang.String"/>
<field name="CATEGORY_NAME" class="java.lang.String"/>
<field name="TAX_RATE" class="java.lang.Double"/>
Размещаем данные
Когда данные доступны, самое время рассказать о том, как их разместить в отчёте. Для начала просто разместим полученные поля в секции Detail, автоматически подписи к полям будут вынесены в секцию заголовка Column Header.
<columnHeader>
<band height="25" splitType="Stretch">
<staticText>
<reportElement uuid="3f6fd221-3e67-43c8-99fb-42ba466921b2" x="0" y="0" width="105" height="20"/>
<textElement/>
<text><![CDATA[CATEGORY_NAME]]></text>
</staticText>
<staticText>
<reportElement uuid="94d5a5b4-7214-4859-a6d5-a6c42f4613f7" x="105" y="0" width="105" height="20"/>
<textElement/>
<text><![CDATA[PRODUCT_REFERENCE]]></text>
</staticText>
<staticText>
<reportElement uuid="d50c28cb-ea78-4b71-9be5-b0902bd20a3f" x="210" y="0" width="105" height="20"/>
<textElement/>
<text><![CDATA[PRODUCT_NAME]]></text>
</staticText>
<staticText>
<reportElement uuid="6ddf9669-96cc-4b68-9dc3-e5a9ae8378f7" x="315" y="0" width="105" height="20"/>
<textElement/>
<text><![CDATA[TAX_RATE]]></text>
</staticText>
<staticText>
<reportElement uuid="4d226419-7cf1-4bfe-97fa-3dc1ff0ef184" x="420" y="0" width="105" height="20"/>
<textElement/>
<text><![CDATA[PRODUCT_PRICESELL]]></text>
</staticText>
</band>
</columnHeader>
<detail>
<band height="36" splitType="Stretch">
<textField>
<reportElement uuid="2ba3f12f-05fc-459e-aafa-12fdc94f3b33" x="0" y="0" width="105" height="20"/>
<textElement/>
<textFieldExpression><![CDATA[$F{CATEGORY_NAME}]]></textFieldExpression>
</textField>
<textField>
<reportElement uuid="0305692c-11b5-4e56-a593-67d1e5125acc" x="105" y="0" width="105" height="20"/>
<textElement/>
<textFieldExpression><![CDATA[$F{PRODUCT_REFERENCE}]]></textFieldExpression>
</textField>
<textField>
<reportElement uuid="b6788acf-3936-473c-ba83-b6fc204b6df0" x="210" y="0" width="105" height="20"/>
<textElement/>
<textFieldExpression><![CDATA[$F{PRODUCT_NAME}]]></textFieldExpression>
</textField>
<textField>
<reportElement uuid="c7cf41b1-72db-4683-a83f-b7b1953a4f55" x="315" y="0" width="105" height="20"/>
<textElement/>
<textFieldExpression><![CDATA[$F{TAX_RATE}]]></textFieldExpression>
</textField>
<textField>
<reportElement uuid="fe6e95c1-b6c2-4cf2-94c3-3a22bc176fe9" x="420" y="0" width="105" height="20"/>
<textElement/>
<textFieldExpression><![CDATA[$F{PRODUCT_PRICESELL}]]></textFieldExpression>
</textField>
</band>
</detail>
Теперь, если нажмём на предпросмотр, то увидим наш список товаров из базы данных уже в виде отчёта.
Данные получены и отчёт сформирован, теперь можно приступить к их обработке и оформлению отчёта для более наглядного представления информации.
Оформляем шаблон
Начнём с самого простого, сделаем заголовок и нижний колонтитул нашего отчёта. Для этого в секцию Title добавим подпись и синий баннер для фона, а в Page Footer поле с подстановкой переменных для количества страниц.
<title>
<band height="36" splitType="Stretch">
<rectangle>
<reportElement uuid="9fb6b6a2-ea0f-4f4d-b1a6-a31857059071" style="banner" x="0" y="0" width="525" height="36"/>
<graphicElement>
<pen lineWidth="0.0"/>
</graphicElement>
</rectangle>
<staticText>
<reportElement uuid="5b0c017a-ff0b-436d-a40c-80193711879f" style="title" x="2" y="2" width="365" height="32"/>
<textElement>
<font fontName="SansSerif" pdfFontName="DejaVu Sans" isPdfEmbedded="false"/>
</textElement>
<text><![CDATA[Товары на продажу]]></text>
</staticText>
</band>
</title>
<pageFooter>
<band height="26" splitType="Stretch">
<line>
<reportElement uuid="fa91a55e-d7ff-40ea-9c32-850fe9e071d1" x="0" y="0" width="525" height="1"/>
<graphicElement>
<pen lineWidth="1.5" lineStyle="Double" lineColor="#000000"/>
</graphicElement>
</line>
<textField>
<reportElement uuid="c6de22d0-f93b-4514-bd5f-991f3096f696" style="base" x="449" y="6" width="39" height="20"/>
<textElement textAlignment="Right">
<font isBold="true"/>
</textElement>
<textFieldExpression><![CDATA[$V{PAGE_NUMBER}.toString().concat("/")]]></textFieldExpression>
</textField>
<textField evaluationTime="Report">
<reportElement uuid="5fe48711-cf48-4b70-8b17-0826c3aaf6ca" style="base" x="488" y="6" width="21" height="20"/>
<textElement>
<font isBold="true"/>
</textElement>
<textFieldExpression><![CDATA[$V{PAGE_NUMBER}]]></textFieldExpression>
</textField>
</band>
</pageFooter>
Для того, чтобы предать единообразность отчётам, рекомендую использовать набор стилей, сделав один из них основным по-умолчанию. Стили – это группа полей в начале нашего шаблона, они устанавливают общие параметры для оформления элементов отчёта, и могут быть заданны не только для текстовых элементов, но и для фигур и линий включённых в шаблон.
<style name="base" isDefault="true" hAlign="Left" vAlign="Middle" fontSize="12" isBold="false" isItalic="false" isUnderline="false" isStrikeThrough="false"/>
<style name="header" style="base" hAlign="Center" isBold="true"/>
<style name="title" style="base" forecolor="#FFFFFF" fontSize="20" isBold="true">
<paragraph leftIndent="24"/>
</style>
<style name="banner" backcolor="#003278" radius="5">
<box>
<pen lineWidth="0.0"/>
<topPen lineWidth="0.0"/>
<leftPen lineWidth="0.0"/>
<bottomPen lineWidth="0.0"/>
<rightPen lineWidth="0.0"/>
</box>
</style>
Делаем расчёты
При создании шаблонов в разделе переменных автоматически генерируется несколько счётчиков, их значение меняется в зависимости от положения в шаблоне. Например мы уже использовали PAGE_NUMBER для подсчёта страниц, при этом используя одну и туже переменную, но задав параметр evaluationTime=«Report» для второго поля, мы получили общее количество страниц в отчёте.
Для указания порядкового номера номенклатурной позиции в отчёте используем переменную COLUMN_COUNT, выбрав её из списка и вставив в выражение для расчёта значения поля.
<textField>
<reportElement uuid="852df6f7-daf1-4deb-8a05-94c0dba32355" style="base" x="10" y="2" width="25" height="32"/>
<textElement textAlignment="Center" verticalAlignment="Top"/>
<textFieldExpression><![CDATA[$V{COLUMN_COUNT}]]></textFieldExpression>
</textField>
Значение артикула PRODUCT_REFERENCE, так как оно содержит только цифры и знак минус в качестве разделителя между кодом группы и кодом товара, поместим в отдельный объект генерирующий штрих-код.
<jr:Code128 xmlns:jr=«jasperreports.sourceforge.net/jasperreports/components» xsi:schemaLocation=«jasperreports.sourceforge.net/jasperreports/components jasperreports.sourceforge.net/xsd/components.xsd» moduleWidth=«1.5» textPosition=«bottom» quietZone=«10.0»>
<jr:codeExpression><![CDATA[$F{PRODUCT_REFERENCE}]]></jr:codeExpression>
</jr:Code128>
Поле PRODUCT_NAME, PRODUCT_PRICESELL и TAX_RATE оставим таким, какими они были получены из база данных, только указав паттерны для PRODUCT_PRICESELL валюты, а для TAX_RATE процента.
<textField>
<reportElement uuid="aacdbf49-b841-46b4-b7bf-12fe12c7fb96" x="193" y="2" width="98" height="32"/>
<textElement verticalAlignment="Top"/>
<textFieldExpression><![CDATA[$F{PRODUCT_NAME}]]></textFieldExpression>
</textField>
<textField pattern="#,##0.00 ₸" isBlankWhenNull="false">
<reportElement uuid="4e564eb3-3c86-4d3b-831b-2038679ebb3f" x="296" y="2" width="84" height="32"/>
<textElement textAlignment="Right" verticalAlignment="Top"/>
<textFieldExpression><![CDATA[$F{PRODUCT_PRICESELL}]]></textFieldExpression>
</textField>
<textField pattern="#,##0 %" isBlankWhenNull="false">
<reportElement uuid="4e564eb3-3c86-4d3b-831b-2038679ebb3f" x="384" y="2" width="35" height="32"/>
<textElement textAlignment="Right" verticalAlignment="Top"/>
<textFieldExpression><![CDATA[$F{TAX_RATE}]]></textFieldExpression>
</textField>
Последним у нас идёт поле расчёта для получения суммы цены с налогом.
<textField pattern="#,##0.00 ₸" isBlankWhenNull="false">
<reportElement uuid="1158eb51-5046-4b15-88f2-2456bac9eea1" x="425" y="2" width="84" height="32"/>
<textElement textAlignment="Right" verticalAlignment="Top"/>
<textFieldExpression><![CDATA[$F{PRODUCT_PRICESELL} * (1.0 + $F{TAX_RATE})]]></textFieldExpression>
</textField>
Группируем поля
Ну и напоследок сделаем две группировки. Первая, сгруппируем нашу номенклатуру товаров по коду категории, посчитав с помощью Categories_COUNT сколько позиция входит в каждую.
<group name="Categories" isReprintHeaderOnEachPage="true">
<groupExpression><![CDATA[$F{CATEGORY_ID}]]></groupExpression>
<groupHeader>
<band height="33" splitType="Prevent">
<textField>
<reportElement uuid="8bcbd93c-b2e7-4abd-a377-a8658c3ef27a" style="base" x="40" y="6" width="148" height="20"/>
<textElement>
<font size="14" isBold="true"/>
</textElement>
<textFieldExpression><![CDATA[$F{CATEGORY_NAME}]]></textFieldExpression>
</textField>
<textField evaluationTime="Group" evaluationGroup="Categories" pattern="#,##0">
<reportElement uuid="871626e0-83a8-4591-bccc-fc1c507b5a60" x="384" y="6" width="125" height="20"/>
<textElement>
<font size="14" isBold="true"/>
</textElement>
<textFieldExpression><![CDATA[$V{Categories_COUNT}]]></textFieldExpression>
</textField>
</band>
</groupHeader>
</group>
Вторая, это финальная секция отчёта Summary, в ней REPORT_COUNT подсчитает сколько всего номенклатурных позиций вошло в наш отчёт.
<summary>
<band height="30">
<line>
<reportElement uuid="fa91a55e-d7ff-40ea-9c32-850fe9e071d1" x="10" y="1" width="503" height="1"/>
<graphicElement>
<pen lineWidth="1.0" lineStyle="Solid" lineColor="#000000"/>
</graphicElement>
</line>
<textField>
<reportElement uuid="e2e2a0be-0edc-4e04-b909-4452cab89989" x="384" y="6" width="125" height="20"/>
<textElement>
<font size="14" isBold="true"/>
</textElement>
<textFieldExpression><![CDATA[$V{REPORT_COUNT}]]></textFieldExpression>
</textField>
<staticText>
<reportElement uuid="9780a598-1afc-41a5-9363-ef4bf3dc6e02" x="40" y="6" width="148" height="20"/>
<textElement>
<font size="14" isBold="true"/>
</textElement>
<text><![CDATA[Всего позиций]]></text>
</staticText>
</band>
</summary>
Шаблон готов
Всё, можно нажимать просмотр, наш шаблон готов для формирования отчёта.
Теперь можно приступать к его интеграции в NORD POS, но этому будет посвящена вторая часть статьи. А в этой части я постарался максимально обобщённо и просто рассказать об основных действиях для создания шаблонов в iReport, так что описанное здесь пригодится не только пользователям NORD POS, а всем у кого появилась необходимость в первый раз создать собственный отчёт для библиотеки JasperReports.
Автор: Svininykh