unit fcajas;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls, Data.DB,
  ZAbstractRODataset, ZAbstractDataset, ZDataset, Vcl.Grids, Vcl.DBGrids,
  Vcl.DBCtrls, Vcl.ComCtrls;

type
  Tcajas = class(TForm)
    Panel20: TPanel;
    Label47: TLabel;
    btenuevo: TButton;
    btesalir: TButton;
    Button9: TButton;
    Panel1: TPanel;
    DBGrid1: TDBGrid;
    Panel2: TPanel;
    DBGrid2: TDBGrid;
    dsqcajas: TDataSource;
    qcajas: TZQuery;
    qcajasid: TIntegerField;
    qcajasdescripcion: TWideStringField;
    qcajassaldo: TFloatField;
    dsqlineascaja: TDataSource;
    qlineascaja: TZQuery;
    qlineascajaid: TIntegerField;
    qlineascajaidcaja: TIntegerField;
    qlineascajafecha: TDateField;
    qlineascajaconcepto: TWideStringField;
    qlineascajaentrada_efectivo: TFloatField;
    qlineascajasalida: TFloatField;
    qlineascajaentrada_otros: TFloatField;
    Panel3: TPanel;
    Button1: TButton;
    Button2: TButton;
    Button3: TButton; // Botón para editar
    qimportesdia: TZReadOnlyQuery;
    qimportesdiaefectivo: TFloatField;
    qimportesdiatarjeta: TFloatField;
    qimportesdiaingreso: TFloatField;
    qimportesdiadomiciliado: TFloatField;
    vfecha: TDateTimePicker;
    Label1: TLabel;
    Label2: TLabel;
    Label3: TLabel;
    dsqimportesdia: TDataSource;
    DBText1: TDBText;
    DBText2: TDBText;
    qlineascajasaldo_inicial: TFloatField;
    qlineascajasaldo_final: TFloatField;
    qcalculosaldos: TZQuery;
    procedure qlineascajaBeforeOpen(DataSet: TDataSet);
    procedure dsqcajasDataChange(Sender: TObject; Field: TField);
    procedure FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
    procedure FormCreate(Sender: TObject);
    procedure qlineascajaBeforePost(DataSet: TDataSet);
    procedure qlineascajaAfterPost(DataSet: TDataSet);
    procedure FormKeyPress(Sender: TObject; var Key: Char);
    procedure Button2Click(Sender: TObject);
    procedure qlineascajaAfterDelete(DataSet: TDataSet);
    procedure btenuevoClick(Sender: TObject);
    procedure qcajasAfterPost(DataSet: TDataSet);
    procedure qimportesdiaBeforeOpen(DataSet: TDataSet);
    procedure vfechaChange(Sender: TObject);
    procedure qlineascajaAfterInsert(DataSet: TDataSet);
    procedure btesalirClick(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure Button3Click(Sender: TObject);
  private
    { Private declarations }
    // La bandera FActualizandoSaldos ya no es necesaria con este nuevo método.
    procedure ActualizarSaldosCascada(AID_Caja: Integer; AFecha: TDate; AID_Linea: Integer);
    procedure ManejarDialogoLineaCaja(ModoEdicion: Boolean);
  public
    { Public declarations }
  end;

var
  cajas: Tcajas;

implementation

{$R *.dfm}

uses principal,datos, fdlgcajas, ZConnection, ZAbstractConnection;

// ==============================================================================
//  PROCEDIMIENTO DE ACTUALIZACIÓN DE SALDOS (LÓGICA COMPLETAMENTE REVISADA)
// ==============================================================================
procedure Tcajas.ActualizarSaldosCascada(AID_Caja: Integer; AFecha: TDate; AID_Linea: Integer);
var
  SaldoAnterior: Currency;
  SaldoFinalCalculado: Currency;
  ZConnection: TZAbstractConnection;
  qUpdate: TZQuery; // Query para ejecutar los UPDATE
begin
  ZConnection := qcajas.Connection;

  // Creamos un query temporal para las sentencias UPDATE
  qUpdate := TZQuery.Create(nil);
  try
    qUpdate.Connection := ZConnection;
    qUpdate.SQL.Text := 'UPDATE lineas_caja SET saldo_inicial = :si, saldo_final = :sf WHERE id = :id';
    qUpdate.ParamCheck := True;

    // --- PASO 1: OBTENER EL SALDO INICIAL CORRECTO ---
    // Obtenemos el saldo final del registro cronológicamente anterior al que cambió.
    // Usamos COALESCE para que devuelva 0 si no hay registros anteriores.
    qCalculoSaldos.SQL.Text := 'SELECT COALESCE(saldo_final, 0) FROM lineas_caja ' +
                               'WHERE idcaja = :caja AND (fecha < :fecha OR (fecha = :fecha AND id < :id)) ' +
                               'ORDER BY fecha DESC, id DESC LIMIT 1';
    qCalculoSaldos.ParamByName('caja').AsInteger := AID_Caja;
    qCalculoSaldos.ParamByName('fecha').AsDate := AFecha;
    qCalculoSaldos.ParamByName('id').AsInteger := AID_Linea;
    qCalculoSaldos.Open;

    if qCalculoSaldos.IsEmpty then
      SaldoAnterior := 0
    else
      SaldoAnterior := qCalculoSaldos.Fields[0].AsCurrency;

    qCalculoSaldos.Close;

    // --- PASO 2: RECORRER Y ACTUALIZAR EN ORDEN CRONOLÓGICO ---
    // Seleccionamos todos los registros desde el que cambió en adelante, EN ORDEN ASCENDENTE.
    qCalculoSaldos.SQL.Text := 'SELECT id, entrada_efectivo, salida FROM lineas_caja ' +
                               'WHERE idcaja = :caja AND (fecha > :fecha OR (fecha = :fecha AND id >= :id)) ' +
                               'ORDER BY fecha ASC, id ASC';
    qCalculoSaldos.ParamByName('caja').AsInteger := AID_Caja;
    qCalculoSaldos.ParamByName('fecha').AsDate := AFecha;
    qCalculoSaldos.ParamByName('id').AsInteger := AID_Linea;
    qCalculoSaldos.Open;

    if qCalculoSaldos.IsEmpty then Exit; // No hay nada que actualizar

    if not ZConnection.InTransaction then
      ZConnection.StartTransaction;
    try
      while not qCalculoSaldos.Eof do
      begin
        // Calculamos el saldo final para la fila actual
        SaldoFinalCalculado := SaldoAnterior +
                               qCalculoSaldos.FieldByName('entrada_efectivo').AsCurrency -
                               qCalculoSaldos.FieldByName('salida').AsCurrency;

        // Ejecutamos el UPDATE directamente en la BD
        qUpdate.ParamByName('si').AsCurrency := SaldoAnterior;
        qUpdate.ParamByName('sf').AsCurrency := SaldoFinalCalculado;
        qUpdate.ParamByName('id').AsInteger := qCalculoSaldos.FieldByName('id').AsInteger;
        qUpdate.ExecSQL;

        // El saldo final de esta fila es el saldo inicial de la siguiente
        SaldoAnterior := SaldoFinalCalculado;
        qCalculoSaldos.Next;
      end;
      if ZConnection.InTransaction then
        ZConnection.Commit;
    except
      on E: Exception do
      begin
        if ZConnection.InTransaction then ZConnection.Rollback;
        raise;
      end;
    end;

  finally
    qCalculoSaldos.Close;
    qUpdate.Free;
  end;
end;


// ==============================================================================
//  EVENTOS DE DATOS MODIFICADOS (LLAMAN AL NUEVO PROCEDIMIENTO)
// ==============================================================================

procedure Tcajas.qlineascajaAfterPost(DataSet: TDataSet);
var
  id_caja, id_linea: Integer;
  fecha_linea: TDate;
begin
  // Obtenemos los datos clave del registro que acabamos de guardar
  id_caja := qlineascajaidcaja.AsInteger;
  id_linea := qlineascajaid.AsInteger;
  fecha_linea := qlineascajafecha.AsDateTime;

  // Llamamos al procedimiento de actualización
  ActualizarSaldosCascada(id_caja, fecha_linea, id_linea);

  // Refrescamos la vista para mostrar todos los cambios
  qcajas.Refresh;
  qlineascaja.Refresh;

  // Reposicionamos los cursores
  qcajas.Locate('id', id_caja, []);
  qlineascaja.Locate('id', id_linea, []);
end;

procedure Tcajas.qlineascajaAfterDelete(DataSet: TDataSet);
var
  id_caja: Integer;
begin
  // Al borrar, necesitamos recalcular todo. El punto de inicio es ahora irrelevante,
  // así que podemos empezar desde el principio del tiempo para esta caja.
  id_caja := qcajasid.AsInteger;
  ActualizarSaldosCascada(id_caja, 0, 0); // Usamos 0 para recalcular todo

  // Refrescamos las vistas
  qcajas.Refresh;
  qlineascaja.Refresh;
  qcajas.Locate('id', id_caja, []);
end;

// ==============================================================================
//  LÓGICA DEL DIÁLOGO (sin cambios)
// ==============================================================================
procedure Tcajas.ManejarDialogoLineaCaja(ModoEdicion: Boolean);
var
  dlgcajas: Tdlgcajas;
begin
  dlgcajas := Tdlgcajas.Create(nil);
  try
    if ModoEdicion then
    begin
      dlgcajas.eefectivo.text := qlineascajaentrada_efectivo.AsString;
      dlgcajas.esalida.text := qlineascajasalida.AsString;
      dlgcajas.etarjeta.text := qlineascajaentrada_otros.AsString;
      dlgcajas.vfecha.date := qlineascajafecha.AsDateTime;
      dlgcajas.econcepto.text := qlineascajaconcepto.AsString;
    end
    else
    begin
      dlgcajas.vfecha.Date := Date();
      dlgcajas.eefectivo.text := qimportesdiaefectivo.AsString;
      dlgcajas.etarjeta.text := qimportesdiatarjeta.AsString;
      dlgcajas.esalida.text := '0';
    end;

    if dlgcajas.ShowModal = mrOk then
    begin
      qlineascajaidcaja.Value := qcajasid.AsInteger;
      qlineascajaentrada_efectivo.Value := StrToFloatDef(dlgcajas.eefectivo.text, 0);
      qlineascajasalida.Value := StrToFloatDef(dlgcajas.esalida.text, 0);
      qlineascajaentrada_otros.Value := StrToFloatDef(dlgcajas.etarjeta.text, 0);
      qlineascajafecha.Value := dlgcajas.vfecha.date;
      qlineascajaconcepto.Value := dlgcajas.econcepto.text;
      qlineascaja.Post;
    end
    else
    begin
      qlineascaja.Cancel;
    end;
  finally
    dlgcajas.Free;
  end;
end;

// ==============================================================================
//  RESTO DE PROCEDIMIENTOS (sin cambios funcionales importantes)
// ==============================================================================

procedure Tcajas.Button1Click(Sender: TObject);
begin
  qlineascaja.Append;
  ManejarDialogoLineaCaja(False);
end;

procedure Tcajas.Button3Click(Sender: TObject);
begin
  if qlineascaja.IsEmpty then Exit;
  qlineascaja.Edit;
  ManejarDialogoLineaCaja(True);
end;

procedure Tcajas.Button2Click(Sender: TObject);
begin
    if qlineascaja.IsEmpty then Exit;
    if MessageDlg('¿Está seguro de que desea eliminar esta línea?', mtConfirmation, [mbYes, mbNo], 0) = mrYes then
    begin
       qlineascaja.Delete;
    end;
end;

procedure Tcajas.btenuevoClick(Sender: TObject);
begin
   dbgrid1.SetFocus;
   qcajas.Append;
end;

procedure Tcajas.btesalirClick(Sender: TObject);
begin
  close;
end;

procedure Tcajas.vfechaChange(Sender: TObject);
begin
   qimportesdia.Close;
   qimportesdia.Open;
end;

procedure Tcajas.dsqcajasDataChange(Sender: TObject; Field: TField);
begin
   if qcajas.State <> dsInsert then
   begin
     qlineascaja.Close;
     qlineascaja.Open;
   end;
end;

procedure Tcajas.FormCreate(Sender: TObject);
begin
    qcajas.Open;
    vfecha.Date:=date;
end;

procedure Tcajas.FormKeyDown(Sender: TObject; var Key: Word;
  Shift: TShiftState);
begin
    if (activecontrol is tdbgrid) then
    begin
        if Key = 13 then Key :=9;
    end;
end;

procedure Tcajas.FormKeyPress(Sender: TObject; var Key: Char);
begin
    key:=upcase(key);
    if (key='.') and (activecontrol is tdbgrid) then
    begin
        key:=',';
    end;
end;

procedure Tcajas.qcajasAfterPost(DataSet: TDataSet);
begin
    btenuevo.SetFocus;
    qcajas.ApplyUpdates;
end;

procedure Tcajas.qimportesdiaBeforeOpen(DataSet: TDataSet);
begin
   qimportesdia.Prepare;
   qimportesdia.ParamByName('fecha').AsDate:=vfecha.Date;
end;

procedure Tcajas.qlineascajaAfterInsert(DataSet: TDataSet);
begin
    qlineascajafecha.Value:=vfecha.Date;
    qlineascajaentrada_efectivo.Value:=qimportesdiaefectivo.asfloat;
    qlineascajaentrada_otros.Value:=qimportesdiatarjeta.AsFloat;
end;

procedure Tcajas.qlineascajaBeforeOpen(DataSet: TDataSet);
begin
   // Aseguramos que el grid visual se ordena como el usuario quiere (DESC)
   qlineascaja.SQL.Text := 'SELECT * FROM lineas_caja WHERE idcaja = :caja ORDER BY fecha DESC, id DESC';
   qlineascaja.ParamByName('CAJA').AsInteger:=qcajasid.AsInteger;
end;

procedure Tcajas.qlineascajaBeforePost(DataSet: TDataSet);
begin
   qlineascajaidcaja.Value:=qcajasid.AsInteger;
end;

end.

