Technology Blogs by SAP
Learn how to extend and personalize SAP applications. Follow the SAP technology blog for insights into SAP BTP, ABAP, SAP Analytics Cloud, SAP HANA, and more.
cancel
Showing results for 
Search instead for 
Did you mean: 
fau
Advisor
Advisor

Analytics Designer complements SAC stories by allowing to respond to very specific scenarios.
One of these scenarios is to be able to explore and analyze data following an alert sent by the system (for example a sales revenue warning). Then be able to make simulations to correct this problem (review the material prices) and finally validate the option that solves the problem (update the material prices).
This workflow is called a close loop scenario. It basically gives the power to a business user to do a what-if analysis and to take a concrete action based on his simulation.


 
In this blog post I will show you how to implement an ODataService in S/4Hana and how to call it from an Analytic Application. I will use a well-known demo at SAP called BestRun Bike.

Demo context


The demo shows BestRun Bikes company tracks, analyzes eBike sales performance issue using SAP Analytics Cloud analytic application and takes the insight into action to improve the sales revenue.

The highlighted features include:

  • Embed Explorer, Smart Discovery, R visualization in analytic application

  • Call OData actions in the backend S/4HANA system via scripting


I will concentrate the rest of the blog post on the OData part. In this demo all the product prices are read from the S4HANA system using OData Function

In the simulation&planning page we, can see that the sales revenue based on current prices is showing red alert compared to budget.


During the analysis of the eBike sales performance, we can see that the MZ-FZ-E211 eBike model has a much higher price than competitive products.


To adjust the price to the competition, we can run a simulation on the eBike prices. In our simulation we reduce the price by 15%.


This has the effect of increasing the forecast revenue and the quantity sold in a positive way. We need to update the price based on our simulation. The good news is that to change the price you don't need to go into the S/4Hana system. Thanks to Analytics Designer you can directly call an OData service that has been implemented in S/4Hana system. To do so we just need to click on the “Update” button



 

Create an OData Service in S/4HANA


In S/4Hana you can use 2 versions of OData : version 2 and version 4. In order to write back data you need to use the OData V4 version. In this blog I will explain what are the main steps to create an ABAP OData Service. If you want to have more details regarding the implementation of an OData V4 Service please have a look here

To be able to consume the S/4Hana OData service in this demo we had to:

  1. Create ABAP CDS view

  2. Create ABAP OData Service and implement ABAP classes

  3. Test the OData Service URL


Create ABAP CDS View
For this demo we created 2 ABAP CDS views that read the data from a Pricing table:

  • ZMATERIALCOND : Base CDS view to read Material Condition Records


@AbapCatalog.sqlViewName: 'ZMATCONDRECORD'
@AbapCatalog.compiler.compareFilter: true
@AbapCatalog.preserveKey: true
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'Material Conditio Record'
define view ZMATERIALCOND as select from a304 join konp on a304.knumh = konp.knumh{
key a304.matnr as Material,
a304.knumh as ConditionRecord,

@Semantics.amount.currencyCode: 'Currency'
konp.kbetr as Amount,

@Semantics.currencyCode: true
konp.konwa as Currency,
konp.kpein as PriceUnit,
konp.kmein as UoM

}
where a304.kappl = 'V' and a304.kschl = 'PPR0' and a304.vkorg = '1710' and a304.vtweg = '10' and konp.konwa != '';


  • ZMATERIALPRICE : Consumption CDS view to join Material Valuation & Condition Records


@AbapCatalog.sqlViewName: 'ZMATPRICE'
@AbapCatalog.compiler.compareFilter: true
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'Material price'
@OData.publish: true
define view ZMATERIALPRICE as select from mbew join ZMATERIALCOND on mbew.matnr = ZMATERIALCOND.Material {

key mbew.matnr as materialno
,
@Semantics.amount.currencyCode: 'Currency'
mbew.stprs as StandardPrice,

@Semantics.amount.currencyCode: 'Currency'
mbew.verpr as PeriodicUnitPrice,

@Semantics.amount.currencyCode: 'Currency'
mbew.vjstp as StdPricePreviousYear ,

@Semantics.amount.currencyCode: 'Currency'
ZMATERIALCOND.Amount as ConditionAmount,
@Semantics.currencyCode: true
ZMATERIALCOND.Currency as Currency

// ZMATERIALCOND.PriceUnit as PriceUnit,

//ZMATERIALCOND.UoM as UoM

}

To expose these CDS view to external application, we created an ABAP OData service. To do so, you need to type the Transaction Code : SEGW in the S/4Hana System

Create ABAP OData Service
Then we created a new OData service. You need to select the Project Type= OData 4.0 Service.
In our example, we called the project zanalytics_material


Add EntitySet, Entity and OData actions
Once the service has been created we added an entityset called MaterialPriceDataSet and an entity MaterialPriceData

We also needed to implement 2 mandatory classes :

  • The model provider class  (ZCL_ZANALYTICS_MATERIA_MPC_EXT)

  • The data provider class ( ZCL_ZANALYTICS_MATERIA_DPC_EXT)


In the MPC EXT class we declared a new action called “MaterialPriceUpdate“ (line14). This is the action that is called by the analytic application



Here is an example of the implementation for this class
class ZCL_ZANALYTICS_MATERIA_MPC_EXT definition
public
inheriting from ZCL_ZANALYTICS_MATERIA_MPC
create public .

public section.

methods DEFINE_MATERIALDATA
importing
!IO_MODEL type ref to /IWBEP/IF_V4_MED_MODEL .

methods /IWBEP/IF_V4_MP_BASIC~DEFINE
redefinition .
protected section.
private section.
ENDCLASS.



CLASS ZCL_ZANALYTICS_MATERIA_MPC_EXT IMPLEMENTATION.


* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Public Method ZCL_ZANALYTICS_MATERIA_MPC_EXT->/IWBEP/IF_V4_MP_BASIC~DEFINE
* +-------------------------------------------------------------------------------------------------+
* | [--->] IO_MODEL TYPE REF TO /IWBEP/IF_V4_MED_MODEL
* | [--->] IO_MODEL_INFO TYPE REF TO /IWBEP/IF_V4_MODEL_INFO
* | [!CX!] /IWBEP/CX_GATEWAY
* +--------------------------------------------------------------------------------------</SIGNATURE>
method /IWBEP/IF_V4_MP_BASIC~DEFINE.
**TRY.
*CALL METHOD SUPER->/IWBEP/IF_V4_MP_BASIC~DEFINE
* EXPORTING
* IO_MODEL =
* IO_MODEL_INFO =
* .
** CATCH /IWBEP/CX_GATEWAY .
super->/IWBEP/IF_V4_MP_BASIC~DEFINE(
EXPORTING
IO_MODEL = IO_MODEL " OData V4 metadata model
IO_MODEL_INFO = IO_MODEL_INFO " Information of / for the metadata model
).
*CATCH /IWBEP/CX_GATEWAY. " SAP Gateway Exception
*CATCH /IWBEP/CX_GATEWAY. " SAP Gateway Exception

**ENDTRY.
define_materialdata( io_model ).


endmethod.


* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Public Method ZCL_ZANALYTICS_MATERIA_MPC_EXT->DEFINE_MATERIALDATA
* +-------------------------------------------------------------------------------------------------+
* | [--->] IO_MODEL TYPE REF TO /IWBEP/IF_V4_MED_MODEL
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD define_materialdata.
DATA:
lo_action TYPE REF TO /iwbep/if_v4_med_action,

lo_action_import TYPE REF TO /iwbep/if_v4_med_action_imp,
lo_parameter TYPE REF TO /iwbep/if_v4_med_act_param,
lo_return TYPE REF TO /iwbep/if_v4_med_act_return.

"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
" Update Material price
"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""

lo_action = io_model->create_action( 'MATERIALPRICEUPDATE' ).
lo_action->set_edm_name( 'MaterialPriceUpdate' ).

lo_action_import = lo_action->create_action_import( iv_action_import_name = 'MATERIALPRICEUPDATE' ).
lo_action_import->set_edm_name( 'MaterialPriceUpdate' ).
lo_action_import->set_entity_set_name( 'MATERIALPRICEDATASET' ).

* CATCH /iwbep/cx_v4_med. " Metadata exception
* CATCH /iwbep/cx_v4_med. " OData V4 - Metadata exception

io_model->create_primitive_type( iv_primitive_type_name = 'STRING' )->set_edm_type( /iwbep/if_v4_med_element=>gcs_edm_data_types-string ).
lo_parameter = lo_action->create_parameter( 'MATERIAL' ).
lo_parameter->set_edm_name( 'Material' ).
lo_parameter->set_primitive_type( 'STRING' ).
* lo_parameter->set_entity_type( 'MATERIALPRICEDATA' ).
* lo_parameter->set_is_binding_parameter( ).

* io_model->create_primitive_type( iv_primitive_type_name = 'STRING' )->set_edm_type( /iwbep/if_v4_med_element=>gcs_edm_data_types-string ).
* FREE lo_parameter.
lo_parameter = lo_action->create_parameter( 'PRICE' ).
lo_parameter->set_edm_name( 'price' ).
lo_parameter->set_primitive_type( 'STRING' ).

lo_return = lo_action->create_return( ).
lo_return->set_entity_type( 'MATERIALPRICEDATA' ).
lo_return->set_is_collection( ).
.
ENDMETHOD.
ENDCLASS.

 

In the DPC_EXT Class, we implemented the Read and Update Price methods
The screenshot below is an example of the method “UPDATE_MATERIAL_PRICE”
You can see the places where Material Number and Material Price is being passed in the code.


Here is an example of implementation of this class
class ZCL_ZANALYTICS_MATERIA_DPC_EXT definition
public
inheriting from ZCL_ZANALYTICS_MATERIA_DPC
create public .

public section.

methods READ_MATERIAL_DATA
importing
!IO_REQUEST type ref to /IWBEP/IF_V4_REQU_BASIC_LIST
!IO_RESPONSE type ref to /IWBEP/IF_V4_RESP_BASIC_LIST
!IV_ORDERBY_STRING type STRING
!IV_WHERE_CLAUSE type STRING
!IV_SELECT_STRING type STRING
!LV_SKIP type I
!LV_TOP type I
!IS_DONE_LIST type /IWBEP/IF_V4_REQU_BASIC_LIST=>TY_S_TODO_PROCESS_LIST .

methods /IWBEP/IF_V4_DP_ADVANCED~EXECUTE_ACTION
redefinition .
methods /IWBEP/IF_V4_DP_BASIC~READ_ENTITY_LIST
redefinition .
protected section.
private section.

methods UPDATE_MATERIAL_PRICE
importing
!MATERIAL_NO type STRING
!PRICE type STRING .
ENDCLASS.



CLASS ZCL_ZANALYTICS_MATERIA_DPC_EXT IMPLEMENTATION.


* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Public Method ZCL_ZANALYTICS_MATERIA_DPC_EXT->/IWBEP/IF_V4_DP_ADVANCED~EXECUTE_ACTION
* +-------------------------------------------------------------------------------------------------+
* | [--->] IO_REQUEST TYPE REF TO /IWBEP/IF_V4_REQU_ADV_ACTION
* | [--->] IO_RESPONSE TYPE REF TO /IWBEP/IF_V4_RESP_ADV_ACTION
* | [!CX!] /IWBEP/CX_GATEWAY
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD /iwbep/if_v4_dp_advanced~execute_action.
**TRY.
*CALL METHOD SUPER->/IWBEP/IF_V4_DP_ADVANCED~EXECUTE_ACTION
* EXPORTING
* IO_REQUEST =
* IO_RESPONSE =
* .
** CATCH /IWBEP/CX_GATEWAY .
**ENDTRY.


TYPES: BEGIN OF s_matprice_action,
material TYPE string,
price TYPE string,
END OF s_matprice_action.

DATA: ls_matprice_params TYPE s_matprice_action,
lt_materialprice TYPE STANDARD TABLE OF zif_odata_v4_mat_types=>gty_cds_views-materialprice.

DATA:
lv_type_kind TYPE /iwbep/if_v4_med_element=>ty_e_med_element_kind,
lv_action_name TYPE /iwbep/if_v4_med_element=>ty_e_med_internal_name,
lv_action_import_name TYPE /iwbep/if_v4_med_element=>ty_e_med_internal_name,
ls_done TYPE /iwbep/if_v4_requ_adv_action=>ty_s_todo_process_list,

ls_materialdata TYPE zmatprice,
lt_materialdata TYPE STANDARD TABLE OF zmatprice,

ls_todo TYPE /iwbep/if_v4_requ_adv_action=>ty_s_todo_list,
* ls_done TYPE /iwbep/if_v4_requ_adv_action=>ty_s_todo_process_list,
ls_textid LIKE if_t100_message=>t100key,


lo_navigation_node TYPE REF TO /iwbep/if_v4_navigation_node.
* todo list
io_request->get_todos( IMPORTING es_todo_list = ls_todo ).

IF ls_todo-process-action_import = abap_true.


io_request->get_action_import( IMPORTING ev_action_import_name = lv_action_name ).
io_request->get_action( IMPORTING ev_action_name = DATA(lv_action) ).

IF lv_action_name = 'MATERIALPRICEUPDATE'.

io_request->get_parameter_data(
IMPORTING
es_parameter_data = ls_matprice_params " Structure with the action parameters

).

ls_done-parameter_data = abap_true.

update_material_price(
EXPORTING
material_no = ls_matprice_params-material
price = ls_matprice_params-price
).

SELECT * FROM zmaterialprice INTO TABLE @lt_materialprice
WHERE materialno = @ls_matprice_params-material.

* FIELD-SYMBOLS: <fs_material> TYPE zmaterialprice.
*
* LOOP AT lt_materialprice ASSIGNING <fs_material>.
* <fs_material>-conditionamount = ls_matprice_params-price.
** <fs_material>-standardprice = ls_matprice_params-price.
* ENDLOOP.

io_response->set_busi_data( ia_busi_data = lt_materialprice ).

ls_done-action_import = abap_true.

io_response->set_is_done( ls_done ).

ENDIF.

ENDIF.

ENDMETHOD.


* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Public Method ZCL_ZANALYTICS_MATERIA_DPC_EXT->/IWBEP/IF_V4_DP_BASIC~READ_ENTITY_LIST
* +-------------------------------------------------------------------------------------------------+
* | [--->] IO_REQUEST TYPE REF TO /IWBEP/IF_V4_REQU_BASIC_LIST
* | [--->] IO_RESPONSE TYPE REF TO /IWBEP/IF_V4_RESP_BASIC_LIST
* | [!CX!] /IWBEP/CX_GATEWAY
* +--------------------------------------------------------------------------------------</SIGNATURE>
method /IWBEP/IF_V4_DP_BASIC~READ_ENTITY_LIST.
**TRY.
*CALL METHOD SUPER->/IWBEP/IF_V4_DP_BASIC~READ_ENTITY_LIST
* EXPORTING
* IO_REQUEST =
* IO_RESPONSE =
* .
** CATCH /IWBEP/CX_GATEWAY .
**ENDTRY.

data lv_entityset_name type /iwbep/if_v4_med_element=>ty_e_med_internal_name.

data: ls_todo_list type /iwbep/if_v4_requ_basic_list=>ty_s_todo_list,
ls_done_list type /iwbep/if_v4_requ_basic_list=>ty_s_todo_process_list,
lv_where_clause type string,
lv_select_string type string,
lv_orderby_string type string,
lt_selected_property type /iwbep/if_v4_runtime_types=>ty_t_property_path,
lv_skip type i value 0,
lv_top type i value 0,
iv_orderby_string type string,
iv_where_clause type string,
iv_select_string type string,
lt_orderby_property type abap_sortorder_tab.


io_request->get_todos( importing es_todo_list = ls_todo_list ).
" $orderby was called
if ls_todo_list-process-orderby = abap_true.
ls_done_list-orderby = abap_true.
"** only supported as of 751 or 752
"get Open SQL Order by Clause
"io_request->get_osql_orderby_clause( IMPORTING ev_osql_orderby_clause = lv_orderby_string ).
* CATCH /iwbep/cx_gateway. "

io_request->get_orderby( importing et_orderby_property = lt_orderby_property ).
clear lv_orderby_string.
loop at lt_orderby_property into data(ls_orderby_property).
if ls_orderby_property-descending = abap_true.
concatenate lv_orderby_string ls_orderby_property-name 'DESCENDING' into lv_orderby_string separated by space.
else.
concatenate lv_orderby_string ls_orderby_property-name 'ASCENDING' into lv_orderby_string separated by space.
endif.
endloop.

else.
" lv_orderby_string must not be empty.
lv_orderby_string = 'PRIMARY KEY'.
endif.


" $skip / $top handling
if ls_todo_list-process-skip = abap_true.
ls_done_list-skip = abap_true.
io_request->get_skip( importing ev_skip = lv_skip ).
endif.
if ls_todo_list-process-top = abap_true.
ls_done_list-top = abap_true.
io_request->get_top( importing ev_top = lv_top ).
endif.


" $select handling
if ls_todo_list-process-select = abap_true.
ls_done_list-select = abap_true.
io_request->get_selected_properties( importing et_selected_property = lt_selected_property ).
concatenate lines of lt_selected_property into lv_select_string separated by ','.
else.
"check coding. If no columns are specified via $select retrieve all columns from the model instead?
lv_select_string = '*'.
"or better to throw an exception instead?
endif.


" specific sales orders based on $filter?
if ls_todo_list-process-filter = abap_true.
ls_done_list-filter = abap_true.
io_request->get_filter_osql_where_clause( importing ev_osql_where_clause = lv_where_clause ).
endif.

io_request->get_entity_set( importing ev_entity_set_name = lv_entityset_name ).
CASE lv_entityset_name.
WHEN 'MATERIALPRICEDATASET'.
CALL METHOD me->READ_MATERIAL_DATA
EXPORTING
IO_REQUEST = io_request
IO_RESPONSE = io_response
IV_ORDERBY_STRING = lv_select_string
IV_WHERE_CLAUSE = lV_WHERE_CLAUSE
IV_SELECT_STRING = lV_SELECT_STRING
LV_SKIP = LV_SKIP
LV_TOP = LV_TOP
IS_DONE_LIST = ls_done_list
.




WHEN OTHERS.
RAISE EXCEPTION TYPE /iwbep/cx_v4_sample_bas
EXPORTING
textid = /iwbep/cx_v4_sample_bas=>resource_not_implemented
exception_category = /iwbep/cx_v4_sample_bas=>gcs_excep_categories-provider
http_status_code = '501'
is_for_user = abap_true.
ENDCASE.
endmethod.


* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Public Method ZCL_ZANALYTICS_MATERIA_DPC_EXT->READ_MATERIAL_DATA
* +-------------------------------------------------------------------------------------------------+
* | [--->] IO_REQUEST TYPE REF TO /IWBEP/IF_V4_REQU_BASIC_LIST
* | [--->] IO_RESPONSE TYPE REF TO /IWBEP/IF_V4_RESP_BASIC_LIST
* | [--->] IV_ORDERBY_STRING TYPE STRING
* | [--->] IV_WHERE_CLAUSE TYPE STRING
* | [--->] IV_SELECT_STRING TYPE STRING
* | [--->] LV_SKIP TYPE I
* | [--->] LV_TOP TYPE I
* | [--->] IS_DONE_LIST TYPE /IWBEP/IF_V4_REQU_BASIC_LIST=>TY_S_TODO_PROCESS_LIST
* +--------------------------------------------------------------------------------------</SIGNATURE>
method READ_MATERIAL_DATA.
"entity type specific data types
DATA : lt_key_range_materialdata type ZIF_ODATA_V4_MAT_TYPES=>gt_key_range-materialprice,
ls_key_range_salesorder type line of ZIF_ODATA_V4_MAT_TYPES=>gt_key_range-materialprice,
lt_materialprice type standard table of ZIF_ODATA_V4_MAT_TYPES=>gty_cds_views-materialprice,
lt_key_materialprice type standard table of ZIF_ODATA_V4_MAT_TYPES=>gty_cds_views-materialprice.
"generic data types
data: ls_todo_list type /iwbep/if_v4_requ_basic_list=>ty_s_todo_list,
ls_done_list type /iwbep/if_v4_requ_basic_list=>ty_s_todo_process_list,
lv_count type i,


lv_max_index type i.

" Get the request options the application should/must handle
io_request->get_todos( importing es_todo_list = ls_todo_list ).

" Get the request options the application has already handled
ls_done_list = ls_done_list.

" specific sales orders based on navigation?
if ls_todo_list-process-key_data = abap_true.
io_request->get_key_data( importing et_key_data = lt_key_materialprice ).
loop at lt_key_materialprice into data(ls_key_entity).
append value #( sign = 'I' option = 'EQ' low = ls_key_entity-materialno ) to lt_key_range_materialdata.
endloop.
ls_done_list-key_data = abap_true.
endif.

" read_list must either be called with a filter or via navigation
" or $top has to be used to avoid a full table scan
if ls_todo_list-process-filter = abap_false
and ls_todo_list-process-key_data = abap_false
and lv_top = 0.

endif.

if ls_todo_list-return-busi_data = abap_true.

" read data from the CDS view
"value for max_index must only be calculated if the request also contains a $top
if lv_top is not initial.
lv_max_index = lv_top + lv_skip.
else.
lv_max_index = 0.
endif.

select (iv_select_string) from ZMATERIALPRICE
where (iv_where_clause)
and materialno in @lt_key_range_materialdata
order by (iv_orderby_string)
into corresponding fields of table @lt_materialprice
up to @lv_max_index rows.

"skipping entries specified by $skip
"not needed as of NW751 where OFFSET is supported in Open SQL
if lv_skip is not initial.
delete lt_materialprice to lv_skip.
endif.

*

io_response->set_busi_data( it_busi_data = lt_materialprice ).

else.
"if business data is requested count will be calculated by
"the framework
if ls_todo_list-return-count = abap_true.

select count( * ) from ZMATERIALPRICE
where (iv_where_clause) and
materialno in @lt_key_range_materialdata
into @lv_count.

io_response->set_count( lv_count ).
endif.
endif.
ls_done_list = 'X'.
io_response->set_is_done( ls_done_list ).
endmethod.


* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Private Method ZCL_ZANALYTICS_MATERIA_DPC_EXT->UPDATE_MATERIAL_PRICE
* +-------------------------------------------------------------------------------------------------+
* | [--->] MATERIAL_NO TYPE STRING
* | [--->] PRICE TYPE STRING
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD update_material_price.


DATA : ls_key_fields TYPE komg,
ls_copy_records TYPE komv,
lt_copy_records TYPE TABLE OF komv,
ls_konp TYPE konp.

CLEAR : ls_key_fields, lt_copy_records.
REFRESH : lt_copy_records.

SELECT SINGLE * FROM a304 INTO @DATA(ls_mat_cond) WHERE
kappl = 'V' AND
kschl = 'PPR0' AND
vkorg = '1710' AND
vtweg = '10' AND
matnr = @material_no.

CHECK sy-subrc = 0.

SELECT SINGLE * FROM konp
INTO ls_konp
WHERE knumh = ls_mat_cond-knumh
AND loevm_ko <> 'X'.

ls_key_fields-bukrs = '1710'.
ls_key_fields-vkorg = '1710'.
ls_key_fields-vtweg = '10'.
ls_key_fields-matnr = material_no.

*lt_copy_records-knumv = ls_mat_cond-knumh.
ls_copy_records-kposn = ls_konp-kopos.
ls_copy_records-kappl = 'V'.
ls_copy_records-kschl = 'PPR0'.
ls_copy_records-kbetr = price.

ls_copy_records-waers = ls_konp-konwa.
ls_copy_records-krech = ls_konp-krech.
ls_copy_records-stfkz = ls_konp-stfkz.
ls_copy_records-kpein = ls_konp-kpein.
ls_copy_records-kmein = ls_konp-kmein.
ls_copy_records-zaehk_ind = ls_konp-zaehk_ind.
APPEND ls_copy_records TO lt_copy_records.

CALL FUNCTION 'RV_CONDITION_COPY'
EXPORTING
application = 'V'
condition_table = '304'
condition_type = 'PPR0'
enqueue = 'X'
key_fields = ls_key_fields
maintain_mode = 'B'
no_authority_check = 'X'
TABLES
copy_records = lt_copy_records
EXCEPTIONS
enqueue_on_record = 1
invalid_application = 2
invalid_condition_number = 3
invalid_condition_type = 4
no_authority_ekorg = 5
no_authority_kschl = 6
no_authority_vkorg = 7
no_selection = 8
table_not_valid = 9
no_material_for_settlement = 10
no_unit_for_period_cond = 11
no_unit_reference_magnitude = 12
invalid_condition_table = 13
OTHERS = 14.
IF sy-subrc = 0.
* MESSAGE ID SY-MSGID TYPE SY-MSGTY NUMBER SY-MSGNO
* WITH SY-MSGV1 SY-MSGV2 SY-MSGV3 SY-MSGV4.ENDIF.
CALL FUNCTION 'RV_CONDITION_SAVE'.
COMMIT WORK AND WAIT.

* CALL FUNCTION 'RV_CONDITION_RESET'.
* COMMIT WORK.

* SELECT SINGLE * FROM zmaterialprice INTO @DATA(ls_materialprice) WHERE materialno = @mat.
* ls_materialprice-conditionamount = price.
* ls_materialprice-standardprice = price.
*
* UPDATE zmaterialprice FROM ls_materialprice .

ENDIF.

ENDMETHOD.
ENDCLASS.

 

Test the OData service URL
Once the OData service has been implemented you can call it from a URL. To retrieve this URL you can type the transaction code = /n/IWFND/V4_ADMIN

This transaction code is to open OData v4 services. Here you can see list of OData v4 services available in the system and also test them


Then you need to select  “ZANALYTICS_MATERIAL” entry and click on Service Test -> Gateway Client


 

Call an S/4Hana OData action from an Application


In Analytics Designer in SAP Analytics Cloud, you can define OData Services based on an existing live connection in your system which was created using CORS (Cross-origin resource sharing) connectivity

Before calling the OData action “MaterialPriceUpdate“ (that we have defined in our S/4Hana system) from an application the main steps are:

  • Define the CORS configuration to your system according to the help page.

  • Additionally: Configure support for “if-match” as allowed header in your system.

  • Define a direct connection to this system.

  • Open an application and add an OData service technical component


Regarding the CORS configuration for S/4Hana system you can consult the following blog

Define a direct connection to this system
In our SAC system we declared a S/4Hana connectivity where we specified different parameters:

  • The name of the connection = S4OPHE4

  • The connection type = Direct

  • Host: cihe4.internal.sde.cloud.sap

  • HTTPS Port: 50081

  • Client: 400


And we used the SAML Single Sign On authentication method.


 

Add and configure the OData Services technical component
To be able to call the OData service we needed to create an OData Service technical component in the application. This component has few properties:

Name: name of your technical component
Connection: connection to the S/4Hana system
End-Point URL: OData Service URL generated in S/4Hana

In an analytic application in the Layout Outline in the Scripting Section you can create a new OData Service by clicking on plus button.


In the side panel you can change the name, select the System from the list of available systems whose connections are already created in SAC under Connections, and specify the End-Point URL of the OData Service manually.

In the Metadata section you see all the Entity sets and Actions expose by the OData service. We find well the action “MaterialPriceUpdate“ that we defined previously in our S/4Hana system


 

Call the OData action
In the demo when we click on the “Update” button we use the following code to call the “MaterialPriceUpdate” action.

 The executeAction method accepts 2 arguments:

  • Name of an OData Action (in our case MaterialPriceUpdate)

  • Parameters (in our case price and Material)


When this method is called then the price is udpated on the S/4Hana system side.

We can check that the price has been correctly changed in the S/4Hana system. In our system we select the "Prices" application.


Then we execute PPR0 condition type


But before we need to change the condition display. This is a very important step. By default, this set material price app is in edit mode, which will lock the price changes. So you neet to set it to display mode first.


Finally we can see that the price has changed to 1104,99 USD.


 

In Conclusion


In this blog post we learned how to create an OData Service in S/4Hana system and how to call it from an analytic application. Because we used OData v4 we were able to udpate values in S/4Hana system direclty from an analytical application. This workfow is called a close loop scenario and it’s very powerful as a business user can explore, analyze and do simulation of its data and take concrete actions from a simple application.

If you want to know more about all the OData services capabilities in Analytics Designer look at the developer handbook.
5 Comments