MIDAS. Практическое применение

select * from REP_INOUT(:FromDate, :ToDate) order by TO_NAMEПри этом необходимо учитывать, что данные из этой процедуры получаются совершенно не в

MIDAS. Практическое применение

Информация

Компьютеры, программирование

Другие материалы по предмету

Компьютеры, программирование

Сдать работу со 100% гаранией
очень похожа на аналогичную реализацию в 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 then

raise Exception.Create('Нет нового или открытого документа');

// Вычисляем итоговую сумму документа

with cdsTitle do

begin

Edit;

FieldByName('DOC_SUM').asCurrency := CalcSum;

Post;

end;

RenumLines; // перенумерация содержимого

 

// Сохранение в БД...

 

ibtDoc.StartTransaction;

// При вставке сначала сохраняем изменения в cdsTitle...

if FState = osInsert then

begin

%200%20then"> if cdsTitle.ChangeCount > 0 then

cdsTitle.ApplyUpdates(0);

%200%20then"> if cdsBody.ChangeCount > 0 then

cdsBody.ApplyUpdates(-1);

end;

// ...а при изменении в cdsBody.

if FState = osUpdate then

begin

%200%20then"> if cdsBody.ChangeCount > 0 then

cdsBody.ApplyUpdates(-1);

%200%20then"> 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 >= 0

cdsBody.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;Разумеется, и вычисление суммы документа, и перенумерацию содержимого можно сделать на клиентской части, но этот пример создавался именно чтобы показать перенос вычислений на сервер. При более сложных вычислениях это гораздо выгоднее, например, если в расчетах используются данные из дополнительных таблиц.

Остается последний модуль данных сервера, rdmReport, предназначенный для создания отчета. По сравнению с предыдущими модулями он довольно прост (рисунок 4.).

Рисунок 4.

Здесь находится всего один компонент транзакции ibtInOut и один компонент запроса ibqInOut, обращающийся к процедуре отчета:

select * from REP_INOUT(:FromDate, :ToDate) order by TO_NAMEПри этом необходимо учитывать, что данные из этой процедуры получаются совершенно не в том виде, который нужен, и нуждаются в дополнительной обработке. Такую дополнительную обработку лучше осуществлять на стороне клиента, так как это потенциально позволяет передавать данные в более компактном виде, да и само представление данных является частью презентационной логики. Но этот пример создавался, чтобы продемонстрировать, в основном, работу серверной стороны. Поэтому обработку данных мы будем производить на сервере. cdsInOut это компонент ClientDataSet, в котором формируется отчет в том виде, в котором он должен быть отображен клиенту. К этому компоненту подсоединен провайдер dspInOut с установленным флагом poIncFieldProps. Его свойство Exported равно false. От провайдера требуется только генерация пакета данных. И, как обычно, ResolveToDataSet = true. cdsInOut не соединен ни с каким провайдером (свойство ProviderName пустое), и должен создаваться явно вызовом своего метода CreateDataSet. Для того, чтобы набор данных содержал поля, их описания должны содержаться в свойстве FieldDefs. Но по той причине, что в отчете-шахматке количество полей в записи заранее неизвестно, их описания приходится создавать динамически при обработке результата запроса. Для этого удобно создать отдельный метод, CollectInOutData:

function TrdmReport.CollectInOutData: OleVariant;

const

FieldPrefix = 'Receiver_';

var

ReceiverFieldName: string;

Похожие работы

<< < 1 2 3 4 5 6 7 8 > >>