Customer-Exit-Variablen werden in BW/4HANA über den BAdI RSROA_VARIABLES_EXIT_BADI gefüllt. Das Framework implementiert diesen BAdI genau einmal als schlanken Dispatcher. Jede fachliche Logik lebt in einer eigenen, testbaren Klasse; welche Variable welche Logik nutzt, steht in einer Customizing-Tabelle statt im Code.
- Eine Logik wird einmal programmiert und von beliebig vielen Variablen genutzt.
- Abhängigkeiten zwischen Variablen sind ohne Codelesen sichtbar.
- Eine neue Variable braucht im Normalfall keinen Code, nur einen Customizing-Eintrag.
- Jede Logik ist mit ABAP Unit ohne laufende Query testbar.
Ausgangsproblem
Der BAdI filtert nach InfoObject und bündelt alle Verarbeitungszeitpunkte in einer Methode PROCESS. Ohne Struktur entstehen daraus typischerweise:
| Problem | Folge |
|---|---|
Eine Sammelimplementierung mit CASE i_vnam / CASE i_step |
Jede Änderung betrifft alle Variablen, Regressionstest und Transport inklusive |
| Logik und Parameter fest im Code („Tag minus 7“, „Tag minus 14“) | Gleiche Logik mehrfach programmiert |
| Referenzvariablen nur im Code | Abhängigkeiten erst beim Codelesen sichtbar |
Lesen von I_T_VAR_RANGE und Aufbau von C_T_RANGE in jeder Variable neu |
Uneinheitliche Fehlerbehandlung |
| Ein Fehler in einer Variable | Kann alle Queries des Systems treffen |
Lösungsprinzip
- Klassen nach Logik geschnitten, nicht nach Variable oder InfoObject.
- Methoden nach Verarbeitungszeitpunkt:
get_range( )für die Steps 0, 1 und 2,check( )für Step 3. - Variablenname, Step, Referenzvariable und Parameter stehen im Customizing.
- Ein Kontextobjekt kapselt die BAdI-Parameter; Logiken sehen nur, was sie brauchen.
Architektur
Architektur des Frameworks
Ablauf je Aufruf
- Der OLAP-Prozessor ruft den BAdI je Exit-Variable und Step auf.
- Der Dispatcher liest den Customizing-Eintrag. Ohne Eintrag, inaktiv oder bei nicht gesetztem Step kehrt er sofort zurück.
- Die Factory erzeugt die konfigurierte Logikklasse.
- Die Logik liest Referenzvariable und Parameter über den Kontext und liefert nur die Werte.
- Der Dispatcher schreibt die Werte nach
C_T_RANGE(nur wenn Werte geliefert werden) oder behandelt den Fehler.
In Step 3 führt der Dispatcher alle für die Query gepflegten Prüfungen aus; eine fehlgeschlagene Prüfung setzt C_CHECK_AGAIN, das Variablenbild erscheint erneut. Über GET_DEPENDENCIES meldet er die Referenzvariable jeder Exit-Variable an den OLAP-Prozessor.
Customizing
ZBWV_VAR_CFG enthält eine Zeile je Exit-Variable:
| Feld | Bedeutung |
|---|---|
VNAM |
Exit-Variable (Schlüssel) |
LOGIC_CLASS |
Logikklasse, implementiert ZIF_BWV_LOGIC; leer = Null-Logik |
REF_VNAM |
Referenzvariable, deren Wert die Logik liest (optional) |
PARAMS |
Parameter als NAME=WERT;NAME=WERT |
STEP_0 / STEP_1 / STEP_2 |
0 = Berechtigung, 1 = vor dem Variablenbild, 2 = nach der Eingabe |
ACTIVE |
Eintrag ein- oder ausschalten |
ON_ERROR |
W Warnung, Variable bleibt leer (Standard); E Fehlermeldung und Abbruch; S still |
DESCRIPTION / OWNER |
Beschreibung, Verantwortlicher |
ZBWV_CHK_CFG enthält die Prüfungen für Step 3: Schlüssel COMPID (Query oder * für alle), CHECK_CLASS und REF_VNAM, dazu PARAMS, ACTIVE, DESCRIPTION und OWNER.
Faustregel für den Step: ohne Referenz Step 1, mit Referenz auf eine Eingabevariable Step 2, Berechtigungsvariablen Step 0. Die Factory puffert das Customizing je Sitzung; Änderungen wirken ab der nächsten Query-Sitzung.
| VNAM | LOGIC_CLASS | REF_VNAM | PARAMS | Step | Ergebnis |
|---|---|---|---|---|---|
ZV_CALDAY_M7 |
ZCL_BWV_L_DATE_OFFSET |
– | OFFSET=-7;UNIT=D |
1 | Heute minus 7 Tage |
ZV_CALMONTH_VM |
ZCL_BWV_L_PERIOD_OFFSET |
ZV_CALMONTH_IN |
OFFSET=-1;IOBJ=CALMONTH |
2 | Vormonat zur Eingabe |
ZV_F_WORKDAYS |
ZCL_BWV_L_WORKDAYS |
ZV_CALMONTH_IN |
CALENDAR=01 |
2 | Arbeitstage im Monat (Formelvariable) |
ZV_T_MONTH |
ZCL_BWV_L_PERIOD_TEXT |
ZV_CALMONTH_IN |
PATTERN={MONTH} {YEAR} |
2 | „März 2026“ (Textvariable) |
Die Schnittstellen
Logik und Prüfung sind zwei kleine Interfaces. Alles, was eine Logik braucht, kommt über den Kontext.
INTERFACE zif_bwv_logic PUBLIC.
"! Ermittelt die Werte der Exit-Variable.
"! Variablenname, Step, Referenzvariable und Parameter kommen aus dem Kontext.
METHODS get_range
IMPORTING io_ctx TYPE REF TO zcl_bwv_context
RETURNING VALUE(rt_range) TYPE rsr_t_rangesid
RAISING zcx_bwv.
ENDINTERFACE.
INTERFACE zif_bwv_check PUBLIC.
"! Prueft die Eingaben nach dem Variablenbild.
"! Ein Fehler wird als ZCX_BWV gemeldet; das Variablenbild erscheint erneut.
METHODS check
IMPORTING io_ctx TYPE REF TO zcl_bwv_context
RAISING zcx_bwv.
ENDINTERFACE.
Der Kontext liefert die Referenzvariable (ref_var( )), beliebige andere Variablen aus I_T_VAR_RANGE (var( )), die Parameter (param( ), param_int( ), param_default( )) und das Tagesdatum (today( )). Das Tagesdatum kommt aus einer austauschbaren Uhr (ZIF_BWV_CLOCK), damit Tests mit einem festen Datum rechnen.
Der Dispatcher
Die BAdI-Methode PROCESS ist kurz. Step 3 geht an die Prüfungen, alles andere an die Variable aus dem Customizing:
METHOD if_rsroa_variables_exit_badi~process.
IF i_step = 3.
" Step 3 kommt einmal pro Query, ohne Variablenname
run_checks( EXPORTING iv_compid = i_s_rkb1d-compid
iv_periv = i_periv
it_var_range = i_t_var_range
CHANGING cv_check_again = c_check_again ).
RETURN.
ENDIF.
DATA(ls_config) = zcl_bwv_factory=>get_config( i_vnam ).
IF zcl_bwv_factory=>is_responsible( is_config = ls_config iv_step = i_step ) = abap_false.
RETURN.
ENDIF.
process_variable( EXPORTING is_config = ls_config
iv_iobjnm = i_iobjnm
iv_step = i_step
iv_periv = i_periv
iv_compid = i_s_rkb1d-compid
it_var_range = i_t_var_range
CHANGING ct_range = c_t_range ).
ENDMETHOD.
METHOD if_rsroa_variables_exit_badi~get_dependencies.
" Referenzvariable aus dem Customizing als Abhaengigkeit melden
DATA(ls_config) = zcl_bwv_factory=>get_config( i_vnam ).
IF ls_config-active = abap_true AND ls_config-ref_vnam IS NOT INITIAL.
INSERT ls_config-ref_vnam INTO TABLE c_t_dep_vnam.
ENDIF.
ENDMETHOD.
In process_variable entsteht der Kontext, die Factory liefert die Logik, und das Ergebnis geht nach C_T_RANGE. Jede Ausnahme wird abgefangen: fachliche Fehler als ZCX_BWV, Laufzeitfehler einer Logik werden in eine ZCX_BWV verpackt. Was dann passiert, entscheidet ON_ERROR der Variable: Warnung und leere Variable, Fehlermeldung mit Abbruch oder stilles Übergehen. Die Meldung selbst läuft über RRMS_MESSAGE_HANDLING und ist so abgesichert, dass eine Query nie wegen einer Meldung abbricht.
Fehlerverhalten
| Situation | Verhalten |
|---|---|
| Variable nicht im Customizing, inaktiv oder Step nicht gesetzt | Dispatcher kehrt sofort zurück, C_T_RANGE bleibt unverändert |
| Klasse fehlt oder hat das falsche Interface | ZCX_BWV, Reaktion gemäß ON_ERROR |
| Referenzvariable leer, mehrwertig oder kein Einzelwert | ZCX_BWV |
| Parameter fehlt oder ist ungültig | ZCX_BWV |
| Laufzeitfehler in einer Logik | Als ZCX_BWV verpackt; andere Variablen laufen weiter |
| Step-3-Prüfung schlägt fehl | Fehlermeldung, C_CHECK_AGAIN, Variablenbild erscheint erneut |
| Meldungsausgabe schlägt fehl | Abgefangen; die Query bricht nie wegen einer Meldung ab |
Beispielimplementierung: Monat oder Periode verschieben
ZCL_BWV_L_PERIOD_OFFSET liefert Vormonat, Vorjahresmonat oder Vorperiode. Für Kalendermonate ohne Referenz ist die Basis der aktuelle Monat. Für Geschäftsperioden ist eine Referenz Pflicht; die Anzahl Perioden pro Jahr kommt aus der Geschäftsjahresvariante, Sonderperioden zählen wie die letzte reguläre Periode. 0FISCPER ist an 0FISCVARNT geklammert, der Wert kommt deshalb als K42026003; das Präfix wird abgetrennt und dem Ergebnis wieder vorangestellt.
| Parameter | Werte | Standard | Bedeutung |
|---|---|---|---|
OFFSET |
ganze Zahl, auch negativ | Pflicht | Verschiebung in Monaten oder Perioden |
IOBJ |
CALMONTH, FISCPER |
aus dem InfoObject der Variable | Art des Werts |
| Beispiel | REF_VNAM | PARAMS | Eingabe | Ergebnis |
|---|---|---|---|---|
ZV_CALMONTH_VM |
ZV_CALMONTH_IN |
OFFSET=-1;IOBJ=CALMONTH |
202601 |
202512 |
ZV_CALMONTH_VJM |
ZV_CALMONTH_IN |
OFFSET=-12;IOBJ=CALMONTH |
202603 |
202503 |
ZV_FISCPER_VP |
ZV_FISCPER_IN |
OFFSET=-1;IOBJ=FISCPER |
K42026001 |
K42025012 |
"! Kalendermonat oder Geschaeftsperiode plus/minus Offset.
"! Basis: Referenzvariable; ohne Referenz bei CALMONTH der aktuelle Monat.
"! PARAMS: OFFSET=n; IOBJ=CALMONTH | FISCPER (Default aus dem InfoObject)
CLASS zcl_bwv_l_period_offset DEFINITION
PUBLIC
INHERITING FROM zcl_bwv_logic_base
FINAL
CREATE PUBLIC.
PUBLIC SECTION.
METHODS zif_bwv_logic~get_range REDEFINITION.
ENDCLASS.
CLASS zcl_bwv_l_period_offset IMPLEMENTATION.
METHOD zif_bwv_logic~get_range.
DATA lv_base TYPE string.
DATA lv_prefix TYPE string.
DATA lv_core TYPE string.
DATA(lv_default_type) = COND string( WHEN io_ctx->mv_iobjnm CS 'FISCPER' THEN `FISCPER` ELSE `CALMONTH` ).
DATA(lv_type) = to_upper( io_ctx->param_default( iv_name = 'IOBJ' iv_default = lv_default_type ) ).
DATA(lv_offset) = io_ctx->param_int( 'OFFSET' ).
CASE lv_type.
WHEN 'CALMONTH'.
IF io_ctx->mv_ref_vnam IS INITIAL.
DATA(lv_today) = io_ctx->today( ).
lv_base = lv_today(6).
ELSE.
lv_base = io_ctx->ref_var( )->get_single( ).
ENDIF.
IF zcl_bwv_time=>is_calmonth( lv_base ) = abap_false.
RAISE EXCEPTION TYPE zcx_bwv MESSAGE e008(zbwv) WITH io_ctx->mv_vnam lv_base.
ENDIF.
rt_range = single_value( zcl_bwv_time=>calmonth_add( iv_calmonth = lv_base iv_months = lv_offset ) ).
WHEN 'FISCPER'.
" 0FISCPER ist an 0FISCVARNT geklammert: Wert kommt als K42026003
zcl_bwv_time=>split_compound( EXPORTING iv_value = io_ctx->ref_var( )->get_single( )
iv_core_length = 7
IMPORTING ev_prefix = lv_prefix
ev_core = lv_core ).
IF zcl_bwv_time=>is_fiscper( lv_core ) = abap_false.
RAISE EXCEPTION TYPE zcx_bwv MESSAGE e008(zbwv) WITH io_ctx->mv_vnam lv_core.
ENDIF.
DATA(lv_periv) = io_ctx->mv_periv.
IF lv_periv IS INITIAL.
lv_periv = lv_prefix.
ENDIF.
DATA(lv_periods) = 12.
IF lv_periv IS NOT INITIAL.
lv_periods = zcl_bwv_time=>periods_per_year( lv_periv ).
ENDIF.
rt_range = single_value( lv_prefix && zcl_bwv_time=>fiscper_add( iv_fiscper = lv_core
iv_periods = lv_offset
iv_periods_per_year = lv_periods ) ).
WHEN OTHERS.
RAISE EXCEPTION TYPE zcx_bwv MESSAGE e007(zbwv) WITH io_ctx->mv_vnam 'IOBJ' lv_type.
ENDCASE.
ENDMETHOD.
ENDCLASS.
Die Basisklasse ZCL_BWV_LOGIC_BASE liefert die Helfer single_value( ), interval( ) und value_list( ), die aus Werten die Range-Zeilen für C_T_RANGE bauen.
Testen ohne Query
Ein Unit-Test baut den Kontext selbst, mit Customizing-Zeile, Variablenwerten und einer festen Uhr. Weder BAdI noch OLAP-Prozessor sind beteiligt.
CLASS ltd_clock DEFINITION FINAL FOR TESTING.
PUBLIC SECTION.
INTERFACES zif_bwv_clock.
ENDCLASS.
CLASS ltd_clock IMPLEMENTATION.
METHOD zif_bwv_clock~today.
rv_date = '20260315'.
ENDMETHOD.
ENDCLASS.
CLASS ltc_period_offset DEFINITION FINAL FOR TESTING
DURATION SHORT RISK LEVEL HARMLESS.
PRIVATE SECTION.
METHODS previous_month FOR TESTING RAISING zcx_bwv.
ENDCLASS.
CLASS ltc_period_offset IMPLEMENTATION.
METHOD previous_month.
DATA(lo_ctx) = zcl_bwv_context=>create(
iv_vnam = 'ZV_TEST'
iv_step = 2
it_var_range = VALUE #( ( vnam = 'ZV_REF' sign = 'I' opt = 'EQ' low = '202601' ) )
is_config = VALUE #( vnam = 'ZV_TEST' ref_vnam = 'ZV_REF' params = 'OFFSET=-1;IOBJ=CALMONTH' )
io_clock = NEW ltd_clock( ) ).
DATA(lt_range) = NEW zcl_bwv_l_period_offset( )->zif_bwv_logic~get_range( lo_ctx ).
cl_abap_unit_assert=>assert_equals( act = lt_range[ 1 ]-low exp = `202512` ).
ENDMETHOD.
ENDCLASS.
Neue Exit-Variable anlegen
- In der Übersicht des Customizings prüfen, ob es die Variable oder eine passende Logik schon gibt.
- Variable mit Verarbeitungsart „Customer Exit“ anlegen, in der Regel nicht eingabebereit.
- Eintrag in
ZBWV_VAR_CFGpflegen und die Konsistenzprüfung ausführen.
Nur wenn keine Logik passt: neue Klasse, die von ZCL_BWV_LOGIC_BASE erbt und GET_RANGE redefiniert, mit Unit-Test. Eine Prüfung implementiert stattdessen ZIF_BWV_CHECK~CHECK und wird in ZBWV_CHK_CFG eingetragen.