Чтобы было проще анализировать затраты и делать какие-то выводы, очень часто прибегают к группировке затрат (в бухгалтерском учете для этого служит механизм синтетических и аналитических счетов). Я сознательно не стал следовать этой логике, чтобы на первых порах непосвященным в учет пользователям было не страшно и понятно.
Но теперь настало время объединить счета по какому-то признаку в группы, чтобы в дальнейшем давать обобщенное представление группы затрат.
Напомню, что при создании базы данных я заготовил пару таблиц:
ExpenseGroup
и ExpenseAcc
С первой таблицей - все просто: это - классический кодификатор (список, перечень) названий будущих групп. Ее можно сразу же предварительно заполнить для наглядности (мне в голову пришли вот такие группы, но у Вас могут быть иные):
Вторая таблица называется корреляционной. Чтобы понять, в чем ее суть, нужно вернуться к схеме данных:
На этом фрагменте схемы данных изображены три связанные между собой таблицы. Две из них - кодификаторы (Accounts и ExpenseGroup), содержащие перечень счетов и перечень названий групп.
Третья таблица - связывает (через ID) счета с названиями групп, т.е. в ней хранится ответ на вопрос:
"Какие счета содержит та или иная группа?" или "В какую группу входит тот или иной счет?"
Например (я сейчас покажу картинку, которой у Вас пока быть не может, пока мы находимся на пути к созданию соответствующего интерфейса, это будет просто иллюстрация того, как устроена корреляция):
Раскрыв в списке групп строку с ID=6 (Дом), видно, что в связанной с ней корреляционной таблице существует несколько строк, т.е. в эту группу включены счета с их ID: 36, 44, 24, 38 и 28. По таблице счетов видно, что все эти счета так или иначе связаны с расходами по содержанию квартиры (дома).
Обратите внимание, что эти счета отмечены в поле Groups таблицы Accounts. Это - важно.
Надеюсь, что идея ясна. Пора приступать к реализации интерфейса.
Общий алгоритм добавления формы в проект уже пройден несколько раз.
Добавьте в проект форму GroupsFrm вот такого вида:
Несколько слов о компонентах этой формы.
Компонент TADOTable,
с которым через TDataSource
связана верхняя сетка:
Эта форма должна позволять наполнять и редактировать список групп, поэтому на ней размещен навигатор собственного производства, который так же связан с источником ADOTableGroups.
Следующие компоненты: запрос TADOQuery, источник данных и Grid слева внизу связаны между собой аналогичным образом:
Grid справа внизу связана посредством своего DataSource с таблицей, расположенной на главной форме:
ActionList имеет одну строку, о которой тоже говорилось ранее:
Компонент, о котором так же говорилось ранее - TADOCommand
Остались две кнопки со стрелками, которые и будут определять основную логику работы формы: выбрал справа счет, нажал кнопочку "<<", счет включился в выбранную в верхней табличке группу. Кнопка ">>" будет исключать счет из группы.
Теперь займемся кодом.
В главной форме припишите к соответствующему пункту меню обработчик события:
procedure TMainFrm.N_ExpGrClick(Sender: TObject);
begin
Application.CreateForm(TGroupsFrm, GroupsFrm);
GroupsFrm.ShowMoDal;
GroupsFrm.Free;
end;
Весь следующий текст будет касаться модуля Groups формы GroupsFRM.
Среди переменных общего пользования, нужно объявить одну - для хранения значения фильтра:
var
GroupsFrm: TGroupsFrm;
LastFilter: String; // Понадобится запоминать значение фильтра
Чтобы можно было обращаться к главной форме, использовать ее общие компоненты и переменные и к форме Accounts:
implementation
Uses Main
, Accounts;
Далее - в привычном порядке, сначала об открытии и закрытии формы, а потом - по сути алгоритма.
Процедура создания формы:
procedure TGroupsFrm.FormCreate(Sender: TObject);
Var MyStr: String;
begin
// Подготовка таблицы - кодификатора групп
ADOTableGroups.Connection:=MainFrm.ADOConnection1;
ADOTableGroups.Filter:='ID>1'; // Первая строка, в которой обычно пишется
ADOTableGroups.Filtered:=True; // что-то типа "Значение не задано"
// отфильтровывается
ADOTableGroups.Active:=True;
// Наполняется значениями запрос ADOQueryMembers
ADOQueryMembers.Connection:=MainFrm.ADOConnection1;
ADOQueryMembers.Active:=False;
ADOQueryMembers.SQL.Clear;
MyStr:='SELECT Accounts.Name, ExpenseAcc.IDGroup, ExpenseAcc.IDAcc, ExpenseAcc.ID ';
MyStr:=MyStr + 'FROM Accounts INNER JOIN ExpenseAcc ON Accounts.ID = ExpenseAcc.IDAcc ';
ADOQueryMembers.SQL.Add(MyStr);
ADOQueryMembers.Active:=True;
ADOCommand1.Connection:=MainFrm.ADOConnection1;
// Запоминается фильтр, наложенный на таблицу ранее (другим интерфейсом)
LastFilter:= MainFrm.ADOTableAcc.Filter;
// С помощью нового фильтра выбираются счета с признаком Analiz, относящиеся к определенной валюте
// и не включенные в группу
MainFrm.ADOTableAcc.Filter:='(Analiz=True) AND (Val='+IntToStr(Main.MySelect.MySel_IDVal)+') AND Groups=0';
MainFrm.ADOTableAcc.Filtered:=True;
end;
и процедура закрытия формы:
procedure TGroupsFrm.FormClose(Sender: TObject; var Action: TCloseAction);
begin
// При закрытии формы - вернуть таблице счетов прежний фильтр
MainFrm.ADOTableAcc.Filter:=LastFilter;
MainFrm.ADOTableAcc.Filtered:=True;
Action:= caFree;
end;
Код для обработки единственного Action:
procedure TGroupsFrm.BottonsClicksExecute(Sender: TObject);
begin
// Если таблица ADOTableGroups находится в режиме редактирования
if ADOTableGroups.Modified
then
Try
ADOTableGroups.Post; // то - запись в источник
finally
end;
// Если пользователь не завершил редактирование запроса
if ADOQueryMembers.Modified
then
Try
ADOQueryMembers.Post; // то - запись в источник
finally
end;
case ModalResult of // Обработка нажатия кнопок OK и Cancel
1:
begin
MyClose;
end;
2:
Begin
MyClose;
end;
end;
end;
где процедура MyClose имеет следующее содержание:
procedure TGroupsFrm.MyClose();
Begin
// Отключение таблицы
ADOTableGroups.Active:=False;
// Закрытие формы
Close;
end;
Несколько вспомогательных процедур.
Объявите приватную процедуру MyRefresh и напишите ее в следующем виде:
procedure TGroupsFrm.MyRefresh();
begin
// Чтобы избежать сбоя на пустой таблице
if ADOTableGroups.FieldByName('ID').Value>0
then
begin
ADOQueryMembers.Filter:='IDGroup='+IntToStr(ADOTableGroups.FieldByName('ID').Value);
ADOQueryMembers.Filtered:=True;
end;
end;
Она обновляет набор содержащихся в группе счетов. Набор должен обновляться, когда пользователь осуществляет скролинг - переход по записям в таблице Групп или, когда он закончил ее редактирование:
procedure TGroupsFrm.ADOTableGroupsAfterScroll(DataSet: TDataSet);
begin
MyRefresh();
end;
procedure TGroupsFrm.ADOTableGroupsAfterPost(DataSet: TDataSet);
begin
MyRefresh();
end;
Добавьте еще две Private процедуры, одна из которых будет добавлять счет в группу, а вторая - удалять:
procedure TGroupsFrm.MyAdd();
begin
// Добавление счета в группу
With ADOQueryMembers Do // Работаем с запросом
Begin
Insert; // Добавление новой записи
Try
// Присвоение значений полям
FieldByName('IDGroup').Value:=ADOTableGroups.FieldByName('ID').Value;
FieldByName('IDAcc').Value:=MainFrm.ADOTableAcc.FieldByName('ID').Value;
// Запись в таблицу-источник
Post;
// Фиксирование признака в кодификаторе счетов
MainFrm.ADOTableAcc.Edit;
MainFrm.ADOTableAcc.FieldByName('Groups').Value:=True;
MainFrm.ADOTableAcc.Post;
// Обновление данных
Requery();
finally
MainFrm.ADOTableAcc.Requery();
end;
end;
end;
Procedure TGroupsFrm.MyDelete();
begin
Try
// Найти счет и пометить его, что он больше не используется в группах
ADOCommand1.CommandText:='UPDATE Accounts SET Accounts.Groups = False WHERE (((Accounts.ID)='+IntToStr(ADOQueryMembers.FieldByName('IDAcc').Value)+'))';
ADOCommand1.Execute;
// Обновить кодификатор счетов
MainFrm.ADOTableAcc.Requery();
// Удалить строку из корреляционной таблицы
ADOCommand1.CommandText:='DELETE ExpenseAcc.ID FROM ExpenseAcc WHERE (((ExpenseAcc.ID)='+IntToStr(ADOQueryMembers.FieldByName('ID').Value)+')) ';
ADOCommand1.Execute;
finally
ADOQueryMembers.Requery();
end;
end;
Теперь осталось назначить обработчики событий нажатия кнопок со стрелками и для удобства - двойных кликов по соответствующим сеткам:
procedure TGroupsFrm.Button1Click(Sender: TObject);
begin
MyAdd();
end;
procedure TGroupsFrm.DBGridEh3DblClick(Sender: TObject);
begin
MyAdd();
end;
procedure TGroupsFrm.Button2Click(Sender: TObject);
begin
MyDelete;
end;
procedure TGroupsFrm.DBGridEh2DblClick(Sender: TObject);
begin
MyDelete();
end;
Для любителей пользоваться клавиатурой можно запрограммировать под аналогичные действия нажатия кнопок:
procedure TGroupsFrm.FormKeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
begin
case Key of // Start Case
VK_INSERT:
MyAdd();
VK_DELETE:
MyDelete();
else
End; // End case
end;
С точки зрения программирования, я не рассказал ничего нового, но... в следующем уроке я планирую переделать интерфейс данной формы для использования технологии Drag & Drop при формировании списка счетов, включаемых в группу затрат.
Комментариев нет:
Отправить комментарий
Примечание. Отправлять комментарии могут только участники этого блога.