Работа с Oracle 3: Печать



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

В этой статье я покажу как настроить печать из приложения с помощью системы Rave Reports. Ведь без печатных форм документов наша система теряет смысл существования, получается что есть входящяя информация, которую вводят посредством экранных форм, но нет выходящей. Таким образом, система перестаёт выполнять свою функцию.
Итак, продолжаем модернизировать разработанную нами в предыдущих статьях мини-информационную систему. В выходящей документации информация чаще всего группируется, суммируется, такая документация носит аналитический характер, и предназначена в основном для руководителей. Для реализации данной функции (аналитики), используются SQL- запросы. Формирование запросов возможно выполнять либо в клиентской части информационной системы (т.е. в самом приложении), либо в серверной (на сервере Oracle, где находится база данных). Как сказал один умный человек –

Я предпочитаю решать большинство проблем на уровне СУБД. Если что-то можно сделать в СУБД, я так и сделаю. Для этого есть две причины. Первая и главная состоит в том, что если встроить функциональность в СУБД, то ее можно будет применять где угодно. Я не знаю серверной операционной системы, для которой нет реализации СУБД Oracle. Одна и та же СУБД Oracle со всеми опциями работает везде — от Windows до десятков версий ОС UNIX и больших ЭВМ типа OS/390.

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

Для печати заказа:

CREATE OR REPLACE VIEW ZAKAZ_REPORT
AS 
SELECT zakaz.nomer, zakaz.DATA, klient.naimen klient, klient.adres, klient.fax,
klient.tel, zakaz.id_zak
FROM zakaz, klient
WHERE ((klient.id_kl = zakaz.id_kl));

Структура данного запроса такая:

Здесь отмечу только переименование столбца klient.naimen klient, т.е. в представлении он будет называться klient.

CREATE OR REPLACE VIEW POS_ZAK_REPORT
AS 
SELECT pos_zakaza.id_zak, pos_zakaza.kolvo, izdelie.naimen izdelie, izdelie.charact
FROM pos_zakaza, izdelie
WHERE ((izdelie.id_izd = pos_zakaza.id_izd));

Структура данного запроса такая:

Для печати сводного отчёта:

CREATE OR REPLACE VIEW KLIENT_SVOD
AS 
SELECT Distinct klient.naimen, klient.id_kl
FROM klient
WHERE EXISTS (Select * from zakaz where klient.id_kl=zakaz.id_kl);

Здесь конструкция Distinct означает отбор неповоторяющихся значений, ну конечно, ведь если заказчик сделает два или три заказа, то запрос отберёт его так же два или три раза, как будто это три разных заказчика. А также в условии отбора конструкция Exists помогает отобрать только тех клиентов, для которых создан заказ.

CREATE OR REPLACE VIEW POS_ZAK_SVOD
AS 
SELECT SUM (pos_zakaza.kolvo) kolvo, pos_zakaza.id_zak, izdelie.naimen,
izdelie.charact
FROM pos_zakaza, izdelie
WHERE ((izdelie.id_izd = pos_zakaza.id_izd))
GROUP BY pos_zakaza.id_zak, izdelie.naimen, izdelie.charact;

Структура данного запроса полностью соответствует предыдущему.

Здесь переименовывается столбец, в котором идёт суммирование количества заказанных изделий SUM (pos_zakaza.kolvo) kolvo. В данном представлении выполняется группировка по столбцам pos_zakaza.id_zak, izdelie.naimen, izdelie.charact, т.е. происходит суммирование значения Количества изделий по Заказу, Изделию, Характеристике изделия.

Изменения с базой закончены, теперь приступаем к доработке приложения. Сначала добавим в приложение компоненты для доступа к нашим представлениям. Здесь возможны два варианта: использовать Table’ы, или использовать Query. При работе с локальными базами данных чаще используется Table. С его помощью проще не только просматривать таблицу базы данных, но и модифицировать записи, удалять их, вставлять новые записи. Но при работе с серверными базами данных компонент Table становится мало эффективным. В этом случае он создаёт на компьютере пользователя временную копию серверной базы данных и работает с этой копией. Естественно, что подобная процедура требует больших ресурсов и существенно загружает сеть.
Этот недостаток отсутствует в компоненте Query. Если запрос SQL сводится к просмотру таблицы (запрос Select), то результат этого запроса (а не сама исходная таблица) помещается во временном файле на компьютере пользователя. Благодаря такой организации работы эффективность Query при работе в сети становится много выше, чем эффективность Table.
Таким образом наш выбор остановится на Query, а в приложении мы лишь установим фильтр, который можно реализовать добавив в теле запроса условие отбора.
Итак, добавляем четыре компонента Query с закладки BDE, устанавливаем их свойство DatabaseName (имя нашей базы). У меня получилось соответственно четыре Query: Zakaz_report, Pos_zak_report, Klient_svod и Pos_zak_svod. Теперь необходимо прописать в свойстве SQL каждого из компонентов запрос к базе данных - получение данных созданных нами ранее представлений.

Для Zakaz_report:

Select * from ZAKAZ_REPORT
Where ID_ZAK=:id_zak

Здесь :id_zak – это параметр, т.е. это значение столбца ID_ZAK в представлении ZAKAZ_REPORT которое нас интересует, а интересовать нас будет текущее значение столбца ID_ZAK Table’а Zakaz, т.е. значение ID_ZAK текущей строки в таблице Заказов, ведь печать мы будем делать из журнала заказов, для получения отчёта по выбранному заказу.

Для Pos_zak_report:

Select * from POS_ZAK_REPORT
Where ID_ZAK=:id_zak

Здесь :id_zak – также параметр, значение столбца ID_ZAK Table’а Zakaz. Т.е. будут отобраны все строки для выбранного заказа.

Для Klient_svod:

Select * from KLIENT_SVOD

Без комментариев.

Для Pos_zak_svod:

Select * from POS_ZAK_SVOD

Здесь я думаю тоже всё понятно.

Теперь необходимо у всех Query зайти в свойство Params (кнопка с тремя точками), и в свойстве DataType единственного параметра (см. выше) выбрать ftInteger, иначе будет ругаться на то что не определён тип параметра. После этого можно сохранить весь проект. Затем как обычно идём в редактор полей наших Query (двойной щелчок мышью), и добавляем все столбцы (Add all fields).
Осталось добавить на главной форме кнопку Печать и подпункт меню Сводный отчёт пункта Печать, вот что получилось:

Таким образом, если необходимо распечатать информацию по заказу (например, для заказчика), нажимаем кнопку Печать, если необходимо предоставить сводный отчёт по заказчикам (для начальника) – заходим в пункт меню Печать->Сводный отчёт.
Ну а теперь приступим к программированию печати. Схема печати отчёта по заказу будет выглядеть следующим образом: мы встаём на нужную позицию (заказ), нажимаем кнопку Печать, соответственно в Query Zakaz_report и Pos_zak_report будут загружаться соответствующие данные (не забываем про параметры), затем компоненты с закладки Rave будут передавать эти данные в отчёт. Соответственно для печати сводного отчёта схема та же.
Для связи наших Query c отчётом добавим компонент RvProject с закладки Rave, четыре компонента RvQueryConnection и свяжем их с нашими Query (свойство Query у каждого RvQueryConnection), а также RvTableConnection для связи с Table’ом Zakaz (объясню позже).
Далее запускаем редактор Rave Reports (Tools->Rave Reports Designer). Прежде чем начать работать с редактором, необходимо сохранить проект (File->Save As) в папку с приложением, я например, создал в папке с приложением папку reports и сохранил новый проект туда. Сохранять лучше всего в папку с приложением потому, что в компоненте RVProject в свойстве ProjectFile необходимо указать путь к файлу отчёта. Если например мы сохраним файл отчёта в каком-то другом месте (например C:Documents and settings...), при поставке приложения клиентам прийдётся позаботиться об указании пути к файлу отчёта, так как например у тебя система стоит на диске С, а у того кому ты поставляешь она установлена например на D, т.е. у того человека папки Documents and settings на диске С нет. Прописав же в свойстве ProjectFile компонента RVProject просто имя файла отчёта (по умолчанию Project1.rav) и сохранив его в папку с приложением ты создаёшь так называемый абсолютный путь к файлу отчёта. Теперь, при поставке приложения клиенту, тебе достаточно сам файл приложения и файл отчёта скопировать в одну папку, и приложение само найдёт этот файл (отчёт), так как при отсутствии пути к файлу, компонент RVProject в первую очередь ищет файл в папке с приложением. У меня в свойстве ProjectFile компонента RVProject прописано reportsProject1.rav, т.е. при поставке клиенту нужно будет создать папку для приложения (например, C:Журнал заказов), а в ней создать папку reports и скопировать туда файл отчёта.
Итак, приступаем к разработке отчёта. Первое что нам надо сделать, это связаться с компонентами Rave в приложении для получения данных. Для этого заходим в меню File->New data object (или пиктограмма на панели), в окне Data connections выбираем Direct data view, затем в списке Active data connections выбираем наши RVQueryConnection’ы (RVTableConnection’ы не забываем), можно через SHIFT, и нажимаем Finish. Всё связь настроена, теперь справа в дереве появилась новая ветка Data view dictionary, в которой ты можешь наблюдать наши DataView:

DataView1 связан с RVQueryConnection1 (представление ZAKAZ_REPORT), DataView2 связан с RVQuryConnection2 (представление POS_ZAK_REPORT), DataView3 связан с RVQueryConnection3 (представление POS_ZAK_SVOD), DataView4 связан с RVTableConnection1 (таблица ZAKAZ), DataView5 связан с RVQueryConnection4 (представление KLIENT_SVOD).

Приступим к проектированию отчёта по заказу. Для этого добавим компонент Region с закладки Report, и растянем его на весь лист отчёта. Затем с этой же закладки добавим последовательно компоненты: два Band’а и DataBand. Первый бэнд у нас будет содержать атрибуты заказа (представление ZAKAZ_REPORT), второй будет играть роль шапки у позиций заказа, третий (DataBand) будет отображать строки позиций заказа. Не буду расписывать как добавлять компоненты, думаю вы и так поняли, выглядеть будет так:

Здесь поля с надписями это компонент Text с закладки Standart, поля с данными это компонент DataText с закладки Report. В свойстве каждого из DataText’а DataView установим DataView1 (представление ZAKAZ_REPORT), а в свойстве DataField – соответствующее поле (см. скриншот).
Чтобы посмотреть что получилось, надо добавить следующий код при нажатии кнопки Печать:

procedure TForm3.Button1Click(Sender: TObject);
begin
Zakaz_report.Close;
Zakaz_report.ParamByName('id_zak').Value:=
Form3.ZakazID_ZAK.Value;
Zakaz_report.Open;
 
Pos_zak_report.Close;
Pos_zak_report.ParamByName('id_zak').Value:=
Form3.ZakazID_ZAK.Value;
Pos_zak_report.Open;
 
RVProject1.Open;
RVProject1.ExecuteReport('Report1');
RVProject1.Close;
end;

Здесь для каждого Query указывается параметр, на основе которого идёт отбор данных (по столбцу ID_ZAK), а также открывается и запускается отчёт. Давайте ещё добавим компонент RVSystem с закладки Rave, и укажем в свойстве RVProject Engine данный компонент. Компонент RVSystem позволит нам управлять параметрами отчёта, установив например свойство DefaultDest в rdPrinter вместо rdPreview (по умолчанию), в окошке появляющемся перед печатью будет стоять пункт Printer вместо Preview, т.е. по умолчанию будет предлагаться распечатать отчёт вместо просмотра. А установив свойство SystemSetups->ssAllowSetup в false окошко диалога вообще не будет появляться, т.е. будет выполняться действие установленное в свойстве DefaultDest, например rdPreview – сразу просмотр отчёта.
Теперь можете запустить приложение и встав на нужный заказ нажать кнопку Печать для просмотра отчёта, у меня получилось так:

Осталось только «нарисовать» печать позиций заказа, и наш отчёт будет готов. Возвращаемся к Rave Report. На втором бэнде рисуем шапку для позиций заказа, а на третьем (DataBand), симметрично шапке добавляем поля данных указав в свойстве DataView этих полей DataView2 (представление POS_ZAK_REPORT), а в свойстве DataField – соответствующее поле данных.
В свойстве DataView DataBand’а укажите DataView2, затем заходим в Band Style editor данного DataBand’а (свойство BandStyle), и поставим галочку Detail(D) и NewPage(P). Ну и последний штрих – свойство BandStyle у первого бэнда ставим BodyHeader(B) (NewPage(P) не ставим), у второго бэнда (шапки) – BodyHeader(B), NewPage(P), чтобы шапка появлялась и на последующих страницах, а так же свойство ControllerBand у данного бэнда – DataBand1.
Всё теперь можете смотреть отчёт, у меня он выглядит так:

Ну я думаю как оформить отчёт вы догадаетесь и без меня. Стоит отметить только ещё одну вещь. Если у вас не установлен принтер (ну нет принтера), то вам можно установить виртуальный принтер (печать в файл), иначе вы не сможете просматривать отчёты на своём компьютере. Для этого заходим в Панель управления->Принтеры и факсы, устанавливаем новый принтер. Выбираем локальный принтер, отменяя автоматическое определение, далее выбираем Использовать порт: File(печать в файл), далее выбираем любую марку принтера, ну а в остальном вы и без меня разберётесь.

Нам осталось создать сводный отчёт (а то начальник обидится). Для этого в Rave Reports заходим в меню File->New Report. В дереве справа появится созданный отчёт Report2. Добавляем Region, два бэнда и три датабэнда. На первом бэнде будет название отчёта, на втором – шапка для позиций заказа, датабэнды будут отображать данные, вот как всё это выглядит в дизайнере:

Теперь по порядку о настройках:

1.	Band1 – BandStyle: Body Header(B), First(1)
2.	DataBand1 – BandStyle: Group Header(G), First(1), New Page(P)
                    DataView: DataView5
3.	DataBand2 – BandStyle: Detail(D), First(1), New Page(P)
                    DataView: DataView4
                    ControllerBand: DataBand1
                    MasterDataView: DataView5
                    MasterKey: ID_KL
                    DetailKey: ID_KL
4.	Band2 - BandStyle: Body Header(B), First(1), New Page(P)
                ControllerBand: DataBand3
5.	DataBand3 - BandStyle: Detail(D), First(1), New Page(P)
                    DataView: DataView3
                    ControllerBand: DataBand2
                    MasterDataView: DataView4
                    MasterKey: ID_ZAK
                    DetailKey: ID_ZAK

Здесь я указал свойства отличные от свойств по-умолчанию, т.е. после добавления компонента надо установить эти свойства.А теперь вспоминаем добавленный RVTableConnection связанный с Tablе’ом Zakaz, получается данные у нас берутся из таблицы, а не запросом, ну конечно, а зачем нам нагромождать приложение лишними компонентами, если можно обойтись нашим Table’ом, ведь никаких ограничений на отбор данных в нём не стоит, и нас устроят те данные, которые в нём содержатся.
С отчётом закончим, перейдём к программированию. Осталось только добавить следующий код на нажатие пункта меню Печать->Сводный отчёт:

procedure TForm3.N6Click(Sender: TObject);
begin
Klient_svod.Close;
Klient_svod.Open;
 
Pos_zak_svod.Close;
Pos_zak_svod.Open;
 
RVProject1.Open;
RVProject1.ExecuteReport('Report2');
RVProject1.Close;
end;

Здесь я думаю всё понятно, принцип тот же самый как и при печати отчёта по заказу, отличие только в имени отчёта – Report2. Вот как отчёт выглядит в действии:

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