Skip to Content
Technical Articles
Author's profile photo Uladzislau Pralat

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:

 

Assigned Tags

      10 Comments
      You must be Logged on to comment or reply to a post.
      Author's profile photo Filip Nespor
      Filip Nespor

      Good job and thanks for sharing this with other customers/consultants.

      Author's profile photo Youri Catinat
      Youri Catinat

      Thanks for this post.

      Could you explain why this step is necessary ?

      This code is used in the method "itemset_get_entityset" OK, but I would like to understand why it is necessary.

      Author's profile photo Uladzislau Pralat
      Uladzislau Pralat
      Blog Post Author

      Youri,

      because mo_model and mt_headers are not publicly accessible.

      Regards, Uladzislau

      Author's profile photo Smriti Gupta
      Smriti Gupta

      Hi,

       

      Many thanks for this useful blog. It was super helpful in extending the app with custom fields. I also added the field as a filter. However the filter is not working when the results are grouped on some field. Also sorting is not working.

      If we extend the field in the interface view I_ARLINEITEM, can the sorting and grouping work then?

      Could you please let me know how can I make the custom filter work with grouping? ie. I group the records on Customer number and then want to select the value in custom filter

      Many thanks

      Smriti

      Author's profile photo Uladzislau Pralat
      Uladzislau Pralat
      Blog Post Author

      Hi Smriti,

      glad that you found my blog helpful. Now you know as much as I know on this subject. I overlooked grouping and sorting with custom filters in my testing. With some minor fixes, I am sure, you will make it work. Please share with the community once you find a solution.

      Regards, Uladzislau

       

      Author's profile photo Sushant Nath
      Sushant Nath

      Hello Uladzislau,

       

      Hope you are doing good!

      Thanks for sharing, a valuable blog/post, to enhance the standard app "Manage customer line items".

      We have extended/enhanced the app, with a custom filter named as "Payment block" and it working fine as well.

      However, now we need suggestions/assisted help, on the "Payment block" field like "Customer" field.

      PFA a screenshot, supporting the same.

       

       

      May I know, if you have any suggestions, for this?

       

      Best Regards,

      Sushant

      Author's profile photo Uladzislau Pralat
      Uladzislau Pralat
      Blog Post Author

      Hi Sushant,

      my blog covers both how to add custom field and enable it for filtering.

      Regards, Uladzislau

       

      Author's profile photo Sushant Nath
      Sushant Nath

      Hello Uladzislau,

      Thanks for looking into this issue.

      We have achieved, adding and filtering of custom custom fields, using this blog.

      However, now we  need, suggestions/assisted/match code functionality, on the custom fields "Release Group", as per screenshot, similar to "company code".

      May i know, if this is covered in this blog?

      If not, may I know, if you have any inputs on this?

      Best Regards,

      Sushant

       

      Author's profile photo Uladzislau Pralat
      Uladzislau Pralat
      Blog Post Author

      Hi Sushant,

      it is not covered in the blog, but it is done for number of fields in standard implementation. All you need to do is see how it is done and do the same.

      Regards, Uladzislau

       

      Author's profile photo Mayank Jaiswal
      Mayank Jaiswal

      Hi Pralat,

      Thanks for the blog, your blogs are always real time scenarios based, so I like to go about it whether I have the background of it or not.

      Just a small request, can you please throw some light on DPC classes and which all scenarios requires to use this approach in Embedded Analytics. Do you have some useful blogs or link pointing out the same.

      Best Regards,

      Mayank