Quantcast
Channel: Programmierung – Tricktresor
Viewing all 166 articles
Browse latest View live

Popup nach verbuchten Daten

$
0
0

Trotz UI5, Webservices und HANA sind Verbuchungsbausteine nach wie vor ein wichtiger Bestandteil eines SAP-Systems. Ein Verbuchungsbaustein ist ein Funktionsbaustein mit der Eigenschaft „Verbucher“.

Eigenschaften eines Funktionsbausteins

Ein Verbuchungsbaustein kann wie ein ganz normaler Funktionsbaustein aufgerufen werden. Allerdings kann ein Verbuchungsbaustein keine Rückgabeparameter besitzen, denn mit Aufruf in der Verbuchung – Zusatz IN UPATE TASK – wird die Ausführung an einen anderen Prozess übergeben. Läuft bei einem Verbuchungsbaustein etwas schief, dann gibt es einen Kurzdump.

Verbucher

Die Verbucherfunktionalität wird in erster Linie dafür genutzt, um das aufwändige Speichern im Hintergrund durchführen zu lassen, so dass der Anwender schnell weiter arbeiten kann. Die Daten, die an die Verbuchung übergeben werden, müssen „wasserdicht“, also in sich stimmig und korrekt sein. Es geht also nicht mehr um Prüfungen, sondern um das reine Speichern der Daten. Je nach Prozess kann die Speicherung ziemlich lange dauern. In SAP-Standardanwendungen hat man manchmal das Phänomen, dass man einen Beleg speichert und ihn sofort wieder ändern möchte. Der Beleg befindet sich jedoch noch in der Verbuchung und ist noch gesperrt. 

V1- und V2-Verbucher

Es gibt V1-Verbucher und V2-Verbucher. Die V1-Verbucher enthalten wichtige Daten, die in jedem Fall verbucht werden müssen und die auch schnellstmöglich verbucht werden sollen. V2-Verbucher werden eingesetzt für zum Beispiel das Fortschreiben von Statistikdaten. Wenn die Statistikdaten nicht vollständig sind, ist es nicht so schlimm. Es rechtfertigt zum Beispiel in keinem Fall, dass der zugehörige Beleg deswegen nicht verbucht werden kann.

V1-Verbucher haben Vorrang vor V2-Verbuchern. Ein V2-Verbucher wird also erst dann ausgeführt, wenn alle zugehörigen V1-Verbucher erfolgreich verbucht worden sind. Bricht ein V1-Prozess ab, dann werden alle zugehörigen Bausteine, die sich in der Verbuchung befinden, ebenfalls abgebrochen.

V2-Verbucher missbrauchen

Bei einigen Prozessen kann das Verbuchen der Daten extrem lange dauern und deswegen sinnvoll sein, den Benutzer nach Beendigung des Buchungsprozesses zu informieren. 

Hierfür kann man den V2-Verbucher sozusagen missbrauchen. Man startet einfach die notwendigen V1-Verbucher und registriert am Ende noch einen V2-Verbucher, der mit Hilfe des Baustein TH_POPUP eine Meldung ausgibt. 

TH_POPUP

Der Funktionsbaustein TH_POPUP ist ein kleines Systempopup, dass einen Benutzer sofort informiert. Der Benutzer muss dafür im SAP-System angemeldet sein.

TH_POPUP in Aktion

Du kannst also diesen TH_POPUP in einem V2-Verbuchungsbaustein aufrufen:

  CALL FUNCTION 'TH_POPUP'
    EXPORTING
      client  = sy-mandt
      user    = sy-uname
      message = 'Verbuchung beendet!'.

Durch die Reihenfolge ist sichergestellt, dass das Popup erst aufgerufen wird, nachdem alle V1-Verbucher, also der Verbuchungsprozess ansich, beendet wurde.

Aufruf

Um die Verbuchung anzustoßen, muss zwingend ein COMMIT WORK erfolgen. Der implizit am Ende eines Programms ausgeführte Commit ist nicht ausreichend!

  CALL FUNCTION 'Z_TEST_BOOK' IN UPDATE TASK.
  CALL FUNCTION 'Z_TEST_BOOK_POPUP' IN UPDATE TASK.
  COMMIT WORK.

Der Beitrag Popup nach verbuchten Daten erschien zuerst auf Tricktresor.


OLE-Handle zu DOI-Objekt ermitteln

$
0
0

Für die Integration von Word und Excel gibt es nur zwei Möglichkeiten:

  • OLE – Object Linking and Embedding
  • DOI – Desktop Office Integration

Beide Varianten haben ihre Vor- und Nachteile. Ein Nachteil bei der Verwendung von DOI ist auf jeden Fall, dass die Methode CLOSE_DOCUMENT nicht zuverlässig funktioniert, wenn man Word oder Excel in einem separaten Fenster öffnet.

Beispiel

Zuerst jedoch ein Beispielcode, der zeigt, wie mittels DOI eine Excel-Instanz gestartet wird. Die Drucktaste „Create“ startet Excel.

Desktop Office Integration hat Excel gestartet

Die Schaltfläche „Create“ wird dann ausgeschaltet und die Schaltflächen „Destroy with „Close_Document“ und „Destroy with OLE Quit“ erscheinen. Mit diesen Drucktaste kann Excel wieder geschlossen werden. Das Beenden von Excel/ Word geschieht in der Regel über document->close_document und document->close_activex_document. Falls dies jedoch aus unerfindlichen Gründen nicht funktioniert, hilft vielleicht die Methode über das OLE-Objekt und die Methode „Quit“.

Code

REPORT LINE-SIZE 200.


"Create Control
SELECTION-SCREEN PUSHBUTTON /1(30) TEXT-cre USER-COMMAND create      MODIF ID cre.
"Destroy Control with Close_Document
SELECTION-SCREEN PUSHBUTTON /1(30) TEXT-dst USER-COMMAND destroy     MODIF ID dst.
"Destrpy Control with OLE and method Quit
SELECTION-SCREEN PUSHBUTTON /1(30) TEXT-dso USER-COMMAND destroy_ole MODIF ID dst.


CLASS demo DEFINITION.
  PUBLIC SECTION.
    METHODS create.
    METHODS destroy.
    METHODS destroy_with_ole.
    METHODS is_destroyed RETURNING VALUE(result) TYPE i.
  PROTECTED SECTION.

    DATA mr_control       TYPE REF TO i_oi_container_control." OIContainerCtrl
    DATA mr_document      TYPE REF TO i_oi_document_proxy.   " Office Dokument
    DATA mr_spreadsheet   TYPE REF TO i_oi_spreadsheet.      " Spreadsheet

    METHODS set_data.
    METHODS create_application.

ENDCLASS.


INITIALIZATION.
  DATA(go_demo) = NEW demo( ).

AT SELECTION-SCREEN.
  CASE sy-ucomm.
    WHEN 'CREATE'.
      go_demo->create( ).
    WHEN 'DESTROY'.
      go_demo->destroy( ).
    WHEN 'DESTROY_OLE'.
      go_demo->destroy_with_ole( ).
  ENDCASE.

AT SELECTION-SCREEN OUTPUT.

  LOOP AT SCREEN.
    CASE screen-group1.
      WHEN 'DST'.
        IF go_demo->is_destroyed( ) = 0.
          screen-active = '1'.
        ELSE.
          screen-active = '0'.
        ENDIF.

      WHEN 'CRE'.
        IF go_demo->is_destroyed( ) = 0.
          screen-active = '0'.
        ELSE.
          screen-active = '1'.
        ENDIF.
    ENDCASE.
    MODIFY SCREEN.
  ENDLOOP.

CLASS demo IMPLEMENTATION.


  METHOD create.

    create_application( ).
    set_data( ).

  ENDMETHOD.

  METHOD is_destroyed.

    IF mr_document IS BOUND.
      mr_document->is_destroyed(
        IMPORTING
          ret_value = result
      ).
    ELSE.
      result = 1.
    ENDIF.

  ENDMETHOD.

  METHOD create_application.

    DATA lr_error         TYPE REF TO i_oi_error.

    c_oi_container_control_creator=>get_container_control(
        IMPORTING
          control = mr_control
          error   = lr_error ).

** init control
    mr_control->init_control(
      EXPORTING
        inplace_enabled       = abap_false
        no_flush              = 'X'
        r3_application_name   = 'Test DOI'
        inplace_show_toolbars = abap_false
        parent                = cl_gui_container=>screen9
      IMPORTING
        error                 = lr_error
      EXCEPTIONS
        OTHERS                = 2 ).

    IF lr_error->has_failed = abap_true. lr_error->raise_message( 'I' ). RETURN. ENDIF.

*** Get Documentproxy
    CALL METHOD mr_control->get_document_proxy
      EXPORTING
        document_type  = soi_doctype_excel_sheet "'Excel.Sheet'
        no_flush       = 'X'
      IMPORTING
        document_proxy = mr_document
        error          = lr_error.
    IF lr_error->has_failed = abap_true. lr_error->raise_message( 'I' ). RETURN. ENDIF.

    mr_document->create_document(
      EXPORTING
       document_title   = 'Demo-Arbeitsblatt'
        no_flush         = 'X'
        open_inplace     = abap_false
      IMPORTING
       error = lr_error ).
    IF lr_error->has_failed = abap_true. lr_error->raise_message( 'I' ). RETURN. ENDIF.

    CALL METHOD mr_document->get_spreadsheet_interface
      IMPORTING
        sheet_interface = mr_spreadsheet
        error           = lr_error.
    IF lr_error->has_failed = abap_true. lr_error->raise_message( 'I' ). RETURN. ENDIF.

  ENDMETHOD.

  METHOD set_data.

    DATA lt_values        TYPE soi_generic_table.
    DATA lt_ranges        TYPE soi_range_list.
    DATA ls_range         LIKE LINE OF lt_ranges.
    DATA lr_error         TYPE REF TO i_oi_error.


    "Demo-Daten
    lt_values = VALUE #(
        ( row = 1 column = 1 value = '1' )
        ( row = 2 column = 1 value = '2' )
        ( row = 3 column = 1 value = '3' ) ) .

*== Neuen Bereich definieren
    mr_spreadsheet->insert_range_dim(
       EXPORTING name = 'myarea'
                 top       = 1
                 left      = 1
                 rows      = lines( lt_values )
                 columns   = 1
                 no_flush  = abap_false ).
    ls_range-columns = 1.
    ls_range-rows    = lines( lt_values ).
    ls_range-name    = 'myarea'.
    APPEND ls_range TO lt_ranges.

    "Daten übergeben
    mr_spreadsheet->set_ranges_data(
      EXPORTING
        ranges    = lt_ranges
        contents  = lt_values
      IMPORTING
        error     = lr_error
    ).
    IF lr_error->has_failed = abap_true. lr_error->raise_message( 'I' ). RETURN. ENDIF.

  ENDMETHOD.

  METHOD destroy.

    mr_document->close_document( ).
    mr_document->close_activex_document( ).
    FREE mr_document.

    mr_control->destroy_control( ).
    FREE mr_control.

  ENDMETHOD.

  METHOD destroy_with_ole.

    DATA lv_document_cntl_handle TYPE cntl_handle.
    DATA lv_application          TYPE ole2_object.
    DATA lv_oi_ret               TYPE soi_ret_string.
    DATA lr_error                TYPE REF TO i_oi_error.

    mr_document->get_document_handle(
      IMPORTING
        handle  = lv_document_cntl_handle
        retcode = lv_oi_ret ).
    GET PROPERTY OF lv_document_cntl_handle-obj 'Application' = lv_application.
    CALL METHOD OF lv_application 'Quit'.
    FREE OBJECT lv_application.
    FREE mr_document.

  ENDMETHOD.

ENDCLASS.

OLE-Objekt zu DOI-Dokument erhalten

Wenn die Methode close_document aus irgendwelchen Gründen nicht funktioniert, das Excel-Fenster also nicht geschlossen wird, oder du eine Methode anwenden möchtest, die das DOI-Interface nicht unterstützt, dann kannst du dir den OLE-Handle zur Applikation besorgen:

    DATA lv_document_cntl_handle TYPE cntl_handle.
    DATA lv_application          TYPE ole2_object.
    DATA lv_oi_ret               TYPE soi_ret_string.
    DATA lr_error                TYPE REF TO i_oi_error.

    mr_document->get_document_handle(
      IMPORTING
        handle  = lv_document_cntl_handle
        retcode = lv_oi_ret ).
    GET PROPERTY OF lv_document_cntl_handle-obj 'Application' = lv_application.
    CALL METHOD OF lv_application 'Quit'.
    FREE OBJECT lv_application.
    FREE mr_document.

SAP-Demoprogramme

Folgende zwei Demoprogramme zeigen noch erweiterte Funktionen des DOI-Interfaces:

  • SAPRDEMO_FORM_INTERFACE
  • SAPRDEMO_SPREADSHEET_INTERFACE
  • SAPRDEMO_MAILMERGE_INTERFACE

Bei diesen drei Programmen lässt sich übrigens der Fehler, dass die Applikation nicht korrekt geschlossen wird, sehr gut nachstellen.

Bei SAPRDEMO_MAILMERGE_INTERFACE wird beim Beenden des Programms zwar das Dokument geschlossen, aber die Word-Applikation bleibt bestehen. Bei SAPRDEMO_FORM_INTERFACE wird das Programm zwar beendet, doch obwohl Close_Document und Destroy_Control aufgerufen werden, bleibt das Excel-Fenster geöffnet. Im Programm SAPRDEMO_SPREADSHEET_INTERFACE funktioniert das Beenden von Excel wiederum perfekt.

Der Beitrag OLE-Handle zu DOI-Objekt ermitteln erschien zuerst auf Tricktresor.

Buchungsbelege erstellen

$
0
0

Folgend ein Report, der exemplarisch zeigt, wie FI-Belege gebucht werden können. Es werden Die Bausteine BAPI_ACC_DOCUMENT_CHECK und BAPI_ACC_DOCUMENT_POST verwendet.

Aktuell macht der Report gar nichts!

Die Kopf- und Positionsdaten müssen in LT_BKPF und LT_BSEG entsprechend eingefügt werden. Wie genau das geht, erfährst du in dieser ausführlichen Doku:

Dokumentation

FI Buchungen mittels BAPI_ACC_DOCUMENT_POST

Vorgaben

Ich gehe davon aus, das die zu buchenden Daten in der Form Kopf/Position vorliegen. Sollte dies nicht der Fall sein, so sollten diese vorher so aufbereitet werden, da dies den Umgang beim Programmieren wesentlich vereinfacht.

Das folgende Beispiel behandelt exemplarisch eine kreditorische Buchung,
läßt sich jedoch prinzipiell ebenso auf debitorische oder Sachkonten-
buchungen übertragen.
Als Schmankerl wird ein zusätzliches Feld, welches nicht in der Sachkontenzeile
enthalten ist (LZBKZ – Landeszentralbankkennzeichen) mitgegeben und die Steuer
mit einem Steuerschema gebucht, welches zwei Steuerzeilen enthält.

Prinzipiell muss man folgendes Wissen zur Buchung von FI-Belegen wissen:

  1. Die erste Zeile des zu buchenden Beleges enthält den Betrag IMMER als Brutto-Wert! Unabhängig davon ob diese kreditorisch, debitorisch oder eine Sachkontenbuchung ist.
  2. Bei kreditorischen oder debitorischen Buchungen tauchen diese als Zeile nur ein mal als erste Zeile auf!
  3. Steuerkennzeichen MÜSSEN mitgeliefert werden sonst kann keine Steuer gebucht werden.

Kopfdaten

Wenn die Daten aus einem R3-System kommen, liegen die Daten normalerweise in der Form BUKRS/BELNR/GJAHR + Datenteil (für BKPF/BSEG) vor.
Sollte dies nicht der Fall, so erhält man normalerweise, wie in diesem Beispiel, eine laufende, eindeutige Nummer als Identifikator.
Für den Kopf auf jeden Fall müssen mitgegeben werden:

  • BUDAT – Buchungsdatum
  • BLDAT – Belegdatum
  • BUKRS – Buchungskreis
  • BLART – Belegart
  • WAERS – Währung in der gebucht werden soll (Belegwährung)

Bei Fremdwährungsbuchungen müssen zusätzlich das Feld

  • KURSF – Umrechnungskurs zur Buchungskreiswährung (Zeilentyp BAPIACCR09)
    ODER!!!!
  • WWERT – Umrechnungsdatum (Zeilentyp BAPIACHE09)

mitgegeben werden – keinesfalls beide!

Bei kreditorischen Buchungen ist z.B. auch die Referenz zu füllen

  • XBLNR – Referenznummer (Belegnummer des Lieferanten)

All diese Werte müssen bekannt sein und mitgegeben werden.

Das Buchungsdatum kann auch leer gelassen werden, dann wird aus dem Systemdatum die entsprechenden Werte für GJAHR und POPER errechnet (siehe Routine ADD_DATA_BKPF).
Ansonsten wird das mitgegebene Buchungsdatum verwendet und zur Berechnung verwendet

  • BKTXT – Kopftext (braucht man manchmal)

Positionsdaten

  • Als Mussdaten sind notwendig:
  • BUZEI fortlaufende Buchungszeilennummer.
    Kann man auch „on the fly“ erzeugen einfacher ist es, wenn sie bereits gefüllt ist. Die Buchungszeile stellt über den in jedem Segment vorhandenen Parameter ITEMNO_ACC die Verbindung bzw. die Sortierung der einzelnen Zeilen des Beleges untereinander sicher
  • KOART Kontoart
    K – Kreditorische Buchungszeile, D – Debitorische Buchungszeile,
    S – Sachkontenzeile
  • SHKZG Soll-/Haben Kennzeichen S/H
    Auch aus Fremdsystemen erhält man normalerweise dieses Kennzeichen. Es dient dazu, das Vorzeichen für die Beträge, welche dem BAPI übergeben werden, richtig zu ermitteln. Dies setzt voraus, das die Beträge (!!) immer als positive Werte übergeben werden – was im R/3 immer der Fall ist und bei Fremdsystembelegen zu 99% (SAP hat schließlich die Buchhaltung nicht erfunden 😉 )
    Habenwerte sind hierbei immmer mit -1 zu multiplizieren, Sollwerte behalten Ihr positives Vorzeichen
  • GKONT Gegenkonto
    Im Fall einer kreditorischen bzw. debitorischen Zeile die Lieferanten- bzw. Kundennummer. Im Fall einer Sachkontenzeile die Kontonummer, auf welche gebucht werden soll
  • MWSKZ Mehrwertsteuerkennzeichen
    MUSS, im Falle einer ggf. zu buchenden Steuer, mitgegeben werden. Hierüber werden die zu buchenden Steuerzeilen ermittelt
  • BRUTTO Bruttowert der Buchungszeile. Wird immer benutzt bei „Kopfzeilen“ d.h. bei kreditorische, debitorischen oder der 1. Zeile eines Sachkontenbeleges
  • NETTO Nettowert der Buchungszeile d.h. ohne Steuer
    Wird bei allen „Positionszeilen“, d.h. ab Zeile 2 des Beleges benutzt

Kontierungen

Im Beispiel habe ich Kontierungen auf Kostenstellen, Innenaufträge, SD-Belege und Anlagen vorgesehen.

  • SGTXT Positionstext (braucht man manchmal)
  • LANDL Lieferland
    Wird benötigt, wenn der Lieferant im Ausland ansässig ist – kann man aber auch durch nachlesen des Landes aus der Adresse des Lieferanten holen. Hier der Einfachheit halber in der Struktur

Schmankerl 1

  • LZBKZ Landeszentralbankkennzeichen
    Die betrifft Rechnungen von ausländischen Lieferanten die eine Sonstige Leistung i.S. des Umsatzsteuergesetzes erbracht haben. Der §13b UStG regelt unter einzelnen Punkten in welchen Fällen der Leistungsempfänger für die Leistungen die Umsatzsteuer schuldet.
    Gleichzeitig darf der Leistungsempfänger sich diesen Betrag als Vorsteuer in Abzug bringen.
    Steht in der BSEG als Feld zur Verfügung, ist aber nicht in der kreditorischen Struktur BAPIACAP09 vorhanden!!! Muß über die Tabelle extension2 des BAPI’s übergeben werden
    Ausführungen dazu in der Unterroutine für die Kreditorenzeile

Schmankerl 2

  • CO-PA Kontierung
    Bei Kontierung auf CO-PA Objekte (Ergebnisobjekte) kommt es oft genug vor, das vom Kunden nur der Vertriebsbeleg vorgegeben wird und alles andere soll ermittelt werden. Wir befinden uns nicht in der FB01 oder FB60 und können auf den Knopf „Ableitung“ drücken.
    Was also tun? Siehe hierzu die Unterroutine „ADD_COPA_LINE‘.

Coding

*&---------------------------------------------------------------------*
*& Report Z_POST_ACC_DOCUMENT
*&---------------------------------------------------------------------*
*&
*&---------------------------------------------------------------------*
REPORT z_post_acc_document.

******** KOPFDATEN **************
TYPES: BEGIN OF gtys_bkpf,
         id    TYPE numc10,
         bukrs TYPE bkpf-bukrs,
         gjahr TYPE bkpf-gjahr,
         poper TYPE poper,
         blart TYPE bkpf-blart,
         bldat TYPE bkpf-bldat,
         budat TYPE bkpf-budat,
         xblnr TYPE bkpf-xblnr,
         bktxt TYPE bkpf-bktxt,
         waers TYPE bkpf-waers,
         kursf TYPE bkpf-kursf,
         wwert TYPE bkpf-wwert,
         belnr TYPE bkpf-belnr,
       END OF gtys_bkpf,
       gtyt_bkpf TYPE STANDARD TABLE OF gtys_bkpf.

******** POSITIONSDATEN ***********
* Schmankerl 1
* LZBKZ  Landeszentralbankkennzeichen
*        Die betrifft Rechnungen von ausländischen Lieferanten die eine Sonstige Leistung
*        i.S. des Umsatzsteuergesetzes erbracht haben. Der §13b UStG regelt unter einzelnen
*        Punkten in welchen Fällen der Leistungsempfänger für die Leistungen die Umsatzsteuer schuldet.
*        Gleichzeitig darf der Leistungsempfänger sich diesen Betrag als Vorsteuer in Abzug bringen.
*        Steht in der BSEG als Feld zur Verfügung, ist aber nicht in der kreditorischen
*        Struktur BAPIACAP09 vorhanden!!! Muß über die Tabelle extension2 des BAPI's übergeben werden
*        Ausführungen dazu in der Unterroutine für die Kreditorenzeile "ADD_CRED_LINE"
* Schmankerl 2
* VBELN   CO-PA Kontierung
*        Bei Kontierung auf CO-PA Objekte (Ergebnisobjekte) kommt es oft genug vor,
*        das vom Kunden nur der Vertriebsbeleg vorgegeben wird und alles andere soll
*        ermittelt werden. Wir befinden uns nicht in der FB01 oder FB60 und können auf
*        den Knopf "Ableitung" drücken.
*        Was also tun?
*        Siehe hierzu die Unterroutine "ADD_COPA_LINE'.
* Schmankerl 3
* BWASL  Bewegungsartenschlüssel
*        Bei der direkten Buchung von Kreditor auf Anlage muß der Bewegungsartenschlüssel
*        vorgegeben werden. Dieser ist zwingend notwendig, sonst kommt keine Buchung zustande
*        bzw. wird abgelehnt.
*        Näheres hierzu in der Unterroutine "ADD_SACH_LINE'.
TYPES: BEGIN OF gtys_bseg,
         id     TYPE numc10,
         buzei  TYPE bseg-buzei,
         koart  TYPE bseg-koart,
         shkzg  TYPE bseg-shkzg,
         gkont  TYPE gkont,
         brutto TYPE bseg-wrbtr,
         netto  TYPE bseg-wrbtr,
         mwskz  TYPE bseg-mwskz,
         sgtxt  TYPE bseg-sgtxt,
         kostl  TYPE bseg-kostl,
         aufnr  TYPE bseg-aufnr,
         vbeln  TYPE bseg-vbeln,
         posnr  TYPE bseg-posn2,
         anln1  TYPE bseg-anln1,
         anln2  TYPE bseg-anln2,
         lzbkz  TYPE bseg-lzbkz,
         landl  TYPE bseg-landl,
         bwasl  TYPE bwasl,
       END OF gtys_bseg,
       gtyt_bseg TYPE STANDARD TABLE OF gtys_bseg.

* Struktur für den Aufbau der Steuerzeilen
TYPES: BEGIN OF gtys_tax,
         mwskz TYPE mwskz,
         wmwst TYPE wmwst,
         msatz TYPE msatz_f05l,
         ktosl TYPE ktosl,
         kawrt TYPE kawrt,
         hkont TYPE hkont,
         kschl TYPE kschl,
       END OF gtys_tax,
       gtyt_tax TYPE STANDARD TABLE OF gtys_tax.

* Org-Daten für CO-PA Ermittlung
TYPES: BEGIN OF gtys_org_copa,
         bukrs TYPE bukrs,
         kokrs TYPE kokrs,
         erkrs TYPE erkrs,
       END OF gtys_org_copa,
       gtyt_org_copa_hash TYPE HASHED TABLE OF gtys_org_copa
                       WITH UNIQUE KEY bukrs.
* Neue CO-PA Ableitung
TYPES: gtyt_copadata TYPE copadata_tab.

* Testparameter
PARAMETERS: p_xtest TYPE xfeld DEFAULT 'X'.

START-OF-SELECTION.
  PERFORM processing.
*&---------------------------------------------------------------------*
*&      Form  PROCESSING
*&---------------------------------------------------------------------*
*       text
*----------------------------------------------------------------------*
FORM processing .

  DATA: lt_bkpf  TYPE gtyt_bkpf.
  DATA: ls_bkpf  TYPE gtys_bkpf.
  DATA: lt_bseg  TYPE gtyt_bseg.
  DATA: ls_bseg  TYPE gtys_bseg.

* Die Datentabellen sind in diesem Beispiel nicht gefüllt
* Im beiliegenden PDF sind exemplarisch einige Buchungssätze zusammengestellt,
* welche aber nur für das System gültig sind, in dem Sie verbucht wurden.
* Um das Beispiel nutzen zu können, müßt ihr hier die Daten entsprechend
* Eurem System von Hand aufbauen
* Das Buchungsdatum habe ich leer gelassen, da in diesem Beispiel immer mit dem
* aktuellen Tagesdatum gebucht wird.
* Die Felder GJAHR und POPER werden während des Programmlaufes ermittelt
* und dann in die LS_BKPF übertragen - für den Fall, das man das ganze
* als Vorlage für eine Schnittstelle laufen lassen will 😉
*
* Für die Standardbuchung habe ich den Buchungssatz hier ausgesternt schon
* einmal vorbereitet:
*  CLEAR ls_bkpf.
*  ls_bkpf-id    = '1'.
*  ls_bkpf-budat = '00000000'.
*  ls_bkpf-bldat = '20180611'.
*  ls_bkpf-bukrs = '1000'.
*  ls_bkpf-gjahr = '0000'.
*  ls_bkpf-poper = '000'.
*  ls_bkpf-xblnr = '123'.
*  ls_bkpf-waers = 'EUR'.
*  APPEND ls_bkpf TO lt_bkpf.
*
*  CLEAR ls_bseg.
*  ls_bseg-id    = '1'.
*  ls_bseg-buzei = '001'
*  ls_bseg-koart = 'K'.
*  ls_bseg-shgkz = 'H'.
*  ls_bseg-gkont = '0000100205'.
*  ls_bseg-mwskz = abap_false.
*  ls_bseg-brutto = '1725.00'.
*  ls_bkpf-landl = 'DE'.
*  ls_bkpf-lzbkz = abap_false. "kein Nicht Deutscher Kreditor
*  APPEND ls_bseg TO lt_bseg.
*
*  CLEAR ls_bseg.
*  ls_bseg-id    = '1'.
*  ls_bseg-buzei = '002'.
*  ls_bseg-koart = 'S'.
*  ls_bseg-shgkz = 'S'.
*  ls_bseg-gkont = '0000479100'.
*  ls_bseg-mwskz = 'V3'.
*  ls_bseg-netto = '500.00'.
*  ls_bseg-kostl = '0000000100'.
*  ls_bseg-aufnr  = space.
*  ls_bseg-vbeln  = space.
*  ls_bseg-posnr  = '000000'.
*  ls_bseg-anln1  = space.
*  ls_bseg-anln2 = space.
*  ls_bseg-bwasl = space.
*  APPEND ls_besg TO lt_bseg.
*
*  CLEAR ls_bseg.
*  ls_bseg-id    = '1'.
*  ls_bseg-buzei = '003'.
*  ls_bseg-koart = 'S'.
*  ls_bseg-shgkz = 'S'.
*  ls_bseg-gkont = '0000479100'.
*  ls_bseg-mwskz = 'V2'.
*  ls_bseg-netto = '500.00'.
*  ls_bseg-kostl = '0000000100'.
*  ls_bseg-aufnr  = space.
*  ls_bseg-vbeln  = space.
*  ls_bseg-posnr  = '000000'.
*  ls_bseg-anln1  = space.
*  ls_bseg-anln2 = space.
*  ls_bseg-bwasl = space.
*  APPEND ls_besg TO lt_bseg.
*
*  CLEAR ls_bseg.
*  ls_bseg-id    = '1'.
*  ls_bseg-buzei = '004'.
*  ls_bseg-koart = 'S'.
*  ls_bseg-shgkz = 'S'.
*  ls_bseg-gkont = '0000479100'.
*  ls_bseg-mwskz = 'V3'.
*  ls_bseg-netto = '500.00'.
*  ls_bseg-kostl = '0000000140'.
*  ls_bseg-aufnr  = space.
*  ls_bseg-vbeln  = space.
*  ls_bseg-posnr  = '000000'.
*  ls_bseg-anln1  = space.
*  ls_bseg-anln2 = space.
*  ls_bseg-bwasl = space.
*  APPEND ls_besg TO lt_bseg.

  LOOP AT lt_bkpf INTO ls_bkpf.

    PERFORM post_acc_doc USING ls_bkpf
                               lt_bseg.
  ENDLOOP.

ENDFORM.
*&---------------------------------------------------------------------*
*&      Form  POST_ACC_DOC
*&---------------------------------------------------------------------*
*       text
*----------------------------------------------------------------------*
FORM post_acc_doc USING pis_bkpf  TYPE gtys_bkpf
                        pit_bseg  TYPE gtyt_bseg.

  DATA: ls_bkpf   TYPE gtys_bkpf.
  DATA: lt_bseg   TYPE gtyt_bseg.
  DATA: ls_bseg   TYPE gtys_bseg.
  DATA: lt_tax    TYPE gtyt_tax.

  DATA: BEGIN OF ls_awkey,
          belnr TYPE bkpf-belnr,
          bukrs TYPE bkpf-bukrs,
          gjahr TYPE bkpf-gjahr,
        END OF ls_awkey.

  DATA: lv_buzei TYPE bseg-buzei.
  DATA: lv_tabix TYPE sy-tabix.
  DATA: lv_subrc TYPE sy-subrc.
  DATA: lv_exit  TYPE xfeld.

* BAPI-Definitionen
* BKPF
  DATA ls_header TYPE bapiache09.
* BAPI-Protokoll
  DATA lt_return TYPE bapiret2_tab.
  DATA ls_return TYPE bapiret2.
* Sachkontenzeile
  DATA lt_acc_gl TYPE bapiacgl09_tab.
* Kreditorenzeile
  DATA lt_acc_py TYPE bapiacap09_tab.
* Debitorenzeile - Im Beispiel nicht ausgeführt
  DATA lt_acc_rv TYPE bapiacar09_tab               ##NEEDED.
* Wertzeile
  DATA lt_cur_am TYPE bapiaccr09_tab.
* Steuerzeile
  DATA lt_acc_tx TYPE bapiactx09_tab.
* Zusätzliche Parameter (siehe Routine add_cred_line)
  DATA lt_ext2   TYPE tt_bapiparex.
* CO-PA Merkmalstabelle
  DATA lt_acc_crit  TYPE bapiackec9_tab.
* CO-PA Wertetabelle
  DATA lt_acc_value TYPE bapiackev9_tab.

  ls_bkpf = pis_bkpf.

* Kopfdaten aufbereiten
  PERFORM add_head_line CHANGING ls_bkpf
                                 ls_header.

* Loop über alle Buchungszeilen des Beleges
  LOOP AT pit_bseg INTO ls_bseg
    WHERE id EQ ls_bkpf-id.
    lv_tabix = sy-tabix.
*   Kopieren der aktuellen Buchungszeile
*   Brauchen wir später, wenn wir die Steuerzeilen erzeugen wollen
    lv_buzei = ls_bseg-buzei.

*   Unterscheidung nach Kontoart
    CASE ls_bseg-koart.
*     Debitorenzeile
      WHEN 'D'.
*       Behandeln wir hier nicht - ist aber analog zum Kreditoren

*     Kreditorenzeile
      WHEN 'K'.
*       Kreditorenzeile aufbauen
        PERFORM add_cred_line USING    pis_bkpf
                                       ls_bseg
                              CHANGING lt_acc_py
                                       lt_ext2.

*       Betragszeile
        PERFORM add_curr_line USING    pis_bkpf
                                       ls_bseg
                                       lv_tabix
                              CHANGING lt_cur_am.
      WHEN 'S'.
*       Sachkontenzeile
        PERFORM add_sach_line USING    ls_bkpf
                                       ls_bseg
                              CHANGING lt_acc_gl
                                       lt_acc_crit
                                       lt_acc_value
                                       lt_ext2
                                       lv_subrc.
        IF lv_subrc NE 0.
          lv_exit = abap_true.
          EXIT.
        ENDIF.

*       Betragszeile
        PERFORM add_curr_line USING    pis_bkpf
                                       ls_bseg
                                       lv_tabix
                              CHANGING lt_cur_am.

*       Steuer für Sachkontenpositionszeilen ermitteln
        PERFORM get_tax USING    pis_bkpf
                                 ls_bseg
                                 lv_tabix
                        CHANGING lt_tax.

    ENDCASE.
  ENDLOOP.

* Fehler bei der CO-PA Ermittlung - und raus
  IF lv_exit EQ abap_true.
    RETURN.
  ENDIF.

* Nachdem wir auf den Sachkontenzeilen alle Steuern
* gesammelt haben, bauen wir daraus jetzt die Steuerzeilen auf
  PERFORM add_tax_lines USING    lt_tax
                                 pis_bkpf
                                 lv_buzei
                        CHANGING lt_acc_tx
                                 lt_cur_am.

* Beleg prüfen
  CALL FUNCTION 'BAPI_ACC_DOCUMENT_CHECK'
    EXPORTING
      documentheader = ls_header
    TABLES
      accountgl      = lt_acc_gl
      accountpayable = lt_acc_py
      accounttax     = lt_acc_tx
      currencyamount = lt_cur_am
      extension2     = lt_ext2
      return         = lt_return.
* Protokoll auswerten
  READ TABLE lt_return INTO ls_return
    WITH KEY type   = 'S'
             id     = 'RW'
             number = '614'.
  IF sy-subrc EQ 0.
*   Testflag abfragen
*   Wenn gesetzt, raus
    IF NOT p_xtest IS INITIAL.
      FORMAT COLOR COL_POSITIVE.
      WRITE:/ 'ID', pis_bkpf-id,
            'erfolgreich gebucht'                       ##NO_TEXT.
      FORMAT COLOR OFF.
      RETURN.
    ENDIF.
*   Kein Testflag. Buchen
    FREE lt_return.
    CALL FUNCTION 'BAPI_ACC_DOCUMENT_POST'
      EXPORTING
        documentheader = ls_header
      TABLES
        accountgl      = lt_acc_gl
        accountpayable = lt_acc_py
        accounttax     = lt_acc_tx
        currencyamount = lt_cur_am
        extension2     = lt_ext2
        return         = lt_return.
    READ TABLE lt_return INTO ls_return
      WITH KEY type   = 'S'
               id     = 'RW'
               number = '605'.
    IF sy-subrc EQ 0.
      MOVE ls_return-message_v2 TO ls_awkey.
*     Commit Work durchführen
      CALL FUNCTION 'BAPI_TRANSACTION_COMMIT'
        IMPORTING
          return = ls_return.
      IF NOT ls_return IS INITIAL.
      ELSE.
        FORMAT COLOR COL_POSITIVE.
        WRITE:/ 'ID', pis_bkpf-id,
                'erfolgreich gebucht'                  ##NO_TEXT.
        FORMAT COLOR OFF.
        WRITE:/ 'Beleg'                                ##NO_TEXT,
                pis_bkpf-id,
                ls_awkey-bukrs,
                ls_awkey-belnr,
                ls_awkey-gjahr,
                'erfolgreich gebucht'                  ##NO_TEXT.
      ENDIF.
    ELSE.
      FORMAT COLOR COL_NEGATIVE.
      WRITE: /'ID', pis_bkpf-id, 'fehlerhaft'          ##NO_TEXT.
      FORMAT COLOR OFF.
      LOOP AT lt_return INTO ls_return
      WHERE type NE 'S'
      AND   type NE 'I'.
        IF ls_return-id     EQ 'RW' AND
           ls_return-number EQ '609'.
          CONTINUE.
        ENDIF.
        WRITE: / pis_bkpf-id,
                 ls_return-row,
                 ls_return-type,
                 ls_return-id,
                 ls_return-number,
                 ls_return-message.
      ENDLOOP.
    ENDIF.
* Fehler bei der Belegprüfung
  ELSE.
    FORMAT COLOR COL_NEGATIVE.
    WRITE: /'ID', pis_bkpf-id, 'fehlerhaft'            ##NO_TEXT.
    FORMAT COLOR OFF.
    LOOP AT lt_return INTO ls_return
    WHERE type NE 'S'
    AND   type NE 'I'.
      IF ls_return-id EQ 'RW' AND
       ls_return-number EQ '609'.
        CONTINUE.
      ENDIF.
      WRITE:/ pis_bkpf-id,
              ls_return-row,
              ls_return-type,
              ls_return-id,
              ls_return-number,
              ls_return-message.
    ENDLOOP.
  ENDIF.

ENDFORM.
*&---------------------------------------------------------------------*
*&      Form  ADD_HEAD_LINE
*&---------------------------------------------------------------------*
*       text
*----------------------------------------------------------------------*
FORM add_head_line  CHANGING pcs_bkpf   TYPE gtys_bkpf
                             pes_header TYPE bapiache09.

  CLEAR pes_header.

  DATA: ls_header TYPE bapiache09.

  ls_header-bus_act           = 'RFBU'.               "Betriebswirtschaftlicher Vorgang
  ls_header-username          = sy-uname.             "Name des Benutzers
  ls_header-header_txt        = pcs_bkpf-bktxt.       "Belegkopftext
  ls_header-comp_code         = pcs_bkpf-bukrs.       "Buchungskreis
  ls_header-doc_date          = pcs_bkpf-bldat.       "Belegdatum

* Ermitteln von Geschäftsjahr und Periode aus dem Buchungsdatum
* In unserem Fall ist das Buchungsdatum leer, also nehmen wir das
* Tagesdatum. Es kann aber auch ein bestimmtes Datum übergeben werden
* Dann aber darauf achten, das die entsprechenden Perioden zum
* Buchen offen sind
  PERFORM add_data_to_bkpf CHANGING pcs_bkpf.
  IF pcs_bkpf-budat IS INITIAL.
    ls_header-pstng_date      = sy-datum.             "Buchungsdatum
  ELSE.
    ls_header-pstng_date      = pcs_bkpf-budat.     "Buchungsdatum
  ENDIF.

  ls_header-trans_date        = sy-datum.             "Umrechnungsdatum
  ls_header-fisc_year         = pcs_bkpf-gjahr.       "Geschäftsjahr
  ls_header-fis_period        = pcs_bkpf-poper.       "Geschäftsmonat
  ls_header-doc_type          = pcs_bkpf-blart.       "Belegart
  ls_header-ref_doc_no        = pcs_bkpf-xblnr.       "Referenznummer

* Achtung !!!!
* Entweder Umrechnungsdatum oder Kurs - nicht beides mitgeben !!!!!
  IF NOT pcs_bkpf-wwert IS INITIAL.
    ls_header-trans_date    = pcs_bkpf-wwert.
  ENDIF.

* Sollten zusätzliche Werte benötigt werden, hier übergeben
* oder die Kopftabelle entsprechend "aufbohren".

  pes_header = ls_header.

ENDFORM.
*&---------------------------------------------------------------------*
*&      Form  ADD_DATA_TO_BKPF
*&---------------------------------------------------------------------*
*       text
*----------------------------------------------------------------------*
FORM add_data_to_bkpf CHANGING pcs_bkpf TYPE gtys_bkpf.

  TYPES: BEGIN OF ltys_t001,
           bukrs TYPE t001-bukrs,
           periv TYPE t001-periv,
         END OF ltys_t001,
         ltyt_t001 TYPE HASHED TABLE OF ltys_t001 WITH UNIQUE KEY bukrs.

  STATICS: lv_first_call TYPE xfeld.
  STATICS: lt_t001       TYPE ltyt_t001.
  DATA: ls_t001 TYPE ltys_t001.
  DATA: lv_buper TYPE poper.
  DATA: lv_gjahr TYPE gjahr.
  DATA: lv_budat TYPE bkpf-budat.

* Ermittlung der Geschäftsjahresvariante für alle im System vorhandenen
* Buchungskreise
  IF lv_first_call IS INITIAL.
    SELECT bukrs periv
           FROM t001
           INTO TABLE lt_t001.
    MOVE abap_true TO lv_first_call.
  ENDIF.

  READ TABLE lt_t001 INTO ls_t001
    WITH TABLE KEY bukrs = pcs_bkpf-bukrs.
  IF sy-subrc EQ 0.
    IF pcs_bkpf-budat IS INITIAL.
      MOVE sy-datum       TO lv_budat.
    ELSE.
      MOVE pcs_bkpf-budat TO lv_budat.
    ENDIF.

*   Ermittlung der Periode und des Geschäftsjahres
*   anhand des Buchungsdatum und der Geschäftsjahresvariante
*   (Stichwort: Verschobenes Geschäftsjahr)
    CALL FUNCTION 'DATE_TO_PERIOD_CONVERT'
      EXPORTING
        i_date         = lv_budat
        i_periv        = ls_t001-periv
      IMPORTING
        e_buper        = lv_buper
        e_gjahr        = lv_gjahr
      EXCEPTIONS
        input_false    = 1
        t009_notfound  = 2
        t009b_notfound = 3
        OTHERS         = 4.
    IF sy-subrc <> 0.
      MESSAGE ID sy-msgid TYPE 'E' NUMBER sy-msgno
              WITH sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4.
    ENDIF.
    MOVE: lv_buper TO pcs_bkpf-poper,
          lv_gjahr TO pcs_bkpf-gjahr.
  ELSE.
*   Fehlermeldung - darf nicht vorkommen !!!!
*   Buchungskreis &1 ist nicht vorhanden
    MESSAGE a215(fagl_emu) WITH pcs_bkpf-bukrs.
  ENDIF.

ENDFORM.
*&---------------------------------------------------------------------*
*&      Form  ADD_CRED_LINE
*&---------------------------------------------------------------------*
*       text
*----------------------------------------------------------------------*
FORM add_cred_line  USING    pis_bkpf TYPE gtys_bkpf
                             pis_bseg TYPE gtys_bseg
                    CHANGING pct_cred TYPE bapiacap09_tab
                             pct_ext2 TYPE tt_bapiparex.

  DATA: ls_struc_ext2 TYPE zsc_s_soawf_badi_acc_doc.
  DATA: ls_cred  LIKE LINE OF pct_cred.
  DATA: ls_ext2  LIKE LINE OF pct_ext2.

* Kreditorenzeile
  ls_cred-itemno_acc        = pis_bseg-buzei.       "Positionsnummer des Rechnungswesenbeleges
  ls_cred-vendor_no         = pis_bseg-gkont.       "Lieferantennummer
* Das Sachkonto auf welches gebucht wird, ist immer das im Lieferantenstamm
* eingetragene Abstimmkonto
* Gilt übrigens auch für Debitoren. Dort ist es AKONT in der KNB1
  SELECT SINGLE akont
                FROM lfb1
                INTO ls_cred-gl_account
                WHERE lifnr EQ pis_bseg-gkont
                AND   bukrs EQ pis_bkpf-bukrs.
  ls_cred-item_text         = pis_bseg-sgtxt.       "Positionstext
  ls_cred-supcountry        = pis_bseg-landl.       "Lieferland wenn nicht DE

* Schmankerl 1
* Übergabe eines nicht in der Kreditorenstruktur vorhandenen Feldes - in diesem
* Falle das Landeszentralbankkennzeichen
  IF NOT pis_bseg-lzbkz IS INITIAL.
*   Hierzu MUSS!! man sich im DDIC eine Struktur definieren, welche
*   all die Felder enthält, welche man zusätzlich übergeben will.
*   Diese wird im Feld STRUCTURE der Struktur BAPIPAREX übergeben.
*
*   Als Maximalmenge können die Felder der Struktur ACCIT übergeben werden.
*   Die Struktur MUSS!!! immer ein Feld vom Typen
*   POSNR TYPE POSNR_ACC
*   enthalten. Dieser stellt die Verbindung von der Übergabezeile (in diesem Falle
*   der Kreditorenzeile) zur Extension2-Zeile her.
*
*   Als weitere Felder werden dann die zu zusätzlich benötigten Felder übergeben.
*
*   Um nun die Felder auch ins BAPI zu bringen, muß man zum BADI ACC_DOCUMENT
*   mit der SE19 eine eigene Implementierung anlegen (oder, wenn Glück hat, ist
*   bereits eine vorhanden).
*   Hierzu MUSS!!! man den Filterwert auf BKPFF (nicht BKPF) stellen, da dieser
*   vom BAPI gefordert bzw. geschrieben wird.
*   Als Coding kann man das Coding der Beispielimplementierung der SAP nutzen, welche man
*   aus der Beispielimplementierung in die eigene Implementierung kopiert.
*   ACHTUNG!!!!!!
*   Nicht den Fehler machen und die ACCIT als Strukturnamen und Übergabestruktur
*   benutzen. Die Felder VALUEPART1 bis VALUEPART4 werden concateniert und sind
*   insgesamt nur 960 Zeichen lang !!! Da kann man mit der ACCIT und Ihren 496 Feldern!
*   auch schon mal ganz schnell ins Klo greifen, wenn die Felder erst hinter
*   den 960 Zeichen aufgerufen werden.
*   Wenn man mehr als 240 Zeichen Übergabe benötigt, ein Charakterfeld mit 960 Zeichen
*   definieren, die Struktur darauf moven, dann zerhaken und als VALUEPART1 bis 4 übergeben.
*   ACHTUNG !!!!
*   Bei Übergabe von Zahlfeldern diese vorher in Charakterwerte umwandeln und dann übergeben,
*   sonst geht es schief. Ggf. die Implentierung entsprechend anpassen.
*   ACHTUNG !!!
*   Da die Übergabefelder CHAR-Werte sind, kann Kleinschreibung nicht übergeben werden.
*   Danke SAP!!!
    MOVE 'Z_BADI_ACC_DOC' TO ls_ext2-structure.
    ls_struc_ext2-posnr = pis_bseg-buzei.
    ls_struc_ext2-lzbkz = pis_bseg-lzbkz.
    MOVE ls_struc_ext2 TO ls_ext2-valuepart1.
    APPEND ls_ext2 TO pct_ext2.
  ENDIF.

  APPEND ls_cred TO pct_cred.

ENDFORM.
*&---------------------------------------------------------------------*
*&      Form  ADD_CURR_LINE
*&---------------------------------------------------------------------*
*       text
*----------------------------------------------------------------------*
FORM add_curr_line  USING    pis_bkpf   TYPE gtys_bkpf
                             pis_bseg   TYPE gtys_bseg
                             piv_tabix  TYPE sy-tabix
                    CHANGING pet_curr   TYPE bapiaccr09_tab.

  DATA: ls_curr   LIKE LINE OF pet_curr.
  DATA: lv_amount TYPE bseg-wrbtr.

  CASE pis_bseg-koart.
*   Kreditorische/Debitorische Zeilen nur einmal im Beleg
*   als Kopfzeile - daher immer Brutto!!
    WHEN 'K' OR 'D'.
      MOVE pis_bseg-brutto TO lv_amount.
    WHEN 'S'.
*     Bei Sachkontenbuchungen Unterscheidung
*     nach Kopf- und Positionszeilen
      IF piv_tabix EQ 1.
        MOVE pis_bseg-brutto TO lv_amount.
      ELSE.
        MOVE pis_bseg-netto  TO lv_amount.
      ENDIF.
  ENDCASE.

* Habenwerte immer mit -1 multiplizieren (braucht das BAPI
* um zu erkennen, was es buchen soll)
  CASE pis_bseg-shkzg.
    WHEN 'H'.
      lv_amount = lv_amount * -1.
  ENDCASE.

  ls_curr-itemno_acc        = pis_bseg-buzei.
  ls_curr-currency          = pis_bkpf-waers.
* Wenn die Währung nicht stimmen sollte, hier umwandeln
  ls_curr-currency_iso      = pis_bkpf-waers.
  ls_curr-amt_doccur        = lv_amount.
* Währungstyp
* Hier steht die Belegwährung, was der Standardfall ist.
* Sollen zusätzlich andere Typen verwendet werden, müßen die Werte angepaßt
* werden. Aber das ist ein ganz eigenes Kapitel. Werde ich ggf. bei
* Zeiten ergänzen.
  ls_curr-curr_type         = '00'.
* Umrechnungskurs für Fremdwährung
* ACHTUNG!!!
* Entweder hier KURSF ODER!!!! im Kopf WWERT mitgeben
  ls_curr-exch_rate         = pis_bkpf-kursf.

  APPEND ls_curr TO pet_curr.

ENDFORM.
*&---------------------------------------------------------------------*
*&      Form  ADD_SACH_LINE
*&---------------------------------------------------------------------*
*       text
*----------------------------------------------------------------------*
FORM add_sach_line  USING    pis_bkpf  TYPE gtys_bkpf
                             pis_bseg  TYPE gtys_bseg
                    CHANGING pct_sach  TYPE bapiacgl09_tab
                             pct_crit  TYPE bapiackec9_tab
                             pct_value TYPE bapiackev9_tab
                             pct_ext2  TYPE tt_bapiparex
                             pev_subrc TYPE sy-subrc.

  DATA: ls_struc_ext2 TYPE zsc_s_soawf_badi_acc_doc.
  DATA: ls_ext2       LIKE LINE OF pct_ext2.
  DATA: ls_acc_gl     LIKE LINE OF pct_sach.
  DATA: lv_subrc      TYPE sy-subrc.

  CLEAR pev_subrc.

  ls_acc_gl-itemno_acc        = pis_bseg-buzei.       "Positionsnummer des Rechnungswesenbeleges
  ls_acc_gl-gl_account        = pis_bseg-gkont.       "Sachkonto der Hauptbuchhaltung
  ls_acc_gl-item_text         = pis_bseg-sgtxt.       "Positionstext
  ls_acc_gl-acct_key          = space.                "Vorgangsschlüssel
* Bei Kontierung Kreditor auf Anlage muß die Kontoart auf 'A'
* wie Anlage umgestellt werden.
* Ferner brauchen wir das Abstimmkonto der Anlage
* und wir müssen, über die Externen Felder, die Bewegungsart
* mitgeben. Andernfalls bekommen wir keine Anlagenbuchung
  IF NOT pis_bseg-anln1 IS INITIAL.
    ls_acc_gl-acct_type         = 'A'.                "Kontoart
    ls_acc_gl-gl_account        = pis_bseg-gkont.     "Sachkonto der Hauptbuchhaltung
    ls_acc_gl-acct_key          = 'ANL'.              "Vorgangsschlüssel für Anlagenbuchungen (BSCHL 70/75)
    MOVE 'ZSC_S_SOAWF_BADI_ACC_DOC' TO ls_ext2-structure.
    ls_struc_ext2-posnr = pis_bseg-buzei.
    ls_struc_ext2-anbwa = '100'.
    MOVE ls_struc_ext2 TO ls_ext2-valuepart1.
    APPEND ls_ext2 TO pct_ext2.
  ELSE.
    ls_acc_gl-acct_type         = 'S'.                "Kontoart
    ls_acc_gl-gl_account        = pis_bseg-gkont.     "Sachkonto der Hauptbuchhaltung
  ENDIF.

  ls_acc_gl-acct_type         = pis_bseg-koart.       "Kontoart
  ls_acc_gl-tax_code          = pis_bseg-mwskz.       "Mehrwertsteuerkennzeichen
* Standardkontierungen, ggf. ergänzen.
  ls_acc_gl-costcenter        = pis_bseg-kostl.       "Kostenstelle
  ls_acc_gl-orderid           = pis_bseg-aufnr.       "Innenauftrag
  ls_acc_gl-asset_no          = pis_bseg-anln1.       "Anlagenhauptnummer
  ls_acc_gl-sub_number        = pis_bseg-anln2.       "Anlagenunternummer

  IF NOT pis_bseg-vbeln IS INITIAL.
    PERFORM add_copa_line USING    pis_bkpf
                                   pis_bseg
                          CHANGING pct_crit
                                   pct_value
                                   lv_subrc.
    IF sy-subrc NE 0.
      pev_subrc = 4.
      RETURN.
    ENDIF.
  ENDIF.

  APPEND ls_acc_gl TO pct_sach.

ENDFORM.
*&---------------------------------------------------------------------*
*&      Form  ADD_COPA_LINE
*&---------------------------------------------------------------------*
*       Schmankerl CO-PA Kontierung
*       Dem BAPI ACC_DOCUMENT_POST müssen die Merkmals- sowie die
*       Wertedaten für die CO-PA Kontierung mitgegeben werden
*       Hierbei kommt es vor, das von Kundenseite oder der Schnittstellt
*       nur der Kundenauftrag zur Verfügung gestellt wird und die
*       CO-PA Kontierungen dem Programmierer "überlassen" werden.
*       Danke für die Info 😉
*       Was tun?
*       Zum Glück für uns gibt es den Report RFBIBL00 bzw RFBBIBL01
*       der ebenfalls mit CO-PA Daten bestückt werden kann.
*       Hierfür gibt es den Baustein RKE_CONVERT_CRITERIA_PAOBJNR
*       der aus gegegbenen Daten eine neu CO-PA Ableitung fährt
*       aus der man dann die Merkmale nachlesen und dem BAPI unterschieben kann.
******  ACHTUNG !!!!!! ***********
*       Das neue Kontierungsobjekt wird nur dann weggeschrieben und
*       kann nachgelesen werden, wenn ein COMMIT WORK erfolgt!!!!
*       Das ist zwar unschön aber ich kenne keine andere Methode um an eine
*       komplette Ableitung zu gelangen. Wenn jemand eine kennt, wäre
*       ich dankbar, wenn diese öffentlich gemacht würde.
*       Man muß also aufpassen, das man vorher keine Daten auf der
*       Datenbank bereits geändert oder angelegt hat. Diese würde
*       hier unweigerlich commited!!!!!
*
*       Das ist aber leider nur die Hälfte der Wahrheit
*       Wir brauchen leider nicht nur die Merkmale, sondern wir müssen
*       ja auch noch die WErtfelder bestücken.
*       Hierzu siehe Routine ADD_VALUE_LINE
*
*       Und als ob das noch nicht reichen würde, müssen wir die erhaltene
*       Ableitung auch noch durch eine Routine schicken, welche die
*       alle relevanten Konvertierungsexits durchläuft, weil der
*       Ableitungsbaustein nur die externe Darstellung (RFBIBL -> BI-Programm)
*       zurückliefert aber nicht die interne, welche das BAPI fordert.
*       Hierzu siehe Routine 'CHANGE_DATA_CONV_EXIT
*       Diese Routine kann man in abgewandelter Form auch benutzen, um
*       z.B. Datenbankdaten für einen Batch-Input aufzubereiten (z.B.
*       Arbeitsplätze oder PSP-Elemente).
*----------------------------------------------------------------------*
FORM add_copa_line  USING    pis_bkpf  TYPE gtys_bkpf
                             pis_bseg  TYPE gtys_bseg
                    CHANGING pct_crit  TYPE bapiackec9_tab
                             pct_value TYPE bapiackev9_tab
                             pev_subrc TYPE sy-subrc.

  DATA: ls_copabbseg TYPE copabbseg.
  DATA: ls_crit      LIKE LINE OF pct_crit.
  DATA: lv_paobjnr   TYPE rkeobjnr.
  DATA: lv_subrc     TYPE sy-subrc.
  DATA: lt_copadata  TYPE gtyt_copadata.
  DATA: ls_copadata  LIKE LINE OF lt_copadata.
  DATA: ls_org_copa  TYPE gtys_org_copa.

  CLEAR pev_subrc.

  IF pis_bseg-vbeln IS INITIAL.
    RETURN.
  ENDIF.

* Zuordnung BURKS zu KOKRS/ERKRS ermitteln
  PERFORM get_orgdata_copa USING    pis_bkpf-bukrs
                           CHANGING ls_org_copa.

* Wertfeldtabelle füllen
  PERFORM add_value_line USING    pis_bkpf
                                  pis_bseg
                                  ls_org_copa-kokrs
                                  ls_org_copa-erkrs
                         CHANGING pct_value
                                  lv_subrc.
  IF lv_subrc NE 0.
    pev_subrc = 4.
    RETURN.
  ENDIF.

* Übergabe für Baustein füttern
* Buchungskreis MUSS zwingend gefüllt werden
  MOVE: pis_bkpf-bukrs TO ls_copabbseg-rke_bukrs.
  MOVE: pis_bseg-vbeln TO ls_copabbseg-rke_kaufn.
  MOVE: pis_bseg-posnr TO ls_copabbseg-rke_kdpos.

* Neue Ableitung
  CALL FUNCTION 'RKE_CONVERT_CRITERIA_PAOBJNR'
    EXPORTING
      is_copabbseg     = ls_copabbseg
*     i_date           =
    IMPORTING
      e_paobjnr        = lv_paobjnr
    EXCEPTIONS
      no_bukrs_found   = 1
      no_erkrs_found   = 2
      error_criterion  = 3
      error_derivation = 4
      OTHERS           = 5.
  IF sy-subrc <> 0.
*   Fehler bei Ableitung CO-PA Merkmale für ID & Buzei &.
    pev_subrc = 4.
    RETURN.
  ENDIF.

* Commit Work durchführen, damit die Ergebnisobjektnummer auf
* der Datenbank vorliegt
  COMMIT WORK  AND WAIT.
  IF sy-subrc NE 0.
*   Fehler bei der Fortschreibung PAOBJNR für ID & Buzei &.
    pev_subrc = 4.
    RETURN.
  ENDIF.

* Nachlesen der neuen Ableitung
  CALL FUNCTION 'RKE_CONVERT_PAOBJNR_COPADATA'
    EXPORTING
      bukrs          = pis_bkpf-bukrs
      kokrs          = ls_org_copa-kokrs
      paobjnr        = lv_paobjnr
    TABLES
      i_copadata     = lt_copadata
    EXCEPTIONS
      no_erkrs_found = 1
      paobjnr_wrong  = 2
      OTHERS         = 3.
  IF sy-subrc <> 0.
*   Fehler bei Ermittlung Wertfelder aus PAOBJNR für ID & Buzei &.
    pev_subrc = 4.
    RETURN.
  ENDIF.

* Umstellung der externen Darstellung der Merkmale auf
* interne Werte (durchlaufen der Konvertierungsexits)
  IF NOT lt_copadata IS INITIAL.
    PERFORM change_data_conv_exit USING    ls_org_copa-erkrs
                                  CHANGING lt_copadata.
  ENDIF.

* Anhängen der Merkmale an die Merkmalstabelle für das BAPI
  LOOP AT lt_copadata INTO ls_copadata.
    IF NOT ls_copadata-fval IS INITIAL.
      CLEAR ls_crit.

      MOVE: pis_bseg-buzei   TO ls_crit-itemno_acc,
            ls_copadata-fnam TO ls_crit-fieldname,
            ls_copadata-fval TO ls_crit-character.
      APPEND ls_crit TO pct_crit.
    ENDIF.
  ENDLOOP.

ENDFORM.
*&---------------------------------------------------------------------*
*&      Form  GET_ORGDATA_COPA
*&---------------------------------------------------------------------*
*       text
*----------------------------------------------------------------------*
FORM get_orgdata_copa  USING    piv_bukrs    TYPE bukrs
                       CHANGING pes_org_copa TYPE gtys_org_copa.

  CLEAR pes_org_copa.

  DATA: ls_org_copa    LIKE pes_org_copa.
  DATA: lv_kokrs       TYPE kokrs.
  DATA: lv_erkrs       TYPE erkrs.
  STATICS: lt_org_copa TYPE gtyt_org_copa_hash.

  READ TABLE lt_org_copa INTO ls_org_copa
    WITH TABLE KEY bukrs = piv_bukrs.
  IF sy-subrc EQ 0.
    pes_org_copa = ls_org_copa.
    RETURN.
  ENDIF.

  CALL FUNCTION 'KOKRS_GET_FROM_BUKRS'
    EXPORTING
      i_bukrs        = piv_bukrs
    IMPORTING
      e_kokrs        = lv_kokrs
    EXCEPTIONS
      no_kokrs_found = 1
      OTHERS         = 2.
  IF sy-subrc <> 0.
    MESSAGE ID sy-msgid TYPE 'E' NUMBER sy-msgno
            WITH sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4.
  ENDIF.

  CALL FUNCTION 'COPA_ERKRS_FIND'
    EXPORTING
      bukrs              = piv_bukrs
    IMPORTING
      erkrs              = lv_erkrs
    EXCEPTIONS
      error_kokrs_find   = 1
      kokrs_wrong        = 2
      no_erkrs_defined   = 3
      no_erkrs_for_kokrs = 4
      OTHERS             = 5.
  IF sy-subrc <> 0.
    MESSAGE ID sy-msgid TYPE 'E' NUMBER sy-msgno
            WITH sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4.
  ENDIF.

  MOVE: piv_bukrs TO ls_org_copa-bukrs,
        lv_kokrs  TO ls_org_copa-kokrs,
        lv_erkrs  TO ls_org_copa-erkrs.
  INSERT ls_org_copa INTO TABLE lt_org_copa.
  pes_org_copa = ls_org_copa.

ENDFORM.
*&---------------------------------------------------------------------*
*&      Form  ADD_VALUE_LINE
*&---------------------------------------------------------------------*
*       Das BAPI fordert von uns nicht nur die Merkmale sondern wir
*       müssen auch die Wertfelder bestücken - und zwar die richtigen!!!
*
*       Die erreichen wir, indem wir den Baustein
*       COPA_GET_SETTLEMENT_STRUCTURE benutzen. Dieser gibt uns
*       das Ergebnisschmema, die Zuordnung sowie den Inhalt der Tabelle
*       TKB9F zurück.
*       Hierzu muß man wissen, das für das Konto, auf welches gebucht
*       werden soll, im Customizing hinterlegt sein MUSS, auf welches
*       Wertfeld die Beträge laufen sollen.
*       Also lesen wir zunächst mit dem Baustein Schema und Zuordnung
*       und dann damit die Tabelle.
*       Nicht wundern, das hier bestimmte Werte hart verdrahtet sind.
*       Das ist so und muß auch so gecustomized sein.
*       Es MUSS ein Wertfeld sein (MWKZ = 1) und das Fix-/Variabel Kz.
*       MUSS auf Gesamtwert (FVKZ = 3) stehen. Ist das nicht der Fall,
*       ist dies ein Fehler!!!!
*       Hat man vor mehrere Belege für unterschiedliche Währungs-
*       sichten zu erzeugen, so muß man dies auch hier tun. Im diesem
*       Beispiel nehmen wir nur die Belegwährung
*----------------------------------------------------------------------*
FORM add_value_line USING    pis_bkpf  TYPE gtys_bkpf
                             pis_bseg  TYPE gtys_bseg
                             piv_kokrs TYPE kokrs
                             piv_erkrs TYPE erkrs
                    CHANGING pct_value TYPE bapiackev9_tab
                             pev_subrc TYPE sy-subrc.

  DATA: lt_9f TYPE STANDARD TABLE OF tkb9f.
  DATA: ls_9f LIKE LINE OF lt_9f.
  DATA: lv_ersch TYPE tkb9f-ersch.
  DATA: lv_erzuo TYPE tkb9f-erzuo.

  DATA: ls_value LIKE LINE OF pct_value.
  DATA: BEGIN OF ls_wertfeld,
          hkont  TYPE bseg-hkont,
          wrtfld TYPE fieldname,
        END OF ls_wertfeld.
  STATICS: lt_wertfeld LIKE HASHED TABLE OF ls_wertfeld
                       WITH UNIQUE KEY hkont.

  CLEAR pev_subrc.

* Statische Wertfeldtabelle lesen (damit es schneller geht)
  READ TABLE lt_wertfeld INTO ls_wertfeld
    WITH TABLE KEY hkont = pis_bseg-gkont.
  IF sy-subrc EQ 0.
    MOVE: pis_bseg-buzei     TO ls_value-itemno_acc,
          ls_wertfeld-wrtfld TO ls_value-fieldname,
          '00'               TO ls_value-curr_type,
          pis_bkpf-waers     TO ls_value-currency,
          pis_bkpf-waers     TO ls_value-currency_iso,
          pis_bseg-netto     TO ls_value-amt_valcom.
    APPEND ls_value TO pct_value.
    RETURN.
  ENDIF.

* Ansonsten Ergebnisschema und -zuordnung ermitteln
  CALL FUNCTION 'COPA_GET_SETTLEMENT_STRUCTURE'
    EXPORTING
      i_erkrs              = piv_erkrs
      i_vrgng              = 'RFBU'
      i_hkont              = pis_bseg-gkont
      i_kokrs              = piv_kokrs
    IMPORTING
      e_ersch              = lv_ersch
      e_erzuo              = lv_erzuo
    TABLES
      t_tkb9f              = lt_9f
    EXCEPTIONS
      incomplete_structure = 1
      error_structure      = 2
      OTHERS               = 3.
  IF sy-subrc <> 0.
*   Wertfeld für Bukrs & Konto & kann nicht ermittelt werden (&).
    pev_subrc = 4.
    RETURN.
  ENDIF.

* TKB9F mit den ermittelten Werten lesen
  READ TABLE lt_9f INTO ls_9f WITH KEY ersch = lv_ersch
                                       erzuo = lv_erzuo
                                       erkrs = piv_erkrs
                                       mwkz  = '1'
                                       fvkz  = '3'.
  IF sy-subrc NE 0.
*   Ist hier nichts zu finden, ist dies ein Fehler und der Beleg
*   würde sowieso aufbrummen - also Schluß
*   Wertfeld für Bukrs & Konto & kann nicht ermittelt werden (&)
    pev_subrc = 4.
    RETURN.
  ENDIF.

* Value Rückgabetabelle füllen
  MOVE: pis_bseg-buzei TO ls_value-itemno_acc,
        ls_9f-wrtfld   TO ls_value-fieldname,
        '00'           TO ls_value-curr_type,
        pis_bkpf-waers TO ls_value-currency,
        pis_bkpf-waers TO ls_value-currency_iso,
        pis_bseg-netto TO ls_value-amt_valcom.
  APPEND ls_value TO pct_value.

* Und zum Schluß noch die statische Wertfeldtabelle erweitern
  MOVE: pis_bseg-gkont TO ls_wertfeld-hkont,
        ls_9f-wrtfld   TO ls_wertfeld-wrtfld.
  INSERT ls_wertfeld INTO TABLE lt_wertfeld.

ENDFORM.
*&---------------------------------------------------------------------*
*&      Form  CHANGE_DATA_CONV_EXIT
*&---------------------------------------------------------------------*
*       text
*----------------------------------------------------------------------*
FORM change_data_conv_exit  USING    piv_erkrs TYPE erkrs
                            CHANGING pct_data  TYPE gtyt_copadata.

  DATA: BEGIN OF ls_ddic,
          tabname   TYPE dfies-tabname,
          fieldname TYPE dfies-fieldname,
          rollname  TYPE dfies-rollname,
          convexit  TYPE dfies-convexit,
          funcname  TYPE tfdir-funcname,
          reference TYPE REF TO data,
        END OF ls_ddic,
        lt_ddic LIKE HASHED TABLE OF ls_ddic
                WITH UNIQUE KEY tabname fieldname.
  DATA: lt_ddic_info TYPE ddfields.
  DATA: ls_ddic_info LIKE LINE OF lt_ddic_info.
  DATA: lv_tabname   TYPE tabname.
  DATA: lv_fieldname TYPE fieldname.
  DATA: lv_value     TYPE REF TO data.

  FIELD-SYMBOLS: <ls_data>  LIKE LINE OF pct_data,
                 <lv_value> TYPE any.


  CONCATENATE 'CE0' piv_erkrs INTO lv_tabname.

  LOOP AT pct_data ASSIGNING <ls_data>.
    CHECK NOT <ls_data>-fval IS INITIAL.
    CLEAR: lv_tabname, ls_ddic.

    CONCATENATE 'CE0' piv_erkrs INTO lv_tabname.
    CONDENSE lv_tabname.

    MOVE <ls_data>-fnam TO lv_fieldname.
    READ TABLE lt_ddic INTO ls_ddic
      WITH TABLE KEY tabname   = lv_tabname
                     fieldname = lv_fieldname.
    IF sy-subrc EQ 0.
      IF ls_ddic-funcname IS INITIAL.
        CONTINUE.
      ENDIF.
    ELSE.
      CALL FUNCTION 'DDIF_FIELDINFO_GET'
        EXPORTING
          tabname        = lv_tabname
          fieldname      = lv_fieldname
        TABLES
          dfies_tab      = lt_ddic_info
        EXCEPTIONS
          not_found      = 1
          internal_error = 2
          OTHERS         = 3.
      IF sy-subrc <> 0.
        MESSAGE ID sy-msgid TYPE 'E' NUMBER sy-msgno
                WITH sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4.
      ENDIF.
      CLEAR ls_ddic.
      READ TABLE lt_ddic_info INTO ls_ddic_info INDEX 1.
      MOVE-CORRESPONDING ls_ddic_info TO ls_ddic.
      IF NOT ls_ddic-convexit IS INITIAL.
        CONCATENATE 'CONVERSION_EXIT_' ls_ddic_info-convexit '_INPUT'
                    INTO ls_ddic-funcname.
        CONDENSE ls_ddic-funcname.
        CREATE DATA lv_value TYPE (ls_ddic-rollname).
        MOVE lv_value TO ls_ddic-reference.
      ENDIF.
      INSERT ls_ddic INTO TABLE lt_ddic.
    ENDIF.
    IF NOT ls_ddic-funcname IS INITIAL.
      ASSIGN ls_ddic-reference->* TO <lv_value>.
      MOVE <ls_data>-fval TO <lv_value>.
      CALL FUNCTION ls_ddic-funcname
        EXPORTING
          input  = <lv_value>
        IMPORTING
          output = <lv_value>
        EXCEPTIONS
          OTHERS = 0.
      CLEAR <ls_data>-fval.
      MOVE <lv_value> TO <ls_data>-fval.
    ENDIF.
  ENDLOOP.

ENDFORM.
*&---------------------------------------------------------------------*
*&      Form  GET_TAX
*&---------------------------------------------------------------------*
*       text
*----------------------------------------------------------------------*
FORM get_tax  USING    pis_bkpf  TYPE gtys_bkpf
                       pis_bseg  TYPE gtys_bseg
                       piv_tabix TYPE sy-tabix
              CHANGING pct_tax   TYPE gtyt_tax.

  DATA: lv_amount TYPE bseg-wrbtr.
  DATA: lt_mwdat  TYPE STANDARD TABLE OF rtax1u15.
  DATA: ls_mwdat  LIKE LINE OF lt_mwdat.
  DATA: ls_tax    TYPE gtys_tax.
  DATA: lt_tax    TYPE gtyt_tax.

* Nur Steuer auf Positions-, nicht auf Kopfzeilen
  IF piv_tabix EQ 1.
    RETURN.
  ENDIF.

* Steuerkennzeichen muß mitgegeben sein
  IF NOT pis_bseg-mwskz IS INITIAL.
*   ACHTUNG
*   Der Baustein ermittelt die Steuer abhängig vom
*   übergebenen Wert. Da wir mit positiven Werten arbeiten
*   müssen wir hier natürlich auch ggf. das Vorzeichen drehen
*   sonst geht es schief.
    lv_amount = pis_bseg-netto.
    IF pis_bseg-shkzg EQ 'H'.
      lv_amount = pis_bseg-netto * -1.
    ENDIF.

*   Baustein für Ermittlung der Steuer aus dem Nettowert
*   Anmerkung: Gibt es auch für Bruttowerte
*              CALCULATE_TAX_FROM_GROSSAMOUNT
    CALL FUNCTION 'CALCULATE_TAX_FROM_NET_AMOUNT'
      EXPORTING
        i_bukrs           = pis_bkpf-bukrs
        i_mwskz           = pis_bseg-mwskz
        i_waers           = pis_bkpf-waers
        i_wrbtr           = lv_amount
      TABLES
        t_mwdat           = lt_mwdat
      EXCEPTIONS
        bukrs_not_found   = 1
        country_not_found = 2
        mwskz_not_defined = 3
        mwskz_not_valid   = 4
        ktosl_not_found   = 5
        kalsm_not_found   = 6
        parameter_error   = 7
        knumh_not_found   = 8
        kschl_not_found   = 9
        unknown_error     = 10
        account_not_found = 11
        txjcd_not_valid   = 12
        OTHERS            = 13.
    IF sy-subrc <> 0.
      MESSAGE ID sy-msgid TYPE 'E' NUMBER sy-msgno
              WITH sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4.
    ENDIF.
    LOOP AT lt_mwdat INTO ls_mwdat.
      MOVE-CORRESPONDING ls_mwdat TO ls_tax                 ##ENH_OK.
*     Wir brauchen für die Steuerzeilen aber auch noch das Steuerkenn-
*     zeichen. Daher umkopieren auf eigene Struktur
      MOVE: pis_bseg-mwskz TO ls_tax-mwskz.
      APPEND ls_tax TO lt_tax.
    ENDLOOP.
    IF NOT lt_mwdat IS INITIAL.
      APPEND LINES OF lt_tax TO pct_tax.
    ENDIF.
  ENDIF.

ENDFORM.
*&---------------------------------------------------------------------*
*&      Form  ADD_TAX_LINES
*&---------------------------------------------------------------------*
*       text
*----------------------------------------------------------------------*
FORM add_tax_lines  USING    pit_tax    TYPE gtyt_tax
                             pis_bkpf   TYPE gtys_bkpf
                             piv_buzei  TYPE bseg-buzei
                    CHANGING pct_acc_tx TYPE bapiactx09_tab
                             pct_cur_am TYPE bapiaccr09_tab.

  DATA: ls_tax    LIKE LINE OF pit_tax.
  DATA: ls_acc_tx LIKE LINE OF pct_acc_tx.
  DATA: ls_cur_am LIKE LINE OF pct_cur_am.
  DATA: lv_buzei  TYPE bseg-buzei.

  DATA: BEGIN OF ls_collect,
          mwskz TYPE mwskz,
          kschl TYPE kschl,
          ktosl TYPE ktosl,
          hkont TYPE hkont,
          wmwst TYPE wmwst,
          kawrt TYPE kawrt,
        END OF ls_collect.
  DATA: lt_collect LIKE HASHED TABLE OF ls_collect
                   WITH UNIQUE KEY mwskz ktosl kschl hkont.

* Steuern müssen wir schon ermittelt haben
  IF NOT pit_tax IS INITIAL.

*   Über den Collect werden Steuer und Steuerbasis summiert
*   und ggf. verdichtet
    LOOP AT pit_tax INTO ls_tax.
      MOVE-CORRESPONDING ls_tax TO ls_collect.
      COLLECT ls_collect INTO lt_collect.
    ENDLOOP.

*   Wir kopieren die letzte Zeilennummer
    lv_buzei = piv_buzei.

*   Und los
    LOOP AT lt_collect INTO ls_collect.
*     Eins auf die Buchungszeile da wir eine neue
*     Zeile erzuegen
      ADD 1 TO lv_buzei.
      CLEAR ls_acc_tx.
*     Übertragung der Steuerdaten
      MOVE: lv_buzei         TO ls_acc_tx-itemno_acc,
            ls_collect-hkont TO ls_acc_tx-gl_account,
            ls_collect-kschl TO ls_acc_tx-cond_key,
            ls_collect-ktosl TO ls_acc_tx-acct_key,
            ls_collect-mwskz TO ls_acc_tx-tax_code.
*     Und anhängen
      APPEND ls_acc_tx TO pct_acc_tx.

*     Da dies nur die Parameter für die Steuerzeile sind,
*     müssen wir auch noch eine Wertezeile erzeugen
      CLEAR ls_cur_am.
*     Gleiche Zeile wie Steuerzeile (Bezug!!!)
      MOVE: lv_buzei         TO ls_cur_am-itemno_acc,
            pis_bkpf-waers   TO ls_cur_am-currency,
            pis_bkpf-waers   TO ls_cur_am-currency_iso,
*           Ermittelter, summierter Steuerwert für Steuerkennzeichen
            ls_collect-wmwst TO ls_cur_am-amt_doccur,
*           Ermittelter, summierter Basiswert für Steuerkennzeichen
            ls_collect-kawrt TO ls_cur_am-amt_base,
            '00'             TO ls_cur_am-curr_type,
*           Umrechnungskurs für Fremdwährung
*           ACHTUNG!!!
*           Entweder hier KURSF ODER!!!! im Kopf WWERT mitgeben!!!
            pis_bkpf-kursf   TO ls_cur_am-exch_rate.
      APPEND ls_cur_am TO pct_cur_am.
    ENDLOOP.
  ENDIF.

ENDFORM.

 

 

Der Beitrag Buchungsbelege erstellen erschien zuerst auf Tricktresor.

Call Stack umgehen

$
0
0

In dem Artikel Pflegeview mit Datennavigation habe ich eine Möglichkeit vorgestellt, wie man Daten mit Hilfe einer Treedarstellung besser visualisieren und bearbeiten kann.

Leider gab es hier Umstand, dass mit jedem Doppelklick auf einen Eintrag im Tree ein neuer Pflegedialog aufgerufen wurde (Call Stack). Mit jeder Navigation wird also ein CALL SCREEN gemacht und somit der Call Stack erhöht. Der Call Stack ist auf eine bestimmte Anzahl Aufrufe beschränkt (ca. 60).  Selbst wenn der Call Stack höher wäre und somit „anwendertauglich“, so wäre es doch irgendwie falsch, es so zu machen.

Ein Freund hat dann den entscheidenden Hinweis gefunden, um den Call Stack zu durchbrechen.

Exception Class

Wir benötigen dazu eine eigene Ausnahmeklasse, die von CX_NO_CHECK erbt und die Parameter GROUPLEVEL und INDEX_OUTTAB als Parameter hat:

CLASS lcx_restart DEFINITION INHERITING FROM cx_no_check.
  PUBLIC SECTION.
    DATA grouplevel TYPE lvc_fname.
    DATA index_outtab TYPE lvc_index.
    METHODS constructor
      IMPORTING
        grouplevel   TYPE lvc_fname
        index_outtab TYPE lvc_index.
ENDCLASS.

CLASS lcx_restart IMPLEMENTATION.
  METHOD  constructor.
    CALL METHOD super->constructor
      EXPORTING
        textid   = textid
        previous = previous.
    me->index_outtab = index_outtab .
    me->grouplevel = grouplevel .
  ENDMETHOD.
ENDCLASS.

Hilfsklasse

Wir benötigen eine Hilfsklasse, die die Ausnahme aufruft und den VIEW_MAINTENANCE_CALL beendet:

CLASS lcl_helper IMPLEMENTATION.
  METHOD handle_item_double_click.
    handle_node_double_click(
      grouplevel   = grouplevel
      index_outtab = index_outtab ).
  ENDMETHOD.

  METHOD handle_node_double_click.

    RAISE EXCEPTION TYPE lcx_restart
      EXPORTING
        grouplevel   = grouplevel
        index_outtab = index_outtab.
  ENDMETHOD.

  METHOD install_handler.
    tree = i_tree.
    SET HANDLER handle_node_double_click FOR tree.
    SET HANDLER handle_item_double_click FOR tree.
  ENDMETHOD.
ENDCLASS.

Ereignisregistrierung

Die Ereignisregistrierung kann weiterhin in der Navigationsklasse erfolgen. Die Handler müssen jedoch in der externen Klasse LCL_HELPER installiert werden:

 METHOD register_events.

    lcl_helper=>install_handler( mo_tree ).

    mo_tree->set_registered_events( VALUE #(
          "Used here for applying current data selection
          ( eventid = cl_gui_column_tree=>eventid_node_double_click )
          ( eventid = cl_gui_column_tree=>eventid_item_double_click )
          "Important! If not registered nodes will not expand ->No data
          ( eventid = cl_gui_column_tree=>eventid_expand_no_children ) ) ) .

  ENDMETHOD.

Zur Installation der Handler übergeben wir einfach die Instanz des Trees um in der Hilfsklasse auf die Ereignisse reagieren zu können.

View_Maintenance_Call

Der eigentliche Clou ist jedoch, dass wir den Aufruf des Funktionsbaustein VIEW_MAINTENANCE_CALL mit einem TRY-CATCH-Block kapseln:

  METHOD view_maintenance_call.
    TRY.
        CALL FUNCTION 'VIEW_MAINTENANCE_CALL'
          EXPORTING
            action      = 'S'
            view_name   = ms_tvdir-tabname
          TABLES
            dba_sellist = it_sellist
          EXCEPTIONS
            OTHERS      = 15.
      CATCH lcx_restart INTO DATA(restart).
        handle_selection( EXPORTING
          grouplevel   = restart->grouplevel
          index_outtab = restart->index_outtab ).
    ENDTRY.
  ENDMETHOD.

Wir das Ereignis LCX_RESTART ausgelöst, dann starten wir den Pflegedialog einfach erneut. Allerdings nun mit einem abgebauten Call Stack.

Call Stack umgehen

Den Call Stack durch einen externen RAISE EXCEPTION abzubrechen könnte auch in anderen Fällen hilfreich sein. In welchen, das musst du selber herausfinden… 😉

 

Der Beitrag Call Stack umgehen erschien zuerst auf Tricktresor.

Bilder aus MIME-Repository anzeigen

$
0
0

Ich präsentiere: Das hässlichste Logo aller Zeiten:

Allerdings hat es auch einen Vorteil: Taucht dieses Bild irgendwo auf, weiß man sofort: Es handelt sich um ein Demo-Programm.

SAP-Web-Repository

Das Bild ist im SAP-Web Repository gespeichert. Dieses Repository wird mit Transaktion SMW0 aufgerufen. Zur Auswahl stehen „HTML-Schablonen“ und „Binäre Daten“. Wähle „Binäre Daten“ aus:

Wenn du weißt, wie der Name des Bildes lautet, kannst du diesen vorbelegen:

Die entsprechenden Dateien im Web-Repository werden angezeigt:

Einlesen eines Web-Repository-Objektes

Das Einlesen und des Bildes erfolgt mit Hilfe der Funktionsbausteine WWW_GET_MIME_OBJECT und DP_CREATE_URL. Das Beispielprogramm zeigt das Bild in einem Docking-Container auf dem Selektionsbild an:

Code

REPORT.

PARAMETERS dummy.

CLASS mime_picture DEFINITION.
  PUBLIC SECTION.
    CLASS-METHODS get IMPORTING name TYPE clike RETURNING VALUE(url) TYPE w3url.
ENDCLASS.

CLASS mime_picture IMPLEMENTATION.
  METHOD get.

    DATA query_table TYPE STANDARD TABLE OF w3query.
    DATA query_line TYPE w3query.
    DATA html_table TYPE STANDARD TABLE OF w3html .
    DATA html_line TYPE w3html .
    DATA return_code TYPE w3param-ret_code.
    DATA content_type TYPE  w3param-cont_type.
    DATA content_length TYPE  w3param-cont_len.
    DATA pic_data TYPE STANDARD TABLE OF w3mime .

    query_line-name = '_OBJECT_ID'.
    query_line-value = name.
    APPEND query_line TO query_table.

    CALL FUNCTION 'WWW_GET_MIME_OBJECT'
      TABLES
        query_string        = query_table
        html                = html_table
        mime                = pic_data
      CHANGING
        return_code         = return_code
        content_type        = content_type
        content_length      = content_length
      EXCEPTIONS
        object_not_found    = 1
        parameter_not_found = 2
        OTHERS              = 3.

    CALL FUNCTION 'DP_CREATE_URL'
      EXPORTING
        type     = 'image'
        subtype  = cndp_sap_tab_unknown
        size     = content_length
        lifetime = cndp_lifetime_transaction
      TABLES
        data     = pic_data
      CHANGING
        url      = url
      EXCEPTIONS
        OTHERS   = 1.

  ENDMETHOD.
ENDCLASS.

INITIALIZATION.

  DATA(go_pic) = NEW cl_gui_picture(
                  parent = NEW cl_gui_docking_container(
                                 side  = cl_gui_docking_container=>dock_at_bottom
                                 ratio = 80 ) ).
  go_pic->load_picture_from_url( mime_picture=>get( 'ENJOYSAP_LOGO' ) ). 
  go_pic->set_display_mode( cl_gui_picture=>display_mode_fit ).

Anstelle der beiden Funktionsbausteine WWW_GET_MIME_OBJECT und DP_CREATE_URL kann auch der Funktionsbaustein DP_PUBLISH_WWW_URL verwendet werden.

Der Beitrag Bilder aus MIME-Repository anzeigen erschien zuerst auf Tricktresor.

Hacking SAPGUI

$
0
0

Heute bin ich zufällig auf etwas gestoßen, dass ich erst nicht glauben konnte. Aber eigentlich hätte es klar sein müssen. Ich zeige dir erst einmal ein Bild:

Icon an ungewöhnlicher Stelle…

Vielleicht ist deine erste Reaktion genauso wie meine:

via GIPHY

Folgende Controls verwende ich für diesen kleinen Hack:

  • CL_GUI_PICTURE
  • CL_GUI_GOS_CONTAINER

Normalerweise wird der GOS-Container nicht explizit aufgerufen, sondern nur implizit von der Klasse CL_GOS_MANAGER verwendet.

Container ist Container

Im Generic Object Services Menü wird normalerweise ein Pull-Down-Menü angezeigt:

Das GOS-Menü in Aktion

In einem Menü können jedoch alle Controls angezeigt werden. Sinnvoll sind hier nur wenige, denn der GOS-Container ist ziemlich klein. Ein Menü passt dort gut hinein. Aber auch ein Bild oder Icon.

Anzeige Icon in Container

Die Anzeige eines Bildes oder Icons ist ziemlich simpel und schnell erledigt:

REPORT.

PARAMETERS p_test.

INITIALIZATION.

  DATA(picture) = NEW cl_gui_picture( parent = NEW cl_gui_gos_container( width = 38 ) ).
  DATA url TYPE cndp_url.
  CALL FUNCTION 'DP_PUBLISH_WWW_URL'
    EXPORTING
      objid                 = 'ACHTUNG'
      lifetime              = cndp_lifetime_transaction
    IMPORTING
      url                   = url
    EXCEPTIONS
      dp_invalid_parameters = 1
      no_object             = 2
      dp_error_publish      = 3.
  IF sy-subrc = 0.
    picture->load_picture_from_url_async( url = url ).
    picture->set_display_mode( picture->display_mode_fit ).
  ENDIF.

Da geht noch mehr…

Nachdem ich ein bisschen herumgespielt habe, ist mir eine Eigenschaft aufgefallen, die nicht ganz offensichtlich ist und die ich auch so nicht erwartet hätte. So, wie man mehrere Docking-Container an ein Dynpro andocken kann, so kann man auch den CL_GUI_GOS_CONTAINER mehrfach erzeugen.

Zusätzlich können in einem Picture-Control auch die Klick- und Doppelklick-Ereignisse aktiviert und verwendet werden.

Auch das Ein- und Ausblenden des Controls ist möglich (Methode SET_VISIBLE).

Code

Folgendes kleine Programm zeigt die Möglichkeiten, die sich ergeben:

Mehrere GOS-Container

Durch Klicken des Parameters P_SHOW wird das Bild „ACHTUNG“ ein- und ausgeblendet. Ein Klick auf eines der Icons zeigt eine Info-Meldung. Ich nutze solch kleine Spielereien immer gerne, um mich an die neue Syntax zu gewöhnen und auszutesten, was möglich und sinnvoll ist. Hier habe ich die Gelegenheit am Schopfe gepackt und auch funktional programmiert, so dass Method-Chaining über mehrere Methoden hinweg auf ein und das selbe Objekt möglich ist. 

REPORT.

PARAMETERS p_test.
PARAMETERS p_show AS CHECKBOX DEFAULT 'X' USER-COMMAND dummy.

CLASS info DEFINITION.
  PUBLIC SECTION.
    METHODS icon IMPORTING name TYPE clike RETURNING VALUE(info) TYPE REF TO info.
    METHODS pic  IMPORTING name TYPE clike RETURNING VALUE(info) TYPE REF TO info.
    METHODS constructor IMPORTING width TYPE i.
    METHODS with_text IMPORTING text TYPE clike RETURNING VALUE(info) TYPE REF TO info..
    METHODS hide.
    METHODS show.
  PROTECTED SECTION.
    DATA picture TYPE REF TO cl_gui_picture.
    METHODS handle_click FOR EVENT picture_click OF cl_gui_picture.
    DATA text TYPE string.
ENDCLASS.

CLASS info IMPLEMENTATION.

  METHOD constructor.
    picture = NEW cl_gui_picture( parent = NEW cl_gui_gos_container( width = width ) ).
    picture->set_registered_events( VALUE #(
                ( eventid = cl_gui_picture=>eventid_picture_click )
                ) ).
    SET HANDLER handle_click FOR picture.
  ENDMETHOD.
  METHOD icon.

    picture->load_picture_from_sap_icons( name ).
    picture->set_display_mode( picture->display_mode_fit ).
    info = me.
  ENDMETHOD.
  METHOD pic.
    DATA url TYPE cndp_url.
    CALL FUNCTION 'DP_PUBLISH_WWW_URL'
      EXPORTING
        objid                 = CONV w3objid( name )
        lifetime              = cndp_lifetime_transaction
      IMPORTING
        url                   = url
      EXCEPTIONS
        dp_invalid_parameters = 1
        no_object             = 2
        dp_error_publish      = 3.
    IF sy-subrc = 0.
      picture->load_picture_from_url_async( url = url ).
      picture->set_display_mode( picture->display_mode_fit ).
    ENDIF.

    info = me.

  ENDMETHOD.

  METHOD with_text.
    me->text = text.
    info = me.
  ENDMETHOD.

  METHOD handle_click.
    CHECK text IS NOT INITIAL.
    MESSAGE text TYPE 'I'.
  ENDMETHOD.

  METHOD hide.
    picture->set_visible( space ).
  ENDMETHOD.
  METHOD show.
    picture->set_visible( 'X' ).
  ENDMETHOD.

ENDCLASS.


INITIALIZATION.
  DATA(info)   = NEW info( 38 )->pic( 'ACHTUNG' ).
  DATA(green)  = NEW info( 39 )->icon( icon_led_green )->with_text( 'Alles ok' ).
  DATA(yellow) = NEW info( 39 )->icon( icon_led_yellow )->with_text( 'hmpfffff' ).
  DATA(red)    = NEW info( 39 )->icon( icon_led_red )->with_text( 'error. error. error.' ).


AT SELECTION-SCREEN OUTPUT.
  CASE p_show.
    WHEN abap_true.
      info->show( ).
    WHEN abap_false.
      info->hide( ).
  ENDCASE.

WTF?!

Man kann übrigens jedes Control in den Container packen! Auch Text-Edit-Controls und HTML-Viewer… 😉 

REPORT.
PARAMETERS p_test.
INITIALIZATION.

  DATA(html) = NEW cl_gui_html_viewer( parent = NEW cl_gui_gos_container( width = 400 ) ).

  DATA url TYPE c LENGTH 100.
  DATA data TYPE STANDARD TABLE OF w3_html.

  data = VALUE #( ( '<html><head><style>body { margin: 0; background-color: #f9f9f9; color: #666680; font: 20px "Arial"  }</style>'
        && '<title>Hacking SAPGUI</title></head><body background=#aabbcc><marquee behavior=alternate>tricktresor.com</marquee></body></html>' ) ).


  html->load_data( IMPORTING assigned_url = url
                    CHANGING data_table   = data ).
  html->show_url( url ).

  DATA(text) = NEW cl_gui_textedit( parent = NEW cl_gui_gos_container( width = 400 ) ).
  text->set_statusbar_mode( 0 ).
  text->set_toolbar_mode( 0 ).
  text->set_textstream( `Enter your name` ).
  text->select_lines( 1 ).
  cl_gui_control=>set_focus( text ).

SALV-Grid

Für ein ALV ist sicherlich nicht genug Platz…? Denkste:

CL_SALV_TABLE in CL_GUI_GOS_CONTAINER

Der Beitrag Hacking SAPGUI erschien zuerst auf Tricktresor.

GUI-Designer „guidrasil“

$
0
0

Vor langer Zeit habe ich mich mit der automatischen und generischen Erzeugung und Verwaltung von SAPGUI-Controls beschäftigt.

Was sind Controls?

Controls sind ActiveX-Komponenten (auch OCX-Komponenten genannt), die im SAPGUI verwendet werden können und mit diesem ausgeliefert werden. Diese Windows-Komponenten werden über entsprechende Klassen im SAP angesprochen und erzeugt. Die Erzeugung erfolgt in der Regel ähnlich:

  1. CREATE OBJECT <object reference>
  2. <object reference>-SET_….
  3. Manche Controls benötigen noch ein explizites DISPLAY.

Die typischen GUI-Controls sind:

  • CL_GUI_ALV_GRID
  • CL_GUI_TEXTEDIT
  • CL_GUI_PICTURE
  • CL_GUI_CALENDAR
  • CL_GUI_HTML_VIEWER
  • CL_GUI_SIMPLE_TREE
  • CL_GUI_COLUMN_TREE
  • CL_GUI_LIST_TREE

Controls benötigen einen Container, in dem sie platziert werden können. Lustiger Weise erben die Container-Klassen von der gleichen Klasse wie die Controls selber: CL_GUI_CONTROL. Die Container erben dann alle von CL_GUI_CONTAINER:

  • CL_GUI_DOCKING_CONTAINER
  • CL_GUI_CUSTOM_CONTAINER
  • CL_GUI_DIALOGBOX_CONTAINER

Eine Sonderstellung nehmen die Splitter-Controls ein, denn sie stellen ebenfalls wieder Container zur Verfügung:

  • CL_GUI_SPLITTER_CONTAINER
  • CL_GUI_EASY_SPLITTER_CONTAINER

Programmierung von Controls

Eine typische Programmierung sieht wie folgt aus:

  • Erzeuge einen Container
  • Erzeuge das Control in diesem Container
  • Setze Eigenschaften des Controls

In diesem Demoprogramm zeige ich kurz, wie ein Textedit-Control in einem Docking-Container aufgebaut wird.

Setzen von Eigenschaften

Die Ansteuerung der Controls ist natürlich immer unterschiedlich, da sich die Control unterscheiden. Ein Picture-Control ist nun mal immer read-only, Ein Textedit-Control nicht. Das ist auch genau das Problem: Wenn ich ein Control häufig verwende, dann kenne ich die Eigenschaften und notwendigen Attribute. Wenn nicht, dann muss ich suchen. Zudem ist die Aktivierung von Attributen manchmal per BOOLEAN notwendig (X und space) und manchmal verlangt das Control „1“ und „0“.

Idee eines GUI-Designers

Da die Controls alle von der Klasse CL_GUI_CONTROL abstammen, ist es möglich jedes Control über eine generelle Methodenschnittstelle zu erzeugen. Ich kann also einer Methode irgend einen Container übergeben (egal, ob Docking-Container, Custom-Container oder Dialogbox) und das Control wieder zurück bekommen. Oder ich kann ein Control erzeugen und es in einer Tabelle speichern.

Das folgende Beispielprogramm macht genau das: Durch die Einstellungen auf dem Selektionsbildschirm wird definiert, welche Art von Control auf welcher Seite angedockt werden soll. Mit <ENTER> werden der Container sowie das Control erzeugt und in einer internen Tabelle abgelegt:

Demoprogramm

REPORT zguidrsail_demo_generic_ctrl.

SELECTION-SCREEN BEGIN OF BLOCK ctrl WITH FRAME TITLE TEXT-ctl.
PARAMETERS p_text RADIOBUTTON GROUP ctrl DEFAULT 'X'.
PARAMETERS p_icon RADIOBUTTON GROUP ctrl.
SELECTION-SCREEN END OF BLOCK ctrl.

SELECTION-SCREEN BEGIN OF BLOCK side WITH FRAME TITLE TEXT-sid.
PARAMETERS p_left RADIOBUTTON GROUP side DEFAULT 'X'.
PARAMETERS p_rigt RADIOBUTTON GROUP side.
PARAMETERS p_botm RADIOBUTTON GROUP side.
SELECTION-SCREEN END OF BLOCK side.

CLASS ctrl_demo DEFINITION.
  PUBLIC SECTION.
    METHODS add_text
      IMPORTING
        side TYPE i.
    METHODS add_icon
      IMPORTING
        side TYPE i.
  PROTECTED SECTION.
    TYPES: BEGIN OF ts_object,
             container TYPE REF TO cl_gui_container,
             control   TYPE REF TO cl_gui_control,
           END OF ts_object.

    DATA objects TYPE STANDARD TABLE OF ts_object.
    METHODS append_control
      IMPORTING
        container TYPE REF TO cl_gui_container
        control   TYPE REF TO cl_gui_control.

ENDCLASS.

CLASS ctrl_demo IMPLEMENTATION.
  METHOD add_text.
    DATA(parent) = NEW cl_gui_docking_container( side = side ratio = 20 ).
    DATA(textedit) = NEW cl_gui_textedit( parent = parent ).
    textedit->set_text_as_stream( VALUE texttab( ( tdline = `This is a demonstration` ) ) ).
    append_control( container = parent control = textedit ).
  ENDMETHOD.
  METHOD add_icon.
    DATA(parent) = NEW cl_gui_docking_container( side = side ratio = 20 ).
    DATA(icon) = NEW cl_gui_picture( parent = parent ).
    icon->load_picture_from_sap_icons( icon_message_question ).
    icon->set_display_mode( cl_gui_picture=>display_mode_fit_center ).
    append_control( container = parent control = icon ).
  ENDMETHOD.
  METHOD append_control.
    APPEND VALUE #( container = container control = control ) TO objects.
  ENDMETHOD.
ENDCLASS.

INITIALIZATION.
  DATA(demo) = NEW ctrl_demo( ).

AT SELECTION-SCREEN.

  CASE 'X'.
    WHEN p_left.
      DATA(side) = cl_gui_docking_container=>dock_at_left.
    WHEN p_rigt.
      side = cl_gui_docking_container=>dock_at_right.
    WHEN p_botm.
      side = cl_gui_docking_container=>dock_at_bottom.
  ENDCASE.

  CASE 'X'.
    WHEN p_text.
      demo->add_text( side = side ).
    WHEN p_icon.
      demo->add_icon( side = side ).
  ENDCASE.

Dynamische Verwaltung

Da ich nun alle erzeugten Container und Controls in einer Tabelle habe, kann ich auch auf die Objekte und deren Eigenschaften zugreifen. Ich könnte zum Beispiel die Tabelle durchgehen und fragen: Ist im Feld CONTAINER ein Objekt der Klasse CL_GUI_DOCKING_CONTAINER? Wenn ja, frage ich das Control nach seinen wichtigen Eigenschaften: RATIO und SIDE:

IF itab-container IS INSTANCE OF cl_gui_docking_container.
  DATA dock TYPE REF TO cl_gui_docking_container.
  dock ?= itab-container.
  DATA(side) = dock->get_docking_side( ).
  dock->get_ratio( ratio = DATA(ratio) ).
ENDIF.

Auf diese Weise könnte ich mir alle wichtigen Eigenschaften eines Controls beschaffen und speichern.

Dynamische Erzeugung

Mit Hilfe von RTTI (Run Time Type Information) in Form der Klasse CL_ABAP_TYPEDESCR kann ich sogar den Klassennamen des Objektes ermitteln:

DATA(clsnam) = cl_abap_typedescr=>describe_by_object_ref( itab-container )->get_relative_name( ).
Wenn ich diesen habe, dann ich das Objekt auch dynamisch erzeugen:

DATA: container TYPE REF TO cl_gui_container, 
      exc_ref TYPE REF TO cx_root.

DATA: ptab TYPE abap_parmbind_tab.

ptab = VALUE #( 
                ( name  = 'SIDE' 
                  kind  = cl_abap_objectdescr=>exporting 
                  value = REF #( side ) ) 
                ( name  = 'RATIO' 
                  kind  = cl_abap_objectdescr=>exporting 
                  value = REF #( ratio ) ) ).

TRY. 
    CREATE OBJECT container TYPE (clsnam) 
      PARAMETER-TABLE ptab. 
  CATCH cx_sy_create_object_error INTO exc_ref. 
    MESSAGE exc_ref->get_text( ) TYPE 'I'. 
ENDTRY.

Eine dynamische Erzeugung ist jedoch gar nicht notwendig, denn ich kenne ja den Klassennamen und kann die Erzeugung wiederum an eine Erbauer-Klasse auslagern.

guidrasil

Nach dem oben beschriebenen Prinzip funktioniert der GUI-Designer ungefähr. Eine wichtige Eigenschaft ist jedoch, dass man erst auswählen muss, auf welcher Seite man einen Docking-Container erstellen möchte. In diesem Docking-Container wird dann ein Splitter erzeugt, der oben eine Toolbar integriert und unten erneut einen leeren Container anzeigt.

 

In der Toolbar kann man dann die gewünschten Controls oder Splitter-Container auswählen. Der GUI-Designer merkt sich, welche Objekte an welcher Stelle erzeugt werden. Das Erzeugen der einzelnen Controls sowie das Speichern der unterstützten Eigenschaften übernimmt die Erbauer-Klasse, die es für jedes Control gibt.

Zusätzlich stellt die Erbauer-Klasse auch noch einen Dialog zur Verfügung, in dem die Eigenschaften des Control eingestellt werden können.

I’d rather write code that writes code than write code

Eine weitere Eigenschaft der Erbauer-Klasse ist, dass jede Erbauer-Klasse ja genau weiß, wie das eigene Control erzeugt werden muss. Das heißt, es kann auch Code zur Verfügung stellen, der für die Erzeugung des Controls notwendig ist.

Der GUI-Designer weiß genau, welche Controls in welcher Reihenfolge erzeugt werden müssen. Der Designer muss also nur noch jedes Control nach dem Erzeugungscode fragen…

abapGit

Der GUI-Designer guidrasil ist verfügbar per abapGit auf Github:

https://github.com/tricktresor/guidrasil

 

Der Beitrag GUI-Designer „guidrasil“ erschien zuerst auf Tricktresor.

Genereller Suchhilfe-Exit zur Anzeige von Icons

$
0
0

Die Verwendung von grafischen Elementen im SAP-System ist doch arg eingeschränkt. Icons sind im Grunde die einzige Möglichkeit, etwas Farbe und Abwechslung in eine ALV-Liste zu bekommen. Schön ist es, wenn der Anwender selber einstellen kann, welche Icons zu welchen Informationen angezeigt werden sollen. Eine Status bietet sich perfekt an, um diesen durch ein geeignetes Icon visuell darzustellen.

Für die Definition des zu verwendenden Icons sollte jedoch der Name des Icons verwendet werden (ICON_OKAY, ICON_MESSAGE_INFORMATION etc) anstelle der entsprechenden Icon-ID (@0V@, @19@ usw). Der Vorteil der Icon-ID ist zwar, dass dann das Icon direkt angezeigt wird, wenn das Feld nicht Eingabe bereit ist, allerdings ist die ID überhaupt nicht sprechend und die Pflege wird zum Glücksspiel.

Wenn allerdings der Name des Icons verwendet wird, dann sieht man eben nur den Namen, aber nicht das zugehörige Icon.

Beispiel

Um zu verdeutlichen, wie ich die Icons einsetze, habe ich eine kleine Statustabelle definiert:

Der Text sollte natürlich besser in eine separat definierte Texttabelle ausgelagert werden, aber in diesem Fall soll es nur als Beispiel dienen.

Ich habe also eine Tabelle mit verschiedenen Status. Irgendein Eintrag/ Objekt kann einen dieser Status haben. In der Listausgabe wird zu einem Status der Name des zugeordneten Icons ermittelt und als Icon ausgegeben.

Darstellung eines Icons

Ein Icon besteht immer mindestens aus dem Icon selbst. Ein Icon muss immer Anfang eines Feldes stehen! Ich kann in jedem Anzeigefeld ein Icon ausgeben, indem ich die Icon-ID der Icons von zwei Klammeraffen umgeben eintrage: @1A@.

Ein Icon kann aber auch noch einen Text besitzen, der mit angezeigt wird. Ebenso kann ein Icon eine 40 Zeichen lange Quickinfo enthalten, die bei MouseOver angezeigt wird.

Erzeugen eines Icons

Der richtige Weg, um Icons für die Ausgabe in einem Feld vorzubereiten ist die Verwendung des Funktionsbausteins ICON_CREATE:

    CALL FUNCTION 'ICON_CREATE'
      EXPORTING
        name            = 'ICON_DELETE'
        text            = 'Löschen'
        info            = 'Markiertes Objekt löschen'
        add_stdinf      = ' '
      IMPORTING
        result          = lv_icon
      EXCEPTIONS
        OTHERS          = 3.

Dem Funktionsbaustein ist es egal, ob der Name oder die ID des Icons verwendet wird. Es kann also der Text „ICON_DELETE“ übergeben werden oder die Konstante ICON_DELETE. Die Icon-Konstanten sind im Type-Pool ICON definiert und in jedem ABAP-Programm vorhanden.

Tipp:

Erstelle eine globale Klasse mit einer Methode, die dir das fertige Icon direkt zurück gibt. Die Erzeugung von Icons ist dann sehr einfach:

DATA(lv_icon) = zcl_icon=>create( icon_okay ).

Suchhilfe

Zurück zum Thema. Ich wollte ja eigentlich was ganz anderes…

Eine Suchhilfe zum Status wäre sehr sinnvoll:

  1. Der Anwender kann in einer ALV-Gridliste durch betätigen der F4-Hilfe auf dem Statusfeld alle möglichen Status sehen (Legende)
  2. Bei der Auswahl eines Status ist eine Suchhilfe sowieso sinnvoll.

Leider wird in diesem Fall ja nur der Name des Icons angezeigt und nicht das Icon selber. Das möchte ich ändern. Allerdings möchte ich nicht für genau diese eine Tabelle einen Suchhilfe-Exit basteln, der genau für diese Tabelle das Statusicon anzeigt. Ich möchte eine Suchhilfe erstellen, die für alle ähnlichen Tabellen ebenfalls verwendet werden kann.

Suchhilfe-Parameter

Zu diesem Zweck verwende ich die wenig bekannte Möglichkeit, in der Suchhilfe einen Parameter zu definieren. ein Parameter ist kein Feld der zugrunde liegenden Tabelle, sondern ein separat definierter Eintrag. Ein Parameter wird nicht in der Suchhilfe ausgegeben sondern dient der Übergabe an den Funktionsbausteins eines Suchhilfe-Exits.

Suchhilfe-Exit Funktionsbaustein

Ein Suchhilfe-Exit ist ein Funktionsbaustein mit einer definierten Schnittstelle. Zur Erstellung eines Suchhilfe-Exits wird am besten der Funktionsbaustein F4IF_SHLP_EXIT_EXAMPLE kopiert, der auch in der Hilfe zum Dynprofeld „Suchhilfe-Exit“ erwähnt wird.

In diesem Funktionsbaustein kann die Suchhilfe zu verschiedenen Zeitpunkten manipuliert werden (Datenselektion, Darstellung etc).

Mit Hilfe des Funktionsbausteins F4UT_PARAMETER_VALUE_GET kann der Wert zu einem Parameter aus der Suchhilfe ermittelt werden.

Vorgehen

Ich möchte einen Suchhilfe-Exit erstellen, der aus der Suchhilfe ermittelt, in welchem Feld der Name des Icons steht und in welchem Feld das Icon selbst angezeigt werden soll.

Das Ergebnis sieht so aus:

Lösung

Suchhilfe definieren

Als erstes muss ich in der Transaktion SE11 eine Suchhilfe anlegen:

Suchhilfe-Exit anlegen

Den Suchhilfe-Exit kopiere ich vom Funktionsbaustein F4IF_SHLP_EXIT_EXAMPLE.

 

Das Coding sieht folgendermaßen aus:

FUNCTION z_f4if_shlp_exit_icon.
*"----------------------------------------------------------------------
*"*"Lokale Schnittstelle:
*"  TABLES
*"      SHLP_TAB TYPE  SHLP_DESCT
*"      RECORD_TAB STRUCTURE  SEAHLPRES
*"  CHANGING
*"     VALUE(SHLP) TYPE  SHLP_DESCR
*"     VALUE(CALLCONTROL) LIKE  DDSHF4CTRL STRUCTURE  DDSHF4CTRL
*"----------------------------------------------------------------------

  DEFINE get_param.
    CALL FUNCTION 'F4UT_PARAMETER_VALUE_GET'
      EXPORTING
        parameter         = &1
        fieldname         = '*'
      IMPORTING
        value             = lv_parameter_value
      TABLES
        shlp_tab          = shlp_tab
        record_tab        = record_tab
      CHANGING
        shlp              = shlp
        callcontrol       = callcontrol
      EXCEPTIONS
        parameter_unknown = 1.
    IF sy-subrc = 0.
      &2 = lv_parameter_value.
    ENDIF.
  END-OF-DEFINITION.

  DEFINE icon_create.
    CALL FUNCTION 'ICON_CREATE'
      EXPORTING
        name            = &1
        info            = &2
        add_stdinf      = ''
      IMPORTING
        result          = &3
      EXCEPTIONS
        OTHERS          = 3.

  END-OF-DEFINITION.

*== Info:
* Dieser Baustein kann in Suchhilfen als Suchhilfe-Exit verwendet werden
*== Zweck:
* Anzeige des Icons zu einem Icon-Namen, der in der Tabelle vorhanden ist
*== Voraussetzung:
* Spalte mit dem Namen eines Icons
*

  "Tabelle für den Iconnamen, der aus der RESULTTAB ermittelt wird
  DATA lt_col_iconname TYPE STANDARD TABLE OF icon_name.
  "Tabelle für den Inhalt der Info zum Icon
  DATA lt_col_iconinfo TYPE STANDARD TABLE OF icon_text.
  "Tabelle für das Aufbereitete Icon, das an die RESULTTAB übergeben wird
  DATA lt_col_icontext TYPE STANDARD TABLE OF icon_text.
  DATA lv_icontext TYPE icon_text.

  "Generischer Parameter
  DATA lv_parameter_value       TYPE  ddshvalue.
  "Name der Spalte, die den Iconnamen enthält
  DATA lv_column_with_icon_name TYPE  shlpfield.
  "Name der Spalte, die den Icontext enthält
  DATA lv_column_with_icon_info TYPE  shlpfield.
  "Name der Spalte, in der das aufbereitete Icon dargestellt werden soll
  DATA lv_column_for_display    TYPE  shlpfield.


* EXIT immediately, if you do not want to handle this step
  IF callcontrol-step <> 'SELONE' AND
     callcontrol-step <> 'SELECT' AND
     callcontrol-step <> 'SELONE' AND
     callcontrol-step <> 'PRESEL' AND
     callcontrol-step <> 'SELECT' AND
     callcontrol-step <> 'DISP'.
    EXIT.
  ENDIF.

*"----------------------------------------------------------------------
* STEP DISP     (Display values)
*"----------------------------------------------------------------------
  IF callcontrol-step = 'DISP'.

    get_param 'COLUMN_WITH_ICON_NAME'    lv_column_with_icon_name.
    get_param 'COLUMN_WITH_ICON_INFO'    lv_column_with_icon_info.
    get_param 'COLUMN_FOR_ICON_DISPLAY'  lv_column_for_display.


    "Ermitteln aller ICON_NAMEN der einzelnen Einträge
    CALL FUNCTION 'F4UT_PARAMETER_VALUE_GET'
      EXPORTING
        parameter         = lv_column_with_icon_name
        fieldname         = '*'
      TABLES
        shlp_tab          = shlp_tab
        record_tab        = record_tab
        results_tab       = lt_col_iconname
      CHANGING
        shlp              = shlp
        callcontrol       = callcontrol
      EXCEPTIONS
        parameter_unknown = 1
        OTHERS            = 2.
    IF sy-subrc = 0.
      "Wenn alles geklappt hat, dann sind in Tabelle LT_COL_ICONNAME die Namen
      "der Icons aus dem Parameter COLUMN_WITH_ICON_NAME

      "Nun noch die die Texte für die Quickinfo ermitteln
      CALL FUNCTION 'F4UT_PARAMETER_VALUE_GET'
        EXPORTING
          parameter         = lv_column_with_icon_info
          fieldname         = '*'
        TABLES
          shlp_tab          = shlp_tab
          record_tab        = record_tab
          results_tab       = lt_col_iconinfo
        CHANGING
          shlp              = shlp
          callcontrol       = callcontrol
        EXCEPTIONS
          parameter_unknown = 1
          OTHERS            = 2.
      if sy-subrc > 0.
        "Macht nichts: Es ist kein Feld definiert/ vorhanden, das zum Icon angezeigt werden soll
        "Die Tabelle LT_COL_ICONINFO ist dann halt leer
      endif.


      LOOP AT lt_col_iconname INTO DATA(lv_iconname).
        "Info zum Icon lesen:
        READ TABLE lt_col_iconinfo INTO DATA(lv_iconinfo) INDEX sy-tabix.
        "Aufbereitung des Icons zur Darstellung
        icon_create lv_iconname lv_iconinfo lv_icontext.
        APPEND lv_icontext TO lt_col_icontext.
      ENDLOOP.
    ENDIF.

    "Alle aufbereiteten Icons an RESULTTAB übergeben
    CALL FUNCTION 'F4UT_PARAMETER_RESULTS_PUT'
      EXPORTING
        parameter         = lv_column_for_display
      TABLES
        shlp_tab          = shlp_tab
        record_tab        = record_tab
        source_tab        = lt_col_icontext
      CHANGING
        shlp              = shlp
        callcontrol       = callcontrol
      EXCEPTIONS
        parameter_unknown = 1
        OTHERS            = 2.
    IF sy-subrc <> 0.
      EXIT.
    ENDIF.
    EXIT.
  ENDIF.

ENDFUNCTION.

 

 

 

 

 

Der Beitrag Genereller Suchhilfe-Exit zur Anzeige von Icons erschien zuerst auf Tricktresor.


Popup nach verbuchten Daten

$
0
0

Trotz UI5, Webservices und HANA sind Verbuchungsbausteine nach wie vor ein wichtiger Bestandteil eines SAP-Systems. Ein Verbuchungsbaustein ist ein Funktionsbaustein mit der Eigenschaft „Verbucher“.

Eigenschaften eines Funktionsbausteins

Ein Verbuchungsbaustein kann wie ein ganz normaler Funktionsbaustein aufgerufen werden. Allerdings kann ein Verbuchungsbaustein keine Rückgabeparameter besitzen, denn mit Aufruf in der Verbuchung – Zusatz IN UPATE TASK – wird die Ausführung an einen anderen Prozess übergeben. Läuft bei einem Verbuchungsbaustein etwas schief, dann gibt es einen Kurzdump.

Verbucher

Die Verbucherfunktionalität wird in erster Linie dafür genutzt, um das aufwändige Speichern im Hintergrund durchführen zu lassen, so dass der Anwender schnell weiter arbeiten kann. Die Daten, die an die Verbuchung übergeben werden, müssen „wasserdicht“, also in sich stimmig und korrekt sein. Es geht also nicht mehr um Prüfungen, sondern um das reine Speichern der Daten. Je nach Prozess kann die Speicherung ziemlich lange dauern. In SAP-Standardanwendungen hat man manchmal das Phänomen, dass man einen Beleg speichert und ihn sofort wieder ändern möchte. Der Beleg befindet sich jedoch noch in der Verbuchung und ist noch gesperrt. 

V1- und V2-Verbucher

Es gibt V1-Verbucher und V2-Verbucher. Die V1-Verbucher enthalten wichtige Daten, die in jedem Fall verbucht werden müssen und die auch schnellstmöglich verbucht werden sollen. V2-Verbucher werden eingesetzt für zum Beispiel das Fortschreiben von Statistikdaten. Wenn die Statistikdaten nicht vollständig sind, ist es nicht so schlimm. Es rechtfertigt zum Beispiel in keinem Fall, dass der zugehörige Beleg deswegen nicht verbucht werden kann.

V1-Verbucher haben Vorrang vor V2-Verbuchern. Ein V2-Verbucher wird also erst dann ausgeführt, wenn alle zugehörigen V1-Verbucher erfolgreich verbucht worden sind. Bricht ein V1-Prozess ab, dann werden alle zugehörigen Bausteine, die sich in der Verbuchung befinden, ebenfalls abgebrochen.

V2-Verbucher missbrauchen

Bei einigen Prozessen kann das Verbuchen der Daten extrem lange dauern und deswegen sinnvoll sein, den Benutzer nach Beendigung des Buchungsprozesses zu informieren. 

Hierfür kann man den V2-Verbucher sozusagen missbrauchen. Man startet einfach die notwendigen V1-Verbucher und registriert am Ende noch einen V2-Verbucher, der mit Hilfe des Baustein TH_POPUP eine Meldung ausgibt. 

TH_POPUP

Der Funktionsbaustein TH_POPUP ist ein kleines Systempopup, dass einen Benutzer sofort informiert. Der Benutzer muss dafür im SAP-System angemeldet sein.

TH_POPUP in Aktion

Du kannst also diesen TH_POPUP in einem V2-Verbuchungsbaustein aufrufen:

  CALL FUNCTION 'TH_POPUP'
    EXPORTING
      client  = sy-mandt
      user    = sy-uname
      message = 'Verbuchung beendet!'.

Durch die Reihenfolge ist sichergestellt, dass das Popup erst aufgerufen wird, nachdem alle V1-Verbucher, also der Verbuchungsprozess ansich, beendet wurde.

Aufruf

Um die Verbuchung anzustoßen, muss zwingend ein COMMIT WORK erfolgen. Der implizit am Ende eines Programms ausgeführte Commit ist nicht ausreichend!

  CALL FUNCTION 'Z_TEST_BOOK' IN UPDATE TASK.
  CALL FUNCTION 'Z_TEST_BOOK_POPUP' IN UPDATE TASK.
  COMMIT WORK.

Der Beitrag Popup nach verbuchten Daten erschien zuerst auf Tricktresor.

OLE-Handle zu DOI-Objekt ermitteln

$
0
0

Für die Integration von Word und Excel gibt es nur zwei Möglichkeiten:

  • OLE – Object Linking and Embedding
  • DOI – Desktop Office Integration

Beide Varianten haben ihre Vor- und Nachteile. Ein Nachteil bei der Verwendung von DOI ist auf jeden Fall, dass die Methode CLOSE_DOCUMENT nicht zuverlässig funktioniert, wenn man Word oder Excel in einem separaten Fenster öffnet.

Beispiel

Zuerst jedoch ein Beispielcode, der zeigt, wie mittels DOI eine Excel-Instanz gestartet wird. Die Drucktaste „Create“ startet Excel.

Desktop Office Integration hat Excel gestartet

Die Schaltfläche „Create“ wird dann ausgeschaltet und die Schaltflächen „Destroy with „Close_Document“ und „Destroy with OLE Quit“ erscheinen. Mit diesen Drucktaste kann Excel wieder geschlossen werden. Das Beenden von Excel/ Word geschieht in der Regel über document->close_document und document->close_activex_document. Falls dies jedoch aus unerfindlichen Gründen nicht funktioniert, hilft vielleicht die Methode über das OLE-Objekt und die Methode „Quit“.

Code

REPORT LINE-SIZE 200.


"Create Control
SELECTION-SCREEN PUSHBUTTON /1(30) TEXT-cre USER-COMMAND create      MODIF ID cre.
"Destroy Control with Close_Document
SELECTION-SCREEN PUSHBUTTON /1(30) TEXT-dst USER-COMMAND destroy     MODIF ID dst.
"Destrpy Control with OLE and method Quit
SELECTION-SCREEN PUSHBUTTON /1(30) TEXT-dso USER-COMMAND destroy_ole MODIF ID dst.


CLASS demo DEFINITION.
  PUBLIC SECTION.
    METHODS create.
    METHODS destroy.
    METHODS destroy_with_ole.
    METHODS is_destroyed RETURNING VALUE(result) TYPE i.
  PROTECTED SECTION.

    DATA mr_control       TYPE REF TO i_oi_container_control." OIContainerCtrl
    DATA mr_document      TYPE REF TO i_oi_document_proxy.   " Office Dokument
    DATA mr_spreadsheet   TYPE REF TO i_oi_spreadsheet.      " Spreadsheet

    METHODS set_data.
    METHODS create_application.

ENDCLASS.


INITIALIZATION.
  DATA(go_demo) = NEW demo( ).

AT SELECTION-SCREEN.
  CASE sy-ucomm.
    WHEN 'CREATE'.
      go_demo->create( ).
    WHEN 'DESTROY'.
      go_demo->destroy( ).
    WHEN 'DESTROY_OLE'.
      go_demo->destroy_with_ole( ).
  ENDCASE.

AT SELECTION-SCREEN OUTPUT.

  LOOP AT SCREEN.
    CASE screen-group1.
      WHEN 'DST'.
        IF go_demo->is_destroyed( ) = 0.
          screen-active = '1'.
        ELSE.
          screen-active = '0'.
        ENDIF.

      WHEN 'CRE'.
        IF go_demo->is_destroyed( ) = 0.
          screen-active = '0'.
        ELSE.
          screen-active = '1'.
        ENDIF.
    ENDCASE.
    MODIFY SCREEN.
  ENDLOOP.

CLASS demo IMPLEMENTATION.


  METHOD create.

    create_application( ).
    set_data( ).

  ENDMETHOD.

  METHOD is_destroyed.

    IF mr_document IS BOUND.
      mr_document->is_destroyed(
        IMPORTING
          ret_value = result
      ).
    ELSE.
      result = 1.
    ENDIF.

  ENDMETHOD.

  METHOD create_application.

    DATA lr_error         TYPE REF TO i_oi_error.

    c_oi_container_control_creator=>get_container_control(
        IMPORTING
          control = mr_control
          error   = lr_error ).

** init control
    mr_control->init_control(
      EXPORTING
        inplace_enabled       = abap_false
        no_flush              = 'X'
        r3_application_name   = 'Test DOI'
        inplace_show_toolbars = abap_false
        parent                = cl_gui_container=>screen9
      IMPORTING
        error                 = lr_error
      EXCEPTIONS
        OTHERS                = 2 ).

    IF lr_error->has_failed = abap_true. lr_error->raise_message( 'I' ). RETURN. ENDIF.

*** Get Documentproxy
    CALL METHOD mr_control->get_document_proxy
      EXPORTING
        document_type  = soi_doctype_excel_sheet "'Excel.Sheet'
        no_flush       = 'X'
      IMPORTING
        document_proxy = mr_document
        error          = lr_error.
    IF lr_error->has_failed = abap_true. lr_error->raise_message( 'I' ). RETURN. ENDIF.

    mr_document->create_document(
      EXPORTING
       document_title   = 'Demo-Arbeitsblatt'
        no_flush         = 'X'
        open_inplace     = abap_false
      IMPORTING
       error = lr_error ).
    IF lr_error->has_failed = abap_true. lr_error->raise_message( 'I' ). RETURN. ENDIF.

    CALL METHOD mr_document->get_spreadsheet_interface
      IMPORTING
        sheet_interface = mr_spreadsheet
        error           = lr_error.
    IF lr_error->has_failed = abap_true. lr_error->raise_message( 'I' ). RETURN. ENDIF.

  ENDMETHOD.

  METHOD set_data.

    DATA lt_values        TYPE soi_generic_table.
    DATA lt_ranges        TYPE soi_range_list.
    DATA ls_range         LIKE LINE OF lt_ranges.
    DATA lr_error         TYPE REF TO i_oi_error.


    "Demo-Daten
    lt_values = VALUE #(
        ( row = 1 column = 1 value = '1' )
        ( row = 2 column = 1 value = '2' )
        ( row = 3 column = 1 value = '3' ) ) .

*== Neuen Bereich definieren
    mr_spreadsheet->insert_range_dim(
       EXPORTING name = 'myarea'
                 top       = 1
                 left      = 1
                 rows      = lines( lt_values )
                 columns   = 1
                 no_flush  = abap_false ).
    ls_range-columns = 1.
    ls_range-rows    = lines( lt_values ).
    ls_range-name    = 'myarea'.
    APPEND ls_range TO lt_ranges.

    "Daten übergeben
    mr_spreadsheet->set_ranges_data(
      EXPORTING
        ranges    = lt_ranges
        contents  = lt_values
      IMPORTING
        error     = lr_error
    ).
    IF lr_error->has_failed = abap_true. lr_error->raise_message( 'I' ). RETURN. ENDIF.

  ENDMETHOD.

  METHOD destroy.

    mr_document->close_document( ).
    mr_document->close_activex_document( ).
    FREE mr_document.

    mr_control->destroy_control( ).
    FREE mr_control.

  ENDMETHOD.

  METHOD destroy_with_ole.

    DATA lv_document_cntl_handle TYPE cntl_handle.
    DATA lv_application          TYPE ole2_object.
    DATA lv_oi_ret               TYPE soi_ret_string.
    DATA lr_error                TYPE REF TO i_oi_error.

    mr_document->get_document_handle(
      IMPORTING
        handle  = lv_document_cntl_handle
        retcode = lv_oi_ret ).
    GET PROPERTY OF lv_document_cntl_handle-obj 'Application' = lv_application.
    CALL METHOD OF lv_application 'Quit'.
    FREE OBJECT lv_application.
    FREE mr_document.

  ENDMETHOD.

ENDCLASS.

OLE-Objekt zu DOI-Dokument erhalten

Wenn die Methode close_document aus irgendwelchen Gründen nicht funktioniert, das Excel-Fenster also nicht geschlossen wird, oder du eine Methode anwenden möchtest, die das DOI-Interface nicht unterstützt, dann kannst du dir den OLE-Handle zur Applikation besorgen:

    DATA lv_document_cntl_handle TYPE cntl_handle.
    DATA lv_application          TYPE ole2_object.
    DATA lv_oi_ret               TYPE soi_ret_string.
    DATA lr_error                TYPE REF TO i_oi_error.

    mr_document->get_document_handle(
      IMPORTING
        handle  = lv_document_cntl_handle
        retcode = lv_oi_ret ).
    GET PROPERTY OF lv_document_cntl_handle-obj 'Application' = lv_application.
    CALL METHOD OF lv_application 'Quit'.
    FREE OBJECT lv_application.
    FREE mr_document.

SAP-Demoprogramme

Folgende zwei Demoprogramme zeigen noch erweiterte Funktionen des DOI-Interfaces:

  • SAPRDEMO_FORM_INTERFACE
  • SAPRDEMO_SPREADSHEET_INTERFACE
  • SAPRDEMO_MAILMERGE_INTERFACE

Bei diesen drei Programmen lässt sich übrigens der Fehler, dass die Applikation nicht korrekt geschlossen wird, sehr gut nachstellen.

Bei SAPRDEMO_MAILMERGE_INTERFACE wird beim Beenden des Programms zwar das Dokument geschlossen, aber die Word-Applikation bleibt bestehen. Bei SAPRDEMO_FORM_INTERFACE wird das Programm zwar beendet, doch obwohl Close_Document und Destroy_Control aufgerufen werden, bleibt das Excel-Fenster geöffnet. Im Programm SAPRDEMO_SPREADSHEET_INTERFACE funktioniert das Beenden von Excel wiederum perfekt.

Der Beitrag OLE-Handle zu DOI-Objekt ermitteln erschien zuerst auf Tricktresor.

Alles neu macht der Mai

$
0
0

Nun ja, alles macht der Mai sicherlich nicht neu, aber weil der bekannte Text des fast zweihundert Jahre alten Liedtextes gerade passt…

Wahrscheinlich hast du gar nicht bemerkt, dass der Tricktresor umgezogen ist. Und das ist auch gut so. Dank vieler Tipps und Hinweise meines Freundes Christian und des neuen Providers, konnte ich den Tricktresor unbemerkt umziehen. Um den Umzug testen zu können und weil in dem neuen Hostingpaket mehrere Domains inklusive waren, gibt es nun auch tricktresor.com.

tl;dr Version

Für alle, die den nun folgenden Sermon nicht lesen wollen, hier das Entscheidende in Kürze:

Tricktresor international

Die Idee, den Tricktresor auch auf englisch anzubieten, habe ich, seit ich vor ein paar Jahren auf WordPress umgestiegen bin. Aus diesem Grund hatte ich WordPress als Multi-Site eingerichtet. Da ich nie die Muße hatte, die Artikel zu übersetzen, hat die Multi-Site jedoch eher genervt, als dass sie mir genutzt hätte. Es gibt immer wieder Plugins, die mit Multi-Site-Installationen nicht sauber zusammenarbeiten. Auch die Administration ist etwas holpriger. Deswegen ist der Tricktresor jetzt wieder eine stinknormale Single-Site-Installation.

Der Tricktresor ist auch explizit als deutsches Angebot ausgerichtet. Ich habe bereits Mühe, die technischen Einzelheiten auf Deutsch ordentlich zu formulieren. Auf Englisch tue ich mich noch schwerer. Von einigen Seiten habe ich die Rückmeldung bekommen, dass der Google-Übersetzer auch gute Arbeit leistet… 🙂

Den Tricktresor über die Landesgrenzen hinaus bekannt zu machen, ist jedoch immer noch in meinem Hinterkopf vorhanden. Deswegen habe ich lange nach einer adäquaten Übersetzung für „Tricktresor“ gesucht. Mit meinem Kollegen Nicos habe ich ein bisschen geplaudert und „gebrainstormt“. Herausgekommen sind gleich zwei neue Domains:

„vault“ ist die englische Bezeichnung für einen Tresor. Wenn ich das richtig verstanden habe, ist das ein Tresor, wie er in Banken und Actionfilmen vorkommt. Also kein „safe“, sondern ein richtig großes und sicheres Teil. Allerdings heißt „vault“ auch „Gewölbe“, was ich ebenfalls gar nicht so unpassend fand.

This is a „vault“

Ebenfalls im Gespräch war „trickcrypt“, was auf „Geheimnis“ hindeuten sollte. Lustiger Weise heißt „crypt“ ebenfalls „Gewölbe“… 😉

Gewölbe

all-inkl.com

Mein neuer Provider heißt https://all-inkl.com. Hier gibt es wirklich eine enorme Funktionsvielfalt und großzügige Optionen (Speicherplatz, Datenbanken, E-Mailpostfächer usw.) zu einem sensationell günstigen Preis. Wer eine Website betreibt oder betreiben möchte, sollte sich diesen Provider auf jeden Fall einmal ansehen.

Duplicator Pro

Eine Große Hilfe beim Umzug war das WordPress-Plugin Duplicator Pro von der Firma Snapcreek. Hauptgrund, weswegen ich mich für dieses Produkt entschieden habe, war, dass ich die Multi-Site in eine Single-Site konvertieren konnte. Und natürlich konnte ich alle Daten, Datenbanken und Dateien sehr einfach auf den neuen Server übertragen. Eventuell stelle ich das Tool noch einmal separat vor.

Partner SimDia²

Auch beim befreundeten Unternehmen ERSAsoft hat sich einiges getan. Seit ein paar Wochen hat Inhaber geführte Familienunternehmen auch eine neue und ansprechende Internetseite. Einfach mal vorbeischauen: https://www.ersasoft.de.

ERSAsoft – Making SAP easy

ERSAsoft bietet seit vielen Jahren mit der Software SimDia² ein sehr einfaches Tool, um Daten aus Excel in SAP zu importieren – oder umngekehrt. Der große Vorteil zu den SAP-internen Lösungen (LSMW, BAPI, eCATT) ist, dass der Anwender die Datenübernahme selber machen kann. Er muss lediglich die Excel-Datei öffnen und dann im SAP-System beim „Durchklicken“ der Transaktion die Felder aus der Excel-Datei zuweisen.

SimDia² lohnt sich fast immer, wenn regelmäßig wiederkehrende oder auch einmalige größere Aktionen zum Arbeitsalltag gehören. Beispiele hierfür sind:

  • Übernahme von Vertriebsstücklisten ins SAP-System
  • Korrektur von Adressdaten im HR-Stammsatz
  • Generierung von einfachen Kundenaufträgen auf Basis einer Excel-Datei
  • Aktualisieren von Materialstammdaten

Die folgende Aussage begegnet uns fast allen regelmäßig wieder im Berufsalltag::

„Ich habe keine Zeit, meine Axt zu schärfen.
Ich muss so viele Bäume fällen!“

Frei nach Jorge Bucay aus „Komm, ich erzähl dir eine Geschichte“

Deswegen bietet sich ERSAsoft an:

Wir schärfen Ihre Axt, während Sie Bäume fällen!

Wie ich aus gut unterrichteter Quelle erfahren habe, kommt demnächst auch wieder eine neue Version des „Schleifsteins“ heraus, die nochmals mehr Möglichkeiten bietet.

Der Beitrag Alles neu macht der Mai erschien zuerst auf Tricktresor.

SAP Programming Styleguide

$
0
0

SAP hat gerade die neuen Programmierkonventionen vorgestellt, die sich deutlich von vergangenen Empfehlungen unterscheiden und zudem öffentlich auf github.com gepflegt werden.

Clean Code, Unit Tests und mehr

Unter der Internetadresse https://github.com/SAP/styleguides kannst du dich über die aktuellen Empfehlungen informieren. Sie fallen sehr umfangreich aus und sind in Englisch verfasst. Die Empfehlungen geben dir Hinweise zur Benennung und Verwendung von Objekten. In der Regel gibt es einen Good Case und ein Anti-Pattern.

Schreibweise

Die Empfehlungen sagen deutlich aus, wie in welchen Fällen die Schreibweise sein sollte. Zum Beispiel wir darauf hingewiesen, dass Methoden, die einen RECEIVING-Parameter haben, dieser als direkte Zuweisung verwendet werden sollte:

DATA(sum) = aggregate_values( values ).

Das Anti-pattern hierzu wäre:

aggregate_values(
  EXPORTING
    values = values
  RECEIVING
    result = DATA(sum) ).

Einsatz von Sprachelementen

Es wird sehr detailliert darüber geschrieben, in welchen Fällen Sprachelemente sinnvoll eingesetzt werden sollen. Ein gute Beispiel hierfür ist die Inline-DATA-Definition. Es werden auch Empfehlungen zu LOOP und anderen Sprachelementen gegeben. Hierbei wird nicht nur darauf eingegangen, wie LOOP verwendet werden sollte, sondern auch, welche Tabellentypen man benutzen sollte.

Bereiche

Der Styleguide gliedert sich in viele Bereiche: Formatting, Error Handling und mehr. Es wird dabei sehr auf Kleinigkeiten eingegangen, die – selbst wenn man sie selbst nicht so wichtig findet – doch einmal nachdenken sollte. Zum Beispiel wird empfohlen, LOOP INTO REF zu verwenden und nicht ASSIGNING. Einfach aus dem Grund, um klar zustellen, dass ASSIGNING nicht benötigt wird. Wenn Felder geändert werden sollen und deswegen ASSIGNING benutzt wird, ist das okay, aber es soll eben sicherstellen, dass nicht einfach aus dem Grund Dasmacheichimmerso verwendet wird.

Testing

Ein großer Bereich ist Testing. Auf den letzten Veranstaltungen hat sich immer mehr herauskristallisiert, dass das automatische Testen immer wichtiger wird und immer mehr ins Bewusstsein des gemeinen ABAP-Programmierers rückt. Wer sich also noch nicht damit beschäftigt hat, sollte dies dringend tun. Ich empfehle den kostenlosen, sehr umfangreichen OpenSAP-Kurs Writing Testable Code For ABAP.

Fazit

Der SAP Style Guide ist ungewohnt klar und kompromisslos. Wo in der Vergangenheit häufig ein „Kannst du so machen, aber auch anders“ zu lesen war, wird hier klar definiert, wie es sein sollte. Das finde ich gut, auch wenn ich einiges anders sehe.

Sehr gut finde ich auch, dass an Beispielen auch auf Kleinigkeiten eingegangen wird. Ob man diese Kleinigkeiten dann berücksichtigt oder nicht, bleibt einem natürlich weiterhin selbst überlassen. Die SAP zeigt hier jedoch, dass die Regeln einen guten Grund haben.

Es wird sehr viel Wert auf sauberen Code gelegt. An mehreren Stellen wird darauf hingewiesen, dass man Variablen, Methoden etc. so benennen sollte, dass sie eindeutig sind und klar erkennbar machen, wofür sie benutzt werden.

Einem immer wiederkehrenden Streitthema, der Ungarischen Notation, wird eine ganz klare Absage erteilt. In einer Welt, in der man bei Mouseover sofort den Typ einer Variablen oder eines Objektes erkennen kann, ist es nicht mehr notwendig, einen Hinweis auf die Typdeklaration in den Namen aufzunehmen.

Spicker und Goldene Regeln

Die Guidelines sind in einem Cheat Sheet auf vier Seiten zusammengefasst. Ebenso gibt es eine sehr komprimierte Version als Golden Rules.

Der Beitrag SAP Programming Styleguide erschien zuerst auf Tricktresor.

Einfacher systemübergreifender Versionsvergleich

$
0
0

Im SAP-Umfeld gibt es in der Regel eine Drei-System-Landschaft: Die Entwicklung und das Customizing finden im Entwicklungssystem statt. Zum Test wird die Programmierung in das Qualitätssicherungssystem (Q-System) transportiert. Dort finden weitere Tests mit produktionsnahen Daten statt. Dann wird das Programm eventuell noch einmal nachgebessert und es wird eine neue Version in das Q-System transportiert. Nach erfolgreichen Tests werden alle Transporte in das Produktionssystem transportiert.

Durch längere „Standzeiten“ im Q-System kann es dazu kommen, dass man den Überblick darüber verliert, welche Stände in welchem System vorhanden sind. Die manuelle Prüfung der einzelnen Objekte ist mehr als mühsam.

Versionsvergleich

Das untenstehende Programm kann einen Versionsvergleich zwischen dem aktuellen und einem anderen SAP-System durchführen. Folgende Eingaben sind möglich:

  • Transportauftrag
  • Klasse
  • Programm
  • Funktionsgruppe

Der Vergleich wird mit Hilfe des Funktionsbausteins SVRS_MASSCOMPARE_ACT_OBJECTS durchgeführt. Dieser Baustein hat eine sehr wichtige Funktion: Er ermittelt selbstständig die Unterobjekte zu einem Hauptobjekt. Für Programme bedeutet dies, dass alle Includes, Dynpros und sonstigen Programmobjekte ermittelt und verglichen werden. Bei Klassen werden alle Sections als auch die einzelnen Methoden berücksichtigt.

Ergebnisliste

Das Ergebnis des Vergleiches ist eine Liste, in der die Objekte rot markiert werden, die eine andere Version als im aktuellen System haben.

Mit einem Doppelklick auf eine Zeile gelangst du in den direkten Sourcecode-Vergleich des gewählten Objektes.

Vergleich von Klassenquellcode in Eclipse

Obwohl man in der SE24 von der Formularansicht auf die Ansicht Quelltextbasiert wechseln kann, ist ein kompletter Versionsvergleich der Klasse nicht möglich. Um herauszufinden, welche Methode in einem anderen System eventuell eine andere Version hat, muss für jede Methode separat ein Versionsvergleich durchgeführt werden. Hier spielt Eclipse wieder seine enormen Möglichkeiten aus, denn in Eclipse ist genau das mit der Tastenkombination Ctrl +Alt+C „Compare with <project>“ möglich.

Für das SAP-System, mit dem der Versionsvergleich durchgeführt werden soll, muss als ABAP-Project in Eclipse eingerichtet sein.

Code

REPORT zz_compare_versions.
DATA gs_vrs_compare TYPE vrs_compare_item.

PARAMETERS pa_req TYPE trkorr     DEFAULT ''.
PARAMETERS pa_cls TYPE seoclsname DEFAULT 'ZCL_DUMMY'.
PARAMETERS pa_rep TYPE syrepid    DEFAULT 'ZZDUMMY01'.
PARAMETERS pa_fgr TYPE syrepid    DEFAULT 'Z_DUMMY_01'.

PARAMETERS cb_req RADIOBUTTON GROUP mode DEFAULT 'X'.
PARAMETERS cb_cls RADIOBUTTON GROUP mode .
PARAMETERS cb_rep RADIOBUTTON GROUP mode .
PARAMETERS cb_fgr RADIOBUTTON GROUP mode .

PARAMETERS pa_rdt TYPE rfcdest DEFAULT 'Q01_100'.

START-OF-SELECTION.

  CASE 'X'.
    WHEN cb_req.
      PERFORM compare_request USING pa_req.
    WHEN cb_cls.
      PERFORM compare_object USING 'CLAS' pa_cls.
    WHEN cb_rep.
      PERFORM compare_object USING 'PROG' pa_rep.
    WHEN cb_fgr.
      PERFORM compare_object USING 'FUGR' pa_fgr.
  ENDCASE.

AT LINE-SELECTION.
  CHECK gs_vrs_compare IS NOT INITIAL.

  SUBMIT rsvrsrs3
          WITH log_dest = space
          WITH log_dst1 = pa_rdt
          WITH objnam2 = gs_vrs_compare-fragname
          WITH objname = gs_vrs_compare-fragname
          WITH objtyp1 = gs_vrs_compare-fragment
          WITH objtyp2 = gs_vrs_compare-fragment
          WITH versno1 = '99998'
          WITH versno2 = '99998' AND RETURN.

FORM compare_request USING iv_ta TYPE trkorr..

  DATA lt_request TYPE trwbo_request .

  CALL FUNCTION 'TR_READ_REQUEST'
    EXPORTING
      iv_read_e070      = 'X'
      iv_read_objs_keys = 'X'
      iv_trkorr         = pa_req
    CHANGING
      cs_request        = lt_request
    EXCEPTIONS
      error_occured     = 1
      no_authorization  = 2
      OTHERS            = 3.
  IF sy-subrc <> 0.
    CASE sy-subrc.
      WHEN 1.
        WRITE: 'Error "error_occured(1)" at TR_READ_REQUEST' COLOR COL_NEGATIVE.
      WHEN 2.
        WRITE: 'Error "no_authorization(2)" at TR_READ_REQUEST' COLOR COL_NEGATIVE.
    ENDCASE.
    RETURN.
  ENDIF.

  PERFORM compare USING lt_request-objects.

ENDFORM.

FORM compare USING it_objects TYPE trwbo_t_e071.

  DATA lt_cmp_result TYPE  vrs_compare_item_tab.

  CALL FUNCTION 'SVRS_MASSCOMPARE_ACT_OBJECTS'
    EXPORTING
      it_e071                 = it_objects
      iv_rfcdest_a            = space
      iv_rfcdest_b            = pa_rdt
      iv_filter_lang          = 'X'
      iv_delete_lang          = ' '
      iv_abap_ignore_case     = 'X'
      iv_abap_condense        = 'X'
      iv_abap_ignore_comments = 'X'
      iv_abap_equivalence     = 'X'
      iv_with_gui_progress    = 'X'
      iv_ignore_report_text   = ' '
    IMPORTING
      et_compare_items        = lt_cmp_result
    EXCEPTIONS
      rfc_error               = 1
      not_supported           = 2
      OTHERS                  = 3.
  IF sy-subrc <> 0.
    CASE sy-subrc.
      WHEN 1.
        WRITE: 'Error "rfc_error(1)" at SVRS_MASSCOMPARE_ACT_OBJECTS' COLOR COL_NEGATIVE.
      WHEN 2.
        WRITE: 'Error "not_supported(2)" at SVRS_MASSCOMPARE_ACT_OBJECTS' COLOR COL_NEGATIVE.
    ENDCASE.
    RETURN.
  ENDIF.

  LOOP AT lt_cmp_result INTO gs_vrs_compare.
    WRITE: /
      gs_vrs_compare-fragment,
      gs_vrs_compare-fragname.
    IF gs_vrs_compare-equal = abap_true.
      WRITE icon_led_green AS ICON HOTSPOT ON.
    ELSE.
      WRITE icon_led_red AS ICON HOTSPOT ON.
    ENDIF.

    DATA lt_ver_list TYPE STANDARD TABLE OF vrsn.
    DATA lt_versions TYPE STANDARD TABLE OF vrsd.
    DATA lv_obj_name TYPE versobjnam.

    lv_obj_name = gs_vrs_compare-fragname.
    CALL FUNCTION 'SVRS_GET_VERSION_DIRECTORY_46'
      EXPORTING
        objname      = lv_obj_name
        objtype      = gs_vrs_compare-fragment
      TABLES
        lversno_list = lt_ver_list
        version_list = lt_versions
      EXCEPTIONS
        no_entry     = 1
        OTHERS       = 2.

    TRY .
        WRITE lt_versions[ 2 ]-datum.
      CATCH cx_sy_itab_line_not_found.
    ENDTRY.

    CLEAR lt_ver_list.
    CLEAR lt_versions.
    CALL FUNCTION 'SVRS_GET_VERSION_DIRECTORY_46'
      DESTINATION pa_rdt
      EXPORTING
        objname      = lv_obj_name
        objtype      = gs_vrs_compare-fragment
      TABLES
        lversno_list = lt_ver_list
        version_list = lt_versions
      EXCEPTIONS
        no_entry     = 1
        OTHERS       = 2.
    TRY .
        WRITE lt_versions[ 2 ]-datum.
      CATCH cx_sy_itab_line_not_found.
    ENDTRY.

    HIDE gs_vrs_compare.
  ENDLOOP.

  CLEAR gs_vrs_compare.

ENDFORM.

FORM compare_object USING iv_type TYPE c iv_obj TYPE c.

  DATA(lt_objects) = VALUE trwbo_t_e071( 
               ( pgmid = 'R3TR' object = iv_type obj_name = iv_obj ) ).

  PERFORM compare USING lt_objects.

ENDFORM.

Der Beitrag Einfacher systemübergreifender Versionsvergleich erschien zuerst auf Tricktresor.

Ausnahmeklassen und OTR-Texte

$
0
0

Ausnahmeklassen sollte inzwischen jeder kennen. Nach wie vor gibt es die Möglichkeit, eine Ausnahmeklasse mit T100-Unterstützung oder ohne anzulegen. Ich verwende gerne die reinen Ausnahmeklassen, also ohne die Unterstützung einer T100-Nachricht. Hauptsächlich tue ich dies aus zwei Gründen:

  1. Aus meiner Sicht sind die T100-Nachrichten zu umständlich zu handhaben, da es kein MESSAGE RAISING-Konstrukt für Ausnahmeklassen gibt. Erst ab Release 750 ist der Zusatz RAISE EXCEPTION TYPE … MESSAGE möglich.
  2. Die Zuweisung der Parameter zu der entsprechenden T100-Nachricht finde ich ebenfalls umständlich. Man muss die Attribute, die man für die Ausnahmeklasse definiert hat, den entsprechenden &1 bis &4 Parametern der Nachricht zuordnen.

Zusätzlich könnte man als dritten Grund noch anführen, dass der Meldungstext nicht aus der Ausnahme selber kommen sollte, sondern vom jeweils aufrufenden Programm definiert werden sollte. Darüber könnte man jedoch gut streiten…

T100-Nachricht

Ausnahmeklassen mit T100-Nachricht sind jedoch sehr günstig, wenn es darum geht, Fehlermeldungen zu protokollieren. Da geht aus meiner Sicht kaum ein Weg an dem Application Log vorbei und diesen füllt man am besten mit MESSAGE.

Attribute einer Ausnahme

Eine Ausnahme kann ohne Texte und ohne weitere Attribute verwendet werden. Der Aufruf erfolgt dann einfach per

RAISE EXCEPTION TYPE zcx_demo.

Es ist wieder eine Glaubensfrage, ob man unterschiedliche Ausnahmen auch in unterschiedlichen Ausnahmeklassen definieren sollte, oder ob man mehrere Aspekte in einer Ausnahmeklasse zusammenfassen sollte. Ich mache es mal so, mal so… Das hängt von irgendwas ab, das ich nicht definieren kann. Auf jeden Fall ist eine diskrete Ausnahmeklasse einfacher zu verwenden, als eine mit Attributen. Der Aufruf mit Attributen erfordert nämlich, dass der komplette Name der Ausnahmeklasse erneut verwendet werden muss:

RAISE EXCEPTION TYPE zcx_demo
  EXPORTING
    textid = zcx_demo=>no_data.

Das macht den Quelltext unübersichtlicher und erzeugt eventuell unnötiges code noise. Die Alternative wäre folgende:

RAISE EXCEPTION TYPE zcx_demo_no_data.

OTR-Texte

OTR ist die Abkürzung für Online Text Repository.

Wenn man sich dafür entschieden hat, in der Ausnahmeklasse ein zusätzliches Attribut zu definieren, dann wird zu diesem Attribut, das automatisch immer vom Typ SOTR_CONC ist, eine GUID erzeugt. Diese zum Attribut erzeugte GUID ist ein eindeutiger OTR-Schlüssel.

Attribut NO_DATA mit eindeutige GUID

In der SE24 kann dieser Text einfach im Editor geändert werden.

Ausnahmeklasse ZCX_DEMO mit Attribut NO_DATA

OTR Übersetzung

Mit der Übersetzung kommt man das erste Mal etwas ins Straucheln, denn wenn man meint, über Springen – Übersetzung diese Texte in eine andere Sprache übersetzen zu können, dann bekommt man nur die Meldung „Leere Objektliste“ (TL565).

Die OTR-Texte müssen über die Übersetzungstransaktion SE63 übersetzt werden:

Transaktion SE63

In dem Einstiegsbild muss die OTR-ID, anscheinend auch Konzept genannt, eingegeben werden:

Einstieg zur Übersetzung eines OTR-Kurztextes

Nach Klicken auf Bearbeiten erscheint der bekannte Übersetzungsdialog.

Übersetzen eines OTR-Textes

OTR-Texte bearbeiten

OTR-Texte können unabhängig von der Applikation bearbeitet und erstellt werden. Die Transaktion dafür heißt leider nicht SOTR (diese Transaktion ruft den Testreport zur Überprüfung der SAPoffice Empfangs-API auf), sondern lautet SOTR_EDIT. In das Feld Konzept muss die OTR-GUID eingetragen werden. Hier können Kurztexte als auch Langtexte bearbeitet werden.

Anscheinend hat sich SAP mit den OTR-Texten große Dinge überlegt, denn es kann zu einem Text ein Alias angelegt werden und man kann anscheinend einen Kontext zuordnen. Wofür auch immer man das sinnvoll nutzen könnte…

Folgende Bausteine können genutzt werden, um OTR-Texte zu lesen, zu ändern oder zu löschen:

  • SOTR_GET_CONCEPT
  • SOTR_DELETE_CONCEPT
  • SOTR_CREATE_CONCEPT

OTR-Texte und Eclipse

Die OTR-Texte machen es jedenfalls schwer möglich, Attribute zu einer Ausnahmeklasse in Eclipse anzulegen, denn hierfür muss diese eindeutige OTR-GUID erzeugt werden. Der Umweg, diese GUID in SOTR_EDIT anzulegen, um diese dann in Eclipse einzubinden, ist jedenfalls mehr als umständlich.

Der Beitrag Ausnahmeklassen und OTR-Texte erschien zuerst auf Tricktresor.

Selektion zur Berechtigung

$
0
0

Berechtigungen und die dazugehörigen Berechtigungsprüfungen sind eine mitunter eine knifflige Angelegenheit. Bei einzelnen Werten ist es einfach, diese mit der entsprechenden Berechtigung zu prüfen. Wenn es jedoch um Selektionen geht, bei denen der Anwender nur die Berechtigung über einen Teil der Daten hat, dann wird es schon schwieriger…

Problem – Selektionsoptionen

Bei dem hier beschriebenen Problem hat der Anwender bei einem Report die Möglichkeit, ein Objekt über Selektionsoptionen (SELECT-OPTIONS) einzugrenzen. Hierfür muss ich jedoch wissen, welches die zu Grunde liegende Prüftabelle mit allen existierenden Daten ist.

Beispiel Verkaufsorganisation

Der Anwender selektiert Verkaufsorganisationen A* und B*. Der Anwender hat jedoch nur die Berechtigung für A200 und B330.

In der Prüftabelle für die Verkaufsorganisationen (Tabelle TVAK) sind die folgenden Verkaufsorganisationen gepflegt:

VkorgBezeichnung
A100Deutschland Gebiet Nord
A200Deutschland Gebiet Süd
A300Deutschland Gebiet West
A400Deutschland Gebiet Ost
B330Nord-Italien
B340Süd-Italien
C500Spanien

Lösung A – Vorselektion

Bei dieser Lösungsvariante wird vorab geprüft, für welche Objekte der Anwender die Berechtigung hat. Man würde also die tatsächlich vorhandenen Einträge aus der Prüftabelle selektieren und für jeden Eintrag prüfen, ob der Anwender die entsprechende Berechtigung hat.

Mit den verbleibenden Einträgen kann man entweder eine dynamische WHERE-Bedingung bauen oder baut aus diesen Einträgen eine genaue Ranges-Tabelle auf.

Lösung B – Einzelprüfung der Daten

Es gibt natürlich auch die relativ einfache Möglichkeit, alle Daten einzulesen und dann bei jedem einzelnen Datensatz zu prüfen, ob der Anwender berechtigt ist, ihn zu sehen. Das ist einfach zu programmieren, kann jedoch bei großen Datenmengen ein erhebliches Laufzeitproblem werden.

Unterstützung

Durch Zufall bin ich auf die Klasse CL_AUTH_OBJECTS_TO_SQL gestoßen. Mit Hilfe dieser Klasse wird der Lösungsweg A eingeschlagen. Allerdings ohne, dass ich als Programmierer wissen müsste, welches die Zugrunde liegende Prüftabelle ist und ohne, dass ich die WHERE-Bedingung selber erstellen müsste.

Der Klasse müssen folgende Daten übergeben werden:

  • das zu prüfende Berechtigungsobjekt
  • die Aktivität (Anzeigen, Ändern, Löschen etc)
  • Ein Field Mapping (DDIC-Grundlage der Felder)
  • optional: Filter für die Einschränkung auf Objekte

Mit GET_SQL_CONDITION bekommt man dann die WHERE-Bedingung für die Felder zurück, für die der Anwender eine Berechtigung hat.

Folgendes kleine Beispiel zeigt die Anwendung für die Klasse.

Code

DATA h_vkorg TYPE vkorg.
SELECT-OPTIONS s_vkorg FOR h_vkorg DEFAULT 'A100'.

START-OF-SELECTION.

  DATA(lo_converter_osql) = cl_auth_objects_to_sql=>create_for_open_sql( ).

  lo_converter_osql->add_authorization_object( 
    iv_authorization_object = 'V_VBAK_VKO'
    it_activities = VALUE #(
             ( auth_field = 'ACTVT' value = '03' ) )
    it_field_mapping = VALUE #(
             ( auth_field = 'VKORG'
               view_field = VALUE #(
                               table_ddic_name = 'VBAK'
                               table_alias     = ''
                               field_name      = 'VKORG' ) ) )
    it_filter = VALUE #( FOR selopt IN s_vkorg[]
             ( auth_field = 'VKORG' low = selopt-low high = selopt-high ) ) ).
  
  TRY.
      DATA(lv_where_clause) = lo_converter_osql->get_sql_condition( ).
      IF lv_where_clause IS INITIAL.
        MESSAGE 'Du hast die Berechtigung für alle ausgewählten Verkaufsorganisationen' TYPE 'I'.
      ELSE.
        cl_demo_output=>display_text( |Deine WHERE-Bedingung: { lv_where_clause }| ).
      ENDIF.
    CATCH cx_auth_not_authorized.
      MESSAGE 'Keine Berechtigung für die ausgewählten Verkaufsorganisationen' TYPE 'I'.
  ENDTRY.

Der Beitrag Selektion zur Berechtigung erschien zuerst auf Tricktresor.


SELECT WHERE nur ein Eintrag vorhanden

$
0
0

Select-Befehle sind in der Regel im SAP-Umfeld einigermaßen überschaubar. Meiner Meinung nach liegt es daran, dass in der Regel Programmierer am Werk sind, aber keine SQL-Spezialisten. Zudem bietet der Open-SQL-Standard von SAP auch nur einen eingeschränkten Funktionsumfang. Selbst wenn man etwas kompliziertere Selects durchführen möchte, ist das nicht unbedingt möglich.

Joins sind eine andere Geschichte. Hier toben sich Programmierer gerne einmal aus und joinen was das Zeug hält.

SELECT oder ABAP?

In beiden Fällen, der Select-Anweisung und der nachträglichen Verarbeitung, gibt es Situationen, wo beides irgendwie umständlich ist. Ein solcher Fall ist es zum Beispiel, wenn man nur die Daten selektieren möchte, von denen man erst nach einer Aggregatbildung weiß, ob man sie lesen möchte oder nicht. Also zum Beispiel: Gib mir alle Belege, zu denen nur ein Eintrag in der Positionstabelle vorhanden ist.

SUBQUERY

Der folgende Select ermittelt genau die Einträge, bei denen nur ein Eintrag in der VBAP vorhanden ist.

SELECT vbeln, matnr
  FROM vbap AS v
 WHERE vbeln IN @s_vbeln
   AND matnr IN @s_matnr
   AND 1 =  ( SELECT COUNT( * ) FROM vbap
               WHERE vbeln = v~vbeln )
 GROUP BY vbeln, matnr
 ORDER BY vbeln, matnr
  INTO TABLE @DATA(list).

HAVING

durch Zufall bin ich bei der Nachstellung des Problems auf eine andere Lösung gestoßen, bei der die Anzahl der vorhandenen Sätze mittels HAVING eingeschränkt wird.

SELECT vbeln, sum( kwmeng ) as kwmeng, vrkme
  FROM vbap AS v
    WHERE vbeln IN @s_vbeln
      AND matnr IN @s_matnr
  GROUP BY vbeln, vrkme
  HAVING COUNT(*) = 1
  ORDER BY vbeln, vrkme
  INTO TABLE @data(list).

Der Vorteil bei der HAVING-Variante ist, dass hier die Anzahl der Datensätze auch variabel abgefragt werden können. Das geht mit dem oben vorgestellten Subquery nicht.

Performancevergleich

Ich habe beide Varianten in zwei unterschiedlichen Systemen mit unterschiedlichen Tabellen ausprobiert und einen kleinen Performancevergleich gemacht. In der einen Version ist die Variante mit dem Subquery schneller, in der anderen Version ist HAVING schneller.

Im Gegensatz zu einer umständlichen Analyse per Programmcoding, welcher Satz nur einmal vorhanden ist, sind beide SELECT-Versionen meiner Meinung nach eleganter. Die Performance sollte man auf jeden Fall bei jeder Version im Blick haben.

Der Beitrag SELECT WHERE nur ein Eintrag vorhanden erschien zuerst auf Tricktresor.

Modernes ABAP – Ein Beispiel

$
0
0

Die Neuerungen im ABAP-Umfeld sind inzwischen bereits einige Jahre alt. Über den VALUE Operator, der mit Release 7.40 eingeführt wurde, hat Horst Keller bereits 2013 gebloggt. Trotzdem werden einige der neuen Befehle nur sparsam eingesetzt. Das liegt einerseits daran, dass man sich wirklich an die Verwendung gewöhnen muss, andererseits ist man vielleicht unsicher, welche Gefahren (Performance) sie bergen. Ich persönlich finde zudem, dass ein übermäßiger Einsatz der Befehle, die sehr vielfältig untereinander geschachtelt werden können, auch schnell nicht mehr schön aussehen und zudem schwer zu überblicken sind.

Aber um mal wieder einen Beitrag zu schreiben und weil es vielleicht doch elegant ist, eine komplexe Aufgabe in nur einer Zeile zu lösen, stelle ich dir heute folgende Lösung vor.

Interne Tabelle kopieren

Nehmen wir an, wir hätten eine Tabelle mit einer Materialnummer, einer gewünschten Menge und der verfügbaren Menge. Für die Anzeige im SALV-Grid soll die Tabelle noch um den Materialkurztext erweitert werden und Einträge, bei denen die gewünschte Menge von der verfügbaren Menge abweicht, sollen farblich gekennzeichnet werden.

Datenstrukturen

Die Strukturen der Quelltabelle (SRC) und der Zieltabelle (TGT) sehen wie folgt aus:

  TYPES: BEGIN OF _src,
           mat TYPE c LENGTH 1,
           qty TYPE i,
           avq TYPE i,
         END OF _src,
         _src_t TYPE STANDARD TABLE OF _src WITH EMPTY KEY,

         BEGIN OF _tgt,
           mat TYPE c LENGTH 1,
           qty TYPE i,
           avq TYPE c LENGTH 2,
           txt TYPE maktx,
           col TYPE lvc_t_scol,
         END OF _tgt,
         _tgt_t TYPE STANDARD TABLE OF _tgt WITH EMPTY KEY.

Testdaten

Die Tabelle, die ich in die Ausgabetabelle für das SALV-Grid kopieren möschte, wird mit ein paar Testdaten gefüllt:

  DATA(src) = VALUE _src_t(
      ( mat = 'A' qty = 10 avq = 10 )
      ( mat = 'B' qty = 20 avq = 15 )
      ( mat = 'C' qty = 30 avq = 30 ) ).

Herkömmliche Vorgehensweise

Das althergebrachte Coding ohne neue ABAP-Features könnte wie folgt aussehen:

  DATA tgt1     TYPE _tgt_t.
  DATA tgt_line TYPE _tgt.
  DATA col_line TYPE lvc_s_scol.

  LOOP AT src INTO DATA(src_line).
    CLEAR tgt_line.
    MOVE-CORRESPONDING src_line TO tgt_line.
    tgt_line-txt = mat=>text( tgt_line-mat ).
    IF tgt_line-qty <> tgt_line-avq.
      CLEAR col_line.
      col_line-fname = space.
      col_line-color-col = 6.
      APPEND col_line TO tgt_line-col.
    ENDIF.
    append tgt_line to tgt1.
  ENDLOOP.

Neue Vorgehensweise

Mit Hilfe der neuen ABAP-Features VALUE, COND, FOR und CORRESPONDING habe ich die folgende Lösung erarbeitet:

  DATA(tgt) = VALUE _tgt_t( FOR line IN src (
      VALUE #( BASE CORRESPONDING #( line )
      txt = mat=>text( line-mat )
      col = COND #( LET color = VALUE lvc_t_scol( ) IN
                WHEN line-qty <> line-avq 
                  THEN VALUE #( ( fname = '' color-col = 6 ) ) )
      ) ) ).

Auffällig sind folgende Dinge:

  • Das Coding ist deutlich kürzer
  • Es sind keine Variablendeklarationen notwendig
  • Es sieht einigermaßen konfus aus

Schön ist auf jeden Fall, dass mit Hilfe eines Befehls, bzw. einer Befehlskette, Daten von einer Tabelle in eine andere kopiert werden können und sozusagen nebenbei weitere Feldmanipulationen vorgenommen werden können.

VALUE

Den Value-Befehl habe ich lieben gelernt, denn er macht es in vielfältigen Situationen einfach, Daten in eine Struktur oder Tabelle einzufügen. Und zwar ohne dass eine Datendeklaration notwendig wäre.

Der Value-Befehl wird direkt gefolgt von der Typendefinition, die verwendet werden soll. Wenn die Typendefinition implizit ermittelbar ist, zum Beispiel, weil die Daten an eine bereits definierte Variable übergeben werden, dann reicht die Angabe des „#“. In meinem Beispiel möchte ich aber gerade die Datendefinition durch den VALUE-Befehl definieren, also gebe ich den zu verwendenden Tabellentyp an:

DATA(tgt) VALUE _tgt_t( ).

FOR

In der VALUE-Angabe führe ich einen LOOP über die Quelltabelle aus und kopiere die Felder der Quelltabelle in die Zieltabelle mittels CORRESPONDING:

  DATA(tgt) = VALUE _tgt_t( 
    FOR line IN src (
      CORRESPONDING #( line ) ) ).

Der Befehl FOR line IN src entspricht also in etwa dem Befehl:

LOOP AT src INTO DATA(line).

CORRESPONDING

Eine Herausforderung war es, zusätzlich zu CORRESPONDING noch weitere Felder anderweitig belegen zu können. Das folgende Coding funktioniert nämlich nicht:

DATA(tgt) = VALUE _tgt_t( 
  FOR line IN src (
    CORRESPONDING #( line )
    txt = mat=>text( line-mat ) ) ).

Hier muss mit einer erneuten VALUE-Operation gearbeitet werden:

DATA(tgt) = VALUE _tgt_t(
  FOR line IN src (
    VALUE #( BASE CORRESPONDING #( line )
    txt = mat=>text( line-mat ) ) ) ).

COND

Nun haben wir bereits die Tabelle kopiert und zusätzlich den Materialtext dazu gelesen. Zusätzlich möchte ich noch die COLOR-Tabelle füllen, wenn sich die angeforderte Menge von der verfügbaren Menge unterscheidet. Diese Anforderung habe ich mit COND realisiert:

col = COND #( LET color = VALUE lvc_t_scol( ) IN
        WHEN line-qty <> line-avq
          THEN VALUE #( ( fname = '' color-col = 6 ) ) )

Alleine diesen Befehl finde ich deutlich komplexer als eine zuvor ausgeführten IF-Anweisung. COND ist allerdings notwendig, wenn der Code Inline ausgeführt werden soll. Zudem ist er sehr mächtig, denn es können verschiedene Bedingungen abgefragt werden. Er entspricht also in etwa einer verschachtelten IF – ELSEIF – ELSE Struktur.

SWITCH

Den Switch-Befehl, der in etwa einer CASE-Anweisung entspricht, habe ich nicht mehr in der Kopier-Anweisung unter bekommen… Die Arbeitsweise lässt sich jedoch gut in der Hilfsmethode MAT=>TEXT( ) ersehen.

Ausgabe

Die Ausgabe der aufbereiteten Tabelle erfolgt mit Hilfe des SALV-Grid:

  TRY.
      cl_salv_table=>factory(
        IMPORTING
          r_salv_table   = DATA(salv)
        CHANGING
          t_table        = tgt ).
      DATA(cols) = salv->get_columns( ).
      cols->set_color_column( 'COL' ).
      salv->display( ).
    CATCH cx_salv_msg.
      MESSAGE 'error salv' TYPE 'I'.
  ENDTRY.
Ausgabe der kopierten Tabelle

Fazit

Zu der vorgestellten Lösung und allgemein möchte ich folgendes anmerken:

Formatierung

Formatierung ist alles!

Die neuen ABAP-Features sind so komplex und können im Grunde endlos verschachtelt werden. Deswegen ist es notwendig, den Quelltext so zu formatieren, dass deutlich wird, welche Befehle und Sequenzen zusammengehören. Erschwerend kommt hinzu, dass für die neuen Befehle kein Pretty-Print möglich ist. Man muss also selber entscheiden, was noch in eine Zeile passt und was wie weit eingerückt werden sollte.

Wenn das obige Coding sinnlos formatiert wird, dann sieht es wirklich sehr unübersichtlich aus:

DATA(tgt) = VALUE _tgt_t( FOR line IN src (  VALUE #( BASE 
      CORRESPONDING #( line )  txt = mat=>text( line-mat )
          col = COND #( LET color =  VALUE lvc_t_scol( ) 
        IN WHEN line-qty <> line-avq 
             THEN VALUE #( ( fname = '' color-col = 6 ) ) ) ) ) ).

Anzahl der Verwendungen

Wenn die Zuweisungen oder Ermittlungen, die durch die neuen ABAP-Features gemacht werden sozusagen einmalig sind, dann sind sie eine elegante Möglichkeit, die Programmierung kürzer zu machen. Sobald die Ergebnisse jedoch vielschichtiger werden oder die Abfragen komplexer, dann ist es sinnvoll, die entsprechenden Anweisungen entweder vorab berechnet oder in Funktionen ausgelagert werden.

Debugging

Was man immer im Hinterkopf behalten sollte ist, dass das Debuggen komplexer Anweisungsketten deutlich erschwert wird. Man kann zwar im Debugger die Schrittweite setzen, die ein Debuggen der Einzelteile ermöglicht, allerdings ist dies sehr mühsam. Es kann kein Break-Point innerhalb einer Anweisungskette gesetzt werden.

Anwendung

Auf jeden Fall sollte man sich mit den neuen Befehlen beschäftigen und diese in die tägliche Arbeit einfließen lassen. Es übt und erleichtert in vielen Fällen die Arbeit. Nur so lernt man, fremden Code zu verstehen und wann und wie man die neuen Befehle selber am sinnvollsten einsetzt.

Eine gute Möglichkeit, um auf dem Laufenden zu bleiben und auch um die Anwendung der ABAP-Features zu verstehen, ist, Horst Keller auf blogs.sap.com zu folgen.

Der Beitrag Modernes ABAP – Ein Beispiel erschien zuerst auf Tricktresor.

Dateiausgabe in Zwischenablage umleiten

Hierarchiedarstellung von Controls

$
0
0

Hierarchien sind was Tolles! Controls sind auch toll! Wir sollten sie nutzen, so lange es sie noch gibt (Stichwort Fiori, ABAP in the Cloud etc.). Deswegen ein schönes Demoprogramm zur Analyse von GUI-Controls zur Laufzeit und Darstellung als Baumstruktur mit Hilfe der Klasse CL_COLUMN_TREE_MODEL.

GUI-Elemente

GUI-Controls sind ActiveX-Steuerlemente, die mit dem Stichwort EnjoySAP zu SAP Release 4.6C eingeführt wurden. Sie werden auch OCX-Controls genannt. Hier gibt es im Grunde zwei unterschiedliche Arten:

  • Container
  • Controls

Sie alle haben eine Gemeinsamkeit, sie erben nämlich alle von der Hauptklasse CL_GUI_CONTROL. Verwirrender Weise erbt CL_GUI_CONTAINER von CL_GUI_CONTROL.

Container

Typische Container sind:

  • CL_GUI_CUSTOM_CONTAINER Container for customer controls in the dynpro area
  • CL_GUI_DIALOGBOX_CONTAINER Container for customer controls in the dynpro area
  • CL_GUI_DOCKING_CONTAINER Docking Control Container
  • CL_GUI_EASY_SPLITTER_CONTAINER Reduced Version of Splitter Container Control
  • CL_GUI_SIMPLE_CONTAINER Anonymous Container
  • CL_GUI_SPLITTER_CONTAINER Splitter Control

Etwas ungewöhnlicher ist der CL_GUI_GOS_CONTAINER für Generic Objects. Wer sich ansehen möchte, was für Schweinereien man mit diesem machen kann, sollte sich Hacking SAPGUI ansehen.

Container haben die Besonderheit, dass sie weitere Container oder Controls enthalten können.

Controls

Die meistgebrauchten Controls sind sicherlich:

  • CL_GUI_ALV_GRID
  • CL_GUI_TEXTEDIT
  • CL_GUI_PICTURE
  • CL_GUI_HTML_VIEWER
  • CL_GUI_TOOLBAR

Sie alle benötigen einen CL_GUI_CONTAINER, in dem sie dargestellt werden.

Control-Hierarchie

Ich habe ein kleines Programm geschrieben, das eine einfache Anwendung aus zwei Splittern, zwei Textedit-Controls und einem Picture-Control erstellt.

Container und Controls

Hierarchieanalyse

Die Klasse ZCL_TRCKTRSR_CONTAINER_TREE analysiert die Objektstruktur und stellt diese hierarchisch dar.

Hierarchiedarstellung der verwendeten GUI-Controls

Objekt-ID

Im Debugger sieht man die Objekt-ID der erzeugten Objekte:

Objekt-ID von GO_PARENT

Die Objekte werden bei Programmausführung durchnummeriert und erhalten so eine eindeutige Objekt-ID. Der folgende Code ermittelt die Objekt-ID zu einem Objekt:

DATA lo_obj TYPE REF TO cl_abap_objectdescr.

lo_obj ?= cl_abap_typedescr=>describe_by_object_ref( io_object ).

DATA(lv_relname) = lo_obj->get_relative_name( ).
DATA lv_object_id TYPE i.
CALL 'OBJMGR_GET_INFO' ID 'OPNAME' FIELD 'GET_OBJID'
                       ID 'OBJID'  FIELD lv_object_id
                       ID 'OBJ'    FIELD io_object.
rv_object_name = |\{O:{ lv_object_id }*{ 
                 lo_obj->absolute_name }|.

github

Das Demoprogramm liegt bei github und kann mit Hilfe von abapGit heruntergeladen werden:

https://github.com/tricktresor/container_hierarchy

guidrasil

Wenn du Lust bekommen hast, dich noch weiter mit Controls zu beschäftigen, dann schaue dir meinen Control-Designer guidrasil an.

Der Beitrag Hierarchiedarstellung von Controls erschien zuerst auf Tricktresor.

Sprachtransporte

$
0
0

Wenn man ein paar eigenentwickelte Transaktionen oder Reports im SAP-System in eine andere Sprache übersetzt hat, dann stellt als Nächstes sich häufig die Frage, wie diese Texte jetzt in weitere Systeme gelangen sollen. Die offensichtliche Antwort wäre, einfach noch einmal alle Objekte zu transportieren, die zu den übersetzten Transaktionen gehören. Und je nach Einstellung des Transportsystems klappt das auch ganz gut. Aber es ist schon etwas unschön, den ganzen Code noch einmal zu transportieren, wenn sich eigentlich nur die Texte auf der Benutzeroberfläche geändert haben. Vor allem dann, wenn der eigene Arbeitgeber für jeden Transport, den man weiterschiebt, umfassende Qualitätsprüfungen vorschreibt. Solche Qualitätsmaßnahmen mögen zwar sinnvoll sein für Transporte, die tatsächlich die Funktionsweise einer Transaktion u. Ä. ändern – aber Übersetzungen tun dies per Definition nicht.

Unser Beispielreport, auf Deutsch und auf Französisch!

Was sind Sprachtransporte?

Zu diesem Zweck gibt es Sprachtransporte, einen Typ von Transporten, der aus meiner Sicht viel zu selten eingesetzt wird. In Sprachtransporten werden nur die Übersetzungen transportiert, nicht die eigentlichen Entwicklungsobjekte. Das hat immense Vorteile und ist ganz einfach viel sauberer, denn man transportiert so wirklich nur dass, was transportiert werden soll. Sprachtransporte sind kein eigene Art von Transportaufträgen, sondern man verwendet einfach einen Workbenchauftrag oder einen Transport von Kopien. Zunächst zeige ich einmal kurz, wie das Ganze funktioniert, wenn man so einen Transport von Hand zusammenbaut.

Hier wurde der Report in SE63 übersetzt. Für die Qualität der französischen Übersetzung übernehme ich keine Garantie!

Beispiel

In unserem Beispiel haben wir die Textelemente des Reports /LUDECKE/TESTREPORT und des Datenelements /LUDECKE/TESTFELD aus dem Deutschen ins Französische übersetzt, einfach durch Abspringen nach SE63 aus dem jeweiligen Objekt. Dann legen wir zunächst einmal einen neuen Transportauftrag vom Typ Workbenchauftrag oder Transport von Kopien an. In die Objektliste des Transportauftrags oder der Aufgabe tragen wir nun von Hand wie unten abgebildet einige Objekt ein, in diesem Fall einen Report und ein Datenelement. Doch anstatt R3TR geben wir als Programm-ID hier LANG ein. Sobald wir die Eingabe abschließen, fragt uns das System nach einer Sprache. Hier geben wir FR ein.

In diesen Transport haben wir die Objekte, deren französische Texte transportiert werden sollen, manuell eingegeben.

Für alle weiteren Objekte vom Typ LANG, die wir eingeben, wird jetzt automatisch FR eingetragen. Wir können das jedoch manuell ändern und z. B. dasselbe Objekt mehrfach eintragen, mit unterschiedlichen Sprachen.

Freigabe des Sprachtransports

Wenn wir jetzt diesen Transportauftrag freigeben und ins nächste System importieren, wird exakt gar nichts an den eigentlichen Objekten geändert, sondern es werden nur die Übersetzungen importiert. Wenn ein Objekt im Zielsystem nicht existiert, gibt es einen RC-8. Wenn in einem Objekt im Zielsystem die Feldlänge kürzer ist als im Quellsystem, erhält man einen RC-4. In beiden Fällen werden die betreffenden Übersetzungen beim Import übersprungen, aber alle Übersetzungen, für die keine Fehler auftraten, werden erfolgreich importiert. In unserem Beispiel wurde das Datenelement /LUDECKE/TESTFELD nie ins Testsystem transportiert, daher schlägt hier der Import fehl. Die Textelemente des Reports /LUDECKE/TESTREPORT wurden aber trotz dieses Fehlers importiert.

So sieht der Fehler im Transportprotokoll aus, wenn ein Objekt, für das Übersetzungen importiert werden sollen, im Zielsystem nicht existiert.

Transaktion SLXT

Natürlich möchte niemand ständig einzelne Objekte in Transportaufträgen erfassen. Um also massenweise Übersetzungen in Transportaufträge zu schreiben, nutzt man die Transaktion SLXT. Diese Transaktion liest das Übersetzungsprotokoll aus, das beim Übersetzen geschrieben wird. Jedes Objekt, das man in Transaktion SE63 sichert, wird in dieses Protokoll geschrieben. Alle guten SAP-Übersetzungstools (natürlich auch die Übersetzungstools von LUDECKE) unterstützen dieses Übersetzungsprotokoll, und daher können mit der Transaktion SLXT auch Übersetzungen in Transportaufträge verfrachtet werden, die mit Drittanbietertools oder per Export übersetzt wurden.

In SLXT reicht es, Sprache, Transport und Zeitraum einzugeben, und schon wird ein Sprachtransport erzeugt.

In SLXT wählt man eine oder mehrere Sprachen sowie einen Transportauftrag aus, oder man gibt, wie im Beispiel, eine Beschreibung für einen neuen Transportauftrag an. Außerdem kann man auf diverse Weise die Objekte auswählen, für die Übersetzungen transportiert werden sollen. Am einfachsten finde ich die Auswahl nach Übersetzungsdatum, denn so erwischt man meistens genau die Objekte, die man benötigt. Nach dem Ausführen des Reports wird die Nummer des Sprachtransports ausgegeben, in den die Objekte geschrieben wurden. Ein Aufgabe wird hierbei nicht angelegt – die Transportobjekte landen direkt im Transportauftrag. Diesen kann man dann analog zum manuell angelegten Auftrag transportieren.

Der Beitrag Sprachtransporte erschien zuerst auf Tricktresor.

Viewing all 166 articles
Browse latest View live


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>