Skip to Content
Technical Articles

How to deal with unit of measures when calling OData services from S/4 HANA in SAP Cloud Platform, ABAP environment

When calling an OData service in SAP S/4 HANA Cloud in SAP Cloud Platform ABAP Environment you might run into the problem that the service consumption model that is generated does not support the conversion of unit of measures.

Another problem or challenge might be that you want a conversion exit to become active when data for a certain field of the consumed service is read.

Let’s have a look at the following OData service Sales Order (A2X) that allows external applications to integrate with sales order processing in SAP S/4HANA Cloud.

The service specification can be found on the SAP API Business Hub

https://api.sap.com/api/API_SALES_ORDER_SRV/overview 

If you want to consume that service in SAP Cloud Platform, ABAP environment using the ABAP RESTful Programming Model (RAP) you have start with the creation of a service consumption model as described here.

From the numerous entity sets we only select the following four

  • A_SalesOrder
  • A_SalesOrderItem
  • A_SalesOrderText
  • A_SalesOrderItemText

As a result the framework will generate several business objects for us, namely

  • service consumption model ZSC_AFI_SALESORDER_A2X
  • service definition ZSC_AFI_SALESORDER_A2X

and four abstract entities

  • ZA_SALESORDERITEM
  • ZA_SALESORDERITEMTEXT
  • ZA_SALESORDER
  • ZA_SALESORDERTEXT

 

How are conversion exits being called when consuming an OData service using the OData client proxy?

The abstract entities that have been generated are using data elements or built-in types on which the EDM data types of the remote OData service could be mapped. The underlying SAP Gateway framework now uses the conversion exits that are referenced by the domains on which these data elements are based.

When looking at the source code of the abstract entities we find for example that the data element of the field RequestQuantityUnit that contains the unit of measure of the field RequestedQuantity is a basic type abap.unit(3) that does not offer any conversion exit.

In the previous releases of SAP Cloud Platform, ABAP environment this was a problem because the abstract entities that are generated alongside with the service consumption model could not be edited.

This has changed as of release 2005.

/********** GENERATED on 07/03/2020 at 15:28:30 by CB0000000258**************/
 @OData.entitySet.name: 'A_SalesOrderItem' 
 @OData.entityType.name: 'A_SalesOrderItemType' 
 define root abstract entity ZA_SALESORDERITEM { 
 key SalesOrder : abap.char( 10 ) ; 
 key SalesOrderItem : abap.numc( 6 ) ; 
 @OData.property.valueControl: 'HigherLevelItem_vc' 
 HigherLevelItem : abap.numc( 6 ) ; 
 HigherLevelItem_vc : rap_cp_odata_value_control ; 
 ...
 @OData.property.valueControl: 'RequestedQuantity_vc' 
 @Semantics.quantity.unitOfMeasure: 'RequestedQuantityUnit' 
 RequestedQuantity : abap.dec( 15, 3 ) ;
 RequestedQuantity_vc : rap_cp_odata_value_control ; 
 @OData.property.valueControl: 'RequestedQuantityUnit_vc' 
 @Semantics.unitOfMeasure: true 
 RequestedQuantityUnit : abap.unit( 3 ) ; 
 RequestedQuantityUnit_vc : rap_cp_odata_value_control ; 

 

Right after the import of our EDMX file we can use an OData proxy to call the remote OData service provided that the communication setup is in place.

As a result we can then create an ABAP class zsalesorderread that calls our service (code see at the end of this blog).

The source code is quite simple.

  1. First a http destination is created based on the configuration of the destination service in SAP Cloud Platform
  2. Then a http client is created that is needed to create the OData proxy.
  3. We then use the OData proxy to read the two key fields of a single sales order item
  4. Using these two key fields we try to read the complete data

When running this class we will find that a conversion error occurs when trying to read the field RequestedQuantity

Data is read, but the value of the unit of measure is not converted into the correct value and an exception is raised. As a fallback since no conversion exit could be used or could be found the unchanged data is returned for the field RequestedQuantity.

read key values of one order item
read single salesorder item
Conversion error for property 'REQUESTEDQUANTITY' in entity type 'ET_6B254D1A964FDB2C7FEB776613A'
Trading Good 0011,PD,Regular Proc.
Field  
1.0  
PC

This is because the built in type abap.unit(3) does not offer any conversion exit.

What now proves to be useful is the possibility to edit the abstract entities that have been generated so that one can change the code such that either released data elements are used that have an appropriate conversion exit or that custom data elements are used that provide appropriate conversion exits.

To solve the above mentioned issue we can change the source code of the generated abstract entity such that instead of abap.unit(3) the white-listed (released) data element MSEHI is used. MSEHI is based on the domain MEINS that provides the appropriate conversion exit for unit of measures.

 @OData.property.valueControl: 'RequestedQuantityUnit_vc' 
 @Semantics.unitOfMeasure: true 
 RequestedQuantityUnit : msehi ; 
 //RequestedQuantityUnit : abap.unit( 3 ) ; 
 RequestedQuantityUnit_vc : rap_cp_odata_value_control ; 

The class can call the OData service without any errors.

read key values of one order item
read single salesorder item
Trading Good 0011,PD,Regular Proc.
Field 
1.0 
ST

 

CLASS zsalesorderread DEFINITION
  PUBLIC
  FINAL
  CREATE PUBLIC .

  PUBLIC SECTION.
    INTERFACES if_oo_adt_classrun.
  PROTECTED SECTION.
  PRIVATE SECTION.
ENDCLASS.



CLASS zsalesorderread IMPLEMENTATION.
  METHOD if_oo_adt_classrun~main.

    DATA salesorderitem_key TYPE za_salesorderitem.
    DATA salesorderitem TYPE za_salesorderitem.
    DATA salesorderitems TYPE TABLE OF za_salesorderitem.

    DATA   selected_properties  TYPE /iwbep/if_cp_runtime_types=>ty_t_property_path.
    APPEND 'SALESORDER' TO  selected_properties .
    APPEND 'SALESORDERITEM' TO  selected_properties .

    TRY.
        DATA(http_destination) =  cl_http_destination_provider=>create_by_cloud_destination(
                 i_name                  = 'S4CE_HTTP_BASIC'
                 i_service_instance_name = 'SAP_COM_0276_SALESORDER_A2X'
                 i_authn_mode =  if_a4c_cp_service=>service_specific ).

        DATA(http_client) = cl_web_http_client_manager=>create_by_http_destination( http_destination ).

        DATA(odata_client_proxy) = cl_web_odata_client_factory=>create_v2_remote_proxy(
          EXPORTING
            iv_service_definition_name = 'ZSC_AFI_SALESORDER_A2X'
            io_http_client             = http_client
            iv_relative_service_root   = '/sap/opu/odata/sap/API_SALES_ORDER_SRV' ).

        out->write( 'read key values of one order item' ).
        DATA(readlist_request) = odata_client_proxy->create_resource_for_entity_set( 'A_SALESORDERITEM' )->create_request_for_read( ).
        readlist_request->set_top( 1 )->set_skip( 0 ).
        readlist_request->set_select_properties( it_select_property = selected_properties ).
        DATA(readlist_response) = readlist_request->execute( ).
        readlist_response->get_business_data( IMPORTING et_business_data = salesorderitems ).
        salesorderitem_key = VALUE #( salesorder     = salesorderitems[ 1 ]-salesorder
                             salesorderitem = salesorderitems[ 1 ]-salesorderitem ).

        out->write( 'read single salesorder item' ).
        DATA(read_request) = odata_client_proxy->create_resource_for_entity_set( 'A_SALESORDERITEM' )->navigate_with_key( salesorderitem_key )->create_request_for_read( ).
        DATA(read_response) = read_request->execute( ).
        read_response->get_business_data( IMPORTING es_business_data = salesorderitem ).

      CATCH cx_web_http_client_error INTO DATA(lx_response).
        out->write( lx_response->get_text(  ) ).
      CATCH cx_http_dest_provider_error INTO DATA(lx_destexception).
        out->write( lx_destexception->get_text(  ) ).
      CATCH /iwbep/cx_cp_remote INTO DATA(lx_remote).
        out->write( lx_remote->get_text(  ) ).
      CATCH /iwbep/cx_gateway INTO DATA(lx_gateway).
        out->write( lx_gateway->get_text(  ) ).
    ENDTRY.

    out->write( salesorderitem-salesorderitemtext ).
    out->write( salesorderitem-requestedquantity ).
    out->write( salesorderitem-requestedquantityunit ).


  ENDMETHOD.

ENDCLASS.
1 Comment
You must be Logged on to comment or reply to a post.
  • Thanks Andre. had faced this issue last year, but was able to overcome by changing unit type to char.

    Good that now we have a white-listed DE to use. Your blog posts and insights are very useful.

    Gaurav