Enhancing the Freight Order Overview – Part 1
Especially with the new development around Advance Shipping and Receiving released in S/4 HANA 2020 OP the Overview, as the central place for execution processing, became one of the most important screens in our Freight Order. But as we learned from Ben Parker “with great power comes great responsibility” and in our case this is the responsibility to serve and handle also customer specific requirements/enhancements.
As the overview is a completely transient UI, which is built up over different entities and not based on FBI with a direct mapping of views to BO nodes, the enhancement process is rather specific and requires some knowledge around the underlying UI framework we introduced called TBI(Transient BOBF Integration) Sidenote: at least it is since 9.4, for the long term TM supporters you might remember the old BO transient node TOR~Overview.
In this two part enhancement guide series, I will describe the steps necessary to enhance the overview by a custom column filled by a field from a persisted field as well as a custom action to change this field as per the example requirement described below. The first part will focuse on required DDIC, BO and ABAP enhancements while the second part will mainly include the corresponding enhancements in the FPM layer.
Remark: Several Steps could have been performed in a different way, e.g. using customize component on the FPM side. Feel free to comment if you want to share any best practices from your projects on how you would alternatively tackle such a requirement.
Hope you enjoy:)
An additional NFR is to not do any enhancements or modifications to any ABAP coding.
Data Structure & BO Enhancements
In this section we will take part of the structural changes on the backend object, that will enable us to store and manipulate our customer specific status.
This is a well known step for most of you and mainly included for the sake of completeness.
To properly enhance the structure of an existing standard BO node, navigate via the BO to the structure assigned to the BO node. In our case, for the TOR~STOP node, this is structure /SCMTMS/S_TOR_STOP. Identify the named include EEW, this is the include intended for customer appends, which is part of every major TOR BO node. Navigate to the underlying structure of the include (/SCMTMS/INCL_EEW_TOR_STOP). Click on Append Structure and create a new structure as per the naming convention of your project. Include a field in your new structure with a data element required for your scenario. In our scenario the field MY_STATUS, will be a “boolean” and only have 2 values X and blank. Save and activate your changes.
Make sure that the activation was completely successful. Specifically check that DB table /SCMTMS/D_TORSTP is active and includes your new field. Speaking from experience, missing authorization and ending up with a partially active DB table, is a save way to project visibility. 😉
Open Transaction BOBX.
On the Header Toolbar Select Business Object Processing Framework -> Create -> Business Object Enhancement
Enter the name of your new enhancement business object according to the naming conventions of your project. For Super Business Object enter /SCMTMS/TOR.
In the result tree you will find a new a new entry for your enhancement Object.
Navigate to your enhancement object. The Object contains the structure of its original Super BO /SCMTMS/TOR.
Click on the main entry of your BO. Enter a Constant Interface name following your naming convention. On the toolbar click: Extra -> Generate Constant Interface
(You can also use the propose repository name functionality before to name your constant interface).
In the node tree of your Enhancement Object. Navigate to Node STOP -> Actions. Right click on folder actions and select create Action.
Create Action with the name SET_MY_STATUS_X.
Set the action cardinality to Multiple Node Instances, as we want to be able to set the status for multiple stop instances via a single action call.
Repeat the steps above and create an additional action and call it SET_MY_STATUS_BLANK
In a separate window open transaction SE24 to implement the class to implement the logic for the actions which change the value of the status.
For this simple case we will use the same implementation class to handle both actions, setting the status to X or blank.
In the newly created class navigate to tab Interfaces and add the BOBF action interface /BOBF/IF_FRW_ACTION.
Navigate to the tab Methods and implement interface method /BOBF/IF_FRW_ACTION~EXECUTE. In the simple example logic below, we set the MY_STATUS field on the corresponding stop to either blank or X, depending on which action is currently called, which can be identified via the is_ctx-act_key, so we can use a single action implementation for both BO actions.
METHOD /bobf/if_frw_action~execute. DATA: lt_d_tor_stop TYPE /scmtms/t_tor_stop_k, lt_mod TYPE /bobf/t_frw_modification. "read the stop data io_read->retrieve( EXPORTING iv_node = /scmtms/if_tor_c=>sc_node-stop it_key = it_key IMPORTING et_data = lt_d_tor_stop ). "now set the indicator based on which action we are calling LOOP AT lt_d_tor_stop ASSIGNING FIELD-SYMBOL(<s_d_tor_stop>). <s_d_tor_stop>-my_status = SWITCH #( is_ctx-act_key WHEN zom_if_tor_c=>sc_action-stop-set_my_status_blank THEN abap_false WHEN zom_if_tor_c=>sc_action-stop-set_my_status_x THEN abap_true ). "return a success message if desired ENDLOOP. /scmtms/cl_mod_helper=>mod_update_multi( EXPORTING it_data = lt_d_tor_stop iv_node = /scmtms/if_tor_c=>sc_node-stop iv_bo_key = /scmtms/if_tor_c=>sc_bo_key CHANGING ct_mod = lt_mod ). io_modify->do_modify( lt_mod ). ENDMETHOD.
Assign the implementation class to the previously created BO Actions and save your changes.
Implementation Class to BO Action assignment
UI Feeder Class Developments
The standard overview uses structure /SCMTMS/S_UI_TBI_OVW, which defines the field catalogue(columns) for the Overview tree UIBB. In Order to enhance our own Overview UI, we must create an enhanced version.
Via SE11, create a new structure that contains the standard structure as an include and your custom fields in addition. In our case we add the status itself + an icon representation of the status. The suffix TRA is added as the Feeder Class buffer will use this suffix later for the include, we will append.
The buffer is constructed of multiple named includes that are mapped again to the corresponding backend instances, which are included in the corresponding UI instance + a transient include for all kind of fields which are not mapped to a certain instance.
For example take a location entry from the Overview, which contains information from multiple instances: the root, the departure stop it is representing, the arrival stop it is representing.
In our case we will keep it a bit simple and will just add the fields to the transient include, as this gives us the most flexibility to adapt to requirements, where the status should be available on other intance e.g. for items. This is for example also the case for the handling execution status, were you have a single column but it is filled from different backend nodes depending if we are on an Item or Stop entry.
Therefore we will append the same structure we added to our Stop node to structure /SCMTMS/S_TBI_BUF_UI_OVW_TRANS.
Custom Feeder Class
Now the actual interessting part can start as we get to the enhancement of the new TBI based feeder class. The standard Overview UI uses feeder class /SCMTMS/CL_UI_TBI_TOR_OVW_BO and our own feeder class will inherit from the standard. Therefore create the class as described below:
Navigate to tab Methods and create a redefinition for:
- INIT: Set the new UI Structure to include additional fields/columns:
method /SCMTMS/IF_UI_TBI_CORE~INIT. super->/scmtms/if_ui_tbi_core~init( EXPORTING it_parameter = it_parameter iv_lean = iv_lean io_data_interface = io_data_interface ). mv_ui_structure_name = 'ZOM_S_UI_TBI_OVW'. get_substructure_names( EXPORTING iv_structure_name = mv_ui_structure_name CHANGING cv_ui_root_structure = mv_ui_structure_root cv_ui_item_structure = mv_ui_structure_item cv_ui_sdep_structure = mv_ui_structure_sdep cv_ui_sarr_structure = mv_ui_structure_sarr cv_ui_stage_structure = mv_ui_structure_stage cv_ui_trans_structure = mv_ui_structure_trans ). *---- Set hierarchy type for BO overview mv_consumer = /scmtms/if_tor_const=>sc_overview_consumer-overview. mv_current_hier_type = /scmtms/cl_tor_hierarchy_cust=>c_overview_hierarchy. mv_hswitch = sc_hswitch-sameconsumer. endmethod.
- ADAPT_DEFINITION: Rename the new columns and actions, via mapping to OTR Texts. You can also do the naming completely in the UI Configuration layer. But in case you are using multiple configurations with the same feeder class it is recommended to implement this in the backend.
METHOD /scmtms/if_ui_tbi_fpm~adapt_definition. "call superclass for standard logic super->/scmtms/if_ui_tbi_fpm~adapt_definition( EXPORTING is_uibb_key = is_uibb_key CHANGING ct_field_descr_form = ct_field_descr_form ct_sort_reference = ct_sort_reference ct_field_descr_list = ct_field_descr_list ct_field_descr_tree = ct_field_descr_tree ct_action_definition = ct_action_definition ct_dnd_definition = ct_dnd_definition ct_row_actions = ct_row_actions cs_options_form = cs_options_form cs_options_list = cs_options_list cs_options_tree = cs_options_tree ). "add new columns descriptions, tx is with reference to an OTR Text, chose or create a fitting OTR text as required in your project DATA(lt_field) = VALUE /scmtms/cl_ui_tbi_util=>tt_fdescr( ( f = 'MY_STATUS_ICONTRA' tx = '/SCMTMS/UI_CMN/OC_STATUS' ) ). /scmtms/cl_ui_tbi_util=>chg_fcat_t( EXPORTING it_text = lt_field CHANGING ct_descr = ct_field_descr_tree ). "Action defintion + description DATA(lt_action) = VALUE /scmtms/cl_ui_tbi_util=>tt_adescr( ( id = 'SET_MY_STATUS_X' tx = '/SCMTMS/UI_CMN/SET_STATUS' ) ( id = 'SET_MY_STATUS_BLANK' tx = '/SCMTMS/UI_CMN/RESET_STATUS' ) ). /scmtms/cl_ui_tbi_util=>chg_action( EXPORTING it_action_descr = lt_action CHANGING ct_action_definition = ct_action_definition ). ENDMETHOD.
- _INIT_INTERNAL: Map the UI actions to a specific node. As the overview is not based on a single note but a combination of stops, items etc. the feeder needs to know to which BO instance the action call on a UI must be forwarded to. In our case we forward the UI action to the Stop action we previously added to our custom BO and the keys are pulled from the corresponding includes ARR and DEP. This is quite nice as it does not require us to build a mapping from UI instance to backend instance by hand. So even if we would introduce a Status on item level that is supposed to be shown in the same column, we could simply map for the element_cat=Item to another backend action and let the framework determine the keys from the Item group in the buffer structure.
METHOD _init_internal. CLEAR: er_data, ev_convclass, es_object, et_action, et_elemcat, et_fieldsource, et_notifnode. super->_init_internal( EXPORTING it_parameter = it_parameter IMPORTING er_data = er_data ev_convclass = ev_convclass es_object = es_object et_action = et_action et_notifnode = et_notifnode et_elemcat = et_elemcat et_fieldsource = et_fieldsource ). "map the UI action to the correponding action on BO node stop INSERT VALUE #( ui_action = 'SET_MY_STATUS_X' group = sc_ui_groups-stop_arrival element_cat = sc_tbi_element_cat-stop_asr bo_key = /scmtms/if_tor_c=>sc_bo_key bo_action = zom_if_tor_c=>sc_action-stop-set_my_status_x ) INTO TABLE et_action. INSERT VALUE #( ui_action = 'SET_MY_STATUS_X' group = sc_ui_groups-stop_departure element_cat = sc_tbi_element_cat-stop_asr bo_key = /scmtms/if_tor_c=>sc_bo_key bo_action = zom_if_tor_c=>sc_action-stop-set_my_status_x ) INTO TABLE et_action. INSERT VALUE #( ui_action = 'SET_MY_STATUS_BLANK' group = sc_ui_groups-stop_arrival element_cat = sc_tbi_element_cat-stop_asr bo_key = /scmtms/if_tor_c=>sc_bo_key bo_action = zom_if_tor_c=>sc_action-stop-set_my_status_blank ) INTO TABLE et_action. INSERT VALUE #( ui_action = 'SET_MY_STATUS_BLANK' group = sc_ui_groups-stop_departure element_cat = sc_tbi_element_cat-stop_asr bo_key = /scmtms/if_tor_c=>sc_bo_key bo_action = zom_if_tor_c=>sc_action-stop-set_my_status_blank ) INTO TABLE et_action. ENDMETHOD.
- SET_ACT_PROP: Here we determine for all UI instances if the actions should be available or not. As we want to support the logic for the ASR stop only, we filter the instances based on the element_cat = ASR.
METHOD set_act_prop. super->set_act_prop( EXPORTING it_key = it_key it_parameter = it_parameter CHANGING ct_act_prop = ct_act_prop cv_changed = cv_changed ). LOOP AT it_key ASSIGNING FIELD-SYMBOL(<s_key>). READ TABLE mt_data WITH KEY key COMPONENTS key = <s_key>-key ASSIGNING FIELD-SYMBOL(<s_data>). CHECK sy-subrc = 0. "Action should only be enabled for ASR sub-stops IF <s_data>-element_cat = sc_tbi_element_cat-stop_asr. DATA(lv_enable_act) = abap_true. ELSE. lv_enable_act = abap_false. ENDIF. "set properties for action to set status to 'X' READ TABLE ct_act_prop ASSIGNING FIELD-SYMBOL(<s_act_prop>) WITH TABLE KEY key_id COMPONENTS key = <s_data>-key id = 'SET_MY_STATUS_X'. IF sy-subrc = 0. <s_act_prop>-is_enable = lv_enable_act. ELSE. INSERT VALUE #( key = <s_data>-key id = 'SET_MY_STATUS_X' is_visible = lv_enable_act is_enable = lv_enable_act ) INTO TABLE ct_act_prop. ENDIF. READ TABLE ct_act_prop ASSIGNING <s_act_prop> WITH TABLE KEY key_id COMPONENTS key = <s_data>-key id = 'SET_MY_STATUS_BLANK'. IF sy-subrc = 0. <s_act_prop>-is_enable = lv_enable_act. ELSE. INSERT VALUE #( key = <s_data>-key id = 'SET_MY_STATUS_BLANK' is_visible = lv_enable_act is_enable = lv_enable_act ) INTO TABLE ct_act_prop. ENDIF. ENDLOOP. ENDMETHOD.
- BUILD_DATA_TABLE: Here we actually fill the data into the UI layer as per requirement. The build_data_table method is called inside of interface method get_data, and the nice part is that due to the structure of the method we have already all important data already buffered in corresponding tables.
METHOD build_data_table. DATA: lt_data TYPE ty_t_data. CLEAR et_data_table. super->build_data_table( EXPORTING it_key = it_key it_tor_root = it_tor_root it_tor_item_all = it_tor_item_all it_tor_item_relevant = it_tor_item_relevant it_stop = it_stop it_stop_succ = it_stop_succ it_item_all = it_item_all it_stage_overview_d = it_stage_overview_d it_exec = it_exec iv_execute_conversion = 'X' iv_consumer = iv_consumer it_erp_documents = it_erp_documents io_hierarchy_cust = io_hierarchy_cust it_tor_driver = it_tor_driver it_req_attr = it_req_attr it_ref_item = it_ref_item it_indirect_ref_item = it_indirect_ref_item it_ref_stop = it_ref_stop it_indirect_ref_stop = it_indirect_ref_stop it_resource_root = it_resource_root it_capa_root = it_capa_root it_kl_stop_capa_root = it_kl_stop_capa_root it_summary_report = it_summary_report it_charges = it_charges it_packaging_overview = it_packaging_overview it_stop_succ_util = it_stop_succ_util it_loc_group_result = it_loc_group_result it_stop_itm_loadref = it_stop_itm_loadref it_load_dir_profile_overview = it_load_dir_profile_overview it_att_equi_profile_overview = it_att_equi_profile_overview it_loc_loaddir_atteqprof = it_loc_loaddir_atteqprof it_loading_stop = it_loading_stop it_purchase_doc = it_purchase_doc IMPORTING et_data_table = et_data_table ). lt_data = et_data_table. LOOP AT it_stop ASSIGNING FIELD-SYMBOL(<s_d_stop>) WHERE asr_indicator = /scmtms/if_asr_c=>sc_tor_stop_asr_ind-asr_relevant. READ TABLE lt_data ASSIGNING FIELD-SYMBOL(<s_data>) WITH TABLE KEY key COMPONENTS key = <s_d_stop>-key. IF sy-subrc = 0. <s_data>-my_statustra = <s_d_stop>-my_status. IF <s_d_stop>-my_status = abap_false. <s_data>-my_status_icontra = /scmtms/if_ui_cmn_c=>sc_icons-red_led. ELSE. <s_data>-my_status_icontra = /scmtms/if_ui_cmn_c=>sc_icons-green_led. ENDIF. ENDIF. ENDLOOP. et_data_table = lt_data. ENDMETHOD.
So much for Part 1. With these steps performed, we are ready to consume our new feeder in the UI and create the new UI artifacts, to fullfil our customer requirement, which we will do in the second part.
Find the conversation on LinkedIn: