Supply Chain Management Blogs by SAP
Expand your SAP SCM knowledge and stay informed about supply chain management technology and solutions with blog posts by SAP. Follow and stay connected.
cancel
Showing results for 
Search instead for 
Did you mean: 
GRABLERE
Product and Topic Expert
Product and Topic Expert
9,567
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.


Overview Example S/4HANA OP 2020


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:)


 

Requirement: The customer wants to add an additional status to the WH Load/Unloading Stop Level. This Status should be nicely visible represented by a traffic light on the corresponding lines of the TOR overview. The user should be able to change the status on either a single or multiple Sub-Stop instance at once, as he wants to e.g. trigger PPF activities based on the status.


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.

DDIC Enhancement

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. 😉


TOR~STOP Structure including Customer Specific Status Field


BO Enhancements

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.


Creation of BO Enhancement Object


In the result tree you will find a new a new entry for your enhancement Object.


Enhancement Object in Business Object Browser


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

Structure enhancements

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.


UI Structure Enhancements


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:


Custom UI Feeder Class


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.

 
2 Comments