Definition: Badi Stands for BUSINESS ADD-INS, A BADI is an enhancement technique that facilitates a SAP programmer, a user, or a specific industry to add some additional code to the existing program in SAP system. We can use standard or customized logic to improve the SAP system. A BADI must first be defined and then implemented to enhance SAP application.
In SAP BTP, we don’t have standard BADIs. Instead, we could write custom BADIs for performing custom Functionalities. So in this blog I will be demonstrating how to Define, Implement and use Custom BADIS in SAP BTP. Please excuse the logic used in the code side of the BADI.
Let Us consider that we have a pre-defined
Table : ztl_po
@EndUserText.label : 'PURCHASE ORDER'
@AbapCatalog.enhancement.category : #NOT_EXTENSIBLE
@AbapCatalog.tableCategory : #TRANSPARENT
@AbapCatalog.deliveryClass : #A
@AbapCatalog.dataMaintenance : #RESTRICTED
define table ztl_po {
key mandt : abap.clnt not null;
key id : abap.char(10) not null;
purchase_date : abap.dats;
quantity : abap.dec(6,2);
amount : abap.dec(10,2);
remarks : abap.char(100);
}
CDS: ZI_PO
@AbapCatalog.sqlViewName: 'ZPOT'
@AbapCatalog.compiler.compareFilter: true
@AbapCatalog.preserveKey: true
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'PURCHASE Order'
@UI: {
headerInfo: { typeName: 'PURCHASE Order',
typeNamePlural: 'PURCHASE Order',
title: { type: #STANDARD }
}
}
@Search.searchable: true
define root view ZI_PO
as select from ztl_po
{
@UI.facet: [ { id: 'i_po',
purpose: #STANDARD,
type: #IDENTIFICATION_REFERENCE,
label: 'PURCHASE Order',
position: 10 }]
@Search.defaultSearchElement: true
@UI: { lineItem: [ { position: 10, importance: #HIGH } ],
identification: [ { position: 10 } ] }
key id as Id,
@UI: { lineItem: [ { position: 20, importance: #HIGH } ],
identification: [ { position: 20 } ] }
purchase_date as PurchaseDate,
@UI: { lineItem: [ { position: 30, importance: #HIGH } ],
identification: [ { position: 30 } ] }
quantity as Quantity,
@UI: { lineItem: [ { position: 40, importance: #HIGH } ],
identification: [ { position: 40 } ] }
amount as Amount,
@UI: { lineItem: [ { position: 50, importance: #HIGH } ],
identification: [ { position: 50 } ] }
remarks as Remarks
}
Behavior Definition : ZI_PO
unmanaged implementation in class zbp_i_po unique;
define behavior for ZI_PO
{
create;
update;
delete;
}
Behavior Implementation : zbp_i_po
CLASS lcl_buffer DEFINITION.
PUBLIC SECTION.
TYPES: BEGIN OF ty_buffer.
INCLUDE TYPE ztl_po AS lv_data.
TYPES: flag TYPE c LENGTH 1,
END OF ty_buffer.
CLASS-DATA mt_buffer TYPE TABLE OF ty_buffer.
ENDCLASS.
CLASS lhc_po DEFINITION INHERITING FROM cl_abap_behavior_handler.
PRIVATE SECTION.
METHODS create FOR MODIFY
IMPORTING entities FOR CREATE zi_po.
METHODS delete FOR MODIFY
IMPORTING keys FOR DELETE zi_po.
METHODS update FOR MODIFY
IMPORTING entities FOR UPDATE zi_po.
METHODS read FOR READ
IMPORTING keys FOR READ zi_po RESULT result.
ENDCLASS.
CLASS lhc_po IMPLEMENTATION.
METHOD create.
LOOP AT entities INTO DATA(ls_create).
INSERT VALUE #( flag = 'C' lv_data = CORRESPONDING #( ls_create-%data ) ) INTO TABLE lcl_buffer=>mt_buffer.
ENDLOOP.
ENDMETHOD.
METHOD delete.
LOOP AT keys INTO DATA(ls_delete).
READ TABLE lcl_buffer=>mt_buffer
ASSIGNING FIELD-SYMBOL(<ls_buffer>) WITH KEY id = ls_delete-id.
IF sy-subrc = 0.
" already in buffer, check why
IF <ls_buffer>-flag = 'C'.
"delete after create => just remove from buffer
DELETE TABLE lcl_buffer=>mt_buffer FROM <ls_buffer>.
ELSE.
<ls_buffer>-flag = 'D'.
ENDIF.
ELSE.
" not yet in buffer.
INSERT VALUE #( flag = 'D' id = ls_delete-id ) INTO TABLE lcl_buffer=>mt_buffer.
ENDIF.
ENDLOOP.
ENDMETHOD.
METHOD update.
LOOP AT entities INTO DATA(ls_update).
READ TABLE lcl_buffer=>mt_buffer ASSIGNING FIELD-SYMBOL(<ls_buffer>) WITH KEY id = ls_update-id .
IF sy-subrc <> 0.
" not yet in buffer, read from table
SELECT SINGLE * FROM ztl_po WHERE id = @ls_update-id INTO @DATA(ls_db).
INSERT VALUE #( flag = 'U' lv_data = ls_db ) INTO TABLE lcl_buffer=>mt_buffer ASSIGNING <ls_buffer>.
ENDIF.
IF ls_update-%control-id IS NOT INITIAL.. <ls_buffer>-id = ls_update-id. ENDIF.
IF ls_update-%control-PurchaseDate IS NOT INITIAL.. <ls_buffer>-Purchase_Date = ls_update-PurchaseDate. ENDIF.
IF ls_update-%control-amount IS NOT INITIAL.. <ls_buffer>-amount = ls_update-amount. ENDIF.
IF ls_update-%control-quantity IS NOT INITIAL.
ls_update-Quantity = zbadi_class=>quantity.
<ls_buffer>-quantity = ls_update-quantity.
ENDIF.
IF ls_update-%control-remarks IS NOT INITIAL.. <ls_buffer>-remarks = ls_update-remarks. ENDIF.
ENDLOOP.
ENDMETHOD.
METHOD read.
ENDMETHOD.
ENDCLASS.
CLASS lsc_i_sb_thirdpar DEFINITION INHERITING FROM cl_abap_behavior_saver.
PROTECTED SECTION.
METHODS check_before_save REDEFINITION.
METHODS finalize REDEFINITION.
METHODS save REDEFINITION.
ENDCLASS.
CLASS lsc_i_sb_thirdpar IMPLEMENTATION.
METHOD check_before_save.
ENDMETHOD.
METHOD finalize.
ENDMETHOD.
METHOD save.
DATA lt_data TYPE STANDARD TABLE OF ztl_po.
" find all rows in buffer with flag = created
lt_data = VALUE #( FOR row IN lcl_buffer=>mt_buffer WHERE ( flag = 'C' ) ( row-lv_data ) ).
IF lt_data IS NOT INITIAL.
INSERT ztl_po FROM TABLE @lt_data.
ENDIF.
" find all rows in buffer with flag = updated
lt_data = VALUE #( FOR row IN lcl_buffer=>mt_buffer WHERE ( flag = 'U' ) ( row-lv_data ) ).
IF lt_data IS NOT INITIAL.
UPDATE ztl_po FROM TABLE @lt_data.
ENDIF.
" find all rows in buffer with flag = deleted
lt_data = VALUE #( FOR row IN lcl_buffer=>mt_buffer WHERE ( flag = 'D' ) ( row-lv_data ) ).
IF lt_data IS NOT INITIAL.
DELETE ztl_po FROM TABLE @lt_data.
ENDIF.
CLEAR lcl_buffer=>mt_buffer.
ENDMETHOD.
ENDCLASS.
Now let’s create a BADI to do something with the quantity field in the table while creating and updating.
First we will create an Interface, which is to be used in the BADI Definition Class.
Right click on the package -> NEW -> ABAP Interface
Give Interface a name, Description and be sure to add IF_BADI_INTERFACE in the interfaces section, because this interface is necessary to create BADI definition class / Interface.
Click Next -> Attach it to a Transport Request and Click Finish.
Our Interface should look like :
INTERFACE zbadi_int
PUBLIC .
INTERFACES if_badi_interface .
methods validate .
ENDINTERFACE.
I’m not writing any other code in this interface, because for this example I’m not going to do any extensive coding, this blog is just to demonstrate the creation of BADIs in SAP BTP.
After our code / changes are completed and confirmed, here comes the main and important step. If we or our customers need to use our custom created BADI, it should be released. Not only the BADI Definition, Enhancement spot or Implementation but everything related to the BADI even a data element should be released, and the API state of the object should be set to release without any errors. If we do any changes after the releasing, we should rerelease the object where we have done the change.
Since we don’t have any changes in the Interface we have created, lets release it.
Right click on the Interface -> API State -> Add Use system- Internally
Click both the check Boxes and click next
If everything is fine we should see a screen like :
Click next and complete, everything is released.
If any error is found, then the screen should look like :
It will show the error and API state won’t be released. We will need to correct the error and then follows the last steps and release the Object.
Now lets Create a BADI definition class, where the core logic of the BADI resides.
Since we all know the steps to create an ABAP class in BTP I’m skipping those and I have written the following code in the class.
CLASS zbadi_class DEFINITION
PUBLIC
FINAL
CREATE PUBLIC .
PUBLIC SECTION.
INTERFACES zbadi_int.
class-data quantity type zqty.
PROTECTED SECTION.
PRIVATE SECTION.
ENDCLASS.
CLASS zbadi_class IMPLEMENTATION.
METHOD zbadi_int~validate.
IF quantity > 5000.
quantity = 5000.
ENDIF.
ENDMETHOD.
ENDCLASS.
We have used our interface; the interface should contain the Interface IF_BADI_INTERFACE else we wont be able to use the class for BADI Definition.
Also, I have added a Global Variable, which we will use to pass the quantity value to the BADI.
Implemented the method which we have defined in the Interface.
We will also Release the class in the same way as the Interface.
We have our Badi definitions / our core logic of the BADI, now we will be creating the objects needed for to use our BADI in our code.
First Lets Create an Enhancement Spot:
Right click on the package -> New -.> Other repository Objects -> From the list, Under Enhancements Choose BADI Enhancement Spot
Next -> Provide a name, Description
Next -> Attach it to a Transport Request -> Finish
Our Enhancement spot should look like :
Click on the Add Badi Button -> give a BADI Definition Name -> ADD
Give the Description, BADI Interface name, Configuration settings, and Badi Implementation Example class name
Now we will create our BADI Enhancement Implementation.
Right click on the package -> New -.> Other repository Objects -> From the list, Under Enhancements Choose BADI Enhancement Implementation.
Give a Name, Description, Enhancement Spot Name -> Next -> attach it to a Transport request -> finish
Our BADI Enhancement Implementation should look like :
Click On add BADI -> Give an Implementation Name -> ADD
Give the BADI Implementation Class Name, Description.
Set as Active Implementation and Default Implementation.
After the enhancement implementation is created and activated, lets go to the enhancement spot and release it, like we did for the class and interface.
Now we are ready to use our BADI.
We will add the BADI to our existing behavior implementation class.
CLASS lcl_buffer DEFINITION.
PUBLIC SECTION.
TYPES: BEGIN OF ty_buffer.
INCLUDE TYPE ztl_po AS lv_data.
TYPES: flag TYPE c LENGTH 1,
END OF ty_buffer.
CLASS-DATA mt_buffer TYPE TABLE OF ty_buffer.
ENDCLASS.
CLASS lhc_po DEFINITION INHERITING FROM cl_abap_behavior_handler.
PRIVATE SECTION.
METHODS create FOR MODIFY
IMPORTING entities FOR CREATE zi_po.
METHODS delete FOR MODIFY
IMPORTING keys FOR DELETE zi_po.
METHODS update FOR MODIFY
IMPORTING entities FOR UPDATE zi_po.
METHODS read FOR READ
IMPORTING keys FOR READ zi_po RESULT result.
ENDCLASS.
CLASS lhc_po IMPLEMENTATION.
METHOD create.
DATA lo_badi TYPE REF TO zBADI_DEF . " we are using the BADI Definition name to create object
GET BADI lo_badi.
LOOP AT entities INTO DATA(ls_create).
zbadi_class=>quantity = ls_create-Quantity. "pass the quantity as global variable to BADI class
TRY.
CALL BADI lo_badi->validate.
CATCH cx_ble_runtime_error INTO DATA(lx_ble_runtime_error).
" Handle exception.
ENDTRY.
ls_create-Quantity = zbadi_class=>quantity.
INSERT VALUE #( flag = 'C' lv_data = CORRESPONDING #( ls_create-%data ) ) INTO TABLE lcl_buffer=>mt_buffer.
ENDLOOP.
ENDMETHOD.
METHOD delete.
LOOP AT keys INTO DATA(ls_delete).
READ TABLE lcl_buffer=>mt_buffer
ASSIGNING FIELD-SYMBOL(<ls_buffer>) WITH KEY id = ls_delete-id.
IF sy-subrc = 0.
" already in buffer, check why
IF <ls_buffer>-flag = 'C'.
"delete after create => just remove from buffer
DELETE TABLE lcl_buffer=>mt_buffer FROM <ls_buffer>.
ELSE.
<ls_buffer>-flag = 'D'.
ENDIF.
ELSE.
" not yet in buffer.
INSERT VALUE #( flag = 'D' id = ls_delete-id ) INTO TABLE lcl_buffer=>mt_buffer.
ENDIF.
ENDLOOP.
ENDMETHOD.
METHOD update.
DATA lo_badi TYPE REF TO zBADI_DEF . " we are using the BADI Definition name to create object
LOOP AT entities INTO DATA(ls_update).
READ TABLE lcl_buffer=>mt_buffer ASSIGNING FIELD-SYMBOL(<ls_buffer>) WITH KEY id = ls_update-id .
IF sy-subrc <> 0.
" not yet in buffer, read from table
SELECT SINGLE * FROM ztl_po WHERE id = @ls_update-id INTO @DATA(ls_db).
INSERT VALUE #( flag = 'U' lv_data = ls_db ) INTO TABLE lcl_buffer=>mt_buffer ASSIGNING <ls_buffer>.
ENDIF.
IF ls_update-%control-id IS NOT INITIAL.. <ls_buffer>-id = ls_update-id. ENDIF.
IF ls_update-%control-PurchaseDate IS NOT INITIAL.. <ls_buffer>-Purchase_Date = ls_update-PurchaseDate. ENDIF.
IF ls_update-%control-amount IS NOT INITIAL.. <ls_buffer>-amount = ls_update-amount. ENDIF.
IF ls_update-%control-quantity IS NOT INITIAL.
zbadi_class=>quantity = ls_update-Quantity. "pass the quantity as global variable to BADI class
TRY.
CALL BADI lo_badi->validate.
CATCH cx_ble_runtime_error INTO DATA(lx_ble_runtime_error).
" Handle exception.
ENDTRY.
ls_update-Quantity = zbadi_class=>quantity.
<ls_buffer>-quantity = ls_update-quantity.
ENDIF.
IF ls_update-%control-remarks IS NOT INITIAL.. <ls_buffer>-remarks = ls_update-remarks. ENDIF.
ENDLOOP.
ENDMETHOD.
METHOD read.
ENDMETHOD.
ENDCLASS.
CLASS lsc_i_sb_thirdpar DEFINITION INHERITING FROM cl_abap_behavior_saver.
PROTECTED SECTION.
METHODS check_before_save REDEFINITION.
METHODS finalize REDEFINITION.
METHODS save REDEFINITION.
ENDCLASS.
CLASS lsc_i_sb_thirdpar IMPLEMENTATION.
METHOD check_before_save.
ENDMETHOD.
METHOD finalize.
ENDMETHOD.
METHOD save.
DATA lt_data TYPE STANDARD TABLE OF ztl_po.
" find all rows in buffer with flag = created
lt_data = VALUE #( FOR row IN lcl_buffer=>mt_buffer WHERE ( flag = 'C' ) ( row-lv_data ) ).
IF lt_data IS NOT INITIAL.
INSERT ztl_po FROM TABLE @lt_data.
ENDIF.
" find all rows in buffer with flag = updated
lt_data = VALUE #( FOR row IN lcl_buffer=>mt_buffer WHERE ( flag = 'U' ) ( row-lv_data ) ).
IF lt_data IS NOT INITIAL.
UPDATE ztl_po FROM TABLE @lt_data.
ENDIF.
" find all rows in buffer with flag = deleted
lt_data = VALUE #( FOR row IN lcl_buffer=>mt_buffer WHERE ( flag = 'D' ) ( row-lv_data ) ).
IF lt_data IS NOT INITIAL.
DELETE ztl_po FROM TABLE @lt_data.
ENDIF.
CLEAR lcl_buffer=>mt_buffer.
ENDMETHOD.
ENDCLASS.
NB: I have written this BLOG Based on My research and development on a BTP trial account, I haven’t used custom BADIS on a business scenario.