[ABAP] Testen, ob per RFC ein Funktionsbaustein im Zielsystem vorhanden ist

* https://www.berater-wiki.de/RFC-Funktionsbausteine
* https://download.consolut.com/direct/SAP_PrintDoku/de/BCFESDE2/BCFESDE2.PDF

* Funktionsbaustein-Name, verwendet Suchhilfe pb_func_key
PARAMETERS: p_func TYPE rs38l_fnam MATCHCODE OBJECT pb_func_key.
* RFC-Verbindung
PARAMETERS: p_rfc TYPE rfcdest.

START-OF-SELECTION.

* Nachschauen, ob RFC-Verbindung vorhanden
  SELECT SINGLE d~rfcdest, d~rfctype, d~rfcoptions, t~rfcdoc1, t~rfcdoc2, t~rfcdoc3
    INTO @DATA(lv_rfc)
    FROM rfcdes AS d
    INNER JOIN rfcdoc AS t ON ( d~rfcdest = t~rfcdest )
    WHERE d~rfcdest = @p_rfc
      AND t~rfclang = @sy-langu.

  IF sy-subrc = 0.
    DATA: lv_err_msg TYPE char255.
* wenn RFC-Verbindung vorhanden -> anpingen und Systemfehler (Dumps) abfangen
    CALL FUNCTION 'RFC_PING' DESTINATION p_rfc
      EXCEPTIONS
        system_failure        = 1 MESSAGE lv_err_msg
        communication_failure = 2 MESSAGE lv_err_msg
        OTHERS                = 3.

    IF sy-subrc = 0.
* RFC-Ping ok
      WRITE: / '[', p_rfc, '] RFC-Aufruf erfolgreich.'.

* Existenzprüfung und Funktionsgruppe zu Funktionsbaustein besorgen
      CALL FUNCTION 'FUNCTION_EXISTS' DESTINATION p_rfc
        EXPORTING
          funcname           = p_func
        EXCEPTIONS
          function_not_exist = 1
          OTHERS             = 2.

      IF sy-subrc = 0.
        WRITE: / 'Funktionsbaustein:', p_func, 'existiert im Zielsystem:', p_rfc.
      ELSE.
        WRITE: / 'Funktionsbaustein:', p_func, 'existiert nicht im Zielsystem:', p_rfc.
      ENDIF.
    ELSE.
* RFC-Ping fehlerhaft
      WRITE: / '[', p_rfc, '] RFC-Aufruf gescheitert:', lv_err_msg.
    ENDIF.
  ELSE.
* RFC-Verbindung nicht vorhanden
    WRITE: / '[', p_rfc, '] RFC-Verbindung nicht vorhanden.'.
  ENDIF.

[ABAP] RFC-Aufruf einer Transaktion

DATA: it_error TYPE STANDARD TABLE OF bdcmsgcoll.

DATA: lv_rc TYPE sy-subrc.

* Batch
DATA(it_batch) = VALUE bdcdata_tab( ( dynbegin = 'T' fnam = 'FD03' )
                                    ( program = 'SAPMF02D' dynpro = '0106' dynbegin = 'X' )
                                    ( fnam = 'RF02D-KUNNR' fval = '40000' )
                                    ( fnam = 'RF02D-BUKRS' fval = '' )
                                    ( fnam = 'RF02D-D0110' fval = abap_true )
                                    ( fnam = 'RF02D-D0120' fval = abap_true )
                                    ( fnam = 'BDC_OKCODE'  fval = '/00' ) ).

* RFC-Aufruf
CALL FUNCTION 'RFC_CALL_TRANSACTION_USING'
  DESTINATION 'XYZ'
  EXPORTING
    tcode                   = 'FD03'
    mode                    = 'E'
  IMPORTING
    subrc                   = lv_rc
  TABLES
    bt_data                 = it_batch
    l_errors                = it_error
  EXCEPTIONS
    authority_not_available = 1
    OTHERS                  = 2.

* Datenausgabe
WRITE: / sy-subrc.
WRITE: / lv_rc.

LOOP AT it_batch ASSIGNING FIELD-SYMBOL(<b>).
  WRITE: / <b>-program, <b>-dynpro, <b>-dynbegin, <b>-fnam, <b>-fval.
ENDLOOP.

LOOP AT it_error ASSIGNING FIELD-SYMBOL(<e>).
  WRITE: / <e>-tcode, <e>-dyname, <e>-dynumb, <e>-msgtyp, <e>-msgspra, <e>-msgid, <e>-msgnr, <e>-msgv1, <e>-msgv2, <e>-msgv3, <e>-msgv4, <e>-env, <e>-fldname.
ENDLOOP.

[MS Excel] BAPI-Zugriff aus Excel (VBA) heraus

Option Explicit

Sub GetMATNR()
    ' Verbindung zu SAP herstellen
    Dim oSAP As Object
    Set oSAP = CreateObject("SAP.Functions")
    ' Verbindungsdaten vorbelegen
    oSAP.Connection.ApplicationServer = "1.1.1.1" ' IP des Appl-Servers (SM51->Details)
    oSAP.Connection.SystemNumber = "01"           ' Systemnummer, meißt im Namen des Appl-Servers enthalten
    oSAP.Connection.System = "XD1"                ' Entwicklungs-, Test-, Produktivsystem
    oSAP.Connection.Client = "100"                ' Mandant
    oSAP.Connection.Language = "DE"               ' Sprache "EN", "DE" ...
    
    ' RFC-Login: Logon-Fenster anzeigen
    If oSAP.Connection.Logon(0, False) = True Then
        ' BAPI_MATERIAL_GET_DETAIL abfragen
        Dim oFuBa As Object
        Set oFuBa = oSAP.Add("BAPI_MATERIAL_GET_DETAIL")
        
        ' EXPORTING
        Dim e_material As Variant
        Set e_material = oFuBa.Exports("MATERIAL")
        e_material.Value = ActiveWorkbook.ActiveSheet.Cells(1, 2) ' MATNR in B1
        
        ' IMPORTING
        Dim i_material_general_data As Variant
        Set i_material_general_data = oFuBa.Imports("MATERIAL_GENERAL_DATA")
        
        Dim i_return As Variant
        Set i_return = oFuBa.Imports("RETURN")
        
        ' Wenn Fuba-Aufruf ok
        If oFuBa.Call = True Then
            ' RETURN auswerten
            If i_return.Value("TYPE") = "E" Then
                ' Bei Fehler: Fehlermessage ausgeben in B2
                ActiveWorkbook.ActiveSheet.Cells(2, 2) = i_return.Value("MESSAGE")
            Else
                ' Wenn OK: Materialkurztext in B2
                ActiveWorkbook.ActiveSheet.Cells(2, 2) = i_material_general_data.Value("MATL_DESC")
            End If
        End If
    End If
End Sub

[MS Excel] Tabellendaten per RFC-Zugriff (RFC_READ_TABLE) aus einem SAP-System importieren

Sub RFCReadTable()
    ' https://www.linkedin.com/pulse/connect-sap-r3-call-custom-fm-from-ms-excel-erkan-kopuz
    ' https://saplsmw.com/Import_tables_directly_into_Access_from_SAP_using_RFCs
    ' http://sapass.metro.client.jp/Sap_Active_X/UseFunctionControl.htm
    Set oSAP = CreateObject("SAP.Functions")
    oSAP.Connection.ApplicationServer = "1.1.1.1" ' IP des Appl-Servers (SM51->Details)
    oSAP.Connection.SystemNumber = "01"           ' Systemnummer, meißt im Namen des Appl-Servers enthalten
    oSAP.Connection.System = "XA1"                ' Entwicklungs-, Test-, Produktivsystem
    oSAP.Connection.Client = "100"                ' Mandant
    oSAP.Connection.Language = "DE"               ' Sprache "EN", "DE" ...
    oSAP.Connection.User = "USER1"                ' SAP-User
    'oSAP.Connection.Password = "xyz"              ' SAP-Passwort
    oSAP.Connection.UseSAPLogonIni = False
    
    ' RFC-Login, wobei
    ' Logon(0, False): Logon-Fenster anzeigen
    ' Logon(0, True): Silent logon, Passwort muss gesetzt sein
    If oSAP.Connection.Logon(0, False) = True Then
      Dim oFuBa As Object
        
      ' FuBa RFC_READ_TABLE abfragen
      Set oFuBa = oSAP.Add("RFC_READ_TABLE")
      
      ' EXPORTING
      Set e_query_table = oFuBa.Exports("QUERY_TABLE")
      Set e_delimiter = oFuBa.Exports("DELIMITER")
      Set e_rowCount = oFuBa.Exports("ROWCOUNT")
      
      e_query_table.Value = "STXH" ' Tabelle STXH
      e_delimiter.Value = ";"      ' Spalten mit ";" getrennt
      e_rowCount.Value = "100"     ' max. 100 Datensätze lesen, 0 = alle
      
      ' TABLES
      Set t_options = oFuBa.Tables("OPTIONS")
      Set t_fields = oFuBa.Tables("FIELDS")
      Set t_data = oFuBa.Tables("DATA")
      
      ' WHERE-Bedingung
      t_options.AppendRow
      t_options(1, "TEXT") = "TDOBJECT EQ 'TEXT'"
      
      ' Welche Spalten sollen gelesen werden
      t_fields.AppendRow
      t_fields(1, "FIELDNAME") = "TDOBJECT"
      t_fields.AppendRow
      t_fields(2, "FIELDNAME") = "TDNAME"
      t_fields.AppendRow
      t_fields(3, "FIELDNAME") = "TDID"
      t_fields.AppendRow
      t_fields(4, "FIELDNAME") = "TDTITLE"
      t_fields.AppendRow
      t_fields(5, "FIELDNAME") = "TDLUSER"
      
      If oFuBa.Call = True Then
        ' Schnittstellenparameter "TABLES-DATA" holen
        ' Rückgabe in Excel-Sheet einfügen
        
        Dim iRow As Integer
        iRow = 1
            
        ' Rückgabemenge beinhaltet Strings, die mit dem festgelegten Separator ";" getrennt sind
        For Each oDataLine In t_data.Rows

          ' Spalten einer Datenzeile anhand des Separators ";" aufsplitten
          Dim vFields As Variant
          vFields = Split(oDataLine(1), ";")
          
          ' Inhalt der Spalten in die Zellen schreiben
          For iCol = LBound(vFields) To UBound(vFields)
            ActiveWorkbook.Sheets(1).Cells(iRow, iCol + 1) = Trim(vFields(iCol))
          Next iCol
                
         iRow = iRow + 1
        Next
      Else
        ' Exception?
        MsgBox oFuBa.Exception
      End If

      ' Logoff
      oSAP.Connection.Logoff
    
    Else
      ' Kein Login möglich
      MsgBox "Login fehlgeschlagen."
    End If

End Sub

[MS Excel] Nutzerliste per RFC-Zugriff (TH_USER_LIST) aus einem SAP-System importieren

Sub GetUserList()
  ' https://www.linkedin.com/pulse/connect-sap-r3-call-custom-fm-from-ms-excel-erkan-kopuz
  ' https://saplsmw.com/Import_tables_directly_into_Access_from_SAP_using_RFCs
  ' http://sapass.metro.client.jp/Sap_Active_X/UseFunctionControl.htm

  ' SAP-Objekt erzeugen
  Set oSAP = CreateObject("SAP.Functions")
  oSAP.Connection.ApplicationServer = "1.1.1.1"     ' IP des Appl-Servers (SM51->Details)
  oSAP.Connection.SystemNumber = "01"               ' Systemnummer, meißt im Namen des Appl-Servers enthalten
  oSAP.Connection.System = "DV1"                    ' Entwicklungs-, Test-, Produktivsystem
  oSAP.Connection.Client = "100"                    ' Mandant
  oSAP.Connection.Language = "DE"                   ' Sprache "EN", "DE" ...
  oSAP.Connection.User = "USER1"                    ' SAP-User
  'oSAP.Connection.Password = "xyz"                  ' SAP-Passwort
  oSAP.Connection.UseSAPLogonIni = False
    
  ' RFC-Login, wobei
  ' Logon(0, False): Logon-Fenster anzeigen
  ' Logon(0, True): Silent logon, Passwort muss gesetzt sein
  If oSAP.Connection.Logon(0, False) = True Then
    Dim oFuBa As Object
        
    ' FuBa TH_USER_LIST abfragen
    Set oFuBa = oSAP.Add("TH_USER_LIST")

    If oFuBa.Call = True Then
        
      ' Schnittstellenparameter "TABLES-USRLIST"
      Dim oUsrList As Object
      Set oUsrList = oFuBa.Tables("USRLIST")
             
      ' Userliste in Excel-Sheet einfügen         
      Dim i As Integer
      i = 1
            
      For Each User In oUsrList.Rows
               
        ActiveWorkbook.Sheets(1).Cells(i, 1) = User(2)  ' Client
        ActiveWorkbook.Sheets(1).Cells(i, 2) = User(3)  ' UserName
        ActiveWorkbook.Sheets(1).Cells(i, 3) = User(5)  ' Terminal
        ActiveWorkbook.Sheets(1).Cells(i, 4) = User(16) ' IP
               
        i = i + 1
      Next
    Else
      ' Exception?
      MsgBox oFuBa.Exception
    End If

    ' Logoff
    oSAP.Connection.Logoff
    
  Else
    ' Kein Login möglich
    MsgBox "Login fehlgeschlagen."
  End If

End Sub

[ABAP] Parallelverarbeitung / Multitasking

Auch SAP kann Multitasking. Dazu muss mann einen RFC-fähigen Funktionsbaustein in einem neuen Task innerhalb einer sogenannten Taskgroup aufrufen. Die Abarbeitung erfolgt dann asynchron, dass Ergebnis wird in einer Handler-Funktion zurückgeliefert.

Im folgenden Funktionsbeispiel erzeugt das Rahmenprogramm “zparallel” Arbeitspakete (it_input) für die Tasks und ein Objekt der Klasse “lcl_task_manager”. Letzteres managed die Verteilung der Eingangsdaten (it_input) auf mehrere Tasks. Die Taskobjekte werden durch die Klasse “lcl_task” abgebildet. Die einzelnen Tasks rufen asynchron mit ihren Arbeitspaketen den RFC-fähigen Funktionsbaustein “BAPI_BILLINGDOC_GETLIST”.

Nach erfolgreichem Beenden aller Tasks werden die Ergebnisse der Arbeitspakete zusammengesetzt (it_result) und an das Rahmenprogramm zurückgegeben.

Weiterführende Informationen: Link, Link, Link und Link

Rahmenprogramm: REPORT zparallel (Aufruf der Parallelverarbeitung)

REPORT zparallel.

* Klassen für Taskmanagement
INCLUDE ztask_manager.

* iTabs für Eingabe- und Ausgabedaten
DATA: it_input TYPE lcl_task=>ty_it_input_data.
DATA: it_result TYPE lcl_task_manager=>ty_it_final_result.
DATA: it_statistic TYPE lcl_task_manager=>ty_it_statistic.

PARAMETERS: p_rows TYPE i DEFAULT 1000. " Test-Elemente
PARAMETERS: p_wait TYPE i DEFAULT 5.    " Wartezeit in [s]
PARAMETERS: p_max_rt TYPE i DEFAULT 3.  " max. Anzahl Versuche
PARAMETERS: p_size TYPE i DEFAULT 100.  " Intervallgröße

START-OF-SELECTION.

  DATA: it_vbelv TYPE STANDARD TABLE OF vbelv WITH DEFAULT KEY.

* Intervalle erzeugen
  SELECT DISTINCT vbelv FROM vbfa INTO TABLE @it_vbelv UP TO @p_rows ROWS.

  IF sy-subrc = 0.
    SORT: it_vbelv BY table_line.

    DATA(lv_lines) = lines( it_vbelv ).
    DATA(lv_parts) = lv_lines DIV p_size.
    DATA(lv_mod)   = lv_lines MOD p_size.
    IF lv_mod <> 0.
      lv_parts = lv_parts + 1.
    ENDIF.

    DATA(lv_idx_l) = 0.
    DATA(lv_idx_h) = 0.

    DO lv_parts TIMES.
      lv_idx_l = lv_idx_h + 1.
      lv_idx_h = lv_idx_l - 1 + p_size.

      IF lv_idx_h > lv_lines.
        lv_idx_h = lv_lines.
      ENDIF.

      APPEND VALUE #( sign         = 'I'
                      option       = 'BT'
                      ref_doc_low  = it_vbelv[ lv_idx_l ]
                      ref_doc_high = it_vbelv[ lv_idx_h ] ) TO it_input.
    ENDDO.

* Referenz auf Tastmanager für Parallelverarbeitung
    DATA(o_task_manager) = lcl_task_manager=>get_instance( i_task_group = lcl_task_manager=>co_default_task_group ).

    DATA: lv_started TYPE i.
    DATA: lv_ended TYPE i.
    DATA: lv_missed TYPE i.

* Timer starten
    DATA(o_timer) = cl_abap_runtime=>create_hr_timer( ).
    DATA(usec_start) = o_timer->get_runtime( ).

* Parallele Verarbeitung der Eingabedaten starten
    IF abap_true = o_task_manager->start_parallel_working( EXPORTING
                                                             i_it_input_data = it_input
                                                             i_wait_time_sec = p_wait
                                                             i_max_retries   = p_max_rt
                                                           IMPORTING
                                                             e_it_final_result = it_result
                                                             e_it_statistic    = it_statistic
                                                             e_tasks_started   = lv_started
                                                             e_tasks_ended     = lv_ended
                                                             e_tasks_missed    = lv_missed ).

* Timer stoppen
      DATA(usec_end) = o_timer->get_runtime( ).
      DATA(usec) = CONV decfloat16( usec_end - usec_start ).
      DATA(sec) = usec / 1000000.

      WRITE: / '    Zeit [µs]:', usec.
      WRITE: / '     Zeit [s]:', sec.
      WRITE: / '   max. Tasks:', o_task_manager->get_max_tasks( ).
      WRITE: / '   free Tasks:', o_task_manager->get_free_tasks( ).
      WRITE: / 'Tasks started:', lv_started.
      WRITE: / '  Tasks ended:', lv_ended.
      WRITE: / ' Tasks missed:', lv_missed.

      WRITE: / '  Input lines:', lines( it_input ).
      WRITE: / ' Result lines:', lines( it_result ).
      ULINE.
      WRITE: / | Task #     \| Retries     \| Time [µs]  \| S \| IN: LOW    \| IN: HIGH   \| SUBRC  \| SIZE|.
      ULINE.
      LOOP AT it_statistic ASSIGNING FIELD-SYMBOL(<fs_stat>).
        WRITE: / <fs_stat>-task_number, '|', <fs_stat>-retries, '|', <fs_stat>-runtime, '|', <fs_stat>-successful, '|', <fs_stat>-input_data-ref_doc_low, '|', <fs_stat>-input_data-ref_doc_high, '|', <fs_stat>-subrc, '|', <fs_stat>-output_part_size.
      ENDLOOP.

      ULINE.
      WRITE: / | ITEM #     \| IN: LOW    \| IN: HIGH   \| OUT: REF_DOC|.
      ULINE.
* Ergebnisausgabe
      LOOP AT it_result ASSIGNING FIELD-SYMBOL(<fs_res>).
        WRITE: / sy-tabix, '|', <fs_res>-input_data-ref_doc_low, '|', <fs_res>-input_data-ref_doc_high, '|', <fs_res>-output_data-ref_doc.
      ENDLOOP.
    ENDIF.
  ENDIF.

Task-Klassen: INCLUDE ZTASK_MANAGER

*--------------------------------------------------------------------*
* Task-Klasse (Worker)
*--------------------------------------------------------------------*
* Ruft asynchon einen RFC-fähigen Funktionsbaustein
* und verarbeitet asynchron das Ergebnis (Event)
*--------------------------------------------------------------------*
CLASS lcl_task DEFINITION FINAL.
  PUBLIC SECTION.

* Eingabedaten des Task
    TYPES: ty_input_data TYPE bapi_ref_doc_range.
* Ausgabedaten des Task
    TYPES: ty_output_data TYPE bapivbrksuccess.

    TYPES: ty_it_input_data TYPE STANDARD TABLE OF ty_input_data WITH DEFAULT KEY.
    TYPES: ty_it_output_data TYPE STANDARD TABLE OF ty_output_data WITH DEFAULT KEY.

* Name des RFC-fähigen Funktionsbausteines für "start_async_call" und "task_receive"-Handler
    CONSTANTS: co_fb_name TYPE string VALUE 'BAPI_BILLINGDOC_GETLIST'.

* Event, wird getriggert, wenn die asynchrone Methode task_receive aufgerufen wird
    EVENTS:
      data_received
        EXPORTING
          VALUE(e_subrc) TYPE sy-subrc
          VALUE(e_runtime) TYPE int4
          VALUE(e_retries) TYPE i
          VALUE(e_input_data) TYPE ty_input_data
          VALUE(e_output_data) TYPE ty_it_output_data.

* Konstruktor
    METHODS:
      constructor
        IMPORTING
          i_task_group  TYPE rzllitab-classname
          i_task_number TYPE i.

* Startet asynchronen Aufruf des RFC-Bausteins
    METHODS:
      start_async_call
        IMPORTING
                  i_input_data              TYPE ty_input_data
                  i_started_after_n_retries TYPE i
        RETURNING VALUE(rv_subrc)           TYPE sy-subrc.

* Handlermethode, wird asynchron gerufen
    METHODS:
      task_receive
        IMPORTING
          p_task TYPE clike.

* Hilfsfunktionen
    METHODS:
      is_running
        RETURNING VALUE(rv_running) TYPE boolean.

    METHODS:
      get_group
        RETURNING VALUE(rv_group) TYPE rzllitab-classname.

    METHODS:
      get_task_number
        RETURNING VALUE(rv_number) TYPE i.

  PRIVATE SECTION.

    DATA: lv_group TYPE rzllitab-classname.
    DATA: lv_number TYPE i.
    DATA: lv_running TYPE boolean.
    DATA: lv_started_after_n_retries TYPE i.

    DATA: lv_start_time TYPE int4.
    DATA: lv_end_time TYPE int4.

* Merker für Übergabe-Parameter in start_async_call
    DATA: lv_input_data TYPE ty_input_data.
ENDCLASS.

CLASS lcl_task IMPLEMENTATION.
*--------------------------------------------------------------------*
* Konstruktor
*--------------------------------------------------------------------*
* -> i_task_group  - Gruppenname
* -> i_task_number - Tasknummer
*--------------------------------------------------------------------*
  METHOD constructor.
    lv_group                   = i_task_group.
    lv_number                  = i_task_number.
    lv_running                 = abap_false.
    lv_start_time              = 0.
    lv_end_time                = 0.
    lv_started_after_n_retries = 0.
  ENDMETHOD.
*--------------------------------------------------------------------*
* Startet asynchronen Aufruf des RFC-Bausteins
*--------------------------------------------------------------------*
* -> i_input_data              - Eingabedaten für Verarbeitung
* -> i_started_after_n_retries - Statistikwert, nach wieviel Versuchen
*                                der Task gestartet werden konnte
* <- rv_subrc                  - Status des asynchronen Aufrufs
*--------------------------------------------------------------------*
  METHOD start_async_call.

    lv_running                 = abap_false.
    lv_started_after_n_retries = i_started_after_n_retries.

    GET RUN TIME FIELD lv_start_time.

* neuen asynchronen Aufruf des Bausteines starten
* Gruppe, Tasknummer und Handlerfunktion übergeben
* Baustein muss RFC-fähig sein
* Verfügbare RFC-Gruppen für Parallelverarbeitung: Transaktion RZ12
* Namen und Typen der EXPORTING-Parameter beachten:
* -> fehlerhafte Angaben werden nicht vom Compiler bemerkt oder auch nicht als sy-subrc zurückgegeben
    CALL FUNCTION co_fb_name
      STARTING NEW TASK lv_number
      DESTINATION IN GROUP lv_group
      CALLING task_receive ON END OF TASK
      EXPORTING
        refdocrange           = i_input_data
      EXCEPTIONS
        system_failure        = 1
        communication_failure = 2
        resource_failure      = 3
        OTHERS                = 4.

* Fehlerstatus zurückgeben
    rv_subrc = sy-subrc.

    IF rv_subrc = 0.
      lv_input_data      = i_input_data.
      lv_running         = abap_true.
    ENDIF.

  ENDMETHOD.
*--------------------------------------------------------------------*
* Receive-Funktion, welche bei Fertigsstellung vom Task asynchron
* getriggert wird
* Es werden keine WRITE-Statements ausgeführt!
*--------------------------------------------------------------------*
* -> p_task            - Tasknummer
*--------------------------------------------------------------------*
  METHOD task_receive.

    DATA: it_task_data TYPE ty_it_output_data.

* Ergebnisse des asynchronen RFC-Aufrufes vom Funktionsbaustein holen
    RECEIVE RESULTS FROM FUNCTION co_fb_name
      TABLES
        success               = it_task_data
      EXCEPTIONS
        system_failure        = 1
        communication_failure = 2
        resource_failure      = 3
        OTHERS                = 4.

    GET RUN TIME FIELD lv_end_time.

    DATA(lv_runtime) = lv_end_time - lv_start_time.

* Event für Datenrückgabe triggern
    RAISE EVENT data_received
      EXPORTING
        e_subrc       = sy-subrc
        e_runtime     = lv_runtime
        e_retries     = lv_started_after_n_retries
        e_input_data  = lv_input_data
        e_output_data = it_task_data.

    lv_running = abap_false.

  ENDMETHOD.
*--------------------------------------------------------------------*
* Gibt Status des Tasks
*--------------------------------------------------------------------*
* <- rv_running - Status des Tasks
*--------------------------------------------------------------------*
  METHOD is_running.
    rv_running = lv_running.
  ENDMETHOD.
*--------------------------------------------------------------------*
* Gibt Gruppenname, in welcher der Task läuft
*--------------------------------------------------------------------*
* <- rv_group - Gruppenname
*--------------------------------------------------------------------*
  METHOD get_group.
    rv_group = lv_group.
  ENDMETHOD.
*--------------------------------------------------------------------*
* Gibt Tasknummer
*--------------------------------------------------------------------*
* <- rv_number - Tasknummer
*--------------------------------------------------------------------*
  METHOD get_task_number.
    rv_number = lv_number.
  ENDMETHOD.
ENDCLASS.
*--------------------------------------------------------------------*
* Verwaltungs-Klasse für Tasks
*--------------------------------------------------------------------*
* 1. Verarbeitet Eingabedaten (Workitems)
* 2. Holt für jedes Workitem einen freien Task
* 3. Startet pro Workitem einen Task (asynchron)
* 4. Wertet das asynchron gelieferte Ergebis in einer Handlermethode aus
*--------------------------------------------------------------------*
* Sinnvoll ist die Implementierung nur, wenn die Workprozesse
* ausreichend lange dauern, so dass sich eine parallele Verarbeitung
* überhaupt lohnt
*--------------------------------------------------------------------*
CLASS lcl_task_manager DEFINITION FINAL CREATE PRIVATE.
  PUBLIC SECTION.

* Datentyp für finale Ausgabe, wenn alle Workprozesse fertig sind
    TYPES: BEGIN OF ty_final_result,
             input_data  TYPE lcl_task=>ty_input_data,
             output_data TYPE lcl_task=>ty_output_data,
           END OF ty_final_result.

    TYPES: ty_it_final_result TYPE STANDARD TABLE OF ty_final_result WITH DEFAULT KEY.

    TYPES: BEGIN OF ty_statistic,
             task_number      TYPE i,
             runtime          TYPE int4,
             retries          type i,
             input_data       TYPE lcl_task=>ty_input_data,
             output_part_size TYPE i,
             subrc            TYPE sy-subrc,
             successful       TYPE boolean,
           END OF ty_statistic.

    TYPES: ty_it_statistic TYPE STANDARD TABLE OF ty_statistic WITH DEFAULT KEY.

* RFC-Gruppenname
* die Gruppe mit classname = 'parallel_generators' wird standardmäßig von Admins eingerichtet
* Gruppen sind in Tabelle "RZLLITAB" abgelegt, grouptype = 'S' (Server)
* Transaktion RZ12
    CONSTANTS: co_default_task_group TYPE rzllitab-classname VALUE 'parallel_generators'.

* Factory-Funktion
    CLASS-METHODS:
      get_instance
        IMPORTING
                  i_task_group               TYPE rzllitab-classname DEFAULT co_default_task_group
        RETURNING VALUE(rv_ref_task_manager) TYPE REF TO lcl_task_manager.

* Startet die parallele Abarbeitung der Eingabedaten
    METHODS:
      start_parallel_working
        IMPORTING
                  i_it_input_data         TYPE lcl_task=>ty_it_input_data
                  i_wait_time_sec         TYPE i DEFAULT 10
                  i_max_retries           TYPE i DEFAULT 3
        EXPORTING
                  e_it_final_result       TYPE ty_it_final_result
                  e_it_statistic          TYPE ty_it_statistic
                  e_tasks_started         TYPE i
                  e_tasks_ended           TYPE i
                  e_tasks_missed          TYPE i
        RETURNING VALUE(rv_work_complete) TYPE boolean.

    METHODS:
      get_max_tasks
        RETURNING VALUE(rv_max_tasks) TYPE i.

    METHODS:
      get_free_tasks
        RETURNING VALUE(rv_free_tasks) TYPE i.

  PRIVATE SECTION.

    TYPES: BEGIN OF ty_task,
             task_number TYPE i,
             o_task      TYPE REF TO lcl_task,
           END OF ty_task.

* Null-Referenz
    CONSTANTS: null TYPE REF TO lcl_task_manager VALUE IS INITIAL.

* Referenz auf ein Task-Objekt
    CLASS-DATA: o_task_manager TYPE REF TO lcl_task_manager.

    CLASS-DATA: lv_max_tasks TYPE i.
    CLASS-DATA: lv_free_tasks TYPE i.

* Zähler
    CLASS-DATA: lv_tasks_started TYPE i.
    CLASS-DATA: lv_tasks_ended TYPE i.
    CLASS-DATA: lv_active_tasks TYPE i.

* globale Taskliste
    CLASS-DATA: it_tasks TYPE SORTED TABLE OF ty_task WITH UNIQUE KEY task_number.

* Ergebnisliste
    CLASS-DATA: it_final_result TYPE ty_it_final_result.

* Ergebnisliste
    CLASS-DATA: it_statistic TYPE ty_it_statistic.

* Handler für data_received-Ereignis der Tasks
    METHODS:
      on_task_completed FOR EVENT data_received OF lcl_task
        IMPORTING
            e_subrc
            e_runtime
            e_retries
            e_input_data
            e_output_data
            sender.

* Hilfsfunktionen
    METHODS:
      get_free_task
        RETURNING VALUE(rv_task_number) TYPE i.

    METHODS:
      get_task
        IMPORTING
                  i_task_number      TYPE i
        RETURNING VALUE(rv_ref_task) TYPE REF TO lcl_task.

    METHODS:
      start_task
        IMPORTING
                  i_input_data    TYPE lcl_task=>ty_input_data
                  i_wait_time_sec TYPE i DEFAULT 10
                  i_max_retries   TYPE i DEFAULT 3
        RETURNING VALUE(rv_ok)    TYPE boolean.
ENDCLASS.
*--------------------------------------------------------------------*
CLASS lcl_task_manager IMPLEMENTATION.
*--------------------------------------------------------------------*
* Factory Methode für Singleton (genau eine Referenz) auf den
* Taskmanager zurück
*--------------------------------------------------------------------*
* -> i_task_group        - Name der RFC-Task-Group, in der die Tasks ablaufen
*                          sollen
*                          Vorgabewert ist 'parallel_generators', welche
*                          standardmäßig von Admins eingerichtet wird
*                          Gruppen sind in Tabelle "RZLLITAB" abgelegt,
*                          grouptype = 'S' (Server)
*                          Transaktion RZ12
* <- rv_ref_task_manager - Referenz auf Task-Manager-Objekt
*--------------------------------------------------------------------*
  METHOD get_instance.

    rv_ref_task_manager = null.

    IF o_task_manager IS INITIAL.

      DATA(lv_task_group) = i_task_group.

      lv_max_tasks = 0.
      lv_free_tasks = 0.

      IF lv_task_group IS INITIAL.
* falls keine task group übergeben wurde, eine heraussuchen
* Verfügbare RFC-Gruppen für Parallelverarbeitung: Transaktion RZ12
        SELECT SINGLE classname FROM rzllitab INTO @lv_task_group WHERE grouptype = 'S'.
      ENDIF.

      IF NOT lv_task_group IS INITIAL.

* Start neue LUW
        COMMIT WORK.

* Parallel Background Tasks - Initialisierung der PBT-Umgebung
* Sollte nur 1x aufgerufen werden, sonst Exception "pbt_env_already_initialized"
        CALL FUNCTION 'SPBT_INITIALIZE'
          EXPORTING
            group_name                     = lv_task_group
          IMPORTING
            max_pbt_wps                    = lv_max_tasks
            free_pbt_wps                   = lv_free_tasks
          EXCEPTIONS
            invalid_group_name             = 1
            internal_error                 = 2
            pbt_env_already_initialized    = 3
            currently_no_resources_avail   = 4
            no_pbt_resources_found         = 5
            cant_init_different_pbt_groups = 6
            OTHERS                         = 7.

* wenn Initialisierung ok
        IF sy-subrc = 0.
* wenn genug freie Tasks vorhanden
          IF lv_free_tasks >= 1.

            o_task_manager = NEW #( ).

            lv_tasks_started = 0.
            lv_tasks_ended   = 0.
            lv_active_tasks  = 0.

            CLEAR: it_tasks.

* Anzahl freie Tasks zur Liste hinzufügen
            DO lv_free_tasks TIMES.
              DATA(o_task) = NEW lcl_task( i_task_number = sy-index
                                           i_task_group  = lv_task_group ).

* Eventhandler für beendete Tasks
              SET HANDLER o_task_manager->on_task_completed FOR o_task.

              APPEND VALUE #( task_number = sy-index
                              o_task      = o_task ) TO it_tasks.
            ENDDO.

            rv_ref_task_manager = o_task_manager.
          ENDIF.

        ENDIF.

      ENDIF.

    ELSE.
      rv_ref_task_manager = o_task_manager.
    ENDIF.

  ENDMETHOD.
*--------------------------------------------------------------------*
* Parallele / asynchrone Abarbeitung anschieben
*--------------------------------------------------------------------*
* -> i_it_input_data   - Eingabedaten
* -> i_wait_time_sec   - Wartezeit für Abarbeitung aller Tasks in sec
* -> i_max_retries     - max. Anz. Versuche
* <- e_it_final_result - Erbebnismenge der parallelen Verarbeitung
* <- e_tasks_started   - Anz. gestartete Tasks
* <- e_tasks_ended     - Anz. beendete Tasks (sollte im Idealfall
*                        e_tasks_started entsprechen
* <- e_tasks_missed    - Anz. Tasks, die nicht gestartet werden
*                        konnten
*--------------------------------------------------------------------*
  METHOD start_parallel_working.

    rv_work_complete = abap_false.

    lv_tasks_started = 0.
    lv_tasks_ended   = 0.
    lv_active_tasks  = 0.

    CLEAR: it_final_result.
    CLEAR: it_statistic.

    DATA(lv_tasks_missed) = 0.

* Übergebene Workitems abarbeiten
    LOOP AT i_it_input_data ASSIGNING FIELD-SYMBOL(<fs_input_data>).
* pro Workitem einen Task (asynchron) starten
      IF me->start_task( i_input_data  = <fs_input_data>
                         i_max_retries = i_max_retries ).
      ELSE.
* Fehler: kein Task für Workitem verfügbar
        lv_tasks_missed = lv_tasks_missed + 1.

        APPEND VALUE #( task_number      = -1
                        runtime          = 0
                        retries          = i_max_retries
                        input_data       = <fs_input_data>
                        output_part_size = 0
                        subrc            = -1
                        successful       = abap_false ) TO it_statistic.
      ENDIF.
    ENDLOOP.

* warten bis alle Tasks fertig sind
    WAIT FOR ASYNCHRONOUS TASKS UNTIL ( lv_active_tasks = 0 ) UP TO i_wait_time_sec SECONDS.
    IF sy-subrc = 0.
      rv_work_complete = abap_true.
    ENDIF.

* Ergebnisrückgabe
    e_it_final_result = it_final_result.
    e_it_statistic    = it_statistic.
    e_tasks_started   = lv_tasks_started.
    e_tasks_ended     = lv_tasks_ended.
    e_tasks_missed    = lv_tasks_missed.

  ENDMETHOD.
*--------------------------------------------------------------------*
* Gibt Anzahl max. Tasks
*--------------------------------------------------------------------*
* <- rv_max_tasks - Anzahl max. Tasks
*--------------------------------------------------------------------*
  METHOD get_max_tasks.
    rv_max_tasks = lv_max_tasks.
  ENDMETHOD.
*--------------------------------------------------------------------*
* Gibt Anzahl freie Tasks
*--------------------------------------------------------------------*
* <- rv_free_tasks - Anzahl freie Tasks
*--------------------------------------------------------------------*
  METHOD get_free_tasks.
    rv_free_tasks = lv_free_tasks.
  ENDMETHOD.
*--------------------------------------------------------------------*
* Einen Task starten
*--------------------------------------------------------------------*
* -> i_input_data    - Eingabedaten für die Verarbeitung
* -> i_wait_time_sec - Wartezeit für Abarbeitung aller Tasks in sec
* -> i_max_retries   - max. Anz. Versuche wenn System "busy" bis
*                    - Abbruch erfolgt
* <- rv_ok           - true, wenn Task gestartet werden konnte
*--------------------------------------------------------------------*
  METHOD start_task.

    rv_ok = abap_false.

    DATA(lv_retries) = 0.

* n Neuversuche einen freien Task zu bekommen
    WHILE lv_retries < i_max_retries.
      DATA(lv_tasknumber) = me->get_free_task( ).

      IF lv_tasknumber <> -1.
        DATA(o_task) = me->get_task( lv_tasknumber ).

        IF o_task IS BOUND.

          DATA(lv_subrc) = o_task->start_async_call( i_input_data              = i_input_data
                                                     i_started_after_n_retries = lv_retries ).

          CASE lv_subrc.
            WHEN 0.
* Ok
              lv_tasks_started = lv_tasks_started + 1.
              lv_active_tasks  = lv_active_tasks + 1.
              lv_retries       = i_max_retries.

              rv_ok = abap_true.
            WHEN 1 OR 2.
* Failure
              lv_retries = i_max_retries.
            WHEN 3.
* Busy
              IF lv_tasks_started > 0.
                WAIT FOR ASYNCHRONOUS TASKS UNTIL ( lv_tasks_ended >= lv_tasks_started ) UP TO i_wait_time_sec SECONDS.
              ENDIF.

              IF sy-subrc = 0.
                lv_retries = lv_retries + 1.
              ELSE.
                lv_retries = i_max_retries.
              ENDIF.
            WHEN OTHERS.
* Sollte nicht auftreten
              lv_retries = i_max_retries.
          ENDCASE.
        ELSE.
* Task not found (sollte nicht vorkommen)
          lv_retries = lv_retries + 1.
        ENDIF.
      ELSE.
* kein freier Task verfügbar -> warten
        WAIT FOR ASYNCHRONOUS TASKS UNTIL ( lv_tasks_ended >= lv_tasks_started ) UP TO i_wait_time_sec SECONDS.
        IF sy-subrc = 0.
          lv_retries = lv_retries + 1.
        ELSE.
          lv_retries = i_max_retries.
        ENDIF.
      ENDIF.
    ENDWHILE.

  ENDMETHOD.
*--------------------------------------------------------------------*
* Freien Task aus der Liste der verfügbaren suchen
*--------------------------------------------------------------------*
* <- rv_task_number - Tasknummer des gefundenen Tasks
*--------------------------------------------------------------------*
  METHOD get_free_task.

    rv_task_number = -1.

    LOOP AT it_tasks ASSIGNING FIELD-SYMBOL(<fs_task>).
      IF <fs_task>-o_task->is_running( ) = abap_false.
        rv_task_number = <fs_task>-task_number.
        EXIT.
      ENDIF.
    ENDLOOP.

  ENDMETHOD.
*--------------------------------------------------------------------*
* Referenz auf Task anhand der Tasknummer holen
*--------------------------------------------------------------------*
* -> i_task_number - Tasknummer
* <- rv_ref_task   - Referenz auf Task
*--------------------------------------------------------------------*
  METHOD get_task.
    IF line_exists( it_tasks[ task_number = i_task_number ] ).

      ASSIGN it_tasks[ task_number = i_task_number ] TO FIELD-SYMBOL(<fs_task>).

      IF <fs_task> IS ASSIGNED.
        rv_ref_task = <fs_task>-o_task.
      ENDIF.

    ENDIF.
  ENDMETHOD.
*--------------------------------------------------------------------*
* Eventhandler, wenn Task ferig
*--------------------------------------------------------------------*
* -> e_input_data  - Eingabedaten des Tasks
* -> e_output_data - Ausgabedaten des Tasks
* -> sender        - Referenz auf Task
*--------------------------------------------------------------------*
  METHOD on_task_completed.

* Zähler setzen
    lv_tasks_ended = lv_tasks_ended + 1.
    lv_active_tasks = lv_active_tasks - 1.

    APPEND VALUE #( task_number      = sender->get_task_number( )
                    runtime          = e_runtime
                    retries          = e_retries
                    input_data       = e_input_data
                    output_part_size = lines( e_output_data )
                    subrc            = e_subrc
                    successful       = abap_true ) TO it_statistic.

* Hier Daten an Ergebnistabelle einfach anfügen
    LOOP AT e_output_data ASSIGNING FIELD-SYMBOL(<fs_output_data>).
      APPEND VALUE #( input_data  = e_input_data
                      output_data = <fs_output_data> ) TO it_final_result.
    ENDLOOP.

  ENDMETHOD.
ENDCLASS.

[ABAP] Prüfen, ob ein Funktionsbaustein oder Programm per RFC aufgerufen wurde

DATA: lv_caller_in_same_system TYPE answer.

* RFC-Ausführung im gleichen R/3-System ?
CALL FUNCTION 'RFC_WITHIN_SAME_SYSTEM'
  IMPORTING
    caller_in_same_system     = lv_caller_in_same_system
  EXCEPTIONS
    system_call_not_supported = 1
    no_rfc_communication      = 2
    internal_error            = 3
    OTHERS                    = 4.

WRITE: / 'Same system:', lv_caller_in_same_system.
WRITE: / sy-subrc.

oder

CALL 'RFCControl' ID 'CODE' FIELD 'B'.
IF sy-subrc = 0.
  WRITE:/ 'RFC'.
ENDIF.