SAPUI5, SAP ERP

ESS Benefits Enrollment using SAP UI5 and HR-BN Component

There could be infinite ways to design a content-rich and user-friendly web application using SAP UI5 libraries. However designing such an application keeping the core functionality in the back-end ERP intact is a challenging task, especially when it comes to the ESS Benefits Enrollment.

This blog discusses about the key design challenges and the solution architecture that was identified and followed while pursuing a mobile friendly web application for ESS Benefits Enrollment using SAP_UI and HR-BN Components.

Objective

Digital transformation of the ESS benefits enrollment application into an user friendly, device and browser agnostic web application using SAP UI5 libraries, keeping the HR-BN component at it’s core.

Background

We did not wanted to re-invent the wheel or design a whole benefits module from scratch. The idea is to leverage all the functionality, config and the processing logic offered by SAP while making some enhancements and additions on top of it to adapt with the new UI and vice versa too. In other words, the UI and the back-end functionality must support each other adapting and adjusting to each other’s needs.

For our productive usage, our main area of focus was on the following categories, we can extend the solution to other categories and plan types as well if needed.

  • Health Plans & Dependents
  • Savings Accounts
  • Spending Accounts
  • Insurance plans
  • Miscellaneous
  • Stock Plans

Challenges

  1. All the underlying benefits configuration, rules, validations and the processing logic must be retained, while keeping an open eye to all possible future changes.
  2. The standard enqueue and dequeue must be retained to ensure the data is not corrupted while the users are actively working on their benefits.
  3. Optimizing the application run time and avoiding long running busy indicators.

Solution in a Nutshell

Persistence Data Store

A custom repository (Enrollment Cart) was created to act as the persistence storage of the selections and the changes made by the users to their benefits using the generated offer. We took the most common and required attributes from the below structures to build this repository.

  • RPBENOFFER
  • RPBEN_CONTRIB_TRANS
  • RPBEN_CONTRIBUTIONS
  • RPBEN_CONTRIB_LIMITS
  • RPBENDEP

The data in this repository is temporary and will be wiped off as soon as the user exits out of the application. This is similar to the persistence achieved using shared memory buffer in the FPM based ABAP Webdynpro application today.

Gateway Service

A brand new custom gateway service was designed and developed to supply the data to the front-end UI. Each category has it’s own infotype and thereby needs an entity within the gateway model.

The model is based on a single benefits area and all the properties are mostly imported from the standard DDIC structures (RPBEN*) available in SAP. The relationships/associations between each parent and child entities are created using the common key attributes from all the infotypes.

  • Adjustment Reasons → Plan Types
  • Plan Types → Health Plans
  • Health Plans → Dependents
  • Plan Types → Savings
  • Plan Types → Insurance
  • Plan Types → Spending
  • Plan Types → Stock
  • Plan Types → Miscellaneous
  • Adjustment Reasons → Enrollment Cart

The service is made a soft-state enabled to create Lock (enqueue) on the employees using the BAPI_EMPLOYEE_ENQUEUE in the first OData entityset call made to the gateway service by the front end UI5 application.

There are several standard SAP delivered function modules and RFC’s that can help you get the offer, participating plans, validate selections and save the changes to infotypes (some of the important ones are listed below). They need to be called in a certain sequence to keep the offer, selections and changes consistent and clean throughout the app lifetime.

  • HR_BEN_ESS_RFC_ENRO_REASONS → To get list of eligible adjustment reasons
  • HR_BEN_ESS_GET_LATEST_OFFER → To obtain offer for selected enrollment event
  • HR_BEN_ESS_GET_OVERVIEW_DATA → To get an overview of current participation
  • HR_BEN_ESS_MODIFY_PLANS → To add/update the selection from the offer
  • HR_BEN_ESS_CHECK_CONSISTENCY → To perform comprehensive validation
  • HR_BEN_ESS_SAVE_PLANS → To submit the selections to Infotypes

Key Consideration –

Make sure that the employee selections are added/updated to the cart and are included for validations before and after the update for a comprehensive check of the plans and to verify any pre-requisites or co-requisites. → Addresses Challenge 1

UI5 Application

The UI5 applications is designed in such a way that the number of HTTP/oData calls to the back-end are optimized and used only during the key instances when there is a need. As you can see from the picture above, the calls are triggered only during 4 events.

The app initial load will fetch the list of eligible adjustment reasons for the employee while also creating a lock for the PREL. If the Lock fails, the app should let user know that the enrollment cannot be possible at the time.

This lock stays on, as long as the user resides in the app and a webRFC is called in the event the user exits out of the app or closes the browser window to unlock PREL for the employee. We chose a webRFC call using $.ajax over a regular oData call here. → Addresses Challenge 2

You can also use the browser window event ‘unload’, if the event ‘beforeunload‘ doesn’t work.

$(window).on("beforeunload", function () {
/* Locked is a local indicator set to true after the Lock was successful during initial load */
   if (Locked === true) {
      $.ajax({
	type: "GET",
	cache: false,
	async: false,
	url: "/sap/bc/webrfc?_FUNCTION=<FM to unlock PREL>&_odataSrv=/sap/opu/odata/sap/<GW Service>",
	success: function(oSuccess) {/* Set local Lock indicator as false if needed */},
    error: function(oError){}
	});
   }
 });

The selection of an adjustment reason brings the latest offer for the employee and all the data is stored in global JSON models for each infotype/category involved. These global JSON models can be stored globally for the app runtime using sap.ui.getCore().setModel() and can be retrieved whenever needed for binding or creating local copies using sap.ui.getCore().getModel().

/* onInit method of planTypes controller*/
var oModel = new sap.ui.model.json.JSONModel();
oModel.setDefaultBindingMode(sap.ui.model.BindingMode.OneWay);
sap.ui.getCore().setModel(oModel, "globalOffer");
this.getRouter().getRoute("planTypes").attachPatternMatched(this._onPatternMatched, this);

/* onPatternMatched for planTypes */
var gOffer = sap.ui.getCore().getModel("globalOffer");
if (gOffer.getData() === null || !gOffer.getData().results) {
var cResults = getOffer(<Event>,<Pernr>,<EntitySet Name>);
cResults.then(function (data) {
sap.ui.getCore().getModel("globalOffer").setData(data);
}.bind(this));
}

/* method to fetch offer from the gateway sevrice in the back-end */
getOffer: function (oEvent, oPernr, oEntitySetName) {
var oModel = this.getModel();
var sPath = "/EnrollReasons(Pernr='" + oPernr + "',Event='" + oEvent + "')/" + oEntitySetName;
var promise = jQuery.Deferred();
oModel.read(sPath, {
async: true,
success: function (oData, response) {promise.resolve(response.data);},
error: function (oError){} });
return promise;
}

An individual view and an associating controller was created for each benefit category to display eligible plans offered and similarly for their respective detail/dependent selections. Whenever the user navigates to the plans overview or specific detail/dependent view, a local copy of the global JSON model is created and adjusted based on the data in Enrollment cart and the binding is refreshed.

We used the jquery $.extend function to create a local copy and adjust the offer data based on the data already stored in the cart. → Addresses Challenge 3

var gResults, lResults = [];
gResults = sap.ui.getCore().getModel("globalOffer").getData();
lResults = $.extend(true, {}, oResults);
checkCart(lResults); // Method to adjust local offer based on selections in the cart
this.getView().getModel("localOffer").setData(lResults);
this.getView().getModel("localOffer").refresh();

Key Considerations –

Anytime the user navigates to Landing Page or exits out of the app, the JSON models stored in the global memory are cleared and the enrollment cart is refreshed.

Try to bind the individual elements on the detail views with the attributes/properties supplied from the back-end entity (Check structures RPBEN_SCREEN_CTRL, RPBENOFFER etc..) which will take care of most of the things usually driven by config.

For Eg: A form grouped with pre-tax contribution fields should be displayed or hidden based on the indicator if pre-tax contribution is allowed.

Some of the benefit categories (Savings, Stock & Miscellaneous) share a similar cost/contribution structure to a certain extent, so we can leverage that to re-use certain form fields and

WebRFC ↔ Consumer proxy ↔ Service Provider

You may be wondering why such a complex architecture was designed to simply unlock a locked PREL object. Here are the reasons..

  • In a productive landscape, there are most likely more than 1 app servers running on the instance.
  • The lock may have been created in a session running on an app server different from the one that was selected by the web dispatcher for the last call made to the back-end during exit.
  • Employee may not be having the necessary authorizations to read or kill the sessions on all app servers available on the instance.

In order to trigger an unlock without any problems in any of the above mentioned or unmentioned scenarios, the idea of a killing the soft-state session using a proxy service user came up.

So the WebRFC triggers a consumer proxy configured with a logical port (authenticated by a service user that can read and unlock from multiple app servers) which in turn calls a web service created as a wrapper around an RFC function that reads all the app servers, finds the gateway oData session which contains the lock and kills it.

The RFC function module contains the logic to read all the sessions from the instance, identifies the server on which the session is running, calls itself using the RFC destination (created with the same name as the app server) to execute a system command to kill the session.

INCLUDE: tskhincl.
  DATA(lv_path) = iv_path.
  TRANSLATE lv_path TO UPPER CASE.

  DATA with_application_info TYPE ssi_bool.
  DATA tenant                TYPE ssi_tenant_id.
  DATA session_list          TYPE ssi_session_list.
  DATA server_name           TYPE ssi_servername.
  DATA actual_server         TYPE ssi_servername.
  DATA rfcdest               TYPE rfcdest.

  rc = 4.

  TRY.
      session_list = cl_system_info=>get_session_list(
          with_application_info = 1
          tenant                = sy-mandt
             ).
    CATCH cx_ssi_no_auth.
  ENDTRY.

  server_name = cl_abap_syst=>get_instance_name( ).

  LOOP AT session_list ASSIGNING FIELD-SYMBOL(<fs_session_info>)
                                        WHERE user_name  = iv_user
                                          AND logon_type = 3
                                          AND logon_sub_type = 4
                                          AND act_program = 'SAPMHTTP'.

    TRANSLATE <fs_session_info>-application_info TO UPPER CASE.

    IF <fs_session_info>-application_info CS lv_path.
      actual_server = <fs_session_info>-server_name. CONDENSE actual_server.
      IF server_name EQ actual_server.
        CALL 'ThUsrInfo' ID 'OPCODE' FIELD opcode_delete_usr
              ID 'TID' FIELD <fs_session_info>-logon_hdl
              ID 'LOGON_ID' FIELD <fs_session_info>-logon_id.
        IF sy-subrc <> 0.
          CONTINUE.
        ELSE.
          rc = 0.
        ENDIF.
      ELSE.
        rfcdest = <fs_session_info>-server_name.
        CALL FUNCTION <self FM> DESTINATION rfcdest
          EXPORTING
            iv_path = iv_path
            iv_user = iv_user
          IMPORTING
            rc      = rc.
        CONTINUE.
      ENDIF.
    ENDIF.
  ENDLOOP.

Read More: SAP Certifications

Leave a Reply

Your email address will not be published. Required fields are marked *