Skip to Content
Technical Articles

S/4HANA Manage Customer Line Items App – Advanced Fields Extension

SAPĀ documentation explains how to extend S/4HANA Manage Customer Line Items App with custom fields, but it is a very simplistic example. In my blog I will explain how to make advanced fields extensions:

Custom field with a total as a sum of line item values

In my example, I have 3 custom amount fields – Credit, Deduction and Invoice. These amounts are calculated on line item level and depends on Amount sign and Journal Entry Type. Totals for these fields is a sum of line item level calculations.

Custom field extension is advanced to handle multiple currencies as well.

Why it is so complex that SAP documentation example does not cover that? The way Manage Customer Line Items app work it calls FAR_CUSTOMER_LINE_ITEMS OData service two times: for line items and for totals.

During totals call only amounts and units of measure fields are read. There is no information about line item level values.The way advanced extension works it additionally reads information on line item level, calculates totals as a sum of line item values and map custom field totals into entity set.

Advanced extension manipulates request content adding properties that custom amount fields calculation depends on prior to reading data.

Custom field enabled for filtering

I my example, there 2 custom fields that are enabled for filtering: Amount Type and Aging Group. Amount Type depends amount sign and document type. Aging group depends on Days in Arrears

Why it is so complex that SAP documentation example does not cover that? Because it is very complex and risky enhancement. If it is not done right it can mess up a whole application: line item selection, standard fields totals and paging.

Custom fields filters are not handled by standard FAR_CUSTOMER_LINE_ITEMS OData service. That is why custom fields filter values are removed from request context prior to reading data. Custom fields filter values are applied in extension.

Custom fields filters impacts standard fields totals. That is why standard field totals are re-calculated along with custom fields totals and mapped into entity set.

Applying custom fields filters impacts paging since entity set records count might be reduced or even become 0. Advanced extension takes over paging handling completely:

  • Reads entire entity set (skip 0, first 1000000), applies custom fields
  • Takes entire entity set record count and overwrites incline count
  • Return only subset of entire entity set applying (applying skip and first requested by Fiori)

Manage Customer Line Items app custom field advanced extension step by step

Add append structure with custom fields to FAR_IFIARLINEITEM_EX structure

Enhance /IWBEP/CL_MGW_REQUEST class adding custom methods

Create SEGW project and redefine FAR_CUSTOMER_LINE_ITEMS OData Service

Add custom fields properties to Item entity type

Redefine DEFINE method in metadata provider extension class

Add service methods to ZCL_ZFAR_CUSTOMER_LINE_DPC_EXT class

Redefine ITEMSET_GET_ENTITYSET method in data provider extension class

METHOD itemset_get_entityset.
FIELD-SYMBOLS: <s_entityset> TYPE cl_far_customer_line_i_mpc=>ts_item.
CONSTANTS: c_top TYPE i VALUE 1000000.

* Get Information about Request Context (IO_TECH_REQUEST_CONTEXT)
  DATA(request_context) = CAST /iwbep/cl_mgw_request( io_tech_request_context ).
  DATA(model) = request_context->get_model( ).
  DATA(wt_headers) = request_context->get_request_headers( ).
  DATA(ws_request_details) = request_context->get_request_details( ).
* Delete custom filter proprties that not supported by standard data provider
  delete_filter_properties(
    EXPORTING
      it_field_name      = VALUE #( ( CONV string( 'AmountType' ) )
                                    ( CONV string( 'AgingGroup' ) ) )
    CHANGING
      cs_request_details = ws_request_details  ).
  DATA(wt_filter_select_options) = it_filter_select_options.
  DELETE wt_filter_select_options WHERE property = 'AmountType'
                                     OR property = 'AgingGroup'.
* Check if totals are read
  DATA(w_total) = is_total( is_request_details = ws_request_details
                            io_model           = model ).
* Check if custom properties totals or filters required
  IF ( w_total = abap_true ) AND
     ( line_exists( ws_request_details-select_params[ table_line = 'AmountInvoice' ] ) OR
       line_exists( ws_request_details-select_params[ table_line = 'AmountDeduction' ] ) OR
       line_exists( ws_request_details-select_params[ table_line = 'AmountCredit' ] ) OR
       line_exists( it_filter_select_options[ property = 'AmountType' ] ) OR
       line_exists( it_filter_select_options[ property = 'AgingGroup' ] ) ).
*   Add standard fields that are required to calculate custom fields totals
    add_select_properties(
      EXPORTING
        it_field_name      = VALUE #( ( CONV string( 'COMPANYCODE' ) )
                                      ( CONV string( 'ACCOUNTINGDOCUMENT' ) )
                                      ( CONV string( 'FISCALYEAR' ) )
                                      ( CONV string( 'ACCOUNTINGDOCUMENTITEM' ) )
                                      ( CONV string( 'VH_COMPANYCODECURRENCY' ) )
                                      ( CONV string( 'VH_ACCOUNTINGDOCUMENTTYPE' ) )
                                      ( CONV string( 'VH_AMOUNTINCOMPANYCODECURRENCY' ) )
                                      ( CONV string( 'NETDUEARREARSDAYS' ) ) )
      CHANGING
        cs_request_details = ws_request_details ).
*   Change paging to read all line information at document item level
    DATA(ws_paging) = VALUE /iwbep/s_mgw_paging( skip = 0
                                                 top = c_top ).
    ws_request_details-paging = VALUE #( skip = 0
                                         top  = c_top ).
*   Create modified Request Context (IO_TECH_REQUEST_CONTEXT)
    DATA(tech_request_context) =
      NEW /iwbep/cl_mgw_request(
        ir_request_details = ref #( ws_request_details )
        it_headers = wt_headers
        io_model = model ).
*   Get entity set
    super->itemset_get_entityset(
      EXPORTING
        iv_entity_name = iv_entity_name
        iv_entity_set_name = iv_entity_set_name
        iv_source_name = iv_source_name
        it_filter_select_options = wt_filter_select_options
        is_paging = ws_paging
        it_key_tab = it_key_tab
        it_navigation_path = it_navigation_path
        it_order = it_order
        iv_filter_string = iv_filter_string
        iv_search_string         = iv_search_string
        io_tech_request_context = tech_request_context
      IMPORTING
        et_entityset = et_entityset
        es_response_context = es_response_context ).
    LOOP AT et_entityset ASSIGNING <s_entityset>.
*     Apply custom properties filter
      IF line_exists( it_filter_select_options[ property = 'AmountType' ] ).
        IF get_amount_type( <s_entityset> ) NOT IN it_filter_select_options[ property = 'AmountType' ]-select_options.
          DELETE et_entityset.
          CONTINUE.
        ENDIF.
      ENDIF.
      IF line_exists( it_filter_select_options[ property = 'AgingGroup' ] ).
        IF get_aging_group( <s_entityset> ) NOT IN it_filter_select_options[ property = 'AgingGroup' ]-select_options.
          DELETE et_entityset.
          CONTINUE.
        ENDIF.
      ENDIF.
*     Calculate custom properties
      <s_entityset>-amountinvoice = get_amount_invoice( <s_entityset> ).
      <s_entityset>-amountdeduction = get_amount_deduction( <s_entityset> ).
      <s_entityset>-amountcredit = get_amount_credit( <s_entityset> ).
    ENDLOOP.
*   Calculate totals
    get_total(
      EXPORTING
       is_request_details = ws_request_details
       it_entityset       = et_entityset
       io_model           = model
      IMPORTING
       et_entityset       = DATA(wt_entityset_total) ).
  ENDIF.

* Getting information about Request Context (IO_TECH_REQUEST_CONTEXT) again
  request_context = cast /iwbep/cl_mgw_request( io_tech_request_context ).
  ws_request_details = request_context->get_request_details( ).
* Delete custom properties filtering
  delete_filter_properties(
    EXPORTING
      it_field_name      = VALUE #( ( CONV string( 'AmountType' ) )
                                    ( CONV string( 'AgingGroup' ) ) )
    CHANGING
      cs_request_details = ws_request_details  ).

* Check if custom fields are requested
  IF ( line_exists( ws_request_details-select_params[ table_line = 'AmountInvoice' ] ) OR
       line_exists( ws_request_details-select_params[ table_line = 'AmountDeduction' ] ) OR
       line_exists( ws_request_details-select_params[ table_line = 'AmountCredit' ] ) OR
       line_exists( ws_request_details-select_params[ table_line = 'AmountType' ] ) OR
       line_exists( ws_request_details-select_params[ table_line = 'AgingGroup' ] ) OR
       line_exists( it_filter_select_options[ property = 'AmountType' ] ) OR
       line_exists( it_filter_select_options[ property = 'AgingGroup' ] ) )  AND
       w_total = abap_false.
*   Add standard fields that are required for custome fields calculation
    add_select_properties(
      EXPORTING
        it_field_name      = VALUE #( ( CONV string( 'COMPANYCODE' ) )
                                      ( CONV string( 'ACCOUNTINGDOCUMENT' ) )
                                      ( CONV string( 'FISCALYEAR' ) )
                                      ( CONV string( 'ACCOUNTINGDOCUMENTITEM' ) )
                                      ( CONV string( 'VH_ACCOUNTINGDOCUMENTTYPE' ) )
                                      ( CONV string( 'VH_AMOUNTINCOMPANYCODECURRENCY' ) )
                                      ( CONV string( 'NETDUEARREARSDAYS' ) ) )
      CHANGING
        cs_request_details = ws_request_details ).
  ENDIF.
  ws_paging = is_paging.
* Read all data before applying custom property filters
  IF w_total = abap_false AND ( line_exists( it_filter_select_options[ property = 'AmountType' ] ) OR
                                line_exists( it_filter_select_options[ property = 'AgingGroup' ] ) ).
    ws_paging = VALUE #( skip = 0
                         top = c_top ).
    ws_request_details-paging = VALUE #( skip = 0
                                         top  = c_top ).
  ENDIF.
* Create modified Request Context (IO_TECH_REQUEST_CONTEXT)
  tech_request_context =
    NEW /iwbep/cl_mgw_request(
      ir_request_details = REF #( ws_request_details )
      it_headers = wt_headers
      io_model = model ).
* Get entity set
  super->itemset_get_entityset(
    EXPORTING
      iv_entity_name = iv_entity_name
      iv_entity_set_name = iv_entity_set_name
      iv_source_name = iv_source_name
      it_filter_select_options = wt_filter_select_options
      is_paging = ws_paging
      it_key_tab = it_key_tab
      it_navigation_path = it_navigation_path
      it_order = it_order
      iv_filter_string = iv_filter_string
      iv_search_string         = iv_search_string
      io_tech_request_context = tech_request_context
    IMPORTING
      et_entityset = et_entityset
      es_response_context = es_response_context ).
*
  LOOP AT et_entityset ASSIGNING <s_entityset>.
*   Apply customer properties filter
    IF w_total = abap_false AND line_exists( it_filter_select_options[ property = 'AmountType' ] ).
      IF get_amount_type( <s_entityset> ) NOT IN it_filter_select_options[ property = 'AmountType' ]-select_options.
        DELETE et_entityset.
        CONTINUE.
      ENDIF.
    ENDIF.
    IF w_total = abap_false AND line_exists( it_filter_select_options[ property = 'AgingGroup' ] ).
      IF get_aging_group( <s_entityset> ) NOT IN it_filter_select_options[ property = 'AgingGroup' ]-select_options.
        DELETE et_entityset.
        CONTINUE.
      ENDIF.
    ENDIF.
    IF line_exists( ws_request_details-select_params[ table_line = 'AmountType' ] ).
      <s_entityset>-amounttype = get_amount_type( <s_entityset> ).
    ENDIF.
    IF line_exists( ws_request_details-select_params[ table_line = 'AgingGroup' ] ).
      <s_entityset>-aginggroup = get_aging_group( <s_entityset> ).
    ENDIF.
    IF ( w_total = abap_false ) AND
       ( line_exists( ws_request_details-select_params[ table_line = 'AmountInvoice' ] ) OR
         line_exists( ws_request_details-select_params[ table_line = 'AmountDeduction' ] ) OR
         line_exists( ws_request_details-select_params[ table_line = 'AmountCredit' ] ) ).
       <s_entityset>-amountinvoice   = get_amount_invoice( <s_entityset> ).
       <s_entityset>-amountdeduction = get_amount_deduction( <s_entityset> ).
       <s_entityset>-amountcredit    = get_amount_credit( <s_entityset> ).
    ENDIF.
    IF ( w_total = abap_true ) AND
       ( line_exists( ws_request_details-select_params[ table_line = 'AmountInvoice' ] ) OR
         line_exists( ws_request_details-select_params[ table_line = 'AmountDeduction' ] ) OR
         line_exists( ws_request_details-select_params[ table_line = 'AmountCredit' ] ) OR
         line_exists( it_filter_select_options[ property = 'AmountType' ] ) OR
         line_exists( it_filter_select_options[ property = 'AgingGroup' ] ) ).
      map_total(
        EXPORTING
          is_request_details       = ws_request_details
          it_entityset             = wt_entityset_total
          it_filter_select_options = it_filter_select_options
          io_model                 = model
        CHANGING
          cs_entityset             = <s_entityset> ).
    ENDIF.
  ENDLOOP.
  IF w_total = abap_false AND ( line_exists( it_filter_select_options[ property = 'AmountType' ] ) OR
                                line_exists( it_filter_select_options[ property = 'AgingGroup' ] ) ).
*   Change inline count after applying custom properties filter
    es_response_context-inlinecount = LINES( et_entityset ).
*   Return only portion of data requested by client after applying custom properties filter
    DATA(wt_entityset) = et_entityset.
    CLEAR: et_entityset.
    APPEND LINES OF wt_entityset
      FROM ( is_paging-skip + 1 ) TO ( is_paging-skip + is_paging-top )
      TO et_entityset.
  ENDIF.

ENDMETHOD.

The same source code can be downloaded from GitHub:

 

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