Introduction
This is officially part II of the series that started with SAP EDI/IDoc Communication in CPI Using Bundling and Fewer Mapping Artifacts, which will deal with handling incoming EDI communication, and reuse capabilities of the outgoing communication flow shown in the first part to send functional acknowledgements.
Notes
Archiving was not addressed because we do not have a CMS system that can be used with the latest features that have been made available. The archiving function is also indiscriminate and does not offer functionality to peek at the contents to determine if archiving is deemed necessary. Any custom archiving function that evaluates contents would execute for every message split iteration, even if it only makes sense to save one archive once at the beginning of message processing. At this time a Gather step was also not evaluated to package IDocs, but there are plans to do so in the future when we implement something aside from incoming 997s. One very important note about handling a Gather step is that it would be advisable to not accept messages with multiple functional groups in a single interchange – e.g. 944s and 945s at the same time which would map to WMMBXY and SHPCON respectively, and have the IDoc XML co-mingled after the Gather step. This means that the core EDI processing flow has not been tested fully, but it follows the same implementation strategy of the outgoing messages from part I of the series (The only difference between the two being that outgoing messages offer extended post-processing and incoming messages offer extended pre-processing).
Transformation Flow
The transformation flow consists of two main branches:
- The functional acknowledgement handling based on the envelope inspection and validation handling of the EDI splitter.
- The core EDI transformation flow that is split further into two types of processing – i.e. recording 997s as SYSTAT01 IDocs or converting agreed upon EDI messages into the necessary IDocs for posting.
Initial Processing and EDI Splitter
The initial content modifier step allows setup of basic information for IDoc posting – e.g. Logical system, client, receiver partner name, etc. Not a whole lot to say about the EDI splitter step other than its use is well documented here – Define EDI Splitter.
Functional acknowledgement vs. EDI Handling
The functional acknowledgement branch handling is determined by the following evaluation of the splitter outcome.
There is one additional content modifier step in the functional acknowledgement branch to pass the value from the SenderPid header to the pid header, which is used in the outgoing communication flow from SAP EDI/IDoc Communication in CPI Using Bundling and Fewer Mapping Artifacts. Otherwise, the default route passes the split message to the core EDI branch for processing.
Lookup Partner Direction Information and Execute Basic Partner Validation
After gathering some key headers from the message the information is evaluated first to determine if a message has been received by an unknown partner or has been directed to an unknown partner. In either of those cases the message is flagged in error status, and after saving the payload as an attachment the message body is adjusted to contain an alert. The message will then be directed in the next steps of the flow to trigger an email to the interested parties, and have the message end in escalation status such that it can be reviewed. If basic message validation is successful then core message data is read to determine the IDoc target (and anything we choose to add later). The last steps of the script get IDoc partner information for the sender and all relevant mapping XSL transforms along with partner agreement data that will reflect if extended pre processing should be executed on the message. The 997 messages differ slightly in processing where IDoc numbers are retrieved from a datastore and mappings are not called from the partner directory, hence the exclusion in the associated if block.
import com.sap.gateway.ip.core.customdev.util.Message
import java.util.HashMap
import com.sap.it.api.pd.PartnerDirectoryService
import com.sap.it.api.ITApiFactory
import groovy.json.*
def Message processData(Message message) {
def service = ITApiFactory.getApi(PartnerDirectoryService.class, null)
if (service == null){
throw new IllegalStateException("Partner Directory Service not found")
}
def headers = message.getHeaders()
def SenderPid = headers.get("SenderPid")
def ReceiverPid = headers.get("ReceiverPid")
def std = headers.get("SAP_EDI_Document_Standard")
def stdmes = headers.get("SAP_EDI_Message_Type")
def stdvrs = headers.get("SAP_EDI_Message_Version")
def sndql = headers.get("SAP_EDI_Sender_ID_Qualifier")
def sndid = headers.get("SAP_EDI_Sender_ID")
def rcvql = headers.get("SAP_EDI_Receiver_ID_Qualifier")
def rcvid = headers.get("SAP_EDI_Receiver_ID")
def intchg = headers.get("SAP_EDI_Interchange_Control_Number")
def x12qualifier = service.getParameter("x12_qualifier", SenderPid, java.lang.String.class)
def x12id = service.getParameter("x12_id", SenderPid, java.lang.String.class)
def messageLog = messageLogFactory.getMessageLog(message)
def partnerError = false
if(x12qualifier != sndql || x12id != sndid.trim()) {
message.setProperty("UnknownSender", "true")
messageLog = messageLogFactory.getMessageLog(message)
messageLog.setStringProperty("Unknown Sender", "Sender ID - " + sndql + ":" + sndid.trim() + " not registered for partner " + SenderPid)
partnerError = true
}
def our_x12qualifier = service.getParameter("x12_qualifier", ReceiverPid, java.lang.String.class)
def our_x12id = service.getParameter("x12_id", ReceiverPid, java.lang.String.class)
if(our_x12qualifier != rcvql || our_x12id != rcvid.trim()) {
message.setProperty("UnknownReceiver", "true")
messageLog.setStringProperty("Unknown Receiver", "Receiver ID - " + rcvql + ":" + rcvid.trim() + " not registered")
partnerError = true
}
if(partnerError) {
def body_bytes = message.getBody(byte[].class)
messageLog.addAttachmentAsString("Payload", new String(body_bytes, "UTF-8"), "text/plain")
message.setBody("Unknown sender or receiver partner, please check escalated messages for details")
}
def slurper = new JsonSlurper()
def target
def msgParamsBinary = service.getParameter(stdmes, ReceiverPid, com.sap.it.api.pd.BinaryData)
if(msgParamsBinary != null) {
def msgParameters = slurper.parse(msgParamsBinary.getData(), "UTF-8")
target = msgParameters.Inbound.Target
}
def alternativePartnerId = service.getAlternativePartnerId("SAP SE", "IDOC", SenderPid)
message.setHeader("SenderPartner", alternativePartnerId)
def partnerType = service.getParameter("PartnerType", SenderPid, java.lang.String.class)
message.setHeader("SenderPartnerType", partnerType)
def msgInfo = std + "_" + stdmes + "_" + stdvrs
message.setHeader("SND_CONVERSION_XSD", "pd:" + ReceiverPid + ":" + msgInfo + ":Binary")
if(stdmes != "997") {
message.setHeader("PREPROC_XSLT", "pd:" + ReceiverPid + ":" + msgInfo + "_preproc:Binary")
message.setHeader("MAPPING_XSLT", "pd:" + ReceiverPid + ":" + msgInfo + "_to_" + target + ":Binary")
message.setHeader("POSTPROC_XSLT", "pd:" + ReceiverPid + ":" + target + "_postproc:Binary")
def agreementParamsBinary = service.getParameter("Agreements", ReceiverPid, com.sap.it.api.pd.BinaryData)
if(agreementParamsBinary != null) {
def agreementParameters = slurper.parse(agreementParamsBinary.getData(), "UTF-8")
agreementParameters.Agreements.each { agreement ->
String msg = agreement.Message
if(msg == msgInfo) {
if(agreement.DoExtendedPreProcessing == "true") {
message.setProperty("DoExtPreprocessing", agreement.DoExtendedPreProcessing)
message.setHeader("EXT_PREPROC_XSLT", "pd:" + SenderPid + ":ext_" + msgInfo + "_preproc:Binary")
}
}
}
}
}
return message
}
Map 997
The map 997 local integration process has two core steps – read IDoc numbers from the data store (with delete) and map the incoming 997 accepted/rejected status into the corresponding 16/17 status for the associated IDocs. Below is a screenshot of the configuration of the datastore operation and the XSL transform to generate the STATUS.SYSTAT01 IDocs. Make note that the T/P indicator for the test/productive is passed to the IDoc control record along with the interchange, group and transaction control numbers for correlation purposes. An exception process is added with a message end result to ensure that should the data store read fail that the message will not go into a repetitive JMS retry loop. This outcome would result in some outgoing IDocs that cannot be correlated to an acknowledgement because the interchange no longer exists in the data store (hopefully this should never happen).
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:multimap="http://sap.com/xi/XI/SplitAndMerge" xmlns:hci="http://sap.com/it/" exclude-result-prefixes="hci">
<xsl:param name="Client"/>
<xsl:param name="SAP_ISA_Usage_Indicator"/>
<xsl:param name="SenderPort"/>
<xsl:param name="SenderPartnerType"/>
<xsl:param name="SenderPartner"/>
<xsl:param name="LogicalSystem"/>
<xsl:param name="SAP_EDI_Interchange_Control_Number"/>
<xsl:template match="/">
<SYSTAT01>
<IDOC BEGIN="1">
<EDI_DC40 SEGMENT="1">
<TABNAM>EDI_DC40</TABNAM>
<MANDT><xsl:value-of select="$Client"/></MANDT>
<DIRECT>2</DIRECT>
<TEST>
<xsl:choose>
<xsl:when test="$SAP_ISA_Usage_Indicator = 'T'">X</xsl:when>
<xsl:otherwise/>
</xsl:choose>
</TEST>
<IDOCTYP>SYSTAT01</IDOCTYP>
<MESTYP>STATUS</MESTYP>
<SNDPOR><xsl:value-of select="$SenderPort"/></SNDPOR>
<SNDPRT><xsl:value-of select="$SenderPartnerType"/></SNDPRT>
<SNDPRN><xsl:value-of select="$SenderPartner"/></SNDPRN>
<RCVPRT>LS</RCVPRT>
<RCVPRN><xsl:value-of select="$LogicalSystem"/></RCVPRN>
</EDI_DC40>
<xsl:for-each select="*/multimap:Message1/*/*/G_AK2">
<xsl:variable name="position" select="number(S_AK2/D_329)"/>
<E1STATS SEGMENT="1">
<TABNAM>EDI_DS</TABNAM>
<DOCNUM><xsl:value-of select="../../../../multimap:Message2/Interchange/DOCNUM[$position]"/></DOCNUM>
<STATUS>
<xsl:choose>
<xsl:when test="S_AK5/D_717 = 'A'">16</xsl:when>
<xsl:otherwise>17</xsl:otherwise>
</xsl:choose>
</STATUS>
<REFINT><xsl:value-of select="$SAP_EDI_Interchange_Control_Number"/></REFINT>
<REFGRP><xsl:value-of select="../S_AK1/D_28"/></REFGRP>
<REFMES><xsl:value-of select="S_AK2/D_329"/></REFMES>
</E1STATS>
</xsl:for-each>
</IDOC>
</SYSTAT01>
</xsl:template>
</xsl:stylesheet>
Map Interchange
The map interchange local integration process consists of the four core steps (extended pre is optional) – extended X12 pre-processing, X12 pre-processing, mapping and IDoc post-processing. The extended pre-processing artifacts exist in the sender assigned space of the partner directory, whilst the remaining objects are centralized in our own designated space.
The Last Step
The final content modifier is there to set a header for pid which is used in the communication flow, but it differs from the value set in the functional acknowledgement branch because in this case it will be assigned the value from ReceiverPid. The use of a single header parameter pid with the additional content modifier steps in this integration flow allowed for the reuse of the existing outgoing communication flow created in part I for partner communication and IDoc communication to our systems. Yay for reuse and less artifacts!
Conclusion
Some core functionalities for EDI processing have not been explored yet (archiving, gather for incoming IDoc bundling noted above), so I will probably be undertaking a part III in the future. However, this build out enables the core communication flow into an SAP environment and felt it was worthwhile to share the experience with other integration techies. I encourage folks to comment, like, and ask any questions. Cheers!
References
https://help.sap.com/docs/CLOUD_INTEGRATION/987273656c2f47d2aca4e0bfce26c594/584a3beb81454d40acb052ae371c7063.html?q=edi%20splitter
Source: https://blogs.sap.com/2022/05/22/sap-edi-idoc-communication-in-cpi-using-bundling-and-fewer-mapping-artifacts-part-ii/?utm_source=dlvr.it&utm_medium=linkedin