Create ODATA service and implement CRUD methods using ABAP 7.4
In this article, I have described step-by-step procedure to create ODATA service and implement method for CRUD(Read) operations. In this article I have tried using ABAP 7.4 syntax as much as I can in the methods to write business logic. An attempt to show how we can reduce code lines using ABAP 7.4.
Go to SEGW to create a project: ZEMP_MODEL_ODATA using create project button.
Right click on Data Model node and select Import->DDIC structure to create entity type and entityset.
In the next screen enter below inputs to create Soheaderdata entitytype and SoheaderdataSet entityset
Select all fields in the next screen
Select Key field in the next screen which is SO_ID in this case.
Press Finish button to create entitytype and entityset.
Repeat above steps to create entity type and entity set for item and products using structures: BAPI_EPM_SO_ITEM and BAPI_EPM_PRODUCT_HEADER.
Select SO_ID and SO_ITEM_POS as key fields for item entity type
Select PRODUCT_ID as key for product entity type
Now we have created 3 entity types and entity sets:
You can also see service implementations created for 3 entity sets.
Click on Generate run time objects button to create run time artifacts to implement CRUD operations
Classed generated:
Now expand service Maintenance folder and register ODATA service. And Then save project.
Alternatively you can also perform registering service manually using tcode: /IWFND/MAINT_SERVICE.
You will notice signal light turns to green in below screen shot
To Test ODATA Service, click on Maintain button on same screen:
Select ODATA row and click on call browser:
Now we will create associations and navigation property between Soheaderdata and Soitemdata and Soitemdata and ProductHeaderdata.
Specify association name, principal entity type name, dependent entity type name, Cardinality and Navigation Property in below window.
Specify referential constraints between Soheaderdata and Soitemdata and specify key relation in below window.
Finish setup in next window to create association and navigation property
Now create association and navigation property between Soitemdata and ProductHeaderdata similar way.
Delete below referential Constraints because there is no key relation
Now we are done with ODATA setup. In the next steps, we will implement data provider class methods to retrieve data.
In service implementation folder, for SoheaderdataSet entity set, we will implement method for GetEntitySet(Query) to retrieve all sales orders header data.
To do this, go to Implementation class: ZCL_ZEPM_MODEL_ODATA_DPC_EXT in SE24 tcode and redefine method: SOHEADERDATASET_GET_ENTITYSET.
This method will be called for below URL to retrieve sales orders header data:
http://system:hostnumber/sap/opu/odata/sap/ZEPM_MODEL_ODATA_SRV/SoheaderdataSet
We will also implement URI parameters Filter, Skip, Top and Orderby in the above URL.
To learn more about URI parameter in odata services, refer to below link:
http://www.odata.org/documentation/odata-version-2-0/uri-conventions/
Example URL’s with URI paramters:
For Filter URI on CurrencyCode field:
http://system:hostnumber/sap/opu/odata/sap/ZEPM_MODEL_ODATA_SRV/SoheaderdataSet?$filter=CurrencyCode eq ‘EUR’
For Skip and Top URI parameter: This will skip for 2 entries and display next top 10 sales orders.
http://system:hostnumber/sap/opu/odata/sap/ZEPM_MODEL_ODATA_SRV/SoheaderdataSet?$skip=2&$top=10
For orderby URI parameter:
http://system:hostnumber/sap/opu/odata/sap/ZEPM_MODEL_ODATA_SRV/SoheaderdataSet?$orderby=GrossAmount
Copy below code in the method: SOHEADERDATASET_GET_ENTITYSET
Data declarations:
TYPES: tt_soorder TYPE STANDARD TABLE OF bapi_epm_so_header WITH EMPTY KEY.
TYPES: BEGIN OF ty_where,
cond(70),
END OF ty_where.
DATA: lt_where TYPE TABLE OF ty_where.
DATA: lt_sort_order TYPE STANDARD TABLE OF abap_sortorder.
Get Filter and orderby URI parameter values. Filter or selection URI parameters can retrieved using below method.
IF io_tech_request_context IS BOUND.
DATA(t_orderby) = io_tech_request_context->get_orderby( ).
ENDIF.
* Get filter or select option information
DATA(lo_filter) = io_tech_request_context->get_filter( ).
DATA(lt_filter_select_options) = lo_filter->get_filter_select_options( ).
DATA(lv_filter_str) = lo_filter->get_filter_string( ).
Build dynamic where condition and select sale orders header data
* Build dynamic where condition
DATA(lw_where) = CONV ty_where( lv_filter_str ).
APPEND lw_where TO lt_where.
SELECT *
FROM snwd_so
INTO TABLE @DATA(t_soheader)
WHERE (lt_where).
Handling page and skip:
* paging and skip
DATA(lw_end_row) = is_paging-top + 1.
IF is_paging-skip IS NOT INITIAL.
DELETE t_soheader FROM 1 TO is_paging-skip.
ENDIF.
IF is_paging-top IS NOT INITIAL..
DELETE t_soheader FROM lw_end_row.
ENDIF.
Perform orderby on result data set and populate et_entityset:
lt_sort_order = VALUE abap_sortorder_tab( FOR lw_orderby IN t_orderby LET lw_order = abap_true IN
( name = lw_orderby-property descending = lw_order ) ).
SORT t_soheader BY (lt_sort_order).
MOVE-CORRESPONDING t_soheader TO et_entityset.
To retrieve specific sales order data using below URL, we will implement method: SOHEADERDATASET_GET_ENTITY for GetEntity(Read).
http://system:hostnumber/sap/opu/odata/sap/ZEPM_MODEL_ODATA_SRV/SoheaderdataSet(‘500000000’)
Redefine method in same class and copy below code in the method:
DATA: w_soheader TYPE bapi_epm_so_header.
DATA: w_so_id TYPE bapi_epm_so_id.
DATA(w_salord) = it_key_tab[ name = 'SoId' ]-value.
DATA(w_vbeln) = CONV vbeln( |{ w_salord ALPHA = in }| ).
w_so_id-so_id = w_vbeln.
CALL FUNCTION 'BAPI_EPM_SO_GET_DETAIL'
EXPORTING
so_id = w_so_id
IMPORTING
headerdata = w_soheader.
MOVE-CORRESPONDING w_soheader TO er_entity.
Now we will implement methods related to SoitemdataSet:
Method: SOITEMDATASET_GET_ENTITYSET to get all sales items for sales order using URL: http://system:hostnumber/sap/opu/odata/sap/ZEPM_MODEL_ODATA_SRV/SoheaderdataSet(‘500000000’)/SalesOrderItem
Alternatively we can use URL: $expand=SalesOrderItem(Navigation property from Soheaderdata) http://system:hostnumber/sap/opu/odata/sap/ZEPM_MODEL_ODATA_SRV/SoheaderdataSet(‘500000000’)?$expand=SalesOrderItem
Redefine method SOITEMDATASET_GET_ENTITYSET in the class: ZCL_ZEPM_MODEL_ODATA_DPC_EXT
And Copy below code:
DATA: w_soheader TYPE bapi_epm_so_header.
DATA: w_so_id TYPE bapi_epm_so_id,
w_item_data TYPE STANDARD TABLE OF bapi_epm_so_item.
DATA(w_salord) = it_key_tab[ name = 'SoId' ]-value.
DATA(w_vbeln) = CONV vbeln( |{ w_salord ALPHA = IN }| ).
w_so_id-so_id = w_vbeln.
CALL FUNCTION 'BAPI_EPM_SO_GET_DETAIL'
EXPORTING
so_id = w_so_id
IMPORTING
headerdata = w_soheader
TABLES
itemdata = w_item_data.
et_entityset = CORRESPONDING zcl_zepm_model_odata_mpc=>tt_soitemdata( w_item_data ).
Below URL will calls methods SOHEADERDATASET_GET_ENTITYSET and SOITEMDATASET_GET_ENTITYSET in loop to retrieve all line items related to all sales orders
Next we will implement SOITEMDATASET_GET_ENTITY to display sales item data for specific sales order and sales item. We can use below URL:
Alternatively we can use URL with navigation property:
Redefine method SOITEMDATASET_GET_ENTITY and copy below code:
DATA: w_soheader TYPE bapi_epm_so_header.
DATA: w_so_id TYPE bapi_epm_so_id,
t_so_item TYPE STANDARD TABLE OF bapi_epm_so_item.
* If navigation property used in URL, get key values from it_navigation_path importing parameter with path: SalesOrderItem
* Else use it_key_tab from importing parameter
IF it_navigation_path[] IS NOT INITIAL.
DATA(it_key_tab_tmp) = it_navigation_path[ nav_prop = 'SalesOrderItem' ]-key_tab.
ELSE.
it_key_tab_tmp = it_key_tab.
ENDIF.
DATA(w_salord) = it_key_tab_tmp[ name = 'SoId' ]-value.
DATA(w_vbeln) = CONV vbeln( |{ w_salord ALPHA = IN }| ).
w_so_id-so_id = w_vbeln.
DATA(w_salitem) = it_key_tab_tmp[ name = 'SoItemPos' ]-value.
DATA(w_posnr) = CONV vbeln( |{ w_salitem ALPHA = IN }| ).
CALL FUNCTION 'BAPI_EPM_SO_GET_DETAIL'
EXPORTING
so_id = w_so_id
IMPORTING
headerdata = w_soheader
TABLES
itemdata = t_so_item.
er_entity = CORRESPONDING zcl_zepm_model_odata_mpc=>ts_soitemdata( t_so_item[ so_item_pos = w_posnr ] ).
Below URL also retrieves all items related to single sales order “500000000”: This will call methods SOHEADERDATASET_GET_ENTITY and SOITEMDATASET_GET_ENTITYSET .
Next we will implement: PRODUCTHEADERDAT_GET_ENTITYSET method to retrieve product details:
Below URL retrieves product details related to sales order 500000000 and Item 10:
This URL calls Methods SOITEMDATASET_GET_ENTITY and then PRODUCTHEADERDAT_GET_ENTITYSET
Alternatively we can also use URL:
This URL calls SOHEADERDATASET_GET_ENTITY and SOITEMDATASET_GET_ENTITY and then PRODUCTHEADERDAT_GET_ENTITYSET.
Below URL will retrieve all items and product info related to order: 5000000000
This URL calls SOHEADERDATASET_GET_ENTITY and SOITEMDATASET_GET_ENTITYSET and then PRODUCTHEADERDAT_GET_ENTITYSET.
Below URL will retrieve all products info:
http://system:hostnumber/sap/opu/odata/sap/ZEPM_MODEL_ODATA_SRV/ProductHeaderdataSet
This URL calls PRODUCTHEADERDAT_GET_ENTITYSET.
Now redefine method: PRODUCTHEADERDAT_GET_ENTITYSET and copy below code:
Check for entries in it_key_tab and it_navigation_path to see if we need to retrieve product details related to particular order/item or need to display all product details. If both importing tables it_key_tab and it_navigation_path are empty then retrieve all products using BAPI_EPM_PRODUCT_GET_LIST and populate records to et_entityset.
DATA: lw_product TYPE bapi_epm_product_id,
lw_header_product TYPE bapi_epm_product_header,
lw_header_so TYPE bapi_epm_so_header,
lw_orderno TYPE snwd_so_id,
lw_itemno TYPE snwd_so_item_pos,
lw_so_id TYPE bapi_epm_so_id.
DATA: lt_itemso TYPE STANDARD TABLE OF bapi_epm_so_item,
lt_products TYPE STANDARD TABLE OF bapi_epm_product_header.
IF it_key_tab[] IS INITIAL AND
it_navigation_path[] IS INITIAL.
CALL FUNCTION 'BAPI_EPM_PRODUCT_GET_LIST'
TABLES
headerdata = lt_products.
MOVE-CORRESPONDING lt_products TO et_entityset.
ELSE.
In the else condition, check if URL has navigation property: SalesOrderItem, then get sales order and item number from it_navigation_path-key_tab OR else use it_key_tab importing parameter.
* We can use below read(commented out) instead of conventional read statement, but if no navigation *property with
* SalesOrderItem is used in URL, below read will raise an exception: cx_sy_itab_line_not_found.
* With ABAP 7.5, we can use clause OPTIONAL along with below read so that it will not raise an
* exception and it would also set syst-subrc = 0. My current abap system is 7.4
* DATA(it_key_tab_tmp) = it_navigation_path[ nav_prop = 'SalesOrderItem' ]-key_tab.
READ TABLE it_navigation_path INTO DATA(w_navigation_path) WITH KEY nav_prop = 'SalesOrderItem'.
IF syst-subrc = 0.
DATA(it_key_tab_tmp) = w_navigation_path-key_tab.
ELSEIF it_key_tab IS NOT INITIAL.
it_key_tab_tmp = it_key_tab.
ENDIF.
DATA(lw_order_val) = it_key_tab_tmp[ name = 'SoId' ].
DATA(lw_item_val) = it_key_tab_tmp[ name = 'SoItemPos' ].
lw_orderno = |{ lw_order_val-value ALPHA = IN }|.
lw_itemno = |{ lw_item_val-value ALPHA = IN }|.
lw_so_id-so_id = lw_orderno.
CALL FUNCTION 'BAPI_EPM_SO_GET_DETAIL'
EXPORTING
so_id = lw_so_id
IMPORTING
headerdata = lw_header_so
TABLES
itemdata = lt_itemso.
lw_product = lt_itemso[ so_id = lw_orderno so_item_pos = lw_itemno ]-product_id.
CALL FUNCTION 'BAPI_EPM_PRODUCT_GET_DETAIL'
EXPORTING
product_id = lw_product
IMPORTING
headerdata = lw_header_product.
APPEND lw_header_product TO et_entityset.
ENDIF.
In the above code using sales order number and item number, getting corresponding product number. Use BAPI: BAPI_EPM_PRODUCT_GET_DETAIL to retrieve product details for particular product and populate et_entityset.
Next we will redefine and implement method: PRODUCTHEADERDAT_GET_ENTITY to get product details related to particular product using below URL
http://system:hostnumber/sap/opu/odata/sap/ZEPM_MODEL_ODATA_SRV/ProductHeaderdataSet(‘HT-1000’)
Copy below code:
DATA: lw_product TYPE bapi_epm_product_id,
lw_header_product TYPE bapi_epm_product_header.
lw_product-product_id = it_key_tab[ name = 'ProductId' ]-value.
CALL FUNCTION 'BAPI_EPM_PRODUCT_GET_DETAIL'
EXPORTING
product_id = lw_product
IMPORTING
headerdata = lw_header_product.
MOVE-CORRESPONDING lw_header_product TO er_entity.
Hope this article helpful.
Good Article Santhosh. Quite detailed.
Hello Santhosh,
Thanks for sharing in such great detail. I did learn quite a lot from your blog.
However, when I was trying to navigate from the SalesOrderHeader->SalesOrderItem->Product using a URI like Soheaderdataset(SoId='500000000')/SalesOrderItem(SoId='500000000',SoItemPos='10')/SalesOrderProduct I was getting run-time error.
This appears to happen due to the code written in the PRODUCTHEADERDAT_GET_ENTITY( ) where it is expected it gets a product based on service request. However, for the scenario stated above, it would never have a product and we should deduce the product from the information specified in the navigation path.
So I re-used the code that you provided in the GET_ENTITYSET() and it worked perfectly.
Best Regards,
Shakul.
Nice article Santhosh.
??
Thanks for posting it.