Selecting a specific BADI implementation by customizing

Issue:

Sometimes we want to implement customer specific logics and to activate them by customizing context sensitively. Most famous examples are checks, calculations or conversations.

In most cases we can implement a SAP standard BADI and filter the context by development or BADI filter conditions. Every time the context changes, we have to change the BADI definition (filter criteria) or the source code of the BADI implementation (if or check statement). A consultant has no chance to change the context without help of a developer. That is very bad.

Solution for Classic BADIs:

To solve this issue, we need to use a bridge in order to connect the business context with the technical BADI implementation. This bridge is normally called „check id“, „condition id“, „filter id“ etc. It is configured in an own customizing table, used in some other customizing table (where we want to assign an implementation) and used as single-value filter id in the BADI implementation of a specific BADI definition. Sometimes we have to define such a filter id first and then create a BADI implementation including assign it to the filter id. Sometimes we don’t need to create the filter id as first step because it will automatically be created resp. added by the BADI when defining it.

This solution is working fine for classic BADIs. One prominent example is BADI EXEC_METHODCALL_PPF which is used to implement processing methods for PPF actions.

Solution for new BADIs:

New BADIs still have the feature to assign a data element (with value table) to a filter criteria. But the feature to specify the filter value as „enhanceable“ got lost.

The good news is, that there exist a very easy way to implement such an „enhanceable“ solution. For this solution we don’t need a customizing table to hold the filter id. Therefore the implementation effort is very lower.

  1. Create a new BADI definition.
  2. Set the flag „Limited Filter Use“ in the „Usability“ area.
  3. Define one filter criteria. (More are not allowed)
  4. Define and implement some BADI implementations. Each BADI implementation needs to be assigned to a filter value using the equal operator (other operators are not allowed). One BADI implementation can still be registered for more than one filter values using the OR operator.
  5. Define or extend a customizing table where you want to select a specific BADI implementation. Here you need a field which is used to store the filter value. Please create an own data element (if not done yet) to use proper names like „check id“ or „condition id“.
  6. To provide a proper value help please use search help /RTF/BADI_IMP_FILTER. Here you have to mention the name of the BADI definition in field „BADI_NAME“. Now this value help will always show all active BADI implementation with their corresponding filter values of the fixed BADI definition.
  7. Please maintain the customizing table and select the requested filter values. In program code you now have to read the customizing table based on the current business context. You have to extract the filter value, to get the BADI based on the filter value and to call the BADI with providing some context data (maybe including filter value).

That’s all. Very simple. It’s working!

Restriction: Search help /RTF/BADI_IMP_FILTER is supporting only filter values up to 4 characters. If you need more, you have to implement your own search help similar to the existing one. Just copy the search help + search help exit and exchange the data element.

That’s all. Very simple. It’s working!

SAP Performance Analysis

Traces:

  • ABAP: SAT – SAP ABAP Trace
  • SQL: ST01/ST05 – SAP SQL Trace
  • JOB: ST13 (BACKGROUND_JOB_ANALYSIS) – Background Job Analysis
  • AUTH: ST01 – SAP Authority Trace

Hinweis: Nur für konkrete Sachverhalte geeignet. Explizites Aktivieren und Deaktivierung notwendig. Hat Auswirkungen auf die Performance.

Logging:

  • ABAP: /SDF/UPL_CONTROL – Usage & Procedure Logging
  • ABAP: [SCOV – SAP Coverage Analyzer]
  • SQL: SQLM – SQL Monitor
  • AUTH: STAUTHTRACE – System Trace for Authority Checks

Hinweis: Sollte immer angeschaltet sein. Auch in der Produktion. Keine signifikanten Performanceauswirkungen.

Mein Favorit:

SAT erspart einem stundenlanges Debugging. Gerade bei tief verschachtelten pseudo-objektorientierten Entwicklungen mit vielen parallelen oder nachgelagerten Prozessen. Wichtig ist zuvor eine eigene Variante anzulegen und die Agregation auszuschalten. Denn nur dann erhält man den kompletten Aufrufpfad. Um alle parallelen Prozesse zu erwischen, sollte auf Userebene (und nicht Prozess- oder Transaktionsebene) getract werden.

 

How to detect status changes?

Sometimes we want to trigger an activity (e.g. PPF action) only if a (or if no) status change is performed. Sometimes it can be complicated to detect status changes since error messages are raising system status changes or since status changes can be cancelled.

Dependent on the requirements, we can try following solutions:

  1. Real status changes: Use function module CRM_STATUS_READ_OW and check the fields ACTIVE and ACTIVE_OLD of parameter ET_STATUS_WRK. Alternatively compare the result against CRM_STATUS_READ_DB.
  2. Real status changes or Status cancel: Use function module CRM_EVENT_PASS_INFO_EXE_OW with execution time ‚080‘ and check whether parameter contains ET_EVENT_CALLS contains object „STATUS“. If you are only interested in status changes on header level, please ensure that ORDER_GUID and OBJ_GUID are equal.
  3. Real status changes(?): Use method GET_PREV_STATUS_OF_CHNG_DOC of class CL_HF_HELPER.
  4. Real PPF action triggered status change: Define a PPF action check previous user status in Schedule condition and new user status in start condition. Ensure that the sort number of this action is higher than the sort number of the status change action.

These solutions are partially also working for partner changes, appointment changes, text changes etc.

Links:

https://scn.sap.com/thread/1270181

Use Case 1:

In ChaRM there is always a consistency check performed after status change. It might be useful to perform consistency checks on every transaction save to validate transaction consistency. However, this check should not be performed if the framework is perfoming it (because of performance reasons and because the check result might be different). Therefore we need to detect status change activities which includes real status changes as well as cancelled status changes. This can be done by solution (2).

Use Case 2:

In SAP Solution Manager IT Service Management status changes are not triggered by PPF actions, therefore solution (4) is not working. The same issue we have for ChaRM if status changes are triggered by CRM_SOCM_SERVICE_REPORT. If we want to trigger an action (e.g. e-mail notification) on status change only, we have to follow solution (1). Please note, that this solution is already available in SAP standard: Note 1007346 – Scheduling of actions only after resetting a status.

*"----------------------------------------------------------------------
*"Methoden Signatur:
*"  Importing
*"     VALUE(FLT_VAL) TYPE PPFDFLTCO2
*"    IO_CONTEXT TYPE REF CL_CONTEXT_PPF
*"    IP_PROTOCOL TYPE BALLOGHNDL
*"    IP_TTYPE TYPE PPFDTT
*"    II_CONTAINER TYPE REF IF_SWJ_PPF_CONTAINER
*"  Exporting
*"    EP_RC TYPE SY-SUBRC
*"----------------------------------------------------------------------

METHOD if_ex_eval_startcond_ppf~evaluate_start_condition.

  DATA:
    lr_crm_order        TYPE REF TO cl_doc_crm_order,
    lv_header_guid      TYPE crmt_object_guid,
    lv_exec_mode        TYPE sppfdxmode,
    lv_on_database_flag TYPE abap_bool,
    lt_events           TYPE crmt_event_call_tab.

* default false
  ep_rc = 4.

*Get Execution Mode
  lv_exec_mode = cl_manager_ppf=>get_instance( )->execution_mode.

*Get Header GUID
  TRY.
      lr_crm_order ?= io_context->appl.
    CATCH cx_root.
      RETURN.
  ENDTRY.

  lv_header_guid = lr_crm_order->get_crm_obj_guid( ).

*We need a Business Transaction.
  CHECK lv_header_guid IS NOT INITIAL.

*First Save?
  CALL FUNCTION 'CRM_ORDERADM_H_ON_DATABASE_OW'
    EXPORTING
      iv_orderadm_h_guid  = lv_header_guid
    IMPORTING
      ev_on_database_flag = lv_on_database_flag.

*We only want to allow our action in dialog mode or if we change the Transaction.
  CHECK lv_exec_mode IS INITIAL OR lv_on_database_flag = abap_false.

**Get Status
*  CALL FUNCTION 'CRM_STATUS_READ_OW'
*    EXPORTING
*      iv_guid        = lv_header_guid
*      iv_only_active = abap_false
*    IMPORTING
*      et_status_wrk  = lt_status_wrk1[]
*    EXCEPTIONS
*      not_found      = 1
*      OTHERS         = 2.
*
*  CHECK sy-subrc = 0.

*Find out wich objects are really changed.
  CALL FUNCTION 'CRM_EVENT_PASS_INFO_EXE_OW'
    EXPORTING
      iv_exetime          = '080'
      iv_order_guid       = lv_header_guid
      iv_obj_kind         = 'A'
    IMPORTING
      et_event_calls      = lt_events
*     et_segments_changed = lt_segments
    EXCEPTIONS
      OTHERS              = 99.

*We are only interested in changed objects on header level.
*Therefore we have to ignore/delete every change on item level.
  DELETE lt_events WHERE obj_guid <> lv_header_guid.

*Are there any status changes (including error free flag, including status change cancel)
  READ TABLE lt_events
    TRANSPORTING NO FIELDS
    WITH KEY obj_name = 'STATUS'.

*Fine. We are in normal save mode without any status change activity.
  CHECK sy-subrc <> 0 OR lv_on_database_flag = abap_false.

*Are there any header changes (CHANGED_AT)
  READ TABLE lt_events
    TRANSPORTING NO FIELDS
    WITH KEY obj_name = 'ORDERADM_H'.

*Fine somthing has really changed.
  CHECK sy-subrc = 0.

*OK. Action is allowed to be executed.
  ep_rc = 0.

ENDMETHOD.

class based exception handling (ABAP OO exceptions)

There exist two kinds of exception handling in ABAP OO – The classical one and the object orientated one.

The classical one is similar to exception handling used by function modules: We can raise an exception together with a message (using ABAP statement „message … raising“). The caller need to catch and to handle this exception.

The object orientated exception solution (raised using the ABAP statement „RAISE EXCEPTION TYPE „) has the important positive issue that the caller doesn’t need to handle the exception. If the caller is not able or willing to handle the exception, it will be propagated to the caller of the caller automatically.

However, the object orientated exception is more complicated in cases we just want to throw an error message because we need to create an exception class first.

Good News: This is very simple.

  1. We can use or inherit from exception class CX_T100_MSG if we know message id and number at runtime only and these information are available in variables like SY-MSGID, SY-MAGNO, SY-SMG-TY, SY-MSGVX.
  2. We can use or inherit from class CX_RSR_BAPIRET2 if we have message information available in a BAPIRET2 structure used by many BAPI function modules. Since CX_RSR_BAPIRET2 is final, we cannot inherit from it. Therefore we once need to create a copy first.
  3. If we know message id and numbers (can be several!) at design time but message parameters only at run time, we can create an exception class based on interface IF_T100_MESSAGE (or simply flag „With Message Class“ at creation). For details, we can use the following guides:
    http://freesapabap.blogspot.fr/2014/12/oo-abap-exception-using-message-class.html
    http://zevolving.com/2013/04/exception-class-to-use-messages-from-t100/

Creating, changing and reading business transaction data using class cl_ags_crm_1o_api

To create, change or read business transaction data in SAP CRM or SAP Solution Manager, you can use low level API function modules CRM_ORDER_READ, CRM_ORDER_MAINTAIN, CRM_*_OW or high level layer BOL/GenIL.

However, sometimes you want to use a simple API which is optimized for single transaction processing. In that case, you should use classes cl_ags_crm_1o_api (and cl_crm_bsp_maintain_1o).

*"----------------------------------------------------------------------
*"Methoden Signatur:
*"  Importing
*"    IV_HEADER_GUID TYPE CRMT_OBJECT_GUID
*"    IV_PROCESS_TYPE TYPE CRMT_PROCESS_TYPE
*"  Exporting
*"    EV_HEADER_GUID TYPE CRMT_OBJECT_GUID
*"----------------------------------------------------------------------
METHOD create_transaction.

*We have to use class cl_crm_bsp_maintain_1o instead of cl_ags_crm_1o_api
*because class cl_ags_crm_1o_api don't support creation of follow-ups yet.
*After creation we need to save or we need to handover buffer from cl_crm_bsp_maintain_1o to cl_ags_crm_1o_api.
*Both ways are not possible. Therefore we free our instance of cl_crm_bsp_maintain_1o and hope the best.
"However this a dirty solution because the instance is still in buffer and marked as "to be saved".
*Therefore we might have side effects when someone uses class cl_crm_bsp_maintain_1o afterwards in same session.
*At the moment that isn't the fact.

  DATA:
    lr_maintain_1o TYPE REF TO cl_crm_bsp_maintain_1o.

  CLEAR ev_header_guid.


*Get Instance of 1O Manager
  CALL METHOD cl_crm_bsp_maintain_1o=>get_instance
    RECEIVING
      rr_maintain_1o = lr_maintain_1o.

*Create Follow Up.
  CALL METHOD lr_maintain_1o->copy_order
    EXPORTING
      iv_process_type    = iv_process_type
      iv_old_header_guid = iv_header_guid
      iv_vona_kind       = 'A' "Use Copy Control
*     iv_template_type   =
    IMPORTING
      ev_new_header_guid = ev_header_guid
    EXCEPTIONS
      error_occurred     = 1
      OTHERS             = 2.

  IF sy-subrc <> 0.
    MESSAGE ID sy-msgid TYPE 'X' NUMBER sy-msgno
               WITH sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4.
  ENDIF.

  FREE lr_maintain_1o.

ENDMETHOD.

*"----------------------------------------------------------------------
*"Methoden Signatur:
*"  Importing
*"    IV_HEADER_GUID TYPE CRMT_OBJECT_GUID
*"    IV_DESCRIPTION TYPE CRMT_PROCESS_DESCRIPTION
*"    IV_TEXTTYPE TYPE TDID
*"    IV_TEXTID TYPE DOK_ID
*"    IV_TEXTOBJ TYPE DOKU_OBJ
*"    IS_TRACK TYPE /TMWFLOW/TRACK
*"    IS_CONTEXT TYPE ZCIEH_CONTEXT_S
*"----------------------------------------------------------------------
METHOD adjust_short_text.

  DATA:
    lr_instance   TYPE REF TO cl_ags_crm_1o_api,
*   ls_orderadm_h TYPE crmt_orderadm_h_wrk,
    lv_on_database_flag TYPE abap_bool,
    lv_short_text LIKE iv_description.

*Get Instance of Change Transaction
  CALL METHOD cl_ags_crm_1o_api=>get_instance
    EXPORTING
*     iv_language                   = SY-LANGU
      iv_header_guid                = iv_header_guid
*     iv_process_type               =
      iv_process_mode               = 'C' "We want to Change the Change Transaction. But Exception can not tell that document is just locked.
    IMPORTING
*     et_msg                        =
      eo_instance                   = lr_instance
    EXCEPTIONS
      invalid_parameter_combination = 1
      error_occurred                = 2
      OTHERS                        = 3.

  IF sy-subrc <> 0.
    MESSAGE ID sy-msgid TYPE 'X' NUMBER sy-msgno
               WITH sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4.
  ENDIF.

*Check if Transaction already exists on Database.
*Getting MODE of ORDERADM_H don't work correct.
  CALL FUNCTION 'CRM_ORDERADM_H_ON_DATABASE_OW'
    EXPORTING
      iv_orderadm_h_guid  = iv_header_guid
    IMPORTING
      ev_on_database_flag = lv_on_database_flag.

*We want to change the description.
  IF iv_description IS NOT INITIAL.

*Get Short Text
    CALL METHOD lr_instance->get_short_text
      IMPORTING
        ev_short_text        = lv_short_text
      EXCEPTIONS
        document_not_found   = 1
        error_occurred       = 2
        document_locked      = 3
        no_change_authority  = 4
        no_display_authority = 5
        no_change_allowed    = 6
        OTHERS               = 7.

*Expected & unexpected error...
    CASE sy-subrc.
      WHEN 0.
      WHEN 3. RAISE document_locked.
      WHEN 4. RAISE no_authority.
      WHEN 5. RAISE no_authority.
      WHEN 6. RAISE no_change_allowed.
      WHEN OTHERS. MESSAGE ID sy-msgid TYPE 'X' NUMBER sy-msgno WITH sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4.
    ENDCASE.

*Set Short Text if it is empty yet or if Transaction will be created.
    IF lv_short_text IS INITIAL OR lv_on_database_flag = abap_false. "ls_orderadm_h-mode = 'A'.

      CALL METHOD lr_instance->set_short_text
        EXPORTING
          iv_short_text     = iv_description
        EXCEPTIONS
          error_occurred    = 1
          document_locked   = 2
          no_change_allowed = 3
          no_authority      = 4
          OTHERS            = 5.

*Expected & unexpected error...
      CASE sy-subrc.
        WHEN 0.
        WHEN 2. RAISE document_locked.
        WHEN 3. RAISE no_change_allowed.
        WHEN 4. RAISE no_authority.
        WHEN OTHERS. MESSAGE ID sy-msgid TYPE 'X' NUMBER sy-msgno WITH sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4.
      ENDCASE.

    ENDIF.

  ENDIF.

*This is a little bit strange but necessary.
*Status changes and long text changes bypass the One Order Framework.
*Therefore when we save the Change Transaction via CRM_ORDER_SAVE it might detect nothing to do.
*But this is wrong, we still want a commit work.

*Therefore we simulate a change on ORDERADM_H.
  CALL FUNCTION 'CRM_EVENT_PUBLISH_OW'
    EXPORTING
      iv_obj_name = 'ORDERADM_H'
      iv_guid_hi  = iv_header_guid
      iv_kind_hi  = 'A'
      iv_event    = 'SAVE'.

*Free Instance.
  FREE: lr_instance.

ENDMETHOD.

*"----------------------------------------------------------------------
*"Methoden Signatur:
*"  Importing
*"    IV_HEADER_GUID TYPE CRMT_OBJECT_GUID
*"----------------------------------------------------------------------
METHOD save_transaction.

  DATA:
    lr_instance      TYPE REF TO cl_ags_crm_1o_api,
    lt_exception     TYPE crmt_exception_t,
    lt_msg           TYPE /tmwflow/mo_tt_msg,
    lt_saved_objects TYPE crmt_return_objects,
    lv_log_handle TYPE balloghndl.

*Get Instance of Change Transaction
  CALL METHOD cl_ags_crm_1o_api=>get_instance
    EXPORTING
*     iv_language                   = SY-LANGU
      iv_header_guid                = iv_header_guid
*     iv_process_type               =
      iv_process_mode               = 'B'
    IMPORTING
*     et_msg                        =
      eo_instance                   = lr_instance
    EXCEPTIONS
      invalid_parameter_combination = 1
      error_occurred                = 2
      OTHERS                        = 3.

  IF sy-subrc <> 0.
    MESSAGE ID sy-msgid TYPE 'X' NUMBER sy-msgno
               WITH sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4.
  ENDIF.

*Save Transaction changes to database.
  CALL METHOD lr_instance->save
    EXPORTING
      iv_unlock                  = abap_true
*     iv_save_only_external_data = SPACE
      iv_init                    = abap_true
    IMPORTING
      et_exception               = lt_exception[]
      et_msg                     = lt_msg[]
      et_saved_objects           = lt_saved_objects[]
    CHANGING
      cv_log_handle              = lv_log_handle
    EXCEPTIONS
      error_occurred             = 1
      OTHERS                     = 2.

  IF sy-subrc <> 0.
    MESSAGE ID sy-msgid TYPE 'X' NUMBER sy-msgno
               WITH sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4.
  ELSEIF lt_saved_objects[] IS INITIAL.
    MESSAGE x151(00).
  ENDIF.

  FREE: lr_instance.

ENDMETHOD.

Change user status using method HF_SET_STATUS

*"----------------------------------------------------------------------
*"Methoden Signatur:
*"  Importing
*"    IV_HEADER_GUID TYPE CRMT_OBJECT_GUID
*"    IV_STATUS TYPE J_ESTAT
*"    IV_RESET TYPE OAX
*"----------------------------------------------------------------------
METHOD change_transaction_status.

*Simulate execution of PPF action method HF_SET_STATUS.
*This solution was inspired by report CRM_SOCM_SERVICE_REPORT and action method HF_SET_STATUS.

  DATA:
    lr_methodcall  TYPE REF TO if_ex_exec_methodcall_ppf,
    lr_object      TYPE REF TO object,
    lr_appl_object TYPE REF TO cl_doc_crm_order,
    lr_partner     TYPE REF TO cl_partner_ppf,
    lv_log_handle  TYPE balloghndl,
    lr_container   TYPE REF TO if_swj_ppf_container,
    lv_status      TYPE ppfdtstat.

*Create PPF instance
  CALL METHOD cl_exithandler=>get_instance
    EXPORTING
      exit_name                     = 'EXEC_METHODCALL_PPF'
      null_instance_accepted        = cl_socm_integration=>true
    CHANGING
      instance                      = lr_methodcall
    EXCEPTIONS
      no_reference                  = 1
      no_interface_reference        = 2
      no_exit_interface             = 3
      class_not_implement_interface = 4
      single_exit_multiply_active   = 5
      cast_error                    = 6
      exit_not_existing             = 7
      data_incons_in_exit_managem   = 8
      OTHERS                        = 9.

*Unexpected error...
  IF sy-subrc <> 0 OR lr_methodcall IS NOT BOUND.
    MESSAGE ID sy-msgid TYPE 'X' NUMBER sy-msgno
               WITH sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4.
  ENDIF.

*Get application object.
  CALL FUNCTION 'CRM_ACTION_GET_APPL_OBJECT'
    EXPORTING
      iv_ref_guid    = iv_header_guid
      iv_ref_kind    = 'A'
    IMPORTING
      ev_appl_object = lr_object.

*Unexpected error...
  IF lr_object IS NOT BOUND.
    MESSAGE ID sy-msgid TYPE 'X' NUMBER sy-msgno
               WITH sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4.
  ENDIF.

  lr_appl_object ?= lr_object.

*We have and need no partner.
  CLEAR:
    lr_partner.

*Create log handle.
  CALL METHOD cl_log_ppf=>create_log
*  EXPORTING
*    ip_object    = 'PPF'
*    ip_subobject = 'PROCESSING'
*    ip_ext_no    =
    RECEIVING
      ep_handle    = lv_log_handle.

*Create and build up PPF Container
  CREATE OBJECT lr_container TYPE cl_swj_ppf_container.

*Unexpected error...
  IF lr_container IS NOT BOUND.
    MESSAGE x151(00).
  ENDIF.

  CALL METHOD lr_container->set_value
    EXPORTING
      element_name = 'USER_STATUS'
      data         = iv_status.

  CALL METHOD lr_container->set_value
    EXPORTING
      element_name = 'RESET_STATUS'
      data         = iv_reset.

*Lock Change Transaction if not already done.
  CALL FUNCTION 'CRM_ORDER_ENQUEUE'
    EXPORTING
      iv_guid           = iv_header_guid
    EXCEPTIONS
      foreign_lock      = 1
      system_failure    = 2
      distributed_lock  = 3
      no_change_allowed = 4
      transferring      = 5
      OTHERS            = 6.

*Expected & unexpected error...
  CASE sy-subrc.
    WHEN 0.
    WHEN 1. RAISE document_locked.
    WHEN 4. RAISE no_change_allowed.
    WHEN OTHERS. MESSAGE ID sy-msgid TYPE 'X' NUMBER sy-msgno WITH sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4.
  ENDCASE.

*Change Status...
  TRY.
      CALL METHOD lr_methodcall->execute
        EXPORTING
          flt_val            = 'HF_SET_STATUS'
          io_appl_object     = lr_appl_object
          io_partner         = lr_partner
          ip_application_log = lv_log_handle
          ip_preview         = space
          ii_container       = lr_container
          ip_action          = '' "'RESET_BY_CHECK' "'SET_STATUS_BY_PROGRAM'
        RECEIVING
          rp_status          = lv_status.
    CATCH cx_socm_condition_violated.
      MESSAGE x151(00).
  ENDTRY.

*Free objects.
  CALL METHOD cl_log_ppf=>refresh_log
    EXPORTING
      ip_handle = lv_log_handle.

  FREE:
    lr_container,
    lr_partner,
    lr_appl_object,
    lr_methodcall.

*Status was not changed.
*We don't know why. Maybe authority maybe check condition. Maybe bigfoot. ...
  IF lv_status <> 1.
    RAISE no_change_allowed.
  ENDIF.

ENDMETHOD.

Read text template, resolve placeholders and maintain text

*"----------------------------------------------------------------------
*"Methoden Signatur:
*"  Importing
*"    IV_HEADER_GUID TYPE CRMT_OBJECT_GUID
*"    IV_TEXTTYPE TYPE TDID
*"    IV_TEXTID TYPE DOK_ID
*"    IV_TEXTOBJ TYPE DOKU_OBJ
*"----------------------------------------------------------------------
METHOD maintain_transaction_text.

  DATA:
    lr_instance   TYPE REF TO cl_ags_crm_1o_api,
    ls_head       TYPE thead,
    lt_line       TYPE TABLE OF tline,
    lt_symbol     TYPE TABLE OF itcst,
    lt_text       TYPE crmt_text_comt,
    ls_text       LIKE LINE OF lt_text[].

  FIELD-SYMBOLS:
    <fs_symbol> LIKE LINE OF lt_symbol[],
    <fv_value>  TYPE any.

*Get Instance of Change Transaction
  CALL METHOD cl_ags_crm_1o_api=>get_instance
    EXPORTING
*     iv_language                   = SY-LANGU
      iv_header_guid                = iv_header_guid
*     iv_process_type               =
      iv_process_mode               = 'C' "We want to Change the Change Transaction. But Exception can not tell that document is just locked.
    IMPORTING
*     et_msg                        =
      eo_instance                   = lr_instance
    EXCEPTIONS
      invalid_parameter_combination = 1
      error_occurred                = 2
      OTHERS                        = 3.

  IF sy-subrc <> 0.
    MESSAGE ID sy-msgid TYPE 'X' NUMBER sy-msgno
               WITH sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4.
  ENDIF.

*We want add a text block...
*The following code was inspired by
*  CL_WD_FORMATTED_TEXT->CREATE_FROM_SAPSCRIPT
*  CL_SSF_XSF_UTILITIES
  IF iv_texttype IS NOT INITIAL AND iv_textid IS NOT INITIAL AND iv_textobj IS NOT INITIAL.

*Read text block previously created using transaction SE61.
    CALL FUNCTION 'DOCU_GET'
      EXPORTING
*       EXTEND_EXCEPT          = ' '
        id                     = iv_textid
        langu                  = sy-langu
        object                 = iv_textobj
*       TYP                    = 'E'
*       VERSION                = 0
*       VERSION_ACTIVE_OR_LAST = 'L'
*       PRINT_PARAM_GET        = 'X'
      IMPORTING
*       DOKSTATE               =
*       DOKTITLE               =
        head                   = ls_head
*       DOKTYP                 =
      TABLES
        line                   = lt_line[]
      EXCEPTIONS
        no_docu_on_screen      = 1
        no_docu_self_def       = 2
        no_docu_temp           = 3
        ret_code               = 4
        OTHERS                 = 5.

*Expected & unexpected error...
    CASE sy-subrc.
      WHEN 0.
      WHEN OTHERS. MESSAGE ID sy-msgid TYPE 'X' NUMBER sy-msgno WITH sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4.
    ENDCASE.

*Resolve Text block includes.
    CALL FUNCTION 'TEXT_INCLUDE_REPLACE'
      EXPORTING
*   ALL_LEVEL        = 'X'
*   ENDLINE          = 99999
        header           = ls_head
*   STARTLINE        = 1
*   PROGRAM          = ' '
* IMPORTING
*   CHANGED          =
*   ERROR_TYPE       =
*   NEWHEADER        =
      TABLES
        lines            = lt_line[].

*Resolve text block control statements.
    CALL FUNCTION 'TEXT_CONTROL_REPLACE'
      EXPORTING
        header                = ls_head
*   PROGRAM               = ' '
*   REPLACE_COMMENT       = 'X'
* IMPORTING
*   CHANGED               =
*   NEWHEADER             =
      TABLES
        lines                 = lt_line[].

*Clear buffer for text symbols.
    CALL FUNCTION 'INIT_TEXTSYMBOL'
      EXPORTING
*       PROGRAM = ' '
*       TEST    = ' '
        type    = 'TEXT'.

*Get all used symbols.
    CALL FUNCTION 'TEXT_SYMBOL_COLLECT'
      TABLES
        lines   = lt_line[]
        symbols = lt_symbol[].

*Set values for used symbols.
    LOOP AT lt_symbol[] ASSIGNING <fs_symbol>.

      ASSIGN (<fs_symbol>-name) TO <fv_value>.

      IF sy-subrc = 0.

*An alternative solution would be the use of global variables in a program or function group.
        CALL FUNCTION 'TEXT_SYMBOL_SETVALUE'
          EXPORTING
            name            = <fs_symbol>-name
            value           = <fv_value>
*           VALUE_LENGTH    = 0
*           REPLACE_SYMBOLS = ' '
          .

      ELSE.

*There is a wrong symbol in text block.
        MESSAGE x151(00).

      ENDIF.

    ENDLOOP.

*Replace symbols by value.
    CALL FUNCTION 'TEXT_SYMBOL_REPLACE'
      EXPORTING
*   ENDLINE                = 99999
        header                 = ls_head
*   INIT                   = ' '
*   OPTION_DIALOG          = ' '
*   PROGRAM                = ' '
        replace_program        = ' ' "We don^t use global variables of a program or function group
*       REPLACE_STANDARD       = 'X' "What's that???
        replace_system         = 'X' "Replace system variables (date, time, username, ...)
        replace_text           = 'X' "Replace all variables pubslished by TEXT_SYMBOL_SETVALUE
*   STARTLINE              = 1
* IMPORTING
*   CHANGED                =
*   NEWHEADER              =
      TABLES
        lines                  = lt_line[].

*Add Text block.
    CLEAR:
      ls_text,
      lt_text[].

    MOVE:
      iv_texttype TO ls_text-tdid,
      'I'         TO ls_text-mode,
      lt_line[]   TO ls_text-lines[].

    APPEND ls_text TO lt_text[].

    CALL METHOD lr_instance->set_texts
      EXPORTING
        it_text           = lt_text[]
*  CHANGING
*    cv_log_handle     =
      EXCEPTIONS
        error_occurred    = 1
        document_locked   = 2
        no_change_allowed = 3
        no_authority      = 4
        OTHERS            = 5.

*Expected & unexpected error...
    CASE sy-subrc.
      WHEN 0.
      WHEN 2. RAISE document_locked.
      WHEN 3. RAISE no_change_allowed.
      WHEN 4. RAISE no_authority.
      WHEN OTHERS. MESSAGE ID sy-msgid TYPE 'X' NUMBER sy-msgno WITH sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4.
    ENDCASE.

**I dont really understand it.
**The Standard did not post this event on create/change.
**But on save he detects it.
**We need to post it manually to save the text changes.
**I think this is a bug and should be resolved by SAP.
**Maybe it only occurs when we only change texts.
*    IF sy-subrc = 0.
*      CALL FUNCTION 'CRM_EVENT_PUBLISH_OW'
*        EXPORTING
*          iv_obj_name = 'TEXTS'
*          iv_guid_hi  = iv_header_guid
*          iv_kind_hi  = 'A'
*          iv_event    = 'SAVE'
*        EXCEPTIONS
*          OTHERS      = 2.
*    ENDIF.

  ENDIF.

*This is a little bit strange but necessary.
*Status changes and long text changes bypass the One Order Framework.
*Therefore when we save the Change Transaction via CRM_ORDER_SAVE it might detect nothing to do.
*But this is wrong, we still want a commit work.

*Therefore we simulate a change on ORDERADM_H.
  CALL FUNCTION 'CRM_EVENT_PUBLISH_OW'
    EXPORTING
      iv_obj_name = 'ORDERADM_H'
      iv_guid_hi  = iv_header_guid
      iv_kind_hi  = 'A'
      iv_event    = 'SAVE'.

*Free Instance.
  FREE: lr_instance.

ENDMETHOD.