Skip to Content

Saludos amigos de la comunidad SAP,

Hoy quería compartirles la experiencia que tuve con un demo que prepare para probar la integración entre Amazon Alexa y SAP, realmente fue muy interesante poder mostrarle a la gente en 3 eventos, uno en octubre de 2017 en el congreso ASUG Colombia, otro en el SAP Summit Medellín en febrero de 2018 y el ultimo en el Amazon Cloud Experience en Bogota en Julio pasado, todo lo que podemos hacer con estas 2 plataformas (Amazon Alexa, y SAP) que nos habilitan el poder desarrollar aplicaciones de agentes conversacionales, que fácilmente se integran entre sí utilizando tecnología REST, antes de pasar a los detalles de mi demo, quisiera mostrarles algunas fotos de los tres eventos en los que estuve compartiendo mi experiencia con esta integracion.

Congreso ASUG Colombia Octubre 2017

Congreso SAP Summit Medellín Febrero 2018

Amazon Cloud Experience Julio 2018

Bueno, ya entrando en materia, les cuento de que se trata el demo: el objetivo de este demo es probar que tan facil y rapido se puede construir aplicaciones con agentes conversacionales, mi demo se trata de una aplicación de consulta de datos de ventas y presupuesto utilizando la voz, para desarrollar y configurar esta aplicación se requiere de los siguientes componentes:

  1. SAP Cloud Platform.
  2. SAP Cloud Connector.
  3. Servicio odata provisioning
  4. Servicios odata del lado del ERP.
  5. Amazon Alexa Skill configuration.
  6. Amazon Lambda functions.

Se requiere conocimientos básicos programación javascript, específicamente en Node js, tambien en lenguaje ABAP, servicios odata, además del manejo de las plataformas cloud de SAP y Amazon.

Lo primero que hice fue crear un servicio odata con un simple select a una de las estructuras de información de SD donde se almacena la venta y el presupuesto de la compañía, para esto deben ingresar a la transacción SEGW, donde realizan la creación del proyecto, este servicio luego se agregara al servicio odata provisioning del SCP para llevarlo a cloud ya que nuestros datos están on-premise.

Este es el código del servicio odata que consulta las ventas y el presupuesto.

method VENTASSET_GET_ENTITYSET.
 DATA: ls_filter_select_options TYPE /iwbep/s_mgw_select_option,
       ls_select_option TYPE /iwbep/s_cod_select_option.
 DATA: l_it_so_periodo TYPE RANGE OF spmon,
       l_wa_so_periodo LIKE LINE OF l_it_so_periodo,
       l_it_so_oficina TYPE RANGE OF vkbur,
       l_wa_so_oficina LIKE LINE OF l_it_so_oficina,
       l_it_so_grupoart TYPE RANGE OF matkl,
       l_wa_so_grupoart LIKE LINE OF l_it_so_grupoart,
       l_it_so_vrsio TYPE RANGE OF s700-vrsio,
       l_wa_so_vrsio LIKE LINE OF l_it_so_vrsio.

 LOOP AT it_filter_select_options INTO ls_filter_select_options.
  IF ls_filter_select_options-property EQ 'Periodo'.
    LOOP AT ls_filter_select_options-select_options INTO ls_select_option.
      l_wa_so_periodo-sign = ls_select_option-sign.
      l_wa_so_periodo-option = ls_select_option-option.
      l_wa_so_periodo-low = ls_select_option-low.
      l_wa_so_periodo-high = ls_select_option-high.
      APPEND l_wa_so_periodo TO l_it_so_periodo.
    ENDLOOP.
  ENDIF.
  IF ls_filter_select_options-property EQ 'Oficina'.
    LOOP AT ls_filter_select_options-select_options INTO ls_select_option.
      l_wa_so_oficina-sign = ls_select_option-sign.
      l_wa_so_oficina-option = ls_select_option-option.
      l_wa_so_oficina-low = ls_select_option-low.
      l_wa_so_oficina-high = ls_select_option-high.
      APPEND l_wa_so_oficina TO l_it_so_oficina.
    ENDLOOP.
  ENDIF.
  IF ls_filter_select_options-property EQ 'GrupoArticulos'.
    LOOP AT ls_filter_select_options-select_options INTO ls_select_option.
      l_wa_so_grupoart-sign = ls_select_option-sign.
      l_wa_so_grupoart-option = ls_select_option-option.
      if ls_select_option-low <> ''.
        l_wa_so_grupoart-low = ls_select_option-low.
        l_wa_so_grupoart-high = ls_select_option-high.
        APPEND l_wa_so_grupoart TO l_it_so_grupoart.
      endif.
    ENDLOOP.
  ENDIF.
  IF ls_filter_select_options-property EQ 'Version'.
   LOOP AT ls_filter_select_options-select_options INTO ls_select_option.
      l_wa_so_vrsio-sign = ls_select_option-sign.
      l_wa_so_vrsio-option = ls_select_option-option.
      l_wa_so_vrsio-low = ls_select_option-low.
      l_wa_so_vrsio-high = ls_select_option-high.
      APPEND l_wa_so_vrsio TO l_it_so_vrsio.
    ENDLOOP.
  ENDIF.
 ENDLOOP.

 IF l_it_so_vrsio IS INITIAL.
    l_wa_so_vrsio-sign = 'I'.
    l_wa_so_vrsio-option = 'EQ'.
    l_wa_so_vrsio-low = '000'.
    APPEND l_wa_so_vrsio TO l_it_so_vrsio.
    l_wa_so_vrsio-low = '999'.
    APPEND l_wa_so_vrsio TO l_it_so_vrsio.
 ENDIF.

 IF l_it_so_grupoart IS NOT INITIAL.
  SELECT spmon, matkl, vkbur, vrsio, SUM( netwr ) AS netwr, SUM( zzpesoneto ) AS zzpesoneto
  FROM s700 INTO CORRESPONDING FIELDS OF TABLE @et_entityset
  WHERE spmon IN @l_it_so_periodo
    AND vkbur IN @l_it_so_oficina
    AND vrsio IN @l_it_so_vrsio
    AND matkl IN @l_it_so_grupoart
  GROUP BY spmon, matkl, vkbur, vrsio.
 ELSE.
  SELECT spmon, vkbur, vrsio, SUM( netwr ) AS netwr, SUM( zzpesoneto ) AS zzpesoneto
  FROM s700 INTO CORRESPONDING FIELDS OF TABLE @et_entityset
  WHERE spmon IN @l_it_so_periodo
    AND vkbur IN @l_it_so_oficina
    AND vrsio IN @l_it_so_vrsio
  GROUP BY spmon, vkbur, vrsio.
 ENDIF.
endmethod.

En resumen lo que hace este servicio es devolver las ventas y presupuesto en registros, se utilizan la versión 000 para las ventas, y la versión 999 para el presupuesto. Aqui vemos los filtros por Periodo, oficina, grupo de artículos y versión.

Una vez listo mi servicio odata, procedi a publicarlo en SAP Cloud Platform utilizando el servicio odata provisioning, para esto se debe tener ya configurada la conexión entre SCP y el ERP utilizando el SAP Cloud Connector.

Este es un screenshot de la configuración del cloud connector que está en la misma red de mis servidores SAP on-premise, como ven esta mapeado el puerto 8000, por efectos del demo lo deje asi, pero realmente por seguridad debe tener el puerto 8443 que es un puerto seguro.

En SCP lo vemos de la siguiente forma:

Una vez garantizamos la conexión entre SCP y nuestro sistema ECC on-premise podemos conectar nuestro servicio odata creado con anterioridad. Para esto vamos al servicio Odata Provisioning.

En configure service accedemos al destination que creamos para la conexión hacia nuestro sistema on-premise, este nos indica exactamente a que servidor nos vamos a conectar, el cloud connector es una conexión global, pero el destination es la conexión a nuestro sistema específico.

Ya estamos listos para agregar el servicio odata que creamos on-premise, damos click en Go to service donde veremos el servicio ya agregado.

Para agregar un servicio simplemente dan click en el boton Register:

Seleccionan el destination creado para acceder al servidor on-premise y dan click en la lupa donde les traerá los servicios creados, lo seleccionan, dan click en Register y ya tienen publicado el servicio en SAP Cloud Platform.

Probamos el servicio accediendo a la URL del servicio con unos parametros de seleccion de ejemplo, y la salida es una respuesta en formato JSON.

Hasta aquí llega la parte SAP, ahora vamos a pasar a la parte Amazon, donde también en cloud se configuran y se hace deploy de la aplicación que se desarrolla en node js. Para entender un poco lo que voy a mostrar quisiera explicarlo en términos no tan técnicos, lo que vamos a hacer a continuación es por un lado configurar la habilidad (así se denomina en lenguaje Amazon) que es la que nos permitirá hacer las preguntas y que alexa nos responda en un diálogo denominado multiturno ya que yo inicio preguntando algo, y si alexa no tiene la información completa procederá a preguntarme por esa información que hace falta para poder interactuar finalmente con nuestro servicio odata publicado en SCP, por el otro lado tenemos una aplicación desarrollada en node js, que se encargará de recibir de la habilidad de alexa los parámetros necesarios para poder interactuar con SAP, y ejecutar la respectiva consulta. La respuesta que es devuelta por nuestro servicio en formato JSON es interpretada por la aplicación node js, y utilizando las librerías propias del servicio de voz de Alexa, se da la orden de “decir” lo que acabamos de consultar, organizando de la manera más conveniente nuestra respuesta.

Amazon Alexa Skill configuration.

La configuración de la habilidad se realiza en la consola de desarrollador de Amazon Web Services en el sitio developer.amazon.com, ahí entramos a Alexa donde podemos crear la habilidad. En este caso la habilidad ya esta creada y el nombre que le puse es Sales, ingresamos a ella dando click en Edit.

Como pueden ver la intención que creé se llama GetSalesAndBudget, dentro de la habilidad Sales donde ingresé puedo crear distintas intenciones que no son nada mas que lo que la persona tiene “intención” de consultarle a Alexa por ejemplo una intención puede ser la de conocer las ventas y presupuesto, pero otra intención podría ser quiero saber que porcentaje crecí en ventas entre 1 año y otro comparando el mismo mes, cada intención va a tener unos slots (parametros) obligatorios y opcionales. Retomando entonces para mi habilidad solo tengo una intención y es conocer las ventas y presupuesto de un periodo determinado.

Mas abajo tengo los tipos de slots que es como un tipo de datos, donde tambien tengo la posibilidad de definir unos dominios como es el caso del slot oficina del tipo City (creé varias ciudades), aqui podemos ver que puedo mapear nombres a códigos lo que significa que dentro de mi función node js desplegada en lambda voy a poder interpretar como código la ciudad que yo le pida a Alexa.

Por ultimo nos vamos a la opción Endpoint donde podremos especificar el id de nuestra funcion lambda que veremos en la siguiente sección, ahi debemos tener cargado el codigo manejador de nuestra habilidad, Alexa contactara a esta función pasándole la información de los slots, y esta función se conectara a SAP, obtendrá los datos en formato json y procedera a enviarle el resultado nuevamente a Alexa con lo que debe “decir”.

Amazon Lambda functions

Lambda es un servicio de Amazon con filosofia serverless (console.aws.amazon.com/lambda) lo cual permite ejecutar código sin necesidad de tener desplegada una infraestructura de servidor en la nube. Lo que hicimos aqui fue desplegar nuestro código fuente implementado en node js, este programa se desarrolló localmente (en mi laptop) utilizando sublime text 3, al final para el deploy se comprime el archivo y se carga a la función lambda. La titulo de la funcion es getSalesAndBudget, ahi doy click para ingresar a editar la funcion.

En la parte superior de la siguiente pagina aparece el ARN que es el id de la función que debemos colocar en la sección endpoint de la configuración de la habilidad. Aqui configuramos el desencadenante de nuestra función que seria un llamado de nuestro servicio de voz de alexa, adicionalmente se puede ver una conexión entre nuestra función y Amazon CloudWatch Logs, en este portal podríamos ver todos los logs generados cada vez que nuestra función se ejecuta, incluso aquellos que nosotros mismos generemos usando la instrucción de javascript console.log.

Mas abajo podemos cargar ya nuestra aplicación node js con el boton Cargar.

En cuanto a la aplicación de node js voy a colocar fragmentos de código donde pueden encontrar las partes clave del manejo de la habilidad y su interacción con SAP. GetSalesAndBudget es la función principal, esta función es la que maneja la intención de conocer las ventas y presupuesto.

'GetSalesAndBudget': function () {
        //delegate to Alexa to collect all the required slot values
        var filledSlots = delegateSlotCollection.call(this);

        var oficina=this.event.request.intent.slots.oficina.value;
        var periodo=this.event.request.intent.slots.periodo.value;
        var fieldsRequested=this.event.request.intent.slots.fieldsRequested.value;

        var grupoArt = isSlotValid(this.event.request, "grupoArt");
        
        if (this.event.request.dialogState === "COMPLETED"){
           oficinaId = this.event.request.intent.slots.oficina.resolutions.resolutionsPerAuthority[0].values[0].value.id;
           grupoArtId = "";
           if (this.event.request.intent.slots.grupoArt.resolutions){
            grupoArtId = this.event.request.intent.slots.grupoArt.resolutions.resolutionsPerAuthority[0].values[0].value.id;
           }
           fieldsRequestedId = this.event.request.intent.slots.fieldsRequested.resolutions.resolutionsPerAuthority[0].values[0].value.id;
           periodoSAP = periodo.substr(0, 4) + periodo.substr(5,2);
           
           getDataFromSAP(oficinaId, periodoSAP, fieldsRequestedId, grupoArtId, function(salesValue, budgetValue){
             console.log("Grupo de articulo: " + grupoArt);
             if (!grupoArt){
                grupoArtPartText = "";
                connectorAnd = "";
              }else{
                connectorAnd = " and ";
                grupoArtPartText = " items group " + grupoArt; 
              }
             switch(fieldsRequestedId){
              case "1": //Solo ventas
               speechOutput = "The total sales value for sales office " + oficina + connectorAnd + grupoArtPartText + " on " + periodo + " is " + salesValue + " millions of colombian pesos";
               break;
              case "2": //Solo presupuesto
               speechOutput = "The total budget value for sales office " + oficina + connectorAnd + grupoArtPartText + " on " + periodo + " is " + budgetValue + " millions of colombian pesos";
               break;   
              case "3": //Ventas y presupuesto
               speechOutput = "For sales office " + oficina + ", " + grupoArtPartText + " and month " + periodo + " the total budget value is " + budgetValue + " millions of colombian pesos, and total sales value is " + salesValue + " millions of colombian pesos";
               break;   
             }
             this.response.speak(speechOutput);
             this.emit(":responseReady");
           }.bind(this)); 
           
           //console.log("Grupo de articulo " + grupoArt);
           console.log("Id de la oficina " + this.event.request.intent.slots.oficina.resolutions.resolutionsPerAuthority[0].values[0].value.id);
        }
    },

Esta es la función que conecta con SAP utilizando el llamado a un servicio odata de odata provisioning publicado en SAP Cloud Platform.

function getDataFromSAP(pOficinaId, pPeriodoSAP, pFieldsRequestedId, pGrupoArtId, callback){
  var url = "https://gwaas-pXXXXXXtrial.hanatrial.ondemand.com/odata/SAP/ZSD_XXXXXX_SRV;v=1";
  var entitySet = "/VentasSet";
  var format = "&$format=json";
  var version = "";
  var args = {
                headers: { 
                           "Authorization": "Basic XXXXXXXXXXXXX" 
                         }
  };
  
  if (pFieldsRequestedId === "1"){ 
       version = " and Version eq '000'";
  } else if (pFieldsRequestedId === "2") {
       version = " and Version eq '999'";
  } //Si toma valor 3 que significa leer ventas y ppto la version va vacia
  
  console.log("Requested fields " + pFieldsRequestedId + " Version " + version);
  var filtro = "/?$filter=" + escape("Periodo eq '" + pPeriodoSAP + "' and Oficina eq '" + pOficinaId + "' and GrupoArticulos eq '" + pGrupoArtId + "'" + version);

  client = new Client(); 

  console.log("Url: " + url + entitySet + filtro + format);

  client.get(url + entitySet + filtro + format, args, function(data, response){
    var totalSales = 0;
    var totalBudget = 0; 
    console.log(data.d.results);
    data.d.results.forEach(function(data){
      if (data.Version === "000"){
        totalSales = (Number(data.ValorNeto) / 1000000).toFixed(0).toString();
      } else {
        totalBudget = (Number(data.ValorNeto) / 1000000).toFixed(0).toString();
      }
    });  
    callback(totalSales,totalBudget);
  });  
}

Espero les sirva a los desarrolladores que quieran incursionar con estas tecnologías que nos ofrecen tanto Amazon como SAP para fácilmente crear aplicaciones muy llamativas utilizando entornos híbridos.

Por ultimo los dejo con la demostración que hice en uno de los eventos en los que estuve.

Saludos a todos.

Jhon Jairo.

To report this post you need to login first.

Be the first to leave a comment

You must be Logged on to comment or reply to a post.

Leave a Reply