unit uDataTableUtils;

interface

uses
  uDACDSDataTable, uDADataTable, uDAInterfaces, uDADelta, uDAMemDataTable;

const
  ID_NULO = -9000;
  ID_TODOS = -9001;
  ID_PRIMERO = -9002;

type

TModoDuplicarRegistros = (mdrTodos, mdrActual, mdrSeleccionados);

function SetFieldNull(ATarget: TDADataTable; const FieldName: String): Boolean;

procedure ConectarTabla (ATarget: TDADataTable);
procedure DesconectarTabla (ATarget: TDADataTable);

procedure CopyDataTableDA5(ASource : TDADataTable; ATarget: TDADataTable;
  const OnlySelectedRows : Boolean = False);

procedure CloneDataTable(const ASource : TDACDSDataTable;
                         var ATarget : TDACDSDataTable;
                         RemoteUpdate: Boolean = True); overload;

function CloneDataTable(const ASource : TDAMemDataTable;
                         RemoteUpdate: Boolean = True): TDAMemDataTable; overload;


procedure DuplicarRegistro(ASource : TDADataTable; ATarget : TDADataTable;
  const WithPKKey: Boolean = False; const WithFKKey: Boolean = False;
  const Insertar: Boolean = True);

procedure DuplicarRegistros(ASource : TDADataTable; ATarget : TDADataTable;
  AModo : TModoDuplicarRegistros; APermitirRepetidos: Boolean = True;
  const WithDeltas: Boolean = True; const WithPKKey: Boolean = False; const WithFKKey: Boolean = False);

procedure DeleteAllTable(const ADataTable : TDADataTable);

function DeltaValuesAreDifferent(const aDelta : IDADelta): boolean;

procedure EnlazarMaestroDetalle(AMasterDataSource : TDADataSource;
  ADetail : IDAStronglyTypedDataTable);

function DataTableModified (const ADataTable : TDADataTable): Boolean;

procedure SetDataTableReadOnly(ADataTable: TDADataTable; const value: Boolean);


implementation

uses
  Classes, DB, uDAClasses, SysUtils, uDABINAdapter, uROTypes, cxControls,
  Dialogs, Variants, uDBSelectionListUtils, uROClasses, uDADataStreamer, uDABin2DataStreamer;


procedure CopyDataTableDA5(ASource : TDADataTable; ATarget: TDADataTable;
  const OnlySelectedRows : Boolean = False);
var
  DABin: Binary;
  DADataStreamer : TDABin2DataStreamer;
  AFiltered : Boolean;
  AFilter : String;
  AObj : ISeleccionable;
  i : Integer;
begin
  AFilter := '';
  AFiltered := False;

  if OnlySelectedRows then
  begin
    if not Supports(ASource, ISeleccionable, aObj) then
      raise Exception.Create('El origen de datos no soporta la interfaz ISeleccionable (CopyDataTable)');

    { Si la tabla está abierta, la cerramos antes de aplicar los filtros
      porque por cada cambio en el filtro se hacen llamadas internas de TDADataTable. }
    if ASource.Active then
       ASource.Close;

    // Si la tabla origen viene con un filtro, lo guardamos para luego restablecerlo.
    if ASource.Filtered then
    begin
      AFiltered := True;
      AFilter := ASource.Filter;
      ASource.Filtered := False;
    end;

    ASource.Filter := '';

    //Si no hay elemento seleccionados filtramos para que ATarget se quede vacia
 	  if (AObj.SelectedRecords.Count = 0) then
          ASource.Filter := ASource.Filter + '(' + ASource.RecIDField.FieldName + ' = ' + IntToStr(ID_NULO) + ')'

 	  //En caso contrario filtramos por los elementos seleccionados
 	  else
 	    for i := 0 to AObj.SelectedRecords.Count - 1 do
 	    begin
 	      if (i > 0) then
 	         ASource.Filter := ASource.Filter + ' or ';
        ASource.Filter := ASource.Filter + '(' + ASource.RecIDField.FieldName + ' = ' + IntToStr(AObj.SelectedRecords.Items[i]) + ')';
 	    end;
    ASource.Filtered := True;
  end;

  DABin := Binary.Create;
  DADataStreamer := TDABin2DataStreamer.Create(nil);

  ShowHourglassCursor;
  ASource.DisableControls;
  ATarget.DisableControls;
  try
    ATarget.LogicalName := ASource.LogicalName;  // We need to specify new dataset LogicalName
    ATarget.RemoteFetchEnabled := False;  // "Desconectamos" la tabla destino del servidor

    if not ASource.Active then
       ASource.Open;

    ASource.First;

    DADataStreamer.WriteDataset(DABin, ASource, [woRows, woSchema], -1);
    DADataStreamer.ReadDataset(DABin, ATarget, True);
    DADataStreamer.Finalize;

    ATarget.RemoteFetchEnabled := True; // "Conectamos" la tabla del servidor otra vez

    // Dejar el filtro de la tabla origen como estaba
    if OnlySelectedRows then
    begin
      ASource.Filtered := False;
      ASource.Filter := AFilter;
      if AFiltered then
        ASource.Filtered := True;
    end;

  finally
    FreeAndNil(DABin);
    FreeAndNil(DADataStreamer);
    ASource.EnableControls;
    ATarget.EnableControls;
    HideHourglassCursor;
  end;
end;


function SetFieldNull(ATarget: TDADataTable; const FieldName: String): Boolean;
begin
  Result := False;
  if Assigned(ATarget) then
  begin
    try
      ATarget.FieldByName(FieldName).AsVariant := Null;
      Result := True;
    except
      on E: Exception do
         Result := False;
    end;
  end;
end;


function DataTableModified (const ADataTable : TDADataTable): Boolean;
var
  bCambiado : Boolean;
  dtDetails : TList;
  i : integer;
begin
  bCambiado := False;

  if Assigned(ADataTable) and (ADataTable.Active) then
  begin
    bCambiado :=(ADataTable.State = dsEdit) or
                (ADataTable.HasDelta and DeltaValuesAreDifferent(ADataTable.Delta));

    if (not bCambiado) then
    begin
      dtDetails := ADataTable.GetDetailDataTables;
      for i := 0 to dtDetails.Count - 1 do
      begin
        bCambiado := bCambiado or
                    ((TDADataTable(dtDetails.Items[i])).State in dsEditModes) or
                    ((TDADataTable(dtDetails.Items[i])).HasDelta and
                    DeltaValuesAreDifferent((TDADataTable(dtDetails.Items[i])).Delta));
        if bCambiado then
          Break;
      end;
    end;
  end;
  Result := bCambiado;
end;


procedure DeleteAllTable(const ADataTable : TDADataTable);
begin
  ADataTable.ClearRows;
end;


function CloneDataTable(const ASource : TDAMemDataTable;
                        RemoteUpdate: Boolean = True): TDAMemDataTable;
var
  i : Integer;
  ATarget : TDAMemDataTable;
begin
  if not Assigned(ASource) then
    raise Exception.Create ('No se ha asignado la tabla de origen (CloneDataTable)');

  ATarget := TDAMemDataTable.Create(NIL);
  with ATarget do
  begin

    { ATENCIÓN !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
      --------------------------------------------------------------------
      DA5 tiene un fallo en el cual las tablas definidas como detalle no
      tienen asociados los eventos internos before... y after... por lo
      que los campos AutoInc no funcionan (no pone el -1, -2...).
      La forma de arreglarlo es llamar a DisableEventHandlers/EnableEventHandlers
      que realiza la asociación de los eventos.
    }
    ATarget.DisableEventHandlers; // <--- INDISPENSABLE!!!!!!!!!!!!!!!!!!!!


    try
      if not Assigned(ASource.RemoteDataAdapter) then
        raise Exception.Create('No está asignado RemoteDataAdapter (' + ASource.Name + ')');

      LogicalName := ASource.LogicalName;
      CustomAttributes.Assign(ASource.CustomAttributes);
      Fields.AssignFieldCollection(ASource.Fields);   // o también ATarget.LoadSchema;
      Params.AssignParamCollection(ASource.Params);

      RemoteDataAdapter := ASource.RemoteDataAdapter;

      Randomize;
      Name := ASource.Name + '_' + IntToStr(Random(MAXINT));

      BusinessRulesID := ASource.BusinessRulesID;

      with MasterParamsMappings do
        for i := 0 to ASource.MasterParamsMappings.Count - 1 do
          Add(ASource.MasterParamsMappings.Strings[i]);

      with MasterRequestMappings do
        for i := 0 to ASource.MasterRequestMappings.Count - 1 do
          Add(ASource.MasterRequestMappings.Strings[i]);

      MasterMappingMode := ASource.MasterMappingMode;

      MasterFields := ASource.MasterFields;
      MasterOptions := ASource.MasterOptions;

      DetailFields := ASource.DetailFields;
      DetailOptions := ASource.DetailOptions;

      RemoteUpdatesOptions := ASource.RemoteUpdatesOptions;
      StreamingOptions := ASource.StreamingOptions;
      RemoteFetchEnabled := ASource.RemoteFetchEnabled;
    finally
      ATarget.EnableEventHandlers; // <--- INDISPENSABLE!!!!!!!!!!!!!!!!!!!!!!!!!
    end;
  end;

  if not RemoteUpdate then
  begin
    ATarget.LogChanges := False;
    ATarget.RemoteFetchEnabled := False;
  end;

  Result := ATarget;
end;


procedure CloneDataTable(const ASource : TDACDSDataTable;
  var ATarget : TDACDSDataTable; RemoteUpdate: Boolean);
var
  i : Integer;
begin
  with ATarget do
  begin

    if not Assigned(ASource.RemoteDataAdapter) then
      raise Exception.Create('No está asignado RemoteDataAdapter (' + ASource.Name + ')');

    RemoteDataAdapter.Assign(ASource.RemoteDataAdapter);

    BusinessRulesID := ASource.BusinessRulesID;
    Randomize;
    Name := ASource.Name + '_' + IntToStr(Random(MAXINT));
    LogicalName := ASource.LogicalName;

    Params.AssignParamCollection(ASource.Params);

    if Assigned(ASource.LocalSchema) then
      LocalSchema := ASource.LocalSchema
    else begin
      ATarget.Fields.AssignFieldCollection(ASource.Fields);   // o también ATarget.LoadSchema;
    end;

    with MasterParamsMappings do
      for i := 0 to ASource.MasterParamsMappings.Count - 1 do
        Add(ASource.MasterParamsMappings.Strings[i]);

    with MasterRequestMappings do
      for i := 0 to ASource.MasterRequestMappings.Count - 1 do
        Add(ASource.MasterRequestMappings.Strings[i]);

    MasterMappingMode := ASource.MasterMappingMode;

    MasterFields := ASource.MasterFields;
    MasterOptions := ASource.MasterOptions;

    DetailFields := ASource.DetailFields;
    DetailOptions := ASource.DetailOptions;

    RemoteUpdatesOptions := ASource.RemoteUpdatesOptions;
    StreamingOptions := ASource.StreamingOptions;
    RemoteFetchEnabled := ASource.RemoteFetchEnabled;
  end;

  if not RemoteUpdate then
  begin
    ATarget.LogChanges := False;
    ATarget.RemoteFetchEnabled := False;
  end;
end;

function DeltaValuesAreDifferent(const aDelta : IDADelta): boolean;
var
  i, x : integer;
  OldNewAreDifferent: boolean;
begin
  OldNewAreDifferent := FALSE;
  for i := 0 to (aDelta.Count-1) do
  begin
    for x := 0 to (aDelta.LoggedFieldCount-1) do
    begin
      OldNewAreDifferent := (aDelta.Changes[i].OldValues[x] <> aDelta.Changes[i].NewValues[x]);

      if OldNewAreDifferent then
        Break;    // Abandon iteration at the first difference between old and new.
    end;

    if OldNewAreDifferent then
      Break;    // Abandon iteration at the first difference between old and new.
  end;
  result := OldNewAreDifferent;
end;


procedure DuplicarRegistro(ASource : TDADataTable; ATarget : TDADataTable; const WithPKKey: Boolean = False; const WithFKKey: Boolean = False; const Insertar: Boolean = True);
var
  i, j: Integer;
  ATargetField: TDAField;
  ASourceField: TDAField;
  ADetailFields : TStringList;
begin
  if not ASource.Active then
    ASource.Open;

  if not ATarget.Active then
    ATarget.Open;

  ADetailFields := Nil;

  // ¿ATarget es una tabla detalle?
  if Assigned(ATarget.MasterSource) then
  begin
    case ATarget.MasterMappingMode of
      //mmDataRequest: //Sin hacer;
      mmParams, mmWhere: begin
        //Creamos la lista de campos foreing key
        ADetailFields := TStringList.Create;
        ADetailFields.Sorted := True;
        ADetailFields.Delimiter := ',';
        ADetailFields.Duplicates := dupIgnore;
        ADetailFields.Add(ATarget.DetailFields);
      end;
    end;
  end
  else
    ADetailFields := Nil;

  if Insertar then 
    ATarget.Insert
  else
    ATarget.Edit;
    
  { Hay que desactivar los eventos para que dejan de funcionar
    las reglas de negocio y no nos interfieran en la copia
    de valores de los campos. }
  ATarget.DisableEventHandlers;
  try
    for i := 0 to ASource.Fields.Count - 1 do
    begin
      ASourceField := ASource.Fields[i];
      ATargetField := ATarget.FindField(ASourceField.Name);

      if Assigned(ATargetField) then
      begin
        // Los campos AutoInc no se rellenan y tampoco los campos que
        // formen parte de la relación maestro-detalle en el
        // caso de que la tabla destino sea una tabla detalle.
        if not WithPKKey then
        begin
           //Si no es campo clave
           //Si no es autoinc (podria desaparecer pero no lo quitamos por si acaso
           if not (ATargetField.InPrimaryKey) and
           (ATargetField.DataType <> datAutoInc) then
           begin
               //En el caso de no copiar las claves foraneas
               //Si no hay campos foreing key o los hay pero el campo a copiar no es uno de ellos
               if not WithFKKey then
               begin
                 if (not Assigned(ADetailFields) or not ADetailFields.Find(ATargetField.Name, j)) then
                    //Copiamos el campo
                    ATargetField.Value := ASourceField.Value;
               end
               else
                  //Copiamos el campo
                  ATargetField.Value := ASourceField.Value;

           end;
        end
        else
          //En el caso de no copiar las claves foraneas
          //Si no hay campos foreing key o los hay pero el campo a copiar no es uno de ellos
          if not WithFKKey then
          begin
             //Si no hay campos foreing key o los hay pero el campo a copiar no es uno de ellos
             if (not Assigned(ADetailFields) or not ADetailFields.Find(ATargetField.Name, j)) then
                //Copiamos el campo
                ATargetField.Value := ASourceField.Value;
          end
          else
            //Copiamos el campo
            ATargetField.Value := ASourceField.Value;
      end;
    end;
  finally
    ATarget.EnableEventHandlers;
    ATarget.Post;
  end;
end;


procedure DuplicarRegistros(ASource : TDADataTable; ATarget : TDADataTable;
  AModo : TModoDuplicarRegistros; APermitirRepetidos: Boolean = True;
  Const WithDeltas: Boolean = True; Const WithPKKey: Boolean = False; Const WithFKKey: Boolean = False);
begin
  if not ASource.Active then
    ASource.Open;

  if not ATarget.Active then
    ATarget.Open;

  try
    //Para que no se generen deltas de inserción, y luego actue como un update
    if not WithDeltas then
       DesconectarTabla(ATarget);

    if AModo = mdrActual then
      DuplicarRegistro(ASource, ATarget, WithPKKey, WithFKKey) //ATarget.CloneSelectedRecord(ASource, True)
    else begin
      ASource.First;
      while not ASource.EOF do
      begin
        if APermitirRepetidos then
           DuplicarRegistro(ASource, ATarget, WithPKKey, WithFKKey)
        else
        begin
          ATarget.First;
          if not ATarget.Locate(ATarget.RecIDField.FieldName, ASource.GetRowRecIDValue, []) then
             DuplicarRegistro(ASource, ATarget, WithPKKey, WithFKKey);
        end;

        ASource.Next;
      end;
    end;
  finally
    //Para que no se generen deltas de inserción, y luego actue como un update
    if not WithDeltas then
       ConectarTabla(ATarget);
  end;
end;


procedure EnlazarMaestroDetalle(AMasterDataSource : TDADataSource;
  ADetail : IDAStronglyTypedDataTable);
begin
  if Assigned(ADetail) then
  begin
    // ¡¡¡¡¡ MUY IMPORTANTE !!!!!!!!!!!!!!!!!!
    //ADetail.DataTable.DisableEventHandlers;
    try
      ADetail.DataTable.MasterSource := AMasterDataSource;
    finally
    { Hay que activar los eventos porque dejan de funcionar
      las reglas de negocio al establecer la relación
      maestro-detalle. (Fallo de Data Abstract 3) }
      //ADetail.DataTable.EnableEventHandlers;
    end;
  end;
end;

procedure DesconectarTabla (ATarget: TDADataTable);
begin
  ATarget.RemoteFetchEnabled := False;
  ATarget.LogChanges := False;
end;

procedure ConectarTabla (ATarget: TDADataTable);
begin
  ATarget.RemoteFetchEnabled := True;
  ATarget.LogChanges := True;
end;

procedure SetDataTableReadOnly(ADataTable: TDADataTable; const value: Boolean);
var
  dtDetails : TList;
  i : integer;

begin
  if Assigned(ADataTable)
  and (ADataTable.ReadOnly <> Value) then
  begin
    ADataTable.ReadOnly := Value;
    dtDetails := ADataTable.GetDetailDataTables;
    for i := 0 to dtDetails.Count - 1 do
        TDADataTable(dtDetails.Items[i]).ReadOnly := ADataTable.ReadOnly;
  end;
end;

end.
