Skip to Content
Author's profile photo Marty McCormick

Posting Sales Orders into S/4HANA Cloud API – Part 1

This blog will provide an example of how to call the Sales Order API in SAP S/4HANA Cloud (S/4HC) from SAP Cloud Platform Integration (CPI) and post header & line items.  This is the first blog in a two piece series–in this one we’ll look at the implementation and the second blog will show how to execute the scenario using Postman and then will also take a look at the logs generated by the iFlow.

The second blog can be found here.

Scenario

For our fictional scenario, we’ll assume that a customer has a requirement to create sales orders in an S/4HC system with multiple line items from a 3rd party system.  The customer will use the white listed API on SAP API hub for Sales Order creation.

In this blog, we’ll create an iFlow to accept, transform and post the sales order data into the S/4HC system.  What makes this scenario unique is that the OData service API_SALES_ORDER_SRV only supports a deep insert to get line items posted and a simple CREATE is currently not possible to include header and line items.  Therefore, special handling needs to occur in CPI to setup the create deep in the iFlow.  Essentially, we’ll need to add the line items back to the request payload after our mapping step for the header items.

Some information before we get started:

The API we’ll use is “Process Sales Order” which can be found on SAP’s API hub: https://api.sap.com/shell/discover/contentpackage/SAPS4HANACloud/api/API_SALES_ORDER_SRV

You can read more about OData payload structures here: https://blogs.sap.com/2017/09/06/payload-structures-in-odata-v2-adapter-for-sap-cloud-platform-integration/

This blog follows the approach outlined in this blog which covers multiple entities and how to handle them in CPI and should be read before this one: https://blogs.sap.com/2016/06/10/deep-insert-suport-in-odata-provisioning-in-hci/

On a side note: There is a great guide attached to scope item 1RW that provides an example of side by side extensibility—it has very similar concepts to the ones discussed in this blog (consuming/ updating S/4HC via whitelisted APIs).  https://rapid.sap.com/bp/#/browse/scopeitems/1RW

Configure S/4HANA Cloud to enable Sales Order API

In order to enable the APIs, a communication user, communication system and the communication arrangement need to be created.  This is well documented already in section 3.2 of the 1RW scope item setup guide.

The Communication arrangement for sales order API is SAP_COM_0109.

CPI Scenario

In our sample application, we will expose an HTTP service that accepts a JSON payload to “simulate” the 3rd party sending the sales order to the interface on CPI.  The iFlow will then transform that data for creating sales order(s) with multiple line items in order to post into S/4HC.

Setting up Mock Sender System

Normally, an external system would send the sales order to CPI for posting in S/4HC.  However, here we will expose an HTTP service on CPI to accept a JSON payload and use the Postman application to send sales orders via JSON manually.

In order to get an xsd file for the mapping step on CPI, I took a sample JSON payload that I used to call the API directly from Postman and converted to XML and then converted the XML to XSD using online tools.

Here are the tools I used and steps followed:

For converting JSON to XSL:

tool: http://www.utilities-online.info/xmltojson/#.Wj03y99KtPY

The JSON I used to convert into XML.

{
"salesOrder":[
	{ "salesOrderType":[
		{
	   "SoldToParty": "USCU_L02",
	   "DistributionChannel": "10",
	   "SalesOrderType": "OR",
	   "OrganizationDivision": "00",
	   "SalesOrganization": "1710",
	   "PurchaseOrderByCustomer": "45000049106" ,
		"lineItem": [
      {
         "Material": "MZ-TG-Y200",
         "SalesOrderItem": "10",
         "RequestedQuantityUnit": "PC",
         "NetAmount": "89"

      },
      {
         "Material": "MZ-FG-M550",
		 "SalesOrderItem": "20",
         "RequestedQuantityUnit": "PC",
         "NetAmount": "75"

      }
   ]
		}
   ]
	}
   ]
}

 

Then convert generated XML to XSD using xmlgrild tool: http://xmlgrid.net/xml2xsd.html

Now you have generated the xsd file to use in the mapping step in the iFlow.

iFlow Development

High Level Flow

At a high level, there is a “Sender” in this scenario, which is the HTTPS call simulating another system submitting sales order via JSON.  Then the iFlow converts the payload to XML and extracts the sales order line items to a header variable.  Then the mapping to the SalesOrder API occurs followed by another Groovy script to (“RecreatePayload”) to add the line item entities back to the payload.  The payload is logged (informational purposes only) and then submitted to the S/4HC system (Request-Reply).  Finally, I log the salesOrder response as well just for informational purposes.

Obviously, you wouldn’t need the scripts that only log the payload before and after the Request-Reply but I did this to show the payloads along the flow, which we’ll take a look at in the next blog.

 

 

iFlow Details

HTTP Sender

The HTTPS sender has a path created for https access and the user role required for access.  In this example, I assigned my CPI tenant user ESBMessaging.Send role (this would be an S user or I number, for example).  The URL would be available as follows:

https://####-iflmap.hcisbt.us2.hana.ondemand.com/http/salesorders

 

JSON to XML Converter

The JSON to XML Converter is just the default converter settings

 

ExtractLineItemsToHeader

This  groovy script object iterates the payload and takes all of the <lineItems> and places them into the header variable “InlineEntry” in the correct XML format that the API expects (A_SalesOrderItemType).  Also, note that the Sales Order API expects SalesOrder and SalesOrderItem entries, and in this case they are sent with blank values for each line item (since SalesOrder # doesnt exist yet).

/*
The integration developer needs to create the method processData
This method takes Message object of package com.sap.gateway.ip.core.customdev.util
which includes helper methods useful for the content developer:
The methods available are:
public java.lang.Object getBody()
public void setBody(java.lang.Object exchangeBody)
public java.util.Map<java.lang.String,java.lang.Object> getHeaders()
public void setHeaders(java.util.Map<java.lang.String,java.lang.Object> exchangeHeaders)
public void setHeader(java.lang.String name, java.lang.Object value)
public java.util.Map<java.lang.String,java.lang.Object> getProperties()
public void setProperties(java.util.Map<java.lang.String,java.lang.Object> exchangeProperties)
public void setProperty(java.lang.String name, java.lang.Object value)
*/

import com.sap.gateway.ip.core.customdev.util.Message;
import java.util.*;
import com.sap.gateway.ip.core.customdev.logging.*;
import java.util.HashMap;
import java.text.SimpleDateFormat;
import java.util.Date;

import org.w3c.dom.Document;
import org.w3c.dom.NodeList;
import org.w3c.dom.Node;

def Message processData(message) {

def headers = message.getHeaders();
StringBuffer reqXML = new StringBuffer();

def payload = message.getBody(java.lang.String) as String;
def tokens = payload.split("(?=<)|(?<=>)");
def salesitems = false;
def haslineitems = false;

for(int i=0;i<tokens.length;i++) {
//begin writing to stream for lineitems
if(tokens[i]=="<lineItem>")
{
salesitems = true;
haslineitems = true;
}
else if(tokens[i] =="</salesOrderType>")
{
salesitems = false;
}
if(salesitems == true)
{
reqXML.append(tokens[i]);
}
}

def newdata = reqXML.toString();
newdata = newdata.replaceAll("<lineItem>","<A_SalesOrderItemType><SalesOrder></SalesOrder><SalesOrderItem></SalesOrderItem>");
newdata = newdata.replaceAll("</lineItem>","</A_SalesOrderItemType>");
newdata = newdata.replaceAll("<NetAmount>","<RequestedQuantity>");
newdata = newdata.replaceAll("</NetAmount>","</RequestedQuantity>");

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

for (header in headers) {
messageLog.setStringProperty("header." + header.getKey().toString(), header.getValue().toString())
}

for (property in properties) {
messageLog.setStringProperty("property." + property.getKey().toString(), property.getValue().toString())
}

if(messageLog != null){
messageLog.addAttachmentAsString("beforeMapping", newdata, "text/plain");
}

if (newdata != null) {
// log.logErrors(LogMessage.TechnicalError, "reqXML: "+ reqXML);
message.setHeader("InlineEntry", newdata);
log.logErrors(LogMessage.TechnicalError, "message header: "+ message.getHeaders().get("InlineEntry"));
}

return message;
}

 Mapping1

Mapping of xsd to the API A_SalesOrder

 

RecreatePayload

This groovy script adds the lineItems back to the payload before making the call to S/4HC.

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

def Message processData(message) {
String payload = new String(message.getBody(), "UTF-8");
log.logErrors(LogMessage.TechnicalError, "payload in script2: "+payload);
StringBuffer newReqXML = new StringBuffer();
String inlineEntry = message.getHeaders().get("InlineEntry");
log.logErrors(LogMessage.TechnicalError, "InlineEntry: "+inlineEntry);
def tokens = payload.split("(?=<)|(?<=>)");
log.logErrors(LogMessage.TechnicalError, "tokens: "+tokens);
for(int i=0;i<tokens.length;i++) {
log.logErrors(LogMessage.TechnicalError, "tokens: "+tokens[i]);
newReqXML.append(tokens[i]);
if(tokens[i].contains("<to_Item>")){
newReqXML.append(inlineEntry+"</to_Item></A_SalesOrderType></A_SalesOrder>");
break;
}
}

log.logErrors(LogMessage.TechnicalError, "newReqXML: "+newReqXML.toString());
message.setBody(newReqXML.toString());
return message;

}

 

LogPayloadBeforeSubmission

A simple groovy script to log the payload before sending to S/4HC.

/*
The integration developer needs to create the method processData
This method takes Message object of package com.sap.gateway.ip.core.customdev.util
which includes helper methods useful for the content developer:
The methods available are:
public java.lang.Object getBody()
public void setBody(java.lang.Object exchangeBody)
public java.util.Map<java.lang.String,java.lang.Object> getHeaders()
public void setHeaders(java.util.Map<java.lang.String,java.lang.Object> exchangeHeaders)
public void setHeader(java.lang.String name, java.lang.Object value)
public java.util.Map<java.lang.String,java.lang.Object> getProperties()
public void setProperties(java.util.Map<java.lang.String,java.lang.Object> exchangeProperties)
public void setProperty(java.lang.String name, java.lang.Object value)
*/

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

def Message processData(Message message) {
def body = message.getBody(java.lang.String) as String;
def messageLog = messageLogFactory.getMessageLog(message);
def propertyMap = message.getProperties()

messageLog.setStringProperty("BeforeSubmission", "Printing Payload As Attachment")
messageLog.addAttachmentAsString("Payload Before Submission After Mappings:", body, "text/xml");

return message;
}

Request-Reply

This is the call to S/4HC SalesOrder API via Request-Reply OData adapter to post the sales order data to the API.

 

salesOrderPayloadResponse

Same script as previous that logs the response from the S/4HC reply which is the new sales order data.

/*
The integration developer needs to create the method processData
This method takes Message object of package com.sap.gateway.ip.core.customdev.util
which includes helper methods useful for the content developer:
The methods available are:
public java.lang.Object getBody()
public void setBody(java.lang.Object exchangeBody)
public java.util.Map<java.lang.String,java.lang.Object> getHeaders()
public void setHeaders(java.util.Map<java.lang.String,java.lang.Object> exchangeHeaders)
public void setHeader(java.lang.String name, java.lang.Object value)
public java.util.Map<java.lang.String,java.lang.Object> getProperties()
public void setProperties(java.util.Map<java.lang.String,java.lang.Object> exchangeProperties)
public void setProperty(java.lang.String name, java.lang.Object value)
*/

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

def Message processData(Message message) {
def body = message.getBody(java.lang.String) as String;
def messageLog = messageLogFactory.getMessageLog(message);
def propertyMap = message.getProperties()

messageLog.setStringProperty("Logging#1", "Printing Payload As Attachment")
messageLog.addAttachmentAsString("ResponsePayload:", body, "text/xml");

return message;

}

That’s it!  The iFlow is ready to be tested, which we’ll cover in the next blog.

 

 

 

Assigned Tags

      8 Comments
      You must be Logged on to comment or reply to a post.
      Author's profile photo Pavan kumar
      Pavan kumar

      Great blog Marty.Well done!

      If I read correctly,JSON To XML Converter in your IFLOW as a first step converts the incoming JSON format to XML from the external service call?Wont you face any errors with the JSON format which you used in the second screen shot?Looks like JSON To XML converter is having certain limitations as per the below KBA.

      https://apps.support.sap.com/sap/support/knowledge/preview/en/2478282

      Today I did a call using Request-Reply and extracted the JSON format from the openweather Rest api and used JSON To XML converter step and ended up with errors.Kindly shed some light on this,what was missing from my side.

      Cheers

      Pavan

       

      Author's profile photo Marty McCormick
      Marty McCormick
      Blog Post Author

      Hi Pavan

      I am aware of this note but did not face any errors using the JSON exactly as shown in the blog and having it convert correctly to XML.  I have had to tweak the structure slightly in the past in other cases though as per note.

      Regards,

      Marty

      Author's profile photo Pavan kumar
      Pavan kumar

      Hi Marty,

      Thanks for the reply.I am on latest CPI tenant and S/4HC 1802 versions however in CPI with JSONToXML converter I am facing serious issues even with simple structures.I am busy investigating by enabling the payload log before JSONToXML converter step.So far for me, in my IFLOWs JSONToXML converter is throwing issues when I configured it as a first step in IFLOW(that was my understanding so far and Its weird) however the same works after configuring XMLToJSON converter

      Sender HTTPS--->Start-->JSONToXML-->XMLToJSON-->MessageMapping--->End-->Receiver ODATA Adapter With Post Operation -----Failed at First Step at JSONToXML converter as I can see in the trace

      Sender SOAP(SYN)--->Start-->XMLToJSON-->JSONToXML-->MessageMapping--->End-->Receiver ODATA Adapter With Post Operation ----- Record successfully created in S4HC by calling Manage WorkForce Availability Entity .

      Author's profile photo Christoph Bürgmayr-Posseth
      Christoph Bürgmayr-Posseth

       

      Hi Marty,

      thanks for the input, is it possible that you send / post to the blog, the final JSON statement which was jused for the posting of this API?

      This would help a lot, as we are facing some issues when trying to make a testload to our starter system.

      Thanks

      Christoph

      Author's profile photo girdhari mondal
      girdhari mondal

      Hi Marty,

      I was just checking this API in API.SAP.COM and found that there is no POST method for node A_SalesOrderHeaderParther Type . Is that means we can't use this node to pass Sold To Party and Ship To Party while trying to create sales order?

      Also I saw there are some new fields gets added in Header Node in version 1805. We have S/4 HANA on perm 1709. Is there any note to update the API with latest version or we have to go version upgrade? We have to use one of header node filed(AssignmentReference)

       

      Regards,

      Girdhari

      Author's profile photo Amitabh Dubey
      Amitabh Dubey

       

      Hello Marty,

      That's really a great blog when we looking at integrating 3rd party partners with S4HANA Cloud via API Calls. Both Part-1 and Part-2 are informative.

      But I have a different problem and thought of reaching out to you if you could help. I am following scope item 2EL and stuck while sending the PO from On-Premise S4HANA to the cloud. I have raised this issue in another link

      By the way I have completed integrations process for scope item 2EJ. And below are my observations.

      • I could finish 2EJ because I could manipulate the information in inbound on-premise
      • We cannot do the same in Cloud because we do not have any options to debug and find the root-cause
      • SAP does not support with data related issues and at the same time we cannot find out what is the issue
      • The AIF Framework(message monitor dashboard) only shows the error. No further troubleshooting is enabled in S4 Cloud giving us very limited scope. I am not sure if I need to enable some other things for this.

      It would really be a great help if you can look into this.

      Regards

      Amitabh

       

      Author's profile photo SAKRIYANAIK NK
      SAKRIYANAIK NK

      Excellent Blog –Very much useful when we do the integration with logistic partners for picking purchase orders.

      Author's profile photo Ghadeer Zahaiqa
      Ghadeer Zahaiqa

      hi Marty

      thank you for nice and informative blog

      am new to CPI , could you please tell me in real time how the situation will be for sender i mean what tool that they could use to send data and what will be the data format