SAP Integration Suite, Cloud Integration

Publishing Events from S/4 HANA to Event Mesh for Cloud integration for external systems

For the past few months, I have been working on an S/4 HANA new implementation leveraging the SAP integration suite – Cloud Integration, Event Mesh, API Hub, Integration advisor, etc. In this blog, I wanted to capture sending out business transaction events from S/4 HANA to the Event Mesh and having CPI read from the Event Mesh and then process the event, read additional data from S/4 HANA using the OData API to enrich the message, and then publish to an outbound queue for downstream systems.

So we set this up and we were sending sales order creations and changes to the event mesh:

Maintain the channel topics for publishing the business events

Then we create the queue in Event Mesh and the queue subscription to subscribe to the sales order create and change events:

For our testing, we used the SAP transaction code SWUE to trigger the sales order event to send to the queue. Once this is all set up, the events will be triggered automatically when creating or changing a sales order:

Transaction SWUE to trigger sales order creation event
Transaction SWUE to trigger sales order created

Here are the sales order in the queue:

Queue with sales order events

So once we receive the sales order business transaction events in the queue, we can then read the queue and process the message to enhance the details through the OData call to S/4 HANA and publish on an outbound queue.

Here is the IFlow:

SalesOrderEventFromQ IFlow
AMQP General tab for reading queue
AMQP Connection tab for reading queue
AMQP Processing tab for reading queue

Here is the Groovy script to extract the sales order number:

// Extract the SALESORDER from the Business Transaction Event


import com.sap.gateway.ip.core.customdev.util.Message;
import groovy.json.*

def Message processData(Message message) {
    def json = new JsonSlurper().parseText(message.getBody(String));
    message.setHeader('SALESORDER',json.data.KEY[0].SALESORDER);
    message.setHeader('eventType',json.eventType);

    return message;
}

Here we log the business transaction event with the sales order number we extracted:

import com.sap.gateway.ip.core.customdev.util.Message;
import java.util.HashMap;

def Message processData(Message message) {

   def map = message.getHeaders();
   def salesorder = map.get("SALESORDER");
   def eventType = map.get("eventType");

   
   def body = message.getBody(java.lang.String) as String;
   def messageLog = messageLogFactory.getMessageLog(message);


    //Properties
    def properties = message.getProperties();
    
   String sBody = "SalesOrder " + salesorder  + " Event " + eventType + " from S4 HANA";


   if(messageLog != null) {
       messageLog.setStringProperty(sBody, body);
       messageLog.addAttachmentAsString(sBody, body, 'application/json');
    }

   return message;
}

Here is the Request-Reply to call the OData sales order API to get the order details:

OData General tab
OData connection tab
OData processing tab

Here is the Log SAP OData XML Response:

import com.sap.gateway.ip.core.customdev.util.Message;
import java.util.HashMap;

def Message processData(Message message) {

   def map = message.getHeaders();
   def salesorder = map.get("SALESORDER");
   
   def body = message.getBody(java.lang.String) as String;
   def messageLog = messageLogFactory.getMessageLog(message);

    //Properties
    def properties = message.getProperties();
    
   String sBody = "OData SalesOrder " + salesorder + " Details XML from S4 HANA";


   if(messageLog != null) {
       messageLog.setStringProperty(sBody, body);
       messageLog.addAttachmentAsString(sBody, body, 'text/xml');
    }

   return message;
}

Here is the Log SAP OData JSON Response:

import com.sap.gateway.ip.core.customdev.util.Message;
import java.util.HashMap;

def Message processData(Message message) {

   def map = message.getHeaders();
   def salesorder = map.get("SALESORDER");
   
   def body = message.getBody(java.lang.String) as String;
   def messageLog = messageLogFactory.getMessageLog(message);

    //Properties
    def properties = message.getProperties();
    
   String sBody = "OData SalesOrder " + salesorder + " Details JSON from S4 HANA";


   if(messageLog != null) {
       messageLog.setStringProperty(sBody, body);
       messageLog.addAttachmentAsString(sBody, body, 'application/json');
    }

   return message;
}

And here is the final publishing to the outbound queue:

AMQP General tab for writing to queue
AMQP Connection tab for writing to queue
AMQP Processing tab for writing to queue

So once the event is written to the queue and the Flow is triggered, then you can see the following messages in the integration message monitoring:

Message monitoring

Here is how the event json looks like:

SalesOrder 0000000494 Event BO.SalesOrder.Created from S4 HANA

{
  "eventType": "BO.SalesOrder.Created",
  "cloudEventsVersion": "0.1",
  "source": https://sap.corp,
  "eventID": "AvI8lTL3HuyLryFqiGugrw==",
  "eventTime": "2021-10-15T05:30:44Z",
  "schemaURL": https://sap.corp/sap/opu/odata/IWXBE/BROWSER_SRV/,
  "contentType": "application/json",
  "data": {
    "KEY": [
      {
        "SALESORDER": "0000000494"
      }
    ]
  }
}

OData SalesOrder 0000000494 Details XML from S4 HANA

<A_SalesOrder>
  <A_SalesOrderType>
    <CreationDate>2021-10-21T00:00:00.000</CreationDate>
    <CreatedByUser>YTHAM</CreatedByUser>
    <OrganizationDivision>00</OrganizationDivision>
    <PurchaseOrderByCustomer>YT_ODN_AU</PurchaseOrderByCustomer>
    <DistributionChannel>10</DistributionChannel>
    <SalesOrganization>3010</SalesOrganization>
    <SoldToParty>30100001</SoldToParty>
    <TotalNetAmount>120.00</TotalNetAmount>
    <to_Item>
      <A_SalesOrderItemType>
        <SalesOrderItemText>Service Material 01</SalesOrderItemText>
        <ProductionPlant>3010</ProductionPlant>
        <PurchaseOrderByCustomer>YT_ODN_AU</PurchaseOrderByCustomer>
        <Material>SM0001</Material>
        <MaterialByCustomer>
        </MaterialByCustomer>
        <RequestedQuantityUnit>H</RequestedQuantityUnit>
        <TransactionCurrency>AUD</TransactionCurrency>
        <PricingDate>2021-10-21T00:00:00.000</PricingDate>
        <NetAmount>120.00</NetAmount>
        <SalesOrderItemCategory>TAD</SalesOrderItemCategory>
        <SalesOrderItem>10</SalesOrderItem>
        <HigherLevelItem>0</HigherLevelItem>
        <RequestedQuantity>1.000</RequestedQuantity>
        <SalesOrder>494</SalesOrder>
      </A_SalesOrderItemType>
    </to_Item>
    <TransactionCurrency>AUD</TransactionCurrency>
    <SalesOrderType>OR</SalesOrderType>
    <SalesOrderDate>2021-10-21T00:00:00.000</SalesOrderDate>
    <to_Partner>
      <A_SalesOrderHeaderPartnerType>
        <PartnerFunction>SP</PartnerFunction>
        <Customer>30100001</Customer>
        <Supplier>
        </Supplier>
        <Personnel>0</Personnel>
        <ContactPerson>0</ContactPerson>
        <SalesOrder>494</SalesOrder>
      </A_SalesOrderHeaderPartnerType>
      <A_SalesOrderHeaderPartnerType>
        <PartnerFunction>BP</PartnerFunction>
        <Customer>30100001</Customer>
        <Supplier>
        </Supplier>
        <Personnel>0</Personnel>
        <ContactPerson>0</ContactPerson>
        <SalesOrder>494</SalesOrder>
      </A_SalesOrderHeaderPartnerType>
      <A_SalesOrderHeaderPartnerType>
        <PartnerFunction>PY</PartnerFunction>
        <Customer>30100001</Customer>
        <Supplier>
LanguageHTML/XML

        </Supplier>
        <Personnel>0</Personnel>
        <ContactPerson>0</ContactPerson>
        <SalesOrder>494</SalesOrder>
      </A_SalesOrderHeaderPartnerType>
      <A_SalesOrderHeaderPartnerType>
        <PartnerFunction>SH</PartnerFunction>
        <Customer>30100001</Customer>
        <Supplier>
        </Supplier>
        <Personnel>0</Personnel>
        <ContactPerson>0</ContactPerson>
        <SalesOrder>494</SalesOrder>
      </A_SalesOrderHeaderPartnerType>
    </to_Partner>
    <SalesOrder>494</SalesOrder>
  </A_SalesOrderType>
</A_SalesOrder>

OData SalesOrder 0000000494 Details JSON from S4 HANA

{
  "A_SalesOrder": {
    "A_SalesOrderType": {
      "CreationDate": "2021-10-21T00:00:00.000",
      "CreatedByUser": "YTHAM",
      "OrganizationDivision": "00",
      "PurchaseOrderByCustomer": "YT_ODN_AU",
      "DistributionChannel": "10",
      "SalesOrganization": "3010",
      "SoldToParty": "30100001",
      "TotalNetAmount": "120.00",
      "to_Item": {
        "A_SalesOrderItemType": {
          "SalesOrderItemText": "Service Material 01",
          "ProductionPlant": "3010",
          "PurchaseOrderByCustomer": "YT_ODN_AU",
          "Material": "SM0001",
          "MaterialByCustomer": "",
          "RequestedQuantityUnit": "H",
          "TransactionCurrency": "AUD",
          "PricingDate": "2021-10-21T00:00:00.000",
          "NetAmount": "120.00",
          "SalesOrderItemCategory": "TAD",
          "SalesOrderItem": "10",
          "HigherLevelItem": "0",
          "RequestedQuantity": "1.000",
          "SalesOrder": "494"
        }
      },
      "TransactionCurrency": "AUD",
      "SalesOrderType": "OR",
      "SalesOrderDate": "2021-10-21T00:00:00.000",
      "to_Partner": {
        "A_SalesOrderHeaderPartnerType": [
          {
            "PartnerFunction": "SP",
            "Customer": "30100001",
            "Supplier": "",
            "Personnel": "0",
            "ContactPerson": "0",
            "SalesOrder": "494"
          },
          {
            "PartnerFunction": "BP",
            "Customer": "30100001",
            "Supplier": "",
            "Personnel": "0",
            "ContactPerson": "0",
            "SalesOrder": "494"
          },
          {
            "PartnerFunction": "PY",
            "Customer": "30100001",
            "Supplier": "",
            "Personnel": "0",
            "ContactPerson": "0",
            "SalesOrder": "494"
          },
          {
            "PartnerFunction": "SH",
            "Customer": "30100001",
            "Supplier": "",
            "Personnel": "0",
            "ContactPerson": "0",
            "SalesOrder": "494"
          }
        ]
      },
      "SalesOrder": "494"
    }
  }
}

Lastly, I wanted to add some troubleshooting tips. When we first configured the outbound topic, the events were not being sent out to the message queue. So we had to create a ticket for SAP and we were told that the entries:

The issue is because of 2 wrong entries maintained in the table SBO_I_BODEF for Sales Order. Please find the attached screenshot.

Wrong Entries:

SALES ORDER BO BUS2032

SALES ORDER SOA 114

Expected Entries:

SalesOrder BO BUS2032

SalesOrder CL CL_SD_SALESORDER_WORKFLOW

SalesOrder SOA 114

Expected entries are there in the table. Since there are wrong entries also present in the table for the same Business Object, events are not getting passed. Please remove those entries and retest the issue.

Maintain Customizing settings under

SAP NetWeaver->Application Server->Business Management->SAP Object Type Repository->SAP Object Types->Maintain Object Representation

SPRO – Maintain object representation

Make sure the following entries are there for BUS2032. There should not be another set of entries there for BUS2032:

Sales order entries in object representation

Hopefully, this will help if you are embarking on the journey of publishing events from S/4 HANA to downstream subscribers. Though this is the evolution of SAP integration with events and OData, the IDoc and SOAP async approaches still are widely used and maybe the better choice given the scenarios. After having set up IDoc integration and SOAP integration through PI/PO for many projects, the new event mesh approach is a nice option to have also in the wide array of SAP integration methodolgies.