Глава 10. СОЗДАЕМ ОТЧЕТЫ
Отчет о продажах – создаем оконный интерфейс.
Этот отчет мы построим на основе прямого SQL-запроса
к документам типа «Продажа».
Включим режим «Дизайнер» и создадим новый проект.
Главной форме придадим свойства:
Свойство |
Значение |
Name |
SaleReportForm |
Caption |
Отчет о продажах |
FormStyle |
fsMDIChild |
Сохраним все: модуль сохраним в файле sale_report
.pas, а проект – в файле sale_
report_project.ipr.
Добавим на форму компонент Panel с палитры Standard:
Свойство |
Значение |
Name |
Panel1 |
Caption |
|
Alignment |
alTop |
Добавим компонент PageControl с палитры Win32:
Свойство |
Значение |
Name |
PageControl1 |
Alignment |
alClient |
С помощью контекстного меню компонента PageControl1 добавим новую страницу:
и назначим ей свойства:
Свойство |
Значение |
Name |
tsChart |
Caption |
Диаграмма |
Добавим еще одну новую страницу и назначим ей свойства:
Свойство |
Значение |
Name |
tsGrid |
Caption |
Таблица |
Добавим на вторую страницу компонент DBGrid с палитры Data Controls
и придадим ему свойства:
Свойство |
Значение |
Name |
dbgReport |
Alignment |
alClient |
Добавим компоненты IBQuery и DataSource с палитры InterBase:
Придадим компоненту IBQuery1 свойства:
Свойство |
Значение |
Name |
qryReport |
Transaction |
MainConnection.MainTransaction |
Придадим компоненту DataSource1 свойства:
Свойство |
Значение |
Name |
dsrReport |
DataSet |
qryReport |
В свойстве DataSource сетки dbgReport укажем источник данных dsrReport.
Дважды щелкнем в Инспекторе объектов на свойстве SQL компонента qryReport
и впишем такой запрос:
select
O.SHORT_NAME GROUP_NAME,
COUNT(*) DOC_COUNT,
SUM(SI.QUANTITY) QUANTITY,
SUM(SI.AMOUNT_S) SALES,
SUM(SI.COST_S) COST,
SUM(SI.AMOUNT_S - SI.COST_S) EARNINGS
from
SALE_ITEM SI,
GOODS G,
OBJECT_NAMES O
where
SI.GOODS = G.ID and
G.GOODS_KIND = O.OBJECT_ID
group by
O.SHORT_NAME
order by
O.SHORT_NAME
Откроем запрос, установив у компонента qryReport свойство Active =
True.
Как видите, SQL-запрос с группировкой позволяет получить
очень мощный отчет за очень малое время. В данном
случае мы запросили количество позиций в документах, количество проданных
единиц, суммарные доходы от продаж, суммарную себестоимость всех
проданных единиц, и прибыль, сгруппировав все результаты по
видам товаров.
Улучшим отображение в сетке, назначив полям русские заголовки и
подправив ширину полей. Для этого дважды щелкнем на компоненте
qryReport и в появившемся редакторе полей с помощью контекстного меню
добавим все поля в список. В инспекторе объектов назначим
полям свойства:
Поле |
Alignment |
DisplayLabel |
DisplayWidth |
DisplayFormat |
GROUP_NAME |
taLeftJustify |
Группа |
30 |
|
DOC_COUNT |
taCenter |
Док-тов |
10 |
|
QUANTITY |
taCenter |
Продано, Ед. |
10 |
|
SALES |
taRightJustify |
Доход |
12 |
#,##0.00 |
COST |
taRightJustify |
Ср.Себест. |
12 |
#,##0.00 |
EARNINGS |
taRightJustify |
Прибыль |
12 |
#,##0.00 |
Закроем запрос qryReport, установив Active = False. Добавим
в список полей вычисляемое поле RATE типа Float в редакторе
полей и зададим ему свойства:
FieldKind |
Alignment |
DisplayLabel |
DisplayWidth |
DisplayFormat |
fkCalculated |
taCenter |
Коэфф. |
10 |
#0.00 |
Создадим у компонента qryReport обработчик события OnCalcFields:
procedure TSaleReportForm.qryReportCalcFields(DataSet: TDataSet);
begin
with DataSet do
if FieldByName('COST').AsCurrency <> 0 then
FieldByName('RATE').AsCurrency :=
FieldByName('SALES').AsCurrency/FieldByName('COST').AsCurrency;
end;
Cоздадим у формы SaleReportForm обработчик OnCreate и впишем в него
активизацию запроса:
procedure TSaleReportForm.FormCreate(Sender: TObject);
begin
qryReport.Open;
end;
Сохраним все изменения и запустим проект:
Усовершенствуем наш проект так, чтобы иметь возможность группировать результаты
не только по виду товара, но и по марке
. Для этого нам потребуется изменять текст запроса во время
выполнения программы, подставляя в качестве поля группировки либо GOODS
_KIND либо GOODS_MARK.
Добавим в текст модуля в раздел implementation константу:
const
SQL_SALE_REPORT =
'select'#13+
' O.SHORT_NAME GROUP_NAME,'#13+
' COUNT(*) DOC_COUNT,'#13+
' SUM(SI.QUANTITY) QUANTITY,'#13+
' SUM(SI.AMOUNT_S) SALES,'#13+
' SUM(SI.COST_S) COST,'#13+
' SUM(SI.AMOUNT_S - SI.COST_S) EARNINGS'#13+
'from'#13+
' SALE_ITEM SI,'#13+
' GOODS G,'#13+
' OBJECT_NAMES O'#13+
'where'#13+
' SI.GOODS = G.ID and'#13+
' G.%s = O.OBJECT_ID'#13+ //здесь будет подстановка имени поля
'group by'#13+
' O.SHORT_NAME'#13+
'order by'#13+
' O.SHORT_NAME';
Это практически тот же текст запроса. Обратим внимание,
что в строке, выделенной жирным шрифтом вместо имени поля
GOODS_KIND вставлена комбинация символов %s.
Мы будем использовать функцию format, которая умеет отыскивать в
строке знаки процентов и подставлять вместо комбинации %s строковые
выражения, которые можно передать ей в виде массива значений
.
Для того чтобы пользователь мог задать способ группировки нам понадобится
переключатель. Выберем верхнюю панель Panel1 и придадим ее свойству
значение Alignment = alRight. Панель прижмется к правому краю
. Расширим немного панель и поставим на нее компонент RadioGroup
с палитры Standard и зададим ему свойства:
Свойство |
Значение |
Caption |
Способ группировки |
Items |
По виду товара
По марке товара |
ItemIndex |
0 |
Name |
rgGroupMode |
Немного ниже этого компонента расположим кнопку Button с палитры Standard
и придадим ей свойства:
Свойство |
Значение |
Caption |
Запрос |
Name |
btnQuery |
Создадим кнопке обработчик события OnClick:
procedure TSaleReportForm.btnQueryClick(Sender: TObject);
begin
qryReport.Close;
case rgGroupMode.ItemIndex of
0: qryReport.SQL.Text := Format(SQL_SALE_REPORT, ['GOODS_KIND']);
1: qryReport.SQL.Text := Format(SQL_SALE_REPORT, ['GOODS_MARK']);
end;
qryReport.Open;
end;
Запустим проект и убедимся, что после переключения «Способа
группировки», нажав на кнопку «Запрос», мы получаем
либо запрос с группировкой по виду товара, либо -
по марке товара:
Отчет у нас пока строится по всем документам продаж.
Хорошо бы строить его для некоторого диапазона дат, который
пользователь мог бы установить произвольно. Для добавления этой возможности
нам понадобятся два компонента календарей DateEdit с палитры RxControls.
Добавим их на панель, расположив их один под другим
. Изменим текст запроса, хранящийся в константе SQL_
SALE_REPORT:
- добавим в список таблиц в секции FROM таблицу SALE,
- в секцию WHERE условия объединения для этой таблицы и фильтрации
по полю ENTRY_DATE.
- добавим еще в секцию WHERE условие HAS_ENTRY=
1, чтобы в отчет входили только те документы,
у которых установлена птичка «отгружено».
Изменения в тексте запроса показаны жирным шрифтом:
const
SQL_SALE_REPORT =
'select'#13+
' O.SHORT_NAME GROUP_NAME,'#13+
' COUNT(*) DOC_COUNT,'#13+
' SUM(SI.QUANTITY) QUANTITY,'#13+
' SUM(SI.AMOUNT_S) SALES,'#13+
' SUM(SI.COST_S) COST,'#13+
' SUM(SI.AMOUNT_S - SI.COST_S) EARNINGS'#13+
'from'#13+
' SALE_ITEM SI,'#13+
' SALE S,'#13+
' GOODS G,'#13+
' OBJECT_NAMES O'#13+
'where'#13+
' SI.ID = S.ID and'#13+
' S.HAS_ENTRY = 1 and'#13+
' S.ENTRY_DATE BETWEEN :DATE1 and :DATE2 and'#13+
' SI.GOODS = G.ID and'#13+
' G.%s = O.OBJECT_ID'#13+ //здесь будет подстановка имени поля
'group by'#13+
' O.SHORT_NAME'#13+
'order by'#13+
' O.SHORT_NAME';
В обработчик события OnClick кнопки добавим текст, показанный жирным
шрифтом:
procedure TSaleReportForm.btnQueryClick(Sender: TObject);
begin
qryReport.Close;
case rgGroupMode.ItemIndex of
0: qryReport.SQL.Text := Format(SQL_SALE_REPORT, ['GOODS_KIND']);
1: qryReport.SQL.Text := Format(SQL_SALE_REPORT, ['GOODS_MARK']);
end;
qryReport.ParamByName('DATE1').AsDateTime := DateEdit1.Date;
qryReport.ParamByName('DATE2').AsDateTime := DateEdit2.Date;
qryReport.Open;
end;
Из обработчика OnCreate формы удалим активизацию запроса, которая нам
больше не нужна, так как мы реализовали ее в
обработчике нажатия кнопки. Вместо этого добавим присвоение начальных значений
даты компонентам календарей:
procedure TSaleReportForm.FormCreate(Sender: TObject);
begin
DateEdit1.Date := EncodeDate(YearOf(Date), MonthOf(Date), 1);
DateEdit2.Date := Date;
end;
Мы устанавливаем в первом календаре первое число текущего месяца,
а во втором – сегодняшнюю дату.
Над календарями поставим компонент Label с палитры Standard и в
его свойство Caption впишем заголовок «Диапазон дат».
Запустим проект и убедимся, что изменение диапазона дат,
задаваемых календарями, влияет на результаты запроса. Остановим проект
, чтобы вернуться в режим дизайна.
Теперь мы займемся созданием диаграммы.
Щелкнем на закладке «Диаграмма» компонента PageControl1.
Щекнем на поверхности открывшейся в результате пустой страницы, чтобы
выбрать ее, как компонент.
Поставим на нее компонент DBChart c палитры DataControls:
Назначим ему свойства в Инспекторе объектов:
Свойство |
Значение |
Align |
alClient |
BevelOuter |
bvNone |
BorderStyle |
bsSingle |
Name |
ReportChart |
Настроим диаграмму.
Для этого нужно дважды щелкнуть на ней. Появится редактор
компонента TDBChart. Нажмем кнопку «Добавить». Появится множество
вариантов будущей диаграммы, из которых мы выберем пирог и
нажмем OK:
Этим действием мы создали серию Series1. Один компонент DBChart
способен одновременно отображать множество серий, поэтому на закладке «
Серии» (на которой мы находимся) для них
выделено место под целый список:
После того, как мы создали серию, нужно настроить
ее свойства. Для этого выберем в верхнем ряду закладок
закладку «Серии». После этого в нижнем ряду закладок
выберем закладку «Источник данных» и на ней в
выпадающем списке выберем «Набор данных»:
Этот выбор означает, что данные будут браться из компонента
типа TDataSet, например, в нашем случае - это
компонент запроса qryReport. Как только мы выбрали режим из
списка, появятся новые органы управления.
Установим следующие значения:
Свойство |
Значение |
Набор данных |
qryReport |
Метки |
GROUP_NAME |
Пирог |
EARNINGS |
Нажмем кнопку «Применить», затем кнопку «Закрыть».
Теперь откроем запрос qryReport, установив свойство Active = True
(работает тот запрос в компоненте qryReport, что мы
создали в самом начале).
Мы видим, что весьма неудачным оказалось сочетание отображения меток
вокруг пирога и расположения «легенды» справа, так
как это сильно сжало диаграмму по горизонтали. Настроим расположение
легенды так, чтобы она оказалась внизу. Для этого
вызовем редактор диаграмм дважды щелкнем и откроем закладку «Легенда
» в нижнем ряду. На страничке появится еще один
ряд закладок (третий) и среди них нам нужно
выбрать закладку «Позиция». На ней имеется группа радиокнопок
под названием «Позиция». Выберем положение «Нижний»:
Закроем редактор. Теперь диаграмма выглядит более приемлемо:
Еще раз вызовем редактор диаграмм.
На нижней закладке «Заголовки» уберем птичку в свойстве
«Видимо». Синяя надпись TDBChart в верхней части диаграммы
должна исчезнуть.
На нижней закладке «Панель» выберем закладку третьего уровня
«Градиент». Установим птичку «Видимо». Нажмем на
кнопку «Конец» и вместо желтого цвета выберем грязно
-голубой:
Закроем редактор.
Закроем запрос qryReport, установив свойство Active = False.
Выберем панель Panel1 и установим ее свойство внешней фаски BevelOuter
= bvNone. Вообще желательно избегать большого количества «рельефа
» в окнах, так как он, как правило
, скорее отвлекает пользователя, чем помогает ему работать.
Запустим проект.
Сузим диапазон дат до одного дня, чтобы увеличить неравномерность
диаграммы, выберем способ группировки по марка товара и нажмем
кнопку «Запрос».
Перед нами диаграмма распределения прибыли по маркам товара:
Итак, мы получили окно с двумя закладками. На
первой закладке пользователь сможет увидеть отчет в виде диаграммы,
а на второй – виде таблицы. Однако в таблице
пока что отображаются все сведения, а в диаграмме –
лишь распределение прибыли по группам (поле EARNINGS запроса).
Хотелось бы как-то иметь возможность отображать в диаграмме
другие поля.
Для того чтобы это реализовать, добавим на панель компонент
Label, снабдив его заголовком «Диаграмма» и компонент
ComboBox с палитры Standard, установив для него такие свойства
:
Свойство |
Значение |
Name |
cbSeriesValues |
Syle |
csDropDownList |
В обработчик OnCreate формы впишем текст, выделенный жирным шрифтом
:
procedure TSaleReportForm.FormCreate(Sender: TObject);
var
i: integer;
begin
DateEdit1.Date := EncodeDate(YearOf(Date), MonthOf(Date), 1);
DateEdit2.Date := Date;
for i := 1 to qryReport.Fields.Count - 1 do
cbSeriesValues.Items.Add(qryReport.Fields[i].DisplayLabel);
cbSeriesValues.ItemIndex := 0;
end;
Мы просто заполняем выпадающий список cbSeriesValues заголовками полей запроса.
Нумерация полей в компоненте запроса начинается с нуля. Мы
начинаем с единицы, следовательно, пропускаем первое поле (
GROUP_NAME). Нам оно не нужно, так
как это поле группировки, а для выбора режима отображения
в диаграмме потребуеются только численные поля.
Изменим также обработчик события OnClick кнопки «Запрос»:
procedure TSaleReportForm.btnQueryClick(Sender: TObject);
begin
qryReport.Close;
case rgGroupMode.ItemIndex of
0: qryReport.SQL.Text := Format(SQL_SALE_REPORT, ['GOODS_KIND']);
1: qryReport.SQL.Text := Format(SQL_SALE_REPORT, ['GOODS_MARK']);
end;
{назначаем поле в качестве источника Y значений для диаграммы}
Series1.YValues.ValueSource :=
qryReport.Fields[cbSeriesValues.ItemIndex + 1].FieldName;
qryReport.ParamByName('DATE1').AsDateTime := DateEdit1.Date;
qryReport.ParamByName('DATE2').AsDateTime := DateEdit2.Date;
qryReport.Open;
end;
Запустим проект.
Для изменения отображаемых в диаграмме величин нужно выбрать значение из
выпадающего списка и нажать кнопку «Запрос».
Нам осталось реализоветь экспорт набора данных в Excel и организовать
вызов «Отчета о продажах» из Главного меню программы
Allegro. Остановим проект и выйдем из режима «Дизайнер
».
|