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
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:
Good job and thanks for sharing this with other customers/consultants.
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.
Youri,
because mo_model and mt_headers are not publicly accessible.
Regards, Uladzislau
you can create model instead of enhancement and use it in your extension
DATA lo_model TYPE REF TO /iwbep/if_mgw_odata_fw_model.
lo_model ?= lo_metadata_provider->get_service_metadata(
iv_internal_service_name = '<SERVICE NAME>
iv_internal_service_version = <VERSION NO OF ODATA SERVICE>
).
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
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
HI Uladzislau,
Many thanks for very nice blog, i have some query regarding manage customer line items app.
I have the same requirement to add a filter(Sales District) and two fields for Fiori App ID F0711.
can it be done by following steps
Please suggest.
Thanks
Himanshu
Hi,
Thank you for this amazing blog @Uladzislau Pralat.
@Smriti I am experiencing the same issue. Could you please share if you've found a solution?
Thank you.
Jenise
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
Hi Sushant,
my blog covers both how to add custom field and enable it for filtering.
Regards, Uladzislau
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
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
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
Hi Uladzislau Pralat!
Greats for share your knowledge!
I`'m facing a problem with this same Application, F0711.
I've extened the OData, but i didn't set the Overwrite Base/Extended Service checkbox because of.. what if any problem occurs?
So, that is the only difference between what I did and what you show us.
Therefore, to use my new 'Z' OData, I do extended the BSP project FIN_CUST_LITS and I've created a extension of that and I "redefined" the manifest to call the new OData server.
So far so good, the new extened App and project worked fine, but we got one problem that we couldn't found a solution.
No one date field doesn't works!
We've extended the OData to append new fields. We redefine the method ITEMSET_GET_ENTITYSET to set what we want to display on it.But, all of date fields doesn't showup any value. Any other fields is work softly.
The two scenarios are in the same environment
To ensure the same fetch data, we call the SUPER class and the method ITEMSET_GET_ENTITYSET. After retrive data we treat the value of our Zfield.
So... Anyone can help with this problem?
Solved!
My problem it was due to our Z filed was between the Standard fiels in the properties on the Entity Item. So, I just placed our Z filed in the end and done!!!
Hi Uladzislau Pralat and experts!
Me again, with this same application.
After done the extension/enhaces my application extented doesn't work the Search Help and Suggestion.
In the standard application still works, but in my extented for no reason why, it doesn't work.
The Z metadata and standard metadata, show all annotation of Search Help.
Standard anda OData Z with Search Help Annotation
Follow down, there are the comparation of OData services.
Below, I'm showing our redefine method DEFINE, that i treat the Z field to display as Date, but there are a attempt to force a new annotation to display a Search Help.
The new annoation in our Z OData
But, even with that, it didn't work!
While I'm looking for find some problem I noticed that the frameworks are doesn't get the right entity. Standard are getting the VL_SH_DEBIA, but Z it doesn't. I dont know why it is happen
Below i'm showing that Entity VL_SH_DEBIA exists and works in Z OData.
So, please, there are someone can help. I don't know what can I do or look for the fix this problem.
Hi Tulio,
each requirement is unique and requires detailed investigation and testing. In your case, if think, some related changes are not done or incorrect.
Regards Uladzislau
Thx reply, Uladzislau!
I did every think right I supposed!
My requiremente was simple. They wanted just a new date column.
So, my steps were:
I highlighted just what I did
It was all I did.
Different what you did, I didn't Overwrite Base/Extended Service, because in other OData, an ABAP did it and get some problem with the APP. So he tried to rollback and the APP doesn't work anymore.
When you overwrited do Standard OData you no needed to extented a new App, right?
Do you have any suggest what I could do or looking for?
I tried to make the same steps that SAP exposed here. But, some step we can't do, like create a SAPUI5 in Eclipse. And even with not using metadata and set the Component.js, the extended APP ignored the Z OData.
So I'm completely lost what I can do now.
Hello,
Yes there is no need to create new Odata service in /IWFND/MAINT_SERVICE on your SEGW project and no need of Fiori(FIN_CUST_LITS) extension project. Just do redefine the odata and override the base service while generating the classes and MDL. After that redefine your methods to add your custom code after calling the super method.
If you want to delete the service( *_SRV) use Tcode /IWBEP/REG_SERVICE.
Thank you.
Hi ,
I am facing the same issue , did you find any solution for Value Helps ?
Hello,
I want to add a custom field in the Manage Customer Line Items APP . Followed below steps
what are the steps to make custom field visible in UI and when i redefine the standard gateway OData service i see annotations are missing and date fields in the app are not displaying the content
Can any one please suggest what exactly am i missing ?
Hi Uladzislau Pralat,
I have to add a new description field in the F0711 app.
Can you anyone suggest ?
Hi Ashok,
thank for proper testing my solution. I think, to solve the issue some other method(s) need to be extended.
Regards, Uladzislau
Hi Uladzislau.
Are there any way to extend fields of Edit Line Item button?. I tried with Data Source Extensions FAP_VENDOR_LINE_ITEMS_SRV. Please can you help me?
Thanks in advance.
Jimmy
Hi Jimmy,
I have not had chance to work on such requirement. Please share your experience with the community.
Regards, Uladzislau