unit uBizImportesDetalleBase;

interface

uses
  uDAInterfaces, uDADataTable;

const
  TIPODETALLE_CONCEPTO = 'C';
  TIPODETALLE_TITULO = 'T';
  TIPODETALLE_SUBTOTAL = 'S';

  VISIBLE_TRUE = 'S';
  VISIBLE_FALSE = 'N';

  fld_NUMCONCEPTO = 'NUMCONCEPTO';
  fld_POSICION = 'POSICION';
  fld_TIPODETALLE = 'TIPO';
  fld_IMPORTETOTAL = 'IMPORTETOTAL';
  fld_CANTIDAD = 'CANTIDAD';
  fld_IMPORTEUNIDAD = 'IMPORTEUNIDAD';
  fld_VISIBLE = 'VISIBLE';
  fld_DESCRIPCION = 'DESCRIPCION';
  fld_PUNTOS = 'PUNTOS';
  fld_IMPORTEPUNTOS = 'IMPORTEPUNTOS';

type
  { IMPORTANTE **********************************************************
    PARCHE TEMPORAL -> Para evitar que se disparen los eventos de la clase
    de negocio cuando se hace el CancelUpdates de los detalles (se restaura
    la tabla a partir de los deltas y esto provoca que se disparen los eventos y
    se producen muchos fallos }
  IParche = interface
  ['{275F080F-054E-4E5A-BDF2-FE3494790388}']
    procedure ActivarEventos;
    procedure DesactivarEventos;
    procedure Refrescar;
    function PuedoLanzarEvento : Boolean;
  end;


  IBizImportesDetalle = interface(IDAStronglyTypedDataTable)
    ['{16A36AC3-FABD-4809-A238-A806F1D02B95}']
    procedure RecalcularImporte;
    function DarSumaTotalImportes : Currency;
  end;

  IBizPuntosDetalle = interface(IBizImportesDetalle)
    ['{72002878-BD11-4BB8-AB85-73E321A6D595}']
  end;

  IBizVisibleDetalle = interface(IDAStronglyTypedDataTable)
    ['{6BA5B3BF-2E92-4465-A328-60F90F7EA3D2}']
  end;

  TBizCantidadFieldRules = class(TDAFieldRules)
  protected
    procedure OnChange(Sender: TDACustomField); override;
  end;

  TBizImporteUnidadFieldRules = class(TDAFieldRules)
  protected
    procedure OnChange(Sender: TDACustomField); override;
  end;

  TBizTipoDetalleFieldRules = class(TDAFieldRules)
  protected
    procedure OnChange(Sender: TDACustomField); override;
  end;

  TBizPuntosFieldRules = class(TDAFieldRules)
  protected
    procedure OnChange(Sender: TDACustomField); override;
  end;

  TBizImportePuntosFieldRules = class(TDAFieldRules)
  protected
    procedure OnChange(Sender: TDACustomField); override;
  end;

  TBizVisibleFieldRules = class(TDAFieldRules)
  protected
    procedure OnChange(Sender: TDACustomField); override;
  end;

function DarMaximoNumConcepto(aDataTable : TDADataTable): integer;
function DarMaximaPosicion(aDataTable : TDADataTable): integer;
procedure IntercambiarPosiciones(aDataTable : TDADataTable; Pos1, Pos2 : Integer);
procedure ReasignarPosiciones(aDataTable : TDADataTable);
procedure AsignarPosicion(aDataTable : TDADataTable; IsAppend : Boolean);

procedure AsignarNumConcepto(aDataTable : TDADataTable);

procedure RecalcularImporteDetalle(aDataTable : TDADataTable; TieneSubtotales : Boolean; TienePuntos : Boolean);
procedure RecalcularSubtotales(aDataTable : TDADataTable);
function DarTotalDetalles(aDataTable : TDADataTable; TieneSubtotales : Boolean; TienePuntos : Boolean) : Currency;

procedure RellenarImportePuntosEnCapitulo(aDataTable : TDADataTable);

procedure RellenarVisibleEnCapitulo(aDataTable : TDADataTable);


implementation

uses
  SysUtils, DB, uBizImportesCabeceraBase, Controls, Forms, Dialogs, variants;

procedure IntercambiarPosiciones(aDataTable : TDADataTable; Pos1, Pos2 : Integer);
{ Intercambia los valores del campo 'POSICION' de dos filas }
var
  ABookmark : Pointer;
  AField : TDAField;
  AFieldTipo : TDAField;
  ACursor: TCursor;
  bRecalcularSubtotales : Boolean;
begin
  if not Assigned(aDataTable) then
    raise Exception.Create('Tabla no asignada (IntercambiarPosicion)');

  AField := aDataTable.FindField(fld_POSICION);
  if not Assigned(AField) then
    raise Exception.Create('Campo POSICION no encontrado (IntercambiarPosicion)');

  // No importa si el TIPO no está en la lista de columnas
  AFieldTipo := aDataTable.FindField(fld_TIPODETALLE);

  if (aDataTable.State in dsEditModes) then
    aDataTable.Post;

  bRecalcularSubtotales := False;
  ACursor := Screen.Cursor;
  Screen.Cursor := crHourGlass;
  //aDataTable.DisableEventHandlers;  <- No descomentar
  aDataTable.DisableControls;
  ABookmark := aDataTable.GetBookMark;
  try
    aDataTable.First;
    if aDataTable.Locate(fld_POSICION, Pos1, []) then
    begin
      // Si alguna de las dos filas es un subtotal, hay que recalcular todo
      if Assigned(AFieldTipo) and ((AFieldTipo.AsString = TIPODETALLE_SUBTOTAL) or
         (AFieldTipo.AsString = TIPODETALLE_TITULO)) then
        bRecalcularSubtotales := True;

      aDataTable.Edit;
      AField.AsInteger := 0;  // Ponemos el 0 temporalmente
      aDataTable.Post;

      aDataTable.First;
      if aDataTable.Locate(fld_POSICION, Pos2, []) then
      begin
        // Si alguna de las dos filas es un subtotal, hay que recalcular todo
        if Assigned(AFieldTipo) and ((AFieldTipo.AsString = TIPODETALLE_SUBTOTAL) or
           (AFieldTipo.AsString = TIPODETALLE_TITULO)) then
          bRecalcularSubtotales := True;

        aDataTable.Edit;
        AField.AsInteger := Pos1;
        aDataTable.Post;

        aDataTable.First;
        aDataTable.Locate(fld_POSICION, 0, []);
        aDataTable.Edit;
        AField.AsInteger := Pos2;
        aDataTable.Post;
      end
      else
        raise Exception.Create('No se ha encontrado posición 2 (IntercambiarPosicion)');
    end
    else
      raise Exception.Create('No se ha encontrado posición 1 (IntercambiarPosicion)');
  finally
    aDataTable.GotoBookmark(ABookmark);
    aDataTable.EnableControls;
    //aDataTable.EnableEventHandlers;  <- No descomentar
    if bRecalcularSubtotales then
      RecalcularSubtotales(aDataTable);
    Screen.Cursor := ACursor;
  end;
end;


function DarMaximoNumConcepto(aDataTable : TDADataTable): integer;
{ Devuelve el valor máximo del campo 'NUMCONCEPTO' }
var
  ABookmark : Pointer;
  AField : TDAField;
  ACursor: TCursor;
begin
  Result := 0;

  if not Assigned(aDataTable) then
    raise Exception.Create('Tabla no asignada (DarMaximoNumConcepto)');

  if aDataTable.RecordCount < 1 then
    Exit;

  AField := aDataTable.FindField(fld_NUMCONCEPTO);
  if not Assigned(AField) then
    raise Exception.Create('Campo NUMCONCEPTO no encontrado (DarMaximoNumConcepto)');

  ACursor := Screen.Cursor;
  Screen.Cursor := crHourGlass;
  ABookmark := aDataTable.GetBookMark;
  try
    aDataTable.DisableControls;
    aDataTable.DisableEventHandlers;
    aDataTable.First;
    while not aDataTable.Eof do
    begin
      if AField.AsInteger > Result then
        Result := AField.AsInteger;
      aDataTable.Next;
    end;
  finally
    aDataTable.GotoBookmark(ABookmark);
    aDataTable.EnableControls;
    aDataTable.EnableEventHandlers;
    Screen.Cursor := ACursor;
  end;
end;

function DarMaximaPosicion(aDataTable : TDADataTable): integer;
{ Devuelve el valor máximo del campo 'POSICION' }
var
  ABookmark : Pointer;
  AField : TDAField;
  ACursor: TCursor;
begin
  //Sort(['ForeignKeyFieldofMasterDetailRelation','YourSortField'],[sdAscending, NeededOrder ]);
  Result := 0;

  if not Assigned(aDataTable) then
    raise Exception.Create('Tabla no asignada (DarMaximaPosicion)');

  if aDataTable.RecordCount < 1 then
    Exit;

  AField := aDataTable.FindField(fld_POSICION);
  if not Assigned(AField) then
    raise Exception.Create('Campo POSICION no encontrado (DarMaximaPosicion)');

  ACursor := Screen.Cursor;
  Screen.Cursor := crHourGlass;
  ABookmark := aDataTable.GetBookMark;
  try
    aDataTable.DisableControls;
    aDataTable.DisableEventHandlers;
    aDataTable.First;
    while not aDataTable.Eof do
    begin
      if AField.AsInteger > Result then
        Result := AField.AsInteger;
      aDataTable.Next;
    end;
  finally
    aDataTable.GotoBookmark(ABookmark);
    aDataTable.EnableControls;
    aDataTable.EnableEventHandlers;
    Screen.Cursor := ACursor;
  end;
end;

procedure ReasignarPosiciones(aDataTable : TDADataTable);
{ Recalcula el valor del campo 'POSICION' de todas las filas
  para eliminar huecos }
var
  AField : TDAField;
  ABookmark : Pointer;
  Contador : Integer;
  NumCon : Integer;
  TotalConceptos : Integer;
  DetallesEditados : Integer;
  ACursor: TCursor;
begin
  if not Assigned(aDataTable) then
    raise Exception.Create('Tabla no asignada (ReasignarNumConceptos)');

  if aDataTable.RecordCount < 1 then
    Exit;

  AField := aDataTable.FindField(fld_POSICION);
  if not Assigned(AField) then
    raise Exception.Create('Campo POSICION no encontrado (ReasignarNumConceptos)');

  Contador := 1;
  NumCon := 1;
  TotalConceptos := aDataTable.RecordCount;
  DetallesEditados := 0;

  ACursor := Screen.Cursor;
  Screen.Cursor := crHourGlass;
  ABookmark := aDataTable.GetBookMark;
  aDataTable.DisableControls;
  //aDataTable.DisableEventHandlers;     <-- No descomentar
  try
    aDataTable.First;
    while DetallesEditados < TotalConceptos do
    begin
      if aDataTable.Locate(fld_POSICION, Contador, []) then
      begin
        aDataTable.Edit;
        AField.Value := NumCon;
        Inc(NumCon);
        aDataTable.Post;
        Inc(DetallesEditados);
        aDataTable.First;
      end;
      Inc(Contador);
    end;
  finally
    aDataTable.GotoBookmark(ABookmark);
    //aDataTable.EnableEventHandlers;    <-- No descomentar
    aDataTable.EnableControls;
    Screen.Cursor := ACursor;
  end;
end;

procedure AsignarPosicion(aDataTable : TDADataTable; IsAppend : Boolean);
{ Calcula el valor del campo 'POSICION' para una fila nueva. Incrementa en 1
  a las filas que estén por debajo de esa fila nueva }
var
  AField : TDAField;
  ABookmark : Pointer;
  NumPosicion : Integer;
  MaxPosicion : Integer;
  ACursor: TCursor;
begin
  if not Assigned(aDataTable) then
    raise Exception.Create('Tabla no asignada (AsignarPosicion)');

  if aDataTable.RecordCount < 1 then
    Exit;

  AField := aDataTable.FindField(fld_POSICION);
  if not Assigned(AField) then
    raise Exception.Create('Campo POSICION no encontrado (AsignarPosicion)');

  if not IsAppend then
  begin
    if not aDataTable.IsEmpty then
    begin
      ACursor := Screen.Cursor;
      Screen.Cursor := crHourGlass;
      aDataTable.DisableControls;
      // Mover las filas que están por debajo de la actual
      MaxPosicion := DarMaximaPosicion(aDataTable);
      NumPosicion := (AField.AsInteger * (-1));
      ABookmark := aDataTable.GetBookMark;
      try
        aDataTable.First;
        while aDataTable.Locate(fld_POSICION, MaxPosicion, []) do
        begin
          aDataTable.Edit;
          AField.Value := MaxPosicion + 1;
          aDataTable.Post;
          aDataTable.First;
          if MaxPosicion = NumPosicion then
            Break
          else
            Dec(MaxPosicion);
        end
      finally
        aDataTable.GotoBookmark(ABookmark);
        aDataTable.EnableControls;
        Screen.Cursor := ACursor;        
      end;
    end;
    NumPosicion := (AField.AsInteger * (-1));
  end
  else begin
    if aDataTable.RecordCount > 1 then
      NumPosicion := DarMaximaPosicion(aDataTable) + 1
    else
      // Sólo hay una fila en toda la tabla
      NumPosicion := (AField.AsInteger * (-1));
  end;

  // Asignar posición a la nueva fila
  aDataTable.Edit;
  AField.Value := NumPosicion;
  aDataTable.Post;
end;

procedure AsignarNumConcepto(aDataTable : TDADataTable);
{ Asigna el valor del campo 'NUMCONCEPTO' para una fila nueva }
var
  AField : TDAField;
  MaxConcepto : Integer;
  ACursor: TCursor;
  EnEdicion : Boolean;
begin
  if not Assigned(aDataTable) then
    raise Exception.Create('Tabla no asignada (AsignarNumConcepto)');

  if aDataTable.RecordCount < 1 then
    Exit;

  AField := aDataTable.FindField(fld_NUMCONCEPTO);
  if not Assigned(AField) then
    raise Exception.Create('Campo NUMCONCEPTO no encontrado (AsignarNumConcepto)');

  // ¿Viene en modo edición?
  EnEdicion := (aDataTable.State in dsEditModes);

  MaxConcepto := DarMaximoNumConcepto(aDataTable);

  if not aDataTable.IsEmpty then
  begin
    ACursor := Screen.Cursor;
    Screen.Cursor := crHourGlass;
    aDataTable.DisableControls;
    try
      if not EnEdicion then
        aDataTable.Edit;
      AField.AsInteger := MaxConcepto + 1;
      aDataTable.Post;

      if EnEdicion then
        aDataTable.Edit;
    finally
      Screen.Cursor := ACursor;
      aDataTable.EnableControls;
    end;
  end;
end;


procedure RecalcularImporteDetalle(aDataTable : TDADataTable; TieneSubtotales : Boolean; TienePuntos : Boolean);
var
  TipoField : TDAField;
  TotalField : TDAField;
  CantidadField : TDAField;
  ImporteUnidadField : TDAField;
  PuntosField : TDAField;
  ImportePuntosField : TDAField;
  EnEdicion : Boolean;
begin
  if not Assigned(aDataTable) then
    raise Exception.Create('Tabla no asignada (RecalcularImporteDetalle)');

  if aDataTable.RecordCount < 1 then
    Exit;

  // ¿Viene en modo edición?
  EnEdicion := (aDataTable.State in dsEditModes);

  TotalField := aDataTable.FindField(fld_IMPORTETOTAL);
  if not Assigned(TotalField) then
    raise Exception.Create('Campo IMPORTETOTAL no encontrado (RecalcularImporteDetalle)');

  CantidadField := aDataTable.FindField(fld_CANTIDAD);
  if not Assigned(CantidadField) then
    raise Exception.Create('Campo CANTIDAD no encontrado (RecalcularImporteDetalle)');

  ImporteUnidadField := aDataTable.FindField(fld_IMPORTEUNIDAD);
  if not Assigned(ImporteUnidadField) then
    raise Exception.Create('Campo IMPORTEUNIDAD no encontrado (RecalcularImporteDetalle)');

  if TienePuntos then
  begin
    PuntosField := aDataTable.FindField(fld_PUNTOS);
    if not Assigned(PuntosField) then
      raise Exception.Create('Campo PUNTOS no encontrado (RecalcularImporteDetalle)');

    ImportePuntosField := aDataTable.FindField(fld_IMPORTEPUNTOS);
    if not Assigned(ImportePuntosField) then
      raise Exception.Create('Campo IMPORTEPUNTOS no encontrado (RecalcularImporteDetalle)');
  end;

  if TieneSubtotales then
  begin
    TipoField := aDataTable.FindField(fld_TIPODETALLE);
    if not Assigned(TipoField) then
      raise Exception.Create('Campo TIPO no encontrado (RecalcularImporteDetalle)');
  end;

  //
  if not EnEdicion then
    aDataTable.Edit;

  aDataTable.DisableEventHandlers;
  // Calcular el importe de la línea
  try
    if (TieneSubtotales) and ((TipoField.AsString = TIPODETALLE_SUBTOTAL) or
       (TipoField.AsString = TIPODETALLE_TITULO)) then
    begin
      CantidadField.Value := NULL;
      ImporteUnidadField.Value := NULL;
      TotalField.Value := NULL;

      if (TienePuntos) then
      begin
        PuntosField.Value := NULL;
        if (TipoField.AsString = TIPODETALLE_SUBTOTAL) then
          ImportePuntosField.Value := NULL;
      end;
    end
    else begin
      if TienePuntos and
        ((PuntosField.Value <> NULL) and (ImportePuntosField.Value <> NULL)) then
        ImporteUnidadField.Value := PuntosField.AsInteger * ImportePuntosField.AsCurrency;

      TotalField.Value := CantidadField.AsCurrency * ImporteUnidadField.AsCurrency;
    end;
  finally
    aDataTable.EnableEventHandlers;
    if not EnEdicion then
      aDataTable.Post;
  end;
end;

procedure RecalcularSubtotales(aDataTable : TDADataTable);
{ Recalcula el valor de todos los subtotales de la tabla }
var
  ABookmark : Pointer;
  APosicion : Integer;
  MaxPos : Integer;
  Total : Currency;
  Subtotal : Currency;
  TipoField : TDAField;
  TotalField : TDAField;
  ACursor: TCursor;
begin
  if not Assigned(aDataTable) then
    raise Exception.Create('Tabla no asignada (RecalcularTodosSubtotales)');

  if aDataTable.RecordCount < 1 then
    Exit;

  TipoField := aDataTable.FindField(fld_TIPODETALLE);
  if not Assigned(TipoField) then
    raise Exception.Create('Campo TIPO no encontrado (RecalcularTodosSubtotales)');

  TotalField := aDataTable.FindField(fld_IMPORTETOTAL);
  if not Assigned(TotalField) then
    raise Exception.Create('Campo IMPORTETOTAL no encontrado (RecalcularTodosSubtotales)');

  ABookmark := aDataTable.GetBookMark;
  ACursor := Screen.Cursor;
  Screen.Cursor := crHourGlass;
  aDataTable.DisableControls;
//  aDataTable.DisableEventHandlers;  <- No descomentar

  if (aDataTable.State in dsEditModes) then
    aDataTable.Post;

  APosicion := 1;
  Total := 0;
  Subtotal := 0;
  try
    MaxPos := DarMaximaPosicion(aDataTable);
    while (APosicion <= MaxPos) do
    begin
      aDataTable.First;
      if aDataTable.Locate(fld_POSICION, APosicion, []) then
      begin
        if (TipoField.AsString = TIPODETALLE_TITULO) then
           Subtotal := 0
        else if (TipoField.AsString = TIPODETALLE_SUBTOTAL) then
        begin
          try
            aDataTable.Edit;
            TotalField.Value := SubTotal;
            aDataTable.Post;
            Total := Total + SubTotal;
            Subtotal := 0;
          except
            on E : Exception do
              ShowMessage(E.Message);
          end;
        end
        else begin
          Subtotal := Subtotal + TotalField.AsCurrency;
          if (APosicion = MaxPos) then
            Total := Total + SubTotal;
        end;
        Inc(APosicion);
      end
      else
        Break;
    end;
  finally
    aDataTable.GotoBookmark(ABookmark);
    aDataTable.EnableControls;
//    aDataTable.EnableEventHandlers;   <- No descomentar
    Screen.Cursor := ACursor;
  end;
end;

function DarTotalDetalles(aDataTable : TDADataTable; TieneSubtotales : Boolean; TienePuntos : Boolean) : Currency;
{ Calcula el total de todos los detalles a partir del importe total de cada uno }
var
  EnEdicion : Boolean;
  ABookmark : Pointer;
  APosicion : Integer;
  MaxPos : Integer;
  Total : Currency;
  Subtotal : Currency;
  TipoField : TDAField;
  TotalField : TDAField;
  ACursor: TCursor;
begin
  Result := 0;

  if not Assigned(aDataTable) then
    raise Exception.Create('Tabla no asignada (DarTotalDetalles)');

  if aDataTable.RecordCount < 1 then
    Exit;

  if TieneSubtotales then
  begin
    TipoField := aDataTable.FindField(fld_TIPODETALLE);
    if not Assigned(TipoField) then
      raise Exception.Create('Campo TIPO no encontrado (DarTotalDetalles)');
  end;

  TotalField := aDataTable.FindField(fld_IMPORTETOTAL);
  if not Assigned(TotalField) then
    raise Exception.Create('Campo IMPORTETOTAL no encontrado (DarTotalDetalles)');

  ABookmark := aDataTable.GetBookMark;
  ACursor := Screen.Cursor;
  Screen.Cursor := crHourGlass;
  aDataTable.DisableControls;
//  aDataTable.DisableEventHandlers;  <- No descomentar

  // ¿Viene en modo edición?
  EnEdicion := (aDataTable.State in dsEditModes);

  if EnEdicion then
    aDataTable.Post;

  APosicion := 1;
  Total := 0;
  Subtotal := 0;
  try
    MaxPos := DarMaximaPosicion(aDataTable);
    while (APosicion <= MaxPos) do
    begin
      aDataTable.First;
      if aDataTable.Locate(fld_POSICION, APosicion, []) then
      begin
        if TieneSubtotales and (TipoField.AsString = TIPODETALLE_SUBTOTAL) then
        begin
          Total := Total + SubTotal;
          Subtotal := 0;
        end
        else begin
          Subtotal := Subtotal + TotalField.AsCurrency;
          if (APosicion = MaxPos) then
            Total := Total + SubTotal;
        end;
        Inc(APosicion);
      end
      else
        Break;
    end;
  finally
    Result := Total;
    aDataTable.GotoBookmark(ABookmark);
    aDataTable.EnableControls;
//    aDataTable.EnableEventHandlers;   <- No descomentar
    if EnEdicion then
      aDataTable.Edit;
    Screen.Cursor := ACursor;
  end;
end;


procedure RellenarImportePuntosEnCapitulo(aDataTable : TDADataTable);
{ Rellena el importe de puntos a todos los conceptos de un capítulo. El cursor
  está puesto en la fila que es el título. }
var
  ABookmark : Pointer;
  APosicion : Integer;
  MaxPos : Integer;
  ImportePuntos : Currency;
  TipoField : TDAField;
  PosicionField : TDAField;
  ImportePuntosField : TDAField;
  ACursor: TCursor;
  EnEdicion : Boolean;
  ADetalle : IBizImportesDetalle;
begin
  if not Assigned(aDataTable) then
    raise Exception.Create('Tabla no asignada (RellenarImportePuntosEnCapitulo)');

  if aDataTable.RecordCount < 1 then
    Exit;

  if not Supports(aDataTable, IBizImportesDetalle, ADetalle) then
    raise Exception.Create('La tabla no soporta la interfaz requerida (RellenarImportePuntosEnCapitulo)');

  PosicionField := aDataTable.FindField(fld_POSICION);
  if not Assigned(PosicionField) then
    raise Exception.Create('Campo POSICION no encontrado (RellenarImportePuntosEnCapitulo)');

  TipoField := aDataTable.FindField(fld_TIPODETALLE);
  if not Assigned(TipoField) then
    raise Exception.Create('Campo TIPO no encontrado (RellenarImportePuntosEnCapitulo)');

  ImportePuntosField := aDataTable.FindField(fld_IMPORTEPUNTOS);
  if not Assigned(ImportePuntosField) then
    raise Exception.Create('Campo IMPORTEPUNTOS no encontrado (RellenarImportePuntosEnCapitulo)');

  ABookmark := aDataTable.GetBookMark;
  ACursor := Screen.Cursor;
  Screen.Cursor := crHourGlass;
  aDataTable.DisableControls;

  EnEdicion := (aDataTable.State in dsEditModes);

  if EnEdicion then
    aDataTable.Post;

  APosicion := PosicionField.Value + 1; // La posición siguiente a la fila de TITULO
  ImportePuntos := aDataTable.FindField(fld_IMPORTEPUNTOS).AsCurrency;  // Importe de puntos de la fila de TITULO

  try
    MaxPos := DarMaximaPosicion(aDataTable);
    while (APosicion <= MaxPos) do
    begin
      if aDataTable.Locate(fld_POSICION, APosicion, []) then
      begin
        if (TipoField.AsString = TIPODETALLE_CONCEPTO) then
        begin
          aDataTable.Edit;
          aDataTable.DisableEventHandlers;  // Para que no salten otros eventos
          try
            ImportePuntosField.AsCurrency := ImportePuntos;
            ADetalle.RecalcularImporte;
          finally
            aDataTable.EnableEventHandlers;
          end;
          aDataTable.Post;
          Inc(APosicion);
        end
        else
          break;    // Es una fila de SUBTOTAL o de TITULO
      end
      else
        raise Exception.Create('Hay un hueco en la numeración de posiciones');
    end;
  finally
    aDataTable.GotoBookmark(ABookmark);
    aDataTable.EnableControls;
    Screen.Cursor := ACursor;
    if EnEdicion then
      aDataTable.Edit;
  end;
end;


procedure RellenarVisibleEnCapitulo(aDataTable : TDADataTable);
{ Rellena VISIBLE de todos los conceptos de un capítulo. El cursor
  está puesto en la fila que es el título. }
var
  ABookmark : Pointer;
  APosicion : Integer;
  MaxPos : Integer;
  TipoField : TDAField;
  PosicionField : TDAField;
  VisibleField : TDAField;
  ACursor: TCursor;
  EnEdicion : Boolean;
  EsVisible : String;
begin
  if not Assigned(aDataTable) then
    raise Exception.Create('Tabla no asignada (RellenarVisibleEnCapitulo)');

  if aDataTable.RecordCount < 1 then
    Exit;

  PosicionField := aDataTable.FindField(fld_POSICION);
  if not Assigned(PosicionField) then
    raise Exception.Create('Campo POSICION no encontrado (RellenarVisibleEnCapitulo)');

  TipoField := aDataTable.FindField(fld_TIPODETALLE);
  if not Assigned(TipoField) then
    raise Exception.Create('Campo TIPO no encontrado (RellenarVisibleEnCapitulo)');

  VisibleField := aDataTable.FindField(fld_VISIBLE);
  if not Assigned(VisibleField) then
    raise Exception.Create('Campo VISIBLE no encontrado (RellenarVisibleEnCapitulo)');

  ABookmark := aDataTable.GetBookMark;
  ACursor := Screen.Cursor;
  Screen.Cursor := crHourGlass;
  aDataTable.DisableControls;

  EnEdicion := (aDataTable.State in dsEditModes);

  if EnEdicion then
    aDataTable.Post;

  APosicion := PosicionField.Value + 1; // La posición siguiente a la fila de TITULO
  EsVisible := VisibleField.AsString;  // Valor de VISIBLE de la fila de TITULO

  try
    MaxPos := DarMaximaPosicion(aDataTable);
    while (APosicion <= MaxPos) do
    begin
      if aDataTable.Locate(fld_POSICION, APosicion, []) then
      begin
        if (TipoField.AsString = TIPODETALLE_CONCEPTO) then
        begin
          aDataTable.Edit;
          aDataTable.DisableEventHandlers;  // Para que no salten otros eventos
          try
            VisibleField.AsString := EsVisible;
          finally
            aDataTable.EnableEventHandlers;
          end;
          aDataTable.Post;
          Inc(APosicion);
        end
        else
          break;    // Es una fila de SUBTOTAL o de TITULO
      end
      else
        raise Exception.Create('Hay un hueco en la numeración de posiciones');
    end;
  finally
    aDataTable.GotoBookmark(ABookmark);
    aDataTable.EnableControls;
    Screen.Cursor := ACursor;
    if EnEdicion then
      aDataTable.Edit;
  end;
end;

{ TBizCantidadFieldRules }

procedure TBizCantidadFieldRules.OnChange(Sender: TDACustomField);
var
  aDetalle : IBizImportesDetalle;
  aCabecera : IBizImportesCabecera;
  aParche : IParche;
begin
  inherited;

  { PARCHE ********************************** }
  if Supports(DataTable, IParche, aParche) and
    not (aParche.PuedoLanzarEvento) then
    Exit;

  if Supports(DataTable, IBizImportesDetalle, aDetalle) then
  begin
    if Sender.Value = 0 then
       Sender.Value := Null;

    aDetalle.RecalcularImporte;
    RecalcularSubtotales(aDetalle.DataTable);
    if Assigned(aDetalle.DataTable.MasterSource) and
      Supports(aDetalle.DataTable.MasterSource.DataTable, IBizImportesCabecera, ACabecera) then
      ACabecera.RecalcularImporte;
  end;
end;

procedure TBizImporteUnidadFieldRules.OnChange(Sender: TDACustomField);
var
  aDetalle : IBizImportesDetalle;
  aCabecera : IBizImportesCabecera;
  aParche : IParche;  
begin
  inherited;

  { PARCHE ********************************** }
  if Supports(DataTable, IParche, aParche) and
    not (aParche.PuedoLanzarEvento) then
    Exit;

  if Supports(DataTable, IBizImportesDetalle, aDetalle) then
  begin
    if Sender.Value = 0 then
       Sender.Value := Null;

    aDetalle.RecalcularImporte;
    RecalcularSubtotales(aDetalle.DataTable);
    if Assigned(aDetalle.DataTable.MasterSource) and
      Supports(aDetalle.DataTable.MasterSource.DataTable, IBizImportesCabecera, ACabecera) then
      ACabecera.RecalcularImporte;
  end;
end;

procedure TBizTipoDetalleFieldRules.OnChange(Sender: TDACustomField);
var
  aDetalle : IBizImportesDetalle;
  aCabecera : IBizImportesCabecera;
  aParche : IParche;  
begin
  inherited;

  { PARCHE ********************************** }
  if Supports(DataTable, IParche, aParche) and
    not (aParche.PuedoLanzarEvento) then
    Exit;

  if (Sender.AsString = TIPODETALLE_SUBTOTAL) or
     (Sender.AsString = TIPODETALLE_TITULO) then
    if Supports(DataTable, IBizImportesDetalle, aDetalle) then
    begin
      aDetalle.RecalcularImporte;
      RecalcularSubtotales(aDetalle.DataTable);
      if Assigned(aDetalle.DataTable.MasterSource) and
        Supports(aDetalle.DataTable.MasterSource.DataTable, IBizImportesCabecera, ACabecera) then
        ACabecera.RecalcularImporte;
    end;
end;

procedure TBizPuntosFieldRules.OnChange(Sender: TDACustomField);
var
  aDetalle : IBizPuntosDetalle;
  aCabecera : IBizImportesCabecera;
  aParche : IParche;
begin
  inherited;

  { PARCHE ********************************** }
  if Supports(DataTable, IParche, aParche) and
    not (aParche.PuedoLanzarEvento) then
    Exit;

  if Supports(DataTable, IBizPuntosDetalle, aDetalle) then
  begin
    if Sender.Value = 0 then
       Sender.Value := Null;

    aDetalle.RecalcularImporte;
    RecalcularSubtotales(aDetalle.DataTable);
    if Assigned(aDetalle.DataTable.MasterSource) and
      Supports(aDetalle.DataTable.MasterSource.DataTable, IBizImportesCabecera, ACabecera) then
      ACabecera.RecalcularImporte;
  end;
end;

procedure TBizImportePuntosFieldRules.OnChange(
  Sender: TDACustomField);
var
  aDetalle : IBizPuntosDetalle;
  aCabecera : IBizImportesCabecera;
  aParche : IParche;  
begin
  inherited;

  { PARCHE ********************************** }
  if Supports(DataTable, IParche, aParche) and
    not (aParche.PuedoLanzarEvento) then
    Exit;

  if Supports(DataTable, IBizPuntosDetalle, aDetalle) then
  begin
    if Sender.Value = 0 then
       Sender.Value := Null;

    aDetalle.RecalcularImporte;
    if Assigned(aDetalle.DataTable.FindField(fld_TIPODETALLE)) then
      if (aDetalle.DataTable.FindField(fld_TIPODETALLE).AsString = TIPODETALLE_TITULO) then
        RellenarImportePuntosEnCapitulo(aDetalle.DataTable);
    RecalcularSubtotales(aDetalle.DataTable);
    if Assigned(aDetalle.DataTable.MasterSource) and
      Supports(aDetalle.DataTable.MasterSource.DataTable, IBizImportesCabecera, ACabecera) then
      ACabecera.RecalcularImporte;
  end;
end;

{ TBizVisibleFieldRules }

procedure TBizVisibleFieldRules.OnChange(Sender: TDACustomField);
var
  aDetalle : IBizVisibleDetalle;
  aParche : IParche;
begin
  inherited;

  { PARCHE ********************************** }
  if Supports(DataTable, IParche, aParche) and
    not (aParche.PuedoLanzarEvento) then
    Exit;

  if Supports(DataTable, IBizVisibleDetalle, aDetalle) then
  begin
    if Assigned(aDetalle.DataTable.FindField(fld_TIPODETALLE)) then
      if (aDetalle.DataTable.FindField(fld_TIPODETALLE).AsString = TIPODETALLE_TITULO) then
        RellenarVisibleEnCapitulo(aDetalle.DataTable);
  end;
end;

initialization
  RegisterFieldRules('Client.Field.Cantidad', TBizCantidadFieldRules);
  RegisterFieldRules('Client.Field.ImporteUnidad', TBizImporteUnidadFieldRules);
  RegisterFieldRules('Client.Field.TipoDetalle', TBizTipoDetalleFieldRules);

  RegisterFieldRules('Client.Field.ImportePuntos', TBizImportePuntosFieldRules);
  RegisterFieldRules('Client.Field.Puntos', TBizPuntosFieldRules);

  RegisterFieldRules('Client.Field.Visible', TBizVisibleFieldRules);

finalization

end.
