Skip to Content
Technical Articles
Author's profile photo Ryan Crosby

SAP EDI/IDoc Communication in CPI Using Bundling and Fewer Mapping Artifacts Part II

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:

  1. The functional acknowledgement handling based on the envelope inspection and validation handling of the EDI splitter.
  2. 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.

Transformation%20Flow

 

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.

Main%20branch%20split

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")
    }
	
    // Read partner data, and EDI message information for conversion and 
    // communication purposes
    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")
    
    // Get their id and qualifier, and our id and qualifier for matching and 
    // validation
    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
    }
    // Only need to log payload once in case both errors are triggered and set body for alert 
    // to check escalation logs
    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")
    }
    
    // Read extra parameters for message handling - e.g. message target, etc.
    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
    }
    
    // Setup header variables from PD for conversion handling (AT items)
    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")
      
      // Retrieve partner specific information regarding converters and agreement
      // information for extended pre processing
      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).

Read%20Datastore

<?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

Assigned Tags

      Be the first to leave a comment
      You must be Logged on to comment or reply to a post.