Enterprise Resource Planning Blogs by Members
Gain new perspectives and knowledge about enterprise resource planning in blog posts from community members. Share your own comments and ERP insights today!
cancel
Showing results for 
Search instead for 
Did you mean: 
uladzislau_pralat
Contributor
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:

 
33 Comments
Labels in this area