Custom Tabs for Financial Transaction Maintenance (FTR_EDIT)
In case you want to have custom specific data displayed for your financial transactions you can do so by implementing the SAP standard BAdI FTR_CUSTOMER_EXTENT.
This standard BAdI allows you to define up to two custom tabs and to handle all kind of derivations, checks and alike within them. It is executed when maintaining financial transactions via the GUI transaction as well as when creating financial transactions via calling the BAPIs.
When used you will have additional tabs when maintaining your financial transaction similar to what you see in the screenshot below.
However the standard BAdI also comes with a restriction. That restriction being that the BAdI can only be implemented once and with that you have the limitation of not being able to differentiate e.g. on a product type level which implementation to use.
This restriction might limit you especially in cases where you have different business stakeholders with their own set on requirements concerning the additional tabs. If all of the implementation is done within the same class you might find yourself in transport dependencies between those departments and have to try to seperate the different requirements as good as possible.
With this blog entry I will show you a way how you can get rid of that restriction and have different custom tabs and logic on the level you require it.
For doing so an approach I like is to implement a proxy of the FTR_CUSTOMER_EXTENT BAdI that has an attribute that refers to an object that implements IF_EX_FTR_CUSTOMER_EXTENT. This is the Interface defining the methods for the BAdI and is also implemented by my proxy implementation. The actual implementation of the interface is then obtained via a factory class.
The high level class diagram for that approach can be seen in below diagram.
To get the actual object I request from the factory an object in the proxy implementation of method EVT_APPLICATION_START of my BAdI implementation.
All other methods of my BAdI implementation will only forward the call to the respective method of the actual implementation returned by the factory.
Meaning when e.g. method EVT_TRANSACTION_SAVE_CHECK is executed from the BAdI implementation it will forward the call directly to the object that was earlier received from the factory.
That way I have now in each individual class implementing the interface IF_EX_FTR_CUSTOMER_EXTENT the possibility to freely define the logic that should be executed. Including which function module and function group to use for displaying custom tabs.
The factory has now the responsibility to make sure that always the correct concrete instantiation is returned. Usually in my implementations the factory will read a customizing table that allows me to map product type, transaction type and classname of the class implementing IF_EX_FTR_CUSTOMER_EXTENT. In case no suitable implementation is found for a given combination of product type and transaction type I use the null object implementation as a fallback. That fallback implementation contains all empty methods and has the sole purpose of making sure the system is not dumping under any circumstances.
Let me show you some screenshots and code snippets from an example implementation.
Implementation of method EVT_APPLICATION_START of my proxy class
METHOD if_ex_ftr_customer_extent~evt_application_start. IF concr_badi_implementation IS NOT BOUND. concr_badi_implementation = /cobi/fce_cl_impl_factory=>get_instance( )->get_instance_for_transaction( pi_proxy_transaction ). ENDIF. concr_badi_implementation->evt_application_start( EXPORTING pi_proxy_transaction = pi_proxy_transaction pi_proxy_messages = pi_proxy_messages pi_proxy_fcode = pi_proxy_fcode pi_proxy_fmod = pi_proxy_fmod pi_cust_transaction = pi_cust_transaction CHANGING pc_tab_badi_tabs = pc_tab_badi_tabs ). ENDMETHOD.
Implementation of method EVT_TRANSACTION_SAVE_CHECK forwarding the call to the actual implementation
METHOD if_ex_ftr_customer_extent~evt_transaction_save_check. concr_badi_implementation->evt_transaction_save_check( pi_proxy_transaction = pi_proxy_transaction pi_proxy_messages = pi_proxy_messages pi_cust_transaction = pi_cust_transaction ). ENDMETHOD.
Customizing Table used for mapping product category, product type, transaction type and company code to the actual implementation
Method GET_INSTANCE_FOR_TRANSACTION of factory. The select statement also handles initial values for key fields, in case you want a default implementation for certain cases.
METHOD get_instance_for_transaction. SELECT product_category, product_type, transaction_type, company_code, classname FROM /cobi/fce_cfil INTO TABLE @DATA(found_entries) WHERE ( product_category = @proxy_deal_data->a_transaction_ori-sanlf AND product_type = @proxy_deal_data->a_transaction_ori-sgsart AND transaction_type = @proxy_deal_data->a_transaction_ori-sfhaart AND company_code = @proxy_deal_data->a_transaction_ori-bukrs ) OR ( product_category = @proxy_deal_data->a_transaction_ori-sanlf AND product_type = @proxy_deal_data->a_transaction_ori-sgsart AND transaction_type = @proxy_deal_data->a_transaction_ori-sfhaart AND company_code = '' ) OR ( product_category = @proxy_deal_data->a_transaction_ori-sanlf AND product_type = @proxy_deal_data->a_transaction_ori-sgsart AND transaction_type = '' AND company_code = '' ) OR ( product_category = @proxy_deal_data->a_transaction_ori-sanlf AND product_type = '' AND transaction_type = '' AND company_code = '' ) OR ( product_category = '' AND product_type = '' AND transaction_type = '' AND company_code = '' ) ORDER BY product_category DESCENDING, product_type DESCENDING, transaction_type DESCENDING, company_code DESCENDING. IF lines( found_entries ) > 0. DATA(entry) = found_entries[ 1 ]. cl_abap_classdescr=>describe_by_name( EXPORTING p_name = entry-classname RECEIVING p_descr_ref = DATA(type_descriptor) EXCEPTIONS type_not_found = 1 OTHERS = 2 ). IF sy-subrc = 0. DATA potential_badi_impl TYPE REF TO object. CREATE OBJECT potential_badi_impl TYPE (entry-classname). IF potential_badi_impl IS INSTANCE OF if_ex_ftr_customer_extent. instance ?= potential_badi_impl. RETURN. ENDIF. ENDIF. ENDIF. "" If this line is reached customizing was not or wrongly maintained, therefore use null object to have system act as no BAdI is implemented instance = NEW /cobi/fce_cl_cust_Ext_null( ). ENDMETHOD.
So my approach for solving the issue of standard allowing us only one implementation for the BAdI is solved by a Proxy implementation forwarding the calls to the specific implementation and with that implementing a filter by myself.
Have you faced that kind of requirement in the past and if so, how did you solve it? What do you think about the described approach? Let me know your thoughts!