Глава 5. СОЗДАЕМ ОКОННЫЙ ИНТЕРФЕЙС «ПОСТУПЛЕНИЯ НА СКЛАД»
Создаем SQL-запросы Update и Insert для «шапки» документа.
Включим режим «Дизайнер» и откроем проект stock_
in_project.ipr.
Выберем на главной форме проекта компонент qryMaster и щелкнем на
нем правой кнопкой мыши. Появится контекстное меню компонента.
Используем пункт меню DataSet Editor. Перед нами появится редактор
SQL-запросов вставки, модификации и удаления.
Этот редактор весьма полезен при создании SQL-запросов,
так как помогает нам на основе запроса, записанного в
свойстве SelectSQL, сформировать все остальные запросы автоматически. Редактор
имеет две закладки: Options и SQL. На первой
закладке требуется выделить в списке Key Fields ключевые поля,
а в правом списке Upadate Fields – поля, которые
необходимо модифицировать. Нажмем кнопку Get Table Fields, чтобы
выбрать все поля таблицы STOCK_IN, а затем
кнопку Select Primary Keys , чтобы выбрать поле ID в
качестве ключевого. Теперь нажмем кнопку Generate SQL:
Редактор сам сформирует все тексты запросов и покажет их на
закладке «SQL»:
Переключая радиокнопки Statement Type, мы можем посмотреть тексты всех
запросов. Обратим внимание, что запросы Insert, Update
Delete работают только с одной таблицей, хотя в запросе
Select мы использовали объединение нескольких таблиц. Но это правильно
, так как нам нужно изменять данные только в таблице
документа, а остальные таблицы мы используем лишь в целях
получения дополнительной информации при отображении данных. К сожалению,
запрос Refresh, сформированный редактором, тоже запрашивает поля одной
таблицы, поэтому мы не сможем использовать его в таком
виде. Очистим текст запроса Refresh. Очистим также текст
запроса Delete, так как удаление документа мы не будем
использовать. Нажмем OK.
Дважды щелкнем на свойстве SelectSQL компонента qryMaster в Инспекторе объектов
, выделим весь текст запроса и скопируем (Ctrl+
C) его в буфер обмена Windows. Закроем диалог
редактора свойства SelectSQL и дважды щелкнем на свойстве RefreshSQL.
В появившемся редакторе вставим (Ctrl+V) текст
запроса из буфера обмена. Таким образом, в запросах
SelectSQL и RefreshSQL для «шапки» документа мы будем
использовать один и тот же текст. Запрос RefreshSQL используется
после вставки или изменения данных для того, чтобы освежить
текущую строку набора.
Теперь набор данных в компоненте qryMaster, стал редактируемым.
Взглянем на переменные контекста, вызвав соответствующее окно через меню
Запуск/Переменные контекста. Нужно убедиться, что в
переменных контекста сейчас указан конкретный документ. Если это не
так, установим тип документа и текущий документ (первые
две строки). Выйдем из окна «Переменные контекста».
Запустим проект (F9).
Вызовем окно шапки и убедимся, что управляющие элементы теперь
работают. Однако внесенные изменения пока не сохраняются.
Остановим проект и внесем некоторые команды в тексты модулей.
Добавим объявление переменной после implementation:
var
Modified: boolean;
В этой переменной мы будем хранить признак того, что
документ редактировался.
В обработчик OnCreate формы StockInForm, перед другими командами,
добавим команды:
traCurrent.StartTransaction; //стартовать транзакцию
Modified := False;
Старт транзакции должен выполняться при запуске проекта, до того
, как будут открыты SQL-запросы. Нам необходимо
явно стартовать транзакцию, чтобы потом мы могли подтвердить ее
или откатить. Все сделанные нами изменения в базе данных
после подтверждения транзакции запомнятся окончательно, а в случае отката
транзакции, наоборот, все изменения в базе данных,
сделанные после старта транзакции, будут отменены.
Дважды щелкнем на компоненте ActionList1, выберем в редакторе списка
команду «Сохранить» и создадим к ней обработчик события
OnExecute.
Впишем в обработчик следующий тест:
traCurrent.CommitRetaining; //подтвердить транзакцию
Modified := False;
Мы вызвали метод подтверждения транзакции. У компонента класса TIBTransaction
существует еще один метод подтверждения транзакции, который называется просто
Commit. Разница между ними в том, что метод
CommitRetaining оставляет транзакцию активной, и SQL-запросы остаются
открытыми. Так как нам в данном случае хочется иметь
возможность делать иногда сохранения в процессе редактирование документа, мы
выбираем этот метод. Иначе нам пришлось бы каждый раз
после сохранения документа переоткрывать все запросы.
В свойстве DefaultAction компонента транзакции traCurrent мы указали TARollback.
Это означает, что при закрытии окна транзакция откатится автоматически
. Разумеется, мы должны выдать предупреждение пользователю, если
он попытается закрыть окно, что изменения могут быть утеряны
. Лучше всего предложить ему сделать выбор: сохранить изменения
, не сохранять их или отказаться от закрытия окна и
продолжить редактирование.
Для того чтобы это реализовать, создадим для формы StockInForm
обработчик события OnCloseQuery. Этот обработчик вызывается всякий раз,
когда пользователь пытается закрыть форму. Обработчик имеет параметр CanClose
, который можно изменить на False внутрь процедуры обработки и
тогда форма не закроется. Поэтому впишем в обработчик такой
текст:
if Modified then
case MessageDlg('Сохранить изменения в документе?', mtConfirmation,
MkSet(mbYes, mbNo, mbCancel), 0) of
mrYes: miSave.Click; //вызов обработчика OnClick пункта меню miSave
mrCancel: Canclose := False;
end;
Создадим у компонента qryMaster обработчик события AfterPost и впишем в
него:
Modified := True;
Мы должны еще позаботиться о том, чтобы вызвать метод
Post компонента qryMaster, так как только при вызове этого
метода SQL-запросы вставки (insert) и модификации
данных (update) посылаются на сервер. Перейдем к
форме шапки. Создадим обработчик нажатия кнопки btnOK, дважды
щелкнув на этой кнопке. Впишем в него такой текст
:
with StockInForm.qryMaster do
if InSet(State, MkSet(dsEdit, dsInsert)) then
begin
Post;
RunContext.Documents[0].doc_id := FieldByName('ID').AsInteger;
end;
self.ModalResult := mrOK;
Прежде чем применять метод Post, нужно убедиться, что
компонент находится в режиме редактирования или вставки. Поэтому мы
предварительно проверяем, входит ли его состояние State во множество
состояний [dsEdit, dsInsert]. Если метод Post отработал
без ошибок, то свойству ModalResult формы будет присвоено значение
mrOK, что автоматически приведет к закрытию формы.
Если данные в каких-то полях были изменены,
но мы не хотим посылать эти изменения на сервер,
нужно вызвать у метод Cancel компонента qryMaster. Сделаем так
, чтобы независимо от того, каким способом пользователь закрывает
окно редактирования шапки, если компонент находился при этом в
состоянии редактирования или вставки, то будет вызван метод Cancel
. Для этого создадим у формы StockInHeaderForm обработчик формы OnClose
и впишем в него такой текст:
with StockInForm.qryMaster do
if InSet(State, MkSet(dsEdit, dsInsert)) then
Cancel;
Запустим проект и вызовем окно шапки документа с помощью контекстного
меню. Изменим данные в полях шапки, например,
поменяем «Склад». Нажмем OK. А теперь попытаемся
закрыть окно документа. Мы должны увидеть предложение сохранить документ
:
Попробуем сохранить документ. Несколько раз запустим проект, в
каких-то случаях откажемся от сохранения, убедимся,
что все правильно работает. Итак, редактирование шапки документа
мы реализовали. Теперь реализуем создание нового документа.
Прежде, чем добавлять позиции в новый документ «Поступление
на склад», пользователь всегда заполняет его шапку. Документы
в Allegro создаются, как правило, из «Проводника
по документам», который просто вызывает наш проект оконного интерфейса
. Проект должен проверить значение переменной контекста RunContext.Documents
[0].doc_id и если оно не
положительно, сразу принять решение о том, что это
новый документ и вызвать форму для редактирования «шапки»
с помощью метода ShowModal. Нам нужно сделать так,
чтобы пользователь мог отменить создание документа на этом этапе,
нажав кнопку «Отмена» в окне редактирования «шапки
». Вызовем окно «шапки» в событии OnCreate (
при создании) главной формы проекта. Если пользователь откажется
от создания нового документа, нажав «Отмена», то
метод ShowModal вернет значение, отличное от mrOK. Это
и будет сигналом к ому, чтобы завершить работу проекта
. Для того чтобы завершить работу проекта нужно закрыть его
главную форму StockInForm. Так как в обработчике события OnCreate
формы закрыть эту форму нельзя, используем компонент таймера.
Запустим таймер в обработчике OnCreate формы, если требуется ее
закрыть. Когда произойдет событие таймера, то обработчик этого
события закроет главную форму.
Добавим на форму StockInForm компонент Timer (палитра System)
и установим его свойства:
Enabled = False
Interval = 100
А в обработчике OnCreate формы, после выражения
qryMaster.Open;
добавим такой текст:
if RunContext.Documents[0].doc_id <=0 then
begin
qryMaster.Insert;
{присваиваем начальные значения полям}
qryMaster.FieldByName('DIR_ID').AsInteger := RunContext.dir_id;
qryMaster.FieldByName('DOC_KIND').AsInteger := 0;
qryMaster.FieldByName('CALC_MODE').AsInteger := 1;
qryMaster.FieldByName('DOC_DATE').AsDateTime := Date;
qryMaster.FieldByName('HAS_ENTRY').AsInteger := 1;
qryMaster.FieldByName('ENTRY_DATE').AsDateTime := Date;
qryMaster.FieldByName('VAT_RATE').AsCurrency := 20;
qryMaster.FieldByName('TOTAL_L').AsCurrency := 0;
qryMaster.FieldByName('TOTAL_S').AsCurrency := 0;
qryMaster.FieldByName('TOTAL_R').AsCurrency := 0;
qryMaster.FieldByName('CONTRAGENT').AsInteger := 0;
qryMaster.FieldByName('STOCK_ACC').AsInteger := 1007; //Главный склад
qryMaster.FieldByName('ACC_ID').AsInteger := 1012; //Поставщики
if StockInHeaderForm.ShowModal <> mrOK then
Timer1.Enabled := True;
end;
if Timer1.Enabled then
exit;
Выберем компонент Timer, создадим обработчик его события OnTimer и
впишем в него:
Timer1.Enabled := False;
Modified := False;
Self.Close;
Метод Self.Close закроет главную форму, когда произойдет
событие таймера. А оно произойдет только в том случае
, если пользователь отказался от создания нового документа, нажав
кнопку Отмена в окне редактирования шапки.
При создании любой новый документ должен получить новое уникальное значение
для поля ID с помощью специального генератора DOC_ID
_GEN, существующего в базе данных. Займемся подключением
генератора DOC_ID_GEN к компоненту qryMaster.
Для этого выберем компонент qryMaster в Инспекторе объектов и дважды
щелкнем на его свойстве GeneratorField. Появится редактор свойства:
Установим свойства:
Generator = DOC_ID_GEN
Field = ID
Increment By = 1
Apply Event = On Post
Эти установки означают, что для получения новых значений поля
ID, если оно еще пустое, будет использоваться генератор
DOC_ID_GEN, каждый раз увеличивая свое
значение на единицу. Запуск генератора будет производиться в момент
вызова метода Post компонента qryMaster.
Нам нужно еще разобраться с обязательными и необязательными полями.
Дважды щелкнем на компоненте qryMaster и в Инспекторе объектов установим
свойство Required для следующих полей:
Поле |
Свойство Required |
STOCK_ACC_NAME |
False |
LAYER_NAME |
False |
CREDIT_ACC_NAME |
False |
CONTRAGENT_NAME |
False |
|
|
DOC_DATE |
True |
DOC_NO |
True |
VAT_RATE |
True |
EXCH_RATE_S |
True |
EXCH_RATE_L |
True |
ENTRY_DATE |
True |
Сохраним проект. Растянем Главное окно Allegro, вызовем «
Проводник по документам» и попытаемся создать из него с
помощью через контекстное меню новое «Поступление на склад».
Попробуем отменить, нажав Отмена. Попробуем создание еще раз
, заполним все поля шапки и нажмем OK. Закроем
окно документа, на вопрос о сохранении ответим Да.
Документ создан, хоть он пока и не виден в
«Проводнике». Для того чтобы его увидеть, нужно
освежить проводник (Ctrl+F5).
Для того чтобы после каждого сохранения документа проводник освежался автоматически
, добавим в конец текста обработчика OnExecute команды меню actSave
вызов следующей процедуры:
RefreshExplorer; //пересветить «Проводник по документам»
|