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

Глава 10. СОЗДАЕМ ОТЧЕТЫ

Отчет о движении товаров

Предыдущий отчет мы построили исключительно на SQL-запросе к документам. Сейчас нам предстоит сделать отчет на основе SQL -запросов к таблице проводок ACC_TURN.

Заказчик желает видеть отчет в долларах США примерно такого вида :


Товар Начальный остаток (кол-во, сумма) Приход (кол-во, сумма) Расход (кол-во, сумма) Конечный остаток (кол-во, сумма) Средняя стоимость единицы Опт (Тек. цена,%) Розница (Тек. цена,%)

Заказчик желает, чтобы создать такой отчет можно было бы для произвольного диапазона дат и для каких-то определенных товаров и/или марок, например, для всех стиральных машин с марками BOSCH и SIEMENS. Еще он хочет, чтобы отчет можно было построить для какого- то одного склада или суммарно для всех складов.

Не исключено, что «монстры SQL» могут написать такой мудреный SQL-запрос, что он вернет нам весь этот отчет целиком. Но мы будем стараться придерживаться простых запросов и следить за самым главным показателем: скорость . Ничто так не расстраивает конечного пользователя, как если машина «долго думает». Особенно если это менеджер по продажам, которому нужно прямо сейчас ответить по телефону, сколько у него на складе стиральных машин.

Поэтому мы разобьем задачу на более простые задачи:

  • Первым SQL-запросом выясним начальные остатки для всех товаров
  • Вторым SQL-запросом выясним приход и расход для всех товаров за указанный промежуток дат
  • Затем как-то все это объединим по каждому товару .
  • Вычислим конечные остатки и средние стоимости единиц

Вызовем окно Интерактивного SQL.

SQL-запрос, возвращающий начальные остатки на складе 1007 на какую-то дату выглядит очень просто:

select object_id, sum(debit-credit) a, sum(quantity_debit – quantity_credit) q
from acc_turn
where acc_id = 1007 and op_date < ’01.06.2003’
group by object_id

Так же просто выглядит запрос, возвращающий приход и расход товара за определенный промежуток дат:

select object_id, sum(debit) ad, sum(credit) ac, sum(quantity_debit) qd, sum(quantity_credit) qc
from acc_turn
where acc_id = 1007 and op_date between ’01.06.2003’ and ’15.07.2003’
group by object_id

И что очень важно – такие запросы работают быстро. Однако как объединить результаты работы этих двух запросов так, чтобы для каждого товара object_id одной строкой вернуть результаты первого и второго запросов одновременно? Здесь есть один способ, который мы назвали «диагональный метод сборки сводного отчета» и рекомендуем этот метод разработчикам. Суть этого метода в том, что мы создаем в базе данных хранимую процедуру, которая выполняет несколько SQL-запросов последовательно , один за другим, возвращая результаты суммирования в разных колонках, а id объектов группировки – в одной колонке :


object_id a q Ad ac qd qc
xxx xxx xxx 0 0 0 0
xxx xxx xxx 0 0 0 0
xxx xxx xxx 0 0 0 0
xxx 0 0 xxx xxx xxx xxx
xxx 0 0 xxx xxx xxx xxx
xxx 0 0 xxx xxx xxx xxx
xxx 0 0 xxx xxx xxx xxx

Когда сканируются строки результирующего набора первого SQL-запроса, значения колонок присваиваются выходным параметрам хранимой процедуры a и q , а остальным параметрам при этом присваивается нуль. Затем сканируются строки результирующего набора второго SQL-запроса, значения колонок присваиваются выходным параметрам хранимой процедуры ad, ac, qd и qc, а нуль присваивается параметрам a и q. Хранимые процедуры сервера InterBase устроены так, что возможно осуществить еще один SQL-запрос, работающий уже с результатами, выданными этой хранимой процедурой. Это будет простой запрос, с группировкой по object_id.

Итак, создадим хранимую процедуру:

create procedure goods_flow(date1 date, date2 date)
returns(object_id integer, acc_id integer, a decimal(18,2), q integer,
        ad decimal(18,2), ac decimal(18,2), qd integer, qc integer)
as
begin
  a = 0; q = 0; ad = 0; ac = 0; qd = 0; qc = 0; /*присваиваем 0*/
  /*запрос остатков*/
  for select
    a.object_id, a.acc_id,
    sum(a.debit - a.credit) a, sum(quantity_debit - quantity_credit) q
  from acc_turn a, acc
  where
    a.acc_id = acc.acc_id and
    acc.parent_id = 1005 and
    a.op_date < :date1
  group by a.object_id, a.acc_id
  into :object_id, :acc_id, :a, :q do
  suspend;
  a = 0; q = 0; /*присваиваем 0*/
  /*запрос приходов и расходов*/
  for select a.object_id, a.acc_id,
      sum(a.debit) ad, sum(a.credit) ac, sum(a.quantity_debit) qd, sum(a.quantity_credit) qc
  from acc_turn a, acc
  where
    a.acc_id = acc.acc_id and
    acc.parent_id = 1005 and
    a.op_date between :date1 and :date2
  group by a.object_id, a.acc_id
  into :object_id, :acc_id, :ad, :ac, :qd, :qc do
  suspend;
end

В этой процедуре мы объединили таблицу проводок ACC_TURN с таблицей счетов ACC по полю ACC_ID для того, чтобы получить результаты для всех складов. Склады у нас являются дочерними счетами к регистру «Товары», у которого ACC_ID=1005. Поэтому мы и записали условие PARENT_ID = 1005.

Теперь у нас есть хранимая процедура goods_flow, и мы можем сделать такой SQL-запрос:

select object_id,
  sum(a) a, sum(q) q, sum(ad) ad, sum(ac) ac, sum(qd) qd, sum(qc) qc
from goods_flow('01.06.2003', '15.07.2003')
where acc_id = 1007
group by object_id

Мы получили все необходимые нам данные, сгруппированные по товарам на складе 1007. Осталось лишь усовершенствовать запрос так, чтобы получить еще цены из справочника товаров. Значения всех остальных полей (остаток на конец и себестоимость единицы) можно будет вычислить на основе значений уже имеющихся.

Попробуем выполнить в окне Интерактивного SQL усовершенствованный запрос:

select
  gf.object_id,
  gk.name goods_kind_name,
  gm.name goods_mark_name,
  g.article,
  sum(gf.a) a, sum(gf.q) q, sum(gf.ad) ad, sum(gf.ac) ac, sum(gf.qd) qd, sum(gf.qc) qc,
  g.price_w, g.price_r
from
  goods_flow('01.06.2003', '15.07.2003') gf,
  goods g,
  goods_kind gk,
  goods_mark gm
where
  gf.acc_id = 1007 and
  gf.object_id = g.id and
  g.goods_kind = gk.id and
  g.goods_mark = gm.id
group by
  gf.object_id, gk.name, gm.name, g.article, g.price_w, g.price_r
order by 2,3,4

Мы решили, что данный отчет будет смотреться нагляднее, если вместо сборных наименований товаров изобразить три отдельные колонки: вид, марка и артикул. Поэтому мы не стали объединять набор с таблицей OBJECT_NAMES, и вместо этого объединили его с таблицами GOODS_KIND и GOODS _MARK. К тому же нам удалось при таком подходе упорядочить результирующий набор по наименованию вида, марки и артикулу при помощи выражения:

order by 2,3,4

Обратим внимание на время выполнения запроса. Как видим, оно не превышает долей секунды.

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

Включим режим «Дизайнер» и создадим новый проект.

Сохраним модуль формы под названием goods_flow.pas , а проект под названием goods_flow_project .ipr.

Придадим форме свойства:


Свойство Значение
Name GoodsFlowForm
FormStyle fsMDIChild
Caption Отчет о движении товаров

Добавим к форме компонент IBQuery с палитры InterBase и придадим ему свойства:


Свойство Значение
Name qryReport
Transaction MainConnection.MainTransaction

Скопируем запрос из окна ISQL в свойство SQL компонента qryReport . Для этого растянем главное окно, вызовем окно ISQL , если оно не на экране, нажмем Ctrl+ P, чтобы показать текст последнего SQL-запроса, выделим этот текст и скопируем в буфер обмена Windows при помощи клавиш Ctrl+C. Сожмем главное окно, выберем компонент qryReport, дважды щелкнем на его свойства SQL и в появившемся редакторе запроса вставим из буфера обмена наш запрос с помощью клавиш Ctrl+V.

Дважды щелкнем на компоненте qryReport и в Редакторе полей добавим все поля в список.



Так мы создали «постоянные поля», которым теперь можно назначить русские заголовки и способы форматирования. Назначим полям свойства в Инспекторе объектов:


Поле Alignment DisplayLabel DisplayWidth DisplayFormat Visible
OBJECT_ID taLeftJustify OBJECT_ID 20   False
GOODS_KIND_NAME taLeftJustify Вид товара 12   True
GOODS_MARK_NAME taCenter Марка 12   True
ARTICLE taLeftJustify Артикул 12   True
A taRightJustify Нач.Ост.,USD 12 #,##0.00 True
Q taCenter Нач.Ост., Ед. 12   True
AD taRightJustify Приход, USD 12 #,##0.00 True
AC taRightJustify Расход, USD 12 #,##0.00 True
QD taCenter Приход, Ед. 12   True
QC taCenter Расход, Ед. 12   True
PRICE_W taCenter Опт. Цена 10   True
PRICE_R taCenter Розн.Цена 10   True

Расположим поля прихода сумм и количеств друг за другом. Для этого поменяем местами поля QD и AC, перетащив при помощи мыши поле QD на одну позицию вверх в редакторе полей, или просто уменьшив на единицу его свойство Index в Инспекторе объектов.

Нам нужно создать еще пять полей: «Конечный остаток , USD», «Конечный остаток, Ед.», « Средняя себестоимость единицы», «Оптовая наценка, %», « Розничная наценка, %».

Закроем редактор полей. Закроем запрос, если он у нас открыт, установив свойство Active = False. Добавлять вычисляемые поля рекомендуется при закрытом запросе, иначе компонент может выдать сообщение «Поле не найдено». Вызовем снова редактор полей и создадим два новых поля типа IBBCD:


Поле FieldKind Alignment DisplayLabel DisplayWidth DisplayFormat Visible
CA fkCalculated taLeftJustify Остаток,USD 12 #,##0.00 True
CQ fkCalculated taCenter Остаток,Ед. 12   True

Создадим три новых поля типа Float:


Поле FieldKind Alignment DisplayLabel DisplayWidth DisplayFormat Visible
COST fkCalculated taRightJustify Ср.стоим. 10 #,###.# True
W_RATE fkCalculated taCenter Опт.нац,% 10 #0.00 True
R_RATE fkCalculated taCenter Розн.нац,% 10 #0.00 True

Перетащим мышью поля CA и CQ в списке так, чтобы они расположились после полей прихода:



Закроем редактор. Создадим у компонента qryReport обработчик события OnCalcFields :

procedure TGoodsFlowForm.qryReportCalcFields(DataSet: TDataSet);
begin
  with DataSet do
  begin
    {вычисляем остатки}
    FieldByName('CA').AsCurrency := FieldByName('A').AsCurrency +
      FieldByName('AD').AsCurrency - FieldByName('AC').AsCurrency;
    FieldByName('CQ').AsCurrency := FieldByName('Q').AsCurrency +
      FieldByName('QD').AsCurrency - FieldByName('QC').AsCurrency;
    {вычисляем среднюю стоимость единицы на складе}
    if FieldByName('CQ').AsCurrency <> 0 then
      FieldByName('COST').AsCurrency :=
        FieldByName('CA').AsCurrency / FieldByName('CQ').AsCurrency;
    {вычисляем наценки в процентах}
    if FieldByName('COST').AsCurrency <> 0 then
    begin
      FieldByName('W_RATE').AsCurrency :=
        (FieldByName('PRICE_W').AsCurrency /
         FieldByName('COST').AsCurrency - 1) *100;
      FieldByName('R_RATE').AsCurrency :=
        (FieldByName('PRICE_R').AsCurrency /
         FieldByName('COST').AsCurrency - 1)*100;
    end;
  end;
end;

Добавим на форму компонент Panel с палитры Standard:


Свойство Значение
Align alTop
Caption  
BevelOuter bvNone

Для отображения результатов запроса нам понадобится необычная сетка, так как полей очень много и в одну строку они явно не уместятся. Откроем запрос, установив у компонента qryReport свойство Active = True. Добавим в качестве сетки ячеистую сетку DbAltGrid с палитры Quasidata и придадим ей свойства в Инспекторе объектов:


Свойство Значение
Align alClient
DataSource dsrReport
Options добавим dgRowSelect
AltOptions Добавим dagColumnAltResize

Дважды щелкнем на свойстве Columns для того, чтобы вызвать редактор колонок сетки. Добавим все колонки в список при помощи соответствующего пункта контекстного меню:



Удалим из списка колонку OBJECT_ID, так как мы не хотим ее отображать и закроем редактор колонок. Мы создали «постоянные» (persistent) колонки и теперь можем установить их свойства.

Компонент DbAltGrid позволяет расположить ячейки в пределах одной строки произвольно . Увеличим размер ячеек «Вид товара», «Марка » и «Артикул». Для этого подводим курсор мыши к нижнему краю заголовка колонки, и когда вид курсора мыши изменится, нажимаем левую кнопку мыши, далее, удерживая ее, «тянем» границу заголовка вниз. Ячейка увеличивает свой размер, причем дискретно. Установим размер ячеек этих трех колонок равным двойному размеру строки сетки по умолчанию:



А теперь «перетащим» заголовки колонок количеств, расположив каждое количество под соответствующей суммой (остатки под остатками, приходы под приходами и т.д.). Для « перетаскивания» заголовка колонки подводим к его центру курсор мыши , нажимаем левую кнопку и, удерживая ее, перемещаем мышью туда, куда нам нужно, после чего кнопку мыши отпускаем. Эта технология носит название drag and drop . В результате мы должны получить примерно такой вид сетки :



Увеличим размер ячейки колонки средней себестоимости до двойной высоты ( как ячейки колонок «вид товара» и перетащим ее влево, расположив перед колонками цен.

Перетащим заголовки колонок процентов наценки, расположив каждый под соответствующей ценой.

Сохраним все изменения .

Вызовем еще раз редактор колонок сетки, выберем все колонки , кроме вида товара, марки и артикула и установим в Инспекторе объектов для них выравнивание по центру Alignment = taCenter.

Выберем в редакторе колонок все колонки количеств (в их именах присутствует буква Q) и колонки процентов наценок ( в их именах присутствует слово RATE). Одним словом, мы выбрали все колонки, ячейки которых оказались у нас в нижнем ряду. Изменим в Инспекторе объектов цвет Color колонки на серо-голубой, а цвет шрифта колонки Font.Color на clPurple (пурпурный).

Раскрасим по вкусу остальные колонки, устанавливая у них свойство Color.

Стараемся избегать кричащих цветов, так как большие поверхности ярких цветов будут утомлять глаза пользователя:



Обратите внимание, что значения вычисляемых полей не отображаются в режиме дизайна. Только при запуске проекта тот текст, что мы прописали в обработчик OnCalcFields начнет работать и вычисляемые поля получат свои значения.

Теперь займемся органами управления. Нам необходима во-первых кнопка «Запрос». Добавим с палитры Standard кнопку Button на верхнюю панель нашей формы. Назначим ей свойства Caption = Запрос и Name = btnQuery. Создадим ей обработчик события OnClick:

procedure TGoodsFlowForm.btnQueryClick(Sender: TObject);
begin
  Screen.Cursor := crHourGlass; //изменяем курсор мыши на "часы"
  try
    qryReport.Close;
    qryReport.Open;
  finally
    Screen.Cursor := crDefault; //восстанваливаем курсор мыши
  end;
end;

Мы изменяем курсор мыши на «часы» на то время, пока отрабатывается запрос, чтобы пользователь понял, что нужно подождать. Хотя ждать ему придется недолго - наш запрос работет очень быстро. Закроем запрос в режиме дизайна, установив Active = False. Запустим проект ( F9). После нажатия кнопки «Запрос», запрос должен открыться, а в вычисляемых полях - появиться значения.

Однако пока мы запрашиваем отчет на фиксированные даты '01. 06.2003', '15.07.2003' , которые мы прописали явно в тексте запроса. Нам нужно запросить отчет за произвольный период дат. Поэтому вернемся в режим дизайна и изменим текст запроса в компоненте qryReport, заменив эти две даты на параметры :DATE1 и : DATE2.

…..
  goods_flow(:DATE1, :DATE2) gf,
…..

Добавим на верхнюю панель два компонента DateEdit с палитры RX Controls, и изменим текст обработчика OnClick кнопки «Запрос ». Добавленные команды выделены здесь жирным шрифтом:

procedure TGoodsFlowForm.btnQueryClick(Sender: TObject);
begin
  Screen.Cursor := crHourGlass; //изменяем курсор мыши на "часы"
  try
    qryReport.Close;
    qryReport.ParamByName('DATE1').AsDateTime := DateEdit1.Date;
    qryReport.ParamByName('DATE2').AsDateTime := DateEdit2.Date;
    qryReport.Open;
  finally
    Screen.Cursor := crDefault; //восстанваливаем курсор мыши
  end;
end;

Создадим для формы GoodsFlowForm обработчик события OnCreate:

procedure TGoodsFlowForm.FormCreate(Sender: TObject);
begin
  DateEdit1.Date := EncodeDate(YearOf(Date), MonthOf(Date), 1);
  DateEdit2.Date := Date;
end;

Запустим проект и нажмем кнопку «Запрос»:



Нам нужно еще реализовать фильтрацию в отчете. И хорошо бы иметь возможность сортровать записи в отчете, предоставив выбор способа сортировки пользователю.

Вернемся в режим дизайна. Увеличим высоту верхней панели до 152 пикселей.

Добавим три компонента RxCheckListBox с палитры RX Controls. Назовем компоненты clbKind, clbMark и clbStock. Они будут нам служить органами управления фильтрацией по видам товаров, маркам и складам. Выберем компонент clbKind и дважды щелкнем на его свойстве Items в Инспекторе объектов. Появится специальный редактор списка , в котором добавим элемент в список, нажав клавишу New. Назовем пункт <Все виды товаров> и выберем Checked в выпадающем списке:



Аналогично создадим один элемент списка в компоненте clbMark с названием <Все марки>.

Аналогично создадим один элемент списка в компоненте clbStock с названием <Все склады>.

Добавим на панель компонент ComboBox с палитры Standard и придадим ему свойства:


Свойство Значение
Name cbOrder
Items Вид товара, марка, артикул Марка, вид товара, артикул
Style csDropDownList
ItemIndex 0

Добавим два компонента Label с палитры Standard и придадим их свойствам Caption значения ‘Порядок сортировки’ и ‘Даты’.

Добавим компонент CheckBox с палитры Standart и придадим ему свойства :


Свойство Значение
Name cbNotZero
Caption Не показывать нулевые количества

Расположим управляющие элементы так, как показано на рисунке:



Добавим три компонента IBQuery с палитры InterBase и придадим им свойства:


Свойство Значение
Name qryGoodsKind
Transaction MainConnection.MainTransaction
SQL select ID,NAME from GOODS_KIND where ID <> 0 order by NAME

Свойство Значение
Name qryGoodsMark
Transaction MainConnection.MainTransaction
SQL select ID,NAME from GOODS_MARK where ID <> 0 order by NAME

Свойство Значение
Name qryStock
Transaction MainConnection.MainTransaction
SQL select ACC_ID,NAME from ACC where PARENT_ID = 1005 order by ACC_ORDER

Добавим в обработчик события OnCreate формы код, выделенный жирным шрифтом:

procedure TGoodsFlowForm.FormCreate(Sender: TObject);
begin
  DateEdit1.Date := EncodeDate(YearOf(Date), MonthOf(Date), 1);
  DateEdit2.Date := Date;
  with qryGoodsKind do
  begin
    Open;
    while not Eof do //заполняем список видов товаров
    begin
      clbKind.Items.Add(Fields[1].AsString);
      Next;
    end;
  end;

  with qryGoodsMark do
  begin
    Open;
    while not Eof do //заполняем список марок
    begin
      clbMark.Items.Add(Fields[1].AsString);
      Next;
    end;
  end;

  with qryStock do
  begin
    Open;
    while not Eof do //заполняем список складов
    begin
      clbStock.Items.Add(Fields[1].AsString);
      Next;
    end;
  end;
end;

Запустим проект:



Мы видим, что списки заполнились видами товаров и их марками из соответствующих справочников, а список складов - из системной таблицы ACC. Нам осталось организовать саму фильтрацию. Если пользователь оставит птичку на пункте «Все виды товаров », то фильтрация по видам производиться не должна, если же он уберет эту птичку и отметит птичками какие- то конкретные виды товаров, то отчет должен включить только товары отмеченных видов. Аналогично должны работать фильтры марок и складов.

Для того чтобы это реализовать нам понадобится просканировать каждый список , найти отмеченные птичками пункты и собрать строку фильтрации, которую мы вставим в SQL-запрос отчета во время выполнения программы, непосредственно перед тем, как открыть запрос .

Создадим константу в разделе implementation, скопировав текст запроса из компонента qryReport и несколько его видоизменив:

const
  SQL_GOODS_FLOW_REPORT =
       'select'#13+
       '  gf.object_id,'#13+
       '  gk.name goods_kind_name,'#13+
       '  gm.name goods_mark_name,'#13+
       '  g.article,'#13+
       '  sum(gf.a) a, sum(gf.q) q, sum(gf.ad) ad, sum(gf.ac) ac,'#13+
       '  sum(gf.qd) qd, sum(gf.qc) qc,'#13+
       '  g.price_w, g.price_r'#13+
       'from'#13+
       '  goods_flow(:DATE1, :DATE2) gf,'#13+
       '  goods g,'#13+
       '  goods_kind gk,'#13+
       '  goods_mark gm'#13+
       'where'#13+
       '  gf.object_id = g.id and'#13+
       '  g.goods_kind = gk.id and'#13+
       '  g.goods_mark = gm.id'#13+
       '%s'+               //здесь будет фильтр по видам, маркам и складам
       'group by'#13+
       '  gf.object_id, gk.name, gm.name, g.article, g.price_w, g.price_r'#13+
       '  %s'+             //здесь будут отсеиваться нулевые количества
       'order by %s'; //здесь будет упорядочивание

Как можно заметить, мы исключили явное указание склада gf .acc_id = 1007 и добавили в трех местах %s.

Теперь мы создадим функцию, которая будет собирать строку фильтра , анализируя отмеченные птичками пункты определенного списка.

Добавим в секцию private класса формы объявление функции:

    { Private declarations }
    function GetFilterString(ListBox: TRxCheckListBox;
      Query: TIBQuery; const FieldName: string): string;

Добавим в раздел implementation реализацию этой функции:

{Сборка строки фильтрации вида 'and <поле> in (1,5,6,11,92)' }
function TGoodsFlowForm.GetFilterString(ListBox: TRxCheckListBox;
  Query: TIBQuery; const FieldName: string): string;
begin
  Result := '';
  if ListBox.Checked[0] then
    exit;
  with Query do //сканируем набор записей
  begin
    First;
    while not Eof do
    begin
      if ListBox.Checked[RecNo] then //если соответствующий элемент
      begin                          //в списке отмечен птичкой
        if Result <> '' then Result := Result + ','; //добавляем запятую
        Result := Result + Fields[0].AsString; //добавляем в строку
      end;
      Next;
    end;
  end;
  if Result <> '' then //окончательно форматируем условие фильтрации
    Result := Format('and %s in (%s)'#13, [FieldName, Result]);
end;

Добавим в обработчик события OnClick кнопки «Запрос» код , выделенный жирным шрифтом:

procedure TGoodsFlowForm.btnQueryClick(Sender: TObject);
var
  s_filter, s_having, s_orderby: string;
begin
  Screen.Cursor := crHourGlass; //изменяем курсор мыши на "часы"
  try
    qryReport.Close;
    {Собираем строку по всем трем фильтрам}
    s_filter := GetFilterString(clbKind, qryGoodsKind, 'g.goods_kind') +
                GetFilterString(clbMark, qryGoodsMark, 'g.goods_mark') +
                GetFilterString(clbStock, qryStock, 'gf.acc_id');
    if cbNotZero.Checked then //не показывать нулевые количества
      s_having :=  'having sum(gf.q) <> 0 or sum(gf.qd) <> 0 or sum(gf.qc) <> 0'#13
    else
      s_having := '';

    case cbOrder.ItemIndex of
    1: s_orderby := '3,2,4';
    else
      s_orderby := '2,3,4';
    end;

    qryReport.SQL.Text := Format(SQL_GOODS_FLOW_REPORT,
                          [s_filter, s_having, s_orderby]);

    // ShowMessage(qryReport.SQL.Text); //отладочный вывод
    qryReport.ParamByName('DATE1').AsDateTime := DateEdit1.Date;
    qryReport.ParamByName('DATE2').AsDateTime := DateEdit2.Date;
    qryReport.Open;
  finally
    Screen.Cursor := crDefault; //восстанваливаем курсор мыши
  end;
end;

Запустим проект. Снимем птичку с пункта «Все виды товаров» и установим птички на конкретные виды. Аналогично установим фильтр на несколько марок. Установим птичку «Не показывать нулевые количества» и поменяем порядок сортировки на « Марка, вид, артикул». Нажмем кнопку «Запрос »:



В принципе наш отчет готов (осталось реализовать лишь экспорт в Excel).

Можно подключить отчет к Главному меню Allegro, даже не выходя полностью из режима «Дизайнера». Важно лишь, чтобы главное окно было в растянутом положении.

Для подключения отчета к Главному меню вызовем окно «Команды » через меню Инструменты/Команды и создадим там новый пункт под названием «Отчет о движении товаров». Выберем какую-нибудь картинку формата BMP 16x16 и установим остальные поля диалога:


Команда Отчет о движении товаров
Подсказка Отчет о движении товаров
Добавить в главное меню Отчеты
Запускаемый проект D:\Program Files\DAVSAR\Allegro\scripts \TechnoTrade\goods_flow_project.ip
   
Способ запуска Стандартный
   


Выйдем из дизайнера и попробуем вызвать наш отчет через главное меню. Запустим отчет и вызовем окно SQL-монитора . Установим в отчете фильтрацию и посмотрим, как выглядят тексты отрабатываемых SQL-запросов в SQL-мониторе. Например, мы запросили 1 вид товара и три марки , требуя ненулевые количества:

select
  gf.object_id,
  gk.name goods_kind_name,
  gm.name goods_mark_name,
  g.article,
  sum(gf.a) a, sum(gf.q) q, sum(gf.ad) ad, sum(gf.ac) ac,
  sum(gf.qd) qd, sum(gf.qc) qc,
  g.price_w, g.price_r
from
  goods_flow(:DATE1, :DATE2) gf,
  goods g,
  goods_kind gk,
  goods_mark gm
where
  gf.object_id = g.id and
  g.goods_kind = gk.id and
  g.goods_mark = gm.id
  and g.goods_kind in (8)
  and g.goods_mark in (25,26,27)
group by
  gf.object_id, gk.name, gm.name, g.article, g.price_w, g.price_r
having sum(gf.q) <> 0 or sum(gf.qd) <> 0 or sum(gf.qc) <> 0
order by 2,3,4


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