Background
This article will explore how an action(trigger) which is associated with an action profile can be controlled programmatically. This paper will explore how an action can be established as repeatable and how a workflow container can be utilised to control the scheduling and processing of an action.
In a previous article we explored how the RAP framework can be utilised to manage complex transactional processing. In that article a one order transaction type of ZCHG( charge ) was created and in this article we will show how, post processing for an action can be performed dynamically when the start condition is meet.
The Use Case
There is a action, z_chgaction, that can be invoked dynamically within the code and when the start condition of the action is meet the action is to be executed immediately.
The diagram below(diagram 1) depicts the landscape of the repeatable action Z_CHGACTION.
- An action profile of ZCHGPPF has been defined
- The Action Z_CHGACTION has been defined with a method medium
- The Container for the medium is based on attributes from a defined structure(ZS_CHGACTION_MEDIUM)
- The Action Z_CHGACTION has been defined and configured with a start condition
- The Start condition is based on attributes from a defined structure(ZS_CHGACTION_COND)
Key Transactions and Tables
- CRMC_ACTION_DEF (Define action profile and actions)
- CRMC_ACTION_CONF (Configure start and schedule conditions)
- PPFTTRIGG (table of actions)
There is a configuration setting, in the action definition, that will determine if multiple entries are created in table PPFTTRIGG, details shown below in diagram 2.
Software Design
From a design perspective there are a number of steps that need to be performed:
- retrieve the action for the specific order object
- retrieve the rule container for the action
- retrieve the medium container for the action
- populate the rule container elements from a structure
- populate the medium container from a structure
- save the rule container
- save the medium container
- save the rule
- save the medium
- initiate the action
From a design perspective we can see that there are some elements that are generic , namely, the retrieval of the action for an order and the population of the container.
The class diagram below(diagram3) depicts a design that facilitates the common elements. The diagram shows a utility class, zcl_action_util, with facilitates the common actions.
ACTION UTILITY CLASS
get_action_by_order_guid
The code snippet below (diagram 4) shows how the utility class makes use of the action dao to retrieve the action context and also how to retrieve an action.
METHOD zif_action_util~get_action_by_order_guid.
DATA(lr_context) = me->mr_action_dao->create_context( iv_guid ).
rr_action = me->mr_action_dao->get_action( ir_action_context = lr_context
iv_action_name = iv_action_name ).
ENDMETHOD.
Diagram 4
populate_container_elements
The snippet in diagram 5 shows how the elements of a container can be populated based on a supplied structure of type any.
method ZIF_ACTION_UTIL~POPULATE_CONTAINER_ELEMENTS.
DATA lr_abapstructure TYPE REF TO cl_abap_structdescr. "CL_ABAP_TYPEDESCR.
DATA lt_components TYPE abap_component_tab.
"retrieve the components from the structure
CHECK cr_container IS BOUND.
lr_abapstructure ?= cl_abap_structdescr=>describe_by_data( is_structure ). .
lt_components = lr_abapstructure->get_components( ).
IF lines( lt_components ) EQ 0.
RETURN.
ENDIF.
FIELD-SYMBOLS TYPE any .
LOOP AT lt_components ASSIGNING FIELD-SYMBOL(<fs_component>).
DATA(lv_name) = <fs_component>-name.
ASSIGN COMPONENT lv_name OF STRUCTURE is_structure TO <data>.
IF <data> IS NOT INITIAL.
cr_container->set_value( EXPORTING
element_name = CONV #( lv_name )
data = <data> ).
ENDIF.
ENDLOOP.
endmethod.
Diagram 5
get_action_container_rule
In this method we return the container for the start condition rule which is associated with the action.
METHOD zif_action_util~get_action_container_rule.
CHECK ir_action IS BOUND.
DATA(lr_rule_man_if) = ir_action->getm_cond_man( ).
DATA(lv_startcond) = ir_action->get_startcond( ).
mr_rule_man ?= lr_rule_man_if.
mr_rule_man->set_guid1( lv_startcond ).
mr_rule_man->if_condition_container_ppf~get_container( IMPORTING ri_container = rr_container ).
ENDMETHOD.
Diagram 6
get_action_container_medium
In this method we return the container that is associated with the medium. In diagram 1 we can see that the medium that is associated with the action is a badi definition based on definition exec_methodcall_ppf.
METHOD zif_action_util~get_action_container_medium.
DATA(lr_medium_if) = ir_action->get_medium( ).
mr_medium ?= lr_medium_if.
mr_medium->if_medium_container_ppf~get_container( IMPORTING ei_container = rr_container ).
ENDMETHOD.
Diagram 7
set_action_container_rule
If the contents of the container associated with the start condition is modified then we need to set the container for that rule.
METHOD zif_action_util~set_action_container_rule.
mr_rule_man->if_condition_container_ppf~set_container( EXPORTING ii_container = ir_container
ip_condtype = '02'
ip_custcont = '' ).
ENDMETHOD.
Diagram 8
set_action_container_medium
If the contents of the container associated with the method call are modified then we need to set the container for the medium.
METHOD zif_action_util~set_action_container_medium.
CHECK mr_medium IS BOUND.
mr_medium->if_medium_container_ppf~set_container( ii_container = ir_container ).
ENDMETHOD.
Diagram 9
save_action_rule
If details of the rule associated with the start condition have been modified then we need to save the container ref.
METHOD zif_action_util~save_action_rule.
DATA(lr_rule_ref) = mr_rule_man->get_container_ref( ).
lr_rule_ref->save( ).
ENDMETHOD.
Diagram 10
save_action_medium
If details of the medium associated with the action have been modified then we need to save the medium.
METHOD zif_action_util~set_action_container_medium.
CHECK mr_medium IS BOUND.
mr_medium->if_medium_container_ppf~set_container( ii_container = ir_container ).
ENDMETHOD.
Diagram 11
ACTION UTILITY INTERFACE DEFINITION
Details of the utility interface definition are given below in diagram 6.
interface ZIF_ACTION_UTIL
public .
methods GET_ACTION_BY_ORDER_GUID
importing
!IV_GUID type CRMT_OBJECT_GUID
!IV_ACTION_NAME type PPFDTT
returning
value(RR_ACTION) type ref to CL_TRIGGER_PPF .
methods POPULATE_CONTAINER_ELEMENTS
importing
!IS_STRUCTURE type ANY
changing
!CR_CONTAINER type ref to IF_SWJ_PPF_CONTAINER .
methods GET_ACTION_CONTAINER_RULE
importing
!IR_ACTION type ref to CL_TRIGGER_PPF
returning
value(RR_CONTAINER) type ref to IF_SWJ_PPF_CONTAINER .
methods SET_ACTION_CONTAINER_RULE
importing
value(IR_CONTAINER) type ref to IF_SWJ_PPF_CONTAINER .
methods GET_ACTION_CONTAINER_MEDIUM
importing
!IR_ACTION type ref to CL_TRIGGER_PPF
returning
value(RR_CONTAINER) type ref to IF_SWJ_PPF_CONTAINER .
methods SET_ACTION_CONTAINER_MEDIUM
importing
value(IR_CONTAINER) type ref to IF_SWJ_PPF_CONTAINER .
methods SAVE_ACTION_MEDIUM .
methods SAVE_ACTION_RULE .
endinterface.
Diagram 12
Action DAO
There are three methods in the Action DAO:
- create_context
- get_actions
- get_action
The create_context method retrieves the action context using the standard FM ‘CRM_ACTION_CONTEXT_CREATE’.
The second method get_actions retrieves all the actions using factory cl_service_factory_ppf.
The third method get_action retrieves an action of specific type.
In the get_action method the code looks for an specific action that is scheduled and waiting for execution. If no such action is found then we repeat a pre-existing action.
method ZIF_ACTION_DAO~CREATE_CONTEXT.
CALL FUNCTION 'CRM_ACTION_CONTEXT_CREATE'
EXPORTING
iv_header_guid = iv_guid
iv_object_guid = iv_guid
IMPORTING
ev_context = rr_action_context
EXCEPTIONS
no_actionprofile_for_proc_type = 1
no_actionprofile_for_item_type = 2
order_read_failed = 3
OTHERS = 4.
endmethod.
method ZIF_ACTION_DAO~GET_ACTIONS.
data(lr_action) = cl_service_factory_ppf=>get_actions_if( ).
lr_action->get_actions( exporting
io_context = ir_action_context
ip_active = abap_true
ip_inactive = abap_true
ip_other_processing = abap_true
importing
et_actions = lt_action
).
endmethod.
METHOD zif_action_dao~get_action.
DATA lr_action TYPE REF TO cl_trigger_ppf.
DATA lr_action_done TYPE REF TO cl_trigger_ppf.
DATA(lt_actions) = me->zif_action_dao~get_actions( ir_action_context ).
LOOP AT lt_actions ASSIGNING FIELD-SYMBOL(<fs_action>).
lr_action ?= <fs_action>.
DATA(lv_action_type) = lr_action->get_ttype( ).
IF lv_action_type = iv_action_name.
IF lr_action->get_status( ) EQ 0.
rr_action = lr_action.
EXIT.
ELSEIF lr_action_done IS INITIAL.
lr_action_done = lr_action.
ENDIF.
ENDIF.
ENDLOOP.
IF rr_action IS NOT BOUND AND lr_action_done IS BOUND.
DATA(lr_new_action) = lr_action_done->repeat( ).
rr_action = lr_new_action.
ENDIF.
ENDMETHOD.
Diagram 13
EXECUTE THE ACTION
To date we have seen how it is possible to retrieve, set and save containers for a rule and medium. The next stage is to see how to draw these elements together to orchestrate the execution of the action. The diagram below(diagram 14) shows the new micro service zcl_action_execute and its dependency on the utility class.
The basic functionality of the micro service will be to orchestrate the following:
- retrieve the action for the one order
- retrieve containers for start condition rule and for the medium
- populate containers based on supplied structures
- set the containers
- save the containers, rule and medium
- execute the action
Constructor
The constructor as seen in diagram 15 shows the dependency on the utility class and the containers for the medium and the start condition rule are also defined.
METHOD CONSTRUCTOR.
me->mv_guid = iv_order_guid.
me->mr_object_pool = cl_object_pool=>get_instance( ).
mr_action_util = NEW zcl_action_util( ).
mr_container_medium = NEW cl_swj_ppf_container( ).
mr_container_rule = NEW cl_swj_ppf_container( ).
me->mr_object_pool->save_guids( EXPORTING ip_guid = me->mv_guid ).
ENDMETHOD.
Diagram 15
Create Method
In the create process this where the orchestration occurs for the retrieval and setting of the containers. The input structures are the rule container and medium container are defined as type any.
method ZIF_ACTION_EXECUTE~CREATE.
DATA lt_message TYPE bapiret2_tab.
me->mr_action = me->mr_action_util->get_action_by_order_guid( iv_guid = me->mv_guid
iv_action_name = iv_action ).
CHECK mr_action IS BOUND.
lt_message = me->create_container_rule( is_container_rule ).
READ TABLE lt_message WITH KEY type = 'E' TRANSPORTING NO FIELDS.
IF sy-subrc EQ 0.
APPEND LINES OF lt_message TO rt_message.
RETURN.
ENDIF.
lt_message = me->create_container_medium( is_container_medium ).
READ TABLE lt_message WITH KEY type = 'E' TRANSPORTING NO FIELDS.
IF sy-subrc EQ 0.
APPEND LINES OF lt_message TO rt_message.
RETURN.
ENDIF.
endmethod.
Diagram 16
Create container Rule
METHOD create_container_rule.
CHECK mr_action IS BOUND.
me->mr_container_rule = me->mr_action_util->get_action_container_rule( me->mr_action ).
me->mr_action_util->populate_container_elements( EXPORTING is_structure = is_container
CHANGING cr_container = mr_container_rule ).
me->mr_action_util->set_action_container_rule( me->mr_container_rule ).
ENDMETHOD.
Diagram 17
Create Container for Medium
METHOD CREATE_CONTAINER_MEDIUM.
CHECK mr_action IS BOUND.
me->mr_action->set_status( '1' ).
me->mr_action->set_commit_required( abap_true ).
me->mr_container_medium = me->mr_action_util->get_action_container_medium( mr_action ).
me->mr_action_util->populate_container_elements( EXPORTING is_structure = is_container
CHANGING cr_container = mr_container_medium ).
ENDMETHOD.
Diagram 18
SAVE Method
In the save method the details of the containers, rules and medium are saved using methods from the utility class and then the action is executed.
method ZIF_ACTION_EXECUTE~SAVE.
CHECK mr_action IS BOUND.
mr_action->set_processed_manually( abap_true ).
mr_container_rule->set_tansport( tr_disabled = abap_true ).
mr_container_rule->save( ).
me->mr_action_util->save_action_rule( ).
mr_container_medium->set_tansport( tr_disabled = abap_true ).
mr_container_medium->save( ).
me->mr_action_util->save_action_medium( ).
mr_action->execute( ).
endmethod.
Diagram 19
TEST the Action execution
In the ADT debugger ensure that a break point exists in the method call and then retrieve the elements from the medium container to verify that the details have been passed through correctly.
The snippet below, diagram 20, shows details being populated for the rule and medium containers and then action Z_CHGACTION being invoked for execution.
METHOD test.
DATA ls_container_rule TYPE zs_chgaction_cond.
DATA ls_container_medium TYPE zs_chgaction_medium.
DATA lr_action_execute TYPE REF TO zif_action_execute.
DATA lt_message TYPE bapiret2_tab.
ls_container_rule = VALUE #(
startcond = 'ABC'
).
ls_container_medium = VALUE #(
chargetype = 'CHG'
chargesubtype = '123'
chargeamount = '100'
).
lr_action_execute = NEW zcl_action_execute( iv_order_guid ).
lt_message = lr_action_execute->create( iv_action = 'Z_CHGACTION'
is_container_rule = ls_container_rule
is_container_medium = ls_container_medium ).
lr_action_execute->save( ).
ENDMETHOD.
Diagram 20
The method call on the action just retrieves details from the container and in this we can check that the process of dynamically changing the container details have been passed through successfully.
CLASS ZCL_IM_CHGACTION IMPLEMENTATION.
METHOD if_ex_exec_methodcall_ppf~execute.
DATA lv_charge_type TYPE ze_charge_type.
DATA lv_charge_sub_type TYPE ze_charge_sub_type.
data lv_charge_amount type betrw_kk.
ii_container->get_value( EXPORTING element_name = 'CHARGETYPE'
IMPORTING data = lv_charge_type ).
ii_container->get_value( EXPORTING element_name = 'CHARGESUBTYPE'
IMPORTING data = lv_charge_sub_type ).
ii_container->get_value( EXPORTING element_name = 'CHARGEAMOUNT'
IMPORTING data = lv_charge_amount ).
rp_status = 1.
"lets do a sub-process now
ENDMETHOD.
ENDCLASS.
Diagram 21