SAP Omnichannel Promotion Pricing with its integrated promotion maintenance offers a comprehensive package for maintaining promotions and calculating effective sales prices. Beyond the calculation service, the solution also includes the Data Access service (using OData) for reading prices and promotions. SAP Omnichannel Promotion Pricing supports online scenarios only. However, there might be specific customer scenarios that require offline capabilities, like SAP Omnichannel Point-of-Sale by GK or the so-called “Black Box”. For these scenarios, it would be helpful to use the existing IDoc /ROP/PROMOTION03 to replicate data from SAP Omnichannel Promotion Pricing to the according applications.
In this blog, you learn how to use SAP Cloud Integration to send out the respective IDoc format using the Data Access service to read the data.
The example iFlow (integration flow) also manages a timestamp variable to properly handle the replication of updates.
Overview
The following diagram gives an overview of the complete process.
Let’s have a look at the high-level flow before diving deeper into the topic:
-
- Start the iFlow with a timer to start the outbound processing regularly.
-
- In the first step of the process, read the timestamp of the last successful execution of the iFlow.
-
- Before the call to theData Access service is triggered, prepare the filter statement in a Groovy script.
-
- In the next step, call the Data Access service to retrieve the data.
-
- After the call, check if any promotions are found for the filter settings. If no promotions are found, jump to step 8.
-
- Now, the received data is mapped from the OData format of the Data Access service to the
the promotion IDoc format
- Now, the received data is mapped from the OData format of the Data Access service to the
-
- After the data is mapped, the message is pushed to an according HTTP REST endpoint.
-
- The response code of the HTTP call is analyzed and in case of a successful execution, the timestamp is adjusted.
-
- The new timestamp is stored in a CPI tenant-wide variable to be used by subsequent calls.
In the following sections, these steps are explained in more detail.
Timer
In the following example, the iFlow is started every 10 mins:
Read Timestamp
Use a content modifier to read the global variable that stores the last successful execution of the iFlow. Beside this, generate a new timestamp:
Handle Query Parameter
In this part, prepare the call of the Data Access service. It consists of two steps:
-
- Check timestamp and build the filter part of the query
-
- Construct the OData query string
In the following code lines, check if the timestamp variable is provided. (This should always be true after the first successful execution). If the timestamp is provided, construct a filter that selects all promotions that have been changed between the last successful execution and now). Otherwise, all promotions are selected:
def properties = message.getProperties() as Map<String, Object> ;
def timestampValue = properties.get("timestamp");
def newTimestampValue = properties.get("newTimestamp");;
// adjust new time stamp to the format used within Omnichannel Promotions Pricing Service
newTimestampValue = newTimestampValue.replace(" ", "T") + ".000Z";
message.setProperty("newTimestamp", newTimestampValue);
def filterValue = "";
if (timestampValue != null && timestampValue.length()>0) {
filterValue = "$filter=changedOn%20gt%20datetimeoffset%27" + timestampValue + "%27%20and%20changedOn%20le%20datetimeoffset%27" + newTimestampValue + "%27";
}
After defining the filter, also add an additional $expand statement to ensure the response of the Data Access service call includes the necessary promotion data (see OSS note 2777975 for details).
message.setProperty("customQuery",
filterValue
+ "&$expand=promotionPriceDerivationRules,promotionPriceDerivationRules/priceDerivationRule,promotionPriceDerivationRules/priceDerivationRule/externalActionRuleParameters,promotionPriceDerivationRules/priceDerivationRule/externalActionRuleTexts,promotionPriceDerivationRules/priceDerivationRule/mixAndMatchPriceDerivationItems,promotionPriceDerivationRules/priceDerivationRuleEligibilities,promotionTexts,businessUnitAssignments,merchandiseSetNodes,merchandiseSetHeaders,promotionPriceDerivationRules/priceDerivationRule/addBonusPriceDerivationItems");
return message;
Fetch Data from SAP Omnichannel Promotion Pricing
In the next step, use the OData receiver adapter to call the Data Access service.
On the process tab, define details for the OData call:
Select a resource path. For the connection source, use the Local EDMX File option.
Download the necessary EDMX file from the SAP API Business Hub.
For the mapping, it is important to generate an XML schema definition (name marked in green).
Then, select all fields except for the back reference “promotion” under “promotionPriceDerivationRules” (see line marked in blue).
The generated query is very complex. Therefore, delete the Query Options string and use the custom query, you prepared in the Groovy script in the previous step:
Check Content
After calling the Data Access service, introduce a router to call the IDoc receiver only if at least one promotion was provided by the Data Access service. Use the following XML expression to check this:
Message Mapping
The next step is the mapping of the response of the Data Access service call to the promotion IDoc format.
On the source side, select the XSD file you have created in the OData call definition:
Now it gets a bit tricky. We must differentiate between the following cases:
-
- You need the IDoc in XML format and have the IDoc XSD at hand (most probably from transaction WE60 of the CARAB system). Please ensure that the CARAB system provides the current format that includes all necessary fields.
-
- You need the IDoc in JSON format.
-
- You need the IDoc in XML format but do not have an XSD file.
Case 1
You have access to an XSD and plan to send the IDoc in XML format.
In this case it is straight forward: Use the IDoc XSD for the target message in an iFlow as follows:
Case 2
In this case, you must download the OpenAPI documentation from the SAP Business API Hub. Unfortunately, the SAP Cloud Integration mapping doesn’t support the schema definition used by the Data Upload API definition:
...
"requestBody": {
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/_-ROP_-BASE_PRICE01"
},
{
"$ref": "#/components/schemas/_-ROP_-PROMOTION02"
},
{
"$ref": "#/components/schemas/_-ROP_-PROMOTION03"
}
]
},
...
Therefore, create your own local copy and adjust the “schema” element accordingly:
...
"paths": {
"/idocinbound": {
"post": {
"security": [
{
"OAuth2": [
"{xsappname}.InboundProcessing"
]
}
],
...
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/_-ROP_-PROMOTION03"
},
Use the local copy to import the target message format (“Upload Promotions Using OAuth2” -“/idocinbound” – “POST” – “REQUEST”) See the blog SAP Cloud Integration – Swagger/OpenAPI Spec JSON in Message Mapping for further details.
Case 3
In this case, you do the exact same steps as described in case 2 but additionally, you must convert the message in the according XML format after the mapping is done:
Mapping
Now the mapping work can start:
You can find detailed information about the source system and the target system on the SAP API Business Hub: Data Access and Data Upload
Since the IDoc comes from the ABAP world and the OData services provides Java format, you must convert several fields. This can be done with short Groovy scripts:
DateTime:
def String convertToAbapDateTime(String javaDateTime){
def result = "";
if (javaDateTime.length()>20) {
result = javaDateTime.substring(0,4) + javaDateTime.substring(5,7) + javaDateTime.substring(8,10);
result = result + javaDateTime.substring(11,13) + javaDateTime.substring(14,16) + javaDateTime.substring(17,19);
}
return result
}
Boolean:
def String convertToAbapBoolean(String booleanValue){
if ("true".equals(booleanValue))
return "X"
return ""
}
There are further mappings needed e.g., for the business unit type:
def String mapBusinessUnitType(String businessUnitType){
def result = "";
if ("RetailStore".equals(businessUnitType)) {
result = "1040";
}
else if ("Distribution Center".equals(businessUnitType)) {
result = "1002";
}
return result;
}
The list is not complete, but I hope you get the idea.
Call IDoc Receiver
After the mapping, use the HTTP receiver to call an IDoc receiver endpoint:
Check Status and Update Timestamp
After the call, check the status before you overwrite the timestamp with the new one. This is done in an according Groovy script using the property CamelHttpResponseCode, which includes the HTTP status from the last call:
def headers = message.getHeaders() as Map<String, Object>;
def status = headers.get("CamelHttpResponseCode");
if (status != null && status == 200 ) {
def properties = message.getProperties() as Map<String, Object> ;
def newTimestampValue = properties.get("newTimestamp");;
message.setProperty("timestamp", newTimestampValue);
}
return message;
Write Timestamp
In the last step of the iFlow, store the current value of the timestamp (which is still the old one, if the IDoc call fails). This is done by the write variables module:
Outlook
This is just the basic flow and there might be other points to be considered in more complex scenarios:
-
- Supporting several receivers would need additional work, for example , introducing a timestamp variable per receiver and extracting the core process into a reuse iFlow.
-
- To avoid DoS attacks, SAP Omnichannel Promotion Pricing has a size limit and depending on the complexity of your promotion, the number of promotions that will be provided by the Data Access service might be restricted (up to 1000). If you expect a higher number of promotions for the initial load, you might need to introduce a loop to call the Data Access service several times with an according package size (using OData top and skip parameters).
-
- The solution described in this blog does not provide a usual “IDoc provider”. You need to fill the according IDoc segment in a way that the created IDoc messages is accepted by the IDoc receiver.
Tip: A simple iFlow to clear the timestamp variable would easily enable initial loads at any time.
Restrictions
There are also some restrictions you should be aware of:
-
- Unfortunately, OData only allows to set a filter on the promotion header. With that, restricting a location-specific outbound processing isn’t supported “out of the box”.
-
- The MIN_PPS_VERSION cannot be maintained yet in the promotion maintenance of SAP Omnichannel Promotion Pricing. Therefore, a receiver might get some promotions, which cannot be properly processed without even recognizing this.
Conclusion
With this blog, I gave you an idea on how you can use the Data Access service and SAP Cloud Integration to replicate promotions maintained with SAP Omnichannel Promotion Pricing to existing offline solutions leveraging the well-known promotion IDoc.
Not all aspects, which are relevant for a productive setup, have been covered so far. However, it was intended to show that there are no major roadblocks for this approach.