简介
当你使用的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>
因为有些行可能不适合被进一步处理,或者我们想发布/修补特定的行,这意味着我们需要找到一种方法,如何将产品编号与可能的错误信息联系起来。
为了做到这一点,我们将采取以下步骤。

用于创建产品列表的本地流程
我们将创建一个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到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><?xml version="1.0" encoding="utf-8"?><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></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><?xml version="1.0" encoding="utf-8"?><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></body>
<statusCode>400</statusCode>
</batchChangeSetPartResponse>
</batchChangeSetResponse>
</batchPartResponse>
正如你所看到的,我们有两个问题。
- 我们无法真正弄清哪一行失败了。
- 错误文本不能轻易阅读,因为它没有转义。
由于这两个原因,我们无法清楚地了解哪一行出了问题,为了满足我的需要,我做了一些小的升级。
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;
}
以下是所遵循的步骤:
- 我们从原始输入的有效载荷(ListItems)中读取正文和存储产品列表的属性。
- 我们在脚本中创建一个基于产品节点的列表。这个列表将被用来根据不同的位置进行交互。
- 我们解析输入体,并基于每一次出现的 batchChangeSetPartResponse 创建另一个错误节点。
- 错误节点也是以statusCode为条件的,所以它只在NOT创建的状态下被创建。
- 最后我删除了xml定义,以解决模式验证错误。
- 对于每一行,我们从我们先前创建的列表中获取适当的产品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 &000000000010040155& in Plant &0241& 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 &000000000010040155& in Plant &0241& 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 &000000000010040155& in Plant &0241& 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。
总结
通过这种方式,我们将能够对以下情况进行故障排除。
- 所有的行都应该被发送并且被成功地处理。
- 不是所有的行都应该被发送并被成功地处理了。
- 有些行应该被发送,有些被修补,对于每个失败的行,我们都有它的错误信息和适当的产品代码。