unit uControllerDetallesBase;

interface

uses Classes, Variants, uDACDSDataTable, uDADataTable, uControllerBase;

const
  CAMPO_ID = 'ID';
  CAMPO_POSICION = 'POSICION';
  CAMPO_TIPO = 'TIPO_DETALLE';
  CAMPO_CONCEPTO = 'CONCEPTO';
  CAMPO_CANTIDAD = 'CANTIDAD';
  CAMPO_IMPORTE_UNIDAD = 'IMPORTE_UNIDAD';
  CAMPO_IMPORTE_TOTAL = 'IMPORTE_TOTAL';

  TIPO_DETALLE_CONCEPTO = 'Concepto';
  TIPO_DETALLE_TITULO = 'Titulo';
  TIPO_DETALLE_SUBTOTAL = 'Subtotal';
  TIPO_DETALLE_SALTO = 'Salto';

  CTE_DESC_SALTO = 'SALTO DE PAGINA >>';

type
  TIntegerArray = array of Integer;

  IControllerDetallesBase = interface(ISujeto)
  ['{F0B0E714-EC0D-4B6B-98B1-76F72F70B735}']

    function getTipo(ADataTable: IDAStronglyTypedDataTable; pPosicion: Integer): String;
    procedure Clear(ADataTable: IDAStronglyTypedDataTable);
    procedure Add(ADataTable: IDAStronglyTypedDataTable; TipoConcepto: Variant);
    procedure Delete(ADataTable: IDAStronglyTypedDataTable; Posicion: TIntegerArray);
    procedure Move(ADataTable: IDAStronglyTypedDataTable; Posicion: TIntegerArray; Posiciones: Integer);

//    procedure Copy(SMExport: TSMExportToClipboard);
//    procedure Paste;

    procedure ActualizarTotales(ADataTable: IDAStronglyTypedDataTable);
    function DarTotalImporteTotal(ADataTable: IDAStronglyTypedDataTable): Double;

    function DarListaTiposDetalle: TStringList;
  end;

  TControllerDetallesBase = class (TSujeto, IControllerDetallesBase)
  private
    fObservadores: IInterfaceList;
    fUpdateCount: Integer;

    function CalcularTotales(Modificar: boolean; DataTable: TDADataTable): Double;

  protected
    procedure Renumerar(DataTable: TDADataTable; LocalizaPosicion: Integer);
    function DesplazarNPosiciones(DataTable: TDADataTable; NumOrdenIni: Variant; NPosiciones: Variant): Integer;
    procedure Mover(DataTable: TDADataTable; Posicion: Integer; NumPosiciones: Integer);
    procedure BeginUpdate(ADataTable: IDAStronglyTypedDataTable);
    procedure EndUpdate(ADataTable: IDAStronglyTypedDataTable);

    //Si en los hijos existen campos a tener en cuenta se sobreescribira este metodo
    procedure validarCampos(DataTable: TDADataTable); virtual;

    //Si sobreescribimos este método podremos tener en cuenta otras columnas para el calculo del importe total de un concepto
    function CalcularImporteTotalConcepto(DataTable: TDADataTable): Double; virtual;
    procedure TratamientoDetalleConcepto(DataTable: TDADataTable); virtual;
    procedure CalculoDetalleConcepto(DataTable: TDADataTable; var ImporteAcumulado : Double; var ImporteTotal : Double); virtual;
    procedure TratamientoDetalleSalto(DataTable: TDADataTable); virtual;
    procedure CalculoDetalleSalto(DataTable: TDADataTable; var ImporteAcumulado : Double; var ImporteTotal : Double); virtual;
    procedure TratamientoDetalleTitulo(DataTable: TDADataTable); virtual;
    procedure CalculoDetalleTitulo(DataTable: TDADataTable; var ImporteAcumulado : Double; var ImporteTotal : Double); virtual;
    procedure TratamientoDetalleSubtotal(DataTable: TDADataTable); virtual;
    procedure CalculoDetalleSubtotal(DataTable: TDADataTable; var ImporteAcumulado : Double; var ImporteTotal : Double); virtual;
    //Si sobreescribimos este metodo es para continuar el CalcularTotales segun los tipos de concepto de los hijos
    function CalcularTotalesHijos(Modificar: boolean; DataTable: TDADataTable; var ImporteAcumulado : Double; var ImporteTotal : Double): Double; virtual;

  public
    constructor Create; virtual;
    destructor Destroy; override;

    function getTipo(ADataTable: IDAStronglyTypedDataTable; pPosicion: Integer): String;
    procedure Clear(ADataTable: IDAStronglyTypedDataTable);
    procedure Add(ADataTable: IDAStronglyTypedDataTable; TipoConcepto: Variant); virtual;
    procedure Delete(ADataTable: IDAStronglyTypedDataTable; Posicion: TIntegerArray); virtual;
    procedure Move(ADataTable: IDAStronglyTypedDataTable; Posicion: TIntegerArray; Posiciones: Integer); virtual;

//    procedure Copy(SMExport: TSMExportToClipboard);
//    procedure Paste;

    procedure ActualizarTotales(ADataTable: IDAStronglyTypedDataTable);
    function DarTotalImporteTotal(ADataTable: IDAStronglyTypedDataTable): Double;
    function DarListaTiposDetalle: TStringList; virtual;
  end;


implementation

{ TControllerDetallesBase }

uses cxControls, SysUtils, DB, uDAInterfaces;

procedure TControllerDetallesBase.ActualizarTotales(ADataTable: IDAStronglyTypedDataTable);
begin
  BeginUpdate(ADataTable);
  try
    CalcularTotales(True, ADataTable.DataTable);
  finally
    EndUpdate(ADataTable);
  end;
end;

procedure TControllerDetallesBase.Add(ADataTable: IDAStronglyTypedDataTable; TipoConcepto: Variant);
var
  AuxNumOrden : Integer;

begin
  BeginUpdate(ADataTable);
  try
    with ADataTable do
    begin
      AuxNumOrden := desplazarNPosiciones(DataTable, DataTable.FieldByName(CAMPO_POSICION).AsVariant, 1);

      DataTable.Insert;
      DataTable.FieldByName(CAMPO_POSICION).AsInteger := AuxNumOrden;
      DataTable.FieldByName(CAMPO_TIPO).AsVariant := TipoConcepto;
      DataTable.post;
    end;
  finally
    EndUpdate(ADataTable);
  end;
end;

procedure TControllerDetallesBase.BeginUpdate(ADataTable: IDAStronglyTypedDataTable);
begin
  ShowHourglassCursor;
  Inc(fUpdateCount);
  ADataTable.DataTable.DisableControls;
end;

function TControllerDetallesBase.CalcularImporteTotalConcepto(DataTable: TDADataTable): Double;
begin
  with DataTable do
    Result := FieldByName(CAMPO_CANTIDAD).asInteger * FieldByName(CAMPO_IMPORTE_UNIDAD).AsFloat;
end;

function TControllerDetallesBase.CalcularTotales(Modificar: boolean; DataTable: TDADataTable): Double;
{
funcion que recalcula todos los detalles de la tabla pasada por parametro y devuelve
la cantidad total de los mismos
}
var
  AuxPosicionIni : Integer;
  AuxPosicion : Integer;
  AuxImporteAcumulado : Double;
  AuxImporteTotal : Double;

begin
    if (DataTable.State in dsEditModes) then
        DataTable.Post;

    validarCampos(DataTable);

    DataTable.DisableControls;
    try
      AuxPosicionIni := DataTable.FieldByName(CAMPO_POSICION).AsInteger;
      AuxPosicion := 0;
      AuxImporteAcumulado := 0;
      AuxImporteTotal := 0;

      DataTable.First;
      while DataTable.Locate(CAMPO_POSICION, IntToStr(AuxPosicion), []) do
      begin
        //SALTOS DE LINEA
        if (DataTable.FieldByName(CAMPO_TIPO).AsString = TIPO_DETALLE_SALTO) then
        begin
           if Modificar then
              TratamientoDetalleSalto(DataTable); //Se podrá sobreescribir para que se tengan en cuenta nuevos campos en hijos
           CalculoDetalleSalto(DataTable, AuxImporteAcumulado, AuxImporteTotal); //Se podrá sobreescribir para posibles nuevos calculos de los hijos
        end
        //TITULOS
        else if (DataTable.FieldByName(CAMPO_TIPO).AsString = TIPO_DETALLE_TITULO) then
        begin
           if Modificar then
              TratamientoDetalleTitulo(DataTable); //Se podrá sobreescribir para que se tengan en cuenta nuevos campos en hijos
           CalculoDetalleTitulo(DataTable, AuxImporteAcumulado, AuxImporteTotal); //Se podrá sobreescribir para posibles nuevos calculos de los hijos
        end
        //SUBTITULOS
        else if (DataTable.FieldByName(CAMPO_TIPO).AsString = TIPO_DETALLE_SUBTOTAL) then
        begin
           if Modificar then
              TratamientoDetalleSubtotal(DataTable); //Se podrá sobreescribir para que se tengan en cuenta nuevos campos
           CalculoDetalleSubtotal(DataTable, AuxImporteAcumulado, AuxImporteTotal); //Se podrá sobreescribir para posibles nuevos calculos de los hijos
        end
        //CONCEPTOS
        else if (DataTable.FieldByName(CAMPO_TIPO).AsString = TIPO_DETALLE_CONCEPTO) then
        begin
           if Modificar then
              TratamientoDetalleConcepto(DataTable); //Se podrá sobreescribir para que se tengan en cuenta nuevos campos
           CalculoDetalleConcepto(DataTable, AuxImporteAcumulado, AuxImporteTotal); //Se podrá sobreescribir para posibles nuevos calculos de los hijos
        end
        //HIJOS
        else CalcularTotalesHijos(Modificar, DataTable, AuxImporteAcumulado, AuxImporteTotal);

        Inc(AuxPosicion);
        DataTable.First;
      end;

    finally
      //Dejamos el puntero en la misma posición que la que partió
      DataTable.Locate(CAMPO_POSICION, IntToStr(AuxPosicionIni), []);
      DataTable.EnableControls;
    end;

    Result := AuxImporteTotal;
end;

function TControllerDetallesBase.CalcularTotalesHijos(Modificar: boolean; DataTable: TDADataTable; var ImporteAcumulado : Double; var ImporteTotal : Double): Double;
begin
//
end;

procedure TControllerDetallesBase.CalculoDetalleConcepto(DataTable: TDADataTable; var ImporteAcumulado, ImporteTotal: Double);
begin
  with DataTable do
  begin
    ImporteAcumulado := ImporteAcumulado + FieldByName(CAMPO_IMPORTE_TOTAL).AsFloat;
    ImporteTotal := ImporteTotal + FieldByName(CAMPO_IMPORTE_TOTAL).AsFloat;
  end;
end;

procedure TControllerDetallesBase.CalculoDetalleSalto(DataTable: TDADataTable; var ImporteAcumulado, ImporteTotal: Double);
begin
  with DataTable do
  begin
    if not Editing then Edit;
    FieldByName(CAMPO_CANTIDAD).AsVariant := Null;
    FieldByName(CAMPO_IMPORTE_UNIDAD).AsVariant := Null;
    FieldByName(CAMPO_IMPORTE_TOTAL).AsVariant := Null;
    Post;
  end;
end;

procedure TControllerDetallesBase.CalculoDetalleSubtotal(DataTable: TDADataTable; var ImporteAcumulado, ImporteTotal: Double);
begin
  with DataTable do
  begin
    if not Editing then Edit;
    FieldByName(CAMPO_IMPORTE_TOTAL).AsFloat := ImporteAcumulado;
    Post;
  end;
  ImporteAcumulado := 0;
end;

procedure TControllerDetallesBase.CalculoDetalleTitulo(DataTable: TDADataTable; var ImporteAcumulado, ImporteTotal: Double);
begin
//
end;

procedure TControllerDetallesBase.Clear(ADataTable: IDAStronglyTypedDataTable);
begin
//
end;

constructor TControllerDetallesBase.Create;
begin
  inherited;
  fObservadores := TInterfaceList.Create;
end;

function TControllerDetallesBase.DarListaTiposDetalle: TStringList;
begin
  Result := TStringList.Create;
  Result.Values[TIPO_DETALLE_CONCEPTO] := 'Concepto';
  Result.Values[TIPO_DETALLE_TITULO] := 'Título de capítulo';
  Result.Values[TIPO_DETALLE_SUBTOTAL] := 'Final de capítulo';
  Result.Values[TIPO_DETALLE_SALTO] := 'Salto de página';
end;

function TControllerDetallesBase.darTotalImporteTotal(ADataTable: IDAStronglyTypedDataTable): Double;
begin
  Result := CalcularTotales(False, ADataTable.DataTable);
end;

procedure TControllerDetallesBase.Delete(ADataTable: IDAStronglyTypedDataTable; Posicion: TIntegerArray);
var
  i: integer;
  AField: TDAField;
  DeletePosicion: Integer;
begin
  AField := ADataTable.DataTable.FindField(CAMPO_POSICION);
  if not Assigned(AField) then
     raise Exception.Create('Campo ' + CAMPO_POSICION + ' no encontrado (Delete)');

  BeginUpdate(ADataTable);
  try
    with ADataTable do
    begin
      for i := 0 to High(POSICION) do
      begin
        DataTable.First;
        DeletePosicion := POSICION[i];
        if DataTable.Locate(CAMPO_POSICION, IntToStr(DeletePosicion), []) then
           DataTable.Delete;
      end;
      renumerar(DataTable, DeletePosicion);
    end;
  finally
    EndUpdate(ADataTable);
  end;
end;

function TControllerDetallesBase.DesplazarNPosiciones(DataTable: TDADataTable; NumOrdenIni: Variant; NPosiciones: Variant): Integer;
{
Función que desplaza NPosiciones el numero de orden a partir del elemento con el
número de orden dado. Devuelve el numero de orden del primer elemento del hueco
generado
}
var
  AuxNumOrden: Integer;
  AuxNumPos: Integer;
  AField: TDAField;
begin

  AField := DataTable.FindField(CAMPO_POSICION);
  if not Assigned(AField) then
     raise Exception.Create('Campo ' + CAMPO_POSICION + ' no encontrado (desplazarNPosiciones)');

  if VarIsNull(NPosiciones)
  then AuxNumPos := 1
  else AuxNumPos := NPosiciones;

  if VarIsNull(NumOrdenIni)
  then AuxNumOrden := 0
  else AuxNumOrden := NumOrdenIni + 1; //Añadimos por abajo siempre

  Result := AuxNumOrden;

  with DataTable do
  begin
    First;
    while not EOF do
    begin
       if (FieldByName(CAMPO_POSICION).AsInteger >= AuxNumOrden) then
       begin
         if not Editing then Edit;
         FieldByName(CAMPO_POSICION).AsInteger := FieldByName(CAMPO_POSICION).AsInteger + AuxNumPos;
         Post;
       end;
       Next;
    end;
  end;
end;

destructor TControllerDetallesBase.Destroy;
begin
  FreeAndNil(fObservadores);
  inherited;
end;

procedure TControllerDetallesBase.EndUpdate(ADataTable: IDAStronglyTypedDataTable);
begin
  Dec(fUpdateCount);
  CalcularTotales(True, ADataTable.DataTable);
  ADataTable.DataTable.EnableControls;

  if fUpdateCount = 0 then
     AvisarObservadores(ADataTable);

  HideHourglassCursor;
end;

function TControllerDetallesBase.getTipo(ADataTable: IDAStronglyTypedDataTable; pPosicion: Integer): String;
var
  posIni: integer;
  AField: TDAField;
begin
  AField := ADataTable.DataTable.FindField(CAMPO_POSICION);
  if not Assigned(AField) then
     raise Exception.Create('Campo ' + CAMPO_POSICION + ' no encontrado (getTipo)');

  Result := '';
  BeginUpdate(ADataTable);
  try
    with ADataTable do
    begin
      //Guardamos la posicion en la que estamos
      posIni := DataTable.FieldByName(CAMPO_POSICION).AsInteger;

      DataTable.First;
      if DataTable.Locate(CAMPO_POSICION, IntToStr(pPosicion), []) then
         Result := DataTable.FieldByName(CAMPO_TIPO).AsString;

      //Volvemos a posicionar el puntero donde estaba
      DataTable.First;
      if not DataTable.Locate(CAMPO_POSICION, IntToStr(posIni), []) then
         raise Exception.Create('La posición ' + IntToStr(posIni) + ' no existe (getTipo)');
    end;
  finally
    EndUpdate(ADataTable);
  end;
end;

procedure TControllerDetallesBase.Move(ADataTable: IDAStronglyTypedDataTable; Posicion: TIntegerArray; Posiciones: Integer);
var
  i:Integer;
begin
  BeginUpdate(ADataTable);
  try
    with ADataTable do
    begin
      //Empezamos desde abajo
      if Posiciones > 0 then
         for i:= High(POSICION) downto 0 do
             Mover(DataTable, POSICION[i], Posiciones)
      else
      //Empezamos desde arriba
         for i:= 0 to High(POSICION) do
             Mover(DataTable, POSICION[i], Posiciones);
    end;
  finally
    EndUpdate(ADataTable);
  end;
end;

procedure TControllerDetallesBase.Mover(DataTable: TDADataTable; Posicion: Integer; NumPosiciones: Integer);
{
procedimiento que desplaza el número de posiciones (NumPosiciones) pasados por parametro
a la posicion (Posicion) dada, en caso de ser negativo será hacia arriba y positivo hacia
abajo
}
var
  AuxOrden : Integer;
  AuxID : Integer;
  AField: TDAField;
begin
  AField := DataTable.FindField(CAMPO_POSICION);
  if not Assigned(AField) then
     raise Exception.Create('Campo ' + CAMPO_POSICION + ' no encontrado (mover)');

  AField := DataTable.FindField(CAMPO_ID);
  if not Assigned(AField) then
     raise Exception.Create('Campo ' + CAMPO_ID + ' no encontrado (mover)');

  //Buscamos el elemento con la posicion pasada por parametro
  DataTable.First;
  if not DataTable.Locate(CAMPO_POSICION, IntToStr(Posicion), []) then
     raise Exception.Create('Error, no se ha encontrado la POSICION [' + IntToStr(Posicion) + '] (mover)');

  //Guardamos el id del elemento a cambiar de posicion y calculamos su nueva posicion
  AuxID := DataTable.FieldByName(CAMPO_ID).AsInteger;
  AuxOrden := Posicion + NumPosiciones;

  DataTable.First;
  if DataTable.Locate(CAMPO_POSICION, IntToStr(AuxOrden), []) then
  begin
    if not DataTable.Editing then DataTable.Edit;
    DataTable.FieldByName(CAMPO_POSICION).AsInteger := DataTable.FieldByName(CAMPO_POSICION).AsInteger - NumPosiciones;

    //Se hace dentro por si es el ultimo o el primero
    DataTable.First;
    if not DataTable.Locate(CAMPO_ID, IntToStr(AuxID), []) then
       raise Exception.Create('Error, no se ha encontrado el ID [' + IntToStr(AuxID) + '] (mover)');

    if not DataTable.Editing then DataTable.Edit;
    DataTable.FieldByName(CAMPO_POSICION).AsInteger := AuxOrden;

    DataTable.Post;
  end;

  //Colocamos el puntero en la posición en la que estaba
  DataTable.First;
  DataTable.Locate(CAMPO_ID, IntToStr(AuxID), []);
end;

procedure TControllerDetallesBase.Renumerar(DataTable: TDADataTable; LocalizaPosicion: Integer);
{
procedimiento que renumera todos los conceptos de la tabla dada por parametro
}
var
  i, j : Integer;
  AField: TDAField;
begin
  AField := DataTable.FindField(CAMPO_POSICION);
  if not Assigned(AField) then
     raise Exception.Create('Campo ' + CAMPO_POSICION + ' no encontrado (renumerar)');

  with DataTable do
  begin
    for i:=0 to RecordCount-1 do
    begin
      First;
      if not Locate(CAMPO_POSICION, IntToStr(i), []) then
      begin
        j := i;
        First;
        while not Locate(CAMPO_POSICION, IntToStr(j), []) do
        begin
          Inc(j);
          First;
        end;

        if not Editing then Edit;
        FieldByName(CAMPO_POSICION).AsInteger := i;
        Post;
      end;
    end;

    //Posicionamos el puntero en la posición dada por parametro
    if Locate(CAMPO_POSICION, IntToStr(LocalizaPosicion), []) then
  end;
end;

procedure TControllerDetallesBase.TratamientoDetalleConcepto(DataTable: TDADataTable);
begin
  with DataTable do
  begin
    if not Editing then Edit;
    //Si alguno de los campos de calculo de total es nulo el total tambien será nulo
    if (VarIsNull(FieldByName(CAMPO_CANTIDAD).AsVariant)
    or VarIsNull(FieldByName(CAMPO_IMPORTE_UNIDAD).AsVariant))
    then FieldByName(CAMPO_IMPORTE_TOTAL).AsVariant := Null
    else FieldByName(CAMPO_IMPORTE_TOTAL).AsFloat := CalcularImporteTotalConcepto(DataTable);
    Post;
  end;
end;

procedure TControllerDetallesBase.TratamientoDetalleSalto(DataTable: TDADataTable);
begin
  with DataTable do
  begin
    if not Editing then Edit;
    FieldByName(CAMPO_CONCEPTO).AsString := CTE_DESC_SALTO;
    FieldByName(CAMPO_CANTIDAD).AsVariant := Null;
    FieldByName(CAMPO_IMPORTE_UNIDAD).AsVariant := Null;
    FieldByName(CAMPO_IMPORTE_TOTAL).AsVariant := Null;
    Post;
  end;
end;

procedure TControllerDetallesBase.TratamientoDetalleSubtotal(DataTable: TDADataTable);
begin
  with DataTable do
  begin
    if not Editing then Edit;
    if (FieldByName(CAMPO_CONCEPTO).AsString = CTE_DESC_SALTO) then
       FieldByName(CAMPO_CONCEPTO).AsVariant := Null;
    FieldByName(CAMPO_CANTIDAD).AsVariant := Null;
    FieldByName(CAMPO_IMPORTE_UNIDAD).AsVariant := Null;
    Post;
  end;
end;

procedure TControllerDetallesBase.TratamientoDetalleTitulo(DataTable: TDADataTable);
begin
  with DataTable do
  begin
    if not Editing then Edit;
    if (FieldByName(CAMPO_CONCEPTO).AsString = CTE_DESC_SALTO) then
       FieldByName(CAMPO_CONCEPTO).AsVariant := Null;
    FieldByName(CAMPO_CANTIDAD).AsVariant := Null;
    FieldByName(CAMPO_IMPORTE_UNIDAD).AsVariant := Null;
    FieldByName(CAMPO_IMPORTE_TOTAL).AsVariant := Null;
    Post;
  end;
end;

procedure TControllerDetallesBase.validarCampos(DataTable: TDADataTable);
var
  AField: TDAField;
begin
  //Validamos la existencia de todos los campos necesarios
  AField := DataTable.FindField(CAMPO_POSICION);
  if not Assigned(AField) then
     raise Exception.Create('Campo ' + CAMPO_POSICION + ' no encontrado (validarCampos)');
  AField := DataTable.FindField(CAMPO_TIPO);
  if not Assigned(AField) then
     raise Exception.Create('Campo ' + CAMPO_TIPO + ' no encontrado (validarCampos)');
  AField := DataTable.FindField(CAMPO_CANTIDAD);
  if not Assigned(AField) then
     raise Exception.Create('Campo ' + CAMPO_CANTIDAD + ' no encontrado (validarCampos)');
  AField := DataTable.FindField(CAMPO_IMPORTE_UNIDAD);
  if not Assigned(AField) then
     raise Exception.Create('Campo ' + CAMPO_IMPORTE_UNIDAD + ' no encontrado (validarCampos)');
  AField := DataTable.FindField(CAMPO_IMPORTE_TOTAL);
  if not Assigned(AField) then
     raise Exception.Create('Campo ' + CAMPO_IMPORTE_TOTAL + ' no encontrado (validarCampos)');
end;

end.
