OData y SAP Gateway – III – Recuperar Subentidades

En la entrada anterior de esta serie de artículos sobre OData ‘OData y SAP Gateway – II – Publicar un servicio OData en SAP‘ creamos un servicio muy básico de consulta de Business Partners. Ahora toca añadir más funcionalidad a ese servicio que creamos en el anterior artículo. Antes de nada, vamos a recapitular.

Serie de Artículos sobre OData

Este artículo pertenece a una serie de artículos que se van complementando poco a poco como itinerario de conocimiento:


Modelo de datos

Recuperamos cómo era el modelo de datos del servicio que queríamos publicar.

Como podemos ver en el diagrama Entidad-Relación tenemos una Entidad padre «Business Partner» que tiene cuatro entidades relacionadas:

  • Roles del Business Partner
  • Direcciones del Business Partner
  • Relaciones del Business Partner
  • Números de Identificación del Business Partner

Además, este ejemplo lo hemos hecho sencillo, con un solo nivel de relación, por tener un escenario controlado y que esto no se convierta en algo inmanejable de explicar pero, como podreis imaginar, las entidades subordinadas pueden tener otras subentidades a su vez.


Proyecto SAP Gateway creado

Además creamos el siguiente proyecto de Gateway con las entidades del modelo de datos y las relaciones/asociaciones.


Associations Creadas

Vimos en la entrada «OData y SAP Gateway – II – Publicar un servicio OData en SAP» como creábamos Associations o relaciones entre entidades, y el resultado es el siguiente:

Donde vemos que al crear las asociaciones se crean tambien Association Sets y Navigation Properties desde la entidad Padre a las hijas.


Clase de Implementación

En la clase de implementación que crea el proyecto de Gateway ZCL_ZODATA_TEST_BP_DPC_EXT redefinimos solo un método, el GET_ENTITYSET de la entidad BUSINESSPARTNERS.

Pero claro, ahora estamos hablando de relaciones entre entidades, y queremos saber cómo implementar consultas de Business Partners que nos traigan las subentidades de cada BP (Roles, Direcciones, Relaciones con otros BPs y Números de Identificación).

Para ello nos va a valer la Query simple que implementamos, pero queremos que, cuando llamemos a la consulta, nos devuelva los BPs y sus entidades relacionadas.


Uso del $expand en la consulta

Para poder consultar las subentidades de cada entidad en una consulta, usaremos en la URL de llamada al servicio la sentencia $expand. Podemos ver la documentación oficial OData al respecto de la sentencia $expand. Por ejemplo si hacemos una búsqueda de Business Partners y, para cada uno de ellos queremos saber sus roles, realizaremos la consulta de este modo. Para ello ponemos en el Gateway Client (/IWFND/GW_CLIENT).

Error Method ROLESSET_GET_ENTITYSET not implemented

Pero nos da ese error, y esto es porque no hemos implementado el método del GET_ENTITYSET (dame la colección de datos) de la entidad ROLES.


Implementar el GET_ENTITYSET

Para implementar el GET_ENTITYSET nos iremos a nuestra clase Z*DPC_EXT y buscaremos el GET_ENTITYSET de nuestra entidad que queremos consultar. Y redefinimos el método.

Pero claro, una vez redefinido, tenemos que implementar la lógica al respecto de esa acción (GET_ENTITYSET) de esa entidad (ROLES). Vemos que en la firma del método tenemos muchos parámetros de entrada y un par de salida. Son los siguientes:

Parámetros de Entrada

  • IV_ENTITY_NAME: Nombre de la entidad que se va a manejar en el método. Normalmente, cuando hablamos de métodos propios de una entidad concreta, como es el caso, será irrelevante. Pero en otras ocasiones se trata de métodos genéricos, donde tenemos que cambiar la lógica de la acción propuesta en base a este método.
  • IV_ENTITY_SET_NAME: Lo mismo que el anterior pero con el EntitySet.
  • IV_SOURCE_NAME: Nos dirá si partimos de una entidad superior o no y cual es.
  • IT_FILTER_SELECT_OPTIONS: Tabla con los campos de selección que se hayan usado para la búsqueda del EntitySet mediante la sentencia $filter. En este caso si hemos añadido algún criterio de selección para los Roles. Puedes ver la documentación oficial OData sobre el FILTER.
  • IS_PAGING: Nos indica el TOP y SKIP para la consulta, esto sirve para acotar el número de resultados y poder paginar. Por ejemplo, si tenemos 1000 registros pero ponemos un TOP de 100 y un SKIP de 500, recuperaremos del 501 al 600. Puedes ver la documentación oficial OData sobre el TOP y SKIP.
  • IT_KEY_TAB: Si hemos accedido a la entidad por una clave, la suya o la de una entidad superior, en el campo KEY TAB vendrán las claves por las que se accede al EntitySet.
  • IT_NAVIGATION_PATH: Tabla que contiene la ruta de navegación entre entidades.
  • IT_ORDER: Tabla con los criterios de ordenación deseados para recuperar los datos de la colección del EntitySet. Puedes ver la documentación oficial OData sobre ORDERBY.
  • IV_FILTER_STRING: El string de búsqueda con los campos y los valores buscados. Es una forma distinta de manejar los criterios de búsqueda de la tabla IT_FILTER_SELECT_OPTIONS.
  • IV_SEARCH_STRING: Permite realizar búsquedas de texto completo en los datos de una entidad. Este parámetro permite indicar una cadena de búsqueda que se aplicará a varios campos de la entidad, haciendo más fácil encontrar registros que contengan el texto indicado en cualquier parte de esos campos.
  • IO_TECH_REQUEST_CONTEXT: Este parámetro proporciona información técnica sobre la solicitud actual. Contiene detalles relevantes del contexto técnico de la llamada al servicio OData, permitiendo acceder a datos importantes para el procesamiento de la solicitud. Desde este parámetro podemos recuperar mucha información:
    • Usuario llamante
    • Cabecera HTTP
    • Parámetros de la URL
    • IP del cliente
    • Parámetros de la Query

Parámetros de Salida

  • ET_ENTITYSET: Tabla de datos de salida de la consulta para devolver el conjunto de entidades (EntitySet) solicitado. Es una tabla interna, con el tipo de datos de la entidada, que contiene los datos que se devuelven al cliente como resultado de la consulta. Por lo tanto, es la tabla resultado que tenemos que rellenar con los datos buscados una vez aplicados los criterios de entrada.
  • ES_RESPONSE_CONTEXT: Es una estructura de salida de la consulta OData, contiene metadatos sobre la respuesta, como el número de registros devueltos, información de paginación, y cualquier mensaje de error o advertencia.

Implementación de ROLESET_GET_ENTITYSET

Bueno, mucha explicación de lo que son todos los parámetros que se manejan en el GET_ENTITYSET pero no mostramos cómo hacerlo en la práctica. Como es imposible abarcarlo todo en un artículo, los ejemplos de implementación serán sencillos, para complicarlo está la definición de los parámetros del GET_ENTITYSET.

Lo primero que vamos a hacer es ver qué nos llega en la entrada cuando usamos un $expand desde la entidad padre. Para ello, lo que queremos es parar la ejecución en ese método, lo que yo hago es poner una sentencia absurda para poder poner un Breakpoint externo.

Si alguna vez se cumple esto sal corriendo

Al ejecutar de nuevo la llamada en el Gateway Client (/IWFND/GW_CLIENT)

/sap/opu/odata/sap/ZODATA_TEST_BP_SRV/BusinessPartnerSet?$expand=RolesSet

Se abre la sesión debug en el breakpoint externo y podemos ver los datos que nos vienen:

Ahora podemos ver la definición de cada campo que hemos hecho arriba para entenderlo. Vemos que la entidad buscada (IV_ENTITY_NAME) es «Roles» que el origen (IV_SOURCE_NAME) es «BusinessPartner», que no tiene criterios de búsqueda añadidos, ni paginación, ni orden,que la tabla IT_NAVIGATION_PATH tiene un registro con la navigation properties RolesSet desde BusinessPartner, y tiene una clave en la tabla IT_KEY_TAB.

Ahí tenemos nuestro Business Partner padre

Ahora, teniendo el campo Partner en la tabla IT_KEY_TAB vamos a implementar la lógica para rellenar el RolesSet.

Este código se ejecutar una vez por cada Partner servido

Si ahora ejecutamos nuestra sentencia ya veremos datos de los roles de cada uno de los registros de Business Partners.

Implementar GET_ENTITYSET del resto de entidades

Una vez tengamos implementado el GET_ENTITYSET de las subentidades nuestra clase Z*DPC_EXT quedará de este modo:

Y para el que le interese los GET_ENTITYSET quedarán de esta forma:

AddressSet
IdentNumbersSet
RelationSet

Seleccionar las entidades a recuperar

Pero claro, tenemos cuatro subentidades subordinadas de BusinessPartner. ¿Cómo tenemos que hacerlo? Para hacerlo pondremos en la sentencia $expand las subentidades que queramos recuperar, separadas por coma. En nuestro ejemplo la sentencia total sería:

/sap/opu/odata/sap/ZODATA_TEST_BP_SRV/BusinessPartnerSet?$expand=RolesSet,IdentNumbersSet,AddressSet,RelationSet

El resultado será que, para cada Business Partner, tendremos su colección de Roles, Números de Identificación, Direcciones y Relaciones. Por supuesto, para cada subentidad, tendremos que implementar su GET_ENTITYSET correspondiente.

Lo importante de esto es que podemos elegir qué subentidades recuperar, haciendo la recuperación de datos selectiva y dependiente del llamante.


Si te interesa, suscríbete al blog por email

Errores en SAP – DUMPs

Hoy vamos a hablar de eso que aparece en tu vida de vez en cuando, que tu no quieres que pase, pero que es inevitable, al principio te enfada y te genera frustración, pero luego comprendes que puede ayudarte conocer y saber lo que pasa para no cometer errores mayores. No, no vamos a hablar de diarrea, bueno, un poco sí. Hoy vamos a hablar de los DUMPs y errores en SAP.


DUMPs

Lo primero que tenemos es que definir qué es un DUMP en SAP. Los DUMPs son los registros que se generan cuando un programa ABAP se ejecuta y algo sale mal que no puede ser manejado por el programa. Es un punto de ruptura o fuga de un programa ABAP en SAP.

Normalmente se tratan de errores generados por código de cliente (Z), normalmente. Pueden generarse en código estándar también, pero esto supondría una corrección por parte de SAP y una actualización del sistema vía support packages o notas. Y todos sabemos que todos los sistemas SAP están siempre, siempre, siempre actualizados ¿no? (no).

Mi cara cuando llego a un proyecto y pregunto por el nivel de parches

Sea como fuere, cuando una parte del código genera un error no controlado, se interrumpe y muestra un DUMP. Y si el programa se está ejecutando en fondo, se interrumpe y genera el DUMP igualmente pero está vez no hay usuario para verlo en directo claro.


Transacción ST22

La herramienta con la que contamos para buscar, identificar y analizar DUMPs es la ST22. Cuando accedemos a la ST22 lo primero que veremos será una pantalla de búsqueda como esta:

Donde tenemos, como importante:

  • Botón de Hoy: Nos permite acceder a la lista de Dumps de hoy en el sistema donde estamos logados, sea cual sea el usuario que generó el error. A la derecha del botón nos dará un contador del total de registros que coinciden.
  • Botón de Ayer: Nos permite acceder a la lista de Dumps de ayer en el sistema donde estamos logados, sea cual sea el usuario que generó el error. A la derecha del botón nos dará un contador del total de registros que coinciden.
  • Bloque de Selección propia: Nos permite buscar DUMPs por otros criterios que los simples «Hoy» y «Ayer». Por defecto vendrá con el usuario y el día de hoy, pero se puede cambiar. En mi experiencia yo solo lo he usado para buscar por otras fechas que no sean «Hoy» y «Ayer». También para filtrar por un usuario concreto.

Una vez ejecutada cualquiera de las opciones veremos la lista de DUMPs que concuerde con los criterios de selección ejecutados.


Listado de DUMPs

Donde como datos interesantes tenemos:

  • Fecha y Hora: Cuando se produjo el DUMP.
  • Usuario: Usuario de ejecución del programa que generó el DUMP
  • Conservar: Este campo refleja si un DUMP concreto ha sido marcado para ser guardado y no ser borrado en el job diario de limpieza de histórico. Para marcar como guardado o liberado debemos marcar un DUMP y pulsar el icono del candado de arriba.
  • Error tiempo ejecución ABAP: Es el tipo de DUMP que se ha generado. Luego vemos los tipos más comunes y su significado.
  • Programa: Lugar del código que ha generado el DUMP.

Si hacemos doble click encima de cualquier linea o le damos al primer icono de la botonera veremos el detalle de ese DUMP.


Vista de detalle del DUMP

Donde veremos que tenemos mucha información. Vamos a intentar desgranarlo porque esto es la parte importante de este artículo. No voy a explicar cada uno de los apartados, porque algunos de ellos no sé interpretarlos ni nunca me han hecho falta. Vamos a hacerlo de forma práctica en base a mi experiencia. Por lo tanto a continuación desgranaremos aquellos que creo fundamentales para saber identificar el error que subyace del DUMP.


Cabecera del Dump

Donde te da los datos básicos de cabecera del DUMP. Donde los más importante son:

  • Err.tmpo.ejec: El tipo de DUMP que estamos viendo
  • Programa ABAP: El programa que generó el DUMP
  • Fecha y hora: El momento en el que se generó.,

Texto Breve

Nos da un resumen muy somero del error ocurrido. Arriba he puesto varios ejemplos de distintos tipos de DUMPs. Nos da cierta información que puede ser importante, como por ejemplo la excepción no controlada que genera el DUMP o el motivo básico del error.


¿Qué ha sucedido?

Es una explicación un poco más extensa, siendo también muy resumida. Puede darnos algo de información dependiendo del tipo de DUMP.


¿Qué puede hacer?

En general este bloque no suele dar información muy valiosa, en algunos casos, como en el DUMP RFC_NO_AUTHORITY de arriba nos da algo más de los permisos que faltan por otorgar.


Análisis de Errores

Empieza la fiesta, este bloque ya puede contener información muy valiosa del error que ha sucedido. Cada tipo de DUMP contará con una información explicativa que puede ayudarnos a saber qué ocurrió.


Notas para corregir errores

Este bloque nos ayuda a buscar notas en el SAP Support Portal o para abrir una incidencia a SAP.


Info posición de cancelación

Este apartado nos da la posición del código donde se ha producido el error. Siendo importante esta información, en el siguiente punto tenemos la misma información pero con más funcionalidad,.


Detalle código fuente (Texto fuente modificado)

Ojo, que estamos en uno de los apartados fundamentales para saber interpretar un DUMP. Nos dice el punto exacto, con su posición dentro del código. Además, y más importante, si hacemos click entraremos dentro del código en cuestión. Con esto podemos poner un break point y volver a probar el proceso que da error para ver el motivo en debugging.,  Tambien llegamos al código pulsando el icono Editor ABAP de la barra de herramientas superior.


Llamadas/Eventos activos

Apartado también fundamental. Nos dice la pila de llamadas que se han ido haciendo para hasta llegar al código que nos da error. Esto es importante porque, en ocasiones, el origen del error no está en el código que ha fallado, sino que es consecuencia de algo que ha pasado antes. Por ejemplo, si no le pasamos una referencia a un objeto instanciada a un método subordinado, posiblemente el código dentro de ese método de error, pero el origen es anterior. Otro ejemplo, si no manejamos la excepción que devuelve un código subordinado, la excepción la da ese código, pero si ponemos un TRY-CATCH en el inmediatamente superior podemos arreglarlo.


Resto de apartados

El resto de apartados de un DUMP:

  • Entorno sistema 
  • Usuario y transacción 
  • Serverseitige Verbindungsinformationen
  • Contenido campos sistema
  • Variables seleccionadas
  • Llamadas de aplicación
  • Lista de programas ABAP implicados 
  • Notas internas
  • Llamadas activas núcleo SAP
  • Directorio de tablas de aplicación
  • Bloq.control ABAP (CONT) (Texto fuente modificado) 

No los considero fundamentales para la investigación y descubrir el motivo del error. Seguro que alguno habrá importante, pero yo no los uso.


Recepción de emails sobre DUMPs

Ahora bien, una vez visto como buscar e interpretar un DUMPs, tenemos claro que si estamos ejecutando un proceso y nos da un error podemos saber donde ir a verlo y cómo interpretarlo. Pero ¿si el DUMP se genera en un proceso en fondo? ¿Si es en producción por un usuario del sistema? ¿Cómo podemos enterarnos de que ha sucedido? ¿Tenemos que estar entrando todos los días varias veces a comprobarlo o esperar la incidencia?. No, para eso tenemos la posibilidad de que el sistema nos mande mails de los DUMPs.

Para eso tenemos tres opciones por un lado podemos crear un job en fondo sobre el report RSSHOWRABAX con una variante del día anterior.

Job Sobre el report RSSHOWRABAX

Luego programamos un job sobre este report con la variante que hemos creado.

Le damos al botón Destino Listas SPOOL e introducimos nuestra dirección de correo.

Elegimos la periodicidad

Y al ejecutar veremos el mail saliente en la SOST

Nota 176492 – Automatic email when an alert occurs (RZ20)

Podemos ver en la nota de SAP «176492 – Automatic email when an alert occurs (RZ20)» la forma de, usando la RZ20 y el CCMS cada vez que se produzca un DUMP recibir un mail de alerta. Pero el procedimiento de alta de esta alerta es realmente complejo, con acceso al mandante 000 incluso. No lo veo muy claro.

También te lo explican en foros de la SAP como:


Report Z de consulta y envío de DUMPs

Hay otra opción, más rudimentaria pero más controlada, es crearnos nuestro propio Report de consulta, recopilación y envío de Dumps vía email. Para ello tendremos que hacer nuestra selección de datos en la tabla que almacena los DUMPs, la SNAP. A continuación os pongo un ejemplo desarrollado por mi, esto pasado a un JOB diario nos da una idea de los DUMPs ocurridos el día, también se puede hacer algo más inmediato programandolo cada hora o incluso como un demonio en el sistema.

&---------------------------------------------------------------------*
*& Report ZJOB_SEND_DUMPS
*&---------------------------------------------------------------------*
*&
*& Autor: Jorge Ocampos
*& Descripción: Program that collects the Dumps of the system at a given
*&              time by the selection and sends an e-mail with the summary
*&---------------------------------------------------------------------*
REPORT zjob_send_dumps.

*&---------------------------------------------------------------------*
*                T A B L E S
*&---------------------------------------------------------------------*
TABLES: somlreci1.

*&---------------------------------------------------------------------*
*                D A T A   T Y P E S
*&---------------------------------------------------------------------*
TYPES: BEGIN OF ty_text_dump,
         id    TYPE c LENGTH 2,
         ll    TYPE c LENGTH 3,
         errid TYPE snapt-errid,
       END OF ty_text_dump.

TYPES: BEGIN OF ty_data,
         datum TYPE string,
         uzeit TYPE string,
         uname TYPE snap-uname,
         errid TYPE snapt-errid,
       END OF ty_data.


*&---------------------------------------------------------------------*
*               V A R I A B L E S
*&---------------------------------------------------------------------*
DATA: ls_text_dump TYPE ty_text_dump,
      gt_data      TYPE TABLE OF ty_data,
      gt_objtxt    TYPE STANDARD TABLE OF solisti1,
      gt_objpack   TYPE STANDARD TABLE OF sopcklsti1,
      gs_docdata   TYPE sodocchgi1,
      gt_objhead   TYPE STANDARD TABLE OF solisti1.


*&---------------------------------------------------------------------*
*                S E L E C T I O N - S C R E E N
*&---------------------------------------------------------------------*
SELECTION-SCREEN BEGIN OF BLOCK b1 WITH FRAME TITLE TEXT-001.

  SELECT-OPTIONS: s_datum FOR sy-datum.

SELECTION-SCREEN END OF BLOCK b1.


SELECTION-SCREEN BEGIN OF BLOCK b2 WITH FRAME TITLE TEXT-004.

  SELECT-OPTIONS: s_mail FOR somlreci1-receiver NO INTERVALS DEFAULT 'tu mail' OBLIGATORY.

SELECTION-SCREEN END OF BLOCK b2.



*&---------------------------------------------------------------------*
*                S T A R T - O F - S E L E C T I O N
*&---------------------------------------------------------------------*
START-OF-SELECTION.

  PERFORM get_data.

  IF gt_data IS NOT INITIAL.

    PERFORM create_message.

    PERFORM send_message.
  ELSE.
    WRITE:/ 'NO DUMPs in selection!!'.
  ENDIF.

*&---------------------------------------------------------------------*
*                F O R M S
*&---------------------------------------------------------------------*
*&VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV*
*&---------------------------------------------------------------------*
*& Form get_data
*&---------------------------------------------------------------------*
*& text
*&---------------------------------------------------------------------*
FORM get_data.

  DATA: ls_data TYPE ty_data.

  SELECT * INTO TABLE @DATA(lt_snap_beg)
                 FROM snap_beg
                WHERE datum IN @s_datum
                  AND seqno = '000'
             ORDER BY datum,
                      uzeit.

  IF sy-subrc = 0.
    DESCRIBE TABLE lt_snap_beg LINES DATA(lv_lines).
    LOOP AT lt_snap_beg INTO DATA(ls_snap_beg).
      ls_text_dump = ls_snap_beg-flist.
      ls_text_dump-errid = ls_text_dump-errid(ls_text_dump-ll).

      MOVE-CORRESPONDING ls_snap_beg TO ls_data.
      ls_data-errid = ls_text_dump-errid.
      CONCATENATE ls_data-datum+6(2)
                  ls_data-datum+4(2)
                  ls_data-datum(4) INTO ls_data-datum SEPARATED BY '/'.
      CONCATENATE ls_data-uzeit(2)
                  ls_data-uzeit+2(2)
                  ls_data-uzeit+4(2) INTO ls_data-uzeit SEPARATED BY ':'.

      INSERT ls_data INTO TABLE gt_data.
      WRITE:/ ls_data-datum, ls_data-uzeit, ls_snap_beg-uname, ls_text_dump-errid.
    ENDLOOP.
  ENDIF.
ENDFORM.
*&---------------------------------------------------------------------*
*& FORM create_message
*&---------------------------------------------------------------------*
*& text
*&---------------------------------------------------------------------*
FORM create_message.

  DATA: ls_data    TYPE ty_data,
        ls_objpack TYPE sopcklsti1.

  " Title
  gs_docdata-obj_name  = 'email notification'.

  " Description
  gs_docdata-obj_descr = 'DUMPs overview'.

  PERFORM add_mail_line USING '<html> <body style="background-color:#ffe4c4;">'.
  PERFORM add_mail_line USING 'List of DUMPs:'.
  PERFORM add_mail_line USING '<table style="margin: 10px" cellspacing="0″ cellpadding="3″ width="800″ border="1″><tbody><tr>'.
  PERFORM add_mail_line USING '<th>Day</font></th>'.
  PERFORM add_mail_line USING '<th>Time</font></th>'.
  PERFORM add_mail_line USING '<th>User</font></th>'.
  PERFORM add_mail_line USING '<th>DUMP</font></th>'.

*   table Contents
  LOOP AT gt_data INTO ls_data.

    PERFORM add_mail_table_line USING '<tr><td><center>'
                                      ls_data-datum
                                      '</center></td>'.
    PERFORM add_mail_table_line USING '<td><center>'
                                      ls_data-uzeit
                                      '</center></td>'.
    PERFORM add_mail_table_line USING '<td><center>'
                                      ls_data-uname
                                      '</center></td>'.
    PERFORM add_mail_table_line USING '<td><center>'
                                      ls_data-errid
                                      '</center></td>'.
  ENDLOOP.

  PERFORM add_mail_line USING '</tbody> </table> </body> </html> '.


  DESCRIBE TABLE gt_objtxt LINES DATA(lv_lines).

  READ TABLE gt_objtxt INTO DATA(wa_objtxt) INDEX lv_lines.

  gs_docdata-doc_size = ( lv_lines - 1 ) * 255 + strlen( wa_objtxt ).

* Packing data

  CLEAR ls_objpack-transf_bin.
  ls_objpack-head_start = 1.
  ls_objpack-head_num   = 0.
  ls_objpack-body_start = 1.
  ls_objpack-body_num   = lv_lines.
  ls_objpack-doc_type   = 'HTML'.
  APPEND ls_objpack TO gt_objpack.

ENDFORM.
*&---------------------------------------------------------------------*
*& FORM send_message
*&---------------------------------------------------------------------*
*& text
*&---------------------------------------------------------------------*
FORM send_message .

  DATA: lt_receivers TYPE TABLE OF somlreci1,
        ls_receiver  TYPE somlreci1.
  LOOP AT s_mail INTO DATA(ls_mail).
    ls_receiver-receiver = s_mail-low.
    ls_receiver-rec_type = 'U'.
    INSERT ls_receiver INTO TABLE lt_receivers.
  ENDLOOP.

*   Send Message to external Internet ID
  CALL FUNCTION 'SO_NEW_DOCUMENT_ATT_SEND_API1'
    EXPORTING
      document_data              = gs_docdata
      put_in_outbox              = 'X'
      commit_work                = 'X'
    TABLES
      packing_list               = gt_objpack
*     object_header              = gt_objhead
      contents_txt               = gt_objtxt
      receivers                  = lt_receivers
    EXCEPTIONS
      too_many_receivers         = 1
      document_not_sent          = 2
      document_type_not_exist    = 3
      operation_no_authorization = 4
      parameter_error            = 5
      x_error                    = 6
      enqueue_error              = 7
      OTHERS                     = 8.

ENDFORM.
*&---------------------------------------------------------------------*
*& Form add_line
*&---------------------------------------------------------------------*
*& text
*&---------------------------------------------------------------------*
FORM add_mail_line USING p_var.

  DATA: ls_objtxt  TYPE solisti1.

  ls_objtxt-line = p_var.

  APPEND ls_objtxt TO gt_objtxt.

ENDFORM.
*&---------------------------------------------------------------------*
*& Form add_line
*&---------------------------------------------------------------------*
*& text
*&---------------------------------------------------------------------*
FORM add_mail_table_line USING p_var1
                               p_var2
                               p_var3.

  DATA: ls_objtxt  TYPE solisti1.

  CONCATENATE p_var1
              p_var2
              p_var3 INTO ls_objtxt-line.

  APPEND ls_objtxt TO gt_objtxt.

ENDFORM.

Como este artículo se ha quedado un poco extenso y hay más cosas que contar abordaremos más información en otros artículos posteriores.

OData y SAP Gateway – II – Publicar un servicio OData en SAP

Nos metemos en harina y dejamos de teorizar. Después del artículo ‘OData y SAP Gateway‘ donde explicamos OData y su uso en SAP. Hoy vamos a aprender lo básico para saber montar un servicio OData en SAP usando el Gateway. Vamos a intentar que esto sea una serie de artículos sobre OData y SAP Gateway.


Serie de Artículos sobre OData

Este artículo pertenece a una serie de artículos que se van complementando poco a poco como itinerario de conocimiento:


Modelo de datos

Lo primero que vamos a hacer es definir nuestro modelo de datos a extraer. Esto no forma parte del proceso, pero es algo que necesitamos tener claro antes de continuar. Aquí os hago una propuesta. Vamos a hacer un servicio sobre Business Partners (BP) y sus objetos relacionados. Si no sabes lo que es un Business Partner revísa la entrada del blog:

Lo escribo en esquema para tenerlo claro.

Datos generales de Business Partner.

  1. Roles de BP
  2. Direcciones
  3. Relaciones
  4. Datos de Área de Ventas

Ahora lo pongo en un diagrama Entidad-relación.

Y además añadimos la estructura sencilla de tablas donde se encuentran estos datos:

Con esta estructura «sencilla» podemos abordar el problema (publicar un servicio web) de múltiples formas. Podríamos crear un servicio de consulta SOAP que por id de BP te devuelva una tabla con tablas anidadas con esa estructura. Pero luego, si queremos crear o actualizar tenemos que crear otro o crear un servicio monstruoso.

La potencia de OData reside en generar estas entidades y subentidades con las acciones CRUD (Create Read Update Delete) que necesitemos sobre cualquier entidad o subentidad. Por lo tanto, con OData hacemos un servicio web de microservicios. Pudiendo elegir entidad por entidad y campo por campo si se puede consultar, crear, actualizar o eliminar.


Estructuras a usar

Para crear este OData podemos hacer que cada entidad del modelo sea una entidad OData. De esta forma veremos que el servicio OData será muy versátil, pudiendo recuperar la profundidad de datos que necesitemos.

Para ello vamos a crear las siguientes estructuras y tipos tabla de las entidades subordinadas a los datos de cabecera.

Estructura para Roles de BP
Tipo Tabla para Roles de BP
Estructura para Direcciones de BP
Tipo Tabla para Direcciones de BP
Estructura de datos para Relaciones del BP
Tipo Tabla de Relaciones del BP
Estructura de Identificadores del BP
Tipo Tabla de Identificadores del BP

Con estos tipos de tablas creamos la estructura de datos de cabecera del BP.

Donde vemos que hay 4 tablas dentro de la estructura para los datos de Roles, Direcciones, Relaciones y Números de identificación del BP.


Gateway Service Builder (SEGW)

Una vez tenemos nuestro modelo y las estructuras creadas tenemos que crear un proyecto en la Gateway Service Builder (SEGW) y añadir la entidad del BP.

Al pulsar el botón de Nuevo nos aparecerá esta ventana

El campo Project Type es importante, porque nos permitiría crear un servicio OData v4. Ya hablaremos de eso, pero por ahora vamos a hacer un servicio OData básico, ya lo complicaremos más adelante.

Una vez creado el proyecto, estará vacío, por lo que vamos a añadir las entidades. Como previamente hemos creado la entidad de datos generales del Business Partner junto con todas las tablas relacionadas, vamos a importar esa estructura generada.

Entramos en la opción de importar una estructura del diccionario

Añadimos nuestra estructura padre de datos generales de Business Partners.

Seleccionamos nuestra estructura padre

Al darle a Next nos pedirá que indiquemos que campos/entidades queremos importar de la estructura seleccionada.

Como la estructura es nuestra, seleccionamos todo

Una vez hayamos seleccionado los campos y entidades nos pedirá, de cada entidad que vaya a crear y campo, cual es la clave, nombre y etiqueta. No obstante es algo que podemos cambiar posteriormente.

En Address la clave es Partner y Addrnumber
De IdentNumber la clave es PARTNER y TYPE.
De Relation la clave es PARTNER1 y PARTNER2.
De Roles la clave es PARTNER y RLTYP

Como resultado tendremos cinco entidades creadas:

  • BusinessPartner
  • Address
  • IdentNumbers
  • Relation
  • Roles

Cada una de ellas con los atributos y claves seleccionados en el punto anterior.

Relaciones entre Entidades

Ahora lo que tenemos que hacer es relacionar las entidades subordinadas con la entidad padre, para ello creamos una Assiciation

Botón derecho en Association y Create

Vamos a ver el ejemplo de Roles, el resto serán iguales puesto que la estructura de entidades es sencilla.

Definición de la Asociación entre entidades y su cardinalidad
Identificamos la asociación de claves entre entidades
Resumen de Asociación

Una vez hayamos hecho todas las asociaciones tendremos que generar el servicio con el botón

Vemos las entidades, asociaciones entre entidades y pulsamos a Generar

Al generar se nos propone unos nombres de clases controladoras del servicio OData. Por norma general esto aceptamos la propuesta y seguimos generando.

Clases controladoras del servicio OData

Al generar se nos añade la información de Service Implementation con todas las acciones CRUD de cada una de las entidades.


Implementación del servicio

Una vez tengamos generado el servicio veremos, en la parte de Service Implementation veremos cada una de las acciones posibles de cada una de las entidades. Por cada entidad tendremos las acciones:

  • Create: Creación de la entidad. Si estamos hablando de BusinessPartner, pues creación de un BP nuevo.
  • Delete: Borrado de la entidad. Por ejemplo, cuando queremos eliminar una dirección del Business Partner.
  • GetEntity: Consulta de los datos por clave, es decir, solo consultamos un registro
  • GetEntitySet: Consulta de los datos por Query, es decir, se va a recuperar una colección (tabla) de datos en base a unos criterios de búsqueda.
  • Update: Actualización de una entidad ya existente.

Como en nuestro caso se trata de un servicio OData de consulta de datos de BPs nos fijamos en la carpeta de Service Implementation del proyecto OData. Donde vemos cada una de las entidades y subentidades las acciones y, al pulsar doble click vemos el objeto técnico y la clase y el método donde se debe implementar la lógica.

De una entidad vemos sus métodos de implementación de la clase DPC_EXT

La clase donde se debe implementar la lógica es la ZCL_<NOMBRE_PROYECTO>_DPC_EXT (guárdate esto en la cabeza que lo vas a usar mucho). Para acceder a la clase es tan sencillo como entrar en la SE24 o entrar, dando doble-click sobre ella en el apartado Runtime Artifacs.

En una primera instancia veremos todos los métodos del CRUD de cada entidad sin redefinir. Para ir añadiendo nuestro código tenemos que ir redefiniendo cada método en la clase DCP_EXT e ir añadiendo la lógica esto lo haremos con más en otro artículo, pero en este vamos a hacer algo muy básico.


Implementado una query simple de BPs

Para implementar la consulta básica de los Business Partners vamos a implementar el método GET_ENTITYSET de la entidad BusinessPartners. Por lo tanto será el método BUSINESSPARTNERS_GET_ENTITYSET de la clase ZCL_ZODATA_TEXT_BP_DPC_EXT. Para ello debemos redefinir este método.

Formas de Redefinir el método en la clase DPC_EXT

Al redefinir el método aparece una propuesta de código.

Propuesta de código

Vamos a realizar una implementación muy muy sencilla para ver la funcionalidad básica. Todo esto puede llegar a ser mucho más complejo. Ya lo explicaremos.

Ponemos una Select muy sencilla para ver cómo se implementa.
En posteriores artículos entraremos en detalle de las posibilidades que nos da de implementación.

Activación del servicio /IWFND/MAINT_SERVICE

Una vez tenemos el servicio OData generado e implementado tenemos que activar el servicio para que pueda ser llamado, Para ello entramos en la transacción /IWFND/MAINT_SERVICE. Es algo que sólo hay que hacerlo una vez por servicio.

En una primera vez, tenemos que añadir el servicio a la lista.

Al pulsar el botón «Añadir servicio» se nos abrirá una búsqueda de servicios, buscamos el nuestro, lo seleccionamos y pulsamos «Añadir servicios seleccionados».

Añadimos nuestro servicio

Nos pedirá el nombre, paquete y si queremos que se genere el nodo ICF.

Una vez añadido el servicio lo tendremos disponible en la lista de servicios de la /IWFND/MAINT_SERVICE.


Probando el servicio

Una vez tengamos el servicio activo en la /IWFND/MAINT_SERVICE podemos entrar en el SAP Gateway Client para probar el servicio OData. Para ello podemos entrar con la transacción /IWFND/GW_CLIENT o bien desde la /IWFND/MAINT_SERVICE pulsando el botón Cliente SAP Gateway.

Podemos pulsar este botón o ir directamente a la Transacción /IWFND/GW_CLIENT

Una vez dentro del SAP Gateway Client veremos la siguiente pantalla. Pero esa URL propuesta lo único que nos dará será un XML de definición del servicio. Si queremos probarlo será en base a una entidad con las acciones CRUD.

Una ves aquí podemos seleccionar la entidad sobre la que queramos hacer la acción con el botón .

Seleccionamos BusinessPartnerSet porque es lo que hemos configurado

Resumen

El resultado de este servicio OData que hemos creado es una consulta básica a los datos de cabecera de los Business Partners. Tan básica que no tiene ni filtros y no sacamos datos de subentidades. Pero el objetivo es que tuviésemos algo que pueda ejecutarse de forma fácil para ver un resultado. En siguientes capítulos de esta serie me gustaría abordar:

  • Subentidades de una entidad (Expanded Entities)
  • Filtros en la consulta
  • Opciones de CRUD por entidad en OData
  • Documentar un servicio OData con OpenAPI/Swagger
  • Probar el servicio con Postman
  • Creación de un OData desde un Módulo de Función

Veremos a lo que llegamos, porque esto tiene mucho contenido, paso a paso.


Si te interesa, suscríbete al blog por email

SAP Mandante (Cliente)

Vamos a explicar algo básico, pero no por ello menos importante. Además es algo que damos por hecho y, normalmente no tenemos en cuenta porque no solemos tener que manejar más de uno. El mandante (o cliente) en SAP.

¿Qué es el Mandante?

En SAP, un mandante o cliente se refiere a una instancia separada en un mismo sistema SAP que puede ser utilizado por diferentes empresas de un grupo de empresas o bien distintas unidades organizativas de la misma empresa. Usando un símil con el armario de la imagen superior. Es como tener un armario con varios cajones, uno para cada área de negocio que quiera separar.

El mandante es ese código de 3 cifras que cuando entras en SAP viene por defecto, normalmente 100 pero pudiendo venir otro. Además en las tablas pones el campo MANDT. Siendo 3 cifras podríamos tener hasta 1000 mandantes, a mí con uno me vale.

Nunca encuentro el mandante 404

¿Para que sirve tener más de un mandante?

Cada mandante tiene su propio conjunto de datos, configuraciones (customizing) y usuarios que lo hacen único. Esto permite a diferentes organizaciones o divisiones operar en un mismo sistema SAP mientras mantienen su información y accesos separados.

Imagínate que eres el CIO de un grupo de empresas y quieres implementar un ERP para gestión del grupo. Pero no quieres que los datos de una de las empresas interfieran con los de la otra. Para ello creas dos mandantes uno con la configuración y datos de la empresa A y otro con la configuración y los datos de la empresa B. ¿Bonito no? Te ahorras hierro, mantenimiento de sistemas, etc. Volviendo al símil del armario, solo gastas en comprar uno, solo mantienes en buen estado ese, solo te ocupa el lugar de uno, etc… Pero también tiene sus inconvenientes ya que hay elementos comunes a todos los cajones, y eso puede producir problemas.


Customizing independiente de mandante

Pues aquí empieza el primero. En general, el customizing o configuración de SAP es dependiente de mandante. Es decir, por ejemplo tienes unos tipos de documentos para la empresa A y otros para la B. O tienes unos datos transaccionales propios para cada mandante, pedidos, facturas, materiales, clientes, etc… (No digo que puedas tener una contabilidad B, ¿no?)

Pero hay algunas configuraciones que son independientes de mandante (Cross-Client) que SAP te avisa que vas a configurar algo independiente de mandante, que tengas cuidado porque afectará a todos los mandantes por igual.


Workbench

Lo mismo pasa con el workbench, es decir todos los objetos del repositorio como son reports, clases, módulos de función, elementos de datos, etc… Este es el mayor riesgo porque se comparte el código y eso hay que tenerlo en cuenta a la hora de hacer una u otra cosa. En símil del armario es como si haces cambios en los rodamientos donde abre y cierra cada armario, o si pintas el armario de un color porque uno te lo ha pedido así.


SELECT a tablas dependientes de mandante

Si bien hemos dicho que el workbench se comparte, cuando hacemos una SELECT a una tabla dependiente de mandante, no necesitamos indicar explícitamente el Mandante en el que estamos, por defecto lo tendrá en cuenta, lo mismo al crear registros.


Mandantes especiales (000, 001, 066)

En los sistemas SAP, además de los mandantes que se hayan creado para la gestión de la empresa o área de negocio, existen una serie de mandantes «especiales» que tienen, cada uno de ellos, un propósito particular.

  • Mandante 000: Es un mandante de referencia proporcionado por SAP. Contiene la configuración estándar de SAP, pero no incluye datos maestros o transaccionales. Se utiliza comúnmente como base para crear nuevos mandantes mediante la función de copia de mandante. Además para ciertas tareas del antiguo (en paz descanse) Middleware de SAP CRM, era necesario entrar a configurar.
  • Mandante 001: Se trata de un mandante ejemplo, una base para crear nuevos mandantes limpios.
  • Mandante 066 (Early Watch): Se utiliza principalmente para propósitos de soporte remoto por parte de SAP. Permite a SAP acceder al sistema para diagnósticos y optimización del rendimiento.

Copia de Mandante

Este proceso permite duplicar la configuración de un mandante existente en otro nuevo. Es especialmente útil cuando se necesita establecer un mandante para desarrollo, pruebas o formación, basado en la configuración actual de un mandante productivo. La copia de mandante incluye tanto la configuración (customizing) como los datos maestros, permitiendo un entorno de trabajo completo y coherente, pero cuidado, que el workbench no se pasa en una copia de Mandante, con lo que si tienes los sistemas de desarrollo y test hechos un desastre, solo pasas datos y configuración (NOTA: No tengas los entornos hechos un desastre).

Para mí este proceso es fundamental realizarlo periódicamente desde producción a test, con un proceso de blanqueo de datos si se requiere en medio, para poder establecer un escenario de resolución de incidencias óptimo.


Copia Homogénea

A diferencia de la Copia de Mandante, y aunque nos salgamos un poco del tema, la copia homogénea es una copia tal cual de un sistema a otro. Es muy útil y usado para escenarios de upgrades, cuando necesitamos crear un Sandbox (sí, sí, arenero, los ingleses no se andan con chorradas) para probar los upgrades sin afectar la cadena de transportes.

Quedamos a las 5 en el arenero a jugar

En casos extremos se puede usar para hacer borrón y cuenta nueva desde Producción a Desarrollo, pero como ya os he dicho antes, esto sería porque tenéis un sistema de desarrollo descabalado y sin control, con muchas modificaciones descontroladas. El problema de la copia homogénea es que todo se copia, y claro, tendremos un periodo de reconfigurar muchas cosas y de quitar ciertas que no se necesitan en entorno no productivo. Por ejemplo, salida de emails.

¿Cómo ver los mandantes que hay?

Para ver los mandantes que hay en el sistema podemos acceder a la transacción SCC4. Además esta transacción nos permite administrar cada uno de los mandantes indicando su tipología y su gestión de cambios y transporte.


En conclusión

Si bien es algo que siempre pasamos por alto, porque lo habitual es que tengamos un mandante  de trabajo por entorno. Podemos encontrarnos, o incluso proponer, un escenario distinto, donde haya varios mandantes en la misma instancia de SAP, y tenemos que ser conscientes de lo que implica y las capacidades que da.

SAP Referencia de utilización (Where-Used List)

Una de las funcionalidades importantes dentro de nuestro trabajo día a día, sobre todo si estamos asumiendo un rol técnico, pero también como funcional, es saber donde se usan los objetos de SAP, saber buscar.

Un buen sabueso sabe seguir las pistas

Pongamos ejemplos.

  • Estamos ejecutando un proceso o una aplicación y nos aparece un mensaje, queremos saber donde se lanza ese mensaje.
  • Tenemos un campo en pantalla y queremos averiguar en qué tabla de base de datos se guarda. O en que BAPI se usa.
  • Tenemos una tabla de base de datos y queremos saber donde se actualiza.
  • Queremos buscar un literal en el código fuente de todos los objetos de un paquete.
  • Queremos documentar un sistema en el cual no hemos participado desde el principio.

Mensajes

Estas ejecutando un proceso, sea online o batch, sea en SAP GUI o en WebUI, y te salta un mensaje de error. No tienes idea de porqué sale, qué lo llama y necesitas identificarlo. Te has debugueando (olé) todo el proceso y no lo sacas. Necesitas saber el punto donde se lanza ese mensaje. Calma, vamos a ver como sacarlo vía referencia de utilización.

Identificando el mensaje

Para buscar donde se lanza un mensaje primero hay que saber identificarlo, es decir, saber qué mensaje es el que estamos buscando.

Mensajes en SAP GUI

Cuando estamos ejecutando un report o una transacción en SAP GUI veremos en la barra inferior el mensaje que nos salta. Al pulsar sobre esa barra obtendremos un popup con la información detallada del mensaje.

Pulsando sobre el mensaje tenemos este popup.
Donde vemos el numero de mensaje y la clase de mensaje

Si pulsamos el botón se nos abrirá otro popup con datos más técnicos.

Donde posicionando el cursor en uno de los campos y dando a Navegar podemos ver el punto del código donde se llama. Pero a veces con esto no vale, así que nos quedamos además con la clase de mensaje y el número de mensaje.


Mensajes en Jobs de Fondo

Cuando se ejecuta un proceso en fondo no hay nadie para ver el mensaje cuando se produce. En este caso el estándar guarda cada mensaje que se da en el log del Job.

Para ello entraremos en la SM37, seleccionando el job que queremos y pulsando el botón Log job.

Mensajes en WebUI

En WebUI los mensajes aparecen, dependiendo del skin que estemos usando, en la barra superior o en la inferior. Si nos posicionamos con el cursor encima del texto del mensaje veremos el Tipo, Número, clase de mensajes y Variables de ese mensaje.

Si no lo ves es porque tienes que ajustar el parámetro BSPWD_USER_LEVEL al máximo (9) en tu perfil de usuario en la transacción SU3 (o dentro de Parámetros de la SU01).

¿Dónde se usa ese mensaje?

Una vez tengamos identificado la clase de mensajes y el Número de mensaje podemos ir a la transacción SE91 a visualizar el mensaje.

Seleccionando el número de mensaje y pulsando el botón <> obtendremos todos los puntos de código donde se llama explícitamente el mensaje.

La importancia de usar la sentencia MESSAGE

Porque es importante el matiz «explícitamente» ya que si un mensaje no es llamado con la sentencia MESSAGE la Referencia de Utilización no lo encontrará en el código.

Por ejemplo cuando un mensaje se añade vía un Módulo de Función o un método de una clase. Esto es muy habitual cuando se manda un mensaje en el WebUI. En estos casos no vamos a encontrarlo y tendremos que usar la búsqueda por literal (ver sección «Buscar en código»)

Por lo tanto, es importante, cuando añada os un mensaje en nuestro código, si se realiza vía módulo de función o método de clase que pongamos la siguiente sentencia.

IF 1 = 2. MESSAGE<tipo><número>(clase). ENDIF.

Ese mensaje nunca se va a llamar puesto que el IF nunca se cumplirá. Pero sirve para encontrar el mensaje en el código usando el botón de Referencia de Utilización desde la SE91.


Objetos del diccionario

Como objetos del diccionario de datos me refiero a elementos de datos, dominios, estructuras, tipos tabla, vistas y tablas de base de datos. Vamos, aquello que vemos en la transacción SE11.

Al entrar en cualquiera de estos objetos veremos el botón que nos permitirá buscar donde se usa ese objeto en otras entidades, pudiendo además elegir sobre qué subconjunto elegir.

Además dándole al botón podemos acotar la búsqueda sobre los objetos de un paquete concreto o por nombre de objeto.

El resultado es una lista de dónde se usa el objeto dentro del ámbito que hayamos seleccionado.


Programas, Clases y Módulos de función

Lo mismo que con los Objetos del diccionario de datos, la diferencia es que desde un programa, MF o Método de una clase solo podrán ser usados en otro código fuente. Con lo que algo más se acota por el tipo de objeto y sus posibles usos.


No me funciona el botón

Si no funciona la búsqueda puede ser que falte por reconstruir el índice de los objetos SAP en el sistema. Para ello tenemos que ejecutar los siguientes reports:

  • SAPRSEUC – Para reconstruir el índice de los objetos de cliente (Z* y Y*)
  • SAPRSEUB – Para reconstruir el índice de los objetos de SAP

Buscar en el código

A pesar de todas las opciones anteriormente descritas, es posible que necesitemos buscar algo que hayamos usado en código o que use el estándar. Imagínate que quieres recuperar sitios donde se ha usado un parámetro de un Módulo de Función, un hardcode concreto, un comentario, un mensaje llamado vía BAPI o una sentencia concreta.

Para ello yo siempre uso (y lo uso muy a menudo) el report estándar RS_ABAP_SOURCE_SCAN.

Para mi es una de los reports que más uso en mi día a día. Para buscar algo que hice y no recuerdo o para buscar como el estándar usa determinada lógica, MF o Clase. Mi forma de usarla siempre es poniendo el String buscado y el paquete. Si estoy buscando código Z pongo como paquete Z* y si no ya voy jugando.

Por ejemplo si busco así:

Recibo esta información:

También suelo jugar con el campo «Lugar del hallazgo +/- líneas para que muestre más o menos código en el resultado.

Como problemas son:

  • Si no acotas bien el paquete puede ser que busque en demasiados objetos, tardando en realizar la búsqueda.
  • La lista de resultados puede ser demasiado grande

En Conclusión

Estamos para aprender, cualquiera que tenga otra idea o que use otros métodos me gustaría saberlos. Yo esto lo uso continuamente, cualquiera de las soluciones que explico. Sobra decir que la experiencia y la intuición también ayudan a encontrar aquello que buscas, pero eso no se puede explicar.

Un buen sabueso sabe seguir las pistas.