Skip to Content
Technical Articles

CRM – Identifying user changes on a decision table in BRF+

At CRM system, when you need to maintain the content of a decision table at Business Rule Framework plus, could be a requirement, during the activation action, to identify the changes done by the user.

For this, we need to compare the changes that the user has done on the table content at the decision table settings screen with the content stored on the database.

After press the “Activate” button, an object of the exit class registered on the application is instantiated. On this instance, we can check the differences, identifying the rows that have been modified by the user.

 

After implementation, when you change the table content of a decision table linked to the application where the exit class have been registered, the method ACTIVATION_VETO wil be triggered when the “Activate” button is pressed, on the decision table settings screen.

On this method, you can read the content of the screen and the content that is already saved on the database, making checks accordingly with your requirements.

 

Implementation

Create a class for the interface IF_FDT_APPLICATION_SETTINGS on T-CODE SE24.

Implement the method ACTIVATION_VETO.

Ps.: the method is only triggered whether the CONSTRUCTOR is implemented as static and attribute GV_ACTIVATION_VETO setted to ABAP_TRUE.

 

Take note of the application ID:

 

On the application settings we should link the created class to the application on BRFPLUS:

 

At T-CODE SE11, create:

Structure Z_S_DECISION_TABLE_RANGE

 

Table Type Z_TT_DECISION_TABLE_RANGE

 

Structure Z_S_DECISION_TABLE_DATA

 

Source code:

METHOD if_fdt_application_settings~activation_veto.

    TYPES: BEGIN OF ty_decision_table_string,
             decision_table_id   TYPE <Decision table ID>,
             ts_tange_low TYPE string,
             r_value      TYPE string,
           END OF   ty_decision_table_string.

    DATA: lt_decision_table_str     TYPE TABLE OF ty_decision_table_string,
          ls_decision_table_str     TYPE ty_decision_table_string,
          lt_decision_table_str_old TYPE TABLE OF ty_decision_table_string,
          ls_decision_table_str_old TYPE ty_decision_table_string,
          lref_decision_table       TYPE REF TO cl_fdt_decision_table,
          lref_factory              TYPE REF TO if_fdt_factory,
          lr_data                   TYPE REF TO data,
          lt_decision_table         TYPE TABLE OF z_s_decision_table_data,
          ls_decision_table         LIKE LINE OF lt_decision_table,
          lt_decision_table_old     TYPE TABLE OF z_s_decision_table_data,
          ls_decision_table_old     LIKE LINE OF lt_decision_table,
          lv_timestamp              TYPE timestamp,
          lv_col_no                 TYPE int4,
          lt_decision_table_id      TYPE TABLE OF <Decision table ID>.

    FIELD-SYMBOLS: <fs_decision_table_id> TYPE any.

    " GUID of application 
    CONSTANTS:  co_app_id_ctt_product_rules  TYPE if_fdt_types=>id  VALUE '<Application ID>'.

*********************************************************************************************************************************

    GET TIME STAMP FIELD lv_timestamp.

    " Generic factory instance
    lref_factory = cl_fdt_factory=>if_fdt_factory~get_instance( co_app_id_ctt_product_rules ).

    " Get the expression instance of the decision table with the object ID
    lref_decision_table ?= lref_factory->get_expression( iv_id = iv_id ).

    " Get the decision table data based on above expression GUID
    " it means that it will read the data including the user's changes before the activation
    lref_decision_table->if_fdt_decision_table~get_table_data( IMPORTING ets_data = DATA(ets_data) ).

    lt_decision_table = ets_data.
    SORT lt_decision_table BY col_no row_no.
    CHECK lt_decision_table IS NOT INITIAL.

    " Transform the decision table on a string table to be possible to compare the content with the data that is stored on the database
    DATA(lv_end_of_loop) = abap_false.

    WHILE lv_end_of_loop = abap_false.
      lv_col_no = lv_col_no + 1.

      LOOP AT lt_decision_table INTO ls_decision_table WHERE col_no = lv_col_no.
        DATA(ls_range) = COND #( WHEN line_exists( ls_decision_table-ts_range[ 1 ] )
                                 THEN ls_decision_table-ts_range[ 1 ] ).

        lr_data = ls_range-r_low_value.
        ASSIGN lr_data->* TO FIELD-SYMBOL(<fs_ts_range_low>).

        IF <fs_ts_range_low> IS ASSIGNED.
          " The ROW_NO 1 stores the decision table ID
          IF ls_decision_table-row_no = 1.
            ls_decision_table_str-decision_table_id = <fs_ts_range_low>.
          ELSE.
            ls_decision_table_str-ts_tange_low = ls_decision_table_str-ts_tange_low && ls_range-option && <fs_ts_range_low>.
          ENDIF.
          UNASSIGN <fs_ts_range_low>.
        ENDIF.

        lr_data = ls_decision_table-r_value.
        ASSIGN lr_data->* TO FIELD-SYMBOL(<fs_r_value>).

        IF <fs_r_value> IS ASSIGNED.
          ls_decision_table_str-r_value = ls_decision_table_str-r_value && <fs_r_value>.
          UNASSIGN <fs_r_value>.
        ENDIF.
      ENDLOOP.

      IF sy-subrc NE 0.
        lv_end_of_loop = abap_true.
        CLEAR lv_col_no.
      ELSE.
        APPEND ls_decision_table_str TO lt_decision_table_str.
        CLEAR  ls_decision_table_str.
      ENDIF.
    ENDWHILE.

    " Get the decision table data based on above expression GUID and the current timestamp
    " it means that it will read the data saved on the database 
    lref_decision_table->if_fdt_decision_table~get_table_data( EXPORTING iv_timestamp = lv_timestamp
                                                               IMPORTING ets_data     = DATA(ets_data_old) ).

    lt_decision_table_old = ets_data_old.
    SORT lt_decision_table_old BY col_no row_no.
    CHECK lt_decision_table_old IS NOT INITIAL.

    " Transform the decision table on a string table to be possible to compare the content with the data with the user's changes before the activation
    lv_end_of_loop = abap_false.

    WHILE lv_end_of_loop = abap_false.
      lv_col_no = lv_col_no + 1.

      LOOP AT lt_decision_table_old INTO ls_decision_table_old WHERE col_no = lv_col_no.
        DATA(ls_range_old) = COND #( WHEN line_exists( ls_decision_table_old-ts_range[ 1 ] )
                                     THEN ls_decision_table_old-ts_range[ 1 ] ).

        lr_data = ls_range_old-r_low_value.
        ASSIGN lr_data->* TO <fs_ts_range_low>.

        IF <fs_ts_range_low> IS ASSIGNED.
          " The ROW_NO 1 stores the decision table ID
          IF ls_decision_table_old-row_no = 1.
            ls_decision_table_str_old-decision_table_id = <fs_ts_range_low>.
          ELSE.
            ls_decision_table_str_old-ts_tange_low = ls_decision_table_str_old-ts_tange_low && ls_range_old-option && <fs_ts_range_low>.
          ENDIF.
          UNASSIGN <fs_ts_range_low>.
        ENDIF.

        lr_data = ls_decision_table_old-r_value.
        ASSIGN lr_data->* TO <fs_r_value>.

        IF <fs_r_value> IS ASSIGNED.
          ls_decision_table_str_old-r_value = ls_decision_table_str_old-r_value && <fs_r_value>.
          UNASSIGN <fs_r_value>.
        ENDIF.
      ENDLOOP.

      IF sy-subrc NE 0.
        lv_end_of_loop = abap_true.
        CLEAR lv_col_no.
      ELSE.
        APPEND ls_decision_table_str_old TO lt_decision_table_str_old.
        CLEAR  ls_decision_table_str_old.
      ENDIF.
    ENDWHILE.

*********************************************************************************************************************************

    " Check differences between the internal tables
    " and removes the content that have not been changed
    LOOP AT lt_decision_table_str_old INTO ls_decision_table_str_old.
      DATA(lv_tabix) = sy-tabix.

      READ TABLE lt_decision_table_str WITH KEY decision_table_id = ls_decision_table_str_old-decision_table_id
                                                r_value           = ls_decision_table_str_old-r_value
                                                ts_tange_low      = ls_decision_table_str_old-ts_tange_low 
                                           INTO ls_decision_table_str.

      IF sy-subrc = 0.
        DELETE lt_decision_table_str     INDEX sy-tabix.
        DELETE lt_decision_table_str_old INDEX lv_tabix.
      ENDIF.
    ENDLOOP.

    APPEND LINES OF lt_decision_table_str_old TO lt_decision_table_str.
    SORT lt_decision_table_str BY decision_table_id ASCENDING.
    DELETE ADJACENT DUPLICATES FROM lt_decision_table_str COMPARING decision_table_id.

    " Fill table with the ID of the rows that have been modified
    LOOP AT lt_decision_table_str INTO ls_decision_table_str.
      APPEND ls_decision_table_str-decision_table_id TO lt_decision_table_id.
    ENDLOOP.
  ENDMETHOD.
2 Comments
You must be Logged on to comment or reply to a post.
  • Thank you for your post. If I understand well, you have implemented the method CLASS_CONSTRUCTOR with the single line GV_ACTIVATION_VETO = abap_true, so that SAP calls the method ACTIVATION_VETO at activation time.

    Something you don’t mention is that if the implementation chooses to stop the activation process, it should return the parameters EV_VETO = abap_true along with one or more messages in parameter ET_MESSAGE.

    Sandra