Developer Extensibility: Custom Logic in Standard-Apps einbauen

kategorie
ABAP Cloud
Veröffentlicht
autor
Johannes

Developer Extensibility ermöglicht ABAP-Entwicklern, Standard-SAP-Anwendungen mit eigenem Code zu erweitern. Dieser Artikel zeigt die verschiedenen Extensibility-Konzepte und ihre praktische Anwendung.

Was ist Developer Extensibility?

Developer Extensibility ist das Erweiterungsmodell für ABAP-Entwickler in ABAP Cloud:

AspektDeveloper Extensibility
ZielgruppeABAP-Entwickler
WerkzeugeADT (Eclipse), Business Application Studio
SpracheABAP Cloud (Restricted Language Version)
FlexibilitätVolle Programmierfreiheit
VerfügbarkeitS/4HANA Cloud, BTP ABAP Environment

Developer vs. Key User Extensibility

KriteriumDeveloper ExtensibilityKey User Extensibility
KomplexitätBeliebig komplexEinfache Logik
CodeABAP CloudLow-Code/No-Code
TestingUnit Tests möglichManuell
VersionierungGit/gCTSAutomatisch
PerformanceOptimierbarVorgegeben
APIsAlle Released APIsVordefinierte Trigger

Extension Include Strukturen

Extension Includes ermöglichen das Hinzufügen eigener Felder zu SAP-Standardtabellen.

Konzept

SAP definiert Extension-Strukturen in Standard-Datenbanktabellen:

-- Standard-Tabelle mit Extension Include
define table I_SALESORDER {
key SalesOrder : vbeln;
SalesOrderType : auart;
SoldToParty : kunnr;
...
-- Extension Include Point
include structure ZZ1_SALESORDER_EXT;
}

Eigene Extension erstellen

Schritt 1: Extension Structure anlegen

@EndUserText.label: 'Sales Order Extension Fields'
@AbapCatalog.enhancement.category: #EXTENSIBLE_ANY
define structure zz1_salesorder_ext {
zz1_project_id : char20;
zz1_priority : /dmo/priority;
zz1_external_ref : char35;
}

Schritt 2: In ADT aktivieren

  1. Rechtsklick auf die Struktur → “Include in Extension Include”
  2. Ziel-Tabelle auswählen (z.B. I_SALESORDER)
  3. Aktivieren

Beispiel: Zusatzfelder für Kundenauftrag

@EndUserText.label: 'Sales Order Customer Extensions'
@AbapCatalog.enhancement.category: #EXTENSIBLE_ANY
define structure zz1_sd_order_ext {
" Projektreferenz für Auftragsverfolgung
zz1_project_id : char20;
" Prioritätskennzeichen
zz1_priority_code : char1;
" Externe Systemreferenz
zz1_external_ref : char35;
" Kundenspezifisches Datum
zz1_customer_date : dats;
}

Zugriff auf Extension-Felder

" In CDS View
define view entity ZI_SalesOrderExt
as select from I_SalesOrder
{
key SalesOrder,
SoldToParty,
" Extension-Felder direkt verfügbar
zz1_project_id as ProjectId,
zz1_priority_code as PriorityCode,
zz1_external_ref as ExternalReference,
zz1_customer_date as CustomerDate
}

BAdI-Implementierungen

BAdIs (Business Add-Ins) sind der zentrale Erweiterungsmechanismus in ABAP Cloud.

BAdI finden

In ADT nach verfügbaren BAdIs suchen:

  1. Open Development Object (Ctrl+Shift+A)
  2. Suche nach *BADI* im gewünschten Bereich
  3. Oder in der Dokumentation: SAP API Business Hub

Wichtige BAdI-Kategorien

KategorieBeispielVerwendung
ValidationBADI_SD_SALES_DOC_CHECKEingabeprüfung
DeterminationBADI_SD_PRICINGWertableitung
ModificationBADI_SD_ITEM_DATADatenänderung
AuthorizationBADI_AUTH_CHECKBerechtigungen

BAdI implementieren

Schritt 1: Enhancement Implementation anlegen

" Enhancement Implementation: ZEI_SALES_ORDER_CHECK
@ObjectModel.leadingEntity.name: 'I_SALESORDER'

Schritt 2: BAdI-Klasse erstellen

CLASS zcl_sales_order_validation DEFINITION
PUBLIC
FINAL
CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES if_sd_sales_doc_check.
ENDCLASS.
CLASS zcl_sales_order_validation IMPLEMENTATION.
METHOD if_sd_sales_doc_check~check.
" Projektnummer-Validierung
LOOP AT it_order_items ASSIGNING FIELD-SYMBOL(<item>).
" Projekt muss bei bestimmten Auftragsarten gefüllt sein
IF <item>-order_type = 'ZPR'
AND <item>-zz1_project_id IS INITIAL.
" Fehler zur Nachrichtentabelle hinzufügen
APPEND VALUE #(
msgty = 'E'
msgid = 'ZSD'
msgno = '001'
msgv1 = <item>-sales_order_item
) TO ct_messages.
ENDIF.
ENDLOOP.
ENDMETHOD.
ENDCLASS.

Beispiel: Preisfindungs-BAdI

CLASS zcl_custom_pricing DEFINITION
PUBLIC
FINAL
CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES if_sd_pricing_exit.
ENDCLASS.
CLASS zcl_custom_pricing IMPLEMENTATION.
METHOD if_sd_pricing_exit~calculate_price.
" Kundenspezifischer Rabatt basierend auf Priorität
DATA(lv_priority) = is_item_data-zz1_priority_code.
CASE lv_priority.
WHEN 'A'.
" VIP-Kunden: 5% Extra-Rabatt
cv_discount_percent = cv_discount_percent + 5.
WHEN 'B'.
" Wichtige Kunden: 2.5% Extra-Rabatt
cv_discount_percent = cv_discount_percent + '2.5'.
WHEN OTHERS.
" Keine Änderung
ENDCASE.
ENDMETHOD.
ENDCLASS.

BAdI mit Filter implementieren

Filter ermöglichen gezielte Aktivierung:

CLASS zcl_sales_check_de DEFINITION
PUBLIC
FINAL
CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES if_sd_sales_doc_check.
PRIVATE SECTION.
" Nur für bestimmte Länder aktiv
CONSTANTS: c_country_filter TYPE land1 VALUE 'DE'.
ENDCLASS.
CLASS zcl_sales_check_de IMPLEMENTATION.
METHOD if_sd_sales_doc_check~check.
" Prüfung nur für deutsche Aufträge
IF is_header-country <> c_country_filter.
RETURN.
ENDIF.
" Deutsche USt-ID Prüfung
IF is_header-vat_registration IS INITIAL
AND is_header-customer_type = 'B2B'.
APPEND VALUE #(
msgty = 'E'
msgid = 'ZSD'
msgno = '002'
msgv1 = 'USt-ID für B2B-Kunden erforderlich'
) TO ct_messages.
ENDIF.
ENDMETHOD.
ENDCLASS.

CDS View Extensions

CDS View Extensions erweitern Standard-CDS-Views um eigene Felder und Logik.

Extend View Entity

extend view entity I_SalesOrder with ZE_SalesOrder_Ext {
" Extension-Felder hinzufügen
zz1_project_id as ProjectId,
zz1_priority_code as PriorityCode,
zz1_customer_date as CustomerDate,
" Berechnetes Feld
case zz1_priority_code
when 'A' then 'High Priority'
when 'B' then 'Medium Priority'
when 'C' then 'Low Priority'
else 'Not Classified'
end as PriorityText,
" Assoziation zu Custom Entity
_CustomProject
}

Mit Assoziationen erweitern

extend view entity I_SalesOrder with ZE_SalesOrder_Project {
" Assoziation zu Projektdaten
association [0..1] to ZI_Project as _CustomProject
on $projection.zz1_project_id = _CustomProject.ProjectId,
" Felder aus der Assoziation exponieren
_CustomProject.ProjectName,
_CustomProject.ProjectManager,
_CustomProject.PlannedEndDate
}

Beispiel: Kundenrating-Extension

@EndUserText.label: 'Business Partner Extension - Customer Rating'
extend view entity I_BusinessPartner with ZE_BP_CustomerRating {
" Extension-Felder für Kundenbewertung
zz1_customer_rating as CustomerRating,
zz1_last_rating_date as LastRatingDate,
" Berechneter Rating-Status
case
when zz1_customer_rating >= 8 then 'Gold'
when zz1_customer_rating >= 5 then 'Silver'
when zz1_customer_rating >= 3 then 'Bronze'
else 'Standard'
end as RatingCategory,
" Assoziation zu Rating-Historie
association [0..*] to ZI_CustomerRatingHistory as _RatingHistory
on $projection.BusinessPartner = _RatingHistory.BusinessPartner,
_RatingHistory
}

Access Control für Extensions

@EndUserText.label: 'Access Control for Sales Order Extension'
@MappingRole: true
define role ZE_SalesOrder_Auth {
grant select on ZE_SalesOrder_Ext
where ( SalesOrganization ) =
aspect pfcg_auth( V_VBAK_VKO, VKORG, ACTVT = '03' )
and ( zz1_priority_code ) =
aspect pfcg_auth( Z_PRIORITY, PRIORITY, ACTVT = '03' );
}

Behavior Extensions

Behavior Extensions erweitern das RAP-Verhalten von Standard-Entitäten.

Behavior Extension definieren

extension using interface zif_rap_sales_order
implementation in class zbp_sales_order_ext unique;
extend behavior for I_SalesOrderTP alias SalesOrder {
// Eigene Validierungen hinzufügen
validation validateProjectId on save {
field zz1_project_id;
}
// Eigene Determinations
determination setDefaultPriority on modify {
field SalesOrderType;
}
// Eigene Actions
action ( features : instance ) escalatePriority result [1] $self;
// Side Effects für Extension-Felder
side effects {
field zz1_project_id affects field ProjectName;
}
}

Behavior Extension implementieren

CLASS zbp_sales_order_ext DEFINITION
PUBLIC
FINAL
CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES zif_rap_sales_order.
ENDCLASS.
CLASS zbp_sales_order_ext IMPLEMENTATION.
METHOD zif_rap_sales_order~validateProjectId.
" Projekt-IDs lesen
READ ENTITIES OF i_salesordertp IN LOCAL MODE
ENTITY SalesOrder
FIELDS ( zz1_project_id SalesOrderType )
WITH CORRESPONDING #( keys )
RESULT DATA(lt_orders).
LOOP AT lt_orders ASSIGNING FIELD-SYMBOL(<order>).
" Bei Projektaufträgen muss Projekt-ID gefüllt sein
IF <order>-SalesOrderType = 'ZPR'
AND <order>-zz1_project_id IS INITIAL.
APPEND VALUE #(
%tky = <order>-%tky
%msg = new_message_with_text(
severity = if_abap_behv_message=>severity-error
text = 'Projektauftrag erfordert eine Projekt-ID' )
%element-zz1_project_id = if_abap_behv=>mk-on
) TO reported-salesorder.
APPEND VALUE #( %tky = <order>-%tky ) TO failed-salesorder.
ENDIF.
ENDLOOP.
ENDMETHOD.
METHOD zif_rap_sales_order~setDefaultPriority.
" Auftragstyp lesen
READ ENTITIES OF i_salesordertp IN LOCAL MODE
ENTITY SalesOrder
FIELDS ( SalesOrderType zz1_priority_code )
WITH CORRESPONDING #( keys )
RESULT DATA(lt_orders).
" Default-Priorität basierend auf Auftragstyp
LOOP AT lt_orders ASSIGNING FIELD-SYMBOL(<order>)
WHERE zz1_priority_code IS INITIAL.
DATA(lv_priority) = COND char1(
WHEN <order>-SalesOrderType = 'ZEXP' THEN 'A' " Express
WHEN <order>-SalesOrderType = 'ZSTD' THEN 'C' " Standard
ELSE 'B' " Default Medium
).
MODIFY ENTITIES OF i_salesordertp IN LOCAL MODE
ENTITY SalesOrder
UPDATE FIELDS ( zz1_priority_code )
WITH VALUE #( (
%tky = <order>-%tky
zz1_priority_code = lv_priority
) ).
ENDLOOP.
ENDMETHOD.
METHOD zif_rap_sales_order~escalatePriority.
" Aktuelle Daten lesen
READ ENTITIES OF i_salesordertp IN LOCAL MODE
ENTITY SalesOrder
FIELDS ( zz1_priority_code )
WITH CORRESPONDING #( keys )
RESULT DATA(lt_orders).
LOOP AT lt_orders ASSIGNING FIELD-SYMBOL(<order>).
" Priorität um eine Stufe erhöhen
DATA(lv_new_priority) = COND char1(
WHEN <order>-zz1_priority_code = 'C' THEN 'B'
WHEN <order>-zz1_priority_code = 'B' THEN 'A'
ELSE 'A' " Bereits höchste Priorität
).
MODIFY ENTITIES OF i_salesordertp IN LOCAL MODE
ENTITY SalesOrder
UPDATE FIELDS ( zz1_priority_code )
WITH VALUE #( (
%tky = <order>-%tky
zz1_priority_code = lv_new_priority
) ).
" Ergebnis zurückgeben
APPEND VALUE #(
%tky = <order>-%tky
%param = CORRESPONDING #( <order> )
) TO result.
ENDLOOP.
ENDMETHOD.
ENDCLASS.

Feature Control für Extension Actions

METHOD zif_rap_sales_order~get_instance_features.
READ ENTITIES OF i_salesordertp IN LOCAL MODE
ENTITY SalesOrder
FIELDS ( zz1_priority_code OverallStatus )
WITH CORRESPONDING #( keys )
RESULT DATA(lt_orders).
LOOP AT lt_orders ASSIGNING FIELD-SYMBOL(<order>).
" Eskalation nur wenn nicht bereits Priority A
" und Auftrag nicht abgeschlossen
DATA(lv_escalate_enabled) = COND #(
WHEN <order>-zz1_priority_code = 'A'
THEN if_abap_behv=>fc-o-disabled
WHEN <order>-OverallStatus = 'C'
THEN if_abap_behv=>fc-o-disabled
ELSE if_abap_behv=>fc-o-enabled
).
APPEND VALUE #(
%tky = <order>-%tky
%action-escalatePriority = lv_escalate_enabled
) TO result.
ENDLOOP.
ENDMETHOD.

Praktisches Beispiel: Komplette Extension

Ein vollständiges Beispiel für die Erweiterung von Kundenaufträgen:

1. Extension Include Structure

@EndUserText.label: 'Sales Order Extension Structure'
@AbapCatalog.enhancement.category: #EXTENSIBLE_ANY
define structure zz1_salesorder_ext {
zz1_project_id : char20;
zz1_priority : char1;
zz1_approval_status : char2;
zz1_approved_by : syuname;
zz1_approved_at : timestampl;
}

2. CDS View Extension

extend view entity I_SalesOrderTP with ZE_SalesOrderTP_Ext {
zz1_project_id as ProjectId,
zz1_priority as Priority,
zz1_approval_status as ApprovalStatus,
zz1_approved_by as ApprovedBy,
zz1_approved_at as ApprovedAt,
case zz1_approval_status
when 'AP' then 'Approved'
when 'RJ' then 'Rejected'
when 'PN' then 'Pending'
else 'Not Required'
end as ApprovalStatusText
}

3. Behavior Extension

extension implementation in class zbp_so_approval_ext unique;
extend behavior for I_SalesOrderTP alias SalesOrder {
validation validateApprovalRequired on save {
field NetAmount;
}
determination setApprovalPending on modify {
create;
}
action ( features : instance ) approve;
action ( features : instance ) reject;
}

4. Implementation Class

CLASS zbp_so_approval_ext IMPLEMENTATION.
METHOD validateApprovalRequired.
" Aufträge über 10.000 EUR müssen genehmigt werden
READ ENTITIES OF i_salesordertp IN LOCAL MODE
ENTITY SalesOrder
FIELDS ( NetAmount zz1_approval_status TransactionCurrency )
WITH CORRESPONDING #( keys )
RESULT DATA(lt_orders).
LOOP AT lt_orders ASSIGNING FIELD-SYMBOL(<order>).
IF <order>-NetAmount > 10000
AND <order>-zz1_approval_status <> 'AP'.
APPEND VALUE #(
%tky = <order>-%tky
%msg = new_message_with_text(
severity = if_abap_behv_message=>severity-error
text = |Aufträge über 10.000 { <order>-TransactionCurrency
} erfordern Genehmigung| )
) TO reported-salesorder.
APPEND VALUE #( %tky = <order>-%tky ) TO failed-salesorder.
ENDIF.
ENDLOOP.
ENDMETHOD.
METHOD setApprovalPending.
" Neue Aufträge über Schwellwert auf 'Pending' setzen
READ ENTITIES OF i_salesordertp IN LOCAL MODE
ENTITY SalesOrder
FIELDS ( NetAmount )
WITH CORRESPONDING #( keys )
RESULT DATA(lt_orders).
LOOP AT lt_orders ASSIGNING FIELD-SYMBOL(<order>)
WHERE NetAmount > 10000.
MODIFY ENTITIES OF i_salesordertp IN LOCAL MODE
ENTITY SalesOrder
UPDATE FIELDS ( zz1_approval_status )
WITH VALUE #( (
%tky = <order>-%tky
zz1_approval_status = 'PN' " Pending
) ).
ENDLOOP.
ENDMETHOD.
METHOD approve.
MODIFY ENTITIES OF i_salesordertp IN LOCAL MODE
ENTITY SalesOrder
UPDATE FIELDS ( zz1_approval_status zz1_approved_by zz1_approved_at )
WITH VALUE #( FOR key IN keys (
%tky = key-%tky
zz1_approval_status = 'AP'
zz1_approved_by = cl_abap_context_info=>get_user_technical_name( )
zz1_approved_at = cl_abap_context_info=>get_system_time( )
) ).
" Ergebnis zurückgeben
READ ENTITIES OF i_salesordertp IN LOCAL MODE
ENTITY SalesOrder
ALL FIELDS
WITH CORRESPONDING #( keys )
RESULT DATA(lt_orders).
result = CORRESPONDING #( lt_orders ).
ENDMETHOD.
METHOD reject.
MODIFY ENTITIES OF i_salesordertp IN LOCAL MODE
ENTITY SalesOrder
UPDATE FIELDS ( zz1_approval_status zz1_approved_by zz1_approved_at )
WITH VALUE #( FOR key IN keys (
%tky = key-%tky
zz1_approval_status = 'RJ'
zz1_approved_by = cl_abap_context_info=>get_user_technical_name( )
zz1_approved_at = cl_abap_context_info=>get_system_time( )
) ).
READ ENTITIES OF i_salesordertp IN LOCAL MODE
ENTITY SalesOrder
ALL FIELDS
WITH CORRESPONDING #( keys )
RESULT DATA(lt_orders).
result = CORRESPONDING #( lt_orders ).
ENDMETHOD.
ENDCLASS.

Best Practices

Extension-Entwicklung

EmpfehlungBegründung
Eigene NamensräumeZZ1_, YY1_ für SAP-Cloud-Kompatibilität
Interface-basiertTestbarkeit und Entkopplung
DokumentationJede Extension dokumentieren
Unit TestsAutomatisierte Tests für BAdIs

Vermeiden

❌ Direkte Tabellenänderungen ohne Extension Include
❌ Hardcodierte Werte in BAdIs
❌ Zu viele Extensions auf eine Entität
❌ Extensions ohne Fehlerbehandlung
❌ Performance-intensive Logik in Determinations

Versionierung

Empfohlene Commit-Struktur:
├── feat: Add project tracking extension fields
├── feat: Implement validation BAdI for projects
├── feat: Add CDS view extension with project assoc
└── feat: Implement approval workflow behavior ext

Migration und Upgrade

Von Key User zu Developer Extensibility

  1. Custom Fields beibehalten: Datenstrukturen bleiben erhalten
  2. Custom Logic ersetzen: ABAP-BAdI statt Key User Logic
  3. Testen: Sicherstellen, dass beide Implementierungen nicht kollidieren
  4. Deaktivieren: Key User Logic nach erfolgreicher Migration deaktivieren

Upgrade-Sicherheit

  • Extension Includes sind upgrade-stabil
  • BAdI-Schnittstellen können sich ändern → Release Notes beachten
  • CDS View Extensions bei Basisview-Änderungen prüfen

Weiterführende Themen