Skip to Content

This blog is to explain step by step process to configure a

  • custom workflow along with
  • custom UI screen to call workflow
  • custom UI screen to approve/reject workflow
  • and call backend service which is running on-premise

Inspiration:

This blog is inspired by some of awesome blogs written in this area by Seshadri Sreenivas R  Murali Shanmugham , Christian Loos , Archana Shukla and many others in this area. You can find numerous blogs in this are at following link:

https://blogs.sap.com/tags/73554900100800000555/

Prerequisites:

  • SAP Cloud Platform access
  • Destination(s) setup in SAP Cloud Platform
  • SAP Cloud Connector Setup
  • Back-end system access for OData Creation

Scenario:

  1. User logs on to SAP fiori launchpad and runs a custom UI5 app running on SAP cloud platform to enter 3 fields which are user first name, last name and email address.
  2. After submission, user gets a message about submission of user entry.
  3. Submission of this entry triggers SAP cloud platform custom workflow.
  4. Once workflow administrator approves the workflow, it will call an OData backend service which is running in S4HANA on-premise environment. This OData Service will create an entry in a Z table.
  5. If workflow administrator chooses to reject the workflow, it will reject the submission and sends an email to email id provided on the user registration box.

Picture Representation:

 

Development Tasks:

  1. Create OData Service

I have created a Z table which will get updated with userid, user firstname, lastname and email address along with timestamp

Go to SEGW and create a new project for your OData Service. There are lots of blogs available to create OData service from Z table for example: https://blogs.sap.com/2017/02/28/generate-odata-services-by-mapping-db-tables-as-data-sources-in-sap-gw-and-the-limits/

This is how my OData service looks like:

I had to implement class ****_DPC_EXT because for some reason mapping of insert reason wasn’t working , here is the code for my DPC_EXT insert operation:

In above code I have created a number object for userid assignment.

After this please activate and publish your OData service and you can run some tests using SAP Gateway Client.

2. Create UI5 application for user registration which will serve as front end for trigger of SAP Cloud Platform workflow

I have used SAP WebIDE to develop my UI5 application.

Design a layout for your input form. I have chosen vertical grid layout, please feel free to arrange the UI fields the way you like. Here’s how my layout looks like:

Here you can see the code generated behind for above view, if this helps 😊

<App id="idAppControl">
		<pages>
			<Page title="{i18n>title}">
			<content>
			    <sap.ui.layout:VerticalLayout xmlns:sap.ui.layout="sap.ui.layout" width="100%" id="__layout0">
			    <sap.ui.layout:content><sap.ui.layout:Grid id="__grid0">
			        <sap.ui.layout:content>
			            <sap.ui.layout.form:Form xmlns:sap.ui.layout.form="sap.ui.layout.form" editable="true" id="__form0">
			                <sap.ui.layout.form:formContainers>
			                    <sap.ui.layout.form:FormContainer title="{i18n>persInfo}" id="__container0">
			                        <sap.ui.layout.form:formElements>
			                            <sap.ui.layout.form:FormElement label="{i18n>firstName}" id="firstName_f">
			                                <sap.ui.layout.form:fields>
			                                    <Input width="100%" id="firstname" required="true" value="{user>/firstname}"/>
			                                </sap.ui.layout.form:fields>
			                            </sap.ui.layout.form:FormElement>
			                            <sap.ui.layout.form:FormElement label="{i18n>lastName}" id="lastName_f">
			                                <sap.ui.layout.form:fields>
			                                    <Input width="100%" id="lastname" required="true" value="{user>/lastname}"/>
			                                </sap.ui.layout.form:fields>
			                            </sap.ui.layout.form:FormElement>
			                            <sap.ui.layout.form:FormElement label="{i18n>emailID}" id="email_f">
			                                <sap.ui.layout.form:fields>
			                                    <Input width="100%" id="email" required="true" value="{user>/email}"/>
			                                    <Button text="Submit" width="100px" id="Submit_Form" press="submitRequest"/>
			                                </sap.ui.layout.form:fields>
			                            </sap.ui.layout.form:FormElement>			                            
			                        </sap.ui.layout.form:formElements>
			                    </sap.ui.layout.form:FormContainer>
			                </sap.ui.layout.form:formContainers>
			                <sap.ui.layout.form:layout>
			                    <sap.ui.layout.form:ResponsiveGridLayout id="__layout1"/>
			                </sap.ui.layout.form:layout></sap.ui.layout.form:Form>
			        </sap.ui.layout:content>
			        </sap.ui.layout:Grid>
			    </sap.ui.layout:content>
			    </sap.ui.layout:VerticalLayout>
			</content>
			<headerContent>
			</headerContent>
			</Page>
		</pages>
	</App>

Here is the code for Component.JS file for your custom UI5 application:

return UIComponent.extend("xxxxxx.xx.xxxxxxshowcasefinal.Component", {

		metadata: {
			manifest: "json",
			publicMethods: [ "updateBinding" ]
		},

		/**
		 * The component is initialized by UI5 automatically during the startup of the app and calls the init method once.
		 * @public
		 * @override
		 */
		init: function() {
			// call the base component's init function
			UIComponent.prototype.init.apply(this, arguments);

			// enable routing
			this.getRouter().initialize();

			// set the device model
			this.setModel(models.createDeviceModel(), "device");
			this.setModel(models.createUserModel(), "user");			
		}
	});

Here is the code for model.js file:

return {

		createDeviceModel: function() {
			var oModel = new JSONModel(Device);
			oModel.setDefaultBindingMode("OneWay");
			return oModel;
		},
		
		createUserModel: function() {
			var oModel = new JSONModel();
			oModel.setDefaultBindingMode("TwoWay");
			return oModel;
		}		

	};

Here is the code for your view’s controller.js file

return Controller.extend("xxxxxx.xx.xxxxxxshowcasefinal.controller.user_register_form", {
						submitRequest: function() {
							//var userModel = this.getView().getModel("user");
							var userModel = this.getView().getModel("user");

							var context = JSON.stringify({
								"definitionId": "xxxxxx_test_workflow",
								"context": {
									"userData": {
										"input": {},
										"output": {},
										"firstname": userModel.oData.firstname,
										"lastname": userModel.oData.lastname,
										"email": userModel.oData.email,
									}
								}
							});

			$.ajax({
				type: "GET",
				url: "/bpmworkflowruntime/rest/v1/xsrf-token",
				headers: {
					"X-CSRF-Token": "Fetch"
				},
				success: function(data, statusText, xhr) {
					var token = xhr.getResponseHeader("X-CSRF-Token");

					$.ajax({
						type: "POST",
						url: "/bpmworkflowruntime/rest/v1/workflow-instances",
						data: context,
						headers: {
							"X-CSRF-Token": token
						},
						success: function() {
							sap.m.MessageToast.show("Your user creation request has been submitted for approval!");
						},
						error: function(errMsg) {
							sap.m.MessageToast.show("XSRF token request didn't work: " + errMsg.statusText);
						},
						dataType: "json",
						contentType: "application/json"
					});
				},
				error: function(errMsg) {
					sap.m.MessageToast.show("Didn't work: " + errMsg.statusText);
				},
				contentType: "application/json"
			});
		}
	});

In above code lies the magic of integration between your SAPUI5 application and your SAP cloud platform workflow. The important things to look out are following:

 

  • In submitrequest function which is called when submit button is pressed on your UI5 application has got a variable defined as “context”, please make sure the “definitionId” is the name of your “custom workflow application” and the “context” of workflow has got parameters “input”,”output” and the UI elements from your UI screen
  • You can also see there is an ajax call with GET to get CSRF token from your SAP Cloud platform url and a POST call to post data from UI application to context of workflow
  • I have added a message to show success of POST call in following way:

sap.m.MessageToast.show(“Your user creation request has been submitted for approval!”);

 

Now your UI application is ready and you are ready to Deploy it to SAP Cloud platform by right clicking on application and choose to deploy as shown below:

If you have got your portal site up and running on SAP Cloud Platform, you can also choose to ‘Register to SAP Fiori Launchpad’.

Once you have done this, you can go to SAP Fiori Launchpad running on your SAP cloud platform and can see tile for custom UI application.

Following is the screen-shot of my user registration application running on SAP Cloud Platform.

3. Now let’s create custom workflow on SAP Cloud platform.

Go back to SAP WebIDE and choose File->New->Project from Template, in category choose ‘Business Process Management’ and you will see the template as per following screen-shot:

If you don’t see as mentioned above please make sure

  • You are running SAP WebIDE Full Stack from your SAP Cloud platform
  • In SAP WebIDE Full Stack ->tools->features->Workflow Editor is ON

 

After giving name to your workflow project (please make sure that your workflow id is same as you used in your custom UI5 application for submission of workflow. For example, in my case it was “xxxxxx_test_workflow”).

Now you are in workflow editor, which is graphical tool and very much simple to use.

As per following screen-shot I have used 2 service tasks, one script task, one exclusive gateway.

In my case first service task calls custom UI screen to approve the user registration. After that exclusive gateway checks the outcome of approval i.e whether it was rejected or accepted. Script task makes sure the context of workflow has got data. The last service task does the call to SAP backend Service to create user in our Z table.

Please find below screen-shot of the workflow:

Settings for Approve User Service Task:

  • Please note that in above screen-shot the ‘Users’ and ‘Groups’ is blank. I have removed it for obvious reasons (hide personal data), but you need to provide users who will get this workflow for approval, this information you can find on SAP Cloud Platform->Services->workflow->configure service->roles, you need to have workflow approval role assigned to your s-userid if you are using your own userid to approve the workflow task
  • Also you can see in above screen-shot there is HTML5 app name and SAPUI5 Component name have been given (The logic behind this is that by default SAP cloud platform workflow is integrate with SAP Fiori Inbox app running on SAP Cloud platform so when your workflow entry goes to SAP Fiori Inbox it doesn’t show any information as there is NO UI. However if you have built a custom UI screen for approval then that screen will be shown when you click on workflow approval entry in SAP fiori My Inbox app. I have described steps to create that UI further down in blog at step number 4)

Settings for Exclusive Gateway:

In the screen-shot for workflow which is shown after point f, you can see from exclusive gateway there are 2 paths, one is showing approval and other is showing ‘rejected’. This is basically like If<->else condition of workflow. Here one important thing is to have one of branches selected as ‘default’. This is a checkbox as per following screen-shot. It’s basically to tell workflow the default behaviour of your workflow.

 

Here is the setting for rejection, the point to note here is that you must provide a condition, the condition tells workflow which path to take. As per below screen-shot I am checking if user selected ‘rejection’.

 

Settings for Convertinput service task:

In above screen-shot you can see there is convertinput.js file. A JS file is mandatory for this task type. Following is the simple code which I am doing for my Script file

Settings for createuserid task:

Here you can see I am using POST method as this task will call back-end of ODATA service which is running on-premise. For security reason CSRF token functionality is available here. So you can give path to your ODATA service again here. SAP Cloud platform workflow automatically uses ‘path to XSRF token’ url to firstly do a ‘GET’ call to fetch CSRF token from backend and then automatically append CSRF token to POST header. I quite frankly like the beauty of this function.

You can open workflow editor in code editor as well, following is code representation of my workflow:

 

{
	"contents": {
		"326c26a0-ac59-4ac2-a2c0-1780fba15276": {
			"classDefinition": "com.sap.bpm.wfs.Model",
			"id": "xxxxxx_test_workflow",
			"subject": "xxxxxx_test_workflow",
			"name": "xxxxxx_test_workflow",
			"documentation": "Workflow For User Registration",
			"lastIds": "e358ed89-374f-4866-a2e8-97e4309214c3",
			"events": {
				"00409a22-fa0c-4584-a365-c61086e16aed": {
					"name": "StartEvent1"
				},
				"b3dac5df-c682-4bbb-9e3b-d6bcaab6170a": {
					"name": "Complete"
				}
			},
			"activities": {
				"07a3c547-1ab4-49dc-9080-700f804131a9": {
					"name": "ApproveUser"
				},
				"16b85e4a-2800-41b3-8e8e-323229ce5d62": {
					"name": "checkapprovalstatus"
				},
				"d13c0a1f-7939-4330-89c7-4d364665050e": {
					"name": "createuserid"
				},
				"6766f508-51bc-4c3d-96a7-2db5386f998c": {
					"name": "convertinput"
				}
			},
			"sequenceFlows": {
				"78813c21-0c1c-46b4-97c4-f7708e83355e": {
					"name": "SequenceFlow1"
				},
				"bee44173-c5e4-4af7-8f8b-9ec9575abebb": {
					"name": "SequenceFlow32"
				},
				"aff972a9-0b75-46b7-880b-47bfd8ef9010": {
					"name": "approved"
				},
				"69f3a1d9-f981-48fa-b640-abae15a4c737": {
					"name": "SequenceFlow34"
				},
				"9d264671-8990-4e55-98f4-850c2d5c873d": {
					"name": "rejected"
				},
				"8aaff4b9-d464-4b76-b438-f5b6c9d74646": {
					"name": "SequenceFlow37"
				}
			},
			"diagrams": {
				"1f8998ef-cdc3-45d4-b8a8-39c046b59287": {}
			}
		},
		"00409a22-fa0c-4584-a365-c61086e16aed": {
			"classDefinition": "com.sap.bpm.wfs.StartEvent",
			"id": "startevent1",
			"name": "StartEvent1"
		},
		"b3dac5df-c682-4bbb-9e3b-d6bcaab6170a": {
			"classDefinition": "com.sap.bpm.wfs.EndEvent",
			"id": "endevent1",
			"name": "Complete"
		},
		"07a3c547-1ab4-49dc-9080-700f804131a9": {
			"classDefinition": "com.sap.bpm.wfs.UserTask",
			"subject": "Approve userid for  ${context.userData.firstname} ${context.userData.lastname}",
			"priority": "MEDIUM",
			"isHiddenInLogForParticipant": false,
			"userInterface": "sapui5://html5apps/xxxxxxnewuseruiapprove/NewUserUIApprove.xxxxxxNewUserUIApprove",
			"recipientUsers": "S0017648388",
			"id": "usertask1",
			"name": "ApproveUser"
		},
		"16b85e4a-2800-41b3-8e8e-323229ce5d62": {
			"classDefinition": "com.sap.bpm.wfs.ExclusiveGateway",
			"id": "exclusivegateway7",
			"name": "checkapprovalstatus",
			"default": "aff972a9-0b75-46b7-880b-47bfd8ef9010"
		},
		"d13c0a1f-7939-4330-89c7-4d364665050e": {
			"classDefinition": "com.sap.bpm.wfs.ServiceTask",
			"destination": "KS4_300",
			"path": "/sap/opu/odata/sap/ZZAG_USER_SCP_SRV/UserIDSet?",
			"httpMethod": "POST",
			"xsrfPath": "/sap/opu/odata/sap/ZZAG_USER_SCP_SRV/UserIDSet?$format=json",
			"requestVariable": "${context.userData.input}",
			"responseVariable": "${context.userData.output}",
			"id": "servicetask10",
			"name": "createuserid"
		},
		"6766f508-51bc-4c3d-96a7-2db5386f998c": {
			"classDefinition": "com.sap.bpm.wfs.ScriptTask",
			"reference": "/scripts/xxxxxx_test_workflow/convertinput.js",
			"id": "scripttask1",
			"name": "convertinput"
		},
		"78813c21-0c1c-46b4-97c4-f7708e83355e": {
			"classDefinition": "com.sap.bpm.wfs.SequenceFlow",
			"id": "sequenceflow1",
			"name": "SequenceFlow1",
			"sourceRef": "00409a22-fa0c-4584-a365-c61086e16aed",
			"targetRef": "07a3c547-1ab4-49dc-9080-700f804131a9"
		},
		"bee44173-c5e4-4af7-8f8b-9ec9575abebb": {
			"classDefinition": "com.sap.bpm.wfs.SequenceFlow",
			"id": "sequenceflow32",
			"name": "SequenceFlow32",
			"sourceRef": "07a3c547-1ab4-49dc-9080-700f804131a9",
			"targetRef": "16b85e4a-2800-41b3-8e8e-323229ce5d62"
		},
		"aff972a9-0b75-46b7-880b-47bfd8ef9010": {
			"classDefinition": "com.sap.bpm.wfs.SequenceFlow",
			"id": "sequenceflow33",
			"name": "approved",
			"sourceRef": "16b85e4a-2800-41b3-8e8e-323229ce5d62",
			"targetRef": "6766f508-51bc-4c3d-96a7-2db5386f998c"
		},
		"69f3a1d9-f981-48fa-b640-abae15a4c737": {
			"classDefinition": "com.sap.bpm.wfs.SequenceFlow",
			"id": "sequenceflow34",
			"name": "SequenceFlow34",
			"sourceRef": "d13c0a1f-7939-4330-89c7-4d364665050e",
			"targetRef": "b3dac5df-c682-4bbb-9e3b-d6bcaab6170a"
		},
		"9d264671-8990-4e55-98f4-850c2d5c873d": {
			"classDefinition": "com.sap.bpm.wfs.SequenceFlow",
			"condition": "${context.userData.Rejected == 'X'}",
			"id": "sequenceflow35",
			"name": "rejected",
			"sourceRef": "16b85e4a-2800-41b3-8e8e-323229ce5d62",
			"targetRef": "b3dac5df-c682-4bbb-9e3b-d6bcaab6170a"
		},
		"8aaff4b9-d464-4b76-b438-f5b6c9d74646": {
			"classDefinition": "com.sap.bpm.wfs.SequenceFlow",
			"id": "sequenceflow37",
			"name": "SequenceFlow37",
			"sourceRef": "6766f508-51bc-4c3d-96a7-2db5386f998c",
			"targetRef": "d13c0a1f-7939-4330-89c7-4d364665050e"
		},
		"1f8998ef-cdc3-45d4-b8a8-39c046b59287": {
			"classDefinition": "com.sap.bpm.wfs.ui.Diagram",
			"symbols": {
				"f9241189-4a1a-4a81-8bd0-fbf5a0ca34f3": {},
				"c6078b39-7f64-424c-ac47-a7c7704dce52": {},
				"9f512907-f706-4d0c-8b4a-813383b803a0": {},
				"9b1f7730-cc0a-413f-af7f-9c3e2d0177d7": {},
				"f5c67205-9447-4c79-bf72-6d63d591ed0f": {},
				"62fe8d15-031f-42a5-9690-e225123540fd": {},
				"65a53bed-6f5e-4f95-9da5-abc5a9cf6bd2": {},
				"cb4ad202-7172-4917-8816-b2fc63c871d1": {},
				"cbb50edd-0a2d-4f0c-91ae-96f9b1e186fb": {},
				"be0a0b4e-51b5-477f-b1d2-7ba679c77aa0": {},
				"3e9f7f76-edc3-43c7-82b5-90616152af85": {},
				"85c0be08-b4da-492a-bb8e-2ef3f8c951d7": {}
			}
		},
		"f9241189-4a1a-4a81-8bd0-fbf5a0ca34f3": {
			"classDefinition": "com.sap.bpm.wfs.ui.StartEventSymbol",
			"x": 31,
			"y": 96,
			"width": 32,
			"height": 32,
			"object": "00409a22-fa0c-4584-a365-c61086e16aed"
		},
		"c6078b39-7f64-424c-ac47-a7c7704dce52": {
			"classDefinition": "com.sap.bpm.wfs.ui.EndEventSymbol",
			"x": 722,
			"y": 96,
			"width": 32,
			"height": 32,
			"object": "b3dac5df-c682-4bbb-9e3b-d6bcaab6170a"
		},
		"9f512907-f706-4d0c-8b4a-813383b803a0": {
			"classDefinition": "com.sap.bpm.wfs.ui.SequenceFlowSymbol",
			"points": "47,116.5 153,116.5",
			"sourceSymbol": "f9241189-4a1a-4a81-8bd0-fbf5a0ca34f3",
			"targetSymbol": "9b1f7730-cc0a-413f-af7f-9c3e2d0177d7",
			"object": "78813c21-0c1c-46b4-97c4-f7708e83355e"
		},
		"9b1f7730-cc0a-413f-af7f-9c3e2d0177d7": {
			"classDefinition": "com.sap.bpm.wfs.ui.UserTaskSymbol",
			"isAdjustToContent": false,
			"x": 101,
			"y": 87,
			"width": 104,
			"height": 68,
			"object": "07a3c547-1ab4-49dc-9080-700f804131a9"
		},
		"f5c67205-9447-4c79-bf72-6d63d591ed0f": {
			"classDefinition": "com.sap.bpm.wfs.ui.SequenceFlowSymbol",
			"points": "153,118.75 267.25,118.75",
			"sourceSymbol": "9b1f7730-cc0a-413f-af7f-9c3e2d0177d7",
			"targetSymbol": "62fe8d15-031f-42a5-9690-e225123540fd",
			"object": "bee44173-c5e4-4af7-8f8b-9ec9575abebb"
		},
		"62fe8d15-031f-42a5-9690-e225123540fd": {
			"classDefinition": "com.sap.bpm.wfs.ui.ExclusiveGatewaySymbol",
			"x": 246.25,
			"y": 95.5,
			"object": "16b85e4a-2800-41b3-8e8e-323229ce5d62"
		},
		"65a53bed-6f5e-4f95-9da5-abc5a9cf6bd2": {
			"classDefinition": "com.sap.bpm.wfs.ui.SequenceFlowSymbol",
			"points": "267.25,116.75 519,116.75",
			"sourceSymbol": "62fe8d15-031f-42a5-9690-e225123540fd",
			"targetSymbol": "3e9f7f76-edc3-43c7-82b5-90616152af85",
			"object": "aff972a9-0b75-46b7-880b-47bfd8ef9010"
		},
		"cb4ad202-7172-4917-8816-b2fc63c871d1": {
			"classDefinition": "com.sap.bpm.wfs.ui.ServiceTaskSymbol",
			"x": 581.8226228040958,
			"y": 87.25,
			"width": 100,
			"height": 60,
			"object": "d13c0a1f-7939-4330-89c7-4d364665050e"
		},
		"cbb50edd-0a2d-4f0c-91ae-96f9b1e186fb": {
			"classDefinition": "com.sap.bpm.wfs.ui.SequenceFlowSymbol",
			"points": "631.8226228040958,114.625 738,114.625",
			"sourceSymbol": "cb4ad202-7172-4917-8816-b2fc63c871d1",
			"targetSymbol": "c6078b39-7f64-424c-ac47-a7c7704dce52",
			"object": "69f3a1d9-f981-48fa-b640-abae15a4c737"
		},
		"be0a0b4e-51b5-477f-b1d2-7ba679c77aa0": {
			"classDefinition": "com.sap.bpm.wfs.ui.SequenceFlowSymbol",
			"points": "267.25,137 267.25,187.5 738,187.5 738,127.5",
			"sourceSymbol": "62fe8d15-031f-42a5-9690-e225123540fd",
			"targetSymbol": "c6078b39-7f64-424c-ac47-a7c7704dce52",
			"object": "9d264671-8990-4e55-98f4-850c2d5c873d"
		},
		"3e9f7f76-edc3-43c7-82b5-90616152af85": {
			"classDefinition": "com.sap.bpm.wfs.ui.ScriptTaskSymbol",
			"x": 469,
			"y": 87,
			"width": 100,
			"height": 60,
			"object": "6766f508-51bc-4c3d-96a7-2db5386f998c"
		},
		"85c0be08-b4da-492a-bb8e-2ef3f8c951d7": {
			"classDefinition": "com.sap.bpm.wfs.ui.SequenceFlowSymbol",
			"points": "519,117.125 631.8226228040958,117.125",
			"sourceSymbol": "3e9f7f76-edc3-43c7-82b5-90616152af85",
			"targetSymbol": "cb4ad202-7172-4917-8816-b2fc63c871d1",
			"object": "8aaff4b9-d464-4b76-b438-f5b6c9d74646"
		},
		"e358ed89-374f-4866-a2e8-97e4309214c3": {
			"classDefinition": "com.sap.bpm.wfs.LastIDs",
			"terminateeventdefinition": 1,
			"messageeventdefinition": 1,
			"hubapireference": 1,
			"sequenceflow": 38,
			"startevent": 1,
			"intermediatemessageevent": 1,
			"endevent": 3,
			"usertask": 1,
			"servicetask": 13,
			"scripttask": 1,
			"exclusivegateway": 7,
			"parallelgateway": 1
		}
	}
}

 

4. The final piece of this puzzle is the custom UI to approve/reject the workflow.

Create another custom UI5 application. I have again used SAP WebIDE as my favourite tool to create this application.

Create a view as per following screen-shot:

Here is the code behind my UI5 screen:

<App height="90%">
		<pages>
			<Page showHeader="false" showFooter="false">
				<content>
					<l:VerticalLayout width="100%" id="__layout0">
						<l:content>
							<ObjectHeader title="{/context/task/Title}" titleActive="false" id="__header0">
								<attributes>
									<ObjectAttribute title="{i18n>createdOn}" text="{/context/task/CreatedOn}"/>
								</attributes>
								<statuses>
									<ObjectStatus text="{/context/task/Status}" state="None"/>
									<ObjectStatus text="{/context/task/Priority}" state="{/context/task/PriorityState}"/>
								</statuses>
							</ObjectHeader>
							<l:HorizontalLayout id="__layout1">
								<l:content>
									<IconTabBar selectedKey="__filter0" id="__bar0">
										<items>
											<IconTabFilter icon="sap-icon://hint" id="__filter0">
												<content>
													<l:HorizontalLayout id="hLayout11">
														<FlexBox width="100%" id="__box0">
															<items>
																<l:VerticalLayout id="hLayout12">
																	<HBox width="100%" id="__hbox0">
																		<l:HorizontalLayout id="hLayoutJob1">
																			<f:Form>
																				<f:layout>
																					<f:GridLayout singleColumn="true"/>
																				</f:layout>
																				<f:FormContainer expanded="true" title="{i18n>persInfo}" expandable="false">
																					<f:formElements>
																						<f:FormElement label="First name">
																							<Text text="{/context/userData/firstname}"/>
																						</f:FormElement>
																						<f:FormElement label="Last name">
																							<Text text="{/context/userData/lastname}"/>
																						</f:FormElement>
																						<f:FormElement label="Email ID">
																							<Text text="{/context/userData/email}" id="email"/>
																						</f:FormElement>
																					</f:formElements>
																				</f:FormContainer>
																			</f:Form>
																		</l:HorizontalLayout>
																	</HBox>
																</l:VerticalLayout>
															</items>
														</FlexBox>
													</l:HorizontalLayout>
												</content>
											</IconTabFilter>
											<IconTabFilter icon="sap-icon://employee-lookup" id="__filter1" visible="false"/>
										</items>
									</IconTabBar>
								</l:content>
							</l:HorizontalLayout>
						</l:content>
					</l:VerticalLayout>
				</content>
			</Page>
		</pages>
	</App>

Here is the code from my Component.js file

metadata: {
			manifest: "json",
			publicMethods: ["updateBinding"]
		},

		/**
		 * The component is initialized by UI5 automatically during the startup of the app and calls the init method once.
		 * @public
		 * @override
		 */
		init: function() {
			// call the base component's init function
			UIComponent.prototype.init.apply(this, arguments);
			this.setModel(models.createDeviceModel(), "device");
			// get task data
			var startupParameters = this.getComponentData().startupParameters;
			var taskModel = startupParameters.taskModel;
			var taskData = taskModel.getData();
			var taskId = taskData.InstanceID;
			var processContext = new sap.ui.model.json.JSONModel();

			var that = this;
			var jsonModel = new sap.ui.model.json.JSONModel();
			that.setModel(jsonModel);

			$.ajax({
				type: "GET",
				url: "/bpmworkflowruntime/rest/v1/task-instances/" + taskId + "/context",
				contentType: "application/json",
				dataType: "json",
				success: function(result, xhr, data) {

					var processContext = new sap.ui.model.json.JSONModel();
					processContext.context = data.responseJSON;

					processContext.context.task = {};
					processContext.context.task.Title = taskData.TaskTitle;
					processContext.context.task.Priority = taskData.Priority;
					processContext.context.task.Status = taskData.Status;

					if (taskData.Priority === "HIGH") {
						processContext.context.task.PriorityState = "Warning";
					} else if (taskData.Priority === "VERY HIGH") {
						processContext.context.task.PriorityState = "Error";
					} else {
						processContext.context.task.PriorityState = "Success";
					}

					processContext.context.task.CreatedOn = taskData.CreatedOn.toDateString();
					// get task description and add it to the model
					startupParameters.inboxAPI.getDescription("NA", taskData.InstanceID).done(function(dataDescr) {
						processContext.context.task.Description = dataDescr.Description;
						jsonModel.setProperty("/context/task/Description", dataDescr.Description);
					}).
					fail(function(errorText) {
						jQuery.sap.require("sap.m.MessageBox");
						sap.m.MessageBox.error(errorText, {
							title: "Error"
						});
					});
					jsonModel.setData(processContext);
					that.setModel(jsonModel);
				}
			});
			startupParameters.inboxAPI.addAction({
					action: "Approve",
					label: "Approve"
				}, function(button) {
					this._completeTask(taskId, true);
				},
				this);
			startupParameters.inboxAPI.addAction({
				action: "Reject",
				label: "Reject"
			}, function(button) {
				var email = jsonModel.getProperty("/context/userData/email");
				jsonModel.setProperty("/context/userData/Rejected", "X");
				sap.m.URLHelper.triggerEmail(email, "Request to create userid rejected",
					"Request to create userid has been rejected. Please contact helpdesk.");
				this._completeTask(taskId, false);
			}, this);
		},

		_completeTask: function(taskId, approvalStatus) {
			var token = this._fetchToken();
			$.ajax({
				url: "/bpmworkflowruntime/rest/v1/task-instances/" + taskId,
				method: "PATCH",
				contentType: "application/json",
				async: false,
				data: "{\"status\": \"COMPLETED\", \"context\": {\"approved\":\"" + approvalStatus + "\"}}",
				headers: {
					"X-CSRF-Token": token
				}
			});
			this._refreshTask(taskId);
		}

		,
		_fetchToken: function() {
			var token;
			$.ajax({
				url: "/bpmworkflowruntime/rest/v1/xsrf-token",
				method: "GET",
				async: false,
				headers: {
					"X-CSRF-Token": "Fetch"
				},
				success: function(result, xhr, data) {
					token = data.getResponseHeader("X-CSRF-Token");
				}
			});
			return token;
		},
		_refreshTask: function(taskId) {
			this.getComponentData().startupParameters.inboxAPI.updateTask("NA", taskId);
		}
	});

In above code you can see the code behind Approve and Reject buttons. It is important to understand the code here.

 

Now please make sure all 3 development pieces i.e custom workflow, custom UI for user registration and custom UI for approval are all deployed on SAP cloud platform.

Time to run some tests now and see if it all works together.

User submit request

In Monitor workflow app you can see your workflow instance running:

If you go to My Inbox app, you can see your workflow is ready for approval there:

After user has clicked on ‘Approve’ and it will call OData service and create entry in Z table in S4HANA system on premise.

 

This concludes our testing and hopefully who all are trying this blog can conclude their testing too.

To report this post you need to login first.

11 Comments

You must be Logged on to comment or reply to a post.

  1. Kavya Chilukuri

    Hi Arpit,

     

    Thanks a lot for the clarity given in this post. I have a question to ask regarding an application I am working on and since I not aware of whom to redirect my question to, I thought you could help.

    My application involves a Custom UI (not SAPUI5) where SCP Workflow service would provide the necessary workflow functionality along with consuming services within CRM.

    The ideal flow is that when the workflow is initiated, SCP Workflow services calls an OData service and returns a unique transaction ID provided by CRM. This unique transaction ID will then be displayed on the UI.

    From what I have explored, the Workflow initiation (Workflow instance creation) API gives a standard response with details such as ID, definitionID etc.

    In addition to these, I would like the Workflow initiation to also provide the payload / context with the unique transaction ID as part of the response. I haven’t come across any documentation to help me with this.

    Can you please guide?

     

    (0) 
    1. Archana Shukla

      Hello Kavya,

      When you start the workflow – you give a workflow context. You can update the workflow context during the course of the workflow processing. If you want to access the workflow context in your custom user interface then we provide public API as mentioned below.

      https://api.sap.com/shell/discover/contentpackage/SAPCPWorkflowAPIs/api/SAP_CP_Workflow

      [GET:/v1/workflow-instances/{workflowInstanceId}/context]

      In you case, while modelling the workflow you would have used a service task to call your OData service. In that service task there is a Response field. You need to fill that with something like ${context.CRM} – then all the response from the OData service would be appended in the workflow context under CRM node. Later you can access the workflow context (which has the initial payload and other information that you have appended or modified during course of workflow execution)

      You can also post question on SAP Cloud Platform Workflow in our community page:
      https://answers.sap.com/tags/73554900100800000555

      We have numerous blogs published for the workflow here:
      https://blogs.sap.com/tags/73554900100800000555/

      Official documentation is available here:
      https://help.sap.com/viewer/p/WORKFLOW_SERVICE

      Regards,
      Archana

       

      (0) 
      1. Kavya Chilukuri

        Hi Archana,

        Thank you very much for your quick reply. That makes it clearer, but I still have a few concerns. Could you please throw some light on them?

        1)  Since my UI application will involve collecting the form details from the customer and on successful submission redirect them to the page that displays the unique transaction ID, would it mean that the front-end application will have to make 2 calls in succession? One for Workflow initiation, and the second to read the updated context for the unique ID?

        Is there any way in which this can be handled in a single call? I am a little wary to ask the front end team to make two calls for every POST / PATCH request since there would be many such events in the workflow.

        2) From the point of view of the application, the unique ID generation is considered as the starting point of the very long & complex workflow. Supposing that the workflow initiation was successful, but the immediate call to the ID-generating OData service fails, then I naturally will have to terminate the workflow as an exception-handling approach. Is there any standard way in which this should be done?

         

        I am considerably new to the SCP Workflow service and hence I am still uncertain regarding the approach that needs to be adopted for such things. Thanks in advance!

         

        – Regards,

        Kavya C

        (0) 
        1. Archana Shukla

          Hello Kavya,

          Here are the answers:

          Question 1:
          There is a concept of businesskey in workflow which is a unique ID associated with the workflow context like OrderID, TravelRequestID etc. but in your case the transaction ID is determined during the workflow processing and not when then workflow is started – which means that you need to start the workflow and once its completes (or passes through that service task) then only you can read that transactionID from the workflow context.

          In that case it has to be two calls. You can write your own service to combine these two calls or use cloud integration to combine them. But I do not see a problem calling them twice – we have done that in several of our projects. May be if you can be more elaborate on your hesitance towards making 2 calls then I can help you better.

          Question 2:
          If the service calls fails then workflow will also enter into erroneous state. You can see that from Monitor Workflow application. We do have API from where you can terminate the workflow manually as well. It is also good to keep erroneous instance for analysis – but if you really want to terminate then admin can do that manually from monitor workflow app or via API from the application.

          Hope that answers.

          Regards,
          Archana

           

          (1) 
  2. Jing Biscocho

    Hi Arpit,

    Nice informative blog. Obviously, this is for a setup where the gateway is on-premise. Have you tried doing this for a setup where the gateway is on the SAP Cloud Platform (OData provisioning).

    If you have, can you please share your experience.

     

    Regards,

    Jing

    (0) 
    1. Arpit Oberoi Post author

      Hello Jing,

      Unfortunately I havent tried it with gateway on SAP Cloud platform yet? But by gateway on SAP Cloud platform do you mean S4 HANA Cloud system?

       

      Kind Regards,

      Arpit

      (0) 
      1. Jing Biscocho

         

        Hi Arpit,

        Thanks for replying. What I meant about gateway on SCP is the Gateway-as-a-service (GWaaS) or OData provisioning as it is called now. Instead of having a NW Gateway on-premise to provide OData services, we use OData provisioning on SCP.

         

        Regards,

        Jing

        (0) 
        1. Arpit Oberoi Post author

          Hello Jing,

          I see what you mean. As I havent tried that yet so can’t comment. But if you are trying and stuck anywhere I am more then happy to help if needed.

           

          Regards,

          Arpit

          (0) 

Leave a Reply