[ABAP] Message-Log als Popup: Sammeln und Ausgeben von Nachrichten

*--------------------------------------------------------------------*
* Klasse zur Anzeige eines Nachrichtenprotokolls (Logs)
*
* Kapselt folgende FuBas:
* MESSAGES_INITIALIZE
* MESSAGE_STORE
* MESSAGES_STOP
* MESSAGES_SHOW
*--------------------------------------------------------------------*
CLASS lcl_msg_log DEFINITION.
  PUBLIC SECTION.
    CLASS-METHODS: init
      RETURNING VALUE(rv_ok) TYPE boolean.

    CLASS-METHODS: add
      IMPORTING
                i_arbgb      TYPE smesg-arbgb
                i_msgty      TYPE smesg-msgty
                i_txtnr      TYPE string
                i_zeile      TYPE i DEFAULT 0
                i_msgv1      TYPE string DEFAULT ''
                i_msgv2      TYPE string DEFAULT ''
                i_msgv3      TYPE string DEFAULT ''
                i_msgv4      TYPE string DEFAULT ''
      RETURNING VALUE(rv_ok) TYPE boolean.

    CLASS-METHODS: show
      IMPORTING
                i_show_amodal TYPE boolean DEFAULT abap_false
      RETURNING VALUE(rv_ok)  TYPE boolean.
ENDCLASS.
*--------------------------------------------------------------------*
CLASS lcl_msg_log IMPLEMENTATION.

*--------------------------------------------------------------------*
* Nachrichtenprotokoll initialisieren
*--------------------------------------------------------------------*
  METHOD init.

    rv_ok = abap_false.

* Nachrichtenprotokoll initialisieren
    CALL FUNCTION 'MESSAGES_INITIALIZE'
      EXCEPTIONS
        log_not_active       = 1
        wrong_identification = 2
        OTHERS               = 3.

    IF sy-subrc = 0.
      rv_ok = abap_true.
    ENDIF.

  ENDMETHOD.
*--------------------------------------------------------------------*
* Nachricht im Protokoll ablegen
*--------------------------------------------------------------------*
  METHOD add.

    rv_ok = abap_false.

* Nachricht im Protokoll ablegen
    CALL FUNCTION 'MESSAGE_STORE'
      EXPORTING
        arbgb                  = i_arbgb
        msgty                  = i_msgty
        msgv1                  = i_msgv1
        msgv2                  = i_msgv2
        msgv3                  = i_msgv3
        msgv4                  = i_msgv4
        txtnr                  = i_txtnr
        zeile                  = i_zeile
      EXCEPTIONS
        message_type_not_valid = 1
        not_active             = 2
        OTHERS                 = 3.

    IF sy-subrc = 0.
      rv_ok = abap_true.
    ENDIF.

  ENDMETHOD.
*--------------------------------------------------------------------*
* Nachrichtenprotokoll anzeigen
*--------------------------------------------------------------------*
  METHOD show.

    rv_ok = abap_false.

* Baustein analysiert Fehlerprotokoll und gibt als Status eine Exception aus
    CALL FUNCTION 'MESSAGES_STOP'
      EXCEPTIONS
        a_message         = 1
        e_message         = 2
        w_message         = 3
        i_message         = 4
        s_message         = 5
        deactivated_by_md = 6
        OTHERS            = 7.

* Nachrichtenprotokoll anzeigen
    CALL FUNCTION 'MESSAGES_SHOW'
      EXPORTING
        i_use_grid         = abap_true
        i_amodal_window    = i_show_amodal
      EXCEPTIONS
        inconsistent_range = 1
        no_messages        = 2
        OTHERS             = 3.

    IF sy-subrc = 0.
      rv_ok = abap_true.
    ENDIF.

  ENDMETHOD.
ENDCLASS.

START-OF-SELECTION.

  IF abap_true = lcl_msg_log=>init( ).

    IF abap_true = lcl_msg_log=>add( i_arbgb = 'SY'
                                     i_msgty = 'E'
                                     i_txtnr = '002'
                                     i_zeile = 0
                                     i_msgv1 = 'Fehler.' ).

      IF abap_true = lcl_msg_log=>add( i_arbgb = 'SY'
                                       i_msgty = 'W'
                                       i_txtnr = '002'
                                       i_zeile = 1
                                       i_msgv1 = 'Warnung' ).

        IF abap_true = lcl_msg_log=>add( i_arbgb = 'SY'
                                         i_msgty = 'I'
                                         i_txtnr = '002'
                                         i_zeile = 2
                                         i_msgv1 = 'Info' ).

          IF abap_true = lcl_msg_log=>show( ).

          ENDIF.
        ENDIF.
      ENDIF.
    ENDIF.
  ENDIF.

[ABAP] MM: MIGO_DIALOG – Transaktion MIGO anzeigen

*i_action - Ausführbare Aktion in Transaktion MIGO
*
*Domäne: GOACTION
*
*A01  Wareneingang
*A02  Rücklieferung
*A03  Storno
*A04  Anzeigen
*A05  WE-Sperrb. freigeben
*A06  Nachlieferung
*A07  Warenausgang
*A08  Umbuchung
*A09  Auslagern
*A10  Einlagern
*A11  Nachverrechnung
*A12  Ändern

*i_refdoc - Referenzdokument Transaktion MIGO
*
*Domäne: REFDOC
*
*R01  Bestellung
*R02  Materialbeleg
*R03  Lieferschein
*R04  Anlieferung
*R05  Auslieferung
*R06  Transport
*R07  Transportidentifikation
*R08  Auftrag
*R09  Reservierung
*R10  Sonstige

* Materialbelegnummer
DATA(lv_mblnr) = CONV mblnr( '123456' ).

CALL FUNCTION 'MIGO_DIALOG'
  EXPORTING
    i_action            = 'A04'      " Anzeigen
    i_refdoc            = 'R02'      " Materialbeleg
    i_notree            = abap_false " Baumanzeige ein
    i_no_auth_check     = abap_true  " kein Authority Check
    i_deadend           = abap_false " Listboxen und Menü sind aktiv
    i_mblnr             = lv_mblnr   " Belegnummer
  EXCEPTIONS
    illegal_combination = 1
    OTHERS              = 2.

IF sy-subrc <> 0.
  MESSAGE 'Fehler beim Aufruf der Transaktion MIGO.' TYPE 'S' DISPLAY LIKE 'E'.
ENDIF.

[ABAP] MM: BAPI_MATERIAL_SAVEDATA – Anlegen und Ändern von Materialstammdaten

TYPES: ty_it_materialdescription TYPE STANDARD TABLE OF bapi_makt WITH DEFAULT KEY.

* Kopfdaten
DATA(lv_headdata) = VALUE bapimathead( material      = '123456'             " MATNR
                                       ind_sector    = CONV mbrsh( 'A' )    " Branche (Tabelle T137)
                                       matl_type     = CONV mtart( 'VERP' ) " Materialart (Tabelle T134)

                                       basic_view    = abap_true            " Sicht Grundddaten anlegen
                                       purchase_view = abap_true            " Sicht Einkauf anlegen
                                       account_view  = abap_true            " Sicht Buchhaltung
                                     ).
* Mandantenebene (MARA)
* Daten
DATA(lv_clientdata) = VALUE bapi_mara( del_flag   = abap_false               " Löschvormerkung auf Mandantenebene
                                       matl_group = CONV matkl( '00100000' ) " Warengruppe (Tabelle T023)
                                       base_uom   = CONV meins( 'KG' )       " Basismengeneinheit (Tabelle T006)
                                     ).

* Speicherflags
DATA(lv_clientdatax) = VALUE bapi_marax( del_flag   = abap_true " Löschvormerkung auf Mandantenebene setzen
                                         matl_group = abap_true " Warengruppe setzen
                                         base_uom   = abap_true " Basismengeneinheit setzen
                                       ).
* Werksebene (MARC)
* Daten
DATA(lv_plantdata) = VALUE bapi_marc( plant     = CONV werks_d( '0001' ) " Werk (Tabelle T001W)
                                      del_flag  = abap_false             " Löschvormerkung auf Werksebene
                                      pur_group = CONV ekgrp( '001' )    " Einkäufergruppe (Tabelle T024)
                                    ).

* Speicherflags
DATA(lv_plantdatax) = VALUE bapi_marcx( plant     = CONV werks_d( '0001' ) " Werk (Tabelle T001W)
                                        del_flag  = abap_true              " Löschvormerkung auf Werksebene setzen
                                        pur_group = abap_true              " Einkäufergruppe setzen
                                      ).

* Materialbewertung (MBEW)
* Daten
DATA(lv_valuationdata) = VALUE bapi_mbew( val_area  = CONV bwkey( '0001' )  " Bewertungskreis (Tabelle T001K)
                                          val_type  = CONV bwtar_d( 'ABC' ) " Bewertungsarten (Tabelle T149D)
                                          val_class = CONV bklas( '1234' )  " Bewertungsklasse (Tabelle T025)
                                          del_flag  = abap_false            " Löschvormerkung für alle Materialdaten einer Bewertungsart
                                        ).

* Speicherflags
DATA(lv_valuationdatax) = VALUE bapi_mbewx( val_area = CONV bwkey( '0001' )  " Bewertungskreis (Tabelle T001K)
                                            val_type = CONV bwtar_d( 'ABC' ) " Bewertungsarten (Tabelle T149D)
                                            val_class = abap_true            " Bewertungsklasse setzen
                                            del_flag  = abap_true            " Löschvormerkung setzen
                                          ).

* Lagerort (MARD)
* Daten
DATA(lv_storagelocationdata) = VALUE bapi_mard( plant    = CONV werks_d( '0001' ) " Werk (Tabelle T001W)
                                                stge_loc = CONV lgort_d( '1000' ) " Lagerort (Tabelle T001L)
                                                del_flag = abap_false             " Material auf Lagerortebene zum Löschen vormerken
                                              ).

* Speicherflags
DATA(lv_storagelocationdatax) = VALUE bapi_mardx( plant    = CONV werks_d( '0001' ) " Werk (Tabelle T001W)
                                                  stge_loc = CONV lgort_d( '1000' ) " Lagerort (Tabelle T001L)
                                                  del_flag = abap_false             " Löschvormerkung setzen
                                                ).

* Materialkurztext
DATA(it_materialdescription) = VALUE ty_it_materialdescription( ( langu     = sy-langu
                                                                  langu_iso = cl_i18n_languages=>sap1_to_sap2( im_lang_sap1 = sy-langu )
                                                                  matl_desc = 'Schraube 70mm' ) ).

DATA: lv_return TYPE bapiret2.
DATA: it_returnmessages TYPE STANDARD TABLE OF bapi_matreturn2 WITH DEFAULT KEY.

CALL FUNCTION 'BAPI_MATERIAL_SAVEDATA'
  EXPORTING
    headdata             = lv_headdata
    clientdata           = lv_clientdata
    clientdatax          = lv_clientdatax
    plantdata            = lv_plantdata
    plantdatax           = lv_plantdatax
    valuationdata        = lv_valuationdata
    valuationdatax       = lv_valuationdatax
    storagelocationdata  = lv_storagelocationdata
    storagelocationdatax = lv_storagelocationdatax
  IMPORTING
    return               = lv_return
  TABLES
    materialdescription  = it_materialdescription
    returnmessages       = it_returnmessages.

* Meldungen des Bausteins ausgeben
LOOP AT it_returnmessages ASSIGNING FIELD-SYMBOL(<m>).
  WRITE: / <m>-type,
           <m>-id,
           <m>-number,
           <m>-message,
           <m>-log_no,
           <m>-log_msg_no,
           <m>-message_v1,
           <m>-message_v2,
           <m>-message_v3,
           <m>-message_v4,
           <m>-parameter,
           <m>-row,
           <m>-field,
           <m>-system.
ENDLOOP.

* Fehlerauswertung
* Meldungstyp auswerten, Baustein gibt nur S - Success oder E - Error zurück
IF lv_return-type = 'E'.
* Fehler -> ROLLBACK
  CALL FUNCTION 'BAPI_TRANSACTION_ROLLBACK'
    IMPORTING
      return = lv_return.

  WRITE: / lv_return-type, lv_return-id, lv_return-message.
ELSE.
* kein Fehler -> COMMIT
  CALL FUNCTION 'BAPI_TRANSACTION_COMMIT'
*      EXPORTING
*       wait   = abap_true
    IMPORTING
      return = lv_return.

  WRITE: / lv_return-type, lv_return-id, lv_return-message.
ENDIF.

[ABAP] Konvertierung einstelliges SAP-Sprachenkürzel <-> zweistellige ISO-Sprache

sy-langu -> ISO

* Variante 1 (CONVERSION_EXIT_ISOLA_OUTPUT)
DATA: lv_iso_lang TYPE laiso.

CALL FUNCTION 'CONVERSION_EXIT_ISOLA_OUTPUT'
  EXPORTING
    input  = sy-langu
  IMPORTING
    output = lv_iso_lang.

WRITE: / |{ sy-langu } -> { lv_iso_lang }|.

* Variante 2 (cl_i18n_languages)
DATA(lv_iso_lang) = cl_i18n_languages=>sap1_to_sap2( im_lang_sap1 = sy-langu ).
WRITE: / lv_iso_lang.

ISO -> sy-langu

* Variante 1 (CONVERSION_EXIT_ISOLA_INPUT)
DATA: lv_iso_lang TYPE laiso VALUE 'DE'.
DATA: lv_lang_r3 TYPE sy-langu.

CALL FUNCTION 'CONVERSION_EXIT_ISOLA_INPUT'
  EXPORTING
    input  = lv_iso_lang
  IMPORTING
    output = lv_lang_r3.

WRITE: / |{ lv_iso_lang } -> { lv_lang_r3 }|.

* Variante 2 (cl_i18n_languages)
DATA(lv_lang_r3) = cl_i18n_languages=>sap2_to_sap1( im_lang_sap2 = 'DE' ).
WRITE: / |{ lv_lang_r3 }|.

[ABAP] MM: Materialdaten lesen

DATA: lv_matnr TYPE bapi_mara_ga-material VALUE '000000001234567890'.
DATA: lv_clientdata TYPE bapi_mara_ga.

DATA: it_materialdescription TYPE STANDARD TABLE OF bapi_makt_ga WITH DEFAULT KEY.
DATA: it_return TYPE STANDARD TABLE OF bapireturn WITH DEFAULT KEY.

CALL FUNCTION 'BAPI_MATERIAL_GET_ALL'
  EXPORTING
    material            = lv_matnr
*   COMP_CODE           =
*   VAL_AREA            =
*   VAL_TYPE            =
*   PLANT               =
*   STGE_LOC            =
*   SALESORG            =
*   DISTR_CHAN          =
*   WHSENUMBER          =
*   STGE_TYPE           =
*   LIFO_VALUATION_LEVEL =
*   KZRFB_ALL           =
  IMPORTING
    clientdata          = lv_clientdata
*   PLANTDATA           =
*   FORECASTPARAMETERS  =
*   PLANNINGDATA        =
*   STORAGELOCATIONDATA =
*   VALUATIONDATA       =
*   WAREHOUSENUMBERDATA =
*   SALESDATA           =
*   STORAGETYPEDATA     =
*   PRTDATA             =
*   LIFOVALUATIONDATA   =
  TABLES
    materialdescription = it_materialdescription
*   UNITSOFMEASURE      =
*   INTERNATIONALARTNOS =
*   MATERIALLONGTEXT    =
*   TAXCLASSIFICATIONS  =
*   EXTENSIONOUT        =
    return              = it_return.

* Fehlermeldungen ausgeben
LOOP AT it_return ASSIGNING FIELD-SYMBOL(<r>).
  WRITE: / <r>-type, <r>-code, <r>-message.
ENDLOOP.

* Materialdaten (MARA)
WRITE: / lv_clientdata-material,
         lv_clientdata-matl_type,
         lv_clientdata-mat_status,
         lv_clientdata-del_flag,
         lv_clientdata-matl_group,
         lv_clientdata-base_uom.

* Materialkurztexte (MAKT)
LOOP AT it_materialdescription ASSIGNING FIELD-SYMBOL(<d>).
  WRITE: / <d>-langu, <d>-langu_iso, <d>-matl_desc.
ENDLOOP.

[ABAP] Nummernkreis: Nächste freie Nummer vergeben (Beispiel: BANF)

* http://www.kodyaz.com/articles/create-number-range-in-abap-using-sap-snro-transaction-code.aspx
* https://www.tapatalk.com/groups/abapteamforum/function-module-to-get-next-number-in-number-range-t4162861.html
* http://www.erpworkbench.com/sap-tcodes/snro.htm
* https://www.berater-wiki.de/Access_Tabellenbeziehung_Nummernkreise

* Transaktion: SNRO
* Tabellen: TNRO, TNROT, NRIV, T161

* Nummernkreisobjekte aus Tabelle TNRO, z.B. BANF, ADRNR
PARAMETERS: p_object TYPE tnro-object DEFAULT 'BANF'.
* Einkaufsbelegart für Ermittlung des zugewiesenen Nummernkreisintervalls
PARAMETERS: p_bsart TYPE t161-bsart DEFAULT 'BA'.

* Definition der Nummernkreisobjekte prüfen, siehe Transaktion SNRO
SELECT SINGLE tnro~object,
              tnro~domlen,
              tnro~buffer,
              tnro~noivbuffer,
              tnrot~txtshort
  INTO @DATA(lv_tnro)
  FROM tnro
  INNER JOIN tnrot ON tnro~object = tnrot~object
  WHERE tnro~object = @p_object
    AND tnrot~langu = @sy-langu.

IF sy-subrc = 0.

  WRITE: / 'Objekt:', lv_tnro-object.
  WRITE: / 'Kurztext:', lv_tnro-txtshort.
  WRITE: / 'Domäne:', lv_tnro-domlen.
  WRITE: / 'Buffer:', lv_tnro-buffer.
  WRITE: / 'Buffer-Size:', lv_tnro-noivbuffer.

* internes Nummernkreisintervall für BANF-Belegart holen
* siehe auch Tabelle NRIV
  SELECT SINGLE *
    FROM t161
    INTO @DATA(lv_t161)
    WHERE bstyp = 'B'
    AND bsart = @p_bsart.

  IF sy-subrc = 0.

    WRITE: / 'BSTYP:', lv_t161-bstyp.
    WRITE: / 'BSART:', lv_t161-bsart.
    WRITE: / 'Nummernkreis:', lv_t161-numki.

* Nummernkreis-Objekt sperren
    CALL FUNCTION 'NUMBER_RANGE_ENQUEUE'
      EXPORTING
        object           = p_object
      EXCEPTIONS
        foreign_lock     = 1
        object_not_found = 2
        system_failure   = 3
        OTHERS           = 4.

    IF sy-subrc = 0.

      WRITE: / |Objekt gesperrt: { p_object }|.

* Rückgabewert
      DATA: lv_number TYPE string.
* Fehlercode
      DATA: lv_rc TYPE inri-returncode.

* Nummernkreis: Nächste freie Nummer vergeben
      CALL FUNCTION 'NUMBER_GET_NEXT'
        EXPORTING
          nr_range_nr             = lv_t161-numki   " Intervall
          object                  = p_object        " Objekt (SNRO)
        IMPORTING
          number                  = lv_number       " generierte Nummer
          returncode              = lv_rc
        EXCEPTIONS
          interval_not_found      = 1
          number_range_not_intern = 2
          object_not_found        = 3
          quantity_is_0           = 4
          quantity_is_not_1       = 5
          interval_overflow       = 6
          buffer_overflow         = 7
          OTHERS                  = 8.

* Returncode, siehe INCLUDE LSNR3F01
      CASE lv_rc.
        WHEN ' '.
          WRITE: / 'Alles o.k.'.
        WHEN '1'.
          WRITE: / 'Die vergebene Nummer liegt im kritischen Bereich.'.
        WHEN '2'.
          WRITE: / 'Dies war die letzte Nummer, beim nächsten Mal wird die erste Nummer vergeben'.
        WHEN '3'.
          WRITE: / 'Es werden mehr Nummern verlangt als zur Verfügung stehen, in QUAN steht die Anzahl der vergebenen Nummern.'.
      ENDCASE.

      IF sy-subrc = 0.
        WRITE: / 'Objekt:', p_object.
        WRITE: / 'generierte Nummer:', lv_number.
      ENDIF.

* Nummernkreis-Objekt entsperren
      CALL FUNCTION 'NUMBER_RANGE_DEQUEUE'
        EXPORTING
          object           = p_object
        EXCEPTIONS
          object_not_found = 1
          OTHERS           = 2.

      IF sy-subrc = 0.
        WRITE: / |Objekt entsperrt: { p_object }|.
      ELSE.
        WRITE: / |Objekt konnte nicht entsperrt werden: { p_object }|.
      ENDIF.
    ELSE.
      WRITE: / |Objekt konnte nicht gesperrt werden: { p_object }|.
    ENDIF.
  ELSE.
    WRITE: / 'Kein internes Nummernkreisintervall für BANF-Belegart:', p_bsart.
  ENDIF.
ELSE.
  WRITE: / |Objekt in Tabelle TNRO nicht vorhanden: { p_object }|.
ENDIF.

[ABAP] Anzahl Aufrufe von Transaktionen und Reports pro User ausgeben

Variante 1 (User, Typ, Aufrufe)

PARAMETERS: p_month TYPE dats OBLIGATORY DEFAULT sy-datum.

TYPES: BEGIN OF ty_result,
         user      TYPE sy-uname,
         operation TYPE char30,
         type      TYPE char10,
         count     TYPE swncshcnt,
       END OF ty_result.

DATA: it_result TYPE STANDARD TABLE OF ty_result WITH DEFAULT KEY.

START-OF-SELECTION.

  DATA: it_autc TYPE swnc_t_aggusertcode.

* Daten für den kompletten Monat holen
  CALL FUNCTION 'SWNC_COLLECTOR_GET_AGGREGATES'
    EXPORTING
      component     = 'TOTAL'
      periodtype    = 'M'
      periodstrt    = p_month
    TABLES
      usertcode     = it_autc
    EXCEPTIONS
      no_data_found = 1
      OTHERS        = 2.

  IF sy-subrc = 0.
* nur Tasktyp '01'
    DELETE it_autc WHERE tasktype <> '01'.

* Daten zusammenfügen
    LOOP AT it_autc ASSIGNING FIELD-SYMBOL(<u>).
      COLLECT VALUE ty_result( user      = <u>-account
                               operation = <u>-entry_id
                               type      = <u>-entry_id+72
                               count     = <u>-count ) INTO it_result.
    ENDLOOP.

* Nach User, Typ und Aufrufen sortieren
    SORT: it_result BY user type count DESCENDING.

* Daten ausgeben
    LOOP AT it_result ASSIGNING FIELD-SYMBOL(<group>) GROUP BY <group>-user ASCENDING.
      WRITE: / <group>-user.

      LOOP AT GROUP <group> ASSIGNING FIELD-SYMBOL(<u2>).
        WRITE: / <u2>-type, <u2>-operation, <u2>-count.
      ENDLOOP.

      ULINE.
    ENDLOOP.
  ENDIF.

Aufrufe Transaktionscodes pro User ermitteln

PARAMETERS: p_month TYPE swncdatum DEFAULT sy-datum.

START-OF-SELECTION.

* component: TOTAL oder Hostname
  DATA: lv_swnchostname TYPE swnchostname VALUE 'TOTAL'.
* periodtype: M - first day of month, W - first day of week
  DATA: lv_swncperitype TYPE swncperitype VALUE 'M'.
  DATA: it_swncaggusertcode TYPE STANDARD TABLE OF swncaggusertcode WITH DEFAULT KEY.

* Aggregate aus der Datenbank lesen
  CALL FUNCTION 'SWNC_COLLECTOR_GET_AGGREGATES'
    EXPORTING
      component     = lv_swnchostname
*     ASSIGNDSYS    = SY-SYSID
      periodtype    = lv_swncperitype
      periodstrt    = p_month
    TABLES
*     TASKTYPE      =
*     TASKTIMES     =
*     TIMES         =
*     DBPROCS       =
*     EXTSYSTEM     =
*     TCDET         =
*     FRONTEND      =
*     MEMORY        =
*     SPOOLACT      =
*     TABLEREC      =
      usertcode     = it_swncaggusertcode
*     USERWORKLOAD  =
*     RFCCLNT       =
*     RFCCLNTDEST   =
*     RFCSRVR       =
*     RFCSRVRDEST   =
*     SPOOL         =
*     HITLIST_DATABASE       =
*     HITLIST_RESPTIME       =
*     ASTAT         =
*     ASHITL_DATABASE        =
*     ASHITL_RESPTIME        =
*     COMP_HIERARCHY         =
*     ORG_UNITS     =
*     DBCON         =
*     VMC           =
*     WEBSD         =
*     WEBCD         =
*     WEBS          =
*     WEBC          =
*     TREX          =
    EXCEPTIONS
      no_data_found = 1
      OTHERS        = 2.

  IF sy-subrc = 0.
    cl_demo_output=>write_data( it_swncaggusertcode ).
* HTML-Daten aus itab generieren
    DATA(lv_html) = cl_demo_output=>get( ).

* Daten im Inline-Browser im SAP-Fenster anzeigen
    cl_abap_browser=>show_html( EXPORTING
                                  title        = 'TCodes'
                                  html_string  = lv_html
                                  container    = cl_gui_container=>default_screen ).

* cl_gui_container=>default_screen erzwingen
    WRITE: space.
  ENDIF.

[ABAP] Infos zu Funktionsbausteinen auflisten

* Funktionsbausteine, die mit 'BAPI_MAT' beginnen
DATA(lv_fb_name) = |BAPI_MAT%|.

* Funktionsbaustein
DATA: it_tfdir TYPE STANDARD TABLE OF tfdir WITH DEFAULT KEY.

SELECT * FROM tfdir INTO TABLE @it_tfdir WHERE funcname LIKE @lv_fb_name.

LOOP AT it_tfdir ASSIGNING FIELD-SYMBOL(<fs_tfdir>).
  WRITE: / <fs_tfdir>-funcname, <fs_tfdir>-freedate, <fs_tfdir>-appl.
ENDLOOP.

ULINE.

* Kurztext eines Funktionsbausteins
DATA: it_tftit TYPE STANDARD TABLE OF tftit WITH DEFAULT KEY.

SELECT * FROM tftit INTO TABLE @it_tftit WHERE funcname LIKE @lv_fb_name.

LOOP AT it_tftit ASSIGNING FIELD-SYMBOL(<fs_tftit>).
  WRITE: / <fs_tftit>-funcname, <fs_tftit>-spras, <fs_tftit>-stext.
ENDLOOP.

ULINE.

* Verzeichnis freigegebener Objekte
DATA: it_rodir TYPE STANDARD TABLE OF rodir WITH DEFAULT KEY.

SELECT * FROM rodir INTO TABLE @it_rodir WHERE object LIKE @lv_fb_name.

LOOP AT it_rodir ASSIGNING FIELD-SYMBOL(<fs_rodir>).
  WRITE: / <fs_rodir>-object, <fs_rodir>-objecttype, <fs_rodir>-released, <fs_rodir>-reworked, <fs_rodir>-obsolete.
ENDLOOP.

[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.