unit frm_OrdenProduccionEditor;

interface

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

type
  TfrmOrdenProduccionEditor = class(TfrmBase)
    pnlCabecera: TPanel;
    lblNumeroOrden: TLabel;
    edtNumeroOrden: TEdit;
    lblFechaProduccion: TLabel;
    dtpFechaProduccion: TDateTimePicker;
    lblFechaEntrega: TLabel;
    dtpFechaEntrega: TDateTimePicker;
    lblObrador: TLabel;
    cbbObrador: TComboBox;
    lblPedido: TLabel;
    cbbPedido: TComboBox;
    btnImportarPedido: TSpeedButton;
    lblEstado: TLabel;
    cbbEstado: TComboBox;
    lblPrioridad: TLabel;
    cbbPrioridad: TComboBox;
    lblObservacionesCab: TLabel;
    edtObservacionesCab: TEdit;
    pnlVinculoPedido: TPanel;
    lblVinculoPedido: TLabel;
    btnVerPedido: TSpeedButton;

    mtLineas: TFDMemTable;
    dsLineas: TDataSource;

    procedure FormCreate(Sender: TObject);
    procedure FormShow(Sender: TObject);
    procedure btnGuardarClick(Sender: TObject);
    procedure btnSalirClick(Sender: TObject);
    procedure btnEliminarClick(Sender: TObject);
    procedure btnNuevoArticuloClick(Sender: TObject);
    procedure btnEliminaArticuloClick(Sender: TObject);
    procedure dbgItemsKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
    procedure btnImportarPedidoClick(Sender: TObject);
    procedure cbbPedidoChange(Sender: TObject);
    procedure btnVerPedidoClick(Sender: TObject);
    procedure dbgItemsDrawColumnCell(Sender: TObject; const Rect: TRect;
      DataCol: Integer; Column: TColumn; State: TGridDrawState);
  protected
    procedure Loaded; override;
    procedure DoShow; override;
    procedure DoAnadir; override;
    procedure DoModificar; override;
    function IsNewRecord: Boolean; override;
    function HasUnsavedChanges: Boolean; override;
    procedure UpdateActionButtonsState; override;
  private
    FOrdenId: Integer;
    FInitialFechaProduccion: TDate;
    FEmpresaId: Integer;
    FInitialPedidoId: Integer;
    FPedidoId: Integer;
    FLineasModificadas: Boolean;
    procedure CargarCombos;
    procedure ConfigurarTablaLineas;
    procedure ConfigurarColumnasGrid;
    procedure LoadData;
    function GenerarNumeroOrdenSugerido: string;
    procedure InicializarDesdePedido(APedidoId: Integer);
    procedure ActualizarBannerPedido;
  public
    property OrdenId: Integer read FOrdenId write FOrdenId;
    property InitialFechaProduccion: TDate read FInitialFechaProduccion write FInitialFechaProduccion;
    property InitialPedidoId: Integer read FInitialPedidoId write FInitialPedidoId;
    property InitialEmpresaId: Integer read FEmpresaId write FEmpresaId;
  end;

var
  frmOrdenProduccionEditor: TfrmOrdenProduccionEditor;

implementation

uses
  dmg_Main, frm_SelectProduct, frm_PedidosEditor;

{$R *.dfm}

procedure TfrmOrdenProduccionEditor.Loaded;
begin
  inherited Loaded;
  ConfigurarTablaLineas;
end;

procedure TfrmOrdenProduccionEditor.FormCreate(Sender: TObject);
begin
  inherited;
  FOrdenId := 0;
  FEmpresaId := 0;
  FInitialPedidoId := 0;
  FPedidoId := 0;
  FInitialFechaProduccion := Date;
  ConfigurarTablaLineas;
end;

procedure TfrmOrdenProduccionEditor.DoShow;
begin
  tsDatosEnvio.Caption := 'Artículos y Necesidades a Producir';
  CargarCombos;

  if FOrdenId > 0 then
    LoadData
  else
  begin
    DoAnadir;
    if FInitialPedidoId > 0 then
      InicializarDesdePedido(FInitialPedidoId);
  end;

  ConfigurarColumnasGrid;
  ActualizarBannerPedido;

  inherited DoShow;

  // Vinculación explícita de acciones de botones tras inherited DoShow
  // para asegurar que TfrmBase no reemplace btnGuardar.OnClick ni anule ModalResult:
  btnNuevoArticulo.OnClick := btnNuevoArticuloClick;
  btnEliminaArticulo.OnClick := btnEliminaArticuloClick;
  btnEliminaArticulo.Visible := True;
  dbgItems.OnKeyDown := dbgItemsKeyDown;
  btnGuardar.OnClick := btnGuardarClick;
  btnSalir.OnClick := btnSalirClick;
  btnEliminar.OnClick := btnEliminarClick;
  btnEliminar.Visible := True;

  FLineasModificadas := False;
  UpdateActionButtonsState;
end;

procedure TfrmOrdenProduccionEditor.FormShow(Sender: TObject);
begin
  inherited;
  // La inicialización se realiza en DoShow para asegurar el orden correcto con TfrmBase
end;

procedure TfrmOrdenProduccionEditor.ConfigurarTablaLineas;
begin
  mtLineas.Close;
  mtLineas.FieldDefs.Clear;
  mtLineas.FieldDefs.Add('id', ftInteger);
  mtLineas.FieldDefs.Add('id_articulo', ftInteger);
  mtLineas.FieldDefs.Add('articulo_nombre', ftString, 120);
  mtLineas.FieldDefs.Add('cantidad', ftFloat);
  mtLineas.FieldDefs.Add('cantidad_fabricada', ftFloat);
  mtLineas.FieldDefs.Add('id_pedido_linea', ftInteger);
  mtLineas.FieldDefs.Add('pedido_linea_info', ftString, 50);
  mtLineas.FieldDefs.Add('observaciones', ftString, 255);
  mtLineas.FieldDefs.Add('estado', ftString, 20);
  mtLineas.CreateDataSet;

  dsLineas.DataSet := mtLineas;
  dbgItems.DataSource := dsLineas;
end;

procedure TfrmOrdenProduccionEditor.ConfigurarColumnasGrid;
begin
  dbgItems.Columns.Clear;

  with dbgItems.Columns.Add do
  begin
    FieldName := 'articulo_nombre';
    Title.Caption := 'Artículo a Producir';
    Width := 250;
    ReadOnly := True;
  end;

  with dbgItems.Columns.Add do
  begin
    FieldName := 'cantidad';
    Title.Caption := 'Cant. Necesaria';
    Width := 105;
    Alignment := taRightJustify;
  end;

  with dbgItems.Columns.Add do
  begin
    FieldName := 'cantidad_fabricada';
    Title.Caption := 'Fabricada';
    Width := 85;
    Alignment := taRightJustify;
  end;

  with dbgItems.Columns.Add do
  begin
    FieldName := 'pedido_linea_info';
    Title.Caption := 'Lín. Pedido';
    Width := 95;
    Alignment := taCenter;
    ReadOnly := True;
  end;

  with dbgItems.Columns.Add do
  begin
    FieldName := 'observaciones';
    Title.Caption := 'Observaciones de la Línea (Visible en Calendario Hover)';
    Width := 280;
  end;

  with dbgItems.Columns.Add do
  begin
    FieldName := 'estado';
    Title.Caption := 'Estado';
    Width := 95;
  end;
end;

procedure TfrmOrdenProduccionEditor.CargarCombos;
var
  LQry: TFDQuery;
  LEmpresaId: Integer;
begin
  if FEmpresaId > 0 then
    LEmpresaId := FEmpresaId
  else
    LEmpresaId := StrToIntDef(dmgMain.CurrentCompanyId, 1);

  // 1. Cargar Obradores
  cbbObrador.Items.Clear;
  LQry := TFDQuery.Create(nil);
  try
    LQry.Connection := dmgMain.dbConn;
    LQry.SQL.Text := 'SELECT id, nombre FROM ge_obradores ORDER BY nombre';
    LQry.Open;
    while not LQry.Eof do
    begin
      cbbObrador.Items.AddObject(
        LQry.FieldByName('nombre').AsString,
        TObject(IntPtr(LQry.FieldByName('id').AsInteger))
      );
      LQry.Next;
    end;

    // 2. Cargar Pedidos Recientes de la Empresa
    cbbPedido.Items.Clear;
    cbbPedido.Items.AddObject('-- Sin Pedido Vinculado (Manual) --', TObject(IntPtr(0)));
    LQry.Close;
    LQry.SQL.Text :=
      'SELECT p.id, p.numero_pedido, c.nombre_comercial, p.fecha_pedido ' +
      'FROM ge_pedidos p ' +
      'LEFT JOIN ge_clientes c ON p.cliente_id = c.id ' +
      'WHERE p.empresa_id = :empresa_id ' +
      'ORDER BY p.fecha_pedido DESC, p.id DESC LIMIT 50';
    LQry.ParamByName('empresa_id').AsInteger := LEmpresaId;
    LQry.Open;
    while not LQry.Eof do
    begin
      cbbPedido.Items.AddObject(
        Format('%s - %s (%s)', [
          LQry.FieldByName('numero_pedido').AsString,
          LQry.FieldByName('nombre_comercial').AsString,
          FormatDateTime('dd/MM/yyyy', LQry.FieldByName('fecha_pedido').AsDateTime)
        ]),
        TObject(IntPtr(LQry.FieldByName('id').AsInteger))
      );
      LQry.Next;
    end;
  finally
    LQry.Free;
  end;

  if cbbObrador.Items.Count > 0 then
    cbbObrador.ItemIndex := 0;

  if cbbPedido.Items.Count > 0 then
    cbbPedido.ItemIndex := 0;

  // Estados y Prioridades
  cbbEstado.Items.Clear;
  cbbEstado.Items.Add('PLANIFICADA');
  cbbEstado.Items.Add('EN_PROCESO');
  cbbEstado.Items.Add('COMPLETADA');
  cbbEstado.Items.Add('CANCELADA');
  cbbEstado.ItemIndex := 0;

  cbbPrioridad.Items.Clear;
  cbbPrioridad.Items.Add('BAJA');
  cbbPrioridad.Items.Add('NORMAL');
  cbbPrioridad.Items.Add('ALTA');
  cbbPrioridad.Items.Add('URGENTE');
  cbbPrioridad.ItemIndex := 1; // NORMAL
end;

function TfrmOrdenProduccionEditor.GenerarNumeroOrdenSugerido: string;
var
  LQry: TFDQuery;
  LNextNum: Integer;
  LYear: Word;
begin
  LYear := CurrentYear;
  LQry := TFDQuery.Create(nil);
  try
    LQry.Connection := dmgMain.dbConn;
    LQry.SQL.Text :=
      'SELECT COUNT(*) + 1 AS sig ' +
      'FROM ge_ordenes_produccion ' +
      'WHERE YEAR(fecha_orden) = :anio';
    LQry.ParamByName('anio').AsInteger := LYear;
    LQry.Open;
    LNextNum := LQry.FieldByName('sig').AsInteger;
    Result := Format('OP-%d-%4.4d', [LYear, LNextNum]);
  finally
    LQry.Free;
  end;
end;

procedure TfrmOrdenProduccionEditor.DoAnadir;
begin
  inherited;
  FOrdenId := 0;
  FPedidoId := 0;
  edtNumeroOrden.Text := GenerarNumeroOrdenSugerido;
  if FInitialFechaProduccion > 0 then
    dtpFechaProduccion.Date := FInitialFechaProduccion
  else
    dtpFechaProduccion.Date := Date;

  dtpFechaEntrega.Date := dtpFechaProduccion.Date + 1;
  cbbEstado.ItemIndex := 0; // PLANIFICADA
  cbbPrioridad.ItemIndex := 1; // NORMAL
  edtObservacionesCab.Text := '';

  mtLineas.Close;
  mtLineas.Open;
  ActualizarBannerPedido;
end;

procedure TfrmOrdenProduccionEditor.DoModificar;
begin
  inherited;
end;

procedure TfrmOrdenProduccionEditor.LoadData;
var
  LQry: TFDQuery;
  I: Integer;
  LObradorId, LPedidoId: Integer;
begin
  if FOrdenId <= 0 then Exit;

  LQry := TFDQuery.Create(nil);
  try
    LQry.Connection := dmgMain.dbConn;
    LQry.SQL.Text := 'SELECT * FROM ge_ordenes_produccion WHERE id = :id';
    LQry.ParamByName('id').AsInteger := FOrdenId;
    LQry.Open;

    if not LQry.IsEmpty then
    begin
      FEmpresaId := LQry.FieldByName('empresa_id').AsInteger;
      edtNumeroOrden.Text := LQry.FieldByName('numero_orden').AsString;
      dtpFechaProduccion.Date := LQry.FieldByName('fecha_produccion').AsDateTime;
      if not LQry.FieldByName('fecha_entrega_prevista').IsNull then
        dtpFechaEntrega.Date := LQry.FieldByName('fecha_entrega_prevista').AsDateTime
      else
        dtpFechaEntrega.Date := dtpFechaProduccion.Date;

      LObradorId := LQry.FieldByName('id_obrador').AsInteger;
      LPedidoId := LQry.FieldByName('id_pedido').AsInteger;
      FPedidoId := LPedidoId;
      cbbEstado.Text := LQry.FieldByName('estado').AsString;
      cbbPrioridad.Text := LQry.FieldByName('prioridad').AsString;
      edtObservacionesCab.Text := LQry.FieldByName('observaciones').AsString;

      for I := 0 to cbbObrador.Items.Count - 1 do
      begin
        if Integer(IntPtr(cbbObrador.Items.Objects[I])) = LObradorId then
        begin
          cbbObrador.ItemIndex := I;
          Break;
        end;
      end;

      cbbPedido.ItemIndex := 0;
      if LPedidoId > 0 then
      begin
        for I := 0 to cbbPedido.Items.Count - 1 do
        begin
          if Integer(IntPtr(cbbPedido.Items.Objects[I])) = LPedidoId then
          begin
            cbbPedido.ItemIndex := I;
            Break;
          end;
        end;

        if (cbbPedido.ItemIndex = 0) and (LPedidoId > 0) then
        begin
          var LQryPed := TFDQuery.Create(nil);
          try
            LQryPed.Connection := dmgMain.dbConn;
            LQryPed.SQL.Text :=
              'SELECT p.numero_pedido, ' +
              '       COALESCE(NULLIF(c.nombre_comercial, ''''), c.nombre_fiscal, '''') as cliente_nombre, ' +
              '       p.fecha_pedido ' +
              'FROM ge_pedidos p LEFT JOIN ge_clientes c ON p.cliente_id = c.id ' +
              'WHERE p.id = :id';
            LQryPed.ParamByName('id').AsInteger := LPedidoId;
            LQryPed.Open;
            if not LQryPed.IsEmpty then
            begin
              cbbPedido.Items.AddObject(
                Format('%s - %s (%s)', [
                  LQryPed.FieldByName('numero_pedido').AsString,
                  LQryPed.FieldByName('cliente_nombre').AsString,
                  FormatDateTime('dd/MM/yyyy', LQryPed.FieldByName('fecha_pedido').AsDateTime)
                ]),
                TObject(IntPtr(LPedidoId))
              );
              cbbPedido.ItemIndex := cbbPedido.Items.Count - 1;
            end;
          finally
            LQryPed.Free;
          end;
        end;
      end;
    end;

    // Cargar Líneas
    mtLineas.Close;
    mtLineas.Open;

    LQry.Close;
    LQry.SQL.Text :=
      'SELECT l.*, a.descripcion AS articulo_nombre ' +
      'FROM ge_ordenes_produccion_lineas l ' +
      'LEFT JOIN ge_articulos a ON l.id_articulo = a.id ' +
      'WHERE l.id_orden = :id_orden ' +
      'ORDER BY l.id ASC';
    LQry.ParamByName('id_orden').AsInteger := FOrdenId;
    LQry.Open;

    while not LQry.Eof do
    begin
      mtLineas.Append;
      mtLineas.FieldByName('id').AsInteger := LQry.FieldByName('id').AsInteger;
      mtLineas.FieldByName('id_articulo').AsInteger := LQry.FieldByName('id_articulo').AsInteger;
      mtLineas.FieldByName('articulo_nombre').AsString := LQry.FieldByName('articulo_nombre').AsString;
      mtLineas.FieldByName('cantidad').AsFloat := LQry.FieldByName('cantidad').AsFloat;
      mtLineas.FieldByName('cantidad_fabricada').AsFloat := LQry.FieldByName('cantidad_fabricada').AsFloat;
      mtLineas.FieldByName('id_pedido_linea').AsInteger := LQry.FieldByName('id_pedido_linea').AsInteger;
      if LQry.FieldByName('id_pedido_linea').AsInteger > 0 then
        mtLineas.FieldByName('pedido_linea_info').AsString := Format('Lín. #%d', [LQry.FieldByName('id_pedido_linea').AsInteger])
      else
        mtLineas.FieldByName('pedido_linea_info').AsString := '—';
      mtLineas.FieldByName('observaciones').AsString := LQry.FieldByName('observaciones').AsString;
      mtLineas.FieldByName('estado').AsString := LQry.FieldByName('estado').AsString;
      mtLineas.Post;
      LQry.Next;
    end;
  finally
    LQry.Free;
  end;
  ActualizarBannerPedido;
end;

procedure TfrmOrdenProduccionEditor.btnNuevoArticuloClick(Sender: TObject);
var
  LSelectFrm: TfrmSelectProduct;
  LCantidadStr: string;
  LCantidad: Double;
  LObservaciones: string;
begin
  LSelectFrm := TfrmSelectProduct.Create(Self);
  try
    if LSelectFrm.ShowModal = mrOk then
    begin
      LCantidadStr := '1';
      if not InputQuery('Cantidad a Producir', 'Indique la cantidad de unidades necesarias:', LCantidadStr) then
        Exit;

      LCantidad := StrToFloatDef(StringReplace(LCantidadStr, ',', '.', [rfReplaceAll]), 1.0);
      if LCantidad <= 0 then LCantidad := 1.0;

      LObservaciones := '';
      InputQuery('Observaciones de la Línea', 'Indique observaciones de elaboración (aparecerá en hover del calendario):', LObservaciones);

      mtLineas.Append;
      mtLineas.FieldByName('id').AsInteger := 0;
      mtLineas.FieldByName('id_articulo').AsInteger := StrToIntDef(LSelectFrm.SelectedId, 0);
      mtLineas.FieldByName('articulo_nombre').AsString := LSelectFrm.SelectedDesc;
      mtLineas.FieldByName('cantidad').AsFloat := LCantidad;
      mtLineas.FieldByName('cantidad_fabricada').AsFloat := 0.0;
      mtLineas.FieldByName('id_pedido_linea').AsInteger := 0;
      mtLineas.FieldByName('observaciones').AsString := Trim(LObservaciones);
      mtLineas.FieldByName('estado').AsString := 'PENDIENTE';
      mtLineas.Post;

      FLineasModificadas := True;
      UpdateActionButtonsState;
    end;
  finally
    LSelectFrm.Free;
  end;
end;

procedure TfrmOrdenProduccionEditor.btnEliminaArticuloClick(Sender: TObject);
var
  LArtName: string;
  LLineaId: Integer;
  LQry: TFDQuery;
begin
  if not mtLineas.IsEmpty then
  begin
    if mtLineas.State in [dsEdit, dsInsert] then
      mtLineas.Post;

    LLineaId := mtLineas.FieldByName('id').AsInteger;
    LArtName := mtLineas.FieldByName('articulo_nombre').AsString;
    if LArtName = '' then
      LArtName := 'este artículo';

    if MessageDlg(Format('¿Desea eliminar "%s" de la orden de producción?', [LArtName]),
                  mtConfirmation, [mbYes, mbNo], 0) = mrYes then
    begin
      // Si la línea ya existe físicamente en la base de datos (orden guardada),
      // eliminarla inmediatamente de ge_ordenes_produccion_lineas para garantizar su persistencia:
      if (FOrdenId > 0) and (LLineaId > 0) then
      begin
        LQry := TFDQuery.Create(nil);
        try
          LQry.Connection := dmgMain.dbConn;
          LQry.SQL.Text := 'DELETE FROM ge_ordenes_produccion_lineas WHERE id = :id';
          LQry.ParamByName('id').AsInteger := LLineaId;
          LQry.ExecSQL;
        finally
          LQry.Free;
        end;
      end;

      mtLineas.Delete;
      FLineasModificadas := True;
      UpdateActionButtonsState;
    end;
  end;
end;

procedure TfrmOrdenProduccionEditor.dbgItemsKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
begin
  if (Key = VK_DELETE) and not (ssCtrl in Shift) and not (ssAlt in Shift) then
  begin
    Key := 0;
    btnEliminaArticuloClick(Sender);
  end;
end;

procedure TfrmOrdenProduccionEditor.btnImportarPedidoClick(Sender: TObject);
var
  LPedidoId: Integer;
  LQry: TFDQuery;
  LCount: Integer;
  LDesc: string;
begin
  if cbbPedido.ItemIndex <= 0 then
  begin
    ShowMessage('Seleccione un pedido de cliente en el desplegable superior para importar sus líneas.');
    Exit;
  end;

  LPedidoId := Integer(IntPtr(cbbPedido.Items.Objects[cbbPedido.ItemIndex]));
  if LPedidoId <= 0 then Exit;

  FPedidoId := LPedidoId;
  ActualizarBannerPedido;

  if not mtLineas.IsEmpty then
  begin
    if MessageDlg('Ya existen artículos en la orden. ¿Desea añadir los artículos del pedido a los existentes?',
                  mtConfirmation, [mbYes, mbNo], 0) <> mrYes then
      Exit;
  end;

  LQry := TFDQuery.Create(nil);
  try
    LQry.Connection := dmgMain.dbConn;
    // 1. Intentar cargar líneas del pedido no asignadas aún a órdenes de producción
    LQry.SQL.Text :=
      'SELECT pl.id, pl.id_articulo, pl.descripcion, pl.cantidad, a.descripcion AS art_nombre ' +
      'FROM ge_pedidos_lineas pl ' +
      'LEFT JOIN ge_articulos a ON pl.id_articulo = a.id ' +
      'WHERE pl.id_pedido = :id_pedido ' +
      '  AND pl.id NOT IN ( ' +
      '    SELECT opl.id_pedido_linea ' +
      '    FROM ge_ordenes_produccion_lineas opl ' +
      '    JOIN ge_ordenes_produccion op ON op.id = opl.id_orden ' +
      '    WHERE opl.id_pedido_linea IS NOT NULL ' +
      '  ) ' +
      'ORDER BY pl.id ASC';
    LQry.ParamByName('id_pedido').AsInteger := LPedidoId;
    LQry.Open;

    if LQry.IsEmpty then
    begin
      if MessageDlg('Todas las líneas de este pedido ya han sido asignadas a otras órdenes de producción.' + sLineBreak +
                    '¿Desea importar todas las líneas de todos modos?',
                    mtConfirmation, [mbYes, mbNo], 0) <> mrYes then
        Exit;

      LQry.Close;
      LQry.SQL.Text :=
        'SELECT pl.id, pl.id_articulo, pl.descripcion, pl.cantidad, a.descripcion AS art_nombre ' +
        'FROM ge_pedidos_lineas pl ' +
        'LEFT JOIN ge_articulos a ON pl.id_articulo = a.id ' +
        'WHERE pl.id_pedido = :id_pedido ' +
        'ORDER BY pl.id ASC';
      LQry.ParamByName('id_pedido').AsInteger := LPedidoId;
      LQry.Open;
    end;

    LCount := 0;
    while not LQry.Eof do
    begin
      mtLineas.Append;
      mtLineas.FieldByName('id').AsInteger := 0;
      mtLineas.FieldByName('id_articulo').AsInteger := LQry.FieldByName('id_articulo').AsInteger;
      LDesc := Trim(LQry.FieldByName('descripcion').AsString);
      if LDesc = '' then
        LDesc := Trim(LQry.FieldByName('art_nombre').AsString);
      mtLineas.FieldByName('articulo_nombre').AsString := LDesc;
      mtLineas.FieldByName('cantidad').AsFloat := LQry.FieldByName('cantidad').AsFloat;
      mtLineas.FieldByName('cantidad_fabricada').AsFloat := 0.0;
      mtLineas.FieldByName('id_pedido_linea').AsInteger := LQry.FieldByName('id').AsInteger;
      mtLineas.FieldByName('pedido_linea_info').AsString := Format('Lín. #%d', [LQry.FieldByName('id').AsInteger]);
      mtLineas.FieldByName('observaciones').AsString := 'Importado de Pedido ' + cbbPedido.Text;
      mtLineas.FieldByName('estado').AsString := 'PENDIENTE';
      mtLineas.Post;

      Inc(LCount);
      LQry.Next;
    end;

    if LCount > 0 then
    begin
      FLineasModificadas := True;
      UpdateActionButtonsState;
    end;

    ShowMessage(Format('Se han importado %d artículo(s) desde el pedido.', [LCount]));
  finally
    LQry.Free;
  end;
end;

procedure TfrmOrdenProduccionEditor.InicializarDesdePedido(APedidoId: Integer);
var
  LQry: TFDQuery;
  LQryLines: TFDQuery;
  LNumPedido: string;
  LClienteNombre: string;
  LFechaEntrega: TDateTime;
  LFechaPedido: TDateTime;
  LTextoCombo: string;
  I: Integer;
  LFound: Boolean;
  LArtId: Integer;
  LDesc: string;
begin
  if APedidoId <= 0 then Exit;
  FPedidoId := APedidoId;

  LQry := TFDQuery.Create(nil);
  try
    LQry.Connection := dmgMain.dbConn;
    LQry.SQL.Text :=
      'SELECT p.id, p.empresa_id, p.numero_pedido, p.fecha_pedido, p.fecha_entrega, p.observaciones, ' +
      '       COALESCE(NULLIF(c.nombre_comercial, ''''), c.nombre_fiscal, '''') as cliente_nombre ' +
      'FROM ge_pedidos p ' +
      'LEFT JOIN ge_clientes c ON p.cliente_id = c.id ' +
      'WHERE p.id = :id';
    LQry.ParamByName('id').AsInteger := APedidoId;
    LQry.Open;

    if LQry.IsEmpty then Exit;

    FEmpresaId := LQry.FieldByName('empresa_id').AsInteger;
    LNumPedido := LQry.FieldByName('numero_pedido').AsString;
    LClienteNombre := LQry.FieldByName('cliente_nombre').AsString;
    LFechaPedido := LQry.FieldByName('fecha_pedido').AsDateTime;

    // Asegurar que el pedido está en cbbPedido y seleccionarlo
    LFound := False;
    for I := 0 to cbbPedido.Items.Count - 1 do
    begin
      if Integer(IntPtr(cbbPedido.Items.Objects[I])) = APedidoId then
      begin
        cbbPedido.ItemIndex := I;
        LFound := True;
        Break;
      end;
    end;

    if not LFound then
    begin
      LTextoCombo := Format('%s - %s (%s)', [
        LNumPedido,
        LClienteNombre,
        FormatDateTime('dd/MM/yyyy', LFechaPedido)
      ]);
      cbbPedido.Items.AddObject(LTextoCombo, TObject(IntPtr(APedidoId)));
      cbbPedido.ItemIndex := cbbPedido.Items.Count - 1;
    end;

    // Configurar fechas según el pedido
    if not LQry.FieldByName('fecha_entrega').IsNull then
    begin
      LFechaEntrega := LQry.FieldByName('fecha_entrega').AsDateTime;
      dtpFechaEntrega.Date := LFechaEntrega;
      if LFechaEntrega > Date then
        dtpFechaProduccion.Date := LFechaEntrega - 1
      else
        dtpFechaProduccion.Date := Date;
    end
    else
    begin
      dtpFechaProduccion.Date := Date;
      dtpFechaEntrega.Date := Date + 1;
    end;

    // Observaciones iniciales
    if Trim(LQry.FieldByName('observaciones').AsString) <> '' then
      edtObservacionesCab.Text := Format('Pedido %s: %s', [
        LNumPedido, Trim(LQry.FieldByName('observaciones').AsString)
      ])
    else if LClienteNombre <> '' then
      edtObservacionesCab.Text := Format('Pedido %s - Cliente: %s', [LNumPedido, LClienteNombre])
    else
      edtObservacionesCab.Text := 'Pedido: ' + LNumPedido;

    // Importar líneas del pedido a mtLineas (cargando primero líneas no asignadas a otras órdenes)
    mtLineas.Close;
    mtLineas.Open;

    LQryLines := TFDQuery.Create(nil);
    try
      LQryLines.Connection := dmgMain.dbConn;
      // 1. Intentar cargar sólo las líneas pendientes de asignar a una orden de producción
      LQryLines.SQL.Text :=
        'SELECT pl.id, pl.id_articulo, pl.descripcion, pl.cantidad, a.descripcion AS art_nombre ' +
        'FROM ge_pedidos_lineas pl ' +
        'LEFT JOIN ge_articulos a ON pl.id_articulo = a.id ' +
        'WHERE pl.id_pedido = :id_pedido ' +
        '  AND pl.id NOT IN ( ' +
        '    SELECT opl.id_pedido_linea ' +
        '    FROM ge_ordenes_produccion_lineas opl ' +
        '    JOIN ge_ordenes_produccion op ON op.id = opl.id_orden ' +
        '    WHERE opl.id_pedido_linea IS NOT NULL ' +
        '  ) ' +
        'ORDER BY pl.id ASC';
      LQryLines.ParamByName('id_pedido').AsInteger := APedidoId;
      LQryLines.Open;

      // 2. Si todas las líneas ya están asignadas, cargar todas las líneas del pedido como respaldo
      if LQryLines.IsEmpty then
      begin
        LQryLines.Close;
        LQryLines.SQL.Text :=
          'SELECT pl.id, pl.id_articulo, pl.descripcion, pl.cantidad, a.descripcion AS art_nombre ' +
          'FROM ge_pedidos_lineas pl ' +
          'LEFT JOIN ge_articulos a ON pl.id_articulo = a.id ' +
          'WHERE pl.id_pedido = :id_pedido ' +
          'ORDER BY pl.id ASC';
        LQryLines.ParamByName('id_pedido').AsInteger := APedidoId;
        LQryLines.Open;
      end;

      while not LQryLines.Eof do
      begin
        LArtId := LQryLines.FieldByName('id_articulo').AsInteger;
        mtLineas.Append;
        mtLineas.FieldByName('id').AsInteger := 0;
        mtLineas.FieldByName('id_articulo').AsInteger := LArtId;
        LDesc := Trim(LQryLines.FieldByName('descripcion').AsString);
        if LDesc = '' then
          LDesc := Trim(LQryLines.FieldByName('art_nombre').AsString);
        mtLineas.FieldByName('articulo_nombre').AsString := LDesc;
        mtLineas.FieldByName('cantidad').AsFloat := LQryLines.FieldByName('cantidad').AsFloat;
        mtLineas.FieldByName('cantidad_fabricada').AsFloat := 0.0;
        mtLineas.FieldByName('id_pedido_linea').AsInteger := LQryLines.FieldByName('id').AsInteger;
        mtLineas.FieldByName('pedido_linea_info').AsString := Format('Lín. #%d', [LQryLines.FieldByName('id').AsInteger]);
        mtLineas.FieldByName('observaciones').AsString := 'Pedido ' + LNumPedido;
        mtLineas.FieldByName('estado').AsString := 'PENDIENTE';
        mtLineas.Post;
        LQryLines.Next;
      end;
    finally
      LQryLines.Free;
    end;
  finally
    LQry.Free;
  end;
  ActualizarBannerPedido;
end;

procedure TfrmOrdenProduccionEditor.cbbPedidoChange(Sender: TObject);
begin
  if cbbPedido.ItemIndex > 0 then
    FPedidoId := Integer(IntPtr(cbbPedido.Items.Objects[cbbPedido.ItemIndex]))
  else
    FPedidoId := 0;
  ActualizarBannerPedido;
end;

procedure TfrmOrdenProduccionEditor.btnVerPedidoClick(Sender: TObject);
begin
  if FPedidoId <= 0 then Exit;
  if not Assigned(frmPedidosEditor) then
    Application.CreateForm(TfrmPedidosEditor, frmPedidosEditor);
  frmPedidosEditor.ParentForm := Self;
  frmPedidosEditor.PedidoId := IntToStr(FPedidoId);
  frmPedidosEditor.ShowModal;
end;

procedure TfrmOrdenProduccionEditor.ActualizarBannerPedido;
var
  LQry: TFDQuery;
  LNumPed, LCliente, LFechaEnt: string;
begin
  if FPedidoId > 0 then
  begin
    pnlVinculoPedido.Color := $00F5F9FF;
    btnVerPedido.Visible := True;

    LQry := TFDQuery.Create(nil);
    try
      LQry.Connection := dmgMain.dbConn;
      LQry.SQL.Text :=
        'SELECT p.numero_pedido, p.fecha_entrega, ' +
        '       COALESCE(NULLIF(c.nombre_comercial, ''''), c.nombre_fiscal, '''') AS cliente ' +
        'FROM ge_pedidos p ' +
        'LEFT JOIN ge_clientes c ON p.cliente_id = c.id ' +
        'WHERE p.id = :id';
      LQry.ParamByName('id').AsInteger := FPedidoId;
      LQry.Open;
      if not LQry.IsEmpty then
      begin
        LNumPed := LQry.FieldByName('numero_pedido').AsString;
        LCliente := LQry.FieldByName('cliente').AsString;
        if not LQry.FieldByName('fecha_entrega').IsNull then
          LFechaEnt := ' | Entrega: ' + FormatDateTime('dd/MM/yyyy', LQry.FieldByName('fecha_entrega').AsDateTime)
        else
          LFechaEnt := '';
        lblVinculoPedido.Caption := Format('🔗 Pedido Vinculado: Nº %s — %s%s', [LNumPed, LCliente, LFechaEnt]);
        lblVinculoPedido.Font.Color := $00994D00;
      end
      else
      begin
        lblVinculoPedido.Caption := Format('🔗 Pedido Vinculado ID #%d', [FPedidoId]);
        lblVinculoPedido.Font.Color := $00994D00;
      end;
    finally
      LQry.Free;
    end;
  end
  else
  begin
    pnlVinculoPedido.Color := $00F3F4F6;
    lblVinculoPedido.Caption := 'ℹ️ Orden de producción manual sin pedido de cliente asociado.';
    lblVinculoPedido.Font.Color := $006B7280;
    btnVerPedido.Visible := False;
  end;
end;

function TfrmOrdenProduccionEditor.IsNewRecord: Boolean;
begin
  Result := (FOrdenId <= 0);
end;

function TfrmOrdenProduccionEditor.HasUnsavedChanges: Boolean;
begin
  Result := FLineasModificadas or inherited HasUnsavedChanges;
end;

procedure TfrmOrdenProduccionEditor.UpdateActionButtonsState;
begin
  if Assigned(btnGuardar) then
    btnGuardar.Enabled := True;
  if Assigned(btnEliminar) then
  begin
    btnEliminar.Visible := True;
    btnEliminar.Enabled := (FOrdenId > 0);
  end;
  if Assigned(btnSalir) then
    btnSalir.Enabled := True;
  if Assigned(btnEliminaArticulo) then
    btnEliminaArticulo.Enabled := not mtLineas.IsEmpty;
end;

procedure TfrmOrdenProduccionEditor.btnGuardarClick(Sender: TObject);
var
  LQry: TFDQuery;
  LEmpresaId, LObradorId, LPedidoId: Integer;
  LNumOrden: string;
begin
  if Trim(edtNumeroOrden.Text) = '' then
  begin
    ShowMessage('El número de orden es obligatorio.');
    edtNumeroOrden.SetFocus;
    Exit;
  end;

  if cbbObrador.ItemIndex < 0 then
  begin
    ShowMessage('Debe seleccionar un obrador responsable.');
    cbbObrador.SetFocus;
    Exit;
  end;

  if mtLineas.IsEmpty then
  begin
    ShowMessage('Debe añadir al menos un artículo a producir en la orden.');
    Exit;
  end;

  if FEmpresaId > 0 then
    LEmpresaId := FEmpresaId
  else
    LEmpresaId := StrToIntDef(dmgMain.CurrentCompanyId, 1);

  LObradorId := Integer(IntPtr(cbbObrador.Items.Objects[cbbObrador.ItemIndex]));
  if cbbPedido.ItemIndex > 0 then
    LPedidoId := Integer(IntPtr(cbbPedido.Items.Objects[cbbPedido.ItemIndex]))
  else if FPedidoId > 0 then
    LPedidoId := FPedidoId
  else
    LPedidoId := 0;

  LNumOrden := Trim(edtNumeroOrden.Text);

  LQry := TFDQuery.Create(nil);
  try
    LQry.Connection := dmgMain.dbConn;
    dmgMain.dbConn.StartTransaction;
    try
      if FOrdenId = 0 then
      begin
        // INSERT Cabecera
        LQry.SQL.Text :=
          'INSERT INTO ge_ordenes_produccion (' +
          '  empresa_id, numero_orden, fecha_orden, fecha_produccion, fecha_entrega_prevista, ' +
          '  id_pedido, id_obrador, estado, prioridad, observaciones, id_user_creator' +
          ') VALUES (' +
          '  :empresa_id, :numero_orden, :fecha_orden, :fecha_produccion, :fecha_entrega_prevista, ' +
          '  :id_pedido, :id_obrador, :estado, :prioridad, :observaciones, :id_user_creator' +
          ')';
        LQry.ParamByName('empresa_id').AsInteger := LEmpresaId;
        LQry.ParamByName('numero_orden').AsString := LNumOrden;
        LQry.ParamByName('fecha_orden').AsDate := Date;
        LQry.ParamByName('fecha_produccion').AsDate := dtpFechaProduccion.Date;
        LQry.ParamByName('fecha_entrega_prevista').AsDate := dtpFechaEntrega.Date;
        if LPedidoId > 0 then
          LQry.ParamByName('id_pedido').AsInteger := LPedidoId
        else
          LQry.ParamByName('id_pedido').Clear;
        LQry.ParamByName('id_obrador').AsInteger := LObradorId;
        LQry.ParamByName('estado').AsString := cbbEstado.Text;
        LQry.ParamByName('prioridad').AsString := cbbPrioridad.Text;
        LQry.ParamByName('observaciones').AsString := Trim(edtObservacionesCab.Text);
        LQry.ParamByName('id_user_creator').AsInteger := StrToIntDef(dmgMain.CurrentUserId, 1);
        LQry.ExecSQL;

        // Obtener ID generado
        LQry.Close;
        LQry.SQL.Text := 'SELECT LAST_INSERT_ID() AS new_id';
        LQry.Open;
        FOrdenId := LQry.FieldByName('new_id').AsInteger;
      end
      else
      begin
        // UPDATE Cabecera
        LQry.SQL.Text :=
          'UPDATE ge_ordenes_produccion SET ' +
          '  numero_orden = :numero_orden, ' +
          '  fecha_produccion = :fecha_produccion, ' +
          '  fecha_entrega_prevista = :fecha_entrega_prevista, ' +
          '  id_pedido = :id_pedido, ' +
          '  id_obrador = :id_obrador, ' +
          '  estado = :estado, ' +
          '  prioridad = :prioridad, ' +
          '  observaciones = :observaciones, ' +
          '  id_user_update = :id_user_update ' +
          'WHERE id = :id';
        LQry.ParamByName('id').AsInteger := FOrdenId;
        LQry.ParamByName('numero_orden').AsString := LNumOrden;
        LQry.ParamByName('fecha_produccion').AsDate := dtpFechaProduccion.Date;
        LQry.ParamByName('fecha_entrega_prevista').AsDate := dtpFechaEntrega.Date;
        if LPedidoId > 0 then
          LQry.ParamByName('id_pedido').AsInteger := LPedidoId
        else
          LQry.ParamByName('id_pedido').Clear;
        LQry.ParamByName('id_obrador').AsInteger := LObradorId;
        LQry.ParamByName('estado').AsString := cbbEstado.Text;
        LQry.ParamByName('prioridad').AsString := cbbPrioridad.Text;
        LQry.ParamByName('observaciones').AsString := Trim(edtObservacionesCab.Text);
        LQry.ParamByName('id_user_update').AsInteger := StrToIntDef(dmgMain.CurrentUserId, 1);
        LQry.ExecSQL;

        // Limpiar líneas anteriores para sincronizar
        LQry.Close;
        LQry.SQL.Text := 'DELETE FROM ge_ordenes_produccion_lineas WHERE id_orden = :id_orden';
        LQry.ParamByName('id_orden').AsInteger := FOrdenId;
        LQry.ExecSQL;
      end;

      // Inserción de líneas
      mtLineas.First;
      while not mtLineas.Eof do
      begin
        LQry.Close;
        LQry.SQL.Text :=
          'INSERT INTO ge_ordenes_produccion_lineas (' +
          '  id_orden, id_articulo, cantidad, cantidad_fabricada, id_pedido_linea, ' +
          '  observaciones, estado' +
          ') VALUES (' +
          '  :id_orden, :id_articulo, :cantidad, :cantidad_fabricada, :id_pedido_linea, ' +
          '  :observaciones, :estado' +
          ')';
        LQry.ParamByName('id_orden').AsInteger := FOrdenId;
        LQry.ParamByName('id_articulo').AsInteger := mtLineas.FieldByName('id_articulo').AsInteger;
        LQry.ParamByName('cantidad').AsFloat := mtLineas.FieldByName('cantidad').AsFloat;
        LQry.ParamByName('cantidad_fabricada').AsFloat := mtLineas.FieldByName('cantidad_fabricada').AsFloat;
        if mtLineas.FieldByName('id_pedido_linea').AsInteger > 0 then
          LQry.ParamByName('id_pedido_linea').AsInteger := mtLineas.FieldByName('id_pedido_linea').AsInteger
        else
          LQry.ParamByName('id_pedido_linea').Clear;
        LQry.ParamByName('observaciones').AsString := mtLineas.FieldByName('observaciones').AsString;
        LQry.ParamByName('estado').AsString := mtLineas.FieldByName('estado').AsString;
        LQry.ExecSQL;

        mtLineas.Next;
      end;

      dmgMain.dbConn.Commit;
      FLineasModificadas := False;
      ShowMessage('Orden de producción guardada correctamente.');
      ModalResult := mrOk;
    except
      on E: Exception do
      begin
        dmgMain.dbConn.Rollback;
        ShowMessage('Error al guardar la orden de producción: ' + E.Message);
      end;
    end;
  finally
    LQry.Free;
  end;
end;

procedure TfrmOrdenProduccionEditor.btnSalirClick(Sender: TObject);
begin
  if FLineasModificadas then
    ModalResult := mrOk
  else
    ModalResult := mrCancel;
end;

procedure TfrmOrdenProduccionEditor.btnEliminarClick(Sender: TObject);
var
  LQry: TFDQuery;
  LNumOrden: string;
  LPedidoIdToUpdate: Integer;
  LCountRestantes: Integer;
begin
  if FOrdenId <= 0 then
  begin
    ShowMessage('Esta orden de producción aún no ha sido guardada en la base de datos.');
    Exit;
  end;

  LNumOrden := Trim(edtNumeroOrden.Text);
  if LNumOrden = '' then
    LNumOrden := Format('ID #%d', [FOrdenId]);

  if MessageDlg(
    Format('¿Está seguro de que desea eliminar la orden de producción "%s"?' + sLineBreak + sLineBreak +
           'Esta acción borrará la orden y desvinculará sus artículos planificados.', [LNumOrden]),
    mtConfirmation, [mbYes, mbNo], 0) <> mrYes then
    Exit;

  LPedidoIdToUpdate := FPedidoId;

  LQry := TFDQuery.Create(nil);
  try
    LQry.Connection := dmgMain.dbConn;
    dmgMain.dbConn.StartTransaction;
    try
      // 1. Eliminar líneas de la orden
      LQry.SQL.Text := 'DELETE FROM ge_ordenes_produccion_lineas WHERE id_orden = :id_orden';
      LQry.ParamByName('id_orden').AsInteger := FOrdenId;
      LQry.ExecSQL;

      // 2. Eliminar cabecera de la orden
      LQry.Close;
      LQry.SQL.Text := 'DELETE FROM ge_ordenes_produccion WHERE id = :id';
      LQry.ParamByName('id').AsInteger := FOrdenId;
      LQry.ExecSQL;

      // 3. Si estaba vinculada a un pedido, comprobar si quedan otras órdenes de producción para ese pedido
      if LPedidoIdToUpdate > 0 then
      begin
        LQry.Close;
        LQry.SQL.Text := 'SELECT COUNT(*) AS total FROM ge_ordenes_produccion WHERE id_pedido = :id_pedido';
        LQry.ParamByName('id_pedido').AsInteger := LPedidoIdToUpdate;
        LQry.Open;
        LCountRestantes := LQry.FieldByName('total').AsInteger;

        // Si ya no quedan órdenes de producción para este pedido y su estado es 'EN PREPARACION', devolverlo a 'PENDIENTE'
        if LCountRestantes = 0 then
        begin
          LQry.Close;
          LQry.SQL.Text :=
            'UPDATE ge_pedidos SET estado = ''PENDIENTE'' ' +
            'WHERE id = :id_pedido AND estado = ''EN PREPARACION''';
          LQry.ParamByName('id_pedido').AsInteger := LPedidoIdToUpdate;
          LQry.ExecSQL;
        end;
      end;

      dmgMain.dbConn.Commit;
      ShowMessage(Format('La orden de producción "%s" ha sido eliminada correctamente.', [LNumOrden]));
      ModalResult := mrOk;
    except
      on E: Exception do
      begin
        dmgMain.dbConn.Rollback;
        ShowMessage('Error al eliminar la orden de producción: ' + E.Message);
      end;
    end;
  finally
    LQry.Free;
  end;
end;

procedure TfrmOrdenProduccionEditor.dbgItemsDrawColumnCell(Sender: TObject;
  const Rect: TRect; DataCol: Integer; Column: TColumn; State: TGridDrawState);
var
  LIdPedLin: Integer;
  LBadgeRect: TRect;
  LText: string;
begin
  inherited;

  if (Column <> nil) and (Column.FieldName = 'pedido_linea_info') then
  begin
    if Assigned(dbgItems.DataSource) and Assigned(dbgItems.DataSource.DataSet) and
       dbgItems.DataSource.DataSet.Active and not dbgItems.DataSource.DataSet.IsEmpty then
    begin
      LIdPedLin := dbgItems.DataSource.DataSet.FieldByName('id_pedido_linea').AsInteger;
      if gdSelected in State then
        dbgItems.Canvas.Brush.Color := clHighlight
      else
        dbgItems.Canvas.Brush.Color := clWhite;
      dbgItems.Canvas.FillRect(Rect);

      LBadgeRect := Rect;
      InflateRect(LBadgeRect, -3, -2);

      if LIdPedLin > 0 then
      begin
        dbgItems.Canvas.Brush.Color := $00FDF2E9;
        dbgItems.Canvas.Pen.Color := $00C06000;
        dbgItems.Canvas.RoundRect(LBadgeRect.Left, LBadgeRect.Top, LBadgeRect.Right, LBadgeRect.Bottom, 4, 4);
        dbgItems.Canvas.Font.Color := $00994D00;
        dbgItems.Canvas.Font.Style := [fsBold];
        LText := Format('Lín. #%d', [LIdPedLin]);
      end
      else
      begin
        dbgItems.Canvas.Font.Color := clGray;
        dbgItems.Canvas.Font.Style := [];
        LText := '—';
      end;

      DrawText(dbgItems.Canvas.Handle, PChar(LText), Length(LText), LBadgeRect,
               DT_CENTER or DT_VCENTER or DT_SINGLELINE);
    end;
  end;
end;

end.
