unit uBizAsientos; interface uses uDAInterfaces, uDADataTable, schAsientosClient_Intf, Classes, DBGrids, uDBSelectionList, DB, uExceptions, Controls, uBizCuentas, uBizIntervalos; const CTE_PUNTEADO = 1; CTE_NOPUNTEADO = 0; CTE_ASIENTO = 'A'; CTE_CIERRE = 'C'; type TDatosAsiento = class(TObject) public CodigoCuenta : Integer; FechaAsiento : TDateTime; CodigoPago : Integer; Descripcion : String; Importe: Currency; end; IBizAsiento = interface(IAsientos) ['{4585F549-EBAF-454C-826F-A8D527AD585E}'] function getCuenta: IBizCuenta; property Cuenta: IBizCuenta read getCuenta; procedure setIntervalo(const Value: IBizIntervalos); function getIntervalo: IBizIntervalos; property Intervalo: IBizIntervalos read getIntervalo write setIntervalo; procedure Show; function ShowForSelect : TModalResult; procedure cerrarCaja; procedure eliminarCierreCaja; procedure puntearAsiento; end; TBizAsientoDataTableRules = class(TAsientosDataTableRules, IBizAsiento, IApplyUpdateFailedException, ISelectedRowList) private FCuenta : IBizCuenta; FIntervalo: IBizIntervalos; FSumaAcumulada: Currency; FSelectedRows : TSelectedRowList; procedure ShowToSelect; procedure BeforeApplyUpdates(Sender: TDADataTable; const Delta: IDADelta); procedure OnPostError(DataTable: TDADataTable; Error: EDatabaseError; var Action: TDataAction); override; function puntearAsientos(const ValorPunteo: Integer): TDateTime; procedure recalcular(const DesdeFinal: Boolean); procedure CalcularImportes; function anadirCierre(FechaCierre: TDateTime): Boolean; function eliminarCierre: Boolean; function getCuenta: IBizCuenta; function getIntervalo: IBizIntervalos; procedure setIntervalo(const Value: IBizIntervalos); procedure OnCuentaChanged(Sender : TObject); procedure OnIntervaloChanged(Sender : TObject); procedure refrescarVista; protected procedure OnNewRecord(Sender: TDADataTable); override; function GetSelectedRows : TSelectedRowList; virtual; procedure ShowApplyUpdateFailed (const Error: EDAApplyUpdateFailed); virtual; procedure BeforeDelete(Sender: TDADataTable); override; procedure AfterPost(Sender: TDADataTable); override; procedure AfterDelete(Sender: TDADataTable); override; procedure AfterOpen(Sender: TDADataTable); override; public property SelectedRows : TSelectedRowList read GetSelectedRows; property Cuenta: IBizCuenta read getCuenta; property Intervalo: IBizIntervalos read getIntervalo write setIntervalo; constructor Create(aDataTable: TDADataTable); override; destructor Destroy; override; procedure Show; virtual; // procedure ShowAll; virtual; // procedure Preview; virtual; function ShowForSelect : TModalResult; virtual; procedure cerrarCaja; procedure eliminarCierreCaja; procedure puntearAsiento; end; procedure ValidarAsiento (const AAsiento : IBizAsiento); implementation uses Windows, Dialogs, uDACDSDataTable, SysUtils, uDataModuleBase, uDataModuleUsuarios, uEditorUtils, uDataModuleAsientos, Variants, Forms, uCerrarCaja, DateUtils, uDataModuleCuentas; procedure ValidarAsiento (const AAsiento : IBizAsiento); begin if (AAsiento.TIPOASIENTO = CTE_ASIENTO) then if (AAsiento.FECHAASIENTO <= AAsiento.Cuenta.ULTIMOCIERRE) then raise Exception.Create('El asiento no puede tener fecha anterior al último cierre'); end; { TBizAsientoDataTableRules } { ************************** TBizAsientoDataTableRules ************************** } procedure TBizAsientoDataTableRules.OnNewRecord(Sender: TDADataTable); begin inherited; USUARIO := dmUsuarios.LoginInfo.UserID; FECHAALTA := Date; TIPOASIENTO := CTE_ASIENTO; FECHAASIENTO := Date; PUNTEADO := CTE_NOPUNTEADO; CODIGOCUENTA := Cuenta.CODIGO; IMPORTE := 0; end; procedure TBizAsientoDataTableRules.Show; begin if PUNTEADO = CTE_PUNTEADO then begin raise Exception.Create('No se puede modificar este asiento porque está punteado'); exit; end; ShowEditor(IBizAsiento, Self, etItem); end; {procedure TBizAsientoDataTableRules.Preview; begin // dmContactos.Preview; end; } procedure TBizAsientoDataTableRules.ShowApplyUpdateFailed( const Error: EDAApplyUpdateFailed); begin if (Pos(AUF_FKVIOLATION, Error.Message) > 0) then MessageBox(0, 'No se puede borrar este asiento porque tiene pagos asociados', 'Atención', MB_ICONWARNING or MB_OK); end; function TBizAsientoDataTableRules.GetSelectedRows: TSelectedRowList; begin Result := FSelectedRows; end; procedure TBizAsientoDataTableRules.ShowToSelect; begin // end; constructor TBizAsientoDataTableRules.Create(aDataTable: TDADataTable); begin inherited; FSumaAcumulada := 0; FCuenta := dmCuentas.GetItems; FCuenta.OnCuentaChanged := OnCuentaChanged; FSelectedRows := TSelectedRowList.Create(aDataTable); aDataTable.OnBeforeApplyUpdates := BeforeApplyUpdates; aDataTable.AfterOpen := AfterOpen; end; destructor TBizAsientoDataTableRules.Destroy; begin FCuenta := Nil; FSelectedRows.Free; inherited; end; {procedure TBizAsientoDataTableRules.ShowAll; begin // ShowEditor(IBizProveedor, Self, etItems); end; } function TBizAsientoDataTableRules.ShowForSelect: TModalResult; begin Result := ShowEditor(IBizAsiento, Self, etItems); end; procedure TBizAsientoDataTableRules.BeforeApplyUpdates( Sender: TDADataTable; const Delta: IDADelta); var i: Integer; begin for i := 0 to Delta.Count - 1 do case Delta.Changes[i].ChangeType of ctInsert, ctUpdate : ValidarAsiento(Self); // ctDelete : end; end; procedure TBizAsientoDataTableRules.OnPostError(DataTable: TDADataTable; Error: EDatabaseError; var Action: TDataAction); begin inherited; Action := daAbort; if (Pos(AUF_HAVEVALUE, Error.Message) > 0) then begin if (Pos('Nombre', Error.Message) > 0) then MessageBox(0, 'Debe indicar una descripción', 'Atención', MB_ICONWARNING or MB_OK) else raise Error; end else raise Error; end; procedure TBizAsientoDataTableRules.BeforeDelete(Sender: TDADataTable); begin //Si es un CIERRE lo que se elimina, debemos despuntear los asientos que comprende if TIPOASIENTO = CTE_CIERRE then begin puntearAsientos(CTE_NOPUNTEADO); dmAsientos.AsignarPunteado(Cuenta.CODIGO, FECHAASIENTO, Cuenta.PENULTIMOCIERRE, CTE_NOPUNTEADO); end; inherited; end; procedure TBizAsientoDataTableRules.CalcularImportes; var varImporteAnt: double; varImportePos: double; ABookmark: Pointer; begin DataTable.DisableControls; DataTable.DisableEventHandlers; try ABookmark := DataTable.GetBookMark; DataTable.Next; //Comprobamos si calculamos desde el principio o desde un asiento intermedio if DataTable.EOF then varImportePos:= FSumaAcumulada else begin varImportePos:= IMPORTEPOS; DataTable.Prior; end; while not Self.DataTable.BOF do begin DataTable.edit; IMPORTEANT := varImportePos; IMPORTEPOS := IMPORTEANT + IMPORTE; varImportePos := IMPORTEPOS; DataTable.Post; DataTable.Prior; end; finally DataTable.GotoBookmark(Abookmark); DataTable.FreeBookmark(ABookmark); DataTable.EnableControls; DataTable.EnableEventHandlers; end; end; procedure TBizAsientoDataTableRules.AfterPost(Sender: TDADataTable); var i, x: Integer; begin //Comprobamos los cambios para solo recalcular en el caso de modificar el importe //y añadir un asiento nuevo for i:=0 to DataTable.Delta.count-1 do if (DataTable.Delta.Changes[i].ChangeType = ctInsert) then begin //Este evento salta dos veces, cuando se hace el post en el cliente //y cuando se hace el applyupdates. En el primer caso todavía no se //ha asignado el codigo, de esta forma omitiremos el recalculo y //optimizamos el número de llamadas al mismo if CODIGO > 0 then recalcular(False); end else if (DataTable.Delta.Changes[i].ChangeType = ctUpdate) then if (DataTable.Delta.Changes[i].OldValueByName[fld_AsientosIMPORTE] <> DataTable.Delta.Changes[i].NewValueByName[fld_AsientosIMPORTE]) then recalcular(False); end; procedure TBizAsientoDataTableRules.AfterDelete(Sender: TDADataTable); begin recalcular(False); end; procedure TBizAsientoDataTableRules.AfterOpen(Sender: TDADataTable); begin recalcular(True); end; procedure TBizAsientoDataTableRules.recalcular(const DesdeFinal: Boolean); begin if DesdeFinal then DataTable.Last else begin DataTable.Sort(['FECHAASIENTO', 'CODIGO'],[sdDescending, sdDescending]); //En el caso de ser un CIERRE NUEVO el asiento introducido punteamos "localmente" //todos los asientos anteriores al cierre y luego mandamos al servidor puntear en BD if TIPOASIENTO = CTE_CIERRE then begin puntearAsientos(CTE_PUNTEADO); dmAsientos.AsignarPunteado(Cuenta.CODIGO, FECHAASIENTO, Cuenta.PENULTIMOCIERRE, CTE_PUNTEADO); end; end; CalcularImportes; end; procedure TBizAsientoDataTableRules.cerrarCaja; begin with TfrCerrarCaja.Create(NIL) do try eFechaCierre.Date := FECHAASIENTO; ShowModal; if ModalResult = mrOk then begin if anadirCierre(eFechaCierre.Date) then begin DataTable.ApplyUpdates; Cuenta.DataTable.ApplyUpdates; end else begin DataTable.CancelUpdates; Cuenta.DataTable.CancelUpdates; end; end; finally Free; end; end; function TBizAsientoDataTableRules.anadirCierre(FechaCierre: TDateTime): Boolean; begin try Result:= True; //Insertamos un asiento de cierre DataTable.Insert; FECHAASIENTO := FechaCierre; TIPOASIENTO := CTE_CIERRE; PUNTEADO := CTE_PUNTEADO; DESCRIPCION := 'CIERRE ' + DateToStr(FechaCierre); DataTable.Post; //Asignamos el nuevo último cierre de la cuenta activa Cuenta.DataTable.Edit; Cuenta.PENULTIMOCIERRE := Cuenta.ULTIMOCIERRE; Cuenta.ULTIMOCIERRE := FechaCierre; Cuenta.DataTable.Post; except on E: Exception do Result:= False; end; end; function TBizAsientoDataTableRules.getCuenta: IBizCuenta; begin if (not FCuenta.DataTable.Active) then FCuenta.DataTable.Active := True; Result := FCuenta; end; function TBizAsientoDataTableRules.puntearAsientos(const ValorPunteo: Integer): TDateTime; //Punteamos con DisableEventHandlers con el fin de que no se produzcan deltas, ya //que la modificación de los registros se realizará por un servicio, esto es solo //para que los cambios se reflejen en local sin tener que refrescar. var ABookmark : TBookmark; begin Self.DataTable.DisableControls; Self.DataTable.DisableEventHandlers; try //Saltamos el asiento de cierre ABookmark := DataTable.GetBookMark; DataTable.Next; while (not DataTable.EOF) and (TIPOASIENTO = CTE_ASIENTO) do begin DataTable.edit; PUNTEADO := ValorPunteo; DataTable.Post; DataTable.Next; end; finally //En el caso de salir del bucle por ser final de fichero y no por encontrar otro //cierre restamos un día para incluir todos los asientos del final de la tabla Result := FECHAASIENTO; if (DataTable.EOF) then Result:= Result - 1; DataTable.GotoBookmark(ABookmark); DataTable.FreeBookmark(ABookmark); DataTable.EnableControls; DataTable.EnableEventHandlers; end; end; procedure TBizAsientoDataTableRules.eliminarCierreCaja; begin if (FECHAASIENTO < Cuenta.ULTIMOCIERRE) then raise Exception.Create('No puede eliminar un cierre anterior al último cierre de la cuenta'); if eliminarCierre then begin DataTable.ApplyUpdates; Cuenta.DataTable.ApplyUpdates; end else begin DataTable.CancelUpdates; Cuenta.DataTable.CancelUpdates; end; end; function TBizAsientoDataTableRules.eliminarCierre: Boolean; begin try Result := True; DataTable.Delete; //Asignamos el nuevo último cierre de la cuenta activa Cuenta.Edit; Cuenta.ULTIMOCIERRE := Cuenta.PENULTIMOCIERRE; Cuenta.PENULTIMOCIERRE := dmAsientos.darPenultimoCierre(Cuenta.CODIGO); Cuenta.Post; except on E: Exception do Result := False; end; end; procedure TBizAsientoDataTableRules.refrescarVista; var fechaini: TDateTime; fechaFin: TDateTime; i: integer; begin if DataTable.Active then DataTable.Active := False; if (Intervalo.DIAS = 0) and (Intervalo.MESES = 0) and (Intervalo.ANOS = 0) then begin fechaIni := MaxDateTime; fechaFin := MinDateTime; FSumaAcumulada := 0; end else begin fechaini := date; fechaFin := date; fechafin := IncDay(fechafin,(-1)*Intervalo.DIAS); for i:=1 to Intervalo.MESES do fechafin := IncDay(fechafin,(-1)*DaysInMonth(fechafin)); for i:=1 to Intervalo.ANOS do fechafin := IncDay(fechafin,(-1)*DaysInYear(fechafin)); FSumaAcumulada := dmAsientos.darSumaAcumulada(FCuenta.CODIGO, fechafin, MinDateTime); end; DataTable.Where.Clear; DataTable.Where.OpenBraket; DataTable.Where.AddText('ASIENTOS.CODIGOCUENTA' + ' = ' + IntToStr(FCuenta.CODIGO), False); DataTable.Where.CloseBraket; DataTable.Where.AddOperator(opAND); DataTable.Where.OpenBraket; DataTable.Where.AddText('ASIENTOS.FECHAASIENTO between ''' + FormatDateTime('dd.mm.yyyy', FechaFin) + ''' and ''' + FormatDateTime('dd.mm.yyyy', FechaIni) + '''', False); DataTable.Where.CloseBraket; DataTable.Active := True; end; procedure TBizAsientoDataTableRules.OnIntervaloChanged(Sender: TObject); begin refrescarVista; end; function TBizAsientoDataTableRules.getIntervalo: IBizIntervalos; begin Result := FIntervalo; end; procedure TBizAsientoDataTableRules.setIntervalo(const Value: IBizIntervalos); begin FIntervalo := Value; if Assigned(FIntervalo) then begin Fintervalo.OnIntervaloChanged := OnIntervaloChanged; refrescarVista; end; end; procedure TBizAsientoDataTableRules.OnCuentaChanged(Sender: TObject); begin refrescarVista; end; procedure TBizAsientoDataTableRules.puntearAsiento; begin if (FECHAASIENTO <= Cuenta.ULTIMOCIERRE) then raise Exception.Create('No puede despuntear un asiento cerrado'); try DataTable.Edit; if PUNTEADO = CTE_PUNTEADO then PUNTEADO := CTE_NOPUNTEADO else PUNTEADO := CTE_PUNTEADO; DataTable.Post; DataTable.ApplyUpdates; except on E: Exception do begin DataTable.CancelUpdates; raise Exception.Create(E.message); end; end; end; initialization RegisterDataTableRules('BizAsiento', TBizAsientoDataTableRules); finalization end.