﻿unit frm_DocumentosRelacionados;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.ExtCtrls, Vcl.StdCtrls, Vcl.Buttons,
  Vcl.Grids, Vcl.DBGrids, Data.DB, FireDAC.Comp.Client, FireDAC.Stan.Param,
  FireDAC.Stan.Intf, FireDAC.Stan.Option, FireDAC.DatS, FireDAC.DApt.Intf,
  FireDAC.Comp.DataSet, uAppTheme, FireDAC.Stan.Error, FireDAC.Phys.Intf;

type
  TDocVentaTipo = (dvtPresupuesto, dvtPedido, dvtAlbaran, dvtFactura, dvtEnvioRecogida);

  TfrmDocumentosRelacionados = class(TForm)
    pnlHeader: TPanel;
    lblTitle: TLabel;
    lblSubTitle: TLabel;
    pnlGrid: TPanel;
    dbGridDocs: TDBGrid;
    pnlFooter: TPanel;
    btnAbrir: TBitBtn;
    btnImprimir: TBitBtn;
    btnRefrescar: TBitBtn;
    btnCerrar: TBitBtn;
    mtDocs: TFDMemTable;
    dsDocs: TDataSource;
    mtDocstipo_doc: TIntegerField;
    mtDocstipo_doc_desc: TStringField;
    mtDocsdoc_id: TStringField;
    mtDocsserie_numero: TStringField;
    mtDocsfecha: TDateField;
    mtDocstercero: TStringField;
    mtDocsestado: TStringField;
    mtDocstotal_str: TStringField;
    mtDocstotal_num: TFloatField;
    mtDocsetapa_orden: TIntegerField;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure FormShow(Sender: TObject);
    procedure btnAbrirClick(Sender: TObject);
    procedure btnImprimirClick(Sender: TObject);
    procedure btnRefrescarClick(Sender: TObject);
    procedure dbGridDocsDblClick(Sender: TObject);
    procedure dbGridDocsTitleClick(Column: TColumn);
  private
    FTipoDocOrigen: TDocVentaTipo;
    FDocIdOrigen: string;
    FSerieNumOrigen: string;
    procedure CargarTrazabilidad;
    procedure AgregarDocumento(ATipoDoc: TDocVentaTipo; const ADocId, ASerieNum: string;
      AFecha: TDateTime; const ATercero, AEstado, ATotalStr: string; ATotalNum: Double; AEtapa: Integer);
    function ExisteDoc(ATipoDoc: TDocVentaTipo; const ADocId: string): Boolean;
    procedure CargarDesdePresupuesto(const APresupuestoId: string);
    procedure CargarDesdePedido(const APedidoId: string);
    procedure CargarDesdeAlbaran(const AAlbaranId: string);
    procedure CargarDesdeFactura(const AFacturaId: string);
    procedure CargarDesdeEnvio(const AEnvioId: string);
  public
    property TipoDocOrigen: TDocVentaTipo read FTipoDocOrigen write FTipoDocOrigen;
    property DocIdOrigen: string read FDocIdOrigen write FDocIdOrigen;
    property SerieNumOrigen: string read FSerieNumOrigen write FSerieNumOrigen;

    class procedure MostrarDocumentos(AOwner: TComponent; ATipoDoc: TDocVentaTipo; const ADocId: string; const ASerieNum: string = '');
  end;

var
  frmDocumentosRelacionados: TfrmDocumentosRelacionados;

implementation

uses
  dmg_Main, frm_PresupuestosEditor, frm_PedidosEditor, frm_AlbaranesEditor,
  frm_FacturasEditor, frm_AlbaranEnvioEditor, uFacturaPrintService, System.StrUtils;

{$R *.dfm}

class procedure TfrmDocumentosRelacionados.MostrarDocumentos(AOwner: TComponent; ATipoDoc: TDocVentaTipo; const ADocId: string; const ASerieNum: string);
var
  Frm: TfrmDocumentosRelacionados;
begin
  if Trim(ADocId) = '' then
  begin
    ShowMessage('Debe guardar o seleccionar un documento antes de consultar sus relaciones.');
    Exit;
  end;

  Frm := TfrmDocumentosRelacionados.Create(AOwner);
  try
    Frm.TipoDocOrigen := ATipoDoc;
    Frm.DocIdOrigen := ADocId;
    Frm.SerieNumOrigen := ASerieNum;
    Frm.ShowModal;
  finally
    Frm.Free;
  end;
end;

procedure TfrmDocumentosRelacionados.FormCreate(Sender: TObject);
begin
  TAppTheme.ApplyToForm(Self);
  mtDocs.CreateDataSet;
end;

procedure TfrmDocumentosRelacionados.FormDestroy(Sender: TObject);
begin
  if mtDocs.Active then
    mtDocs.Close;
end;

procedure TfrmDocumentosRelacionados.FormShow(Sender: TObject);
var
  LNombreTipo: string;
begin
  case FTipoDocOrigen of
    dvtPresupuesto: LNombreTipo := 'Presupuesto';
    dvtPedido:      LNombreTipo := 'Pedido de Cliente';
    dvtAlbaran:     LNombreTipo := 'Albarán de Salida';
    dvtFactura:     LNombreTipo := 'Factura de Venta';
    dvtEnvioRecogida: LNombreTipo := 'Petición Recogida / Transporte';
  end;

  if FSerieNumOrigen <> '' then
    lblSubTitle.Caption := Format('Documento Origen: %s Nº %s', [LNombreTipo, FSerieNumOrigen])
  else
    lblSubTitle.Caption := Format('Documento Origen: %s (ID: %s)', [LNombreTipo, FDocIdOrigen]);

  CargarTrazabilidad;
end;

function TfrmDocumentosRelacionados.ExisteDoc(ATipoDoc: TDocVentaTipo; const ADocId: string): Boolean;
begin
  Result := mtDocs.Locate('tipo_doc;doc_id', VarArrayOf([Ord(ATipoDoc), ADocId]), []);
end;

procedure TfrmDocumentosRelacionados.AgregarDocumento(ATipoDoc: TDocVentaTipo; const ADocId, ASerieNum: string;
  AFecha: TDateTime; const ATercero, AEstado, ATotalStr: string; ATotalNum: Double; AEtapa: Integer);
var
  LDesc: string;
begin
  if (Trim(ADocId) = '') or ExisteDoc(ATipoDoc, ADocId) then
    Exit;

  case ATipoDoc of
    dvtPresupuesto:   LDesc := '📋 Presupuesto';
    dvtPedido:        LDesc := '📦 Pedido Venta';
    dvtAlbaran:       LDesc := '🚚 Albarán Salida';
    dvtFactura:       LDesc := '🧾 Factura Venta';
    dvtEnvioRecogida: LDesc := '🚛 Transporte / Recogida';
  end;

  mtDocs.Append;
  mtDocstipo_doc.AsInteger := Ord(ATipoDoc);
  mtDocstipo_doc_desc.AsString := LDesc;
  mtDocsdoc_id.AsString := ADocId;
  mtDocsserie_numero.AsString := ASerieNum;
  if AFecha > 0 then
    mtDocsfecha.AsDateTime := AFecha;
  mtDocstercero.AsString := ATercero;
  mtDocsestado.AsString := AEstado;
  mtDocstotal_str.AsString := ATotalStr;
  mtDocstotal_num.AsFloat := ATotalNum;
  mtDocsetapa_orden.AsInteger := AEtapa;
  mtDocs.Post;
end;

procedure TfrmDocumentosRelacionados.CargarDesdePresupuesto(const APresupuestoId: string);
var
  Qry: TFDQuery;
begin
  Qry := TFDQuery.Create(nil);
  try
    Qry.Connection := dmgMain.dbConn;

    // 1. Presupuesto
    Qry.SQL.Text :=
      'SELECT p.id, p.serie, p.numero, p.fecha, c.nombre_fiscal AS cliente, p.total ' +
      'FROM ge_presupuestos p ' +
      'LEFT JOIN ge_clientes c ON p.cliente_id = c.id ' +
      'WHERE p.id = :id';
    Qry.ParamByName('id').AsString := APresupuestoId;
    Qry.Open;
    if not Qry.IsEmpty then
    begin
      AgregarDocumento(dvtPresupuesto, Qry.FieldByName('id').AsString,
        Qry.FieldByName('serie').AsString + '/' + Qry.FieldByName('numero').AsString,
        Qry.FieldByName('fecha').AsDateTime,
        Qry.FieldByName('cliente').AsString, 'Aprobado',
        FormatFloat('#,##0.00 €', Qry.FieldByName('total').AsFloat),
        Qry.FieldByName('total').AsFloat, 1);
    end;
  finally
    Qry.Free;
  end;
end;

procedure TfrmDocumentosRelacionados.CargarDesdePedido(const APedidoId: string);
var
  Qry: TFDQuery;
  LAlbaranId: string;
begin
  Qry := TFDQuery.Create(nil);
  try
    Qry.Connection := dmgMain.dbConn;

    // 1. Pedido actual
    Qry.SQL.Text :=
      'SELECT ped.id, ped.numero_pedido, ped.fecha_pedido AS fecha, c.nombre_fiscal AS cliente, ped.total ' +
      'FROM ge_pedidos ped ' +
      'LEFT JOIN ge_clientes c ON ped.cliente_id = c.id ' +
      'WHERE ped.id = :id';
    Qry.ParamByName('id').AsString := APedidoId;
    Qry.Open;
    if not Qry.IsEmpty then
    begin
      AgregarDocumento(dvtPedido, Qry.FieldByName('id').AsString,
        Qry.FieldByName('numero_pedido').AsString,
        Qry.FieldByName('fecha').AsDateTime,
        Qry.FieldByName('cliente').AsString, 'Pedido',
        FormatFloat('#,##0.00 €', Qry.FieldByName('total').AsFloat),
        Qry.FieldByName('total').AsFloat, 2);
    end;

    // 2. Albaranes vinculados al pedido
    Qry.Close;
    Qry.SQL.Text :=
      'SELECT a.id, a.serie, a.numero, a.fecha, c.nombre_fiscal AS cliente, a.total, ' +
      '       a.id_factura AS fac_id ' +
      'FROM ge_albaranes a ' +
      'LEFT JOIN ge_clientes c ON a.cliente_id = c.id ' +
      'WHERE a.id_pedido = :ped_id ' +
      '   OR a.id IN (SELECT r.albaran_id FROM ge_envios_recogidas r WHERE r.pedido_id = :ped_id AND r.albaran_id IS NOT NULL)';
    Qry.ParamByName('ped_id').AsString := APedidoId;
    Qry.Open;
    while not Qry.Eof do
    begin
      LAlbaranId := Qry.FieldByName('id').AsString;
      AgregarDocumento(dvtAlbaran, LAlbaranId,
        Qry.FieldByName('serie').AsString + '/' + Qry.FieldByName('numero').AsString,
        Qry.FieldByName('fecha').AsDateTime,
        Qry.FieldByName('cliente').AsString, 'Albarán',
        FormatFloat('#,##0.00 €', Qry.FieldByName('total').AsFloat),
        Qry.FieldByName('total').AsFloat, 3);
      CargarDesdeAlbaran(LAlbaranId);
      Qry.Next;
    end;

    // 3. Peticiones de Recogida / Envíos vinculados al pedido
    Qry.Close;
    Qry.SQL.Text :=
      'SELECT r.id, r.cod_recogida, r.albaran_carga, r.fecha_recogida, ' +
      '       COALESCE(t.nombre, '''') AS transportista, r.estado_descripcion, r.num_bultos ' +
      'FROM ge_envios_recogidas r ' +
      'LEFT JOIN ge_envios_config cfg ON r.config_id = cfg.id ' +
      'LEFT JOIN ge_envios_transportistas t ON cfg.transportista_id = t.id ' +
      'WHERE r.pedido_id = :ped_id AND r.estado_codigo <> 5';
    Qry.ParamByName('ped_id').AsString := APedidoId;
    Qry.Open;
    while not Qry.Eof do
    begin
      AgregarDocumento(dvtEnvioRecogida, Qry.FieldByName('id').AsString,
        'Recogida: ' + Qry.FieldByName('cod_recogida').AsString +
        ' (Alb. Carga: ' + Qry.FieldByName('albaran_carga').AsString + ')',
        Qry.FieldByName('fecha_recogida').AsDateTime,
        Qry.FieldByName('transportista').AsString,
        Qry.FieldByName('estado_descripcion').AsString,
        Format('%d Bultos', [Qry.FieldByName('num_bultos').AsInteger]),
        0, 5);
      Qry.Next;
    end;
  finally
    Qry.Free;
  end;
end;

procedure TfrmDocumentosRelacionados.CargarDesdeAlbaran(const AAlbaranId: string);
var
  Qry: TFDQuery;
  LPedidoId, LFacturaId: string;
begin
  Qry := TFDQuery.Create(nil);
  try
    Qry.Connection := dmgMain.dbConn;

    // 1. Albarán
    Qry.SQL.Text :=
      'SELECT a.id, a.serie, a.numero, a.fecha, c.nombre_fiscal AS cliente, a.total, ' +
      '       a.id_pedido AS ped_id, ' +
      '       a.id_factura AS fac_id ' +
      'FROM ge_albaranes a ' +
      'LEFT JOIN ge_clientes c ON a.cliente_id = c.id ' +
      'WHERE a.id = :id';
    Qry.ParamByName('id').AsString := AAlbaranId;
    Qry.Open;
    if not Qry.IsEmpty then
    begin
      LPedidoId := Qry.FieldByName('ped_id').AsString;
      LFacturaId := Qry.FieldByName('fac_id').AsString;

      AgregarDocumento(dvtAlbaran, Qry.FieldByName('id').AsString,
        Qry.FieldByName('serie').AsString + '/' + Qry.FieldByName('numero').AsString,
        Qry.FieldByName('fecha').AsDateTime,
        Qry.FieldByName('cliente').AsString, 'Albarán',
        FormatFloat('#,##0.00 €', Qry.FieldByName('total').AsFloat),
        Qry.FieldByName('total').AsFloat, 3);

      // Si tiene pedido padre
      if (LPedidoId <> '') and (LPedidoId <> '0') and not ExisteDoc(dvtPedido, LPedidoId) then
        CargarDesdePedido(LPedidoId);

      // Si tiene factura vinculada directa
      if (LFacturaId <> '') and (LFacturaId <> '0') and not ExisteDoc(dvtFactura, LFacturaId) then
        CargarDesdeFactura(LFacturaId);
    end;

    // 2. Recogidas de transporte asociadas al albarán
    Qry.Close;
    Qry.SQL.Text :=
      'SELECT r.id, r.cod_recogida, r.albaran_carga, r.fecha_recogida, ' +
      '       COALESCE(t.nombre, '''') AS transportista, r.estado_descripcion, r.num_bultos ' +
      'FROM ge_envios_recogidas r ' +
      'LEFT JOIN ge_envios_config cfg ON r.config_id = cfg.id ' +
      'LEFT JOIN ge_envios_transportistas t ON cfg.transportista_id = t.id ' +
      'WHERE r.albaran_id = :alb_id AND r.estado_codigo <> 5';
    Qry.ParamByName('alb_id').AsString := AAlbaranId;
    Qry.Open;
    while not Qry.Eof do
    begin
      AgregarDocumento(dvtEnvioRecogida, Qry.FieldByName('id').AsString,
        'Recogida: ' + Qry.FieldByName('cod_recogida').AsString +
        ' (Alb. Carga: ' + Qry.FieldByName('albaran_carga').AsString + ')',
        Qry.FieldByName('fecha_recogida').AsDateTime,
        Qry.FieldByName('transportista').AsString,
        Qry.FieldByName('estado_descripcion').AsString,
        Format('%d Bultos', [Qry.FieldByName('num_bultos').AsInteger]),
        0, 5);
      Qry.Next;
    end;
  finally
    Qry.Free;
  end;
end;

procedure TfrmDocumentosRelacionados.CargarDesdeFactura(const AFacturaId: string);
var
  Qry: TFDQuery;
  LAlbaranId: string;
begin
  Qry := TFDQuery.Create(nil);
  try
    Qry.Connection := dmgMain.dbConn;

    // 1. Factura
    Qry.SQL.Text :=
      'SELECT f.id, f.serie, f.numero, f.fecha, c.nombre_fiscal AS cliente, f.total, f.pagada ' +
      'FROM ge_facturas f ' +
      'LEFT JOIN ge_clientes c ON f.cliente_id = c.id ' +
      'WHERE f.id = :id';
    Qry.ParamByName('id').AsString := AFacturaId;
    Qry.Open;
    if not Qry.IsEmpty then
    begin
      AgregarDocumento(dvtFactura, Qry.FieldByName('id').AsString,
        Qry.FieldByName('serie').AsString + '/' + Qry.FieldByName('numero').AsString,
        Qry.FieldByName('fecha').AsDateTime,
        Qry.FieldByName('cliente').AsString,
        IfThen(Qry.FieldByName('pagada').AsInteger <> 0, 'Pagada', 'Pendiente'),
        FormatFloat('#,##0.00 €', Qry.FieldByName('total').AsFloat),
        Qry.FieldByName('total').AsFloat, 4);
    end;

    // 2. Albaranes con id_factura apuntando a esta factura
    Qry.Close;
    Qry.SQL.Text :=
      'SELECT a.id FROM ge_albaranes a WHERE a.id_factura = :fac_id';
    Qry.ParamByName('fac_id').AsString := AFacturaId;
    Qry.Open;
    while not Qry.Eof do
    begin
      LAlbaranId := Qry.FieldByName('id').AsString;
      if not ExisteDoc(dvtAlbaran, LAlbaranId) then
        CargarDesdeAlbaran(LAlbaranId);
      Qry.Next;
    end;
  finally
    Qry.Free;
  end;
end;

procedure TfrmDocumentosRelacionados.CargarDesdeEnvio(const AEnvioId: string);
var
  Qry: TFDQuery;
  LPedidoId, LAlbaranId: string;
begin
  Qry := TFDQuery.Create(nil);
  try
    Qry.Connection := dmgMain.dbConn;

    Qry.SQL.Text :=
      'SELECT r.id, r.pedido_id, r.albaran_id, r.cod_recogida, r.albaran_carga, ' +
      '       r.fecha_recogida, COALESCE(t.nombre, '''') AS transportista, r.estado_descripcion, r.num_bultos ' +
      'FROM ge_envios_recogidas r ' +
      'LEFT JOIN ge_envios_config cfg ON r.config_id = cfg.id ' +
      'LEFT JOIN ge_envios_transportistas t ON cfg.transportista_id = t.id ' +
      'WHERE r.id = :id';
    Qry.ParamByName('id').AsString := AEnvioId;
    Qry.Open;
    if not Qry.IsEmpty then
    begin
      LPedidoId := Qry.FieldByName('pedido_id').AsString;
      LAlbaranId := Qry.FieldByName('albaran_id').AsString;

      AgregarDocumento(dvtEnvioRecogida, Qry.FieldByName('id').AsString,
        'Recogida: ' + Qry.FieldByName('cod_recogida').AsString +
        ' (Alb. Carga: ' + Qry.FieldByName('albaran_carga').AsString + ')',
        Qry.FieldByName('fecha_recogida').AsDateTime,
        Qry.FieldByName('transportista').AsString,
        Qry.FieldByName('estado_descripcion').AsString,
        Format('%d Bultos', [Qry.FieldByName('num_bultos').AsInteger]),
        0, 5);

      if (LPedidoId <> '') and (LPedidoId <> '0') and not ExisteDoc(dvtPedido, LPedidoId) then
        CargarDesdePedido(LPedidoId);

      if (LAlbaranId <> '') and (LAlbaranId <> '0') and not ExisteDoc(dvtAlbaran, LAlbaranId) then
        CargarDesdeAlbaran(LAlbaranId);
    end;
  finally
    Qry.Free;
  end;
end;

procedure TfrmDocumentosRelacionados.CargarTrazabilidad;
begin
  mtDocs.DisableControls;
  try
    mtDocs.EmptyDataSet;

    case FTipoDocOrigen of
      dvtPresupuesto:   CargarDesdePresupuesto(FDocIdOrigen);
      dvtPedido:        CargarDesdePedido(FDocIdOrigen);
      dvtAlbaran:       CargarDesdeAlbaran(FDocIdOrigen);
      dvtFactura:       CargarDesdeFactura(FDocIdOrigen);
      dvtEnvioRecogida: CargarDesdeEnvio(FDocIdOrigen);
    end;

    // Ordenar por etapa del ciclo comercial y fecha
    mtDocs.IndexFieldNames := 'etapa_orden;fecha';
    mtDocs.First;
  finally
    mtDocs.EnableControls;
  end;
end;

procedure TfrmDocumentosRelacionados.btnAbrirClick(Sender: TObject);
var
  LTipoDoc: TDocVentaTipo;
  LDocId: string;
begin
  if mtDocs.IsEmpty then
    Exit;

  LTipoDoc := TDocVentaTipo(mtDocstipo_doc.AsInteger);
  LDocId := mtDocsdoc_id.AsString;

  case LTipoDoc of
    dvtPresupuesto:
    begin
      if not Assigned(frmPresupuestosEditor) then
        Application.CreateForm(TfrmPresupuestosEditor, frmPresupuestosEditor);
      frmPresupuestosEditor.PresupuestoId := LDocId;
      frmPresupuestosEditor.ShowModal;
    end;
    dvtPedido:
    begin
      if not Assigned(frmPedidosEditor) then
        Application.CreateForm(TfrmPedidosEditor, frmPedidosEditor);
      frmPedidosEditor.PedidoId := LDocId;
      frmPedidosEditor.ShowModal;
    end;
    dvtAlbaran:
    begin
      if not Assigned(frmAlbaranesEditor) then
        Application.CreateForm(TfrmAlbaranesEditor, frmAlbaranesEditor);
      frmAlbaranesEditor.AlbaranId := LDocId;
      frmAlbaranesEditor.ShowModal;
    end;
    dvtFactura:
    begin
      if not Assigned(frmFacturasEditor) then
        Application.CreateForm(TfrmFacturasEditor, frmFacturasEditor);
      frmFacturasEditor.FacturaId := LDocId;
      frmFacturasEditor.ShowModal;
    end;
    dvtEnvioRecogida:
    begin
      if not Assigned(frmAlbaranEnvioEditor) then
        Application.CreateForm(TfrmAlbaranEnvioEditor, frmAlbaranEnvioEditor);
      frmAlbaranEnvioEditor.RecogidaId := StrToIntDef(LDocId, 0);
      frmAlbaranEnvioEditor.ShowModal;
    end;
  end;

  CargarTrazabilidad;
end;

procedure TfrmDocumentosRelacionados.btnImprimirClick(Sender: TObject);
var
  LTipoDoc: TDocVentaTipo;
  LDocId: string;
begin
  if mtDocs.IsEmpty then
    Exit;

  LTipoDoc := TDocVentaTipo(mtDocstipo_doc.AsInteger);
  LDocId := mtDocsdoc_id.AsString;

  case LTipoDoc of
    dvtFactura:
      TFacturaPrintService.ImprimirFactura(Self, LDocId);
    else
      // Abrir el documento para que el usuario pueda imprimir con su plantilla correspondiente
      btnAbrirClick(nil);
  end;
end;

procedure TfrmDocumentosRelacionados.btnRefrescarClick(Sender: TObject);
begin
  CargarTrazabilidad;
end;

procedure TfrmDocumentosRelacionados.dbGridDocsDblClick(Sender: TObject);
begin
  btnAbrirClick(nil);
end;

procedure TfrmDocumentosRelacionados.dbGridDocsTitleClick(Column: TColumn);
begin
  if mtDocs.Active and (Column.Field <> nil) then
  begin
    if mtDocs.IndexFieldNames = Column.Field.FieldName then
      mtDocs.IndexFieldNames := Column.Field.FieldName + ':D'
    else
      mtDocs.IndexFieldNames := Column.Field.FieldName;
  end;
end;

end.
