На главную   На главную   Форумы Новости Документация Скачать Купить  
Регистрация  
Система Allegro
Oб Allegro Характеристики Пример конфигурации Документация База ошибок Развитие
Версия для печати К списку книг Вернуться к оглавлению Предыдущий параграф Следующий параграф
Поиск по книге

Глава 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:

  1. добавим в список таблиц в секции FROM таблицу SALE,
  2. в секцию WHERE условия объединения для этой таблицы и фильтрации по полю ENTRY_DATE.
  3. добавим еще в секцию 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. Остановим проект и выйдем из режима «Дизайнер ».



Пример создания склада в Allegro Наверх