FIN (Finance), ABAP Development

Enhancements in EBS – BADI FIEB_CHANGE_BS_DATA

Introduction

SAP offers a powerful solution for electronic bank statement processing. This solution includes three main components:

  • A range of standard programs for upload and processing of bank statements in various formats (MT940, Multicash, BAI, CAM053 etc.).
  • A variety of configuration options (account symbols, posting rules, transaction types, return processing rules etc.), which are used by these standard programs.
  • A range of enhancement options that can be implemented to further enhance parsing of raw bank statements, their processing / interpretation and posting.

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).

Overview of the BADI attributes

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:

  • T_FEBRE – table with note to payee data (i.e., information about payment details & purpose).
  • C_FEBKO – header of bank statement.
  • C_FEBEP – line item of bank statement.
  • T_FEBCL – clearing details for line item of the bank statement.

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.

Use of the BADI

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”:

Short historical perspective

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.

Enhancement architecture

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:

  • global logic – that can be used across countries / banks.
  • router logic – that can be used to re-route processing logic to other global classes responsible for the requirements for specific countries.

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.

Design of the country specific class

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.

Typical use cases

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:

  • XBLNR – reference number.
  • ZUONR – assignment number.
  • KOSTL – cost center.
  • GSBER – business area.
  • PRCTR – profit center.
  • SGTXT – line-item text.

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:

  • Accounting document numbers (BKPF-BELNR) – customer invoices or down-payment requests.
  • Reference numbers (BFKP-XBLNR) – most commonly billing document numbers for sales invoices.
  • Proforma invoice numbers (VBRK-VBELN of VBAK-VBELN) – based on billing documents or sales orders.
  • Sales contract numbers (VBAK-VBELN) – based on sales contract numbers.
  • Payment document numbers or payment orders (REGUH-PYORD) – for outgoing payments etc.

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:

  • CSNUM – is a counter that shows how many lines with clearing details are available.
  • KOART / AGKON – represent account type and account number.
  • SELFD – is a selection field that will be used as a criterion during selection of open items for clearing.
  • SELVON / SELBIS – store the values for the criteria in accordance with its type. You can indicate one value (SELVON) or a range of values (SELVON to SELBIS).

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.
Rating: 0 / 5 (0 votes)