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: Der OLAP-Prozessor ruft den Dispatcher mit I_VNAM, I_STEP und I_T_VAR_RANGE; der Dispatcher fragt die Factory, die das Customizing liest und für die Steps 0 bis 2 eine Logikklasse, für Step 3 eine Prüfklasse erzeugt; Logiken und Prüfungen nutzen die gemeinsamen Bausteine Kontext, Variable, Basisklasse, Zeit, Uhr und Ausnahme Architektur des Frameworks

Ablauf je Aufruf

  1. Der OLAP-Prozessor ruft den BAdI je Exit-Variable und Step auf.
  2. Der Dispatcher liest den Customizing-Eintrag. Ohne Eintrag, inaktiv oder bei nicht gesetztem Step kehrt er sofort zurück.
  3. Die Factory erzeugt die konfigurierte Logikklasse.
  4. Die Logik liest Referenzvariable und Parameter über den Kontext und liefert nur die Werte.
  5. 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

  1. In der Übersicht des Customizings prüfen, ob es die Variable oder eine passende Logik schon gibt.
  2. Variable mit Verarbeitungsart „Customer Exit“ anlegen, in der Regel nicht eingabebereit.
  3. Eintrag in ZBWV_VAR_CFG pflegen 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.