Technology Blogs by Members
Explore a vibrant mix of technical expertise, industry insights, and tech buzz in member blogs covering SAP products, technology, and events. Get in the mix!
cancel
Showing results for 
Search instead for 
Did you mean: 

Introduction


The S/4 HANA Cloud environment can pose a serious challenge for the development process since you have a limited amount of business process modifications available and the backend is inaccessible. Thus the only way to develop new apps is to browse the SAP API Business Hub , and develop an UI5 app, provided that you have a NEO or Cloud Foundry subaccount to host the application. The Application can easily be added in the Custom Apps section, where the link of the UI5 app is provided and then you just assign the business catalog of your choosing.

Since last year, I've been working on a S/4 HANA (Public) Cloud as a developer/integration consultant and I want to share my experience with developing a hybrid app by combining UI5 with SCP Integration.

 

Limitations with this approach


If you uncover a certain API, that has tens or hundreds of thousands of records and you want to build an algorithm that would loop and change the payload before doing any of the final CRUD operation that you desire, you will see that the browser will be frozen or it will even crash.


With no way of using the backend, a suitable solution would be to use the power of SAP Cloud Platform Integration, that can process all of the records in the background. The only thing that you need to make sure is that you have a Web IDE to develop the UI5 app and a activated tenant for the SCP Integration to begin.

 

Business Scenario


Let's assume that client has multiple customer project that need to be automatically extended or that they need to apply a new tariff to the amount. If the Client in question would have over 10000 of these customer projects, then it could pose a great challenge for the UI5 app alone.

That's SCP Integration comes in to save the day!

The Customer Project in S/4 HANA Cloud can be created and updated using the API SC_PROJ_ENGMT_CREATE_UPD_SRV

As mentioned above, I used this API extend a Customer Project in the Future and to apply a indexation tariff to it's amount, which you can see below:

 


Plan Customer Project - extending the project



Plan Customer Project - Biling Tab - Indexation Process


 

The hybrid app that I'm about to present will mainly focus the Indexation Process, but the execution of these two processes differ only in the types of entities of the API which you would use in the Custom Integration iFlow.

Avoiding the virtually crippling effect of applying this algorithm in the frontend, I harnessed the power of SCP Integration where most of the POST and PATCH calls are ported. That means that I was able to use the UI5 app just as a way of displaying the result, as well as triggering projects to add to queue or remove the ones that get stuck along the way (resetting their status).

 

Using Custom CDS Views and Custom Business Objects


In S/4 Hana Cloud, I was able to expose CDS Views as well as Custom Business Objects (CBOs) as an API. But there is a limitation, since CDS APIs can only be read (GET), whereas on the CBO you can create / update or delete records.

This being said, in order to prepare the model, I first had to build a CBO with the some unique primary keys, specific to the data source I_EngagementProject that was used in the CDS. This CBO will later be called both from the frontend App and the Custom Integration iFlow .

After publishing the CBO, I was able to then proceed with building the CDS View, that had the primary source  I_EngagementProject  and the CBO as a secondary source.

Inside of the CDS, a custom column was build (Add -> Calculation), where a rule was made that populated the status column of the CBO as such:
case _YY1_SCP_CBO.Status when 'Finished'
then
_YY1_SCP_CBO.Status
else
case _YY1_SCP_CBO.Status when 'Added to queue'
then
_YY1_SCP_CBO.Status
else
case _YY1_SCP_CBO.Status when 'Processing Started'
then
_YY1_SCP_CBO.Status
else
'To be send'
end
end
end

As a result, you can use this field to showcase the status of your records. (all unprocessed records will automatically have "To be send" status ).

After building both the CBO and the CDS, both the CDS and CBO were exposed via Communication Scenario and Communication Arrangement which provided me with a service url that uses OData V2.

With this information the CDS can now be in the front-end app.

 

Building the UI5 App


For the UI5 app I used a combination of the two UI elements SmartTable   and IconTabBar .

To recap, the UI5 App will start initially with the status "To be send" for all records. The user will have the flexibility of selecting one or several rows of the table and process them in an OData Batch Call  of our CBO, changing the status to "Added to queue". When at least one project has this status, I was able to trigger the iflow and as such the status will move to "Processing Started" and later "Finished".

The SmartTable will load the CDS data and the IconTabBar helps us visually monitor the operation.. The result should look like this:


The Icon Tab Bar allows you to create different icons which I can then filter based on the Status CBO column. Whenever you press one of the tabs you can automatically filter based on the selected criteria.

This is done with by calling the SmartTable event beforeRebindTable. First specify in the Main View where the SmartTable is defined, the event  "beforeRebindTable="onBeforeRebindTable". This will allow me to automatically filter the table every time I select and deselect a tab by calling the method in our controller:
// accessing the onBeforeRebindTable method
this.byId("table0").rebindTable(); //where table0 is the Table ID of the SmartTable
//( deselects all of the rows)
this.byId("table0_2718281828")._resetItemsBinding(); // where table0_2718281828 is the ID of the Table inside of the SmartTable:items xml
*******
onBeforeRebindTable: function(oEvent){ /method used to filter the data of the SmartTable
...
binding.filters.push(oFilterFinalOriginal); // each time that we will call "rebindTable", we will filter the SmartTable accordingly
}

The Icon Tab Bar also has a count for each status. This is dynamically obtained by creating a method that handles asynchronous calls to the CDS and obtaining and displaying the count as such:
this.getView().getModel("YY1_TBL_DISPLAY_API").read("/YY1_TBL_DISPLAY_API", { //Read to obtain Copy Model
async: true,
filters: [oFilter_Completed],
urlParameters: {
"$top": "1",
"$inlinecount": "allpages",
},
success: function (data, response) {
this.getView().getModel("IconFilter").setProperty("/IconOK", data.__count);
}.bind(this),
error: function (err) {
var sMsg = "Error Occured" + err;
MessageBox.error(oBundle.getText(
return MessageToast.show(sMsg);
}.bind(this)
});

The count will appear by mapping the JSON Model defined in the call with the IconTabFIlter:
<IconTabFilter icon="sap-icon://message-success" iconColor="Positive" count="{IconFilter>/IconOK}" text="Completed" key="Ok" design="Horizontal"/>

Now that I have discussed about the filtering of the table, I want to point out an enhancement that I did to the SmartTable and that is the button Refresh CBO, which the user can use to automatically reset any stuck project with the status Processing Started.


To implement this button, simply declare and invoke the onAfterRendering method of the SmartTable:
onAfterRendering: function () {
var oTable = this.getView().byId("yourTableID");
/add header to smarttable toolbar
oTable.getToolbar().addContent(new Button({
text: "Refresh CBO",
id: "yourButtonID",
icon: "sap-icon://delete",
press: function () {
var oView = this.getView(),
nameSpace = this.getView().getModel("globalView").getProperty("/nameSpace");
if (!this.byId("deleteDialog")) { // load asynchronous XML fragment
Fragment.load({ // Fragment.load method is available since 1.58
id: oView.getId(),
name: nameSpace + ".view.DeleteDialog",
controller: this
}).then(function (oDialog) {// connect dialog to the root view of this component (models, lifecycle)
his.getView().addDependent(oDialog);
oDialog.open();
}.bind(this)); } else {
his.byId("deleteDialog").open(); }}.bind(this)

The button will load a Fragment Dialog. If the User chooses the Yes Event, the method will refresh the CBO that is linked to the displayed CDS. Note that it is important to use the OData method submitChanges ,because batch operations will be done.
this.getView().getModel("yourModel").read("/CBO_Entity", {
async: false,
filters: [oFilter_CBO],
urlParameters: {},
success: function (data, response, result) {
var iLen = data.results.length,i;
if (iLen > 0) {
var oModel = this.getView().getModel("your_Model"); //the model of the CBO API
oModel.setUseBatch(true);
oModel.setDeferredBatchGroups(["batchRemove"]);
for (i = 0; i < iLen; i++) { //remember to declare i outside of the loop to not occupy the memory with it
oModel.remove("/CBO_Entity (guid'" + data.results[i].SAP_UUID + "')", {
success: function (dataa) {
MessageToast.show("Data has been saved", { duration: 3500 });}.bind(this));}
oModel.submitChanges({
success: function (data) {
this._hideBusyIndicator();
MessageBox.success("Projects have been reset ");
}.bind(this),
error: function (err) { MessageBox.error("Insert Error Message");
var oErrorMessage = JSON.parse(err.responseText);
MessageBox.error("An error has occurred: " + oErrorMessage.error.message.value);}.bind(this)});
} else { MessageBox.error("No Project need to be reset! ");}}.bind(this),
error: function (err) {
var sMsg = "CBO Reset has failed " + err;
return MessageToast.showMessage(sMsg);
this._hideBusyIndicator();
}.bind(this)});

In order for the user to trigger the Iflow he first has to flag the status. Please note that you can skip this step, if you want to always trigger the IFlow process for all projects. Adding the field, works exactly the same as the example above, except I will be using the create method when dealing with the OData Model of the CBO.


One reason for adding them manually, is the fact that it gives me more maneuverability, if I was trying for instance to add custom rates for individuals projects so that I can increase the amounts billed for each project. The rate has a default value, but it can be changed manually, so when the user selects a project and adds them to the batch, the App takes the current settings in place.


With all of the projects prepared, I can now trigger the SCPI iFlow. The process itself can be performed by http calls via ajax/xmlhttprequest using promises, as exemplified in this blog.

Once the Custom Integration iFlow is executed, the indexation process of increasing the amount for the Custom Plan Projects can begin, and by refreshing the app the progress of the iFlow can be seen in the IconTabs and thus saves a lot of time.

Let's now proceed with building , linking and configuring the Custom Integration iFlow.

 

Linking the Custom Integration iFlow with the UI5 App


Firstly, I needed to create the iFlow itself and deploy it, in order for us to obtain it's endpoint found in the Monitor / Manage Integration Content Tile.


With it, I will first create a destination inside the tenant that will host our UI5 app.


























Additional Properties  
ClientReadTimeout 300000
TrustAll true
WebIDEEnabled true
WebIDEUsage odata_abap,ui5_execute_abap,dev_abap

Secondly, add the destination name in the application descriptor, the neo-app.json file.
{
"welcomeFile": "/webapp/index.html",
"routes": [
{
"path": "/IND_TRIGGER",
"target": {
"type": "destination",
"name": "IND_TRIGGER",
"entryPath": "/http/IND_TRIGGER"
},
"description": "Mass Batch Processing Trigger"
},
...

With this you are all set to trigger the iFlow. Let's proceed to the next chapter.

 

Building the Custom Integration iFlow


To better monitor the process, I've chosen to split the Integration iFlows into two sections.

  1. The Main iFlow

    • It's endpoint is being used to link with the UI5 App.

    • It will run only once.

    • Queries the CDS for Project with the status "Added to queue.


    • Using a Split and a Gather I will parse each project to start apply the Mass changes.

    • Inside of the Split/Gather I will change the status of the CBO project to "Processing", as well as execute the Secondary Flow using Process Direct.




  2. The Secondary iFlow

    1. Will be triggered (called via ProcessDirect) for each project found while going in the loop made by the Split/Gather events.

    2. Executes the algorithm that runs asynchronous.

    3. For each iteration I will update twice the CBO. Because some projects may get stuck due to the high amount of volume, I suggest adding the iFlow Message ID a column inside of the CBO. The id can easily be obtained by using '${property.SAP_MessageProcessingLogID}' in the header call.




To apply the increased amount for your projects you can do the following:

a) Query the API SC_PROJ_ENGMT_CREATE_UPD_SRV , entity A_CustProjSlsOrd ( to obtain the SalesOrder)


b) Query the APi API_SALES_ORDER_SRV


c) Apply a General Splitter for each Sales Order Item


d) Query the API SC_PROJ_ENGMT_CREATE_UPD_SRV based on certain criteria (requesting the Billing Plan Items to calculate the amount for each ProjectID - the information comes from the main Flow, and it unique to each project ).


e) Apply another General Splitter for each Billing Plan Item.


e) Calculate the New Sales Amount inside a groovy script.

Note: use the library import java.math.BigDecimal;

, if you want to have just 2 decimals for instance:
    iRate = Float.parseFloat(sTransfRate);
float iBillAmount = Float.parseFloat(BillingPlanAmount);
iBillAmount = iBillAmount + iBillAmount * iRate;
BigDecimal numberBigDecimal = new BigDecimal(iBillAmount);
numberBigDecimal = numberBigDecimal .setScale(2, BigDecimal.ROUND_HALF_UP);
BillingPlanAmount = "" + numberBigDecimal;

f) Using a Content Modifier add the header to obtain the CSRF token.


g) Get the CSRF token for the API SC_PROJ_ENGMT_CREATE_UPD_SRV

h) Execute the PATCH operation for SC_PROJ_ENGMT_CREATE_UPD_SRV ( does the update for the new BillingPlanAmount for each BillingPlanItem).


i) Lastly if Sales Order Item has been changed, I will updated the Sales Order Item:

in the API SC_PROJ_ENGMT_CREATE_UPD_SRV ( executes the patch for to update the total sum of the amount reflected in the field ExpectedNetAmount ).



In the final section, the process can be speeded up by applying a Router which will execute or not the final patch based on the fact that the BillingPlanAmount has been modified or not. (there are some SalesOrderItem exceptions which can no longer be updated).

Conclusions


It seems that S/4 HANA Cloud is not as limited as it might be marketed to be, provided you are using all of SAP tools for it.

This hybrid app demonstrates that with the right implementation, an end-to-end solution is possible when combining SCP Integration with UI5 app. I strongly hope that this blog will serve as a working example for business scenarios in the S/4 HANA Cloud projects to come. Using the CDS, CBO and API's at your disposal, allows any developer to build a range of similar solutions.

I encourage you to try and use SCP Integration in your future endeavors to harness the full potential of SAP Cloud Platform capabilities.

 

 

Many thanks for taking the time to read my blog and I hope it was worth your while. I also welcome any comment, opinion and feedback on the above implementation.

 
Labels in this area