Найти в Дзене
XSQUARE

Интегрируем XSQUARE - REPORTS в приложение Oracle Apex

ФОРС Дистрибуция – крупнейшая и одна из самых известных компаний по поставке решений Oracle. В компании довольно много приложений, реализованных на Oracle ApEx. Понятно, что без формирования документов в некоторых из них не обойтись. Документы нужны, как правило, в виде файлов в формате MS Word, либо Adobe Acrobat. Для того, чтобы это делать, много лет назад мы использовали генератор отчётов Jasper Reports. Заметим, что у него имелся встроенный редактор шаблонов. Однако из-за ошибок в «движке», получаемые файлы зачастую «плыли» в разметке – окончательный их вид получался искаженным, по сравнению с размеченным в шаблоне. В какой-то момент мы решили поискать замену. Так мы перешли на генератор отчётов Oracle BI Publisher.
В нем дела пошли лучше. Но не идеально. Как отдельный продукт не доступен, а входит в состав большого набора разных приложений, под общим названием Oracle Analytics Server – явно больше, чем нам надо. Как следствие такого варианта поставки, получается громоздкий и сло
Оглавление

Предпосылки

ФОРС Дистрибуция – крупнейшая и одна из самых известных компаний по поставке решений Oracle. В компании довольно много приложений, реализованных на Oracle ApEx. Понятно, что без формирования документов в некоторых из них не обойтись. Документы нужны, как правило, в виде файлов в формате MS Word, либо Adobe Acrobat.

Для того, чтобы это делать, много лет назад мы использовали генератор отчётов Jasper Reports. Заметим, что у него имелся встроенный редактор шаблонов. Однако из-за ошибок в «движке», получаемые файлы зачастую «плыли» в разметке – окончательный их вид получался искаженным, по сравнению с размеченным в шаблоне. В какой-то момент мы решили поискать замену. Так мы перешли на генератор отчётов Oracle BI Publisher.

В нем дела пошли лучше. Но не идеально.

Что не так с Oracle BI Publisher:

Как отдельный продукт не доступен, а входит в состав большого набора разных приложений, под общим названием Oracle Analytics Server – явно больше, чем нам надо.

Как следствие такого варианта поставки, получается громоздкий и сложный во всех смыслах инструмент. Работает в виде Java-приложений на базе Oracle WebLogic Server. То есть чтобы использовать BI Publisher, надо установить, а потом администрировать Oracle WebLogic Server, для чего нужен специально обученный человек: высокой квалификации, дорогостоящий и всегда сильно загруженный другой работой.

Для вёрстки шаблонов BI Publisher применяется отдельно устанавливаемое на компьютер разработчика приложение BI Publisher Desktop. Оно интегрируется в MS Word, становясь своего рода плагином для этого редактора. А уже для работы с шаблоном, нужно настроить точно такие же соединения к Базе данных, как и для сервиса. При этом может оказаться что сервер, где крутится сервис, и компьютер разработчика - в разных сетях. Нужно это учитывать, настраивать отдельный сетевой доступ и т.д. В общем, это неудобство. Более того, на компьютеры наших разработчиков BI Desktop не так просто поставить. Снова нужен специально обученный человек – разбираться теперь уже с рабочими машинами разработчиков.

Далее. Работа BI Publisher основана на постоянной связи с БД (как и у Jasper Reports, кстати). Отчёт, кроме шаблона, имеет прописанный там же на сервере коннект к базе и необходимый для заполнения полей шаблона SQL запрос. Сервер постоянно подключен к БД и держит свои сессии в рабочем состоянии. Если у нас топология продуктивных контуров разнесена между серверами, виртуалками и датацентрами, то иногда возникает проблема в виде потери связи с БД. Пришлось сделать мониторинг соединения…

И, наконец, то, что стало последней каплей. Есть у нас документ, имеющий «подложку» под текст в виде рисунка. Это официальный документ - сертификат. Его упростить нельзя. Так вот, BI Publisher упрямо выдает его так: подложка отдельно, на отдельной странице, текст отдельно – на другой! В оригинале, то есть в шаблоне, это одна страница.

Именно из-за этого бага, в частности, захотелось попробовать применить XREPORTS.

Когда познакомились с продуктами от компании «Хи Квадрат», заинтересовал XREPORTS как отдельный компонент. Сразу возникла идея попробовать XREPORTS в продуктивном контуре.

Для начала приведу принципиальные отличия и видимые на первый взгляд преимущества XREPORTS от BI Publisher.

XREPORTS:

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

У данного продукта лучшая перспектива в использовании у нас в стране - в связи с уходом Oracle.

Нет никакой связи с базами!

Это ключевое. Влияет сразу на многое:
- нет проблем с сетевым доступом к БД,
- использовать генератор отчётов можно из любой системы: - ApEx это, не ApEx, База Данных Oracle / Postgres – не имеет значения (связи с базой ведь не требуется), на Python и т.д.
- на комп не нужно ничего устанавливать! MS Word, либо гуглдок, либо любой другой любимый редактор с выходом «вордового» файла.

Реализация

Итак, на одной из страниц Apex у нас есть кнопка для получения документа в формате Word с графической подложкой – картинкой через BI Publisher. Здесь же будем делать новую кнопку – через XREPORTS. У пользователя будет доступно оба варианта чтобы иметь возможность, во-первых, сравнивать их работу сколь угодно долгое время. Во-вторых, если с XREPORTS что-то пойдёт не так (новый инструмент всё ж), пользователь всегда мог бы использовать предыдущий вариант.

1. Делаем в БД Oracle новую View с данными, удобными для применения в XREPORTS

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

Для отправки запроса в XREPORTS методом POST нам понадобится body с данными в формате JSON. В этой вьюшке мы их приготовим.

Фрагмент SQL для View:
SELECT t.ID,
t.SERT_TYPE,
t.ROW_1,
t.ROW_2,
t.FROM_DATE,
t.TO_DATE,
t.FULL_NAME,
t.ADDRESS,
t.CONTRACT_NO,
t.CONTRACT_FROM_DATE,
t.ROW_3,
t.SIGN_POST,
t.SIGN_IOF,
xREPORTS.JSON_begin('W') –-- здесь с помощью функции готовится шапка JSON
|| json_serialize ( --- json_serialize
JSON_OBJECT (    --- объект 1 уровня
'ID' VALUE t.ID,
'SERT_TYPE' VALUE t.SERT_TYPE,
'ROW_1'     VALUE t.ROW_1,
'ROW_2'     VALUE t.ROW_2,
--- эта группа полей для печати или сокрытия элементов отчёта в зависимости от условий – типа сертификата
'SERT_HW'   VALUE
DECODE(t.SERT_TYPE, 'SERT_HW', 'true', 'false'),
'SERT_HW_F' VALUE
DECODE(t.SERT_TYPE, 'SERT_HW', 'false', 'true'),
'SERT_HW_ORA' VALUE
DECODE(t.SERT_TYPE, 'SERT_HW_ORA', 'true', 'false'),
'SERT_INSTALL' VALUE
DECODE(t.SERT_TYPE, 'SERT_INSTALL', 'true', 'false'),
'SERT_INSTALL_F' VALUE
DECODE(t.SERT_TYPE, 'SERT_INSTALL', 'false', 'true'),
'SERT_SW_SOLAR' VALUE
DECODE(t.SERT_TYPE, 'SERT_SW_SOLAR', 'true', 'false'),
'SERT_SW' VALUE
DECODE(t.SERT_TYPE, 'SERT_SW', 'true', 'false'),
'FROM_DATE' VALUE t.FROM_DATE,
'TO_DATE'   VALUE t.TO_DATE,
'FULL_NAME' VALUE t.FULL_NAME,
'ADDRESS'   VALUE t.ADDRESS,
'CONTRACT_NO' VALUE t.CONTRACT_NO,
'CONTRACT_FROM_DATE' VALUE t.CONTRACT_FROM_DATE,
'ROW_3'     VALUE t.ROW_3,
'SIGN_POST' VALUE t.SIGN_POST,
'SIGN_IOF'  VALUE t.SIGN_IOF,
'TABLE1' VALUE  --- этот элемент для таблицы в документе
JSON_OBJECT ( --- объект 2 уровня
'rows' VALUE
JSON_ARRAYAGG ( --- массив для таблицы
JSON_OBJECT  ( -- строка таблицы
'ITEM_NAME' VALUE t.ITEM_NAME,
'ITEM_ATTR' VALUE t.ITEM_ATTR,
'ITEM_CNT'  VALUE t.TO_CHAR (t.ITEM_CNT)
) RETURNING CLOB -- строка таблицы
) --- массив для таблицы
) RETURNING CLOB --- объект 2 уровня
) RETURNING CLOB PRETTY --- объект 1 уровня
) --- json_serialize
|| xREPORTS.JSON_end('W') AS PRETTY_JS, -- функция делает хвост JSON
FROM DEMO_sertificate_for_docx t
GROUP BY t.ID,
t.SERT_TYPE,
ROW_1,
ROW_2,
FROM_DATE,
TO_DATE,
FULL_NAME,
ADDRESS,
CONTRACT_NO,
CONTRACT_FROM_DATE,
ROW_3,
SIGN_POST,
SIGN_IOF;

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

{
"template":
{
"uri": "local",
"id": "fd/sert"
},
"input-data":
---- выше – это шапка, которая прописана в функции xREPORTS.JSON_begin('W')
---- ниже – то, что мы получили в запросе через json_serialize
{
"ID" : 5,
"SERT_TYPE" : "SERT_HW_ORA",
"ROW_1" : "О ПРЕДОСТАВЛЕНИИ ДОСТУПА К УСЛУГАМ ТЕХНИЧЕСКОЙ ПОДДЕРЖКИ ОБОРУДОВАНИЯ (\"ОБОРУДОВАНИЯ\")",
"ROW_2" : "ТЕХНИЧЕСКАЯ ПОДДЕРЖКА ОКАЗЫВАЕТСЯ НА СЛЕДУЮЩЕЕ ОБОРУДОВАНИЕ:",
"SERT_HW" : "false",
"SERT_HW_F" : "true",
"SERT_HW_ORA" : "true",
"SERT_INSTALL" : "false",
"SERT_INSTALL_F" : "true",
"SERT_SW_SOLAR" : "false",
"SERT_SW" : "false",
"FROM_DATE" : "01.03.2023",
"TO_DATE" : "30.04.2023",
"FULL_NAME" : "ООО \"Тест==тест\"",
"ADDRESS" : "Далекий полустаночек",
"CONTRACT_NO" : null,
"CONTRACT_FROM_DATE" : null,
"ROW_3" : "СЕРТИФИКАТ ВЫДАН ЦЕНТРОМ ТЕХНИЧЕСКОЙ ПОДДЕРЖКИ\nОБОРУДОВАНИЯ\n \"ООО …. \"",
"SIGN_POST" : "Генеральный директор",
"SIGN_IOF" : "И.И. Иванов ",
"TABLE1" :
{
"rows" :
[
{
"ITEM_NAME" : "test 2",
"ITEM_ATTR" : null,
"ITEM_CNT" : "1"
},
{
"ITEM_NAME" : "test 1",
"ITEM_ATTR" : null,
"ITEM_CNT" : "1"
},
{
"ITEM_NAME" : "Memory upgrade 768 GB (twelve 64 GB) DIMM (for field installation)",
"ITEM_ATTR" : "-",
"ITEM_CNT" : "8"
},
{
"ITEM_NAME" : "Another Database Server XYZ-2 with 768 GB",
"ITEM_ATTR" : null,
"ITEM_CNT" : "4"
},
{
"ITEM_NAME" : "Somekind Storage Server XXX High Capacity",
"ITEM_ATTR" : null,
"ITEM_CNT" : "8"
}
]
}
}
---- ниже – хвост из функции xREPORTS.JSON_end('W')
,
"options":
{
"enable-debug-report-save": false,
"enable-binary-output": true,
"formatting":
{
"tables":
{
"enable-cells-auto-merge": false
}
}
}
}

2. Делаем в Apex модернизацию рабочей страницы

На странице в разделе Function and Global Variable Declaration прописываем JavaScript функцию, которая будет выполнять запрос в XREPORTS и получать от него файл документа.

-2

В неё подадим два параметра – имя сгенерированного файла документа и наш CLOB с JSON:

function GetWordDocument_xReport(p_filename, p_json) {
var url = "https://reports.aaa.bbb/word_report_json";
var filename = p_filename + '.docx';
fetch(url, {
body: JSON.stringify(p_json),
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin' : '*'
},
})
.then(response => response.blob())
.then(response => {
const blob = new Blob([response], {type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'});
const downloadUrl = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = downloadUrl;
a.download = filename
document.body.appendChild(a);
a.click();
});
};

Интерактивный отчёт

В нем в запрос SQL добавляем три поля: x.pretty_js, Link_X2, FileName

select ds.id,
ds.created_on,
ds.sert_type,
ds.from_date,
ds.to_date,
ds.sert_owner_id,
ds.signature_id,
'<a title="Выгрузить сертификат в формате Word" href="'
||:APP_BI_REPORT_URL
||'sert.xdo?id=aaa'||:P62_P||'&_xf=rtf&_xpt=1&_xt='
||to_char(ds.created_on,'dd.mm.yyyy')
||'&P_ID='
||ds.ID
||'"><span aria-hidden="true" class="fa fa-file-word-o fa-lg">' To_Word_BI,
--- новые три поля:
x.pretty_js, --- JSON для отправки в XREPORTS
'' Link_X2, --- здесь будет линк-кнопка запуска отчёта
'Sert_' || to_char(ds.created_on,'dd.mm.yyyy') as FileName
from JN_DEAL_SERTIFICATES ds, X2_V_DEMO_SERTIFICATE_DOCX x
where ds.ID = x.ID(+)

В интерактивном отчёте в колонке Link_X2:

- задаем тип колонки –> HTML Expression

- в разделе Settings - HTML Expression - > прописываем HTML-тэг, который отобразится в виде иконки и сделает редирект на нашу JS функцию GetWordDocument_xReport

<a href='javascript:GetWordDocument_xReport("Сертификат от&CREATED_ON.", &PRETTY_JS.)'>
<span class="t-Icon fa fa-file-word-o fa-lg"></span> </a>

Где:

"Сертификат от &CREATED_ON." – таким будет имя сгенерированного файла

&PRETTY_JS. – ссылка на колонку в нашем IG, содержащую JSON

-3

Проверим отчёты и сравним

Кликнув в гриде на кнопку в колонке Выгрузить в Word BI Publisher, получаем немного кривой документ. Графическая подложка напечаталась на первой странице, а всё содержимое – на второй

-4

А теперь кликаем кнопку в колонке Word XREPORTS.

Отчёт прекрасен! Результат соответствует шаблону, всё на своих местах.

-5

Отчёт у вас получится, но не сразу! :)

Будет еще одна проблема (гарантируем! :)
Это проблема с CORS -
Cross-Origin Resource Sharing

Дело вот в чём. Старый отчёт в BI Publisher выполняется путём перехода по URL на сервер отчёта и метод этого перехода (запроса) – GET. Проблемы с CORS не возникает.

Запрос к отчёту в
XREPORTS осуществляется методом POST. Ну, потому что параметры (имя шаблона и т.д.) и данные необходимо передать в теле body, а это возможно через POST.

И тут на защиту систем встает Браузер. Сторона, куда отправляем запрос POST, должна дать добро на это, ответив нашему браузеру кодом 200 и т.д. и т.п.
Если погуглить про
CORS, всё будет понятно объяснено, что это такое и мы тут повторять не будем. Там же можно найти варианты решений.

Мы нужный отклик браузеру прописали в Nginx, который стоит между браузером пользователя и сервером XREPORTS (это штатная конфигурация системы XSQUARE и подробно описана в документации).

Happy end

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

У нас есть демо-стенд, курсы для желающих понять в деталях особенности решений от Хи-квадрат, включая XReports. Обращайтесь. Расскажем и покажем больше!

Андрей Тахтаулов

https://forsd.ru/

E-mail: partner@fors.ru