ABAP Development, ABAP Connectivity, ABAP RESTful Application Programming Model

CRM Repeatable Actions and Dynamic Control

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)
Diagram 1

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.

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.

Diagram 3

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:

  1. create_context
  2. get_actions
  3. 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:

  1. retrieve the action for the one order
  2. retrieve containers for start condition rule and for the medium
  3. populate containers based on supplied structures
  4. set the containers
  5. save the containers, rule and medium
  6. execute the action
Diagram 14

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