Dear SAPers !
With this post, I’m starting a new series of blog posts on topic around enhancements in electronic bank statement processing. I hope it will be interesting for you.
SAP offers a powerful solution for electronic bank statement processing. This solution includes three main components:
In this blog post, I’ll focus on the use of BADI FIEB_CHANGE_BS_DATA. This BADI is triggered during interpretation of bank statement. It is called both – during upload and initial processing of bank statement as well as during post-processing (e.g., via GUI transaction FEB_BSPROC or Fiori App F1520 “Reprocess Bank Statement Items”).
I’ll explain the interface of the BADI, the possibilities provided by this enhancement spot and share some tips & recommendations based on my previous experience. These findings are applicable to both SAP ECC as well as SAP S/4 HANA systems (on-premises).
Let’s walk through the BADI attributes in transaction SE18. As you can see, BADI FIEB_CHANGE_BS_DATA is a single-use BADI i.e., it has no filter and is called only once. This has an important implication. In my practice, this BADI is used very often to address custom requirements across many countries & many banks. Therefore, proper architecture should be put into place to separate the implementation logic for various competing business requirements, simplify the maintenance of the logic, and minimize the regression impact.
This BADI implements the interface IF_EX_FIEB_CHANGE_BS_DATA, which has one method CHANGE_DATE:
Screenshot below shows the signature for this method i.e., the list of method parameters & their typing:
The meaning behind these parameters is as follows:
These are four main parameters, which you can use to implement your custom logic. The data in these parameters corresponds to the data in standard SAP tables FEBRE, FEBKO, FEBEP and FEBCL. You can change all parameters except the table with note to payee. This is the only limitation within this BADI.
I’m not 100% sure what is the purpose behind parameter I_TESTRUN. I never had a chance or a need to use it before. You can use other parameters if you want to issue a custom error message during bank statement processing.
Before the program calls this BADI, the underlying program for bank statement processing (e.g., RFEKA400 for MT940) has already split the bank statement into lines, parsed them and saved the data into the tables listed above.
The tables are linked between themselves via the fields KUKEY and ESNUM that represent the internal key for bank statement header and line item respectively:
As I mentioned earlier, this BADI is called during interpretation stage of the bank statement. To be more exact, it is called before standard interpretation of line items in accordance with interpretation mechanisms begins. Standard interpretation mechanisms for bank statement are defined in transaction OT83 (table T028G). Standard interpretation and search string processing happens immediately after this BADI and in some cases might overwrite your logic.
Interpretation of line item is executed during upload / initial processing of bank statement (both via GUI Transaction FF_5 or Fiori App F1680 “Manage Incoming Payment Files”). The BADI is triggered during post-processing each time when you press the button “Scan” in the transaction FEB_BSPROC:
If you’re post-processing bank statement items via Fiori App F1520 “Reprocess Bank Statement Items”, BADI is triggered when you press the button “Allocate Open Items”:
You might also notice that there is another alternative for this BADI namely SMOD-enhancement FEB00001, which implements user exit EXIT_RFEBBU10_001, include ZXF01U01:
The screenshot below shows the interface of this user exit. As you can see, it is almost the same, which one difference: parameter T_FEBRE is defined as such that can be changed. As I already mentioned before, is not possible to change this parameter in BADI.
User exit EXIT_RFEBBU10_001 was the first enhancement point introduced by SAP that allowed the customers to implement their own logic before standard interpretation mechanisms kick-in. Later, SAP introduced the definition of BADI FIEB_CHANGE_BS_DATA as a more modern enhancement approach.
I worked with the user exit EXIT_RFEBBU10_001 on one of my previous projects. There were a lot of legacy enhancements in this spot. The company introduced separate includes per country to structure the code. But it was not overly reliable / efficient, because there were a lot of global objects and conflicts between different countries. So eventually, I had to define a global class which encapsulated all logic and called it from the EXIT_RFEBBU10_001.
My personal preference is to use BADI. The main benefit of the BADI is that we have much better control over the enhancement due to the object-oriented design of this enhancement spot.
Technically speaking you might have implementation for both enhancement spots in your system. If that’s the case, the processing sequence is as follows: BADI → SMOD User Exit.
As I mentioned before BADI FIEB_CHANGE_BS_DATA is a single-use BADI, which is a certain limitation from development point of view. But if you care about the architecture & design of your solution, it is easy to overcome this limitation. My proposal is to use BADI implementing class for two main purposes implementing:
In my experience, country level is enough to separate the business logic, because business requirements for company codes in one country will be similar across banks. A simplified UML diagram below shows the relationship between BADI implementing class and global classes that implement the specific per country:
For the purposes of this post, I’ve created a BADI implementing class ZCL_IM_FIEB_CHANGE_BS_DATA and a global class ZCL_UA_FIEB_CHANGE_BS_DATA that implements logic for Ukraine.
As I hinted before, the global class might implement some business logic that is not dependent on a country or company code. For example, you might want to implement a custom configuration table, where you’ll maintain a mapping between specific text patterns and posting rules for bank statements. This mapping can be defined on the house bank account level, therefore it can be implemented globally and executed only once.
If you’re familiar with the functionality of search strings in SAP, you might ask, why do we need such a table? It is a valid question, and, in some cases, it would be easier to implement a search string. But the functionality of search strings has its own limitations with regards to REGEX-usage and in general is not very easy to implement / troubleshoot.
Another common requirement has a technical nature and deals with the way a note to payee is stored. It is stored in the table FEBRE and is split into several lines. One of the common ways to enhance the interpretation of bank statement is to analyze the text of note to payee and search for some patterns or details. Therefore, it is a good idea to transfer the table to a string. Along the way you can implement some additional logic e.g., remove some special characters, or covert the case (to uppercase or lowercase).
I provide a sample source code for a global class below. As you can see, it has two additional private methods: PREPARE_NOTE_TO_PAYEE and GET_COUNTRY_CODE. The first method transfers the note to payee from a table to a string-representation. The second method fetches the country code associated with the company code. Both methods are called from the standard method CHANGE_DATE. A case-statement is used as a router to call specific classes. In this case, global class will call country-specific logic for Ukraine i.e., ZCL_UA_FIEB_CHANGE_BS_DATA.
class zcl_im_fieb_change_bs_data definition
public
final
create public .
public section.
interfaces if_ex_fieb_change_bs_data .
protected section.
private section.
class-methods get_country_code
importing
!is_febko type febko
returning
value(rv_land) type land1 .
class-methods prepare_note_to_payee
importing
!it_febre type any table
returning
value(rv_note_to_payee) type string .
endclass.
class zcl_im_fieb_change_bs_data implementation.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Static Private Method ZCL_IM_FIEB_CHANGE_BS_DATA=>GET_COUNTRY_CODE
* +-------------------------------------------------------------------------------------------------+
* | [--->] IS_FEBKO TYPE FEBKO
* | [<-()] RV_LAND TYPE LAND1
* +--------------------------------------------------------------------------------------</SIGNATURE>
method get_country_code.
select single land1
from t001
into rv_land
where bukrs = is_febko-bukrs.
endmethod.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Public Method ZCL_IM_FIEB_CHANGE_BS_DATA->IF_EX_FIEB_CHANGE_BS_DATA~CHANGE_DATA
* +-------------------------------------------------------------------------------------------------+
* | [--->] I_TESTRUN TYPE XFELD
* | [--->] T_FEBRE TYPE STANDARD TABLE
* | [<---] E_SUBRC TYPE SY-SUBRC
* | [<---] E_MSGID TYPE SY-MSGID
* | [<---] E_MSGTY TYPE SY-MSGTY
* | [<---] E_MSGNO TYPE SY-MSGNO
* | [<---] E_MSGV1 TYPE SY-MSGV1
* | [<---] E_MSGV2 TYPE SY-MSGV2
* | [<---] E_MSGV3 TYPE SY-MSGV3
* | [<---] E_MSGV4 TYPE SY-MSGV4
* | [<-->] C_FEBKO TYPE FEBKO
* | [<-->] C_FEBEP TYPE FEBEP
* | [<-->] T_FEBCL TYPE STANDARD TABLE
* +--------------------------------------------------------------------------------------</SIGNATURE>
method if_ex_fieb_change_bs_data~change_data.
data lv_note_to_payee type string.
lv_note_to_payee = prepare_note_to_payee( t_febre ).
" Some global logic can be applied here
" Example: adjustment of posting rules based on Z-configuration table
data lv_country type land1.
lv_country = get_country_code( c_febko ).
" Initiate country specific logic
case lv_country.
when 'UA'.
zcl_ua_fieb_change_bs_data=>process_line(
exporting
iv_note_to_payee = lv_note_to_payee
changing
cs_febko = c_febko
cs_febep = c_febep
ct_febcl = t_febcl ).
when others.
" Do nothing
endcase.
endmethod.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Static Private Method ZCL_IM_FIEB_CHANGE_BS_DATA=>PREPARE_NOTE_TO_PAYEE
* +-------------------------------------------------------------------------------------------------+
* | [--->] IT_FEBRE TYPE ANY TABLE
* | [<-()] RV_NOTE_TO_PAYEE TYPE STRING
* +--------------------------------------------------------------------------------------</SIGNATURE>
method prepare_note_to_payee.
field-symbols <line> type febre.
" Prepare string representation of note to payee
loop at it_febre assigning <line>.
if <line> is not assigned.
continue.
endif.
concatenate rv_note_to_payee <line>-vwezw into rv_note_to_payee.
endloop.
condense rv_note_to_payee.
" Additional logic can be added here
" Transfer to upper / lower case, removal of special characters etc.
endmethod.
endclass.
I provide a sample source code for a country-specific class below. So far it is just a backbone of the global class that shows how it is defined and has several global constants. I’ll use this backbone and will provide additional details along the way.
class zcl_ua_fieb_change_bs_data definition
public
final
create public .
public section.
types:
tt_febcl type standard table of febcl with default key .
class-methods process_line
importing
!iv_note_to_payee type string
changing
!cs_febko type febko
!cs_febep type febep
!ct_febcl type standard table .
protected section.
private section.
constants:
begin of c_clear_by,
ref_number type febcl-selfd value 'XBLNR' ##NO_TEXT,
doc_number type febcl-selfd value 'BELNR' ##NO_TEXT,
paym_order type febcl-selfd value 'PYORD' ##NO_TEXT,
doc_amount type febcl-selfd value 'WRBTR' ##NO_TEXT,
end of c_clear_by.
constants:
begin of c_account_type,
customer type koart value 'D' ##NO_TEXT,
vendor type koart value 'K' ##NO_TEXT,
gl_account type koart value 'S' ##NO_TEXT,
end of c_account_type.
constants:
begin of c_operation_type,
inc_payment type shkzg value 'H' ##NO_TEXT,
out_payment type shkzg value 'S' ##NO_TEXT,
end of c_operation_type.
endclass.
class zcl_ua_fieb_change_bs_data implementation.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Static Public Method ZCL_UA_FIEB_CHANGE_BS_DATA=>PROCESS_LINE
* +-------------------------------------------------------------------------------------------------+
* | [--->] IV_NOTE_TO_PAYEE TYPE STRING
* | [<-->] CS_FEBKO TYPE FEBKO
* | [<-->] CS_FEBEP TYPE FEBEP
* | [<-->] CT_FEBCL TYPE STANDARD TABLE
* +--------------------------------------------------------------------------------------</SIGNATURE>
method process_line.
endmethod.
endclass.
There are several common use cases for this BADI. Let’s review them one by one.
Identification of business partner
Ideally speaking the purpose of the bank statement interpretation (especially for incoming payments) is to find an open item representing the sales invoice, for which the customer paid. If this reference is found, you do not really need to bother about the identification of the business partner. But what happens, if the system is not able to identify the open item or the business partner? In these cases, you can use this BADI to find a business partner.
There are several approaches: you can analyze the bank details of the business partner. Relevant fields in the parameter FEBEP are: PABKS, PABLZ, PASWI, PAKTO, PARTN & PIBAN.
Alternatively, you can analyze the content of the note to payee and check if there are any details that allow you to identify the business partner. In one case, I was working with the bank statement, where the bank provided the tax number of the business partner for each payment. My requirement was to identify this tax number and query the tables LFA1/KNA1 to identify the partner.
One more alternative is to search for a business partner based on the reference information provided in the note to payee (i.e., based on sales invoice or pro-forma invoice number).
Once a business partner is known, you can update it in the parameter FEBEP. Two fields are used for this purpose: AVKOA – account type, AVKON – account number. A sample source code below provides some hints on how to implement the logic:
" Definition of the methods
class-methods identify_business_partner
importing
iv_note type string
is_febko type febko
changing
cs_febep type febep.
class-methods is_bp_known
importing
is_febep type febep
returning
value(rv_yes) type abap_bool.
class-methods get_customer_number
importing
iv_note type string
is_febko type febko
is_febep type febep
returning
value(rv_kunnr) type kunnr .
class-methods get_vendor_number
importing
iv_note type string
is_febko type febko
is_febep type febep
returning
value(rv_lifnr) type lifnr .
" Implementation of the methods
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Static Private Method ZCL_UA_FIEB_CHANGE_BS_DATA=>GET_CUSTOMER_NUMBER
* +-------------------------------------------------------------------------------------------------+
* | [--->] IV_NOTE TYPE STRING
* | [--->] IS_FEBKO TYPE FEBKO
* | [--->] IS_FEBEP TYPE FEBEP
* | [<-()] RV_KUNNR TYPE KUNNR
* +--------------------------------------------------------------------------------------</SIGNATURE>
method get_customer_number.
" Your own logic here
endmethod.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Static Private Method ZCL_UA_FIEB_CHANGE_BS_DATA=>GET_VENDOR_NUMBER
* +-------------------------------------------------------------------------------------------------+
* | [--->] IV_NOTE TYPE STRING
* | [--->] IS_FEBKO TYPE FEBKO
* | [--->] IS_FEBEP TYPE FEBEP
* | [<-()] RV_LIFNR TYPE LIFNR
* +--------------------------------------------------------------------------------------</SIGNATURE>
method get_vendor_number.
" Your own logic here
endmethod.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Static Private Method ZCL_UA_FIEB_CHANGE_BS_DATA=>IDENTIFY_BUSINESS_PARTNER
* +-------------------------------------------------------------------------------------------------+
* | [--->] IV_NOTE TYPE STRING
* | [--->] IS_FEBKO TYPE FEBKO
* | [<-->] CS_FEBEP TYPE FEBEP
* +--------------------------------------------------------------------------------------</SIGNATURE>
method identify_business_partner.
if is_bp_known( cs_febep ) = abap_true.
return.
endif.
" Determination of business partner based on payment type
if cs_febep-epvoz = c_operation_type-inc_payment.
data lv_kunnr type kunnr.
lv_kunnr = get_customer_number(
iv_note = iv_note
is_febko = is_febko
is_febep = cs_febep ).
if lv_kunnr <> ''.
cs_febep-avkoa = c_account_type-customer.
cs_febep-avkon = lv_kunnr.
endif.
elseif cs_febep-epvoz = c_operation_type-out_payment.
data lv_lifnr type lifnr.
lv_lifnr = get_vendor_number(
iv_note = iv_note
is_febko = is_febko
is_febep = cs_febep ).
if lv_lifnr <> ''.
cs_febep-avkoa = c_account_type-vendor.
cs_febep-avkon = lv_lifnr.
endif.
else.
" Should not reach this stage
endif.
endmethod.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Static Private Method ZCL_UA_FIEB_CHANGE_BS_DATA=>IS_BP_KNOWN
* +-------------------------------------------------------------------------------------------------+
* | [--->] IS_FEBEP TYPE FEBEP
* | [<-()] RV_YES TYPE ABAP_BOOL
* +--------------------------------------------------------------------------------------</SIGNATURE>
method is_bp_known.
if is_febep-avkon <> '' and is_febep-avkoa <> ''.
rv_yes = abap_true.
endif.
endmethod.
" Call of the method from the main method in local class
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Static Public Method ZCL_UA_FIEB_CHANGE_BS_DATA=>PROCESS_LINE
* +-------------------------------------------------------------------------------------------------+
* | [--->] IV_NOTE_TO_PAYEE TYPE STRING
* | [<-->] CS_FEBKO TYPE FEBKO
* | [<-->] CS_FEBEP TYPE FEBEP
* | [<-->] CT_FEBCL TYPE STANDARD TABLE
* +--------------------------------------------------------------------------------------</SIGNATURE>
method process_line.
identify_business_partner(
exporting
iv_note = iv_note_to_payee
is_febko = cs_febko
changing
cs_febep = cs_febep ).
endmethod.
I propose using a small utility method IS_BP_KNOWN to check if the business partner was already found before. This might be useful in post-processing mode because you no longer need to search for a business partner if it is already known.
Transactional details
You can use the BADI to update a range of attributes that will be passed down to accounting document level. The list of these details includes the following fields in the parameter FEBEP:
Change of the posting rule
Posting rule for a line item is stored in the field FEBEP-VGINT. You can use this field to adjust the determination of the posting rules depending on the business scenario. A typical business case from my practice. Incoming payments in Ukraine should be analyzed and processed differently depending on the fact if it is a down-payment or a post payment. Down-payments are posted with VAT on so called gross-basis and require assignment of the tax code and ideally speaking a reference to sales order or pro-forma invoice (i.e., local VAT requirements). Down-payments generate a new open item on the customer account and no clearing is required (i.e., because the invoice is not even posted yet). Whereas post payment should automatically clear the existing invoice. You can easily define two separate posting rules for these cases, but you can only assign one of them in the mapping, because the bank provides only one BTC-code for customer payments. So, what can you do in this case?
My solution is as follows: I’ve used the BADI to identify the customer, then I analyzed the list of open items associated with the customer via FM BAPI_AR_ACC_GETOPENITEMS. Depending on the current balance of these open items, I decided about the payment type and changed the posting rule accordingly.
Parsing for reference numbers
Note to payee in the bank statement might contain a lot of useful information. Main problem is that sometimes it is not easy to extract this information due to various technical or business-related issues. Let’s suppose your company is issuing sales invoices with document numbers in the range of 9100000000-9199999999. Some customers might pay several invoices at a time and provide details as follows: “payment for invoices 9100900901, *900902 & *900903 from 01.01.2023”. In this case, there are three invoice numbers in the payment details, but standard SAP will be able to recognize one the first invoice number. I’ve encountered such a case on one of my previous projects, where many customers provided only the last 4-3 invoice numbers on a regular basis, and I managed to automate the search and assignment of open items for incoming payments of this kind.
Alternatively, reference numbers can be impacted by various technical issues i.e., they can be truncated or split into several lines during upload. For example, invoice number 9100900901 might be stored in the database table as 9100 900901. Extra space between the digits turns recognizable invoice number into two numbers that cannot be recognized.
Therefore, a common requirement is to implement custom logic that will search for these reference numbers more efficiently. Common references numbers are:
Update of clearing information
Once you interpreted the note to payee and extracted some useful reference numbers, you have to update the clearing details (if relevant for a posting rule). Clearing details are stored in the table FEBCL and can be updated via the parameter T_FEBCL.
Let’s return to our case with payment details “9100900901, *900902 & *900903”. If you managed to extract all three invoice numbers and you checked that these accounting document numbers exist in the system, you can fill the table T_FEBCL as described below. If the amount of the payment equals the total amount of open items across these three invoices, all three invoices will be automatically cleared.
Automatic clearing is also possible if payment amount is less than total amount of invoices, but it assumes that the difference is within tolerance limit (i.e., case of underpayment) or in line with the conditions for early payment discounts as defined in the payment terms.
What is of importance here:
Special note: if you use clearing by accounting document numbers i.e., BELNR, you can provide a document number 9100900901 only or a full reference to a line item 91009009012023001 (concatenation of document number, fiscal year & line ID).
Additional notes below provide some recommendations about update of the clearing details.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Static Public Method ZCL_UA_FIEB_CHANGE_BS_DATA=>PROCESS_LINE
* +-------------------------------------------------------------------------------------------------+
* | [--->] IV_NOTE_TO_PAYEE TYPE STRING
* | [<-->] CS_FEBKO TYPE FEBKO
* | [<-->] CS_FEBEP TYPE FEBEP
* | [<-->] CT_FEBCL TYPE STANDARD TABLE
* +--------------------------------------------------------------------------------------</SIGNATURE>
method process_line.
" Sample code to update clearing details
data: lv_xblnr type xblnr.
data: lv_belnr type belnr.
data: lv_pyord type pyord.
data: lt_febcl type tt_febcl,
ls_febcl like line of lt_febcl.
" Fill common clearing parameters
ls_febcl-kukey = cs_febep-kukey.
ls_febcl-esnum = cs_febep-esnum.
ls_febcl-csnum = '1'.
ls_febcl-koart = cs_febep-avkoa.
ls_febcl-agkon = cs_febep-avkon.
" Optional parameter
ls_febcl-agums = 'X'. " SGL-indicator can be passed if you want to clear against SGL-items
" Specific parameters
" You can clear by reference numbers, or
ls_febcl-selfd = c_clear_by-ref_number.
ls_febcl-selvon = lv_xblnr.
" You can clear by accounting document numbers, or
ls_febcl-selfd = c_clear_by-doc_number.
ls_febcl-selvon = lv_belnr.
" You can clear by payment order number, or
ls_febcl-selfd = c_clear_by-paym_order.
ls_febcl-selvon = lv_pyord.
" You can clear by amount
ls_febcl-selfd = c_clear_by-doc_amount.
ls_febcl-selvon = cs_febep-kwbtr.
append ls_febcl to ct_febcl.
endmethod.
Other specific business cases
As part of this series, I plan to publish a couple of other follow-up blog posts, that will describe several other topics, which are related to this enhancement spot. As of now, I have at least 4 topics in my action list. The details will be updated here.
I hope that this post provided some useful insights and it was not too boring. I did not provide a lot in terms of actual coding. The purpose of this post is not to solve specific issues, but rather to explain the enhancement spot, its purpose and potential use cases. On the other hand, this is an enhancement spot. As such it is should be used to implement customer-specific logic. So it would be up to you to decide what that should should be and how it should be implemented. I hope that this post would be a good guidance if you are looking to improve your processes around electronic bank statement processing.
I’m looking forward to your comments and questions. I would be happy to address / answer them.
Regards,
Bohdan