Übersetzungen sind immer ein leidiges Thema. Stephan hat mir einen Tipp zugesendet, wie man die Arbeit in der Transaktion SE63 Übersetzungseditor etwas zügiger gestalten kann.
Noch im Einstieg der SE63 kann man durch Eingabe eines Funktionscodes die Navigation abkürzen so dass man direkt zum Zielobjekt gelangt.
Einige Beispiele:
DE – Datenelement
NA – Nachricht
PR – Programm
OTRS – OTR-Text
Nach wie vor muss man jedoch erst einmal den richtigen Code für das jeweilige Objekt ermitteln. Wenn du den Objekttyp nicht kennst, dann kannst du den Joker „Transportobjekt“ einsetzen. Hier kannst du den aus dem Transportauftrag verwendeten R3TR-Objekttyp verwenden. Im Einstiegsbild der SE63 gelangst du über die Drucktaste Transportobjekt (STRG + F1) oder über den Funktionsode „T“ dorthin.
Heute hatte ich einen merkwürdigen Fehler: Ich konnte eine inaktive Klasse nicht aktivieren. Der Class Builder hat nur gemeldet: „Active class has been generated“, aber die Klasse war weiterhin inaktiv. Bei der Fehlersuche bin ich zwar nicht auf die Ursache gestoßen, habe aber einige Funktionsbausteine und Klassen kennengelernt, die bei der Fehlersuche hilfreich waren.
Mit Hilfe des Funktionsbausteins RS_INACTIVE_PART habe ich immerhin herausgefunden, welcher Teil der Klasse nicht aktiv war, aber da die Komponenten einer Klasse sehr uneinheitlich definiert werden, half es mir nicht weiter, dass der inaktive Part vom Typ „CLSD“ war. Über die Tabelle DWINACTIV habe ich den Include des inaktiven Klassenartefakts ermitteln können. Über den Objekttyp habe ich zwar herausgefunden, dass dies der Teil „CLSD – LIMU CLSD Class Definition (ABAP Objects)“ ist. Da der Definitionsteil im Class Builder jedoch als aktiv angezeigt wurde, wollte ich den Quelltext vergleichen. Im Artikel über die Classname Services habe ich bereits beschrieben, wie man über die Einzelteile an die technischen Details kommt. Das war mir natürlich deutlich zu Umständlich… Also habe ich letztendlich ein kleines Programm – einen Class Inspector – geschrieben, dass die einzelnen Includes einer Klasse auflistet und über Doppelklick den jeweiligen Quelltext anzeigt.
In der Auftragserfassung (Modulpool SAPMV45A) sollen bei Auftragsanlage oder -änderung kundenindividuelle Texte definiert werden.
Standard-Textfindung
Im Standard lässt sich bereits eine umfangreiche Textfindung einstellen. Hier geht es jedoch darum, Texte aus anderen Objekten (Vorgängerbelegen, Kunden, Materialien, …) zu finden und zu übernehmen.
Ausgangspunkt ist die Transaktion VOFM, Menü Kopierbedingungen • Texte oder Datenübernahme • Texte. Hier kannst du Bedingungen programmieren und zuordnen, die den gewünschten Text ermitteln.
Textverwaltung in SAP
In diesem Artikel geht es darum, individuelle Texte in eine Auftragsposition zu bekommen. Um das zu realisieren, muss man wissen, wie die Textverwaltung im SAP funktioniert. Darum hier ein kurzer Abriss zu den SAP-Langtexten.
Standardlangtexte können in der Transaktion SO10 erstellt und bearbeitet werden. Zur Bearbeitung hast die Auswahl zwischen dem uralten Zeilen-Editor und dem PC-Editor, der zwischenzeitlich durch Microsoft Word abgelöst wurde.
Absatzformate
Ein SAP-Langtext oder auch SAP-Script- oder ITF-Text, besteht aus einem Absatzformat und einer Zeile. Absatzformate können in Stilen definiert und zugeordnet werden (Transaktion SE72).
Das Sternchen * ist das Standardabsatzformat. Steht das * in der Spalte Absatzformat, dann erfolgt auf jeden Fall ein Zeilenumbruch. Bei einem Fließtext ist das Absatzformat leer.
Alter ZeileneditorMicrosoft Word Editor
Felder
Um einen Text eindeutig zu identifizieren, sind die folgenden Felder notwendig:
Feldname
Beschreibung
Infos
TDOBJECT
Textobjekt
Definiert den Anwendungsbereich. Standardtexte haben das Textobjekt TEXT, Auftragskopftexte VBBK und Auftragspositionstexte VBBP, Materialstamm hat MATERIAL und MVKE. Textobjekte werden mit Hilfe der Transaktion SE75 definiert. Für eigene Anwendungen können im Kundennamensraum eigene Objekte definiert werden.
TDNAME
Textname
Der Textname besteht bei Texten zu Anwendungsobjekten (Aufträge, Kunden, Materialien, etc.) aus den Schlüsselfeldern der zugehörigen Tabelle. Beispiel: Vertriebstext Material (MVKE): <matnr><vkorg><vtweg>
TDID
Text-ID
Die Id definiert Texte innerhalb eines Text-Objektes. Sie muss im Customizing definiert werden (Transaktion SE75).
TDSPRAS
Sprache
Sprache des Textes.
Schlüsselfelder STXH
Speicherort
Die Texte werden in den Tabellen STXH (Kopfdaten) und STXL (Textzeilen) gespeichert. Die Tabelle STXH kann man in der Tabellenanzeige (Transaktion SE16n) einsehen. In der STXL werden die Textzeilen jedoch komprimiert gespeichert und können nicht angesehen werden.
Texte lesen
Um Texte einzulesen, solltest du den Funktionsbaustein READ_TEXT verwenden. Um Texte zu suchen, kannst du direkt die Tabelle STXH durchsuchen oder du verwendest den Funktionsbaustein SELECT_TEXT.
Speichern von Texten
Die Speicherung von Texten erfolgt mit Hilfe des Funktionsbausteins SAVE_TEXT. Diesem Baustein müssen die Schlüsselfelder übergeben werden. Wenn man weiß, dass der Text noch nicht existiert, dann sollte aus Performancegründen der Parameter INSERT = X gesetzt werden. Möchte man den Text in einer eigenen Anwendung sofort sichern, so kann der Parameter SAVEMODE_DIRECT = X gesetzt werden. Beim Speichern von mehreren Texten sollte der Funktionsbaustein COMMIT_TEXT verwendet werden.
Mit dem Funktionsbaustein INIT_TEXT können Texte angelegt werden.
Zentrales Textmemory
Die Textverwaltung hat ein zentrales Memory. Aus diesem Grund sollte immer mit den entsprechenden Funktionsbausteinen auf die Texte zugegriffen werden. Der READ_TEXT liest den Text aus dem Textmemory, sofern vorhanden.
Textverwaltung SAPMV45A
Kommen wir nun endlich zum Ausgangsthema zurück: Programmatische Bearbeitung von Texten in der Auftragsbearbeitung (Modulpool SAPMV45A). Du solltest wegen des zentralen Textmemories zur Bearbeitung immer die oben beschriebenen Funktionsbausteine verwenden. So kannst du in der Transaktion den Baustein READ_TEXT verwenden, um zu prüfen, ob bereits ein Auftragskopftext zu einer bestimmten ID oder zu einer Position schon ein Text angelegt wurde, auch wenn diese noch nicht auf der Datenbank gespeichert wurde.
Eine separate Speicherung ist nicht notwendig bzw. sogar verboten -, denn die Speicherung erfolgt zentral durch die Applikation. Wird die Transaktion abgebrochen, dann werden die Texte auch nicht gespeichert.
Auf keinen Fall darfst du im Baustein SAVE_TEXT den Parameter SAVEMODE_DIRECT setzen, den Baustein COMMIT_TEXT oder COMMIT WORK verwenden! Das wird von der Applikation SAPMV45A beim Sichern gemacht!
Verwendest du doch einen COMMIT in den Exits, dann riskierst du einen Datenschiefstand und Fehlfunktionen von BAPIs.
Codebeispiel
In folgendem Code-Beispiel wird der Text zu einem neu eingegebenen Material aus dem Literal „ARKTX:“ und dem aktuellen Artikeltext erstellt.
FORM userexit_move_field_to_vbap.
DATA ls_header TYPE thead.
DATA lt_lines TYPE STANDARD TABLE OF tline.
IF vbap-vbeln IS INITIAL.
ls_header-tdname = 'XXXXXXXXXX' && vbap-posnr.
ELSE.
ls_header-tdname = vbap-vbeln && vbap-posnr.
ENDIF.
ls_header-tdobject = 'VBBP'.
ls_header-tdid = '0001'.
ls_header-tdspras = sy-langu.
CALL FUNCTION 'READ_TEXT'
EXPORTING
id = ls_header-tdid
language = ls_header-tdspras
name = ls_header-tdname
object = ls_header-tdobject
local_cat = space
IMPORTING
header = ls_header
TABLES
lines = lt_lines
EXCEPTIONS
OTHERS = 8.
IF sy-subrc <> 0.
lt_lines = VALUE #( ( tdformat = '*' tdline = |test { vbap-arktx }| ) ).
CALL FUNCTION 'SAVE_TEXT'
EXPORTING
header = ls_header
insert = abap_true
savemode_direct = space
local_cat = space
TABLES
lines = lt_lines
EXCEPTIONS
OTHERS = 5.
ENDIF.
ENDFORM.
Im SAPGUI Easy Access Menu gibt es die Möglichkeit, ein Bild anzuzeigen. Das ist einerseits hilfreich, um das Unternehmens-Branding direkt beim Anmelden an das R/3-System zu präsentieren. Andererseits kann man auf diese Weise die unterschiedlichen Systeme gut und einfach kennzeichnen, so dass die Anwendenden auf Anhieb wissen, in welchem System sie sich angemeldet haben.
In unserem Entwicklungssystem sieht das zum Beispiel wie folgt aus:
Inwerken SAP-System
Einstellungen
Um ein Bild für das Easy Access Startmenü festzulegen, gehst du wie folgt vor:
Hochladen eines Bildes in Transaktion SMW0
Einstellen der Parameter
START_IMAGE – Name des Bildes
RESIZE_IMAGE (YES/NO) – Anpassen des Bildes an die Fenstergröße
Hochladen des Bildes
Starte die Transaktion SMW0 und wähle die Option Binary data for WebRFC applications und führe das Programm mit F8 aus. Du kannst auch direkt das Programm RSWWWSHW aufrufen.
Im folgenden Selektionsbild kannst du die Auswahl einschränken, musst du aber nicht. Führe den Report mit F8 aus.
Report RSWWWSHW
Lege einen neuen Eintrag an. Drücke hierzu das Anlegen-Symbol in der Drucktastenleiste oder drücke F5. Im Popup musst du einen Namen und eine Beschreibung vergeben. Der Name muss mit Z beginnen (Kundennamensraum!). Drücke danach das Import-Symbol oder die Tastenkombination SHIFT+F6. Es erscheint ein Dateiauswahldialog in dem du deine Bilddatei auswählst. Ordne noch ein Paket zu und du bist fertig. Den Namen des Bildes benötigst du gleich.
Das Startbild wird über Parameter in der Tabelle SSM_CUST definiert. Rufe die Transaktion SM30 auf, um die Parameter zu pflegen. Oder rufe direkt die Transaktion SM30_SSM_CUST auf.
Gib die folgenden Parameter ein:
Parametername
Wert
Beschreibung
START_IMAGE
<NAME>
Name des in SMW0 angelegten Bildes in Großbuchstaben
RESIZE_IMAGE
YES oder NO
Bildgröße an Fenstergröße anpassen ja/ nein
HIDE_START_IMAGE
YES oder NO
Startbild ignorieren ja/nein
Parametertabelle SSM_CUST
Ein genaues Vorgehen mit weiteren Hinweisen findest du in SAP-Hinweis 1638985.
Die Einstellungen sind Mandanten-unabhängig. Das bedeutet, dass du leider keine unterschiedlichen Bilder in unterschiedlichen Mandanten einstellen kannst (Siehe SAP-Hinweis 1337986).
Animiertes GIF anzeigen
Anstelle eines JPG oder PNG Bildes kannst du auch ein animierten GIF anzeigen. Hierfür musst du das Bild jedoch im Mime Repository ablegen. Wie das genau geht, steht in SAP-Hinweis 2693398.
URL anzeigen
Seit Release 7.00 kannst du auch Webseiten anzeigen. Dazu musst du den Parameter SESS_URL setzen (SAP-Hinweis 1387086). Die URL darf maximal 200 Zeichen lang sein. Eine interessante Möglichkeit also, um ein internes Wiki, die neuesten Tricks oder ähnliche Webseiten einzublenden.
Achtung: Die Verwendung eine URL verzögert gegebenenfalls den Start des SAPGUI, da die Daten erst geladen werden müssen. Das Quartz-Theme hat auf unserem System auf jeden Fall große Schwierigkeiten mit der Anzeige einer URL: Der Start dauert extrem lange, bzw. werden neue Modi gar nicht geöffnet.
Tipps zum Bild
Folgende Tipps helfen dir eventuell bei der Auswahl des Bildes weiter.
Bild ausblenden
Mit dem Parameter HIDE_START_IMAGE kannst du steuern, dass das Startbild nicht angezeigt wird. Falls das Bild nicht angezeigt wird, stelle sicher, dass der Parameter auf NO steht.
Bildgröße
Wähle ein Bild, das nicht zu groß ist. Der Parameter RESIZE_IMAGE passt das Bild nämlich nicht im gleichen Größenverhältnis an, sondern verzerrt es. Das hat zwar den Vorteil, dass das Bild vollflächig angezeigt wird, allerdings sehen – auch leicht – verzerrte Bilder sehr unprofessionell aus.
Leider wird in dem zur Anzeige verwendeten Baustein WB_BITMAP_SHOW für die Darstellung mit CL_GUI_PICTURE die Option DISPLAY_MODE_STRETCH anstelle DISPLAY_MODE_FIT bzw. DISPLAY_MODE_FIT_CENTER verwendet. Mit FIT wird das Bild mit korrektem Seitenverhältnis an die Fenstergröße angepasst.
Bildformat
Als Bildformat kann ich dir PNG empfehlen, denn hier hast du die Möglichkeit, einen transparenten Hintergrund zu definieren. Dadurch sieht das Bild auch dann ordentlich aus, wenn ein anderes GUI-Theme eine andere Hintergrundfarbe hat:
Quartz-Theme (links) und Blue-Crystal-Theme (rechts)
In dem Beitrag ALV-Grid aus SALV ermitteln habe ich beschrieben, wie du bei einem SALV-Grid an das zugrundeliegende ALV-Grid (Klasse CL_GUI_ALV_GRID) heran kommst. Die Vorgehensweise funktioniert allerdings ab Release 7.55 nicht mehr. Deswegen hier ein Coding, das für SAP-Releases ab 7.55 funktioniert.
Danke an Stefan für den Hinweis!
Coding Hilfsklasse
CLASS access_salv DEFINITION INHERITING FROM cl_salv_model_base FINAL.
PUBLIC SECTION.
CLASS-METHODS get_grid_from_salv
IMPORTING
salv TYPE REF TO cl_salv_table
RETURNING
VALUE(grid) TYPE REF TO cl_gui_alv_grid.
PRIVATE SECTION.
" We need to get to r_controller which is protected
" --> access inherits from cl_salv_model_base where controller is defined.
CLASS-METHODS get_cl_gui_alv_grid
IMPORTING
salv_base TYPE REF TO cl_salv_model_base
salv TYPE REF TO cl_salv_table
RETURNING
VALUE(grid) TYPE REF TO cl_gui_alv_grid.
ENDCLASS.
CLASS access_salv IMPLEMENTATION.
METHOD get_cl_gui_alv_grid.
DATA adapter TYPE REF TO if_salv_table_display_adapter.
" Adapter must be bound
IF salv_base->r_controller IS NOT BOUND
OR salv_base->r_controller->r_adapter IS NOT BOUND.
MESSAGE 'programming error: Call GET_GRID_FROM_SALV after SALV->DISPLAY( )!'
TYPE 'S' DISPLAY LIKE 'W'.
salv->display( ).
ENDIF.
IF salv_base->r_controller IS NOT BOUND
OR salv_base->r_controller->r_adapter IS NOT BOUND.
RETURN. " Still not bound --> can't do anything
ENDIF.
" Get grid
TRY.
IF salv_base->r_controller->r_adapter
IS INSTANCE OF if_salv_table_display_adapter.
grid = CAST if_salv_table_display_adapter(
salv_base->r_controller->r_adapter )->get_grid( ).
ENDIF.
CATCH cx_root ##CATCH_ALL.
RETURN.
ENDTRY.
ENDMETHOD.
METHOD get_grid_from_salv.
DATA extended_grid_api TYPE REF TO cl_salv_gui_om_table_info.
" Must be bound
IF salv IS NOT BOUND.
RETURN.
ENDIF.
" Get extended API
" Comment to API: Restricted Use (maintained dev.packages) of extended Grid API
extended_grid_api ?= salv->if_salv_gui_om_table_info~extended_grid_api( ).
grid = access_salv=>get_cl_gui_alv_grid(
salv = salv
salv_base = extended_grid_api ).
ENDMETHOD.
ENDCLASS.
Coding Verwendung
In folgendem Beispiel erzeuge ich ein SALV-Grid und zeige es auf dem Selektionsbildschirm an. Mit der oben genannten Hilfsklasse ermittele ich die CL_GUI_ALV_GRID-Instanz zum SALV und setze mit dieser Instanz zwei Zeilenmarkierungen.
Damit der Report funktioniert, musst du die oben gezeigte Klasse lokal in den Report einbinden, oder du definierst die Hilfsklasse global im Kundennamensraum – was sinnvoll wäre, um sie systemweit nutzen zu können – und musst dann natürlich den Aufruf anpassen.
REPORT.
PARAMETERS test.
INITIALIZATION.
SELECT * FROM t006a INTO TABLE @DATA(demo_data)
UP TO 20 ROWS
WHERE spras = @sy-langu.
TRY.
cl_salv_table=>factory(
EXPORTING
r_container = NEW cl_gui_docking_container(
ratio = 50 side = cl_gui_docking_container=>dock_at_right )
IMPORTING
r_salv_table = DATA(demo_salv)
CHANGING
t_table = demo_data ).
demo_salv->display( ).
CATCH cx_salv_msg. " ALV: General Error Class with Message
ENDTRY.
"access grid
DATA(grid) = access_salv=>get_grid_from_salv( demo_salv ).
"use grid
grid->set_selected_rows(
it_row_no = VALUE #(
( row_id = 2 )
( row_id = 4 ) ) ).
In diesem Beispielprogramm zeige ich dir, wie du ein niedliches Pony anzeigen kannst. Grüße gehen raus an Marco Matjes. Das Coding ist nicht schön, aber es zeigt, wie man ein BASE64-Codiertes Bild (in diesem Fall ein GIF) in einem Container anzeigen kann. Kleine Bilder oder Icons können so relativ einfach im Quelltext eingebunden werden.
So sieht das Pony aus
Code
REPORT.
DATA picture TYPE REF TO cl_gui_picture.
DATA pic_container TYPE REF TO cl_gui_docking_container.
DATA graphic_url(255).
TYPES: BEGIN OF graphic_str,
line(255) TYPE x,
END OF graphic_str,
graphic_tab TYPE STANDARD TABLE OF graphic_str.
DATA graphic_table TYPE graphic_tab.
DATA graphic_size TYPE i.
PARAMETERS p_dummy.
INITIALIZATION.
PERFORM show.
START-OF-SELECTION.
DEFINE a.
concatenate l_graphic_str &1 into l_graphic_str.
END-OF-DEFINITION.
FORM show.
DATA: l_graphic_xstr TYPE xstring,
l_graphic_x TYPE x,
l_graphic_conv TYPE i,
l_graphic_str TYPE string,
l_graphic_offs TYPE i.
pic_container = NEW #( extension = 300 no_autodef_progid_dynnr = 'X' ).
picture = NEW #( parent = pic_container ).
CLEAR graphic_table.
"GIF rennpony 16 colors
a 'R0lGODlhFQATADMAACH5BAAAAAAALAAAAAAVABMAg////19qcHaPjo6ij5XL/p3Mx6rR3qTS+LPUksPessjcysvl+ufbrvrclPfv1Or'.
a '0+wSMEMg5F1mY6g0eztzmNA3TKEu4LYaptI2jShbBMIahlDNwXCwDYcdQeQ6HwEDxOyiKmyNhKhj8pobYBjkl6K5TokZRKBe+3enhRn'.
a 'Eg3u/EbwH2aiWJfB6hfjzAa3d6ewYHDzRpWQ0SCoMJBoc0gCYOMo15hho1aiaLAAqgkRR/aYE9iKVQPZtppwCsXREAOw=='.
CALL FUNCTION 'SSFC_BASE64_DECODE'
EXPORTING
b64data = l_graphic_str
IMPORTING
bindata = l_graphic_xstr
EXCEPTIONS
OTHERS = 8.
graphic_size = xstrlen( l_graphic_xstr ).
CHECK graphic_size > 0.
l_graphic_conv = graphic_size.
l_graphic_offs = 0.
WHILE l_graphic_conv > 255.
APPEND VALUE #( line = l_graphic_xstr+l_graphic_offs(255) ) TO graphic_table.
l_graphic_offs = l_graphic_offs + 255.
l_graphic_conv = l_graphic_conv - 255.
ENDWHILE.
APPEND VALUE #( line = l_graphic_xstr+l_graphic_offs(l_graphic_conv) ) TO graphic_table.
CALL FUNCTION 'DP_CREATE_URL'
EXPORTING
type = 'image' "#EC NOTEXT
subtype = 'gif'
cacheable = space
size = graphic_size
lifetime = cndp_lifetime_transaction
TABLES
data = graphic_table
CHANGING
url = graphic_url.
picture->clear_picture( ).
picture->load_picture_from_url( url = graphic_url ).
picture->set_display_mode( cl_gui_picture=>display_mode_fit ).
ENDFORM.
In diesem Beitrag schrieb ich bereits darüber, wie du den Funktionsbaustein SVRS_MASSCOMPARE_ACT_OBJECTS nutzen kannst, um einen detaillierten Versionsvergleich über Systemgrenzen hinweg zu bekommen.
Durch Zufall bin ich nun auf den Funktionsbaustein SVRS_MASSCOMPARE_ACT_AND_SHOW gestoßen, der alles noch viel einfacher macht. Den Baustein kannst du für kleinere Vergleiche direkt aus der Testumgebung heraus verwenden. Du musst lediglich die RFC-Destination für das entfernte System in Parameter IV_RFCDEST_B eintragen sowie die Tabelle IT_E071:
Eingabeparameter Testumgebung
In Parameter IT_E071 reicht es, die Felder PGMID, OBJECT und OBJ_NAME zu füllen:
Parameter IT_E071
Als Ergebnis erhältst du eine Liste mit allen Teilobjekten und der Info, ob diese unterschiedlich sind. Mit einem Doppelklick gelangst zur direkten Gegenüberstellung der Unterschiede:
Die Klasse CL_SALV_TABLE ist super für alle Arbeiten rund um Grid-basierte Listen. Der riesengroße Vorteil des CL_SALV_TABLE ist der Umstand, dass man den Feldkatalog nicht selbst ermitteln muss, sondern die anzuzeigende Tabelle einfach der Methode CL_SALV_TABLE=>FACTORY übergeben kann. Das funktioniert selbst mit lokal im Programm definierten internen Tabellen.
Was liegt also näher, um dieses Verfahren auch für die Auswahl von Einträgen aus einer Liste zu verwenden? Um das SALV als Popup anzuzeigen, nutzt du die Methode SET_SCREEN_POPUP. Beim darauffolgenden DISPLAY wird das SALV als Popup angezeigt.
Einschränkungen
Leider hat diese Methode einige Einschränkungen, die es notwendig machen, doch wieder mehr drumherum zu programmieren, als eigentlich notwendig.
Es gibt keine Abbrechen-Drucktaste
Es kann kein Wert mit Doppelklick ausgewählt werden
Beiden Funktionen sind jedoch notwendig, um dem Anwendenden einen sinnvollen Dialog zu bieten, um einen Eintrag aus einer Liste auszuwählen.
Es gibt zwei schöne Beispiele in der Codezentrale, die zeigen, dass es möglich ist:
Das erste Beispiel verwendet sehr viel Coding, denn die fehlenden Drucktasten werden umständlich durch eine Toolbar und Splitter-Container realisiert. Die Lösung ist sehr schick und einmal programmiert, kann diese auch wieder verwendet werden.
In der zweiten Lösung, in der der SALV-Table als F4-Suchhilfe verwendet wird, fehlt leider der Doppelklick, um einen Eintrag auszuwählen.
Einfache Lösung
Meine Lösung nutzt die Registrierung des Doppelklick-Events vom SALV-Table und wertet den implizit vorhandenen Abbrechen-Funktionscode aus.
CL_SALV_TABLE als Auswahl-Popup
Abbruch
Das SAL-Popup hat zwar keine Abbrechen-Drucktaste, jedoch ist der Funktionscode über die Taste ESC aktiv, so dass der Dialog durch das Drücken der Escape-Taste beendet werden kann.
Der Funktionscode wird in der Systemvariablen SY-UCOMM gespeichert. Alle Funktionscodes der Klasse CL_SALV_TABLE sind in dem Interface IF_SALV_C_FUNCTION verfügbar. Nach Aufruf von salv->display() kann also SY-UCOMM abgefragt werden. Leider ist nach Drücken der Enter-Taste SY-UCOMM leer, so dass nicht nur if_salv_c_function=>continue sondern auch SPACE abgefragt werden müssen.
Doppelklick
Um der anwendenden Person einen Doppelklick zu ermöglichen, registriere ich das Ereignis DOUBLE_CLICK der Klasse CL_SALV_EVENTS_TABLE. Um direkt darauf das Popup auch schließen und die Auswahl bestätigen zu können, rufe ich den Funktionsbaustein SAPGUI_SET_FUNCTIONCODE mit dem Funktionscode für „Continue“ auf.
Coding
PARAMETERS p_title TYPE string DEFAULT 'Weekday selection'.
PARAMETERS p_item1 TYPE c LENGTH 30 LOWER CASE DEFAULT 'MONMonday'.
PARAMETERS p_item2 TYPE c LENGTH 30 LOWER CASE DEFAULT 'TUETuesday'.
PARAMETERS p_item3 TYPE c LENGTH 30 LOWER CASE DEFAULT 'WEDWednesday'.
PARAMETERS p_item4 TYPE c LENGTH 30 LOWER CASE DEFAULT 'THUThursday'.
PARAMETERS p_item5 TYPE c LENGTH 30 LOWER CASE DEFAULT 'FRIFriday'.
PARAMETERS p_item6 TYPE c LENGTH 30 LOWER CASE DEFAULT 'SATSaturday'.
PARAMETERS p_item7 TYPE c LENGTH 30 LOWER CASE DEFAULT 'SUNSunday'.
CLASS cancelled DEFINITION INHERITING FROM cx_static_check.
ENDCLASS.
CLASS main DEFINITION.
PUBLIC SECTION.
TYPES: BEGIN OF _item,
key TYPE c LENGTH 3,
value TYPE c LENGTH 20,
END OF _item,
_items TYPE STANDARD TABLE OF _item WITH DEFAULT KEY.
METHODS set_items
IMPORTING
items TYPE _items.
METHODS ask
IMPORTING
title TYPE clike
RETURNING
VALUE(result) TYPE _item
RAISING
cancelled.
PRIVATE SECTION.
DATA salv_popup TYPE REF TO cl_salv_table.
DATA items TYPE _items.
METHODS: on_double_click FOR EVENT double_click OF cl_salv_events_table
IMPORTING
row
column
sender.
ENDCLASS.
CLASS main IMPLEMENTATION.
METHOD set_items.
me->items = items.
ENDMETHOD.
METHOD on_double_click.
DATA(selections) = salv_popup->get_selections( ).
selections->set_selected_rows( VALUE #( ( row ) ) ).
* cl_gui_cfw=>set_new_ok_code( if_salv_c_function=>continue ).
CALL FUNCTION 'SAPGUI_SET_FUNCTIONCODE'
EXPORTING
functioncode = if_salv_c_function=>continue.
ENDMETHOD.
METHOD ask.
TRY.
cl_salv_table=>factory(
IMPORTING
r_salv_table = salv_popup
CHANGING
t_table = items ).
DATA(display) = salv_popup->get_display_settings( ).
display->set_list_header( CONV #( title ) ).
DATA(selections) = salv_popup->get_selections( ).
selections->set_selection_mode( if_salv_c_selection_mode=>single ).
selections->set_selected_rows( VALUE #( ( 1 ) ) ).
DATA(columns) = salv_popup->get_columns( ).
columns->set_optimize( abap_true ).
salv_popup->set_screen_popup(
start_column = 10
end_column = 30
start_line = 10
end_line = 17 ).
SET HANDLER on_double_click FOR salv_popup->get_event( ).
salv_popup->display( ).
CASE sy-ucomm.
WHEN space OR if_salv_c_function=>continue.
DATA(selected_rows) = selections->get_selected_rows( ).
DATA(selected_row) = selected_rows[ 1 ].
result = items[ selected_row ].
WHEN if_salv_c_function=>cancel.
RAISE EXCEPTION TYPE cancelled.
ENDCASE.
CATCH cx_salv_msg INTO DATA(error).
MESSAGE error TYPE 'I'.
ENDTRY.
ENDMETHOD.
ENDCLASS.
INITIALIZATION.
DATA(my_popup) = NEW main( ).
AT SELECTION-SCREEN.
my_popup->set_items(
VALUE #(
( key = p_item1(3) value = p_item1+3(20) )
( key = p_item2(3) value = p_item2+3(20) )
( key = p_item3(3) value = p_item3+3(20) )
( key = p_item4(3) value = p_item4+3(20) )
( key = p_item5(3) value = p_item5+3(20) )
( key = p_item6(3) value = p_item6+3(20) )
( key = p_item7(3) value = p_item7+3(20) ) ) ).
TRY.
DATA(selected_entry) = my_popup->ask( p_title ).
MESSAGE |you selected { selected_entry-value }| TYPE 'I'.
CATCH cancelled.
MESSAGE 'you cancelled the selection' TYPE 'I'.
ENDTRY.
In diesem Artikel habe ich dir gezeigt, wie du die Klasse CL_SALV_TABLE als Popup und zur Auswahl von Daten nutzen kannst. Heute möchte ich dir zeigen, wie du diese Funktion in einer Suchhilfe mit Hilfe eines Suchhilfe-Exits nutzen kannst. In diesem Beispiel rufen wir, wie in dem verlinkten Artikel, eine Liste der Wochentage auf aus denen der Anwender einen auswählen kann.
Auswahl Wochentag
Der einzige Unterschied zum verlinkten Code ist, dass ich für die Schlüsselkomponente nun ein einstelliges Kennzeichen vom Typ WEEKDAY verwende.
Suchhilfe
In der Codezentrale kannst du dir ansehen, wie du einen Wert in einem Report über eine eigenprogrammierte F4-Suchhilfe übergeben kannst:
Das Verfahren nützt dir jedoch nichts, wenn du die F4-Hilfe ohne Programmierung in einem Dynprofeld nutzen möchtest. Hierfür musst du in der Transaktion SE11 eine separate Suchhilfe anlegen:
Suchhilfe mit Suchhilfe-Exit
Suchhilfe-Exit
Die Anlage der Suchhilfe ist in diesem Fall jedoch erst Schritt zwei. Im ersten Schritt musst du einen Funktionsbaustein anlegen, der die Werte anzeigt und mit der Suchhilfe kommuniziert.
Für den Suchhilfe-Exit benötigst du einen Funktionsbaustein mit einer definierten Schnittstelle. In der F1-Hilfe zum Suchhilfe-Exit erfährst du, dass du den Baustein F4IF_SHLP_EXIT_EXAMPLE als Vorlage verwenden kannst. Du musst eine neue Funktionsgruppe anlegen (Transaktion SE37: Springen • Funktionsgruppe • Funktionsgruppe anlegen) und den Vorlage-Baustein in diese Gruppe kopieren.
Vorlagebaustein kopieren
Am Ende des kopierten Funktionsbausteins setzt du das folgende Coding ein:
IF callcontrol-step = 'DISP'.
DATA(my_popup) = NEW main( ).
my_popup->set_items(
VALUE #(
( key = '1' value = 'Monday' )
( key = '2' value = 'Tuesday' )
( key = '3' value = 'Wednesday' )
( key = '4' value = 'Thursday' )
( key = '5' value = 'Friday' )
( key = '6' value = 'Saturday' )
( key = '7' value = 'Sunday' ) ) ).
TRY.
DATA(selected_entry) = my_popup->ask( 'Select day' ).
append value #( string = conv #( selected_entry-key ) ) to record_tab.
callcontrol-step = 'RETURN'.
CATCH cancelled.
callcontrol-step = 'EXIT'.
ENDTRY.
ENDIF.
In diesem Beispiel habe ich das Coding (Siehe CL_SALV_TABLE als Auswahl-Popup) als lokale Klasse im Funktionsbaustein verwendet. Sinnvoller wäre es jedoch, die Klasse MAIN als globale Klasse anzulegen (Transaktion SE24). In diesem Fall musst du bei der Instanziierung natürlich nicht MAIN verwenden, sondern den Namen der globalen Klasse.
Wichtig ist, dass bei Auswahl eines Eintrags die RECORD_TAB mit dem gewählten Eintrag angereichert wird und der CALLCONTROL-STEP auf „RETURN“ gesetzt wird. Bricht der Benutzer den Dialog ab, dann muss CALLCONTROL-STEP auf „EXIT“ gesetzt werden.
Den Funktionsbaustein und die Funktionsgruppe musst du selbstverständlich aktivieren.
Aktivierung Suchhilfe
Nachdem du den Suchhilfe-Exit Funktionsbaustein in die Suchhilfe eingetragen hast, musst du die Suchhilfe aktivieren und kannst sie testen:
Test der Suchhilfe
Suchhilfe einbinden
Du kannst diese Suchhilfe nun in einem Datenelement unter der Registerkarte „Weitere Eigenschaften“ festlegen:
Suchhilfe im Datenelement definieren
Die Suchhilfe steht dir nun immer zur Verfügung, wenn du dieses Datenelement in einer Struktur verwendest.
Suchhilfe testen
Wenn du Suchhilfen mit Suchhilfe-Exits testest, dann achte darauf, dass du nach jeder Codeänderung im Suchhilfe-Exit-Funktionsbaustein die Transaktion SE11 neu aufrufst. Andernfalls ist das alte Coding noch geladen und du wirst die Änderungen nicht sehen!
Das Einfügen von Daten aus der Zwischenablage in einen Tabellenpflegedialog ist leider nur eingeschränkt möglich. Es können immer nur so viele Daten eingetragen werden, wie auf dem Bildschirm dargestellt werden. Möchte man von einem System in ein anderes Daten kopieren, dann geht man in der Regel wie folgt vor:
Quell-Pflegedialog auswählen
STRG -Y drücken um den Block-Markiermodus zu aktivieren
Mit der Maus die Felder markieren, die kopiert werden sollen
Mit STRG – C die ausgewählten Daten kopieren
In den Ziel-Pflegedialog wechseln
Kopierte Daten mit STRG – V einfügen
Alternativ können die Daten – sofern sie die gleiche Struktur haben, wie der Pflegedialog – auch aus Excel kopiert werden. Allerdings auch immer nur so viele Zeilen, wie in den Pflegedialog eingefügt werden können.
Möchte man also mehrere hundert oder sogar tausende von Einträgen kopieren, dann möchte man schnell eine andere Möglichkeit haben…
Alternative
Das folgende Programm zeigt auf, wie Daten auf zwei Möglichkeiten in einen Tabellenpflegedialog eingefügt werden können:
Import als CSV (Comma-separated-values) mit einem Semikolon als Trennzeichen
Import eines kopierten Bereiches aus Microsoft Excel mit einem Tabulator als Trennzeichen.
Die Daten werden mit der korrekten Struktur der zu importierenden Tabelle an den Funktionsbaustein VIEW_MAINTENANCE_GIVEN_DATA und Anzeige der Daten. Sind alle Daten korrekt, können sie gespeichert werden.
TYPES: BEGIN OF _text,
line TYPE c LENGTH 1000,
END OF _text,
_text_tab TYPE STANDARD TABLE OF _text WITH DEFAULT KEY.
PARAMETERS p_demo RADIOBUTTON GROUP a DEFAULT 'X'.
PARAMETERS p_clpb RADIOBUTTON GROUP a.
START-OF-SELECTION.
DATA(import_table) = CONV tabname( 'ZMVDIMP' ).
IF p_demo = abap_true.
DATA(import_data_csv) = VALUE _text_tab(
( line = '100;123;1000;6600' )
( line = '100;333;1000;6600' )
( line = '100;56;3000;2200' )
).
DATA(delimiter) = ';'.
ELSE.
cl_gui_frontend_services=>clipboard_import(
IMPORTING
data = import_data_csv ).
delimiter = cl_abap_char_utilities=>horizontal_tab.
ENDIF.
FIELD-SYMBOLS <import_data_line> TYPE any.
FIELD-SYMBOLS <import_data_tab> TYPE table.
DATA import_data_table_ref TYPE REF TO data.
DATA import_data_struc_ref TYPE REF TO data.
DATA(import_data_struc) = CAST cl_abap_structdescr(
cl_abap_structdescr=>describe_by_name( 'ZMVDIMP' ) ).
DATA(vimflagtab_struc) = CAST cl_abap_structdescr(
cl_abap_structdescr=>describe_by_name( 'VIMFLAGTAB' ) ).
DATA(maint_struc_components) = import_data_struc->get_components( ).
APPEND LINES OF vimflagtab_struc->get_components( ) TO maint_struc_components.
DATA(import_maint_struc) = cl_abap_structdescr=>create( maint_struc_components ).
DATA(import_data_table) = cl_abap_tabledescr=>create( p_line_type = import_maint_struc ).
CREATE DATA import_data_struc_ref TYPE HANDLE import_maint_struc.
ASSIGN import_data_struc_ref->* TO <import_data_line>.
CREATE DATA import_data_table_ref TYPE HANDLE import_data_table.
ASSIGN import_data_table_ref->* TO <import_data_tab>.
LOOP AT import_data_csv INTO DATA(csv_line).
CLEAR <import_data_line>.
SPLIT csv_line AT delimiter INTO TABLE DATA(import_data_values).
LOOP AT import_data_values INTO DATA(value).
ASSIGN COMPONENT sy-tabix OF STRUCTURE <import_data_line> TO FIELD-SYMBOL(<field>).
<field> = value.
ENDLOOP.
ASSIGN COMPONENT 'ACTION' OF STRUCTURE <import_data_line> TO FIELD-SYMBOL(<action>).
<action> = 'N'.
APPEND <import_data_line> TO <import_data_tab>.
ENDLOOP.
CALL FUNCTION 'VIEW_MAINTENANCE_GIVEN_DATA'
EXPORTING
action = 'U'
view_name = import_table
TABLES
data = <import_data_tab>
EXCEPTIONS
client_reference = 1 " View is tied to another client
foreign_lock = 2 " View/Table is locked by another user
invalid_action = 3 " ACTION contains invalid values
no_clientindependent_auth = 4 " no auth. for maintaining client-indep. tables/v
no_database_function = 5 " Fct. mod. for data capture/disposal is missing
no_show_auth = 6 " no display authorization
no_tvdir_entry = 7 " View/table is not entered in TVDIR
no_upd_auth = 8 " no maintenance or display authorization
only_show_allowed = 9 " Display, but not maintain authorization
system_failure = 10 " System locking error
unknown_field_in_dba_sellist = 11 " Selection table contains unknown field
view_not_found = 12 " View/table not found in DDIC
OTHERS = 13.
IF sy-subrc <> 0.
MESSAGE |Error: { sy-subrc }| TYPE 'I'.
ENDIF.
Wer schon einmal einen Funktionsbaustein getestet hat, der kennt mit Sicherheit den Dialog zur Eingabe von strukturierten Daten und Tabellen. Der Funktionsbaustein RS_COMPLEX_OBJECT_EDIT bietet dir genau diese Möglichkeit für deine eigenen Daten an. Folgend ein kleines Demoprogramm, das die Verwendung zeigt.
Ändern von zwei DatensätzenAnzeige der Struktur (Meta-Daten)Ändern der internen Tabelle der ersten Zeile
Code
REPORT zz_edit_complex_data.
PARAMETERS p_edit RADIOBUTTON GROUP mode DEFAULT 'X'.
PARAMETERS p_show RADIOBUTTON GROUP mode.
PARAMETERS p_meta RADIOBUTTON GROUP mode.
START-OF-SELECTION.
TYPES: BEGIN OF ts_detail,
a TYPE c LENGTH 1,
b TYPE c LENGTH 3,
END OF ts_detail,
tt_detail TYPE STANDARD TABLE OF ts_detail WITH EMPTY KEY.
TYPES: BEGIN OF ts_data,
land1 TYPE land1,
landx TYPE landx,
detail TYPE tt_detail,
END OF ts_data.
DATA gt_data TYPE STANDARD TABLE OF ts_data WITH DEFAULT KEY.
DATA gv_mode TYPE c LENGTH 1.
SELECT * FROM t005t INTO CORRESPONDING FIELDS OF TABLE gt_data UP TO 20 ROWS.
gt_data = VALUE #(
( land1 = 'DE' landx = 'Germany' detail = VALUE #(
( a = '1' b = 'AA' )
( a = '2' b = 'BB' ) ) )
( land1 = 'FR' landx = 'France' detail = VALUE #(
( a = 'X' b = 'RR' )
( a = 'Y' b = 'SS' ) ) )
).
gv_mode = COND #(
WHEN p_edit = 'X' THEN 'X'
WHEN p_meta = 'X' THEN 'M'
ELSE space ).
CALL FUNCTION 'RS_COMPLEX_OBJECT_EDIT'
EXPORTING
object_name = 'Edit country details'
mode = gv_mode
insert_tab = ' '
upper_case = ' '
popup = 'X'
display_accessible = 'X'
CHANGING
object = gt_data
EXCEPTIONS
object_not_supported = 1
OTHERS = 2.
IF sy-subrc <> 0.
MESSAGE ID sy-msgid
TYPE sy-msgty
NUMBER sy-msgty
WITH sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4.
ENDIF.
Dynprogrammierung ist eine Angelegenheit mit, sagen wir mal, viel Historie. Gerade wenn man ein Programm mit Historie und Subscreens vor sich hat, ist es mitunter schwer, herauszufinden, in welchem Programm welcher Subscreen definiert wurde und welche Nummer der Subscreen hat.
In Hinweis 324687 (direkter Download) wird beschrieben, wie du die Subscreen-Bereiche kenntlich machen kannst. In einem Business Partner sieht das zum Beispiel so aus:
Aktivierung der Funktionalität
Um die Funktionalität nutzen zu können, müssen in der Registry des Testrechners folgende Einträge erstellt werden: [HKEY_CURRENT_USER\Software\SAP\SAPGUI Front\SAP Frontend Server\ReleaseDebug\ShowBorders]
„ENABLED“ (REG_DWORD) mit Wert 1
„SHOW_ALL“ (REG_DWORD) mit Wert 0
„CMyManager.ShowBorders“ (REG_DWORD) mit Wert 1
„CMyManager.ShowNames“ (REG_DWORD) mit Wert 1
Gegebenenfalls müssen zuerst der Key „ReleaseDebug“ und dann innerhalb dieses Keys „ShowBorders“ angelegt werden. Die Namen der Keys und Werte oben sind jeweils ohne Anführungszeichen einzutragen.
Bei einem ALV-Grid wird die anzuzeigende Datentabelle der Methode SET_TABLE_FOR_FIRST_DISPLAY mitgegeben. Eine Änderung kann eigentlich nur aus der aufrufenden Klasse oder im Ereignis DATA_CHANGED erfolgen. Wird die Datentabelle im Programm geändert, so muss die Methode REFRESH_TABLE_DISPLAY aufgerufen werden, damit die Änderungen auch im Frontend angezeigt werden.
In einigen Fällen kann es jedoch erforderlich sein, die Daten von außen zu ändern. Dies ist jedoch nicht möglich, da das Attribut MT_OUTTAB, das die Datenreferenz zur Datentabelle hält, geschützt (protected) ist. Zudem werden die folgenden beiden öffentlichen Methoden, die es ermöglichen könnten, die Daten zu ändern, nicht unterstützt:
SET_DATA_CELLS
CHANGE_DATA_FROM_INSIDE
Wenn ich diese verwende, erhalte ich den Shortdump ASSERTION_FAILED in Klasse CL_DATAPTABLECACHE.
Glücklicherweise gibt es das If you wanna be my lover – Das FRIENDS-Konzept. Das können wir uns zunutze machen, indem wir die ALV-Grid-Instanz an eine Klasse übergeben, die mit CL_GUI_ALV_GRID befreundet ist und uns die Datenreferenz von MT_OUTTAB zurückgeben lassen.
Friends-Klasse
CLASS alv_data DEFINITION.
PUBLIC SECTION.
INTERFACES if_alv_rm_grid_friend .
CLASS-METHODS get_outtab
IMPORTING
ir_grid TYPE REF TO cl_gui_alv_grid
RETURNING
VALUE(ro_outtab) TYPE REF TO data.
ENDCLASS.
CLASS alv_data IMPLEMENTATION.
METHOD get_outtab.
ro_outtab = ir_grid->mt_outtab.
ENDMETHOD.
ENDCLASS.
Nutzung
Wenn wir also die Instanz eines ALV-Grids haben, dann können wir dieses an die Klasse ALV_DATA=>GET_OUTTAB übergeben und erhalten eine nutzbare Datenreferenz. Diese Datenreferenz können wir zu einem Feldsymbol zuweisen:
DATA(my_outtab) = alv_data=>get_outtab( my_grid ).
FIELD-SYMBOLS <outtab> TYPE TABLE.
ASSIGN my_outtab->* TO <outtab>.
Anwendungsbeispiel „Navigationsprofil“
Wenn du dich bisher gefragt hast, wo zum Geier man sowas machen wollen würde, dann schau dir dieses Beispiel an. Es gibt die Funktionalität Navigationsprofil. Mit dieser Funktionalität kannst du ein ALV-Grid erweitern und modifikationsfrei Funktionalitäten hinzufügen. Das kann zum Beispiel durch eine Klasse erfolgen, die das Interface IF_NAVIGATION_PROFILE implementiert. In der Methode USER_COMMAND werden eine Referenz auf die Daten und die Instanz des ALV-Grid übergeben. mit GET_SELECTED_ROWS ist es dann möglich, ausgewählte Zeilen zu ermitteln und aufgrund dieser eine andere Funktionalität aufzurufen.
Allerdings ist es nicht möglich, die Daten selbst zu ändern. Es sei denn, du verwendest den oben genannten Trick.
Wie das Beispiel im Detail funktioniert, kannst du mit dem Demoprogramm NAVP_DEMO_TABLE nachvollziehen. Hierzu musst du das Navigationsprofil ändern und einen Button hinzufügen. Wie genau das geht, kannst du hier erfahren.
Zuerst benötigst du jedoch eine Klasse, die die Datenänderungen vornimmt.
CLASS zcl_navigation_profile_enno DEFINITION
PUBLIC
FINAL
CREATE PUBLIC .
PUBLIC SECTION.
INTERFACES if_navigation_profile .
PROTECTED SECTION.
PRIVATE SECTION.
ENDCLASS.
CLASS ZCL_NAVIGATION_PROFILE_ENNO IMPLEMENTATION.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Public Method ZCL_NAVIGATION_PROFILE_ENNO->IF_NAVIGATION_PROFILE~USER_COMMAND
* +-------------------------------------------------------------------------------------------------+
* | [--->] IO_ALV TYPE REF TO OBJECT
* | [--->] ID_TABLE TYPE REF TO DATA
* | [--->] IS_PROFILE_KEY TYPE NAVP_S_PROFILE_KEY
* | [--->] IV_PARAMETER TYPE NAVP_FUNCTION_PARAMETER
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD if_navigation_profile~user_command.
DATA outtab TYPE REF TO data.
FIELD-SYMBOLS <table> TYPE table.
DATA(alv) = CAST cl_gui_alv_grid( io_alv ).
outtab = lcl_data=>get_outtab( alv ).
ASSIGN outtab->* TO <table>.
DATA lt_delta TYPE lvc_t_modi.
alv->get_selected_rows( IMPORTING et_row_no = DATA(lt_row_no) ).
LOOP AT lt_row_no INTO DATA(ls_row).
ASSIGN <table>[ ls_row-row_id ] TO FIELD-SYMBOL(<line>).
ASSIGN COMPONENT 'FLDATE' OF STRUCTURE <line> TO FIELD-SYMBOL(<fldate>).
<fldate> = sy-datum.
ENDLOOP.
FIELD-SYMBOLS <outtab> TYPE table.
alv->refresh_table_display(
i_soft_refresh = abap_true
is_stable = VALUE #( row = abap_true col = abap_true ) ).
alv->set_selected_rows( it_row_no = lt_row_no ).
ENDMETHOD.
ENDCLASS.
Danach kannst du die neue Drucktaste anlegen:
Drucktaste „Set Date“
Die Drucktaste wird nun – modifikationsfrei – in der Toolbar angezeigt:
Wenn du Einträge markierst und die Drucktaste drückst, dann wird das Flugdatum der markierten Einträge auf das Tagesdatum gesetzt.
Im Standard gibt es keine Möglichkeit, eine Klasse massenhaft, also mit vielen verschiedenen Namen zu kopieren. Gerade für Schulungen kann es jedoch sinnvoll sein, eine Klasse für viele Benutzer zu kopieren. Besonders sinnvoll ist es zum Beispiel für das UnitTest Koan von Damir Majer. Bei diesem Koan geht es darum, die verschiedenen Techniken der Unit-Tests zu erlernen.
Mit dem folgenden Programm ist es möglich, eine Klasse auf verschiedene Klassen zu kopieren. Es werden alle Benutzer einer bestimmten Benutzergruppe ermittelt. Für jeden Benutzer wird eine Klasse mit dem Benutzernamen aus der zu kopierenden Klasse erstellt.
Zusätzlich kannst du entscheiden, ob für jeden Benutzer eine eigene Entwicklungsklasse angelegt wird.
Mit der Testoption kannst du dir die Klassen, die angelegt werden würden, ausgeben lassen.
Code
REPORT zt9r_copy_class.
PARAMETERS p_test AS CHECKBOX DEFAULT 'X'.
PARAMETERS p_crdev AS CHECKBOX DEFAULT 'X'.
PARAMETERS p_class TYPE seoclskey DEFAULT 'ZCL_KOANS_ABOUT_ABAPUNIT'.
PARAMETERS p_newcl TYPE seoclskey DEFAULT 'ZCL_KOANS_<USER>'.
PARAMETERS p_usrcls TYPE usr02-class DEFAULT 'DEV'.
SELECT-OPTIONS so_user FOR sy-uname.
START-OF-SELECTION.
PERFORM copy.
FORM copy.
DATA ls_newcls TYPE seoclskey.
DATA lt_class_keys TYPE seoc_class_keys.
DATA lv_save TYPE sap_bool.
DATA lv_devclass TYPE devclass.
SELECT bname
FROM usr02
INTO TABLE @DATA(lt_users)
WHERE bname IN @so_user
AND class = @p_usrcls. "do not change DDIC, SAP*, etc
LOOP AT lt_users INTO DATA(ls_user)
WHERE bname <> 'DDIC'
AND bname <> 'SAP*'.
IF p_crdev = abap_true.
PERFORM create_devclass USING ls_user-bname.
ENDIF.
lv_devclass = |${ ls_user-bname }|.
ls_newcls-clsname = p_newcl.
REPLACE '<USER>' WITH ls_user-bname INTO ls_newcls-clsname.
WRITE: / ls_newcls-clsname.
CHECK p_test = space.
CALL FUNCTION 'SEO_CLASS_COPY'
EXPORTING
clskey = p_class
new_clskey = ls_newcls
save = lv_save
CHANGING
devclass = lv_devclass
EXCEPTIONS
not_existing = 1
deleted = 2
is_interface = 3
not_copied = 4
db_error = 5
no_access = 6
OTHERS = 7.
IF sy-subrc = 0.
WRITE: 'copied'.
lt_class_keys = VALUE #( ( ls_newcls ) ).
CALL FUNCTION 'SEO_CLASS_ACTIVATE'
EXPORTING
clskeys = lt_class_keys
EXCEPTIONS
not_specified = 1
not_existing = 2
inconsistent = 3
OTHERS = 4.
. IF sy-subrc = 0.
WRITE: 'und aktiviert'.
ENDIF.
ELSE.
WRITE: / ls_newcls-clsname, 'NOT copied'.
ENDIF.
ENDLOOP.
ENDFORM.
FORM create_devclass USING name.
DATA lv_devclass TYPE devclass.
DATA ls_devclass TYPE trdevclass.
DATA lv_changed TYPE c LENGTH 1.
DATA lv_text TYPE c LENGTH 80.
ls_devclass-devclass = |${ name }|.
CALL FUNCTION 'TRINT_DEVCLASS_GET'
EXPORTING
iv_devclass = ls_devclass-devclass
EXCEPTIONS
devclass_not_found = 1 " Package Does Not Exist
OTHERS = 2.
IF sy-subrc = 0.
RETURN.
ENDIF.
ls_devclass-ctext = |local package for { name }|.
ls_devclass-as4user = name.
ls_devclass-pdevclass = space.
ls_devclass-dlvunit = 'LOCAL'.
ls_devclass-component = space.
ls_devclass-comp_appr = space.
ls_devclass-comp_text = space.
ls_devclass-korrflag = 'X'.
ls_devclass-namespace = space.
ls_devclass-tpclass = space.
ls_devclass-type = 'N'.
ls_devclass-target = space.
ls_devclass-packtype = space.
ls_devclass-restricted = space.
ls_devclass-mainpack = space.
ls_devclass-created_by = 'COPYREPORT'.
ls_devclass-created_on = sy-datum.
CALL FUNCTION 'TRINT_MODIFY_DEVCLASS'
EXPORTING
iv_action = 'CREA'
iv_dialog = space
is_devclass = ls_devclass
iv_request = space
IMPORTING
es_devclass = ls_devclass
ev_something_changed = lv_changed
EXCEPTIONS
no_authorization = 1
invalid_devclass = 2
invalid_action = 3
enqueue_failed = 4
db_access_error = 5
system_not_configured = 6
OTHERS = 7.
IF sy-subrc > 0.
MESSAGE i000(oo) WITH 'error creating package' lv_devclass.
STOP.
ELSE.
WRITE: / 'Package created:', name, lv_text.
ENDIF.
ENDFORM. "create_devclass
Heute stand ich vor der Herausforderung, dass ich alle Code-Inspector-Prüfungen herausfinden wollte, die in einer Prüfvariante aktiv sind. Natürlich kann man sich diese über die Pflege der Varianten im Code-Inspector über Transaktion SCI anzeigen lassen. Allerdings wird hier der gesamte Baum der verfügbaren Prüfungen aufgelistet und man muss sich die heraussuchen, die aktiviert sind. Eine nervige und Fehleranfällige Aufgabe, wenn man die aktiven Prüfungen irgendwie dokumentieren möchte.
So sieht die Baumstruktur der Prüfungen aus:
Um die technischen Texte anstelle der Beschreibungen anzuzeigen, gehe über das Menü Prüfvariante • Anzeigen • Technische Namen.
Auflistung der aktiven Prüfvarianten
Mit dem folgenden Code werden die aktiven Prüfungen zu einer Prüfvariante ermittelt und ausgegeben.
Wenn du die Prüfungen einer globalen Prüfvariante ermitteln möchtest, dann muss OWNER leer bleiben. Für eine lokale Prüfvariante setze den entsprechenden User ein.
Code
PARAMETERS name TYPE scichkv_hd-checkvname DEFAULT 'DEFAULT'.
PARAMETERS owner TYPE scichkv_hd-ciuser DEFAULT space.
START-OF-SELECTION.
SELECT SINGLE * FROM scichkv_hd
WHERE checkvname = @name ##WARN_OK
AND ciuser = @owner "#EC CI_NOORDER
INTO @DATA(variant_header).
DATA(main) = cl_ci_checkvariant=>get_ref(
p_user = owner
p_name = name ).
CHECK main IS BOUND.
main->get_info(
EXCEPTIONS
could_not_read_variant = 1
OTHERS = 2 ).
IF sy-subrc = 0.
cl_demo_output=>display_data(
VALUE string_table(
FOR variant IN main->variant (
CONV #( variant-testname ) ) ) ).
ENDIF.
In einem Projekt entstand die Idee, einen Termin, den eine SAP-Applikation ermittelt hat, als Termin an die verantwortliche Person zu schicken. Technisch muss man hierzu eine ICS-Datei erstellen und diese per E-Mail versenden. Wie ich gehofft hatte, hatte jemand bei SAP auch vorher schon eine ähnliche Idee und hat die Klasse CL_APPOINTMENT programmiert.
Das folgende Beispielprogramm ist eine Abwandlung des Demo-Reports RSSC_DEMO_CL_APPOINTMENT_APPL mit dem Zusatz, den erstellten Termin zu versenden.
Termintyp
Mit der Methode SET_TYPE kannst du den Termintypen setzen. In meinem Beispiel nutze ich den Wert “MEETING”. Die möglichen Termintypen stehen in Tabelle SCAPPTTYPE. Ich schätze, dass dieser Termintyp in erster Linie für die Speicherung im SAP-Office notwendig ist.
In meinem System habe ich den Termintyp ZREMINDER angelegt. Dieser wird in dem Termin entsprechend angezeigt:
Das Icon, das man auswählen kann (in meinem Fall die Alarm-Glocke), wird nur im SAP-Office angezeigt:
Termin senden
Die Methode CREATE_SEND_REQUEST liefert eine Klasse vom Typ CL_BCS zurück. Das bedeutet, das Versenden sowie weitere Parameter können wie gewohnt verwendet werden.
Erinnerung oder Meeting?
Das Programm ermöglicht es, bis zu zwei Teilnehmende zu definieren. Ein Teilnehmer muss vorhanden sein; denn einer soll schließlich den Termin auf jeden Fall bekommen. Die Person, an die die Erinnerung gesendet wird, hat dann die Möglichkeit, den Termin (unter Vorbehalt) anzunehmen oder abzusagen. Sind mehrere Personen im Termin definiert, dann muss ausgewählt werden, ob die Antwort jeweils gesendet, nicht gesendet oder vor dem Senden bearbeitet werden soll.
HTML-Body
Der Informationstext im Mailbody ist reiner Text. Mit Methode SET_TEXT kannst du diesen einfachen Text hinzufügen. Leider habe ich es nicht geschafft, einen Termin mit HTML-Text auszustatten. Wenn du mit SET_DOCUMENT der Klasse CL_BCS einen HTML-Text hinzufügst, dann wird das erzeugte ICS-Dokument ersetzt und du versendest am Ende eine normale E-Mail. Eventuell ist es möglich, wenn man eine Multipart-E-Mail erzeugt, bei der das ICS-Dokument eben ein Part der Nachricht ist.
Wenn du einen Tipp hast, wie das funktioniert, schreib mir gerne eine E-Mail.
Code
Der folgende Code zeigt exemplarisch die notwendigen Methodenaufrufe. Der Code ist ebenfalls im Github-Projek Appointment zu finden.
REPORT zt9tr_send_appointment.
INCLUDE <cntn01>.
TYPE-POOLS: sccon.
PARAMETERS p_orga TYPE xubname DEFAULT sy-uname OBLIGATORY.
PARAMETERS p_mail TYPE ad_smtpadr DEFAULT 'ewf@inwerken.de' OBLIGATORY.
PARAMETERS p_mail2 TYPE ad_smtpadr DEFAULT 'lmr@inwerken.de'.
PARAMETERS p_title TYPE sc_txtshor DEFAULT 'Geschäftsessen'.
PARAMETERS p_loc TYPE sc_room DEFAULT 'La Civetta'.
PARAMETERS p_date TYPE sy-datum DEFAULT sy-datum.
PARAMETERS p_from TYPE sc_timefro DEFAULT '120000'.
PARAMETERS p_to TYPE sc_timeto DEFAULT '130000'.
SELECTION-SCREEN BEGIN OF BLOCK body WITH FRAME TITLE TEXT-bdy.
PARAMETERS p_line1 TYPE so_text255 DEFAULT 'Wichtiges Essen'.
PARAMETERS p_line2 TYPE so_text255 DEFAULT 'Schickes Hemd anziehen'.
PARAMETERS p_line3 TYPE so_text255 DEFAULT 'Blumen mitbringen'.
SELECTION-SCREEN END OF BLOCK body.
CLASS main DEFINITION.
PUBLIC SECTION.
CONSTANTS c_status_confirmation_never TYPE bcs_rqst VALUE 'N'. "Never
CONSTANTS c_status_confirmation_on_error TYPE bcs_rqst VALUE 'E'. "Only if errors occur
CONSTANTS c_status_confirmation_if_sent TYPE bcs_rqst VALUE 'D'. "If sent
CONSTANTS c_status_confirmation_if_read TYPE bcs_rqst VALUE 'R'. "If read
CONSTANTS c_status_confirmation_always TYPE bcs_rqst VALUE 'A'. "Always
METHODS start.
PRIVATE SECTION.
DATA appointment TYPE REF TO cl_appointment.
DATA participant TYPE scspart.
METHODS add_participant IMPORTING i_mail_address TYPE clike.
ENDCLASS.
CLASS main IMPLEMENTATION.
METHOD start.
appointment = NEW #( ).
"MEETING, VACATION, CUSTOMER, ABSENT
appointment->set_type( 'ZREMINDER' ).
appointment->set_organizer( organizer = p_orga ).
add_participant( p_mail ).
IF p_mail2 IS NOT INITIAL.
add_participant( p_mail2 ).
ENDIF.
" add detail body text
appointment->set_text( VALUE #(
( line = p_line1 ) ( line = cl_abap_char_utilities=>cr_lf )
( line = p_line2 ) ( line = cl_abap_char_utilities=>cr_lf )
( line = p_line3 )
) ).
" set title and location
appointment->set_title( p_title ).
appointment->set_location( p_loc ).
" set date and time using default settings
" date_to will be the same as date_from
" time zone will be the one from the user master records settings
appointment->set_date( date_from = p_date
time_from = p_from
time_to = p_to ).
" set it to a high priority meeting
appointment->set_priority( sccon_prio_very_high ).
" this meeting is not yet confirmed
appointment->set_status( sccon_status_planned ).
" Important to set this one to space. Otherwise SAP will send a not user-friendly e-mail
appointment->save( send_invitation = space ).
TRY.
" Now that we have the appointment, we can send a good one for outlook by switching to BCS
DATA(send_request) = appointment->create_send_request( ).
DATA(recipient) = cl_cam_address_bcs=>create_internet_address( p_mail ).
send_request->add_recipient(
i_recipient = recipient
i_copy = abap_true ).
IF p_mail2 IS NOT INITIAL.
DATA(recipient2) = cl_cam_address_bcs=>create_internet_address( p_mail2 ).
send_request->add_recipient(
i_recipient = recipient2
i_copy = abap_true ).
ENDIF.
CATCH cx_address_bcs INTO DATA(error_address).
MESSAGE error_address TYPE 'I'.
RETURN.
CATCH cx_send_req_bcs INTO DATA(error_add_recipient).
MESSAGE error_add_recipient TYPE 'I'.
RETURN.
CATCH cx_bcs INTO DATA(error_create_send_request).
MESSAGE error_create_send_request TYPE 'I'.
RETURN.
ENDTRY.
TRY.
" don't request read/delivery receipts
send_request->set_status_attributes(
i_requested_status = c_status_confirmation_never
i_status_mail = c_status_confirmation_never ).
"sent mail immediately
send_request->set_send_immediately( abap_true ).
" Send it to the world
DATA(appointment_sent) = send_request->send( i_with_error_screen = abap_true ).
IF appointment_sent = abap_true.
COMMIT WORK AND WAIT.
MESSAGE 'Einladung verschickt' TYPE 'S'.
ELSE.
MESSAGE 'Fehler beim Senden der Einladung' TYPE 'I'.
ENDIF.
CATCH cx_send_req_bcs INTO DATA(error_send).
MESSAGE error_send TYPE 'I'.
ENDTRY.
ENDMETHOD.
METHOD add_participant.
DATA address TYPE swc_object.
DATA address_container TYPE STANDARD TABLE OF swcont.
"set an internet address as a second partcipant of that appointment
swc_create_object address 'ADDRESS' space.
swc_set_element address_container 'AddressString' i_mail_address.
swc_set_element address_container 'TypeId' 'U'.
swc_set_element address_container 'NoAdradmi' 'X'.
swc_set_element address_container 'NoIntern' 'X'.
swc_call_method address 'Create' address_container.
CHECK sy-subrc = 0.
"get key and type of object
swc_get_object_key address participant-objkey.
CHECK sy-subrc = 0.
swc_get_object_type address participant-objtype.
CHECK sy-subrc = 0.
participant-send_mail = abap_true.
appointment->add_participant( participant = participant ).
ENDMETHOD.
ENDCLASS.
START-OF-SELECTION.
NEW main( )->start( ).
Den Tricktresor gibt es nun seit über zwanzig Jahren. Am 17.4.2003 habe ich meinen ersten Beitrag verfasst: Drucktaste neben AUSFÜHREN-Button. Anhand des Screenshots kann man das Alter erahnen…
Seit ich angefangen habe, den Tricktresor zu betreiben, habe ich darüber nachgedacht, wie ich mit der Arbeit und der Mühe, die ich investiere, auch Geld verdienen kann. Die erste Idee war, Werbung zu schalten. Da ich selber jedoch Webseiten, die in erster Linie aus Werbung bestehen, hasse wie die Pest, habe ich nur ausgewählte Werbebanner aus einem Affiliate-Programm aufgenommen. Dadurch ist die Reichweite jedoch stark eingeschränkt und meine Interessen entsprechen nicht unbedingt deinen Interessen. Zudem wurde mir auch klar, dass Werbung mit Bannern nicht gut funktioniert. Der Grund ist ziemlich einfach: Wenn du durch die Suche in einer Suchmaschine auf meiner Seite landest, dann hast wahrscheinlich gerade ein Problem, das du Lösen musst. Dementsprechend schnell verlässt du meine Seite wieder. Und ziemlich wahrscheinlich wirst du in dieser Situation nicht die Muße haben, auf irgendwelche Banner zu achten, geschweige denn, eines anzuklicken. Zudem funktionieren Banner in der Regel nur dann gut, wenn man sich in einem Blogeintrag zu etwas informiert und dann Werbung angezeigt wird, die zum Thema passt. Naja, was soll ich sagen: Willst du ein SAP-System kaufen?
Ich habe es also relativ schnell wieder aufgegeben, Werbeanzeigen anzuzeigen. Im Laufe der Zeit wurde mir klar, dass ich froh sein kann, wenn ich die Betriebskosten hereinbekomme. Aber auch da war mir dann der Aufwand für buchstäblich EUR 12,40 pro Quartal zu groß und ich sah den Tricktresor in erster Linie als Werbung für mich und meine Fähigkeiten sowie für meine eigene Wissensspeicherung. Es passiert durchaus mal, dass ich nach einer Lösung suche, die mich dann zu meinem Artikel im Tricktresor führt. Sehr häufig weiß ich, dass ich etwas im Tricktresor finde und suche gezielt danach.
Ich komme ohne weitere Umschweife zum Hauptthema dieses Artikels: den
ABAP Stellenanzeigen
Das Schlagwort ABAP, das ich – wahrscheinlich genau wie du auch – in den verschiedenen Internet-Profilen stehen habe, reicht für viele RecruiterInnen aus, mir ein tolles Jobangebot anzubieten, das mich auf den nächsten Karrierelevel heben wird. Ich finde das sehr nervig, da ich nicht auf Stellensuche bin. Glücklicherweise schreiben die meisten: “Wenn ich Ihr Interesse geweckt habe, melden Sie sich”. Deswegen muss ich nicht mal ein schlechtes Gewissen haben, wenn ich mich nicht melde.
Und natürlich bin ich mir bewusst, dass es viele Firmen gibt, die mehr oder weniger händeringend SAP-Entwicklerinnen und -Entwickler suchen. Deswegen kam einem Kollegen, der seit Jahren das ABAP-Forum betreut, und mir die Idee, dass wir unsere Seiten, die sich wirklich fast ausschließlich mit ABAP-Entwicklung beschäftigen, für maßgeschneiderte Stellenanzeigen nutzen.
Vielleicht ist dir bereits aufgefallen, dass Inwerken seit einiger Zeit ABAP-EntwicklerInnen sucht?
Die Anzeigen tauchen seit ein paar Wochen im Tricktresor und dem ABAP-Forum zwischen den Beiträgen und im Seitenrand auf.
Wir erhoffen uns damit, dass wir dich als Zielgruppe unaufgeregt und dezent erreichen können, wenn du als ABAP-EntwicklerIn gerade auf Jobsuche bist. Beziehungsweise hoffen wir, dass Firmen auf unseren Plattformen mit dem richtigen Publikum mehr Erfolg haben dich zu finden, als auf anderen Jobbörsen.
Wenn du selber eine – oder auch mehrere – Anzeigen schalten möchtest, weil du Mitarbeiter und Mitarbeiterinnen suchst, die dein Team in der ABAP-Entwicklung unterstützen, dann lade ich dich herzlich ein, unser spezialisiertes Angebot zu besuchen (Preisliste).
Wenn du wissen möchtest, was ich die letzten zwei Jahrzehnte noch unternommen habe, um mit dem Tricktresor ein paar Mark nebenbei zu verdienen, dann lies gerne weiter.
ACHTUNG: Dieser Artikel enthält ungewöhnlich viele Affiliate-Links!
T-Shirts
Eine Zeitlang fand ich es cool, ABAP-spezifische Designs und Sprüche zu entwerfen, die du dir bei Spreadshirt auf alle möglichen T-Shirts, Hoodies oder Caps hättest drucken lassen können. Diese “Sparte” habe ich dann jedoch irgendwann eingestellt. Die Gründe findest du im Artikel Warum ich meinen Spreadshirt-Shop gelöscht habe
Eines der meistverkauften Motive war der ABAP-Magician. Wobei “meistverkauft” eventuell irreführend ist; wir sprechen hier über maximal 10 T-Shirts… Ich schätze mal, dass man so viele ABAP’s NOT DEAD T-Shirts bereits auf jeder kleineren SAP-Veranstaltung sieht. Du kannst das T-Shirt übrigens bei Uwe im Spreadshirt-Shop kaufen: SE38.
Zudem scheine ich von meinen eigenen Designs deutlich mehr überzeugt gewesen zu sein, als du. Vielleicht hast du die Entwürfe und meinen Shop aber auch nicht gefunden, weil ich zu wenig Werbung dafür gemacht habe? Ich werde es nicht mehr erfahren.
Dass man mit einem Fachbuch nicht reich wird, war uns ziemlich klar. Trotzdem waren meine Kollegen Maic, Dennis, Udo und Sascha und ich fasziniert von der Idee, ein Buch zu schreiben, in dem wir unsere Erfahrungen zur ABAP-Programmierung kund tun konnten.
Ursprünglich sollte das Buch ABAP – Jetzt helfe ich mir selbst heißen. Angelehnt an die legendäre Buchreihe mit der wahrscheinlich viele Tausend Menschen ihre Autos repariert haben.
Herausgekommen ist letztlich ein Kochbuch, über dessen Titel ich nur bedingt glücklich war. Einerseits ist die Assoziation Programmieren – Kochen ziemlich abgedroschen und zweitens gab es bereits das ABAP Cookbook, das es inzwischen jedoch nur noch im Antiquariat gibt. Das ABAP Cookbook war bereits in englischer Sprache erschienen und hatte mit unserem ABAP Kochbuch wirklich gar nichts zu tun. Der Rheinwerk-Verlag hat sich letztendlich durchgesetzt und mit deren professionellen und geduldigen Unterstützung ist auch ein tolles Buch dabei heraus gekommen.
Es ist ein schönes Gefühl, als Programmierer mal etwas handfestes produziert zu haben. Auch wenn SAPGUI-Programmierung nicht mehr up-to-date sind und die neuen ABAP-Features gänzlich fehlen, weil es sie damals einfach noch nicht gab, enthält das Buch meiner Meinung nach viele tolle Hinweise und Anleitungen, die immer noch relevant sind.
Aber, wie gesagt, zur Finanzierung des Tricktresors taugte diese Aktion wenig bis gar nicht.
Die Firma ERSAsoft Gmbh aus Maisach ist einer der wenigen Partner mit denen die Zusammenarbeit aus verschiedenen Gründen wirklich viel Spaß macht. Als Programmierer sollte ich ein Tool, das den Datenimport aus Excel in das SAP-System für AnwenderInnen sehr vereinfacht eigentlich nicht mögen, denn wenn ein Kunde das im Einsatz hat, darf ich mit Sicherheit keine Programme für den Import schreiben. Aber gerade deswegen mag ich das Produkt SimDia², denn gerade das Schreiben von Importprogrammen gehört für mich eher zu den nervigeren Arbeiten, denen man als Programmierer begegnet:
Es fehlen häufig genaue Definitionen für den Import oder die Verbuchung.
Daten sind nicht korrekt.
Fehler müssen penibel abgefangen und kundenfreundlich präsentiert werden.
Entwicklertests sind umständlich weil viele Objekte mit dem Import genau definiert werden. Hat man also einen Fehler, müssen eventuell die Daten manipuliert werden, damit man erneut buchen kann.
Durch Tests der AnwenderInnen und häufige Testzyklen dauert die Entwicklung lange.
Zusätzlich gibt es auch Nachteile für die AnwenderInnen, die das Programm verwenden müssen:
Die Bedienung ist evtl. nicht intuitiv und anwenderfreundlich, da mehr Wert auf andere Aspekte gelegt wurde.
Wenn sich Daten oder Anforderungen ändern, muss die IT-Abteilung mit einer Programmänderung beauftragt werden, die in der Regel lange dauert.
Das Programm ist unflexibel und kann nur für genau den Zweck verwendet werden, für den es konzipiert wurde. Schon leichte Abweichungen führen zu einem hohen nachträglichen Aufwand.
Ich persönlich bin also froh, wenn AnwenderInnen ihre Daten selber “ins System kloppen” können. Und SimDia² ist wirklich leicht zu erlernen, hochflexibel und vielseitig anwendbar. Die Arbeitsweise und einige Anwendungsmöglichkeiten kannst du gut in den Videos sehen.
Auch im Tricktresor gibt es einige Artikel, in denen ich auf die Funktionsweise von SimDia² eingehe:
Da ich zusammen mit meinen Kollegen selbst das ABAP-Kochbuch geschrieben habe, unterstütze ich die Idee der Espresso-Tutorials. Die Fachbücher, die hauptsächlich auf den SAP-Kosmos abzielen, haben eine hohe Qualität und decken Bereiche ab, die in herkömmlichen SAP-Fachbüchern häufig zu kurz kommen. Die Bücher sind deutlich “leichter” als die Exemplare des Rheinwerk-Verlages, in dem unser Kochbuch erschienen ist. Die Bücher sind also nicht so umfangreich (maximal 120 Seiten), bieten aber gerade zu speziellen Themen einen tollen Mehrwert. Du kannst die Bücher einzeln kaufen oder eine Flatrate buchen. Mit der Flatrate hast du für den Bezahlzeitraum Zugriff auf alle Inhalte. Und das ist inzwischen eine Menge! Neben Büchern kannst du auch Videos und Kurse ansehen. Wenn du die Bibliothek von Espresso-Tutorials noch nicht kennst, solltest du unbedingt einen Blick riskieren!
Der Tricktresor läuft seit einigen Jahren problemlos und günstig bei all-inkl.com. Wenn ich mal ein Problem hatte, hat der Support zeitnah, persönlich und kompetent geholfen. Insgesamt bin ich sehr zufrieden mit diesem Webhoster. Die Weboberfläche wirkt altbacken, ist jedoch funktionabel. Es gibt viele andere Webhoster, die vielleicht für WordPress schneller sind, aber dann sind sie in der Regel teurer. Oder sie sind noch günstiger, aber dann reicht die Leistung nicht.
Die Firma Inwerken, bei der ich seit inzwischen 15 Jahren als Entwickler tätig bin, unterstützt mich in vielen Dingen. Eine Zeitlang hat die Firma die Serverkosten für den Tricktresor übernommen, wofür ich sehr dankbar bin. Der Tricktresor wäre kaum möglich, wenn ich nicht das interne SAP-System von Inwerken nutzen könnte. Auf diese Weise kann ich viele Dinge entwickeln und prüfen und zusätzlich haben meine Kolleginnen und Kollegen eine stetig wachsende Programmsammlung für verschiedenste Problemlösungen “griffbereit”.
Transportaufträge und das Transportmanagementsystem sind ein zentraler Bestandteil einer SAP-Systemlandschaft. In der Regel werden erstelle oder geänderte Objekte automatisch in einen Transportauftrag aufgenommen und gesperrt. Nach erfolgreichem Test werden die beteiligten Transportaufträge in das Produktivsystem transportiert.
Prüfungen
Eine Aufgabe oder Transportauftrag kann auf folgende zwei Dinge überprüft werden:
Konsistenz
Objektsyntax
Die Prüfungen rufst du im Menü Auftrag/ Aufgabe • Prüfen auf
Konsistenzprüfung
Die Konsistenzprüfung prüft zum Beispiel, ob die Transportschicht der den Objekten zugeordneten Pakete mit dem Transportziel des Auftrags übereinstimmt oder dass sich in einer Reparaturaufgabe auch nur reparierte Objekte befinden.
Solche Fehler können auftreten, wenn Objekte nach der Aufnahme in den Transportauftrag geändert werden.
Syntaxprüfung der Objekte
Diese Prüfung stellt sicher, dass alle Objekte syntaktisch fehlerfrei sind.
Massenprüfungen
Wenn es in einer Systemlandschaft zu umfangreichen oder elementaren Änderungen kommt, dann kann es sein, dass Transportaufträge vieler Leute betroffen sind. Ich habe keine Möglichkeit gefunden, Transportaufträge massenhaft zu prüfen. Aber zum Glück kann ich ja programmieren…
Der folgende Report übernimmt die Konsistenzprüfung aller selektieren Aufträge und deren Aufgaben. Die Meldungen werden in einer SALV-Liste ausgegeben.
Code
Den Sourcecode gibt es Dank abapGit auch auf github.
REPORT zt9r_request_consistency_check.
DATA request TYPE e070.
SELECT-OPTIONS so_nam FOR request-as4user DEFAULT sy-uname.
SELECT-OPTIONS so_dat FOR request-as4date DEFAULT '20200101' TO sy-datum.
SELECT-OPTIONS so_req FOR request-trkorr.
SELECT-OPTIONS so_fnc FOR request-trfunction DEFAULT 'K'.
SELECT-OPTIONS so_sta FOR request-trstatus DEFAULT 'D'.
CLASS app DEFINITION.
PUBLIC SECTION.
METHODS start.
PRIVATE SECTION.
TYPES: BEGIN OF _data,
trkorr TYPE trkorr,
as4user TYPE e070-as4user,
as4date TYPE e070-as4date,
as4text TYPE e07t-as4text,
strkorr TYPE strkorr,
message TYPE text100,
pgmid TYPE e071-pgmid,
object TYPE e071-object,
obj_name TYPE e071-obj_name,
END OF _data,
_data_tab TYPE STANDARD TABLE OF _data WITH DEFAULT KEY.
DATA result_table TYPE _data_tab.
DATA requests TYPE SORTED TABLE OF e070 WITH UNIQUE KEY trkorr.
DATA tasks TYPE SORTED TABLE OF e070 WITH UNIQUE KEY trkorr.
DATA messages TYPE ctsgerrmsgs.
METHODS select.
METHODS prepare.
METHODS display.
ENDCLASS.
CLASS app IMPLEMENTATION.
METHOD start.
select( ).
prepare( ).
display( ).
ENDMETHOD.
METHOD select.
SELECT * FROM e070 INTO TABLE @requests
WHERE trkorr IN @so_req
AND trfunction IN @so_fnc
AND trstatus IN @so_sta
AND as4user IN @so_nam
AND as4date IN @so_dat
ORDER BY PRIMARY KEY.
IF sy-subrc = 0 AND lines( requests ) > 0.
SELECT * FROM e070 INTO TABLE @tasks
FOR ALL ENTRIES IN @requests
WHERE strkorr = @requests-trkorr
ORDER BY PRIMARY KEY.
ENDIF.
ENDMETHOD.
METHOD prepare.
DATA full_request TYPE trwbo_request.
LOOP AT requests INTO DATA(request).
LOOP AT tasks INTO DATA(task) WHERE strkorr = request-trkorr.
CLEAR full_request.
full_request-h = CORRESPONDING #( task ).
CALL FUNCTION 'TR_READ_REQUEST'
EXPORTING
iv_read_e070 = 'X'
iv_read_e07t = 'X'
iv_read_e070c = ' '
iv_read_e070m = ' '
iv_read_objs_keys = 'X'
iv_read_attributes = ' '
iv_trkorr = task-trkorr
CHANGING
cs_request = full_request
EXCEPTIONS
error_occured = 1
no_authorization = 2
OTHERS = 3.
IF sy-subrc <> 0.
APPEND VALUE #(
trkorr = request-trkorr
as4user = request-as4user
as4date = request-as4date
as4text = full_request-h-as4text
strkorr = task-trkorr
message = 'Error TR_READ_REQUEST' ##no_Text
) TO result_table.
CONTINUE.
ENDIF.
CLEAR messages.
CALL FUNCTION 'TR_CHECK_REQUEST'
EXPORTING
is_request = full_request
iv_check_lockability = space
iv_collect_mode = 'X'
IMPORTING
et_messages = messages.
LOOP AT messages INTO DATA(message).
MESSAGE ID message-msgid
TYPE message-msgty
NUMBER message-msgno
WITH message-msgv1 message-msgv2 message-msgv3 message-msgv4
INTO DATA(message_text).
DATA(err) = VALUE #( full_request-objects[ message-pos ] OPTIONAL ).
APPEND VALUE #(
trkorr = request-trkorr
as4user = request-as4user
as4date = request-as4date
as4text = full_request-h-as4text
strkorr = task-trkorr
pgmid = err-pgmid
object = err-object
obj_name = err-obj_name
message = message_text
) TO result_table.
ENDLOOP.
ENDLOOP.
ENDLOOP.
ENDMETHOD.
METHOD display.
IF result_table IS INITIAL.
MESSAGE s477(tk) WITH 'selected'.
ELSE.
TRY.
cl_salv_table=>factory(
IMPORTING
r_salv_table = DATA(salv)
CHANGING
t_table = result_table ).
salv->get_functions( )->set_all( ).
salv->get_columns( )->set_optimize( ).
salv->display( ).
CATCH cx_salv_msg INTO DATA(salv_error).
MESSAGE salv_error TYPE 'I'.
ENDTRY.
ENDIF.
ENDMETHOD.
ENDCLASS.
START-OF-SELECTION.
NEW app( )->start( ).
Das folgende Programm liefert eine Übersicht über die Prüfungen, die in einer Code-Inspector-Variante aktiv sind.
Code
PARAMETERS name TYPE scichkv_hd-checkvname DEFAULT 'DEFAULT'.
PARAMETERS owner TYPE scichkv_hd-ciuser DEFAULT space.
START-OF-SELECTION.
SELECT SINGLE * FROM scichkv_hd
WHERE checkvname = @name ##WARN_OK
AND ciuser = @owner "#EC CI_NOORDER
INTO @DATA(variant_header).
DATA(main) = cl_ci_checkvariant=>get_ref(
p_user = owner
p_name = name ).
CHECK main IS BOUND.
main->get_info(
EXCEPTIONS
could_not_read_variant = 1
OTHERS = 2 ).
IF sy-subrc = 0.
cl_demo_output=>display_data(
VALUE string_table(
FOR variant IN main->variant (
CONV #( variant-testname ) ) ) ).
ENDIF.
Ich denke, nach beinahe 30 Jahren Erfahrung mit SAP-Systemen kann ich mich als alten Fuchs bezeichnen und es gibt wenig im ABAP-Umfeld, das ich noch nicht kenne (RAP und CAP und UI5 und so’n neumodischen Kram mal außen vor gelassen… ). Diesen Monat sind mir jedoch bereits zwei Dinge begegnet, dir mir wirklich absolut neu sind. Das erste ist die Möglichkeit, einen Doppelklick in der Code-Vervollständigung von TYPES machen zu können. Das zweite ist ein Programm, über das ich heute gestolpert bin und das mir in Zukunft viel Sucharbeit ersparen wird:
RDOCFINDER – Full Text Search for Short and Long Texts
Bilder sagen mehr, als tausend Worte. Also bitteschön:
Selektionsbild
Ergebnisliste
Unterstützte Objekte
Suche nach Terminologie-Begriffen in folgenden Objekten: - DDIC-Objekte "Datenelemente (F1) DE + "Datenelementzusätze (F1) DZ + "Schlüsselwörter der Datenelemente DS + "Kurztext der Datenelemente DK + "Reportüberschrift Datenelement DR + "Domänen (F1) DO + "Kurztext der Domänen DK + "Domänen-Festwerte (F1) DV + "Tabellen/Strukturen (F1) TB + "Kurztext der Tabellen TK + "Feld/Komponente der Tabellen TF + "Indizes der Tabellen TX + "Kurztext der Tabellentypen TT + "Kurztext der Views TV + "Kurztext der Suchhilfen SH + "Kurztext der Sperrobjekte EQ + - Benutzerstamm "Benutzerstamm Objekte (F1) UO + "Kurztext der Objekte OK + "Benutzerstamm Profile (F1) UP + "Kurztext der Profile PK + - Nachrichten "Nachrichten (F1) NA + "Kurztext der Nachrichten NK + "Kurztext der Nachrichtenklassen NN + "Syslog-Meldungen (F1) SL + "Kurztext der Syslog-Meldung SK + - Reports "Reports (F1) RE + "numerierte Texte usw. R... + "Dynpro-Texte DY + "Dynpro-Titel DX + - Klassenbibliotheken CL/IF "Klassen - Kurztext KT + "Klassen - Langtext CL + "Klassen Attribute - Kurztext KT + "Klassen Attribute - Langtext CA + "Klassen Methoden - Kurztext KT + "Klassen Methoden - Langtext CO + "Klassen Ereignisse - Kurztext KT + "Klassen Ereignisse - Langtext CE + "Klassen Typen - Kurztext KT + "Klassen Typen - Langtext CT + "Interface - Kurztext KT + "Interface - Langtext IF + "Interface Attribute - Kurztext KT + "Interface Attribute - Langtext IA + "Interface Methoden - Kurztext KT + "Interface Methoden - Langtext IO + "Interface Ereignisse - Langtext IE + "Interface Typen - Kurztext KT + "Meth. Parameter/Ausnahmen- Kurztext SC + - Oberfläche "Oberfläche (F1) CF "Titel TI + "Funktionstastentext FM + "Ikonentext FI + "Quickinfo Ikone FQ + "Menüleiste MA + "Drucktastenbelegung MB + "GUI-Status MC + "Funktionstext MM + "Funktionstastenbelegung MP + - Modellierungsobjekte IM "Entitätstypen - Kurztext ET + "Entitätstypen - Langtext UE + "Entitätstypen - Aliasname EA + "Datenmodelle - Kurztext DM + "Datenmodelle - Langtext UD + "Beziehung - Kurztext ER + "Beziehung - Langtext UH/UR/UK + "Spezialisierungsart - Kurztext ES + "Spezialisierungsart - Langtext US + "Entitätstyp Kommentare - Langtext UC + "Entitätstyp Beispiel - Langtext UB + - IMG "IMG-Struktur I + "IMG-Aktivität I0 + "IMG-Pflegeobjekt I1 + "IMG-Attribute I2 + "IMG-Doku IG + - Test-Objekte "CATT-Testfall - Kurztext GK + "CATT-Testfall - Langtext GT + "eCATT-Testskript - Titel E0 + "eCATT-Testskript - Par./Kommandosch.E5 + "eCATT-Testskript - Langtext E + "eCATT-Testkonfig. - Titel E1 + "eCATT-Testkonfig. - Variante E6 + "eCATT-Testkonfig. - Langtext E + "eCATT-Testdaten - Titel E2 + "eCATT-Testdaten - Param./Variante E7 + "eCATT-Testdaten - Langtext E + "eCATT-Systemdaten - Titel E3 + "eCATT-Systemdaten - Zielsystem E8 + "eCATT-Systemdaten - Langtext E + "eCATT-Validierungsobjekt - Titel E4 + "eCATT-Validierungsobjekt - Langtext E + - Erweiterungen "SAP-Erweiterung (Modifik.) - Kurzt. MK + "SAP-Erweiterung (Modifik.) - Langt. MO + "Klassisches BAdI BA + "Implementierung Klassisches BAdI BI + "Zusammengesetzter Erweiterungsspot BJ + "Impl. Zusammenges. Erweiterungsspot BK + "Erweiterungsspot BS + "Erweiterungsspot - BAdI B0 + "Erweiterungsspot - BAdI TechDoku B1 + "Erweiterungsspot - BAdI - Filter B2 + "Erweiterungsspot - BAdI - Menü-Erw. B3 + "Erweiterungsspot - BAdI - Scr.-Erw. B4 + "Impl. Erweiterungsspot BT + "Impl. Erweiterungsspot - BAdI B5 + "Impl. Erweiterungsspot - BAdI TechD.B6 + - allg. Text (TX) TX + - Migrationstext (nur Langtext) MG + - Release-Infos (IN RELN) (nur Langtext) IN + - SAPscript-Formulare (Langtext + Kurztext) SF +- - SAPscript-Stile (Kurztext) SS - - Funktionsbaustein-Doku (FU) FU + - Funktionsbaustein-Ausnahme-Doku (FX) FX + - Funktionsbaustein-Parameter/Ausnahme-Kurztext FP + - Dialogbaustein-Doku "Kurztext der Dialogbausteine DB + "Langtext Dialogbaustein DI + - Syntax-Doku (SD) "ABAP S0 + "Umgebung ABAP S1 + "CONT S2 + "Dynpro S3 + "Editor S4 + "RSYN S5 + - Texte im Dialog (DT) DT + - Kurzdump KD + - SET/GET-Parameter SG + - Bereichsmenüs AM + - verschiedene Kurztexte "Kurztext der Transaktionen TA + "Kurztext der log. Datenbank LD + - Gateway texts "Data Object Hierarchy - long text GH "Data Object Hierarchy - short text GJ "Data Object - long text GD "Data Object - short text GE "Operation - long text GO "Operation - short text GP "Consumption Model - long text GS "Consumption Model - short text GU