unit uEtiquetaBultoECIService;

interface

uses
  System.SysUtils, System.Classes, System.Variants, System.UITypes, Vcl.Dialogs,
  Data.DB, FireDAC.Comp.Client, FireDAC.Stan.Param,
  frxClass, frxDBSet, frxBarcode;

type
  TEtiquetaBultoECIService = class
  public
    /// <summary>
    /// Imprime o muestra la vista previa de las etiquetas de bulto ECI para un albarán dado
    /// </summary>
    class procedure ImprimirEtiquetasBultoAlbaran(AOwner: TComponent; const AAlbaranId: string; APreview: Boolean = True);

    /// <summary>
    /// Asegura que existan bultos registrados en ge_albaranes_bultos con sus códigos SSCC-18
    /// </summary>
    class function AsegurarBultosAlbaran(const AAlbaranId: string; AConn: TFDConnection; ANumBultosSugeridos: Integer = 0): Integer;

    /// <summary>
    /// Genera un nuevo código SSCC-18 para la empresa usando el prefijo configurado
    /// </summary>
    class function GenerarNuevoSSCC18(AConn: TFDConnection; const AEmpGln: string): string;

    /// <summary>
    /// Formatea el número de pedido de cliente a exactamente 8 dígitos numéricos
    /// </summary>
    class function FormatearPedido8(const APedidoRaw: string): string;
  end;

implementation

uses
  dmg_Main, uValidacionFiscal, uFormatosImpresionService, uEdicomDesadvExporter;

{ TEtiquetaBultoECIService }

class function TEtiquetaBultoECIService.FormatearPedido8(const APedidoRaw: string): string;
var
  I: Integer;
  LDigits: string;
begin
  LDigits := '';
  for I := 1 to Length(APedidoRaw) do
    if CharInSet(APedidoRaw[I], ['0'..'9']) then
      LDigits := LDigits + APedidoRaw[I];

  if Length(LDigits) = 0 then
    LDigits := '00000000'
  else if Length(LDigits) > 8 then
    LDigits := Copy(LDigits, Length(LDigits) - 7, 8)
  else
    while Length(LDigits) < 8 do
      LDigits := '0' + LDigits;

  Result := LDigits;
end;

class function TEtiquetaBultoECIService.GenerarNuevoSSCC18(AConn: TFDConnection; const AEmpGln: string): string;
var
  QrySscc: TFDQuery;
  LPrefix: string;
  LNextSscc: Int64;
begin
  LPrefix := Copy(AEmpGln, 1, 9);
  if Length(LPrefix) < 7 then
    LPrefix := '843701918'; // Prefijo GS1 oficial Raúl Asencio

  QrySscc := TFDQuery.Create(nil);
  try
    QrySscc.Connection := AConn;
    try
      QrySscc.SQL.Text := 'SELECT sscc FROM ge_contadores LIMIT 1';
      QrySscc.Open;
      if QrySscc.IsEmpty then
      begin
        LNextSscc := 1;
        QrySscc.Close;
        QrySscc.SQL.Text := 'INSERT INTO ge_contadores (id, sscc) VALUES (1, 2)';
        QrySscc.ExecSQL;
      end
      else
      begin
        LNextSscc := QrySscc.FieldByName('sscc').AsLargeInt;
        QrySscc.Close;
        QrySscc.SQL.Text := 'UPDATE ge_contadores SET sscc = sscc + 1';
        QrySscc.ExecSQL;
      end;

      Result := TValidacionFiscal.GenerarSSCC18(LPrefix, LNextSscc, 3);
    except
      Result := TValidacionFiscal.GenerarSSCC18(LPrefix, 1, 3);
    end;
  finally
    QrySscc.Free;
  end;
end;

class function TEtiquetaBultoECIService.AsegurarBultosAlbaran(
  const AAlbaranId: string; AConn: TFDConnection; ANumBultosSugeridos: Integer = 0): Integer;
var
  QryCheck, QryIns, QryEmp: TFDQuery;
  LCount, LNeededBultos, I: Integer;
  LEmpGln, LSscc, LFirstSscc: string;
begin
  Result := 0;
  if Trim(AAlbaranId) = '' then Exit;

  QryCheck := TFDQuery.Create(nil);
  QryIns := TFDQuery.Create(nil);
  QryEmp := TFDQuery.Create(nil);
  try
    QryCheck.Connection := AConn;
    QryIns.Connection := AConn;
    QryEmp.Connection := AConn;

    // Obtener GLN emisor de la empresa del albarán
    QryEmp.SQL.Text :=
      'SELECT e.gln AS emp_gln, ed.gln_empresa AS edicom_gln ' +
      'FROM ge_albaranes a ' +
      'LEFT JOIN ge_empresas e ON a.empresa_id = e.id ' +
      'LEFT JOIN ge_edicom_config ed ON ed.id_empresa = e.id AND ed.activo = 1 ' +
      'WHERE a.id = :id';
    QryEmp.ParamByName('id').AsString := AAlbaranId;
    QryEmp.Open;
    LEmpGln := Trim(QryEmp.FieldByName('edicom_gln').AsString);
    if LEmpGln = '' then
      LEmpGln := Trim(QryEmp.FieldByName('emp_gln').AsString);

    // Comprobar bultos existentes (cajas o unidades de expedición)
    QryCheck.SQL.Text :=
      'SELECT id, numero_bulto, sscc FROM ge_albaranes_bultos ' +
      'WHERE albaran_id = :alb ORDER BY numero_bulto ASC, id ASC';
    QryCheck.ParamByName('alb').AsString := AAlbaranId;
    QryCheck.Open;
    LCount := QryCheck.RecordCount;

    if LCount = 0 then
    begin
      // Calcular número de bultos necesario
      if ANumBultosSugeridos > 0 then
        LNeededBultos := ANumBultosSugeridos
      else
      begin
        // Sumar bultos de las líneas
        QryCheck.Close;
        QryCheck.SQL.Text :=
          'SELECT COALESCE(SUM(bultos), 0) AS total_bultos ' +
          'FROM ge_albaranes_lineas WHERE albaran_id = :alb';
        QryCheck.ParamByName('alb').AsString := AAlbaranId;
        QryCheck.Open;
        LNeededBultos := Trunc(QryCheck.FieldByName('total_bultos').AsFloat);
        if LNeededBultos <= 0 then
          LNeededBultos := 1;
      end;

      LFirstSscc := '';
      for I := 1 to LNeededBultos do
      begin
        LSscc := GenerarNuevoSSCC18(AConn, LEmpGln);
        if (I = 1) and (LFirstSscc = '') then
          LFirstSscc := LSscc;

        QryIns.SQL.Text :=
          'INSERT INTO ge_albaranes_bultos (albaran_id, numero_bulto, bulto_padre_id, nivel, tipo_embalaje, sscc, id_user_creator) ' +
          'VALUES (:alb, :num, NULL, 3, ''CT'', :sscc, :user)';
        QryIns.ParamByName('alb').AsString := AAlbaranId;
        QryIns.ParamByName('num').AsInteger := I;
        QryIns.ParamByName('sscc').AsString := LSscc;
        QryIns.ParamByName('user').AsString := dmgMain.CurrentUserId;
        QryIns.ExecSQL;
      end;

      // Actualizar SSCC en cabecera de albarán si estaba vacío
      if LFirstSscc <> '' then
      begin
        QryIns.SQL.Text :=
          'UPDATE ge_albaranes SET edi_numero_sscc = :sscc ' +
          'WHERE id = :alb AND (edi_numero_sscc IS NULL OR edi_numero_sscc = '''')';
        QryIns.ParamByName('sscc').AsString := LFirstSscc;
        QryIns.ParamByName('alb').AsString := AAlbaranId;
        QryIns.ExecSQL;
      end;

      Result := LNeededBultos;
    end
    else
    begin
      // Si ya existen bultos, asegurar que todos tengan SSCC asignado
      while not QryCheck.Eof do
      begin
        LSscc := Trim(QryCheck.FieldByName('sscc').AsString);
        if (Length(LSscc) <> 18) or (StrToInt64Def(LSscc, 0) <= 0) then
        begin
          LSscc := GenerarNuevoSSCC18(AConn, LEmpGln);
          QryIns.SQL.Text := 'UPDATE ge_albaranes_bultos SET sscc = :sscc WHERE id = :id';
          QryIns.ParamByName('sscc').AsString := LSscc;
          QryIns.ParamByName('id').AsInteger := QryCheck.FieldByName('id').AsInteger;
          QryIns.ExecSQL;
        end;
        QryCheck.Next;
      end;
      Result := LCount;
    end;
  finally
    QryCheck.Free;
    QryIns.Free;
    QryEmp.Free;
  end;
end;

class procedure TEtiquetaBultoECIService.ImprimirEtiquetasBultoAlbaran(
  AOwner: TComponent; const AAlbaranId: string; APreview: Boolean = True);
var
  LReportPath: string;
  QryCab, QryBultos: TFDQuery;
  MemData: TFDMemTable;
  FrxDB: TfrxDBDataset;
  FrxReport: TfrxReport;
  LTotalBultos, LBultoNum: Integer;
  LSerie, LNumero, LAlbNum7, LPedido8, LSucursal4, LDept3, LProvincia, LCp, LDestDesc, LDestDir, LDestPob: string;
  LEmpNom, LEmpDir, LEmpCp, LEmpPob, LEmpProv, LFullEmpDir: string;
  LIsValdemoro: Boolean;
  LLugar1, LLugar2, LLugar3: string;
  LDest1, LDest2, LDest3: string;
  LSsccVal, LIa91, LIa98, LIa00: string;
begin
  if Trim(AAlbaranId) = '' then
  begin
    MessageDlg('Debe seleccionar un albarán válido.', mtWarning, [mbOK], 0);
    Exit;
  end;

  // 1. Localizar plantilla FastReport
  LReportPath := TFormatosImpresionService.ResolverRutaArchivo('EtiquetaBultoECI.fr3');
  if (LReportPath = '') or not FileExists(LReportPath) then
  begin
    MessageDlg('No se ha encontrado la plantilla de etiqueta de bulto ECI en:' + sLineBreak +
      LReportPath, mtError, [mbOK], 0);
    Exit;
  end;

  // 2. Asegurar que existen bultos registrados para este albarán
  LTotalBultos := AsegurarBultosAlbaran(AAlbaranId, dmgMain.dbConn);
  if LTotalBultos <= 0 then
  begin
    MessageDlg('No se pudieron generar o recuperar los bultos del albarán.', mtError, [mbOK], 0);
    Exit;
  end;

  // 3. Consultar datos de cabecera y destino del albarán
  QryCab := TFDQuery.Create(nil);
  QryBultos := TFDQuery.Create(nil);
  MemData := TFDMemTable.Create(nil);
  FrxDB := TfrxDBDataset.Create(nil);
  FrxReport := TfrxReport.Create(nil);
  try
    QryCab.Connection := dmgMain.dbConn;
    QryBultos.Connection := dmgMain.dbConn;

    QryCab.SQL.Text :=
      'SELECT a.id, a.serie, a.numero, a.fecha, a.empresa_id, a.cliente_id, a.clientedestino_id, ' +
      '       a.edi_referencia_pedido_cliente, ' +
      '       COALESCE(p1.numero_pedido, p2.numero_pedido, '''') AS numero_pedido, ' +
      '       COALESCE(c.nombre_fiscal, ''EL CORTE INGLES S.A.'') AS cliente_nombre, ' +
      '       COALESCE(d.descripcion, '''') AS destino_desc, ' +
      '       COALESCE(d.direccion, '''') AS destino_dir, ' +
      '       COALESCE(d.poblacion, '''') AS destino_pob, ' +
      '       COALESCE(d.cp, '''') AS destino_cp, ' +
      '       COALESCE(d.provincia, '''') AS destino_provincia, ' +
      '       COALESCE(d.centro, '''') AS destino_centro, ' +
      '       COALESCE(d.departamento, '''') AS destino_departamento, ' +
      '       COALESCE(d.ean_receptor, '''') AS destino_ean_receptor, ' +
      '       e.nombre AS empresa_nombre, e.direccion AS empresa_dir, e.poblacion AS empresa_pob, ' +
      '       e.cp AS empresa_cp, e.provincia AS empresa_provincia ' +
      'FROM ge_albaranes a ' +
      'LEFT JOIN ge_clientes c ON a.cliente_id = c.id ' +
      'LEFT JOIN ge_clientes_destinos d ON a.clientedestino_id = d.id ' +
      'LEFT JOIN ge_pedidos p1 ON a.id_pedido = p1.id ' +
      'LEFT JOIN ge_envios_recogidas r ON r.albaran_id = a.id AND r.estado_codigo <> 5 ' +
      'LEFT JOIN ge_pedidos p2 ON r.pedido_id = p2.id ' +
      'LEFT JOIN ge_empresas e ON a.empresa_id = e.id ' +
      'WHERE a.id = :id';
    QryCab.ParamByName('id').AsString := AAlbaranId;
    QryCab.Open;

    if QryCab.IsEmpty then
    begin
      MessageDlg('No se ha encontrado el albarán en la base de datos.', mtError, [mbOK], 0);
      Exit;
    end;

    // Datos de la empresa emisora
    LEmpNom := Trim(QryCab.FieldByName('empresa_nombre').AsString);
    if LEmpNom = '' then
      LEmpNom := 'RAÚL ASENCIO PASTELERIAS S.L.';

    LEmpDir := Trim(QryCab.FieldByName('empresa_dir').AsString);
    LEmpCp := Trim(QryCab.FieldByName('empresa_cp').AsString);
    LEmpPob := Trim(QryCab.FieldByName('empresa_pob').AsString);
    LEmpProv := Trim(QryCab.FieldByName('empresa_provincia').AsString);
    if LEmpDir <> '' then
    begin
      LFullEmpDir := LEmpDir;
      if LEmpCp <> '' then
        LFullEmpDir := LFullEmpDir + ' - ' + LEmpCp;
      if LEmpPob <> '' then
        LFullEmpDir := LFullEmpDir + ' ' + LEmpPob;
      if (LEmpProv <> '') and not SameText(LEmpProv, LEmpPob) then
        LFullEmpDir := LFullEmpDir + ' (' + LEmpProv + ')';
    end
    else
      LFullEmpDir := 'Avda. Alcalde Carlos Carbonell, 130 - 03680 Aspe (Alicante)';

    // Formatear Albarán (7 dígitos numéricos)
    LSerie := QryCab.FieldByName('serie').AsString;
    LNumero := QryCab.FieldByName('numero').AsString;
    LAlbNum7 := FormatDesadvAlbaranNumber(LSerie, LNumero);

    // Formatear Pedido (8 dígitos numéricos)
    var LRawPedido := Trim(QryCab.FieldByName('edi_referencia_pedido_cliente').AsString);
    if LRawPedido = '' then
      LRawPedido := Trim(QryCab.FieldByName('numero_pedido').AsString);
    if LRawPedido = '' then
      LRawPedido := LAlbNum7;
    LPedido8 := FormatearPedido8(LRawPedido);

    // Sucursal destino (4 dígitos)
    LSucursal4 := Trim(QryCab.FieldByName('destino_centro').AsString);
    if LSucursal4 = '' then
    begin
      // Intentar extraer de ean_receptor (los 4 dígitos previos al dígito de control)
      var LGln := Trim(QryCab.FieldByName('destino_ean_receptor').AsString);
      if Length(LGln) = 13 then
        LSucursal4 := Copy(LGln, 9, 4);
    end;
    if Length(LSucursal4) < 4 then
      LSucursal4 := Format('%.4d', [StrToIntDef(LSucursal4, 0)]);

    // Departamento (3 dígitos)
    LDept3 := Trim(QryCab.FieldByName('destino_departamento').AsString);
    if Length(LDept3) < 3 then
      LDept3 := Format('%.3d', [StrToIntDef(LDept3, 0)]);

    // Destino Mercancía
    LDestDesc := Trim(QryCab.FieldByName('destino_desc').AsString);
    LDestDir := Trim(QryCab.FieldByName('destino_dir').AsString);
    LDestPob := Trim(QryCab.FieldByName('destino_pob').AsString);
    LProvincia := Trim(QryCab.FieldByName('destino_provincia').AsString);
    LCp := Trim(QryCab.FieldByName('destino_cp').AsString);

    LDest1 := 'EL CORTE INGLÉS, S.A.';
    LDest2 := 'C.C. ' + LDestDesc;
    if LDestPob <> '' then
      LDest3 := LDestDir + ' (' + LDestPob + ')'
    else
      LDest3 := LDestDir;

    // Lugar de Entrega:
    // "Seguirá entregando directo a tienda y en Valdemoro para pedidos de Canarias y suc 090."
    LIsValdemoro := (SameText(LProvincia, 'LAS PALMAS')) or
                    (SameText(LProvincia, 'SANTA CRUZ DE TENERIFE')) or
                    (Pos('35', LCp) = 1) or
                    (Pos('38', LCp) = 1) or
                    (LSucursal4 = '0090') or (LSucursal4 = '0900') or
                    (SameText(LDestDesc, 'VALDEMORO'));

    if LIsValdemoro then
    begin
      LLugar1 := 'EL CORTE INGLÉS, S.A.';
      LLugar2 := 'ALMACÉN DE VALDEMORO';
      LLugar3 := 'Carretera de Andalucía, Km. 22.300. (MADRID)';
    end
    else
    begin
      LLugar1 := LDest1;
      LLugar2 := LDest2;
      LLugar3 := LDest3;
    end;

    // Códigos de barras IA 91 y 98
    // IA 91: (91) + 2 dígitos empresa ('01' ECI) + 4 dígitos sucursal
    LIa91 := '(91)01' + LSucursal4;
    // IA 98: (98) + 3d dpto + 2d punto venta ('00') + 8d pedido + 7d albaran
    LIa98 := '(98)' + LDept3 + '00' + LPedido8 + LAlbNum7;

    // Configurar tabla en memoria con los registros por bulto
    MemData.FieldDefs.Clear;
    MemData.FieldDefs.Add('proveedor_nombre', ftString, 100);
    MemData.FieldDefs.Add('proveedor_operador', ftString, 100);
    MemData.FieldDefs.Add('proveedor_dir', ftString, 150);
    MemData.FieldDefs.Add('lugar_entrega_linea1', ftString, 100);
    MemData.FieldDefs.Add('lugar_entrega_linea2', ftString, 100);
    MemData.FieldDefs.Add('lugar_entrega_linea3', ftString, 150);
    MemData.FieldDefs.Add('destino_linea1', ftString, 100);
    MemData.FieldDefs.Add('destino_linea2', ftString, 100);
    MemData.FieldDefs.Add('destino_linea3', ftString, 150);
    MemData.FieldDefs.Add('sucursal_codigo', ftString, 10);
    MemData.FieldDefs.Add('barcode_ia91', ftString, 30);
    MemData.FieldDefs.Add('dpto_pventa', ftString, 20);
    MemData.FieldDefs.Add('pedido_num', ftString, 20);
    MemData.FieldDefs.Add('albaran_num', ftString, 20);
    MemData.FieldDefs.Add('barcode_ia98', ftString, 50);
    MemData.FieldDefs.Add('bulto_texto', ftString, 30);
    MemData.FieldDefs.Add('bulto_num', ftInteger);
    MemData.FieldDefs.Add('total_bultos', ftInteger);
    MemData.FieldDefs.Add('sscc', ftString, 30);
    MemData.FieldDefs.Add('barcode_ia00', ftString, 50);
    MemData.CreateDataSet;

    // Cargar bultos de ge_albaranes_bultos
    QryBultos.SQL.Text :=
      'SELECT id, numero_bulto, sscc FROM ge_albaranes_bultos ' +
      'WHERE albaran_id = :alb ORDER BY numero_bulto ASC, id ASC';
    QryBultos.ParamByName('alb').AsString := AAlbaranId;
    QryBultos.Open;

    LTotalBultos := QryBultos.RecordCount;
    LBultoNum := 0;

    while not QryBultos.Eof do
    begin
      Inc(LBultoNum);
      LSsccVal := Trim(QryBultos.FieldByName('sscc').AsString);
      LIa00 := '(00)' + LSsccVal;

      MemData.Append;
      MemData.FieldByName('proveedor_nombre').AsString := LEmpNom;
      MemData.FieldByName('proveedor_operador').AsString := '';
      MemData.FieldByName('proveedor_dir').AsString := LFullEmpDir;
      MemData.FieldByName('lugar_entrega_linea1').AsString := LLugar1;
      MemData.FieldByName('lugar_entrega_linea2').AsString := LLugar2;
      MemData.FieldByName('lugar_entrega_linea3').AsString := LLugar3;
      MemData.FieldByName('destino_linea1').AsString := LDest1;
      MemData.FieldByName('destino_linea2').AsString := LDest2;
      MemData.FieldByName('destino_linea3').AsString := LDest3;
      MemData.FieldByName('sucursal_codigo').AsString := LSucursal4;
      MemData.FieldByName('barcode_ia91').AsString := LIa91;
      MemData.FieldByName('dpto_pventa').AsString := LDept3 + ' - 00';
      MemData.FieldByName('pedido_num').AsString := LPedido8;
      MemData.FieldByName('albaran_num').AsString := LAlbNum7;
      MemData.FieldByName('barcode_ia98').AsString := LIa98;
      MemData.FieldByName('bulto_texto').AsString := Format('%d / %d', [LBultoNum, LTotalBultos]);
      MemData.FieldByName('bulto_num').AsInteger := LBultoNum;
      MemData.FieldByName('total_bultos').AsInteger := LTotalBultos;
      MemData.FieldByName('sscc').AsString := LSsccVal;
      MemData.FieldByName('barcode_ia00').AsString := LIa00;
      MemData.Post;

      QryBultos.Next;
    end;

    // Vincular al dataset de FastReport
    if Assigned(dmgMain) and Assigned(dmgMain.frxInforme) and Assigned(dmgMain.frxDBMaster) then
    begin
      dmgMain.frxDBMaster.DataSet := MemData;
      dmgMain.frxDBMaster.UserName := 'frxDBmaster';

      dmgMain.frxInforme.Clear;
      dmgMain.frxInforme.LoadFromFile(LReportPath);
      dmgMain.frxInforme.DataSets.Clear;
      dmgMain.frxInforme.DataSets.Add(dmgMain.frxDBMaster);
      dmgMain.frxInforme.EnabledDataSets.Clear;
      dmgMain.frxInforme.EnabledDataSets.Add(dmgMain.frxDBMaster);

      var LBand := dmgMain.frxInforme.FindObject('MasterData1');
      if (LBand <> nil) and (LBand is TfrxMasterData) then
        TfrxMasterData(LBand).DataSet := dmgMain.frxDBMaster;

      dmgMain.frxInforme.PreviewOptions.Modal := True;
      if APreview then
        dmgMain.frxInforme.ShowReport(True)
      else
        dmgMain.frxInforme.Print;
    end
    else
    begin
      FrxDB.DataSet := MemData;
      FrxDB.UserName := 'frxDBmaster';
      FrxDB.Name := 'frxDBMaster';

      FrxReport.Clear;
      FrxReport.LoadFromFile(LReportPath);
      FrxReport.DataSets.Clear;
      FrxReport.DataSets.Add(FrxDB);
      FrxReport.EnabledDataSets.Clear;
      FrxReport.EnabledDataSets.Add(FrxDB);

      var LBand := FrxReport.FindObject('MasterData1');
      if (LBand <> nil) and (LBand is TfrxMasterData) then
        TfrxMasterData(LBand).DataSet := FrxDB;

      FrxReport.PreviewOptions.Modal := True;
      if APreview then
        FrxReport.ShowReport(True)
      else
        FrxReport.Print;
    end;

  finally
    QryCab.Free;
    QryBultos.Free;
    MemData.Free;
    FrxDB.Free;
    FrxReport.Free;
  end;
end;

end.
