unit uControllerDetallesBase;

interface

uses Classes, Variants, uDACDSDataTable, uDADataTable, uControllerBase, uCalculosUtils;

const
  TIPO_DETALLE_CONCEPTO = 'Concepto';
  TIPO_DETALLE_TITULO = 'Titulo';
  TIPO_DETALLE_TITULO_OPCIONAL = 'Titulo opcional';
  TIPO_DETALLE_SUBTOTAL = 'Subtotal';
  TIPO_DETALLE_SALTO = 'Salto';
  TIPO_DETALLE_DESCUENTO = 'Descuento';
  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 BeginUpdate(ADataTable: IDAStronglyTypedDataTable);
    procedure EndUpdate(ADataTable: IDAStronglyTypedDataTable);

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

    procedure ActualizarTotales(ADataTable: IDAStronglyTypedDataTable);
    function DarTotalImporteTotal(ADataTable: IDAStronglyTypedDataTable): Double;
    function LocalizarPosicion(ADataTable: IDAStronglyTypedDataTable; const APosicion: Integer): Boolean;
    procedure Renumerar(DataTable: TDADataTable; LocalizaPosicion: Integer);
    function DarListaTiposDetalle: TStringList;
    procedure ValidarDetalles(ADataTable: IDAStronglyTypedDataTable);
    procedure AsignarDescuento(ADataTable: IDAStronglyTypedDataTable; Posicion: TIntegerArray; ADescuento: Variant);
  end;

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


  protected
    function CalcularTotales(Modificar: boolean; DataTable: TDADataTable): Double; virtual;
    procedure Renumerar(DataTable: TDADataTable; LocalizaPosicion: Integer);
    function DesplazarNPosiciones(DataTable: TDADataTable; NumOrdenIni: Variant; NPosiciones: Variant): Integer;
    procedure Mover(DataTable: TDADataTable; Posicion: Integer; NumPosiciones: Integer);

    //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; const Opcional: Boolean); 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 TratamientoDetalleTituloOpcional(DataTable: TDADataTable); virtual;
    procedure CalculoDetalleTituloOpcional(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;
    procedure TratamientoDetalleDescuento(DataTable: TDADataTable); virtual;
    procedure CalculoDetalleDescuento(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; override;
    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 BeginUpdate(ADataTable: IDAStronglyTypedDataTable);
    procedure EndUpdate(ADataTable: IDAStronglyTypedDataTable);

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

    procedure ActualizarTotales(ADataTable: IDAStronglyTypedDataTable);
    function DarTotalImporteTotal(ADataTable: IDAStronglyTypedDataTable): Double;
    function DarListaTiposDetalle: TStringList; virtual;
    function LocalizarPosicion(ADataTable: IDAStronglyTypedDataTable; const APosicion: Integer): Boolean;

    // Comprueba que todos los detalles del documento tengan un tipo_detalle asignado,
    // además de que los capitulos tengan su subtotal de cierre
    procedure ValidarDetalles(ADataTable: IDAStronglyTypedDataTable);
    procedure AsignarDescuento(ADataTable: IDAStronglyTypedDataTable; Posicion: TIntegerArray; ADescuento: Variant); virtual;
  end;


implementation

{ TControllerDetallesBase }

uses
  cxControls, SysUtils, DB, uDAInterfaces, Dialogs;


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.AsignarDescuento(ADataTable: IDAStronglyTypedDataTable; Posicion: TIntegerArray; ADescuento: Variant);
var
  i: integer;
  AField: TDAField;
  AsignarDtoPosicion: Integer;

begin
  if ADescuento >= 0 then
  begin
    AsignarDtoPosicion := 0;
    AField := ADataTable.DataTable.FindField(CAMPO_POSICION);
    if not Assigned(AField) then
       raise Exception.Create('Campo ' + CAMPO_POSICION + ' no encontrado (AsignarDescuento)');

    BeginUpdate(ADataTable);
    try
      with ADataTable do
      begin
        for i := 0 to High(POSICION) do
        begin
          DataTable.First;
          AsignarDtoPosicion := POSICION[i];
          if DataTable.Locate(CAMPO_POSICION, AsignarDtoPosicion, []) then
          begin
             if (DataTable.FieldByName(CAMPO_TIPO).AsString = TIPO_DETALLE_CONCEPTO) then
             begin
               DataTable.Edit;
               DataTable.FieldByName(CAMPO_DESCUENTO).AsVariant := ADescuento;
               DataTable.Post;
             end;
          end;
        end;
        Renumerar(DataTable, AsignarDtoPosicion);
      end;
    finally
      EndUpdate(ADataTable);
    end;
  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).asFloat * 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;
  SemaforoOpcional: Boolean;

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

  ValidarCampos(DataTable);

  DataTable.DisableControls;

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

    DataTable.First;
    while DataTable.Locate(CAMPO_POSICION, 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
         SemaforoOpcional := False;
         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
      //TITULOS OPCIONALES
      else if (DataTable.FieldByName(CAMPO_TIPO).AsString = TIPO_DETALLE_TITULO_OPCIONAL) then
      begin
         SemaforoOpcional := True;
         if Modificar then
            TratamientoDetalleTituloOpcional(DataTable); //Se podrá sobreescribir para que se tengan en cuenta nuevos campos en hijos
         CalculoDetalleTituloOpcional(DataTable, AuxImporteAcumulado, AuxImporteTotal); //Se podrá sobreescribir para posibles nuevos calculos de los hijos
      end
      //SUBTOTAL
      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
         SemaforoOpcional := False;
      end
      //DESCUENTO DE CAPITULO
      else if (DataTable.FieldByName(CAMPO_TIPO).AsString = TIPO_DETALLE_DESCUENTO) then
      begin
         if Modificar then
            TratamientoDetalleDescuento(DataTable); //Se podrá sobreescribir para que se tengan en cuenta nuevos campos
         CalculoDetalleDescuento(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, SemaforoOpcional); //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, AuxPosicionIni, []);
    DataTable.EnableControls;
  end;

  Result := AuxImporteTotal;
end;

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

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

procedure TControllerDetallesBase.CalculoDetalleDescuento(
  DataTable: TDADataTable; var ImporteAcumulado, ImporteTotal: Double);
{var
  ImporteDto : Double;
  Redondeado : Double;}
begin
//Este metodo calcula el descuento por capitulo que no se activa para tecsitel 
{  with DataTable do
  begin
    if not Editing then Edit;
    ImporteDto := (-1)*((ImporteTotal * DataTable.FieldByName(CAMPO_DESCUENTO).AsFloat) / 100);

    ImporteTotal := ImporteTotal + ImporteDto;
    FieldByName(CAMPO_IMPORTE_TOTAL).AsFloat := ImporteTotal;
    Post;
  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.CalculoDetalleTituloOpcional(
  DataTable: TDADataTable; var ImporteAcumulado, ImporteTotal: Double);
begin
//
end;

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

constructor TControllerDetallesBase.Create;
begin
  inherited;
end;

function TControllerDetallesBase.DarListaTiposDetalle: TStringList;
begin
  Result := TStringList.Create;
  Result.Values[TIPO_DETALLE_CONCEPTO] := TIPO_DETALLE_CONCEPTO;
  Result.Values[TIPO_DETALLE_TITULO] := TIPO_DETALLE_TITULO;
  Result.Values[TIPO_DETALLE_TITULO_OPCIONAL] := TIPO_DETALLE_TITULO_OPCIONAL;
  Result.Values[TIPO_DETALLE_SUBTOTAL] := TIPO_DETALLE_SUBTOTAL;
//  Result.Values[TIPO_DETALLE_DESCUENTO] := TIPO_DETALLE_DESCUENTO;
//  Result.Values[TIPO_DETALLE_SALTO] := TIPO_DETALLE_SALTO;
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
  DeletePosicion := 0;
  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, 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
  inherited;
end;

procedure TControllerDetallesBase.EndUpdate(ADataTable: IDAStronglyTypedDataTable);
begin
  Dec(fUpdateCount);

  if fUpdateCount = 0 then
  begin
     CalcularTotales(True, ADataTable.DataTable);
     AvisarObservadores(ADataTable);
  end;

  ADataTable.DataTable.EnableControls;
  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, pPosicion, []) then
         Result := DataTable.FieldByName(CAMPO_TIPO).AsString;

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

function TControllerDetallesBase.LocalizarPosicion(
  ADataTable: IDAStronglyTypedDataTable; const APosicion: Integer): Boolean;
var
  AField: TDAField;
begin
  AField := ADataTable.DataTable.FindField(CAMPO_POSICION);

  if not Assigned(AField) then
     raise Exception.Create('Campo ' + CAMPO_POSICION + ' no encontrado (LocalizarPosicion)');

  with ADataTable.DataTable do
  begin
    DisableControls;
    DisableEventHandlers;
    ShowHourglassCursor;
    try
      First;
      Result := Locate(CAMPO_POSICION, APosicion, []);
    finally
      HideHourglassCursor;
      EnableEventHandlers;
      EnableControls;
    end;
  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, 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, 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, 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, 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, i, []) then
      begin
        j := i;
        First;
        while not Locate(CAMPO_POSICION, 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
    Locate(CAMPO_POSICION, LocalizaPosicion, []);
  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.TratamientoDetalleDescuento(
  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.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.TratamientoDetalleTituloOpcional(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;

procedure TControllerDetallesBase.ValidarDetalles(ADataTable: IDAStronglyTypedDataTable);
var
  ATipoCampo : String;
  bEnCapitulo : Boolean;
  AuxPosicionIni : Integer;
  AuxPosicion : Integer;
begin
  bEnCapitulo := False;
  ATipoCampo := '';

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

  ValidarCampos(ADataTable.DataTable);

  AuxPosicionIni := ADataTable.DataTable.FieldByName(CAMPO_POSICION).AsInteger;
  AuxPosicion := 0;

  BeginUpdate(ADataTable);
  try
    ADataTable.DataTable.First;
    while ADataTable.DataTable.Locate(CAMPO_POSICION, AuxPosicion, []) do
    begin
      ATipoCampo := ADataTable.DataTable.FieldByName(CAMPO_TIPO).AsString;

      //Si la linea de concepto no tiene asignado el tipo de detalle, le asignamos nosotros como concepto
      //para que no falle el calculo del documento
      if (Length(ATipoCampo) = 0) then
      begin
         if not ADataTable.DataTable.Editing then
            ADataTable.DataTable.Edit;
         ADataTable.DataTable.FieldByName(CAMPO_TIPO).AsString := TIPO_DETALLE_CONCEPTO;
         ADataTable.DataTable.Post;
      end;

      if (ATipoCampo = TIPO_DETALLE_SUBTOTAL) and bEnCapitulo then
        bEnCapitulo := False;

      if ((ATipoCampo = TIPO_DETALLE_TITULO) or (ATipoCampo = TIPO_DETALLE_TITULO_OPCIONAL)) then
      begin
        // Borramos cualquier valor que haya en las columnas de cantidad, importe, etc.
        with ADataTable.DataTable do
        begin
          if not ADataTable.DataTable.Editing then
            ADataTable.DataTable.Edit;
          FieldByName(CAMPO_CANTIDAD).AsVariant := Null;
          FieldByName(CAMPO_IMPORTE_UNIDAD).AsVariant := Null;
          FieldByName(CAMPO_IMPORTE_TOTAL).AsVariant := Null;
          FieldByName(CAMPO_DESCUENTO).AsVariant := Null;
          FieldByName(CAMPO_IMPORTE_PORTE).AsVariant := Null;

          if Assigned(FindField('UNIDAD_MEDIDA')) then
            FieldByName('UNIDAD_MEDIDA').AsVariant := Null;

          ADataTable.DataTable.Post;
        end;

        if bEnCapitulo then
        begin
          ADataTable.DataTable.First;
          if ADataTable.DataTable.Locate(CAMPO_POSICION, AuxPosicion-1, []) then
          begin
            Add(ADataTable, TIPO_DETALLE_SUBTOTAL);
            bEnCapitulo := False;
          end;
        end
        else
          bEnCapitulo := True;
      end;

      Inc(AuxPosicion);
      ADataTable.DataTable.First;
    end;

    // ¿Se ha acabado la tabla y queda algún capítulo sin cerrar?
    if bEnCapitulo then
      if ADataTable.DataTable.Locate(CAMPO_POSICION, AuxPosicion-1, []) then
      begin
        Add(ADataTable, TIPO_DETALLE_SUBTOTAL);
        bEnCapitulo := False;
      end;
  finally
    //Dejamos el puntero en la misma posición que la que partió
    ADataTable.DataTable.Locate(CAMPO_POSICION, AuxPosicionIni, []);
    EndUpdate(ADataTable);
  end;
end;

end.
