My Learning Journal On BTP (Series 3) – SAP Cloud Platform Workflow and ABAP Restufl Programming Model (RAP)


Abstract:

In this blog, I will use an example to introduce how to link two intersting new topics, SAP Cloud Platform (SCP) Workflow and ABAP REFTful Model (RAP).

Introduction:

There are different Workflow approaches in SAP environemnt, like Business Workflow, Flexible Workflow and SAP Cloud Platform (SCP) Workflow. SAP SCP Workflow is available on the Cloud Foundry environment since April 2019 and it can be consumed by the REST APIs.

ABAP Restful Applicaiton Programming Model (RAP) was introduced in September 2018 and developers can use the new framework to efficiently build Fiori applicaitons and Web APIs.

I will first use ABAP RESTful Programming Model to create two OData services which will be consumed by the SCP Workflow. After that, I will create a SCP Workflow in BTP Cloud Foundry Environemnt and then we will do a test on the Workflow module in the BTP trial account.

Below is the SAP Cloud Platform Workflow which we will build in this blog post.

Now, let’s start the process.

1. OData Services:

We need two OData services to archive the requirements in the SAP Cloud Platform Workflow.

First OData service is for getting the Manager of the order processor in the HCM structure.

Second OData service is for updating the order status after the Manager approved/rejected the refund requirement.

1.1 Get Manager from the HCM structure in the S/4HANA Backend System

As the ABAP environment in the SAP BTP trial account does not have the HCM structure can be used, so I used one S/4HANA system as the backend system in this example. One thing to note is this blog post will not introduce how to set up the SAP Cloud Connector between the BTP Cloud Foundry Environment and the S/4HANA  backend system, please check other blog posts to see how to set up the SAP Cloud Connector and the ‘Destination’ to make the linkage. Additionaly, the related report relationship should has been maintained in the HCM strcture in the S/4HANA backend system.

The OData service will reuse the standard function module ‘RH_GET_LEADER’ in the S/4HANA system to get the Manager of the order processor. So the first question is how can we call the function module as an OData service?

Firstly, we should defines a custom CDS entity with a single input parameter.

Below is an example, we need the order processor’s user name as the parameter (im_initiator) to call the function module to get the Manager.

@EndUserText.label: 'Get manager of a certain user'
@ObjectModel.query.implementedBy: 'ABAP:zcl_getmanager'
define custom entity ZCDS_MANAGERINFO with parameters im_initiator : aenam { key managerusername : aenam; }

Below is an example implementation class to process the query. The function module will be called in the customizing class.

CLASS zcl_getmanager DEFINITION PUBLIC FINAL CREATE PUBLIC . PUBLIC SECTION. INTERFACES if_rap_query_provider.
PROTECTED SECTION.
PRIVATE SECTION.
ENDCLASS. CLASS ZCL_GETMANAGER IMPLEMENTATION. METHOD if_rap_query_provider~select. DATA lt_result TYPE STANDARD TABLE OF zcds_managerinfo. DATA: lv_initiator TYPE aenam, lv_personalno TYPE persno, lv_position TYPE realo, lv_leader_type TYPE otype, lv_leader_id TYPE realo. IF io_request->is_data_requested( ). io_request->get_paging( ). DATA(lt_filter) = io_request->get_parameters( ). lv_initiator = VALUE #( lt_filter[ parameter_name = 'IM_INITIATOR' ]-value OPTIONAL ). "Get personal number of the workflow initiator SELECT SINGLE pernr FROM pa0105 WHERE uname = @lv_initiator INTO @lv_personalno. CHECK lv_personalno IS NOT INITIAL. "Get position of workflow initiator SELECT SINGLE plans FROM pa0001 WHERE pernr = @lv_personalno INTO @DATA(lv_ini_position). CHECK lv_ini_position IS NOT INITIAL. lv_position = lv_ini_position. "Get manager username CALL FUNCTION 'RH_GET_LEADER' EXPORTING plvar = '01' keydate = '20211101' // Follow the format to input the date otype = 'S' objid = lv_position IMPORTING leader_type = lv_leader_type leader_id = lv_leader_id exceptions no_leader_found = 1 no_leading_position_found = 2 OTHERS = 3. CHECK lv_leader_type = 'US'. lt_result = VALUE #( ( managerusername = lv_leader_id ) ). DATA(lv_offset) = io_request->get_paging( )->get_offset( ). DATA(lv_page_size) = io_request->get_paging( )->get_page_size( ). io_response->set_total_number_of_records( lines( lt_result ) ). io_response->set_data( lt_result ). ENDIF.
ENDMETHOD.
ENDCLASS.

After verified the result of the CDS view, we should create a Service Defination for the CDS view.

@EndUserText.label: 'Service definition on manager info cds'
define service Zsd_managerinfo { expose ZCDS_MANAGERINFO;
}

Finally, we will create a Service Binding based on the just created Service Defination. One thing to note is the ‘Binding Type’ should be OData V2 – Web API or OData V4 – Web API.

Do not forget to active and Publish the service.

After the service is publised, we can do a test in the S/4HANA backend system.

In the Gateway Client, using ‘GET’ method to get the Manager information with the parameter ‘im_initiator’.

1.2  Update Order Status

We also need one more OData service to update the order status after the Manager approved/rejected the refund requirement.

a. Create Interface CDS Views

@AbapCatalog.sqlViewName: 'ZFOODORDERS'
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'Interface CDS view for food orders table'
@AbapCatalog.preserveKey: true
define root view Z_I_CDS_ORDERS as select from zfood_orders_t{ key orderuuid as Orderuuid, key orderid as Orderid, status as Status, receivedtime as Receivedtime, processorid as Processorid, last_changed_by as LastChangedBy, last_changed_at as LastChangedAt
}

b. Create Projection CDS View for the Interface View

@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'Projection view for food orders'
define root view entity Z_C_CDS_ORDERS as projection on Z_I_CDS_ORDERS { key Orderuuid, key Orderid, Status, Receivedtime, Processorid, LastChangedBy, LastChangedAt
}

C. Create Behavior Definiation of the interface CDS view

We need a customizing action to update the status. One thing to note is I will use the ‘Unmanaged’ scenario in this example.

Firstly, I will create the Behavior Definiation for the Business Object CDS view ‘Z_R_CDS_ORDERS’.

unmanaged implementation in class zbp_r_cds_orders unique; define behavior for Z_R_CDS_ORDERS alias Orders
{ action ( features : instance ) updatestatus_approved result [1] $self; action ( features : instance ) updatestatus_rejected result [1] $self;
}

We need an implementation class for the Behavior Definiation. One thing to note is this class will only handle the status update task, I will go deeper into the objects of ABAP RESTfull Programming Model in next blog post.

The global definiation of the class.

CLASS zbp_r_cds_orders DEFINITION PUBLIC ABSTRACT FINAL FOR BEHAVIOR OF Z_R_CDS_ORDERS.
ENDCLASS. CLASS zbp_r_cds_orders IMPLEMENTATION.
ENDCLASS.

The local implementation of the class.

CLASS lhc_z_r_cds_orders DEFINITION INHERITING FROM cl_abap_behavior_handler. PRIVATE SECTION. METHODS updatestatus_approved FOR MODIFY IMPORTING keys FOR ACTION Orders~updatestatus_approved RESULT result. METHODS updatestatus_rejected FOR MODIFY IMPORTING keys FOR ACTION Orders~updatestatus_rejected RESULT result.
ENDCLASS. CLASS lhc_z_r_cds_orders IMPLEMENTATION. METHOD updatestatus_approved. DATA ls_food_orders TYPE zfood_orders_t. READ TABLE keys INTO DATA(ls_key) INDEX 1. SELECT SINGLE * INTO @DATA(ls_food_order) FROM zfood_orders_t WHERE orderid = @ls_key-orderid. ls_food_orders-status = 7. UPDATE zfood_orders_t FROM ls_food_orders. ENDMETHOD. METHOD updatestatus_rejected. DATA ls_food_orders TYPE zfood_orders_t. READ TABLE keys INTO DATA(ls_key) INDEX 1. SELECT SINGLE * INTO @DATA(ls_food_order) FROM zfood_orders_t WHERE orderid = @ls_key-orderid. ls_food_orders-status = 8. UPDATE zfood_orders_t FROM ls_food_orders. ENDMETHOD.
ENDCLASS.

Now, we need another Behavior Defination for the Projection CDS view Z_A_CDS_ORDERS.

projection; define behavior for Z_A_CDS_ORDERS //alias <alias_name>
{ use action updatestatus_approved; use action updatestatus_rejected;
}

d. Create Service Defination

Let’s create one Service Defination for the Projection CDS view ‘Z_A_CDS_ORDERS’.

@EndUserText.label: 'Service definition on Food Orders'
define service ZSRV_A_CDS_ORDERS { expose Z_A_CDS_ORDERS;
}

e. Create Service Binding

Finally, we will create a new Service Binding based on the just created Service Defination.

We can do a test in the Gateway client in the backend system after the service is published.

2. SAP Cloud Platform Workflow:

In this step, I will create a SAP Cloud Platform Workflow in my BTP (Cloud Foundry) trial account to handle the requirmeent mentioned at the benging of the blog post.

Before continue reading this blog post, I recommond you read the great introduction on SAP Cloud Platform Workflow from @DJ Adams.

https://github.com/SAP-archive/cloud-platform-workflow-virtual-event

You can use Booster ‘Set up account for Workflow Management’ to set up the Workflow services in your BTP trial account.

2.1 Create a customizing SAP Cloud Platform (SCP) Workflow

Firstly, we need a Dev Space which activated the ‘Workflow Management’ function.

I created a new Workflow module by Yeoman in the Termanl tool of SAP Business Application Studio. You can also follow the tutorial from SAP Deverloper Center to create a Workflow module.

a. Define the Start Event attributes

One important parameter in the Start Event is the ‘Sample Context’. The pamameters we defined in the file will be used as the initial values to create the Workflow instance later.

Below is an example I used in my Workflow module.

{ "initiator": "PROCESSOR1", "orderid": "OR00000001"
}

b. Create a Service Task to collect the Manager information

I created a ‘Service Task’ to call the OData Service we created in step 2.1  (Get Manager from the HCM structure in the S/4HANA Backend System) to collect the Manager information.

The ‘Destination’ field is for the Destination you configured for the S/4HANA backend system in you BTP account.

Using the parameter ‘initiator’ (follow the format ${context.initiator}’) which we defined as the Start Event in the request. It means we will call the OData service to find the Manager of the Workflow initiator.

c. Extract the collected Manager information

After the previous step finish, the Manager information will be stored in the ‘Response Variable’ (${context.response}).

We need  a ‘Script Task’ to extract the Manager information from the response variable.

The code used in my JS file.

$.context.managerName = $.context.response.d.results[0].managerusername;

d. Send the refund approve request to the Manager

After collected the Manager information, the next step is to send the approve request to the him/her. I created a ‘User Task’ to send the request to the Manager.

The recepitant user is the variable ${context.managerName} which we collected in the above step.

I also created a Form which will be used in MyInbox Fiori applicaiton with some basic information of the order.

Firstly, add some fields which will be shonw in MyInbox Fiori application later.

After that, add the user decisions.

e. Process ‘Approve’ or ‘Reject’ action

As we defined, the Manger can select Approve or Reject the refund request.

Firstly, we need an ‘Exclusive Gateway’ to decide which task should be processed.

If the refund request got approved then ‘Service Task ‘RefundApproved’ will be processed.

The ‘Service Task’ will call the OData Service which we created in step  2.2 (Update Order Status) to update the status of the food order.

Below is an example in my Workflow.

One thing to note is the field ‘Path to XSRF Token’, the connection between the S/4HANA backend system and the SCP Workflow will be protected by XSRF token. In general case, we can use ‘GET’ method to collect the XSRF token from the response of a OData call, and that’s why we do not need to maintain the XSRF token information for OData ‘GET’ method. But this OData call is ‘POST’, so we should make sure the system knows how to collect the XSRF token to pass the check. The good news is the SCP Workflow framework will help us to do the ‘GET’ action first to collect the XSRF token and then auto use the XSRF token to do the ‘POST’ action.

After created the Workflow module, we should deploy it to our BTP trial account. You can follow the guidence in the tutorial (step 4 Build and deploy) below.

https://developers.sap.com/tutorials/cp-workflow-3-add-usertask-cf.html

Finally, the Workflow is created with the necessary tasks.

3. Conclusion

Until this step, we have created two OData services by ABAP Restufl Programming Model (RAP) and created a SAP Cloud Platform Workflow model to consume the OData services. There are also some other interesting topics not included in this blog posts like how the SAP Multitarget Application Model (MTA) works and how to test the OData service and Workflow model.

I will introduce the interesting topics in my future blog posts.

Best regards,

Jason Lu