CRM and CX Blogs by SAP
Stay up-to-date on the latest developments and product news about intelligent customer experience and CRM technologies through blog posts from SAP experts.
cancel
Showing results for 
Search instead for 
Did you mean: 
WolfgangSattler
Product and Topic Expert
Product and Topic Expert
2,127
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:

  1. Start the iFlow with a timer to start the outbound processing regularly.

  2. In the first step of the process, read the timestamp of the last successful execution of the iFlow.

  3. Before the call to theData Access service is triggered, prepare the filter statement in a Groovy script.

  4. In the next step, call the Data Access service to retrieve the data.

  5. After the call, check if any promotions are found for the filter settings. If no promotions are found, jump to step 8.

  6. Now, the received data is mapped from the OData format of the Data Access service to the

    the promotion IDoc format


  7. After the data is mapped, the message is pushed to an according HTTP REST endpoint.

  8. The response code of the HTTP call is analyzed and in case of a successful execution, the timestamp is adjusted.

  9. 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:

  1. Check timestamp and build the filter part of the query

  2. 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:

  1. 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.

  2. You need the IDoc in JSON format.

  3. 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.
6 Comments
Pradeep_Yadav
Explorer
0 Kudos

Hello Wolfgang Sattler,

I hope you're doing well. I’m currently integrating SAP OPPS with SAP Business One and need some clarification regarding the schema for retrieving Merchandise Set Inclusion/Exclusion products. At the moment, I’m referencing the /MerchandiseSetNodes endpoint, but my goal is to obtain promotionID, MerchandiseSetID, and itemID.

Could you please guide me on the correct schema or approach for this?

Regards,

Pradeep

andreas_paul
Product and Topic Expert
Product and Topic Expert

I Pradeep, I am not 100% sure I get the question. If you want to retrieve the included and excluded products, they would be contained in the merchandise set nodes (one product per record). One merchandise set node entry also contains the information about promotion ID and merchandise set ID. Have you checked Schema View | Data Access | SAP Business Accelerator Hub?
What exactly do you mean by "schema for retrieving..." this information? What is the starting point, and which information do you already have then?

Best regards, Andreas

Pradeep_Yadav
Explorer
0 Kudos

Hello Andreas

Thank you for sharing the link!

Initially, I was referring to a different URL, but after reviewing the one you provided,

I found the Merchandise Set Node section, particularly the Field: Combination.

This field determines whether a subnode of the root node is included or excluded from the merchandise set. The possible values are:

Does Not Matter (0) – for operation nodes Inclusion (1) – for leaf nodes Exclusion (2) – for leaf nodes

This clarified my queries completely. As for the starting point, I’m working with Promotion and using the promotionID as the unique primary key to link it with related data like Business Units Assignment and Promotion Texts. From there, I make API calls to retrieve all relevant data and insert it into the staging schema in SAP HANA. However, I’ve encountered a few issues. For instance, in the Business Units Assignment , there’s no changedOn field, unlike in Promotion. Also, if a new business unit is added from the front end, the changedOn field in Promotion does not update.

Thanks again for your help!

Best regards,

Pradeep

andreas_paul
Product and Topic Expert
Product and Topic Expert

Hi Pradeep,

please note that the changedOn field is consistently set for all entities (having this field) if the promotion gets a different status. If you add a business unit for an inactive promotion, this simply "does not matter" since this promotion cannot be applied (and be completely broken / incomplete, by the way), so the addition has no effect (yet) anyway.

Therefore, you should only consume active promotions, and if a promotion becomes inactive, "do the needful" on your side. Changes to inactive promotions should not be looked at. And since adding or removing a business unit is only possible for an inactive promotion, I do not think there is the need to have this field there.

Please also note, there is the field "lastCalcRelevantChange" which is probably even more interesting for you. It records, at which point in time the corresponding promotion has undergone a "relevant change for OPPS(!)". This makes a difference if you upload a promotion from outside. Then, the changedOn date is NOT changed (since it always indicates when the original version of the promotion was changed).

Does this make sense?

Regards, Andreas

Pradeep_Yadav
Explorer

Hello Andreas,

I have tested your suggestions and it worked!

Many Thanks for your prompt response.

 

Regards,

Pradeep

Pradeep_Yadav
Explorer
0 Kudos

Hello Andreas,

I hope you’re doing well!

I’m reaching out to request your assistance with retrieving specific fields from the OPPS API. Specifically, I need help identifying the API call that would provide the following data:

  • RewardID
  • RewardType
  • Price Modification
  • Reward as Loyalty Points
  • New Price Amount
  • Rule Control Code
  • Price Modification for Subsequent Discount
  • Discount Method

If you could spare some time to guide me or provide insight into which API call would deliver these outputs, I would greatly appreciate it.

Thank you in advance for your help!

Regards,

Pradeep