Conectividad de SAP / VB
Obtener una tabla y cargar un combo

Fecha: 30/Nov/2004 (20/11/2004)
Autor: Luis Elías Carro - [email protected]  

Primera Parte: Introducción (18/Nov/04)
Segunda Parte: (este)
Tercera Parte:
Insertar un cliente en SAP desde una aplicación VB (04/Dic)

 


En una publicación anterior ( Conectividad de SAP / Visual Basic - Introducción ) intento dar una explicacion sencilla de cómo conectar Visual Basic con SAP mediante DCOM Connector.
Para dar un paso más y continuando con DCOM, esta nota pretende dar una idea acerca de la obtención de una tabla desde SAP para cargar una grilla y una orientacion para poder cargar un combo con una lista obtenida de una tabla especifica de SAP.

Deberías leer la publicación anterior para conocer acerca de los parámetros que se usan así sobre como hacer la conexión con SAP, como crear la librería DLL y como hacer la referencia desde Visual Basic.

Función en ABAP.
Desde una función RFC (Remote Function Call: funciones que pueden invocarse desde sistemas externos a SAP enviando y recibiendo valores), SAP devuelve una tabla con tres campos de la tabla clientes (KNA1 es el nombre de la tabla de clientes de SAP) y esta se lee desde Visual Basic. La función recibe dos parametros: desde y hasta que cliente quiero ver.

FUNCTION ylu_dcom_ej01.
*"----------------------------------------------------------------------
*"  Interfase local
*"  IMPORTING
*"     VALUE(I_KUNNR_DESDE) TYPE  KUNNR
*"     VALUE(I_KUNNR_HASTA) TYPE  KUNNR
*"  TABLES
*"      T_KNA1 STRUCTURE  YLU_DCOM_KNA1
*"----------------------------------------------------------------------

  TABLES: kna1.

  SELECT kunnr name1 name2
    FROM kna1 INTO CORRESPONDING FIELDS OF TABLE t_kna1
    WHERE kunnr GE i_kunnr_desde AND kunnr LE i_kunnr_hasta.


ENDFUNCTION.

La tabla que devuelve la función se llama T_KNA1 y se basa en la estructura YLU_DCOM_KNA1 que se creó especialmente para el caso con tres campos de la tabla de clientes. En SAP, para guardar en una tabla auxiliar datos provenientes de una tabla del sistema necesito respetar la estructura que estoy leyendo y se hace necesario crear un objeto (que se llama estructura) basado en los campos de la tabla que será leida, en este caso KNA1.
Puede verse que se indica como tipo de dato de los parámetros “KUNNR”, en realidad, es el nombre del elemento de datos que le corresponde al campo de la tabla KNA1 que vamos a filtrar (qué es un “elemento de datos” en SAP es para otra nota). Esta indicación no solo hace referencia al tipo de dato, sino a todas las características y propiedades que tiene el campo y automáticamente me permite buscar en una lista.

Form y código Visual Basic.

La grilla que usé para el ejemplo es la “Formula One”, no es la más común pero es muy fácil de usar y el código muy intuitivo, de manera que creo que es muy fácil adaptar el ejemplo a cualquier otra grilla. De todas formas cada línea de código de la grilla está comentada.
Notarán que el ingreso en las cajas de texto no puede ser menor a 10 caracteres. Y esto es por el tipo de dato que se usó para los parámetros de la función en SAP. “KUNNR” es char de 10 y de recibir una longitud menor no funcionará correctamente.

Diseño de form para cargar la grilla.

Option Explicit

Private Sesion  As DESej01Lib.SessionComponent
Private Funcion As DESej01Lib.Ylu

Private Sub Form_Load()

    On Error GoTo errSAP
    
    Set Sesion = New DESej01Lib.SessionComponent
    Sesion.Destination = "DES"	' servidor
    Sesion.UserID = "usuario"
    Sesion.Password = "password"
    Sesion.Client = "100"	' mandante
    
    Exit Sub
    
errSAP:
    MsgBox Err.Number & " " & Err.Description
    
End Sub

Private Sub Command1_Click()
Dim rs     As ADODB.Recordset
Dim iLinea As Integer

    On Error GoTo errCarga
    
    ' el "lenght" de los campos parámetros debe ser 10.
    If Len(Text1(0).Text) < 10 Then
        Text1(0).Text = String(10 - Len(Text1(0).Text), "0") & Text1(0).Text
    End If
    
    If Len(Text1(1).Text) < 10 Then
        Text1(1).Text = String(10 - Len(Text1(1).Text), "0") & Text1(1).Text
    End If
        
    With Grilla
        .DeleteRange -1, -1, -1, -1, F1ShiftVertical
        .MaxRow = 1
    End With
    
    Set Funcion = Sesion.CreateInstance("DES.YLU")
    
    ' este DimAs "dimensiona" (es decir que le da formato) 
    ' al recordset rs como la tabla de retorno KNA_1
    Funcion.DimAs "YLU_DCOM_EJ01", "T_KNA1", rs
    ' esta es la llamada
    Funcion.Ylu_Dcom_Ej01 Text1(0).Text, Text1(1).Text, rs
    
    ' cargo mi grilla con los datos que obtuve.
    ' si tuviera otro tipo de grilla, incluso una dbgrid, 
    ' podría conectarla al recordset.
    With rs
        Do While Not .EOF
            iLinea = iLinea + 1
            Grilla.MaxRow = iLinea
            Grilla.EntryRC(iLinea, 1) = .Fields!kunnr
            Grilla.EntryRC(iLinea, 2) = .Fields!name1
            Grilla.EntryRC(iLinea, 3) = .Fields!name2
            .MoveNext
        Loop
        .Close
    End With
    
    Set rs = Nothing
    
    Exit Sub
    
errCarga:
    MsgBox Err.Number, Err.Description
    Set rs = Nothing
    
End Sub

Con las versiones de DCOM Connector anteriores a la 6.20 hay un problema al intentar crear un proyecto C++ de este tipo (con una tabla como parámetro de retorno de la función). DCOM Connector devuelve un error y no genera el proyecto de C++ correctamente sino que define mal la estructura de la tabla de retorno. Esto solo puede verse abriendo con C++ el proyecto generado.
Para todo lo que implique lectura o escritura de tablas o ejecución de RFC's o BAPI´s (Business Programming Application Interfaces: funciones provistas por SAP para emular una transacción por medio de una llamada con parámetros) que usan como parámetro tablas, es necesario el uso de ADO. Si hay que enviar información solo declaro un recordset y la función correspondiente de SAP le da la estructura.

Carga de combos.

Cargar un combo con datos que provienen de SAP se usará, generalmente, para completar una interfaz mediante la que se enviarán datos a SAP.
La única manera de insertar datos en SAP es usando transacciones (así se llaman ciertos métodos definidos por SAP para completar una determinada tarea, por ejemplo, para insertar un artículo hay que correr la transacción "MM01").
La BAPI HelpValues de SAP es un objeto por el podemos invocar la función BAPI_HELPVALUES_GET que expone el método GetList. Éste método devuelve una lista de códigos y valores, permitidos por SAP, de una tabla determinada como para cargar un combo.
Debo indicarle varios parametros, y me devolverá varias tablas, una tiene la lista para el combo, pero en una string, es decir, código y descripción concatenados; otra tabla tiene la estructura de esta string (es decir, cuantos campos, cuantos caracteres para el código y cuantos para la descripción); otra tabla es una lista solo con los códigos y en otra tabla, que es de entrada y no de salida, puedo indicar criterios de selección para filtrar registros.

DCOM: creación del proyecto para carga de combos

El uso de esta BAPI para la carga de un combo es específico del entorno en que me encuentro. Es decir que si estoy cargando, por ejemplo, un pedido de ventas y necesito cargar un combo con países debo indicar la lista de países del tipo de dato para el campo que guarda este valor en un pedido de ventas. Por lo tanto no es posible tomar la tabla de paises y meterla en la lista por que la interfaz no entrará en SAP.
En este caso en particular, los pedidos se cargan con el método CreateFromDat2 (función BAPI_SALESORDER_CREATEFROMDAT2). Ubicando esta BAPI en explorador de BAPI's puedo identificar los parametros que debo enviar a la función que cargará el combo:

OBJTYPE Es el tipo de objeto indicado en el grupo de objetos en el que esta el método, en este caso BUS2032.
OBJNAME Es el nombre del grupo de objetos en el que esta el método, en este caso SalesOrder
METHOD Es el método que usara el dato obtenido, aquí CreateFromDat2
PARAMETER Es el nombre del parámetro del método CreateFromDat2 que usará el dato. En este caso, OrderPartners.
FIELD Es el nombre del campo de diccionario donde se usara el dato obtenido, llego a él, a través de la referencia de diccionario de PARAMETER. Aquí es COUNTRY.
EXPLICIT_SHLP No se completa, gracias a Dios.
MAX_OF_ROWS Número de resultados máximo que quiero obtener.
DESCRIPTIONONLY Devolver solo la descripción si se envía una 'X' o ''.
SELECTION_FOR_HELPVALUES Tabla de entrada indicando un filtro particular para la carga del combo.
HELPVALUES Tabla de salida con una cadena concatenando código y descripción buscados.
VALUES_FOR_FIELD Tabla de salida solo con códigos.
DESCRIPTION_FOR_HELPVALUES Tabla de salida con estructura de registro de HELPVALUES.

SAP: Explorador BAPI donde podemos ver las características del método que SAP expone.

Cuando se declaran los recorsets se debe usar el método DimAs, al que enviaremos como parámetro el nombre de la tabla de la que necesitas la estructura, (se debe enviar el nombre del parámetro que desde la BAPI devuelve la tabla). Es decir: una tabla de las que devuelve la función se llama SELECTION_FOR_HELPVALUES, el parámetro de la BAPI que te devuelve la tabla se llama Selection4HelpValues, pues bien, para declarar tu recordset al DimAs le tenés que indicar "Selection4HelpValues" que es el nombre del parámetro y no el nombre de la tabla.

Form y código Visual Basic.

diseño de form para carga de combo


Option Explicit

Private Sesion  As DESCargaCombosLib.CargaCombosSessionComponent
Private Funcion As DESCargaCombosLib.Helpvalues

Private Sub Form_Load()
    
    On Error GoTo errSAP
    
    Set Sesion = New DESCargaCombosLib.CargaCombosSessionComponent
    Sesion.Destination = "DES"
    Sesion.UserID = "usuario"
    Sesion.Password = "password"
    Sesion.Client = "100"
    
    CargarCombo
    
    Exit Sub
    
errSAP:
    MsgBox Err.Number & " " & Err.Description

End Sub

Private Sub CargarCombo()
' en las variables enviare los parametros
Dim stOBJTYPE         As String
Dim stOBJNAME         As String
Dim stMETHOD          As String
Dim stPARAMETER       As String
Dim stFIELD           As String
Dim stEXPLICIT_SHLP   As String
Dim stMAX_OF_ROWS     As String
Dim stDESCRIPTIONONLY As String

Dim iCodigo      As Integer
Dim iDescripcion As Integer

Dim iPaso As Integer

Dim rsSELECTION_FOR_HELPVALUES   As ADODB.Recordset
Dim rsHELPVALUES                 As ADODB.Recordset
Dim rsVALUES_FOR_FIELD           As ADODB.Recordset
Dim rsDESCRIPTION_FOR_HELPVALUES As ADODB.Recordset

    On Error GoTo errCarga
                
    Set Funcion = Sesion.CreateInstance("DES.HELPVALUES")

    ' formato a los recordsets
    Funcion.DimAs "BapiGetList", _
        "SELECTION4HELPVALUES", rsSELECTION_FOR_HELPVALUES
    Funcion.DimAs "BapiGetList", "HELPVALUES", rsHELPVALUES
    Funcion.DimAs "BapiGetList", "VALUES4FIELD", rsVALUES_FOR_FIELD
    Funcion.DimAs "BapiGetList", _
        "DESCRIPTION4HV", rsDESCRIPTION_FOR_HELPVALUES
   
    stOBJTYPE = "BUS2032"
    stOBJNAME = "SALESORDER"
    stMETHOD = "CREATEFROMDAT2"
    stPARAMETER = "ORDERPARTNERS"
    stFIELD = "COUNTRY"
    ' EXPLICIT_SHLP
    stMAX_OF_ROWS = "0"
    stDESCRIPTIONONLY = ""
    
    Funcion.BapiGetList stMETHOD, stPARAMETER, stOBJTYPE, stOBJNAME, _
        stFIELD, stMAX_OF_ROWS, , , rsHELPVALUES, rsVALUES_FOR_FIELD, _
        rsDESCRIPTION_FOR_HELPVALUES
       
    ' la descripcion del registros esta en el recordset
    ' rsDESCRIPTION_FOR_HELPVALUES, un campo en cada registro.
    ' asi que en el primero leo el lenght del código y despues
    ' la descripción, si hubiera mas campos, seguiría.
    With rsDESCRIPTION_FOR_HELPVALUES
        Do While Not .EOF
            If iPaso = 0 Then
                iCodigo = .Fields!Leng
            Else
                iDescripcion = .Fields!Leng
            End If
            iPaso = iPaso + 1
            .MoveNext
        Loop
    End With
    
    ' luego extraigo de la string en el recordset rsHELPVALUES
    ' la parte que me interesa.
    Combo1.Clear
    With rsHELPVALUES
        Do While Not .EOF
            Combo1.AddItem Mid$(.Fields(0).Value, iCodigo + 1)
            Combo1.ItemData(Combo1.NewIndex) = Val(Left$(.Fields(0).Value, iCodigo))
            .MoveNext
        Loop
    End With
    
    Set rsSELECTION_FOR_HELPVALUES = Nothing
    Set rsHELPVALUES = Nothing
    Set rsVALUES_FOR_FIELD = Nothing
    Set rsDESCRIPTION_FOR_HELPVALUES = Nothing
    
    Exit Sub
    
errCarga:
    MsgBox Err.Number, Err.Description
    Set rsSELECTION_FOR_HELPVALUES = Nothing
    Set rsHELPVALUES = Nothing
    Set rsVALUES_FOR_FIELD = Nothing
    Set rsDESCRIPTION_FOR_HELPVALUES = Nothing

End Sub


Private Sub Form_Unload(Cancel As Integer)

    Set Sesion = Nothing
    Set Funcion = Nothing

End Sub

Hemos visto que para cargar un combo necesito dimensionar cuatro recorsets y luego una función de SAP le da la estructura. Uno de estos recorsets trae código y descripción concatenados (HelpValues) en una sola cadena y otro la estructura de esta cadena (DescriptionForHelpValues). Esta última tendrá un registro por cada campo en la cadena, en la cadena los campos están separados por un espacio, es decir que si pido una lista de países recibiré una cadena, por ejemplo, así: "AR··Argentina" en HelpValues y dos registros en DescriptionForHelpValues que entre sus campos informa largo, nombre y otras características de los campos de la cadena. Recorriendo DescriptionForHelpValues, el campo Leng vale 3 y 20, pero si mido la string veo que el espacio entre el código y la descripción no se cuenta y de los 20 de la descripción me manda los que encuentra, sin blancos al final.

 


ir al índice