如何在SAP云整合中处理OData $batch错误

194 阅读6分钟

简介

当你使用的API在错误响应文本中没有提供太多的信息,而你又无法识别失败的那一行时,这种方法就特别有用。我将在下一章解释我为了开发iFlow而使用的场景。

使用案例

让我们假设一个第三方系统通过HTTP调用向SAP云端集成发送以下有效载荷。

Time,Sales Entity,Month,Invoice Customer Code,Article Code,Allocation Code,Planning Type,Preferred Site,Preferred Warehouse,Vendor Account,Final Commercial Forecast,Commercial Forecast Lag 3M,Commercial Forecast Production Program
22-Mar,BE03,202205,300612,10000155,,STS,0241,27,BE03,500,1049,648
22-Mar,BE03,202205,300612,20000155,,STS,0241,27,BE03,501,1049,648
22-Mar,BE03,202208,300612,10000157,,STS,0241,27,BE03,500,1049,648
22-Mar,BE03,202209,300612,10000158,,STS,0241,27,BE03,501,1049,648

这是一个csv格式的有效载荷,以逗号分隔,包括不同产品的预测值。

这个预测将通过POST或PATCH方法在SAP S/4HANA中使用计划独立需求API发送,这取决于几个过滤器(例如,预测期决定了是否也会使用PATCH)。

解决方案设计 :

在SAP云整合中,通过使用CSV到XML转换器,我们生成了这个有效载荷,它将用于iFlow的其余部分。

<root>
  <row>
    <Product>10000155</Product>
    <Plant>0241</Plant>
    <PlndIndepRqmtType>VSF</PlndIndepRqmtType>
    <PlndIndepRqmtVersion>00</PlndIndepRqmtVersion>
    <RequirementPlan/>
    <RequirementSegment/>
    <RequirementPlanIsExternal>false</RequirementPlanIsExternal>
    <PlndIndepRqmtIsActive>X</PlndIndepRqmtIsActive>
    <PlndIndepRqmtPeriod>202205</PlndIndepRqmtPeriod>
    <PeriodType>M</PeriodType>
    <PlannedQuantity>500</PlannedQuantity>
    <WithdrawalQuantity>0</WithdrawalQuantity>
    <UnitOfMeasure>KG</UnitOfMeasure>
  </row>
  <row>
    <Product>10000155</Product>
    <Plant>0241</Plant>
    <PlndIndepRqmtType>VSF</PlndIndepRqmtType>
    <PlndIndepRqmtVersion>00</PlndIndepRqmtVersion>
    <RequirementPlan/>
    <RequirementSegment/>
    <RequirementPlanIsExternal>false</RequirementPlanIsExternal>
    <PlndIndepRqmtIsActive>X</PlndIndepRqmtIsActive>
    <PlndIndepRqmtPeriod>202205</PlndIndepRqmtPeriod>
    <PeriodType>M</PeriodType>
    <PlannedQuantity>501</PlannedQuantity>
    <WithdrawalQuantity>0</WithdrawalQuantity>
    <UnitOfMeasure>KG</UnitOfMeasure>
  </row>
</root>

因为有些行可能不适合被进一步处理,或者我们想发布/修补特定的行,这意味着我们需要找到一种方法,如何将产品编号与可能的错误信息联系起来。

为了做到这一点,我们将采取以下步骤。

Local%20Process%20used%20for%20creating%20the%20product%20list

用于创建产品列表的本地流程

我们将创建一个Message Mapping,它将创建以下XML。

<?xml version="1.0" encoding="UTF-8"?> 
<root>
<row>
<Product>10000155</Product>
</row>
<row>
<Product>10000155</Product>
</row>
</root>

这个XML将被保存为一个属性,供将来使用。

在下一个Message Mapping中,我们将使用原始有效载荷来生成适当的批处理消息结构。

<batchParts>
  <batchChangeSet>
    <batchChangeSetPart>
      <method>POST</method>
      <PlannedIndepRqmt>
        <PlannedIndepRqmtType>
          <Product>10000155</Product>
          <Plant>0241</Plant>
          <MRPArea/>
          <PlndIndepRqmtType>VSF</PlndIndepRqmtType>
          <PlndIndepRqmtVersion>00</PlndIndepRqmtVersion>
          <RequirementPlan/>
          <RequirementSegment/>
          <RequirementPlanIsExternal>false</RequirementPlanIsExternal>
          <PlndIndepRqmtIsActive>X</PlndIndepRqmtIsActive>
          <to_PlndIndepRqmtItem>
            <PlannedIndepRqmtItemType>
              <Product>10000155</Product>
              <Plant>0241</Plant>
              <MRPArea/>
              <PlndIndepRqmtType>VSF</PlndIndepRqmtType>
              <PlndIndepRqmtVersion>00</PlndIndepRqmtVersion>
              <RequirementPlan/>
              <RequirementSegment/>
              <PlndIndepRqmtPeriod>202205</PlndIndepRqmtPeriod>
              <PeriodType>M</PeriodType>
              <PlannedQuantity>500</PlannedQuantity>
              <WithdrawalQuantity>0</WithdrawalQuantity>
              <UnitOfMeasure>KG</UnitOfMeasure>
            </PlannedIndepRqmtItemType>
          </to_PlndIndepRqmtItem>
          <link>
            <to_PlndIndepRqmtItem>
              <PlannedIndepRqmtItemType>
                <Product>10000155</Product>
                <Plant>0241</Plant>
                <MRPArea/>
                <PlndIndepRqmtType>VSF</PlndIndepRqmtType>
                <PlndIndepRqmtVersion>00</PlndIndepRqmtVersion>
                <RequirementPlan/>
                <RequirementSegment/>
                <PlndIndepRqmtPeriod>202205</PlndIndepRqmtPeriod>
                <PeriodType>M</PeriodType>
              </PlannedIndepRqmtItemType>
            </to_PlndIndepRqmtItem>
          </link>
        </PlannedIndepRqmtType>
      </PlannedIndepRqmt>
    </batchChangeSetPart>
  </batchChangeSet>
  <batchChangeSet>
    <batchChangeSetPart>
      <method>POST</method>
      <PlannedIndepRqmt>
        <PlannedIndepRqmtType>
          <Product>10000155</Product>
          <Plant>0241</Plant>
          <MRPArea/>
          <PlndIndepRqmtType>VSF</PlndIndepRqmtType>
          <PlndIndepRqmtVersion>00</PlndIndepRqmtVersion>
          <RequirementPlan/>
          <RequirementSegment/>
          <RequirementPlanIsExternal>false</RequirementPlanIsExternal>
          <PlndIndepRqmtIsActive>X</PlndIndepRqmtIsActive>
          <to_PlndIndepRqmtItem>
            <PlannedIndepRqmtItemType>
              <Product>10000155</Product>
              <Plant>0241</Plant>
              <MRPArea/>
              <PlndIndepRqmtType>VSF</PlndIndepRqmtType>
              <PlndIndepRqmtVersion>00</PlndIndepRqmtVersion>
              <RequirementPlan/>
              <RequirementSegment/>
              <PlndIndepRqmtPeriod>202205</PlndIndepRqmtPeriod>
              <PeriodType>M</PeriodType>
              <PlannedQuantity>501</PlannedQuantity>
              <WithdrawalQuantity>0</WithdrawalQuantity>
              <UnitOfMeasure>KG</UnitOfMeasure>
            </PlannedIndepRqmtItemType>
          </to_PlndIndepRqmtItem>
          <link>
            <to_PlndIndepRqmtItem>
              <PlannedIndepRqmtItemType>
                <Product>10000155</Product>
                <Plant>0241</Plant>
                <MRPArea/>
                <PlndIndepRqmtType>VSF</PlndIndepRqmtType>
                <PlndIndepRqmtVersion>00</PlndIndepRqmtVersion>
                <RequirementPlan/>
                <RequirementSegment/>
                <PlndIndepRqmtPeriod>202205</PlndIndepRqmtPeriod>
                <PeriodType>M</PeriodType>
              </PlannedIndepRqmtItemType>
            </to_PlndIndepRqmtItem>
          </link>
        </PlannedIndepRqmtType>
      </PlannedIndepRqmt>
    </batchChangeSetPart>
  </batchChangeSet>
</batchParts>

在下一个本地流程中,我们将把消息发送到SAP S/4HANA,如果响应中出现任何错误,我们将处理它们。

POST%20to%20SAP%20S/4HANA%20and%20error%20handling

POST到SAP S/4HANA和错误处理

在我们的案例中,我们收到了以下响应。

<batchPartResponse>
<batchChangeSetResponse>
<batchChangeSetPartResponse>
<headers>
<Accept></Accept>
<Accept-Language></Accept-Language>
<Content-Length>1594</Content-Length>
<dataserviceversion>1.0</dataserviceversion>
<Content-Type>application/xml;charset=utf-8</Content-Type>
</headers>
<statusInfo>Bad Request</statusInfo>
<contentId/>
<body>&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;&lt;error xmlns=&quot;http://schemas.microsoft.com/ado/2007/08/dataservices/metadata&quot;&gt;&lt;code&gt;PPH_FCDM/033&lt;/code&gt;&lt;message xml:lang=&quot;en&quot;&gt;Material &amp;amp;000000000010040155&amp;amp; in Plant &amp;amp;0241&amp;amp; or MRP Area not defined&lt;/message&gt;&lt;innererror&gt;&lt;application&gt;&lt;component_id&gt;PP-MRP&lt;/component_id&gt;&lt;service_namespace&gt;/SAP/&lt;/service_namespace&gt;&lt;service_id&gt;API_PLND_INDEP_RQMT_SRV&lt;/service_id&gt;&lt;service_version&gt;0001&lt;/service_version&gt;&lt;/application&gt;&lt;transactionid&gt;75592a5cc25d40f2924ec2b4538ca465&lt;/transactionid&gt;&lt;timestamp/&gt;&lt;Error_Resolution&gt;&lt;SAP_Transaction/&gt;&lt;SAP_Note&gt;See SAP Note 1797736 for error analysis (https://service.sap.com/sap/support/notes/1797736)&lt;/SAP_Note&gt;&lt;Batch_SAP_Note&gt;See SAP Note 1869434 for details about working with $batch (https://service.sap.com/sap/support/notes/1869434)&lt;/Batch_SAP_Note&gt;&lt;/Error_Resolution&gt;&lt;errordetails&gt;&lt;errordetail&gt;&lt;ContentID/&gt;&lt;code&gt;PPH_FCDM/033&lt;/code&gt;&lt;message&gt;Material &amp;amp;000000000010040155&amp;amp; in Plant &amp;amp;0241&amp;amp; or MRP Area not defined&lt;/message&gt;&lt;propertyref/&gt;&lt;severity&gt;error&lt;/severity&gt;&lt;target&gt;MRPArea&lt;/target&gt;&lt;additionalTargets&gt;&lt;target&gt;Plant&lt;/target&gt;&lt;target&gt;Product&lt;/target&gt;&lt;/additionalTargets&gt;&lt;transition&gt;true&lt;/transition&gt;&lt;/errordetail&gt;&lt;errordetail&gt;&lt;ContentID/&gt;&lt;code&gt;PPH_FCDM/033&lt;/code&gt;&lt;message&gt;Material &amp;amp;000000000010040155&amp;amp; in Plant &amp;amp;0241&amp;amp; or MRP Area not defined&lt;/message&gt;&lt;propertyref/&gt;&lt;severity&gt;error&lt;/severity&gt;&lt;target&gt;MRPArea&lt;/target&gt;&lt;additionalTargets&gt;&lt;target&gt;Plant&lt;/target&gt;&lt;target&gt;Product&lt;/target&gt;&lt;/additionalTargets&gt;&lt;transition&gt;true&lt;/transition&gt;&lt;/errordetail&gt;&lt;/errordetails&gt;&lt;/innererror&gt;&lt;/error&gt;</body>
<statusCode>400</statusCode>
</batchChangeSetPartResponse>
</batchChangeSetResponse>
<batchChangeSetResponse>
<batchChangeSetPartResponse>
<headers>
<Accept></Accept>
<Accept-Language></Accept-Language>
<Content-Length>1594</Content-Length>
<dataserviceversion>1.0</dataserviceversion>
<Content-Type>application/xml;charset=utf-8</Content-Type>
</headers>
<statusInfo>Bad Request</statusInfo>
<contentId/>
<body>&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;&lt;error xmlns=&quot;http://schemas.microsoft.com/ado/2007/08/dataservices/metadata&quot;&gt;&lt;code&gt;PPH_FCDM/033&lt;/code&gt;&lt;message xml:lang=&quot;en&quot;&gt;Material &amp;amp;000000000010040155&amp;amp; in Plant &amp;amp;0241&amp;amp; or MRP Area not defined&lt;/message&gt;&lt;innererror&gt;&lt;application&gt;&lt;component_id&gt;PP-MRP&lt;/component_id&gt;&lt;service_namespace&gt;/SAP/&lt;/service_namespace&gt;&lt;service_id&gt;API_PLND_INDEP_RQMT_SRV&lt;/service_id&gt;&lt;service_version&gt;0001&lt;/service_version&gt;&lt;/application&gt;&lt;transactionid&gt;75592a5cc25d40f2924ec2b4538ca465&lt;/transactionid&gt;&lt;timestamp/&gt;&lt;Error_Resolution&gt;&lt;SAP_Transaction/&gt;&lt;SAP_Note&gt;See SAP Note 1797736 for error analysis (https://service.sap.com/sap/support/notes/1797736)&lt;/SAP_Note&gt;&lt;Batch_SAP_Note&gt;See SAP Note 1869434 for details about working with $batch (https://service.sap.com/sap/support/notes/1869434)&lt;/Batch_SAP_Note&gt;&lt;/Error_Resolution&gt;&lt;errordetails&gt;&lt;errordetail&gt;&lt;ContentID/&gt;&lt;code&gt;PPH_FCDM/033&lt;/code&gt;&lt;message&gt;Material &amp;amp;000000000010040155&amp;amp; in Plant &amp;amp;0241&amp;amp; or MRP Area not defined&lt;/message&gt;&lt;propertyref/&gt;&lt;severity&gt;error&lt;/severity&gt;&lt;target&gt;MRPArea&lt;/target&gt;&lt;additionalTargets&gt;&lt;target&gt;Plant&lt;/target&gt;&lt;target&gt;Product&lt;/target&gt;&lt;/additionalTargets&gt;&lt;transition&gt;true&lt;/transition&gt;&lt;/errordetail&gt;&lt;errordetail&gt;&lt;ContentID/&gt;&lt;code&gt;PPH_FCDM/033&lt;/code&gt;&lt;message&gt;Material &amp;amp;000000000010040155&amp;amp; in Plant &amp;amp;0241&amp;amp; or MRP Area not defined&lt;/message&gt;&lt;propertyref/&gt;&lt;severity&gt;error&lt;/severity&gt;&lt;target&gt;MRPArea&lt;/target&gt;&lt;additionalTargets&gt;&lt;target&gt;Plant&lt;/target&gt;&lt;target&gt;Product&lt;/target&gt;&lt;/additionalTargets&gt;&lt;transition&gt;true&lt;/transition&gt;&lt;/errordetail&gt;&lt;/errordetails&gt;&lt;/innererror&gt;&lt;/error&gt;</body>
<statusCode>400</statusCode>
</batchChangeSetPartResponse>
</batchChangeSetResponse>
</batchPartResponse>

正如你所看到的,我们有两个问题。

  1. 我们无法真正弄清哪一行失败了。
  2. 错误文本不能轻易阅读,因为它没有转义。

由于这两个原因,我们无法清楚地了解哪一行出了问题,为了满足我的需要,我做了一些小的升级。

import com.sap.gateway.ip.core.customdev.util.Message
import groovy.xml.MarkupBuilder
import java.time.LocalDate
import java.time.format.DateTimeFormatter

Message processData(Message message) {

    // Access message body and properties
    Reader reader = message.getBody(Reader)
    def messageLog = messageLogFactory.getMessageLog(message);
    def mapProperties = message.getProperties();
    def valueProperty = mapProperties.get("ListItems");
    

    // Define XML parser and builder
    def feed = new XmlParser().parseText(valueProperty)

    def i=0;
    def lista = feed.row.'*'.findAll{ node-> 
    node.name() == 'Product' }*.text()

    def errorLog = "<root>";
    def root = new XmlSlurper().parse(reader);
    int y =0;
     root.batchChangeSetResponse.each {
        y = y+1;
         statusCode = "${it.batchChangeSetPartResponse.statusInfo}";
        if(statusCode != "Created") {
            errorLog = errorLog + "<error>"+"<Product>"+lista[y-1]+"</Product>"+"${it.batchChangeSetPartResponse.body}"+"</error>";
        ErrorFoundFlag = 'ErrorFound';
        message.setProperty("ErrorFoundFlag", ErrorFoundFlag);
        removal = errorLog;
        removal=removal.replace("<?xml version=\"1.0\" encoding=\"utf-8\"?>","");
        errorLog = removal;
        }
     }
    errorLog = errorLog + "</root>";
    message.setBody(errorLog);
    return message;

}

以下是所遵循的步骤:

  1. 我们从原始输入的有效载荷(ListItems)中读取正文和存储产品列表的属性。
  2. 我们在脚本中创建一个基于产品节点的列表。这个列表将被用来根据不同的位置进行交互。
  3. 我们解析输入体,并基于每一次出现的 batchChangeSetPartResponse 创建另一个错误节点。
  4. 错误节点也是以statusCode为条件的,所以它只在NOT创建的状态下被创建。
  5. 最后我删除了xml定义,以解决模式验证错误。
  6. 对于每一行,我们从我们先前创建的列表中获取适当的产品ID。
<root>
  <error>
    <Product>10040155</Product>
    <error xmlns="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata">
      <code>PPH_FCDM/033</code>
      <message xml:lang="en">Material &amp;000000000010040155&amp; in Plant &amp;0241&amp; or MRP Area  not defined</message>
      <innererror>
        <application>
          <component_id>PP-MRP</component_id>
          <service_namespace>/SAP/</service_namespace>
          <service_id>API_PLND_INDEP_RQMT_SRV</service_id>
          <service_version>0001</service_version>
        </application>
        <transactionid>75592a5cc25d40f2924ec2b4538ca465</transactionid>
        <timestamp/>
        <Error_Resolution>
          <SAP_Transaction/>
          <SAP_Note>See SAP Note 1797736 for error analysis (https://service.sap.com/sap/support/notes/1797736)</SAP_Note>
          <Batch_SAP_Note>See SAP Note 1869434 for details about working with $batch (https://service.sap.com/sap/support/notes/1869434)</Batch_SAP_Note>
        </Error_Resolution>
        <errordetails>
          <errordetail>
            <ContentID/>
            <code>PPH_FCDM/033</code>
            <message>Material &amp;000000000010040155&amp; in Plant &amp;0241&amp; or MRP Area  not defined</message>
            <propertyref/>
            <severity>error</severity>
            <target>MRPArea</target>
            <additionalTargets>
              <target>Plant</target>
              <target>Product</target>
            </additionalTargets>
            <transition>true</transition>
          </errordetail>
          <errordetail>
            <ContentID/>
            <code>PPH_FCDM/033</code>
            <message>Material &amp;000000000010040155&amp; in Plant &amp;0241&amp; or MRP Area  not defined</message>
            <propertyref/>
            <severity>error</severity>
            <target>MRPArea</target>
            <additionalTargets>
              <target>Plant</target>
              <target>Product</target>
            </additionalTargets>
            <transition>true</transition>
          </errordetail>
        </errordetails>
      </innererror>
    </error>
  </error>
</root>

在这一点上,我们有了产品ID和信息文本,但因为在一个脚本中处理整个过程有点困难,我决定重复这个脚本,用上面的xml作为下一个脚本的输入,并创建我的最终结构。基本上是一样的,区别在于最终的结构和用于检查是否有必要生成附件的if。

    def root = new XmlSlurper().parse(reader);

    int y =0;
    def ErrorFoundFlag = "";

    def status = "";

    

     root.error.each {

            errorLog = errorLog + "<error>"+"<Product>"+lista[y-1]+"</Product>"+"<Message>"+"${it.error.message}"+"</Message>"+"</error>";
        ErrorFoundFlag = 'ErrorFound';
        message.setProperty("ErrorFoundFlag", ErrorFoundFlag);
        }

    errorLog = errorLog + "</root>";

    message.setBody(errorLog);
    
        if(ErrorFoundFlag.contains('ErrorFound'))
    {
        messageLog.addAttachmentAsString("POST Call Errors", errorLog, "text/plain");
    }

    return message;

}

最后的有效载荷可以作为附件在消息监控中看到,名称为POST Call Errors。

<root>
    <error>
        <Product>10000155</Product>
        <Message>Invalid period name  for period type</Message>
    </error>
    <error>
        <Product>20000155</Product>
        <Message>Material &000000000020000155& in Plant &0241& or MRP Area &0241& not defined</Message>
    </error>
</root>

同样的逻辑被用于PATCH调用,唯一改变的是statusCode从Created变成204。

总结

通过这种方式,我们将能够对以下情况进行故障排除。

  1. 所有的行都应该被发送并且被成功地处理。
  2. 不是所有的行都应该被发送并被成功地处理了。
  3. 有些行应该被发送,有些被修补,对于每个失败的行,我们都有它的错误信息和适当的产品代码。