Pull to refresh

Отчёты для NORD POS. Часть 1

Reading time 14 min
Views 9.7K

Берём данные, 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
<?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, сделаем простой запрос.


Этот запрос, но в формате XML
	<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, так ваш отчёт будет более универсальным для различных СУБД, а вы в дальнейшем более гибко сможете менять последовательность алгоритмов обработки по представлению данных в различной форме.

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


А в XML-шаблоне появится набор тегов
	<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.

В XML-шаблоне будут сгенерированы секции columnHeader и detail
	<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
	<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
	<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 процента.


Поля PRODUCT_NAME, 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.
Tags:
Hubs:
+14
Comments 0
Comments Leave a comment

Articles