Execute Actions or FMs in locked instances mode in SAP TM

1.0 Introduction

In SAP Transportation Management, we get many scenarios to trigger an action from UI or from a BOPF program, to carry out a function. It could be creating a document/changing some status of a document based on certain checks. When we execute these actions from BOBF framework within the same LUW ( Logical Unit of Work ) we are pretty safe in terms of lock handling / BO ( business object) modification and reporting the same in UI, so that user can carry out the same action again by removing the lock.

Imagine the action is being called from a different source ( ECC / CRM etc ) and they do not have the ability to trigger the function again, as it is one of many steps that they perform. So now the responsibility lies within SAP TM to somehow register that Trigger and the data packet that came along with it and then processing it subsequently by a re-trial mechanism. In order to handle this kind of situation we create separate tables to store that data and then re-process it.

But in SAP TM we have a very standard process of doing it. We will discuss how to do it. As of now standard SAP TM environment supports two kinds of updates. ( BO Action and Function Module ( Normal/RFC)).

2.0 Technical Design

The following design is based on a requirement in a project where an update is coming from ECC system via an RFC call into SAP TM box. Within the RFC call we are doing a BO action call on “Customer Freight Invoice Request (CFIR)” to update status on an invoice document.

The idea is to call the action in locked instance mode and if anything goes wrong with the update (BO being locked/update failed due to some data errors etc) we will register the lock, so that we will use standard SAP TM tool to re-process the lock.

A) Design involving an Action Call on a BO

Step 1) Create a Trigger ID ( Custom Trigger ) in a standard table /SCMTMS/I_TRIG

SAP has not given any maintenance view for this table yet. So we will create an entry in this table by SE16. Make sure you create an transport entry for this one directly in a TR.

Create an entry in this table with a custom trigger name. Since in here we will process a BO action we will use the Trigger Class as /SCMTMS/CL_TRIG_CONTROL_ACTION

Step 2) Check if the BO Instance is locked or not?

DATA: lr_srv_mngr TYPE REF TO /bobf/if_tra_service_manager,
lr_message TYPE REF TO /BOBF/IF_FRW_MESSAGE,
ls_key TYPE /bobf/s_frw_key,
lt_locked TYPE /BOBF/T_FRW_KEY.
 
lr_srv_mngr = 
/bobf/cl_tra_serv_mgr_factory=>get_service_manager(scmtms/if_custfreightinvreq_c=>sc_bo_key).
 
* Retrieve all objects in EDIT Exclusive node
lr_srv_mngr->retrieve(
EXPORTING
iv_node_key = /scmtms/if_custfreightinvreq_c=>sc_node-root "Pass the Node Key on which the 
"action is being executed from constant interface. In this case it is ROOT
 
it_key = it_key "Pass the DB Key of the Node on which the action is being executed
iv_fill_data = abap_false
iv_edit_mode = /bobf/if_conf_c=>sc_edit_exclusive "Edit Mode
IMPORTING
eo_message = lr_message ).
 
* Identify locked entries
IF lr_message IS BOUND.
 
CALL METHOD /scmtms/cl_common_helper=>analyze_messages
EXPORTING
io_message = lr_message
IMPORTING
et_locked_key = lt_locked .
 
IF lt_locked IS NOT INITIAL. "If locked keys are found
 
* Loop across the internal table and find out the DB keys which are 
locked by some other process and can't be locked here. Collect such 
locked and unlocked keys and the process those records further 
which are unlocked.
LOOP AT it_key ASSIGNING ls_key.
 
READ TABLE lt_locked TRANSPORTING NO FIELDS 
WITH TABLE KEY key = ls_key-key.
IF sy-subrc = 0.
APPEND ls_key TO TABLE lt_locked. "Locked Keys
ELSE.
APPEND ls_key TO TABLE lt_unlocked. "Unlocked Keys
ENDIF.
 
ENDLOOP.
 
ELSE.
APPEND LINES OF it_key TO lt_unlocked. "Unlocked Keys
ENDIF. " IF lt_locked IS NOT INITIAL.
ENDIF. " IF lr_message IS BOUND.

Step 3) Execute the action directly for keys which are unlocked.

DATA: lref_action_param TYPE REF TO zcs_s_a_fwsd_upd_stat. "Action Parameter
 
IF lt_unlocked IS NOT INITIAL.
lr_service_manager->do_action(
EXPORTING
iv_act_key = zif_cs_custfreightinvreq_c=>sc_action-root- zcs_upd_cfir_status
" Pass the Action Key which you want to execute
it_key = lt_unlocked
is_parameters = lref_action_param "Action Parametrs
IMPORTING
eo_change = lr_change
eo_message = lr_message
et_failed_key = lt_failed_key ).
 
ENDIF.

Step 4) Register the Trigger for the framework to process it later

DATA: ls_action_ctx TYPE /bobf/s_frw_ctx_act.
CONSTANTS: lc_trigger_id TYPE TRIGGER_ID VALUE 'ZCS_UPD_CFIR_STATUS'.
 
IF lt_locked IS NOT INITIAL.
* Schedule action for those instances which are locked by some other 
" process so that they can be processed later on 
IF lr_srv_mngr IS NOT INITIAL.
ls_action_ctx-bo_key = /scmtms/if_custfreightinvreq_c=>sc_bo_key. "BO KEY
ls_action_ctx-root_node_key = /scmtms/if_custfreightinvreq_c=>sc_node-root. "Root key
ls_action_ctx-node_key = /scmtms/if_custfreightinvreq_c=>sc_node- root
ls_action_ctx-act_key = zif_cs_custfreightinvreq_c=>sc_action- root- zcs_upd_cfir_status. 
" Pass the Action Key which you want to execute
 
/scmtms/cl_trig_helper=>set_trigger_for_action(
EXPORTING
io_message = lr_message "Message Variable
it_forced_key = lt_locked "Locked Keys
is_action_context = ls_action_ctx "Action Context
iv_trigger_id = lc_trigger_id "Custom Trigger ID
is_parameter = lref_action_param "Action Parameters
IMPORTING
eo_message = lr_message
et_failed_key = lt_failed_key ).
 
ENDIF.
ENDIF.

Important Notes :

  1. When a BO instance is locked and a Trigger is registered; the same should be seen in a standard table /SCMTMS/D_TRIGHD and the data in the action parameters are registered in table /SCMTMS/D_TRIGCX.
  2. Once the Trigger is registered in these tables; we can run the standard program /SCMTMS/PROCESS_TRIGGER_BGD. By un checking the check box and putting the respective Trigger ID, we can just process the specific trigger.

(If the selection screen of program /SCMTMS/PROCESS_TRIGGER_BGD does not look same then; please make sure you apply few SAP notes in your system; as sap has corrected few bugs in this program by these notes)

3. Once the process is complete and update is carried out, the tables entries are deleted. In case the LOCK is still there in the BO instance, the specific entry will not be processed and retained in the tables for later processing by the same standard program.

4. During action execution if we encounter any error during BO modification and we want to retain the Trigger in the table for later processing; then we just need to fill the FAILED key in the action.

B) Design involving a Function Call on a BO

Step 1) Create a Trigger ID ( Custom Trigger ) in a standard table /SCMTMS/I_TRIG

Create an entry in this table with a custom trigger name. Since in here we will process BO update by a function call; we will use the Trigger Class as /SCMTMS/CL_TRIG_CONTROL_FM.

Step 2) Create a function module with the following signature

You should have at least IT_KEY & IS_PARAMETER as input to the FM as standard frame work searched the FM signature for dynamic data filling with the same name. You can have additional parameters but the minimum is these two. Please do NOT even alter the parameters name. You can have IT_KEY as dummy and it may be even initial if you do not need it but it has to be there in the FM signature.

Step 3) Check if the BO Instance is locked or not ?

DATA: lr_srv_mngr TYPE REF TO /bobf/if_tra_service_manager,
lr_message TYPE REF TO /BOBF/IF_FRW_MESSAGE,
ls_key TYPE /bobf/s_frw_key,
lt_locked TYPE /BOBF/T_FRW_KEY.
 
lr_srv_mngr = 
/bobf/cl_tra_serv_mgr_factory=>get_service_manager(scmtms/if_custfreightinvreq_c=>sc_bo_key).
 
* Retrieve all objects in EDIT Exclusive node
lr_srv_mngr->retrieve(
EXPORTING
iv_node_key = /scmtms/if_custfreightinvreq_c=>sc_node-root "Pass the Node Key on which the action 
"is being executed from constant interface. In this case it is ROOT
 
it_key = it_key "Pass the DB Key of the Node on which the action is being executed
iv_fill_data = abap_false
iv_edit_mode = /bobf/if_conf_c=>sc_edit_exclusive "Edit Mode
IMPORTING
eo_message = lr_message ).
 
* Identify locked entries
IF lr_message IS BOUND.
 
CALL METHOD /scmtms/cl_common_helper=>analyze_messages
EXPORTING
io_message = lr_message
IMPORTING
et_locked_key = lt_locked .
 
IF lt_locked IS NOT INITIAL. "If locked keys are found
 
* Loop across the internal table and find out the DB keys which are 
locked by some other process and can't be locked here. Collect such 
locked and unlocked keys and the process those records further 
which are unlocked.
LOOP AT it_key ASSIGNING ls_key.
 
READ TABLE lt_locked TRANSPORTING NO FIELDS 
WITH TABLE KEY key = ls_key-key.
IF sy-subrc = 0.
APPEND ls_key TO TABLE lt_locked. "Locked Keys
ELSE.
APPEND ls_key TO TABLE lt_unlocked. "Unlocked Keys
ENDIF.
 
ENDLOOP.
 
ELSE.
APPEND LINES OF it_key TO lt_unlocked. "Unlocked Keys
ENDIF. " IF lt_locked IS NOT INITIAL.
ENDIF. " IF lr_message IS BOUND.

Step 4) Execute the Function Module directly for keys which are unlocked.

CALL FUNCTION 'Z_CS_YYYY_GET_PAY_FM'
EXPORTING
it_input = lt_input
IMPORTING
et_messages = lt_message.

Step 5) Register the Trigger for locked keys for the framework to process it later

DATA: lr_parameter TYPE REF TO data, "Data Ref
lr_trig_control TYPE REF TO /scmtms/if_trig_control, " BO Trig
FIELD-SYMBOLS : <fs_data> TYPE any.
CONSTANTS : lc_trigger_id TYPE /scmtms/trig_trigger_id 
VALUE 'Z_CS_YYYY_RECEIVE_INV',
lc_trig_fm TYPE rs38l_fnam VALUE 'Z_CS_YYYY_GET_PAY_FM_LOCK_INST'.
* Parameter
CREATE DATA lr_parameter TYPE zcs_s_yyyy_get_pay_lst_lock.
IF lr_parameter IS BOUND.
ASSIGN lr_parameter->* TO <fs_data>.
IF <fs_data> IS ASSIGNED.
ls_inv_pay_lock-is_input = lt_input_lock[].
<fs_data> = ls_inv_pay_lock.
ENDIF.
ENDIF.
 
TRY.
* Instance for Trig Control
lr_trig_control = /scmtms/cl_trig_factory=>get_instance( iv_trigger_id = lc_trigger_id ). 
"Pass the Trigger ID
 
CREATE DATA lr_s_trigger_context_fm.
* Passing the Data to the context
lr_s_trigger_context_fm->function_module = lc_trig_fm. " FM Name
lr_s_trigger_context_fm->r_s_parameters = lr_parameter. " Parameters
lr_s_trigger_context_fm->bo_key = /scmtms/if_custfreightinvreq_c=>sc_bo_key.
lr_s_trigger_context_fm->node_key = /scmtms/if_custfreightinvreq_c=>sc_node-root.
"Setting the Trigger
lr_trig_control->set_trigger(
EXPORTING
it_trigger_key = lt_locked_key
ir_context = lr_s_trigger_context_fm ).
COMMIT WORK.
CATCH /scmtms/cx_trig ##no_handler.
* No specific error handling is required.
ENDTRY.

Important Notes :

  1. Unlike Action processing by Trigger if we encounter any error during BO modification and we want to retain the Trigger in the table for later processing; then we just need to handle it explicitly as there is no FAILED key here. We have to create entries in trigger table by code in the FM itself in case such situation occurs.
  2. Personally, I would recommend to use Action Based Trigger processing as it involves less coding effort.