Глава 5. СОЗДАЕМ ОКОННЫЙ ИНТЕРФЕЙС «ПОСТУПЛЕНИЯ НА СКЛАД»
Дорабатываем SQL-запросы позиций и шапки документа
Запрос, записанный нами в свойстве SelectSQL компонента qryDetails извлекает
данные только из таблицы STOCK_IN_ITEM.
В этой таблице вместо наименований товара хранятся их внутренние идентификаторы
ID в поле GOODS Для того чтобы видеть не внутренние
номера, а наименования товаров, нам нужен запрос,
объединяющий таблицу STOCK_IN_ITEM с таблицей наименований
объектов OBJECT_NAMES. К тому же мы хотим
, чтобы записи отображались в определенном порядке. Поэтому изменим
у компонента qryDetails свойство SelectSQL, вписав в него такой
SQL-запрос:
select
SI.ID,
SI.N,
SI.GOODS,
O.SHORT_NAME ITEM_NAME,
SI.QUANTITY,
SI.PRICE_L,
SI.PRICE_L_WO_VAT,
SI.PRICE_R,
SI.PRICE_R_WO_VAT,
SI.AMOUNT_L,
SI.AMOUNT_R,
SI.AMOUNT_S
from
STOCK_IN_ITEM SI,
OBJECT_NAMES O
where
SI.GOODS = O.OBJECT_ID
order by SI.N
В данном случае выборка происходит из 2 таблиц, каждой
из них присвоено по псевдониму (SI и O),
что сокращает и делает более читабельным тест запроса. Таблицы
связаны условием:
SI.GOODS = O.OBJECT_ID
Результирующий набор упорядочен по полю N фразой:
order by SI.N
Активизируем запрос, установив свойство Active = True. Теперь
мы видим наименование товара в наборе данных. Хорошо бы
еще присвоить русские названия колонкам этого набора и скрыть некоторые
внутренние и ненужные будущему пользователю колонки, например, ID
и N. Компонент класса TIBDataSet работает с набором данных
при помощи так называемых компонентов-полей, потомков класса
TField библиотеки визуальных компонентов VCL Delphi. Можно создать «
постоянные» (persistent) поля вручную на стадии разработки
интерфейса и придать им нужные свойства. Если «постоянные
» поля не были созданы, то компонент TIBDataSet создает
вместо них «временные» поля сразу после того,
как будет активизирован запрос SELECT. То, что мы
видим в сетке сейчас - это «временные» поля
, созданные автоматически. Их заголовки нас явно не устраивают
.
Давайте создадим список полей вручную.
Для этого нужно дважды щелкнуть мышью на компоненте qryDetail.
Появится редактор полей, пока пустой, так как «
постоянных» полей у компонента еще нет. Вызовем правой
кнопкой мыши контекстное меню и выберем пункт Добавить поля…
Нам будет предложено выбрать какие-то поля из списка
всех потенциальных полей, которые присуствуют в этом наборе данных
, причем по умолчанию предлагается выбрать их все. Так
и поступим, нажав OK.
«Постоянные» поля теперь созданы в списке и видны
в редакторе полей:
Выбирая какие-то из них в редакторе полей,
мы можем присвоить значения их свойствам в Инспекторе объектов.
Параллельно мы можем следить за тем, как изменяется внешний
вид нашей сетки.
У полей ID, N, GOODS, PRICE_
R, PRICE_R_WO_VAT и
AMOUNT_R установим значение свойства
Visible = False
Это отключит отображение соответствующих полей в сетке.
Изменим заголовки некоторых полей, присваивая свойству DisplayLabel новое значение
:
ITEM_NAME |
Наименование |
QUANTITY |
Кол-во |
PRICE_L |
Цена |
PRICE_L_WO_VAT |
Цена б/НДС |
AMOUNT_L |
Сумма |
AMOUNT_S |
Сумма, USD |
Изменим ширину отображения некоторых полей, изменяя значение свойства DisplayWidth
:
ITEM_NAME |
45 |
QUANTITY |
5 |
PRICE_L |
10 |
PRICE_L_WO_VAT |
10 |
AMOUNT_L |
12 |
AMOUNT_S |
12 |
Изменим выравнивание текста в полях QUANTITY, PRICE_L
, PRICE_L_WO_VAT, установив
для них всех:
Alignment = taCenter
Закроем редактор полей. Сохраним и запустим проект (F9
). Сейчас наш интерфейс выглядит так:
Пока что данные в сетке не редактируются, так как
не созданы запросы UPDATE и DELETE.
Займемся пока текстом запроса шапки документа. У компонента qryMaster
в свойство SelectSQL впишем такой текст:
select
SI.ID,
SI.DIR_ID,
SI.DOC_DATE,
SI.DOC_KIND,
SI.DOC_NO,
SI.ENTRY_DATE,
SI.HAS_ENTRY,
SI.STOCK_ACC,
A1.NAME STOCK_ACC_NAME,
SI.LAYER_ID,
L.SHORT_NAME LAYER_NAME,
SI.VAT_RATE,
SI.ACC_ID,
A2.NAME CREDIT_ACC_NAME,
SI.CONTRAGENT,
O.SHORT_NAME CONTRAGENT_NAME,
SI.CALC_MODE,
SI.EXCH_RATE_L,
SI.EXCH_RATE_S,
SI.TOTAL_L,
SI.TOTAL_R,
SI.TOTAL_S
from
STOCK_IN SI,
OBJECT_NAMES O,
LAYER L,
ACC A1,
ACC A2
where
SI.CONTRAGENT = O.OBJECT_ID and
SI.LAYER_ID = L.LAYER_ID and
SI.STOCK_ACC = A1.ACC_ID and
SI.ACC_ID = A2.ACC_ID
Здесь мы объединяем пять таблиц: главную таблицу документа «
Поступление на склад» STOCK_IN, таблицу наименований
объектов OBJECT_NAMES, таблицу слоев LAYER и дважды
таблицу счетов ACC под разными псевдонимами (ACC1 и ACC2
). Создадим «постоянные» поля дважды щелкнув на компоненте
qryMaster и добавив все возможные поля в список.
Добавим на верхнюю панель формы надписи с названиями полей и
компоненты для отображения данных шапки. Для этого используем компоненты
Label с палитры Standard и DBText с палитры DataControls.
Впишем свойства Caption компонентов типа TLabel названия полей самых важных
полей шапки документа:
Выберем все компоненты DBText, держа нажатой клавишу Shift и
щелкая по ним мышью. В Инспекторе объектов назначим им
всем одновременно свойства:
AutoSize = True
DataSource = dsrMaster
Font.Style = [fsBold]
Свойства Name всех этих компонентов изменять не будем. А
свойствам DataField назначим имена полей запроса:
DBText1 |
STOCK_ACC_NAME |
DBText2 |
ENTRY_DATE |
DBText3 |
TOTAL_L |
DBText4 |
LAYER_NAME |
DBText5 |
CONTRAGENT_NAME |
DBText6 |
CREDIT_ACC_NAME |
DBText7 |
DOC_DATE |
DBText8 |
DOC_NO |
Активизируем запрос qryMaster.
Итак, запросы SELECT для шапки и для позиций у
нас почти готовы. Единственное, чего в них недостает
, так это возможности запрашивать информацию а каком-то
конкретном документе, с конкретным ID. Если бы документов
было много, то мы сейчас видели бы их все
позиции одновременно в нашей сетке dbgDetail. Мы не хотели
с самого начала отягощать и без того сложную для многих
читателей тему создания SQL-запросов всеми подробностями, и
потому вносим все необходимые уточнения постепенно по ходу изложения.
Итак, если у нас имеется запрос вида:
select * from stock_in_item
То он возвратит нам все строки таблицы stock_in
_item. Для того чтобы получить только те строки
, которые относятся к документу с ID = 1050,
мы могли бы написать такой запрос:
select * from stock_in_item
where ID = 1050
Однако такой подход требует каждый раз менять текст запроса,
если мы хотим запросить тот или иной документ. Поэтому
существует более гибкий механизм – параметризованные запросы. Для того
чтобы написать параметризованный запрос в компонент типа TIBDataSet, мы
должны включить в его текст переменную, предваряемую двоеточием,
и называемую параметром, например:
select * from stock_in_item
where ID = :A
Прежде чем открыть запрос, мы должны будем сообщить компоненту
значение параметра А. Делается это обычно во время выполнения
программы (run-time) с помощью вызова метода
ParamByName.
Добавим в каждый наш SQL-запрос параметр, с
помощью которого будем передавать ID документа. Для этого в
секцию WHERE к имеющимся условиям нужно добавить условие:
SI.ID = :ID
Это условие ограничит набор только строками, в которых значение
поля ID равно значению параметра :ID.
Итак, окончательно текст запроса в свойстве qryMaster.SelectSQL
выглядит так:
select
SI.ID,
SI.DIR_ID,
SI.DOC_DATE,
SI.DOC_KIND,
SI.DOC_NO,
SI.ENTRY_DATE,
SI.HAS_ENTRY,
SI.STOCK_ACC,
A1.NAME STOCK_ACC_NAME,
SI.LAYER_ID,
L.SHORT_NAME LAYER_NAME,
SI.VAT_RATE,
SI.ACC_ID,
A2.NAME CREDIT_ACC_NAME,
SI.CONTRAGENT,
O.SHORT_NAME CONTRAGENT_NAME,
SI.CALC_MODE,
SI.EXCH_RATE_L,
SI.EXCH_RATE_S,
SI.TOTAL_L,
SI.TOTAL_R,
SI.TOTAL_S
from
STOCK_IN SI,
OBJECT_NAMES O,
LAYER L,
ACC A1,
ACC A2
where
SI.CONTRAGENT = O.OBJECT_ID and
SI.LAYER_ID = L.LAYER_ID and
SI.STOCK_ACC = A1.ACC_ID and
SI.ACC_ID = A2.ACC_ID and
SI.ID = :ID
Аналогично добавим такое же условие в компонент запроса позиций.
Окончательный текст в свойстве qryDetail.SelectSQL должен выглядеть так
:
select
SI.ID,
SI.N,
SI.GOODS,
O.SHORT_NAME ITEM_NAME,
SI.QUANTITY,
SI.PRICE_L,
SI.PRICE_L_WO_VAT,
SI.PRICE_R,
SI.PRICE_R_WO_VAT,
SI.AMOUNT_L,
SI.AMOUNT_R,
SI.AMOUNT_S
from
STOCK_IN_ITEM SI,
OBJECT_NAMES O
where
SI.GOODS = O.OBJECT_ID and
SI.ID = :ID
order by SI.N
После внесения такого изменения в тексты запросов, если их
теперь активизировать в режиме дизайна, то они будут отображать
пустой набор, так как значение параметра ID не определено
.
Теперь займемся передачей параметра ID. Выберем форму StockInForm,
откроем закладку «События» в Инспекторе объектов и дважды
щелкнем на событии OnCreate. В код программы вставится обработчик
события. Впишем в него следующий текст:
procedure TStockInForm.FormCreate(Sender: TObject);
begin
qryMaster.ParamByName('ID').AsInteger := RunContext.Documents[0].doc_id;
qryMaster.Open;
qryDetail.ParamByName('ID').AsInteger := RunContext.Documents[0].doc_id;
qryDetail.Open; //открываем запрос позиций
end;
Смысл этого текста мы поясним позже, а пока назначим
переменные контекста с помощью пункта меню Запуск/Переменные контекста
. В появившемся диалоге установим значения первых двух величин,
с помощью кнопочек с многоточием. Выберем в предложенных диалогах
тип документа «Поступление на склад» и тот документ
, что у нас имеется.
Установим свойство обоих запросов Active = False.
Запустим проект, нажав F9.
Итак, у нас все работает. Выйдем из дизайнера
и попробуем запустить проект из «Проводника по документам»,
щелкнув дважды на названии документа. А теперь, используя
пункт контекстного меню «Создать» «Проводника», попробуем
создать «Поступление на склад»:
Появится еще одно окно «Поступление на склад», но
с пустым набором.
При вызове каждого экземпляра интерфейса документа на экран, программа
создает специальный компонент RunContext, в котором помещает информацию о
том, какой тип документа doc_tye_id
вызван на экран и каков его doc_id.
Собственно, мы этой информацией об doc_id и
воспользовались, когда передавали значение параметра ID в запросы.
При создании нового документа программа Allegro присваивает doc_id
отрицательное значение:
RunContext.Documents[0].doc_id =
-1
После того, как новый документ будет реально создан в
базе данных, мы обязаны присвоить свойству RunContext.Documents
[0].doc_id значение ID созданного документа
. Это нужно для того, чтобы программа могла различать
в многооконном интерфейсе открытые документы и не вызвала один и
тот же документ дважды (в двух разных окнах).
В принципе один проект оконного интерфейса может обслуживать одновременно несколько
документов. Например, заказ и связанные с ним размещения
и поставки. Для этого объект RunContext имеет свойство-
массив RunContext.Documents. В простейшем случае используется один
элемент массива с индексом 0. Подразумевается, что при
вызове проекта оконного интерфейса всегда задействуется хотя бы один какой
-то конкретный документ.
|