ABAP Cloud - Migration (Frontend)
Wie migriert man einen klassischen Report Richtung ABAP Cloud? In diesem Artikel schauen wir uns ein Beispiel für eine Migration an.
Inhaltsverzeichnis
In einem letzten Beispiel hatten wir uns die Migration eines Reports als Application Job angeschaut. In vielen Fällen werden aber auch ALV Reports genutzt, mit denen der User arbeitet. In diesem Artikel schauen wir uns einmal die Migration eines solchen ALV Reports in Richtung Fiori Elements an.
Einleitung
Viele Jahre war SAP GUI der Standard zur Arbeit mit einem SAP System. Mit ABAP Cloud wird die SAP GUI nicht mehr unterstützt und immer mehr Anwendungen werden per Fiori umgesetzt. Als ABAP Entwickler solltest du also nun schauen, wie du den klassischen Report als Fiori Anwendung umsetzen kannst und wie sich die Elemente verhalten. Nicht alles wird so funktionieren wie in der GUI, deshalb sollten die Konzepte an Fiori angepasst werden.
Die Umsetzung wurde auf einem S/4 HANA 2022 System (Cloud Appliance Library) gemacht. Die gezeigten Daten und Konfigurationen findest du dort, wenn du unser Beispiel nachstellen möchtest.
Vorbereitung
In diesem Abschnitt bekommst du alle Informationen zum eigentlichen Report, wie dieser aufgebaut ist und funktioniert. In dem Report möchten wir über ein Selektionsbild FI-Belege abgrenzen und die Köpfe, zusammen mit den Positionen und Texten anzeigen. Der User hat dann die Möglichkeit, zu den einzelnen Positionen auch Kommentare zu hinterlegen, was der Mehrwert des Reports ist.
Tabelle
Die Kommentare zu den Belegpositionen werden in einer Zusatztabelle abgelegt, die Tabelle enthält den vollständigen Schlüssel, den Kommentar und noch einige Steuerungsinformationen zum User.
@EndUserText.label : 'Comments for FI documents'
@AbapCatalog.enhancement.category : #NOT_EXTENSIBLE
@AbapCatalog.tableCategory : #TRANSPARENT
@AbapCatalog.deliveryClass : #A
@AbapCatalog.dataMaintenance : #RESTRICTED
define table zfi_comment {
key client : abap.clnt not null;
key bukrs : bukrs not null;
key belnr : belnr_d not null;
key gjahr : gjahr not null;
key buzei : buzei not null;
commt : abap.string(0);
create_user : xubname;
create_time : abap.utclong;
}
Report
Hier findest du die Reportressourcen. Im Report gibt es ein Selektionsbild, ein Dynpro welches wir als Popup verwenden werden. Die Logik haben wir zur Vereinfachung in einer lokalen Klasse im Report implementiert.
REPORT zfi_comment_bookings.
TABLES bkpf.
" -----------------------------------------------------------------------
" --- Selection Screen
" -----------------------------------------------------------------------
SELECT-OPTIONS:
s_bukrs FOR bkpf-bukrs,
s_belnr FOR bkpf-belnr,
s_gjahr FOR bkpf-gjahr.
PARAMETERS p_numbr TYPE i DEFAULT 50.
" -----------------------------------------------------------------------
" --- Selection Subscreen
" -----------------------------------------------------------------------
SELECTION-SCREEN BEGIN OF SCREEN 0100.
PARAMETERS p_commt TYPE string LOWER CASE.
SELECTION-SCREEN END OF SCREEN 0100.
" -----------------------------------------------------------------------
" --- Report class
" -----------------------------------------------------------------------
CLASS lcl_report DEFINITION FINAL.
PUBLIC SECTION.
TYPES: BEGIN OF ts_selection,
s_bukrs TYPE RANGE OF bkpf-bukrs,
s_belnr TYPE RANGE OF bkpf-belnr,
s_gjahr TYPE RANGE OF bkpf-gjahr,
p_numbr TYPE i,
END OF ts_selection.
TYPES: BEGIN OF ts_data,
bukrs TYPE bkpf-bukrs,
butxt TYPE t001-butxt,
belnr TYPE bkpf-belnr,
gjahr TYPE bkpf-gjahr,
buzei TYPE bseg-buzei,
bldat TYPE bkpf-bldat,
bktxt TYPE bkpf-bktxt,
bschl TYPE bseg-bschl,
sgtxt TYPE bseg-sgtxt,
kostl TYPE bseg-kostl,
commt TYPE string,
END OF ts_data.
TYPES tt_data TYPE STANDARD TABLE OF ts_data WITH EMPTY KEY.
METHODS main
IMPORTING is_selection TYPE ts_selection.
PRIVATE SECTION.
DATA ms_selection TYPE ts_selection.
DATA mt_data TYPE tt_data.
DATA mo_salv TYPE REF TO cl_salv_table.
METHODS select_data.
METHODS show_alv.
METHODS on_double_click
FOR EVENT double_click OF cl_salv_events_table
IMPORTING !row
!column.
METHODS save_comment
IMPORTING is_data TYPE ts_data
id_comment TYPE string.
ENDCLASS.
CLASS lcl_report IMPLEMENTATION.
METHOD main.
ms_selection = is_selection.
select_data( ).
show_alv( ).
ENDMETHOD.
METHOD select_data.
SELECT
FROM bkpf AS h
INNER JOIN
bseg AS p ON p~bukrs = h~bukrs AND p~belnr = h~belnr AND p~gjahr = h~gjahr
INNER JOIN
t001 AS c ON c~bukrs = h~bukrs
LEFT OUTER JOIN
zfi_comment AS z ON z~bukrs = h~bukrs AND z~belnr = h~belnr AND z~gjahr = h~gjahr AND z~buzei = p~buzei
FIELDS h~bukrs,
c~butxt,
h~belnr,
h~gjahr,
p~buzei,
h~bldat,
h~bktxt,
p~bschl,
p~sgtxt,
p~kostl,
z~commt
WHERE h~bukrs IN @ms_selection-s_bukrs
AND h~belnr IN @ms_selection-s_belnr
AND h~gjahr IN @ms_selection-s_gjahr
INTO CORRESPONDING FIELDS OF TABLE @mt_data
UP TO @ms_selection-p_numbr ROWS.
ENDMETHOD.
METHOD show_alv.
TRY.
cl_salv_table=>factory( IMPORTING r_salv_table = mo_salv
CHANGING t_table = mt_data ).
DATA(lo_functions) = mo_salv->get_functions( ).
lo_functions->set_all( ).
DATA(lo_display) = mo_salv->get_display_settings( ).
lo_display->set_striped_pattern( abap_true ).
DATA(lo_events) = mo_salv->get_event( ).
SET HANDLER on_double_click FOR lo_events.
mo_salv->display( ).
CATCH cx_salv_msg.
ENDTRY.
ENDMETHOD.
METHOD on_double_click.
TRY.
DATA(lr_data) = REF #( mt_data[ row ] ).
CATCH cx_sy_itab_line_not_found.
RETURN.
ENDTRY.
CALL SELECTION-SCREEN 0100 STARTING AT 10 10.
IF p_commt IS INITIAL.
RETURN.
ENDIF.
lr_data->commt = p_commt.
save_comment( is_data = lr_data->*
id_comment = p_commt ).
mo_salv->refresh( ).
ENDMETHOD.
METHOD save_comment.
DATA(ls_comment) = CORRESPONDING zfi_comment( is_data ).
ls_comment-commt = id_comment.
ls_comment-create_user = sy-uname.
ls_comment-create_time = utclong_current( ).
INSERT zfi_comment FROM @ls_comment.
IF sy-subrc <> 0.
UPDATE zfi_comment FROM @ls_comment.
ENDIF.
COMMIT WORK.
ENDMETHOD.
ENDCLASS.
" -----------------------------------------------------------------------
" --- Report logic
" -----------------------------------------------------------------------
INITIALIZATION.
DATA(go_app) = NEW lcl_report( ).
START-OF-SELECTION.
go_app->main( VALUE #( s_bukrs = s_bukrs[]
s_belnr = s_belnr[]
s_gjahr = s_gjahr[]
p_numbr = p_numbr ) ).
Verwendung
Wenn wir den Report starten, erhalten wir das Selektionsbild, hier können wir die Belege einschränken, die wir gleich sehen und bearbeiten wollen.
Nach Ausführung der Selektion erhalten wir das Ergebnis als ALV angezeigt und können die verschiedenen Daten prüfen, die angezeigt werden.
Wenn wir auf einen Eintrag Doppelklicken, dann öffnet sich ein entsprechendes Popup und wir können einen Kommentar zu dieser Zeile erzeugen. Der Kommentar wird direkt in der Datenbank gespeichert und in einer Spalte angezeigt.
Umstellung
Beginnen wir mit der Erstellung unserer Fiori Anwendung, dabei werden wir alle Komponenten in die Software Komponente umziehen, die wir auch in ABAP Cloud verwenden können und benötigen.
Schritt 1 - Software Komponente
Anlage einer neuen Software Komponente in der Sprachversion ABAP Cloud. Im Anschluss erstellen wir ein Strukturpaket mit der Software Komponente, um unsere Anwendung anlegen zu können.
Unter dem Strukturpaket legen wir das "Development"-Paket ZFI_T1_COMMENT_APP an, in dem wir nun unsere Anwendung entwickeln.
Schritt 2 - Kundeneigene Tabelle
Bevor wir das Datenmodell migrieren, wollen wir unsere bestehende Tabelle ZFI_COMMENT in unsere neue Software Komponente umziehen. Dazu müssen wir zuerst einmal die Sprachversion auf "ABAP for Cloud Development" umstellen.
Beim Aktivieren werden wir die Fehlermeldung erhalten, dass das Datenelement XUBNAME nicht freigegeben ist. Alle anderen Datenelemente aus dem Standard sind bereits freigegeben.
Die Domäne XUBNAME ist allerdings freigegeben und nach etwas Recherche über das Cloudification Repository oder die freigegebenen Datenelemente, finden wir USNAM. Wir tauschen das Datenelement aus und aktivieren die Tabelle. Nun können wir die aktivierte Tabelle in unsere Software Komponente verschieben.
Schritt 3 - Datenmodell
Als erstes würden wir mit dem Aufbau des Datenmodells beginnen, da dies zentraler Bestandteil der Auswertung war. In diesem Fall können wir allerdings nicht auf den Tabellen aufsetzen, sondern benötigen die freigegebenen Core Data Services zu den Tabellen. Hier hast du die Möglichkeit über den Cloudification Repository Viewer (SAP oder SwH) die freigegebenen Objekte zu suchen.
- BKPF - I_JournalEntry
- BSEG - I_OperationalAcctgDocItem
- T001 - I_CompanyCode
Anstatt einem Zugriff auf ABAP Ebene, würden wir nun einen Core Data Service modellieren, den wir dann als Grundlage für unsere RAP Anwendung benötigen. Im ersten Schritt legen wir einen CDS View für die Kommentartabelle an und normalisieren die Feldnamen für das Datenmodell.
@EndUserText.label: 'Comments to FI documents'
define view entity ZFI_I_Comment
as select from zfi_comment
{
key bukrs as CompanyCode,
key belnr as AccountingDocument,
key gjahr as FiscalYear,
key buzei as AccountingDocumentItem,
commt as DocumentComment,
create_user as CreationUser,
create_time as CreationTime
}
Nun können wir unseren eigentlichen Datenview modellieren. Da wir mit den Positionsdaten arbeiten, würden wir auf den Positionen aufsetzen und uns die Daten über die Assoziationen zusammensuchen. Für die Kommentare müssen wir noch eine Assoziation zusätzlich in unserem View definieren. Da wir später noch ein Verhalten implementieren wollen, benötigst du eine ROOT Entität.
@EndUserText.label: 'Datasource'
@Metadata.allowExtensions: true
define root view entity ZFI_I_DocumentComment
as select from I_OperationalAcctgDocItem
association [0..1] to ZFI_I_Comment as _Comment on _Comment.CompanyCode = $projection.CompanyCode
and _Comment.AccountingDocument = $projection.AccountingDocument
and _Comment.FiscalYear = $projection.FiscalYear
and _Comment.AccountingDocumentItem = $projection.AccountingDocumentItem
{
key CompanyCode,
key AccountingDocument,
key FiscalYear,
key AccountingDocumentItem,
_CompanyCode.CompanyCodeName,
_JournalEntry.DocumentDate,
_JournalEntry.AccountingDocumentHeaderText,
PostingKey,
DocumentItemText,
CostCenter,
_Comment.DocumentComment
}
Wenn wir uns im "Dependency Analyzer" den View anschauen, sieht unser Modell wie im Reiter "SQL Dependency Graph" folgt aus.
Schritt 4 - Anwendung
Um nun eine erste lauffähige Anwendung zu erhalten, legen wir auf dem Core Data Service eine Service Definition an und geben den View damit im Service frei.
@EndUserText.label: 'Comment App'
define service ZFI_COMMENT_APP {
expose ZFI_I_DocumentComment as DocumentComment;
}
Nun erzeugen wir ein Service Binding vom Typ UI für OData v4 und per "Publish" oder On-Premise über Transaktion /IWFND/V4_ADMIN aktivieren wir den Endpunkt im System.
Schauen wir uns nun den aktuellen Stand der Anwendung an, fehlen noch einige Spalten und die Filter für die Anwendung. Über die Einstellungen (Zahnrad) und "Adapt Filters" können wir die UI entsprechend anpassen. Allerdings sind die Anpassungen nur temporär für uns verfügbar.
Damit die Fiori Elements App unseren Anforderungen entspricht, legen wir eine Metadata Extension an, damit können wir die Reihenfolge der Felder beeinflussen, sowie Felder auf der Filterbar definieren.
@Metadata.layer: #CUSTOMER
annotate entity ZFI_I_DocumentComment with
{
@UI : {
selectionField: [{ position: 10 }],
lineItem : [
{ position: 10 },
{ type: #FOR_ACTION, dataAction: 'setComment',label: 'Set comment' } ]
}
CompanyCode;
@UI : {
selectionField: [{ position: 20 }],
lineItem : [{ position: 30 }]
}
AccountingDocument;
@UI : {
selectionField: [{ position: 30 }],
lineItem : [{ position: 40 }]
}
FiscalYear;
@UI : {
lineItem : [{ position: 50 }]
}
AccountingDocumentItem;
@UI : {
lineItem : [{ position: 20 }]
}
CompanyCodeName;
@UI : {
lineItem : [{ position: 60 }]
}
DocumentDate;
@UI : {
lineItem : [{ position: 70 }]
}
AccountingDocumentHeaderText;
@UI : {
lineItem : [{ position: 80 }]
}
PostingKey;
@UI : {
lineItem : [{ position: 90 }]
}
DocumentItemText;
@UI : {
lineItem : [{ position: 100 }]
}
CostCenter;
@UI : {
lineItem : [{ position: 110 }]
}
DocumentComment;
}
Wenn du nun den Preview der Anwendung noch einmal aktualisierst, erhältst du die finale Anwendung mit allen Filtern und Spalten, wie es die ALV bereits dargestellt hatte.
Schritt 5 - Aktion
Im Report hatten wir noch ein Doppelklick-Event auf dem ALV, so ein Event ist kein Standard in Fiori, deshalb implementieren wir eine Alternative. Da der Doppelklick an einen Eintrag der ALV gebunden ist, benötigen wir eine normale ACTION und keine STATIC ACTION. Da wir ein Popup für die Eingabe haben wollen, legen wir eine entsprechende Struktur in Form einer abstrakten Entität an.
@EndUserText.label: 'Popup for comment'
define abstract entity ZFI_S_CommentPopUp
{
@EndUserText.label: 'Comment'
DocumentComment : abap.string;
}
Als nächstes definieren wir eine Verhaltensdefinition auf unserem Root-View. Hierbei legen wir eine MANAGED Implementierung an und verwenden einen UNMANAGED SAVE, da wir keine Tabelle im Hintergrund für die Daten haben. Weiterhin definieren wir eine Aktion, um unsere Kommentare hinzufügen zu können. Das RESULT benötigen wir, damit der Datensatz nach Ausführung der Aktion in der UI aktualisiert wird.
managed implementation in class zbp_fi_document_comment unique;
strict ( 2 );
define behavior for ZFI_I_DocumentComment alias DocumentComment
with unmanaged save
lock master
authorization master ( instance )
{
action setComment parameter ZFI_S_CommentPopUp result [1] $self;
}
Über STRG + 1 lassen wir uns dann die Klasse anlegen und implementieren die Aktion und das Speichern in unsere Zusatztabelle.
CLASS lcl_buffer DEFINITION.
PUBLIC SECTION.
TYPES tt_comment TYPE STANDARD TABLE OF zfi_comment WITH EMPTY KEY.
CLASS-DATA gt_comment TYPE tt_comment.
ENDCLASS.
CLASS lhc_DocumentComment DEFINITION INHERITING FROM cl_abap_behavior_handler.
PRIVATE SECTION.
METHODS get_instance_authorizations FOR INSTANCE AUTHORIZATION
IMPORTING keys REQUEST requested_authorizations FOR DocumentComment RESULT result.
METHODS setComment FOR MODIFY
IMPORTING keys FOR ACTION DocumentComment~setComment RESULT result.
ENDCLASS.
CLASS lhc_DocumentComment IMPLEMENTATION.
METHOD get_instance_authorizations.
ENDMETHOD.
METHOD setComment.
READ ENTITIES OF ZFI_I_DocumentComment IN LOCAL MODE
ENTITY DocumentComment ALL FIELDS WITH CORRESPONDING #( keys )
RESULT DATA(lt_data).
LOOP AT lt_data INTO DATA(ls_data).
DATA(ls_key) = keys[ %tky = ls_data-%tky ].
INSERT VALUE #( bukrs = ls_key-CompanyCode
belnr = ls_key-AccountingDocument
gjahr = ls_key-FiscalYear
buzei = ls_key-AccountingDocumentItem
commt = ls_key-%param-DocumentComment
create_user = cl_abap_context_info=>get_user_technical_name( )
create_time = utclong_current( ) )
INTO TABLE lcl_buffer=>gt_comment.
INSERT VALUE #( %tky = ls_key-%tky
%param = CORRESPONDING #( ls_data ) )
INTO TABLE result REFERENCE INTO DATA(lr_document).
lr_document->%param-DocumentComment = ls_key-%param-DocumentComment.
ENDLOOP.
ENDMETHOD.
ENDCLASS.
CLASS lsc_ZFI_I_DOCUMENTCOMMENT DEFINITION INHERITING FROM cl_abap_behavior_saver.
PROTECTED SECTION.
METHODS
save_modified REDEFINITION.
METHODS
cleanup_finalize REDEFINITION.
ENDCLASS.
CLASS lsc_ZFI_I_DOCUMENTCOMMENT IMPLEMENTATION.
METHOD save_modified.
LOOP AT lcl_buffer=>gt_comment INTO DATA(ls_comment).
INSERT zfi_comment FROM @ls_comment.
IF sy-subrc <> 0.
UPDATE zfi_comment FROM @ls_comment.
ENDIF.
ENDLOOP.
ENDMETHOD.
METHOD cleanup_finalize.
ENDMETHOD.
ENDCLASS.
Was haben wir genau in der Aktion implementiert?
- In der Methode SETCOMMENT lesen wir die selektierten Datensätze ein
- Verarbeitung der Datensätze
- KEYS beinhaltet die Struktur (%param) zur Übergabe des Kommentars
- Befüllung des Puffers zum Anlegen/Ändern der Datensätze in der SAVE-Sequence
- Rückgabe des aktualisierten Datensatzes zum Refresh der Anwendung
- Speichern der Datensätze in SAVE_MODIFIED
Die Aktion haben wir bereits mit der Metadata-Extension implementiert und auf dem UI eingeblendet. Wenn wir nun einen Eintrag in der Liste markieren und die Aktion auslösen, erscheint ein entsprechendes Popup zur Eingabe des Kommentars.
Im Anschluss können wir die Aktion testen, egal ob für einen oder mehrere Datensätze. Der Kommentar wird auf die markierten Dokumentzeilen übernommen. Damit wir haben ohne viel Aufwand auch eine Zusatzanforderung abgedeckt, was mit dem Doppelklick in der ALV erst einmal nicht möglich war.
Vergleich
Zum Abschluss vergleichen wir noch einmal die umgestellten Features der Fiori Elements Anwendung mit dem klassischen Report. Nicht alle Funktionen lassen sich immer eins zu eins auf eine Elements Anwendung übertragen, da die Konzepte auch nicht gleich sind. Fangen wir also mit dem Selektionsbild an, dieses findest du in der Fiori Elements Anwendung als Filterbar im oberen Bereich wieder. Vorteil in Fiori, der User kann weitere Felder zum Filtern selbst hinzufügen.
Die ALV Ausgabe wird als "Responsive Table" unter der Filterbar angezeigt. Alternativ kannst du beim Generieren auch eine "Grid Table" erstellen, um alle Spalten nebeneinander sehen zu können. Vorteil bei der Fiori App ist, dass du über die Liste auch auf die Object Page springen kannst, um alle Informationen in einem strukturierten Format anzeigen zu können.
Zum Abschluss noch der Vergleich der beiden Aktionen. Wir können zwar nicht das Popup per Doppelklick triggern, allerdings können wir mehrere Einträge wählen und für diese einen Kommentar erfassen. Dies wäre allerdings auch mit etwas mehr Aufwand in der ALV Ausgabe möglich.
Best Practice
Das heutige Beispiel soll vor allem eine einfache Migration der Anwendung Richtung ABAP Cloud aufzeigen, dabei werden einige Best Practices nicht berücksichtigt, die im Normalfall vorhanden wären.
- Projektion - Normalerweise würdest du über das Business Objekt noch eine Projektionsschicht einziehen, die die App und die Funktionen nach Außen frei gibt.
- Managed - Im Managed Szenario können wir den Transaktionalen Puffer verwenden und müssen kein eigenes Pufferobjekt benutzen.
- Berechtigungen - Im CDS View ZFI_I_DocumentComment sollte noch eine Berechtigungsprüfung auf die anzuzeigenden Belege stattfinden. Auch die Berechtigung zur Erfassung und Änderung von Kommentaren müsste implementiert werden.
Zusammenfassung
Hier noch eine kurze Zusammenfassung der durchgeführten Schritte aus dem heutigen Artikel.
- Anlage der Software Komponente im System
- Migration der Zusatztabelle nach ABAP Cloud
- Aufbau des Datenmodells
- Erstellung der Anwendung
- Implementierung der Aktion (Verhalten)
Aus dem Report und der Zusatztabellen sind nun die folgenden Objekte entstanden, um unsere Fiori Elements Anwendung zu erstellen. Dabei fehlt allerdings noch die deployte Anwendung im System, hier haben wir nur mit dem Preview der Anwendung gearbeitet.
Fazit
Die Erstellung einer einfachen Anwendung zur Anzeige von Daten ist sehr leicht und benötigt nicht so viele Zusatzobjekte, da wir nicht unbedingt den ganzen RAP Stack erzeugen müssen. Die meisten Funktionen des Reports lassen sich auf eine Fiori Elements App anwenden, allerdings müssen bei der App auch die neuen Konzepte bedacht werden.