Skip to Content

Requirement:

As per latest release, SAP has introduced functionality of adding Parts from Van Stock into Service Ticket. But this functionality was not available in previous releases and in S/4HANA Integration scenarios.

This blog covers, how we can develop the same functionality of fetching Van Stock products from S/4HANA and display in additional tab on Service Ticket. This will also cover; how do we add fetched Van Stock products to Service and Repair Tab. Additionally it provides flexibility of searching Van Stock products with Material Number and an employee for whom Van Stock need to be searched. This covers the requirement on which the solution is been built.

Solution:

To fulfill this requirement, there will be 3 systems involved S4 HANA, HCI and C4C. Following developments will follow in respective systems.

S4/HANA:

First we need a Web Service created based on a custom Function Module which allows us to search Van Stock with given search criteria. Below is screenshots for FM, you can build FM as per your requirement as well.

This is the output format by which it will return output.

FM source code is as below,

DATA: lv_storage_loc TYPE string,
        lv_where_cls   TYPE string,
        lv_material_no TYPE string,
        lv_spchar      TYPE char1 VALUE '*',
        lv_emp_id      TYPE cod_service_emp,
        lv_emp_name    TYPE char80,
        lv_fname       TYPE char40,
        lv_lname       TYPE char40,
        lv_plant       TYPE string,
        ls_out         TYPE zsw_van_stck_rtrn.

  DATA: BEGIN OF ls_output,
          lv_matnr TYPE matnr,
          lv_stloc TYPE lgort_d,
          lv_descr TYPE maktx,
          lv_qty   TYPE labst,
        END OF ls_output,

        lt_output LIKE TABLE OF ls_output.

* Generate Data record for this FM
  fbgenmac 'ZFM_VANSTOCK_WEBSERV'.

  IF iv_emp_id IS NOT INITIAL.
*   Get employee name
    SELECT SINGLE vorna, nachn FROM pa0002 INTO (@lv_fname,@lv_lname) WHERE pernr = @iv_emp_id.
    IF sy-subrc = 0.

*     Get Storage location assigned to employee
      SELECT SINGLE storage_loc, plant FROM cod_plant_srv INTO (@lv_storage_loc,@lv_plant) WHERE service_tech = @iv_emp_id.

*     Start building where clause based on input
      IF lv_storage_loc IS NOT INITIAL.
        CONCATENATE '''' lv_storage_loc '''' INTO lv_storage_loc.
        CONCATENATE 'a~LGORT' 'EQ' lv_storage_loc INTO lv_where_cls SEPARATED BY space.

*       Add condition for material based on pattern if provided
        IF iv_material_no IS NOT INITIAL.
          IF iv_material_no CA lv_spchar.
            REPLACE lv_spchar INTO iv_material_no WITH '%'.
            CONCATENATE '''' iv_material_no '''' INTO lv_material_no.
            CONCATENATE lv_where_cls 'AND' 'a~MATNR' 'LIKE' lv_material_no INTO lv_where_cls SEPARATED BY space.
          ELSE.
            CONCATENATE '''' iv_material_no '''' INTO lv_material_no.
            CONCATENATE lv_where_cls 'AND' 'a~MATNR' 'EQ' lv_material_no INTO lv_where_cls SEPARATED BY space.
          ENDIF.
        ELSE.
          CONCATENATE lv_where_cls 'AND' 'a~MATNR' 'LIKE' '''%''' INTO lv_where_cls SEPARATED BY space.
        ENDIF.

*       Finish where clause and fire query to fetch data
        IF lv_where_cls IS NOT INITIAL.
          CONCATENATE '''' lv_plant '''' INTO lv_plant.
          CONCATENATE lv_where_cls 'AND' 'a~WERKS' 'EQ' lv_plant 'AND' 'b~SPRAS' 'EQ' '''E''' INTO lv_where_cls SEPARATED BY space.
          SELECT a~matnr, a~lgort, b~maktx, a~labst FROM mard AS a INNER JOIN makt AS b ON a~matnr = b~matnr WHERE (lv_where_cls) ORDER BY a~matnr
            INTO TABLE @lt_output.
        ENDIF.

*       Prepare output
        CONDENSE lv_fname.
        CONDENSE lv_lname.
        IF sy-subrc = 0.
          LOOP AT lt_output INTO ls_output.
            ls_out-ev_material_no = ls_output-lv_matnr.
            CONCATENATE lv_fname lv_lname INTO ls_out-ev_empl_name SEPARATED BY space.
            ls_out-ev_description = ls_output-lv_descr.
            ls_out-ev_stock = ls_output-lv_qty.
            APPEND ls_out TO et_results.
          ENDLOOP.
        ELSE.
          ls_out-ev_material_no = iv_material_no.
          CONCATENATE lv_fname lv_lname INTO ls_out-ev_empl_name SEPARATED BY space.
          ls_out-ev_description = TEXT-001.
          APPEND ls_out TO et_results.
        ENDIF.
      ELSE.
        CONDENSE lv_fname.
        CONDENSE lv_lname.
        CONCATENATE lv_fname lv_lname INTO ls_out-ev_empl_name SEPARATED BY space.
        ls_out-ev_description = TEXT-002.
        APPEND ls_out TO et_results.
      ENDIF.
    ELSE.
      ls_out-ev_empl_name = TEXT-003.
      APPEND ls_out TO et_results.
    ENDIF.
  ELSE.
    ls_out-ev_empl_name = TEXT-004.
    APPEND ls_out TO et_results.
  ENDIF.

After FM has been implemented, create a Web Service based on this FM as below:

After Web Service is created and it is exposed via SOAMANAGER configurations, download it’s WSDL. This WSDL will be used at 2 places, one in Cloud Application Studio to create External Web Service Integration(.wsid) and in HCI to create artifact. This artifact will be a mapping between C4C and S/4 system for communication.

HCI:

Below is artifact created in HCI:

Once this artifact is created and mapping has been done in HCI, this will end all prerequisites for Cloud Studio Developments.

Cloud Studio Developments:

In Cloud Studio first thing we need to do is extend standard Service Request object with 2 fields, 1 action and 1 node with 0: n cardinality. This will be required to hold the data temporarily. Particularly in this node extension, as we are not storing any Van Stock data in C4C, we need all the fields in node to be Transient. Please see below screenshot.

After this extension is created and activated, first we will create an empty implementation of action FetchVanStock.

After this, we will create External Service Integration based on WSDL we have downloaded from S4 system. Then a communication scenario and communication arrangement will be created for this Web Service Integration. They will be as per below screenshot,

Communication Scenario:

Communication Arrangement:

Here only part that one must remember is path and Host Name should be taken from HCI instead of keeping it as original.

After this is done we will move on to implementing FetchVanStock action of our extension.

import ABSL;
import AP.Common.GDT;
import AP.CRM.Global;
import CommunicationServicesManagement.Global;
import AP.PC.IdentityManagement.Global;
import AP.FO.BusinessPartner.Global;
import AP.FO.ProductDataMaintenance.Global;

var requestVanStock: Library::VanStockWebService.ZfmVanstockWebserv.Request;
var responseVanStock: Library::VanStockWebService.ZfmVanstockWebserv.Response;

/*Below code is written to get Employee ID which need to be passed to Web Service.
1. See if User has selected Employee ID via F4 help provided on emplID field on screen
   If yes then get it's External ID to pass to Web Service
2. If Employee ID is not selected then first we need to set a default id to this field
   Default id is to be set as 
   a. Service Technician for current opened ticket
   b. If Service Technician is not set in ticket then get current logged in user
   After this employee is determined get thier External ID and set it in Web Service 
   import parameter */
if(!this.emplID.IsInitial()){
	var empQuery = Employee.Identification.QueryByEmployeeAttributes;
	var selParam = empQuery.CreateSelectionParams();
	selParam.Add(empQuery.EmployeeID.content,"I","EQ",this.emplID.RemoveLeadingZeros().content);
	var empResult = empQuery.Execute(selParam);	
	if(empResult.Count() > 0){
		var empInst = empResult.GetFirst();
		var objectQuery = ObjectIdentifierMapping.QueryByElements;
		var selectParam = objectQuery.CreateSelectionParams();
		selectParam.Add(objectQuery.LocalObjectNodeReference.UUID.content,"I","EQ",empInst.ToParent.UUID.content);
		var objQueryRes = objectQuery.Execute(selectParam);
		if(objQueryRes.Count() > 0){
			var objectIDMapp = objQueryRes.GetFirst();
			if(!objectIDMapp.RemoteObjectID.IsInitial()){
				requestVanStock.IvEmpId = objectIDMapp.RemoteObjectID.content;
			}
		}
	}
}
else{
	var serviceTechnPartyCol = this.Party.Where(n=>n.RoleCode == "43");
	if(serviceTechnPartyCol.Count() > 0){
		var servTechnInst = serviceTechnPartyCol.GetFirst();
		if(servTechnInst.Party.BusinessPartner.IsSet()){
			var objectIdMappCol = servTechnInst.Party.BusinessPartner.ObjectIdentifierMapping;
			if(objectIdMappCol.Count() > 0){
				var objectIDMapp = objectIdMappCol.GetFirst();
				if(!objectIDMapp.RemoteObjectID.IsInitial()){
					requestVanStock.IvEmpId = objectIDMapp.RemoteObjectID.content;
					this.emplID.content = servTechnInst.Party.Employee.IdentificationEmployeeID.EmployeeID.RemoveLeadingZeros().content;
				}
			}
		}
	}
	else{
		var identityUUID = Context.GetCurrentIdentityUUID();
		var identity = Identity.Retrieve(identityUUID);
		if(identity.IsSet()){
			var partnerInst = BusinessPartner.Retrieve(identity.BusinessPartnerUUID);
			if(partnerInst.IsSet()){
				var objectIdMappCol = partnerInst.ObjectIdentifierMapping;
				if(objectIdMappCol.Count() > 0){
					var objectIDMapp = objectIdMappCol.GetFirst();
					if(!objectIDMapp.RemoteObjectID.IsInitial()){
						requestVanStock.IvEmpId = objectIDMapp.RemoteObjectID.content;
						this.emplID.content = partnerInst.InternalID.RemoveLeadingZeros();
					}
				}
			}
		}
	}
}

/*This below code is written to provide user a flexible search using wild card parameter(*)
  This will ensure that right pattern is passed to Web Service*/

if(!this.materialNumb.IsInitial()){
	var material = this.materialNumb.Replace("*","%");
	var pos = material.Find("%");
	var nextPos = material.FindLast("%");
	if(pos != nextPos){
		if(pos == 0 || nextPos == (this.materialNumb.Length() - 1)){
			var subString1 = material.Substring(0,pos+1);
			var subString2 = subString1.Replace("%","*");
			var substring3 = material.Substring(pos+1,this.materialNumb.Length()-pos-1);
			requestVanStock.IvMaterialNo = subString2.Concatenate(substring3);
		}
		else{
			requestVanStock.IvMaterialNo = "%".Concatenate(material).Concatenate("*");
		}
	}
	else{
		if(pos == 0 || pos == (this.materialNumb.Length() - 1)){
			requestVanStock.IvMaterialNo = this.materialNumb;
		}
		else{
			requestVanStock.IvMaterialNo = "%".Concatenate(material).Concatenate("*");
		}
	}
}
else{
	requestVanStock.IvMaterialNo = "*";
}

/*Call Web Service*/
responseVanStock = Library::VanStockWebService.ZfmVanstockWebserv(requestVanStock," ","VanStockWebService");

/*Once we receive results, either products or no result found, we will add them in our Node
  Web Service has made sure that it will always have a result.
  First we will delete current Van Stock node entries and then loop on received results
  to create new rows inside our node extension*/
if(!responseVanStock.EtResults.IsInitial()){
	var items = responseVanStock.EtResults.item;
	if(this.VanStock.Count() > 0){
		this.VanStock.Delete();
	}
	foreach(var item in items){
		if(!item.EvStock.IsInitial()){
			var vanStockNode: elementsof ServiceRequest.VanStock;
			vanStockNode.MaterialNumber.content = item.EvMaterialNo;
			vanStockNode.MatDescription = item.EvDescription;
			vanStockNode.Stock.content = item.EvStock;
			if(item.EvStockUnit.IsInitial() && !item.EvMaterialNo.IsInitial()){
				var material = Material.Retrieve(vanStockNode.MaterialNumber);
				if(material.IsSet() && material.ValuationQuantityUnit.IsSet()){
					vanStockNode.Stock.unitCode = material.ValuationQuantityUnit.MeasureUnitCode;
				}
				else{
					vanStockNode.Stock.unitCode = "EA";
				}
			}
			vanStockNode.EmpName = item.EvEmplName;
			this.VanStock.Create(vanStockNode);
		}
	}
}

After this action is implemented, we will create and Embedded Component to display Van Stock. In Embedded Component, we will add model as per below explanation.

First bind root to ServiceRequest object and create InPort. Add newly created fields emplID and materialNum in model.

Next create a Data List by binding it to Item Node of Service Request.

Also add an Item modification structure for this Data List using given fields. This Data List will be a hidden Data List and will not be shown on screen.

Now go to Designer tab and start creating UI.

 

This will the UI that we will be creating. Mapping will be done as Service Employee is mapped to emplID field that we newly added. Material will be mapped to materialNum field.

After this we will add Advanced List Pane with toolbar as shown in above field. Before we do any further coding, we will map Data List created by this Pane to our new Node VanStock

Now our UI is built; we will start implementing various aspects of this Embedded Component.

First we will implement, Initialize event,

Whereas BOAction, which is called in second step, will be FetchVanStock.

This Event is called inside InPort that we have created as below,

After this create two more events as SubmitSearch and ResetSearch. They will be linked with toolbar buttons Go and Reset respectively.

SubmitSearch:

ResetSearch:

If implemented till this point, will give satisfactory result of viewing Van Stock from any Employee’s Van or searching for specific material in the same.

Now we will develop functionality to add selected material displayed on screen (from Van Stock) into Items (Service and Repair Tab). This was a little tricky part. Remember we have added Item Data List and List Modification structure in our Data Model. That plays a crucial role over here.

First we will do some tweaks in List to and Pane so that it behaves as per our needs.

First set List inside Advanced List Pane as Single Select.

Next make our Embedded Component Non-Work protect relevant so that whenever we use this tab in non-edit mode, it will not turn Service Request to edit mode.

Next we will change Service Employee to Object Value Selector using standard value help.

Next Go to Data Model and select VanStock Data List and in properties add below events,

The event is written to fill List Modification Structure with selected Product on screen.

Please note here that Material Number returned by Web Service has exact same id as that of C4C product due to all products already been replicated from S/4 to C4C.

Now back to our Add to Items button, this button will trigger an event which will call List Modification structure pop-up.

Next create a List Modification Form from List Modification structure we have created as below

After this is all done we need to add this Embedded Component to two TI Screens COD_SR_TI.TI.xuicomponent and COD_SEOD_Agent_Workspace_TI.TI.xuicomponent. Why two TI screens? This is because for Service Ticket there are two different screens for Desktop and Mobile devices. First TI screen mentioned above is for mobile devices and tablets whereas second screen is for desktop.

Once this is done, we are ready for the testing.

How it looks and Work:

In non-edit mode:

First time it will fetch all the materials in Van Stock of Service Technician. If Service Technician is not determined, then it will try with current logged in user.

In edit mode, it will let user edit Service Employee and Material pattern to search for specific Product.

To add one of the product from Van Stock, user have to select any row then press Add To Items button.

Here user can enter required quantity and when he presses Add button, it will get added to Service and Repair Tab.

This ends the blog content. Please let me know how it has turned up as it is my first attempt at blog.

Regards,

Omkar Uthale

To report this post you need to login first.

4 Comments

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

  1. Suchita Phulkar

    Hi Omkar,

    Can you please shed some light on the HCI artifact’s component “RequestReply” ?

    Did it hold any field mappings etc. ? if not, what does it hold ?

    Many Thanks !

    Regards

    Suchita

    (0) 
    1. Omkar Uthale Post author

      Hello Suchita,

      Sorry for late reply due to some reasons.

      Yes we have field mapping in artifact component RequestReply. This is very basic mapping between C4C side structure fields and S4 side Web Service fields at input and output. Plain mapping and nothing else.

      Regards,

      Omkar

      (1) 

Leave a Reply