Смекни!
smekni.com

MIDAS. Практическое применение (стр. 4 из 6)

Теперь компоненты для хранения и обработки данных подготовлены, осталось написать сами методы работы с ними.

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

osInactive: данных нет, документ не редактируется,

osInsert: создан новый документ и

osUpdate – происходит изменение существующего документа.

Состояние хранится в переменной Fstate, находящейся внутри модуля. Сразу после создания и после окончания обработки документа модуль данных должен находиться в неактивном состоянии.

Переход из одного состояния в другое должен обеспечиваться соответствующими методами. Я назвал эти методы DoInactiveState (перевод в неактивное состояние), DoOpen (открыть существующий документ) и DoCreateNew (создание нового документа). При редактировании или добавлении документа нужно знать его уникальный номер, записываемый в поле DOC_ID. Для этого достаточно объявить в секции private переменную FDocID: integer, которая и будет его хранить.

В библиотеке типов нужно реализовать методы, которые будут создавать документ или открывать существующий, а также сохранять изменения. Кроме этого, понадобится свойство, позволяющее получить в любой момент сумму по документу. Сумма каждой строки содержимого пусть рассчитывается на клиентской части.

Итак, приступим. Сначала описываются методы перехода между состояниями, они предназначены для внутреннего использования, и поэтому их объявления содержатся в секции private:

procedure DoInactiveState; procedure DoCreateNew; procedure DoOpen(DocID: integer);

Рассмотрим их по порядку.

procedure TrdmDoc.DoInactiveState;begin UnregisterDoc(FDocID); FDocID := 0; cdsTitle.Active := False; cdsBody.Active := False; ibtDoc.Active := False; FState := osInactive;end;

Процедура DoInactiveState удаляет документ из списка редактируемых, закрывает все клиентские наборы данных, а также производит откат транзакции (если она была активна).

procedure TrdmDoc.DoOpen(DocID: Integer);begin if DocID = 0 then Exit; try if not RegisterDoc(DocID) then raise Exception.Create('Документредактируется'); FDocID := DocID; // итолькоздесь, иначе DoInactiveState удалитдокумент ibdDocs.Connected := True; ibtDoc.StartTransaction; with cdsTitle do begin params.paramByName('DocID').AsInteger := FDocID; Active := True; if BOF and EOF then raise Exception.Create('Документненайден'); end; with cdsBody do begin params.paramByName('DocID').AsInteger := FDocID; Active := True; end; FState := osUpdate; ibtDoc.Commit; except DoInactiveState; raise;end;end;

DoOpen предназначена для открытия существующего документа, идентификатор DOC_ID которого равен входному параметру DocID. Первым делом с помощью RegisterDoc производится проверка того, что документ в данный момент не редактируется. Затем идентификатор документа запоминается, и в клиентские наборы данных загружаются данные документа. В случае ошибки состояние документа переводится в osInactive.

procedure TrdmDoc.DoCreateNew;var NewDocID: Integer;begin try NewDocID := NewID; if not RegisterDoc(NewDocID) then raise Exception.Create('Документредактируется'); FDocID := NewDocID; ibdDocs.Connected := True; ibtDoc.StartTransaction; with cdsTitle do begin params.paramByName('DocID').AsInteger := FDocID; Active := True; Append; Post; end; with cdsBody do begin params.paramByName('DocID').AsInteger := FDocID; Active := True; end; ibtDoc.Commit; FState := osInsert; except DoInactiveState; raise;end;end;

Процедура DoCreateNew предназначена для создания нового документа. Она практически аналогична предыдущей, за исключением того, что идентификатор документа получается от сервера БД с помощью процедуры NewID, которая обращается к хранимой процедуре на сервере. Реализация процедуры DoCreateNew очень похожа на аналогичную реализацию в rdmCommon.

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

procedure TrdmDoc.cdsTitleNewRecord(DataSet: TDataSet);var Day, Month, Year: Word;begin DecodeDate(Date, Year, Month, Day); with cdsTitle do begin FieldByName('DOC_ID').AsInteger := FDocID; FieldByName('DOC_NUM').AsString := IntToStr(FDocID) + '/' + IntToStr(Year); FieldByName('DOC_DATE').asDateTime := Date; FieldByName('DOC_SUM').asCurrency := 0; FieldByName('FROM_ID').AsInteger := 0; FieldByName('TO_ID').AsInteger := 0; end;end;procedure TrdmDoc.cdsBodyNewRecord(DataSet: TDataSet);begin cdsBody.FieldByName('DOC_ID').AsInteger := FDocID;end;

В дополнение ко всему нужна еще одна процедура в секции private, для подсчета суммы документа:

function TrdmDoc.CalcSum: Currency;begin Result := 0; if not cdsBody.Active then Exit; with cdsBody do begin First; while not EOF do begin Result := Result + FieldByName('COUNT_NUM').asCurrency * FieldByName('PRICE').asCurrency;Next; end; end;end;

В функции CalcSum просматривается содержимое документа и рассчитывается общая сумма, которая возвращается в качестве результата.

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

protected function ApplyChanges: WideString; safecall; function Get_DocID: Integer; safecall; procedure CreateNewDoc; safecall; procedure Set_DocID(Value: Integer); safecall; function Get_DocSum: Currency; safecall;

Функциональность этих методов такова:

ApplyChanges – сохраняет текущий документ в БД.

DocID – свойство, доступное на запись и чтение При чтении выдается текущий ID документа (FDocID). При изменении значения свойства документ открывается для редактирования с ID, равным новому значению. Если значение свойства равно 0, документ закрывается, и модуль переводится в неактивное состояние.

CreateNewDoc – создает новый документ (вызывает методы DoInactiveState и DoCreateNew).

DocSum – выдается текущая сумма документа, результат работы метода CalcSum.

Реализация этих методов довольно проста, все основные процедуры уже есть, сложность представляет только функция ApplyChanges:

function TrdmDoc.ApplyChanges: WideString;begin lock; try FLastUpdateErrors := ''; if FState = osInactive thenraise Exception.Create('Нет нового или открытого документа'); // Вычисляем итоговую сумму документа with cdsTitle dobegin Edit; FieldByName('DOC_SUM').asCurrency := CalcSum;Post; end; RenumLines; // перенумерация содержимого // Сохранение в БД... ibtDoc.StartTransaction; // При вставке сначала сохраняем изменения в cdsTitle...if FState = osInsert then begin if cdsTitle.ChangeCount > 0 then cdsTitle.ApplyUpdates(0); if cdsBody.ChangeCount > 0 thencdsBody.ApplyUpdates(-1); end; // ...а при изменении – в cdsBody.if FState = osUpdate then begin if cdsBody.ChangeCount > 0 then cdsBody.ApplyUpdates(-1); if cdsTitle.ChangeCount > 0 then cdsTitle.ApplyUpdates(0); end; // FLastUpdateErrors заполняетсяна OnReconcileError. Result := FLastUpdateErrors; if Result = '' then ibtDoc.Commit else begin ibtDoc.Rollback; end; finally ibtDoc.Active := False;unlock; end;end;

Дело в том, что изменение данных в БД происходит не в методе провайдера, а в методе модуля, и клиентские наборы данных ничего об этом не знают. Поэтому функция ApplyChanges возвращает список ошибок, возникших при обновлении данных. Список накапливается в переменной FLastUpdateErrors, описанной в секции private как FLastUpdateErrors: String;. Перед сохранением изменений рассчитывается сумма документа. Процедура RenumLines нумерует строки содержимого по порядку. Это просто дополнительный сервис. Затем ClientDataSet-ы пытаются сохранить изменения в БД. При возникновении ошибки заполняется поле FLastUpdateErrors:

procedure TrdmDoc.cdsTitleReconcileError(DataSet: TClientDataSet; E: EReconcileError; UpdateKind: TUpdateKind; var Action: TReconcileAction);begin Action := raCancel; FLastUpdateErrors := FLastUpdateErrors + 'Заголовок: ' + E.Message + #13#10;end;procedure TrdmDoc.cdsBodyReconcileError(DataSet: TClientDataSet; E: EReconcileError; UpdateKind: TUpdateKind; var Action: TReconcileAction);begin Action := raCancel; FLastUpdateErrors := FLastUpdateErrors + 'Содержимое: ' + E.Message + #13#10;end;

При этом происходит откат транзакции. Сообщения об ошибке записываются в строку. В случае возникновения ошибки клиент должен вывести сообщение и обновить клиентские наборы данных. Как будет видно ниже, в данном случае все проверки можно сделать заранее, и практически возможны только ошибки, связанные с непредвиденными обстоятельствами (например, неожиданный разрыв соединения с сервером БД).

Процедура RenumLines перенумерует строки содержимого документа так, чтобы номера шли по порядку, причем все номера сначала делаются отрицательными, иначе при попытке запомнить вторую запись с тем же ключем сразу генерируется исключение Key violation, что, разумеется, совершенно не нужно (Дело в том, что провайдер великолепно знает, какие поля составляют первичный ключ, вот и контролирует – у ClientDataSet создается контроль первичного ключа. Исключение генерируется сразу, при попытке вставки (до записи в БД)):

procedure TrdmDoc.RenumLines;var Num: Integer;begin cdsBody.IndexFieldNames := 'DOC_ID;LINE_NUM';// Чтобы избежать Key violation при перенумерации, делаем все номера < 0 // На клиенте нужна проверка LINE_NUM >= 0cdsBody.Last; with cdsBody do while FieldByName('LINE_NUM').AsInteger > 0 do begin Edit; Num := FieldByName('LINE_NUM').AsInteger; FieldByName('LINE_NUM').AsInteger := -num; Post; Last; end; // перенумерация... Num := cdsBody.RecordCount; cdsBody.First; with cdsBody do while FieldByName('LINE_NUM').AsInteger <= 0 do begin Edit; FieldByName('LINE_NUM').AsInteger := num; Post; Dec(Num); First; end;end;

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