Skip to Content
Technical Articles
Author's profile photo Elson Meço

OOP Report to clear open items by simulating FB05 transaction

I am writing this blog post to demonstrate how we can structure an ABAP report in a OOP way that helps us solve a real life problem. I want to state firstly that I don’t think that my example entails all the OOP best practices, but it’s a honest attempt of a procedural ABAPer to make the switch to the world of object oriented.

The request was to create a report that helped to clear open items of a business partner with one another. The business partner was configured in the system with both the roles of Customer and Vendor. So the purpose of our program is to find if for a business partner accounting entries both as a customer and a vendor have been generated and if the total amount of this entries is zero. So you can understand that if we find such entries we have to clear them by executing the transaction code FB05.

The accounting entries had to be cleared according to one of the following criteria:

  • ZUONR – Assignment
  • XREF2 – Ref. Key 2
  • XBLNR – Reference

The image below shows the selection screen of our custom program:

I have organized my code in a fashion similar to the MCV Object Pattern and I say similar, because my three classes don’t do exactly what they are supposed to do if we follow strictly this pattern.

So I have created this three classes to help with the logic of the program:

  1. lcl_read_data –> Reads open items for the vendors and customers and saves the data read in the instance attributes mt_bsik (Vendors) and mt_bsid (Customers)
  2. lcl_process_data –> Uses the previous class as an instance attribute to have access to customer and vendor data. The class then combines the data from this two sources and identifies which lines can be cleared with one another. At last if the user hasn’t executed a test run clears these identified items through the transaction code FB05. After all the processing the data is saved in the instance attribute mt_total.
  3. lcl_display_data –> Uses the class lcl_process_data as an instance attribute to have access to the processed data and display it in an ALV.

I have created the main object ZFI_PAREGGIO_CLNT_FORN and some Includes to modularize the program. Below the code of this object:

&----------------------------------------------------------------------*
*& Report ZPAREGGIO_CLNT_FORN
*&---------------------------------------------------------------------*
*&
*&---------------------------------------------------------------------*
REPORT zfi_pareggio_clnt_forn.

INCLUDE zfi_pareggio_clnt_forn_top.  "Global declarations
INCLUDE zfi_pareggio_clnt_forn_sel.  "SELECTION SCREEN definition
INCLUDE zfi_pareggio_clnt_forn_def.  "Class definition
INCLUDE zfi_pareggio_clnt_forn_imp.  "Class implementation
INCLUDE zfi_pareggio_clnt_forn_form. "Form to handle hotspot click

*----------------------------------------------------------------------*
* INITIALIZATION Event
*----------------------------------------------------------------------*
INITIALIZATION.
  DATA(gt_list) = VALUE vrm_values(
    ( key = 'ZUONR' text = TEXT-t01 )
    ( key = 'XREF2' text = TEXT-t02 )
    ( key = 'XBLNR' text = TEXT-t03 ) ).

  CALL FUNCTION 'VRM_SET_VALUES'
    EXPORTING
      id              = 'Z_PAR'
      values          = gt_list
    EXCEPTIONS
      id_illegal_name = 1
      OTHERS          = 2.

  "Object to read data from database
  go_read_data = NEW #( ).

  "Object to prepare data that with be displayed in ALV
  go_process_data = NEW #( io_read_data = go_read_data ).

  "Object to display the final output of the report
  go_disp_data = NEW #( io_process_data = go_process_data ).

*----------------------------------------------------------------------*
* START-OF-SELECTION Event
*----------------------------------------------------------------------*
START-OF-SELECTION.
  go_read_data->read_data( ).
  go_process_data->process_data( ).

*----------------------------------------------------------------------*
* END-OF-SELECTION Event
*----------------------------------------------------------------------*
END-OF-SELECTION.
  go_disp_data->display_data( ).

I will not get into very much details, because I can proudly say it speaks for itself (but anyway don’t hesitate to ask about this piece of code and all that will follow).

Next is the declaration of the global variables that I’ve tried to avoid as much as possible:

&---------------------------------------------------------------------*
*& Include ZFI_PAREGGIO_CLNT_FORN_TOP
*&---------------------------------------------------------------------*
TYPES: BEGIN OF ty_bp,
         bp TYPE char10,
       END OF ty_bp.
DATA wa_bp TYPE ty_bp. "Business Partner structure

DATA wa_bkpf TYPE bkpf.

CONSTANTS c_text TYPE bkpf-bktxt VALUE 'Eq. rev. charg. VAT ent.'.

CLASS: lcl_read_data    DEFINITION DEFERRED,
       lcl_process_data DEFINITION DEFERRED,
       lcl_display_data DEFINITION DEFERRED.

DATA: go_read_data    TYPE REF TO lcl_read_data,
      go_process_data TYPE REF TO lcl_process_data,
      go_disp_data    TYPE REF TO lcl_display_data.

Also the declaration of the SELECTION SCREEN is quite simple to follow:

*&---------------------------------------------------------------------*
*& Include ZFI_PAREGGIO_CLNT_FORN_SEL
*&---------------------------------------------------------------------*
SELECTION-SCREEN BEGIN OF BLOCK select WITH FRAME TITLE TEXT-001.
  SELECT-OPTIONS s_bukrs FOR wa_bkpf-bukrs OBLIGATORY.
  PARAMETERS s_waers TYPE bkpf-waers DEFAULT 'EUR' OBLIGATORY.
  SELECT-OPTIONS: s_belnr FOR wa_bkpf-belnr,
                  s_gjahr FOR wa_bkpf-gjahr,
                  s_budat FOR wa_bkpf-budat,
                  s_bldat FOR wa_bkpf-bldat,
                  s_blart FOR wa_bkpf-blart,
                  s_bp    FOR wa_bp-bp.
  PARAMETERS: z_par(5) TYPE c AS LISTBOX VISIBLE LENGTH 20 OBLIGATORY,
              test  TYPE c AS CHECKBOX.
SELECTION-SCREEN END OF BLOCK select.

SELECTION-SCREEN BEGIN OF BLOCK clearing WITH FRAME TITLE TEXT-002.
  PARAMETERS: r_budat TYPE bkpf-budat OBLIGATORY DEFAULT sy-datum,
              r_bldat TYPE bkpf-bldat OBLIGATORY DEFAULT sy-datum,
              r_blart TYPE bkpf-blart OBLIGATORY DEFAULT 'AB',
              r_bktxt TYPE bkpf-bktxt DEFAULT c_text.
SELECTION-SCREEN END OF BLOCK clearing.

Now here comes the interesting part. Firstly the include with the classes definitions:

*&---------------------------------------------------------------------*
*& Include ZFI_PAREGGIO_CLNT_FORN_DEF
*&---------------------------------------------------------------------*

CLASS lcl_read_data DEFINITION FINAL.
  PUBLIC SECTION.
    TYPES: BEGIN OF ty_bsik,
             bukrs TYPE bsik_view-bukrs,
             lifnr TYPE bsik_view-lifnr,
             waers TYPE bsik_view-waers,

             "One of the following next three fields will be used for clearing
             zuonr TYPE bsik_view-zuonr,
             xref2 TYPE bsik_view-xref2,
             xblnr TYPE bsik_view-xblnr,

             belnr TYPE bsik_view-belnr,
             gjahr TYPE bsik_view-gjahr,
             blart TYPE bsik_view-blart,
             bldat TYPE bsik_view-bldat,
             budat TYPE bsik_view-budat,
             wrbtr TYPE bsik_view-wrbtr,
             shkzg TYPE bsik_view-shkzg, "To check if value is multiplied by -1
           END OF ty_bsik.
    TYPES tt_bsik TYPE STANDARD TABLE OF ty_bsik.

    TYPES: BEGIN OF ty_bsid,
             bukrs TYPE bsid_view-bukrs,
             kunnr TYPE bsid_view-kunnr,
             waers TYPE bsid_view-waers,

             "One of the following next three fields will be used for clearing
             zuonr TYPE bsid_view-zuonr,
             xref2 TYPE bsid_view-xref2,
             xblnr TYPE bsid_view-xblnr,

             belnr TYPE bsid_view-belnr,
             gjahr TYPE bsid_view-gjahr,
             blart TYPE bsid_view-blart,
             bldat TYPE bsid_view-bldat,
             budat TYPE bsid_view-budat,
             wrbtr TYPE bsid_view-wrbtr,
             shkzg TYPE bsid_view-shkzg, "To check if value is multiplied by -1
           END OF ty_bsid.
    TYPES tt_bsid TYPE STANDARD TABLE OF ty_bsid.

    DATA: mt_bsik TYPE tt_bsik,
          mt_bsid TYPE tt_bsid.

    METHODS read_data.

  PRIVATE SECTION.
    METHODS: read_data_from_bsik,
             read_data_from_bsid.
ENDCLASS.

CLASS lcl_process_data DEFINITION FINAL.
  PUBLIC SECTION.
    TYPES: BEGIN OF ty_total,
             z_ind TYPE char4,
             bukrs TYPE bsik_view-bukrs,
             bp    TYPE char10,
             waers TYPE bsik_view-waers,

             "Only one of the following three fields will be used for clearing
             zuonr TYPE bsik_view-zuonr,
             xref2 TYPE bsik_view-xref2,
             xblnr TYPE bsik_view-xblnr,

             koart TYPE bseg-koart, "Account type 'K' or 'D'
             belnr TYPE bsik_view-belnr,
             gjahr TYPE bsik_view-gjahr,
             blart TYPE bsik_view-blart,
             bldat TYPE bsik_view-bldat,
             budat TYPE bsik_view-budat,
             wrbtr TYPE bsik_view-wrbtr,

             "Data for Clearing Document
             clear_belnr TYPE bsik_view-belnr,
             clear_gjahr TYPE bsik_view-belnr,
           END OF ty_total.
    TYPES tt_total TYPE STANDARD TABLE OF ty_total.

    DATA mt_total TYPE tt_total.

    METHODS:
      constructor
        IMPORTING
          io_read_data TYPE REF TO lcl_read_data,
      process_data.

  PRIVATE SECTION.
    TYPES: tt_ftclear TYPE STANDARD TABLE OF ftclear,
           tt_ftpost  TYPE STANDARD TABLE OF ftpost.

    DATA mr_read_data TYPE REF TO lcl_read_data.
    DATA mt_sort_criteria TYPE abap_sortorder_tab.
    METHODS:
      combine_data,
      sort_data,
      evaluate_data,
      build_value
        IMPORTING
          is_total        TYPE ty_total
          it_sort_crit    TYPE abap_sortorder_tab
        RETURNING
          VALUE(rv_value) TYPE string,
      modify_traffic_lights
        IMPORTING
          iv_found_diff_acc_type TYPE c
          iv_sum_rows            TYPE bsik_view-wrbtr
        EXPORTING
          ev_flag_correct        TYPE c
        CHANGING
          ct_group_calc          TYPE tt_total,
      clear_accounting_docs
        IMPORTING
          it_group_calc TYPE tt_total,
      interface_start,
      interface_clearing
        IMPORTING
          it_group_calc TYPE tt_total,
      interface_end,
      prepare_ftpost
        IMPORTING
          it_group_calc TYPE tt_total
        EXPORTING
          et_ftpost     TYPE tt_ftpost,
      prepare_ftclear
        IMPORTING
          it_group_calc TYPE tt_total
        EXPORTING
          et_ftclear    TYPE tt_ftclear,
      check_cleared_items
        CHANGING
          ct_group_calc TYPE tt_total.
ENDCLASS.

CLASS lcl_display_data DEFINITION FINAL.
  PUBLIC SECTION.
    DATA mr_process_data TYPE REF TO lcl_process_data.

    METHODS:
      constructor
        IMPORTING
          io_process_data TYPE REF TO lcl_process_data,
      display_data.

  PRIVATE SECTION.
    METHODS:
      prepare_fieldcatalog
        EXPORTING
          et_fcat TYPE slis_t_fieldcat_alv,
      prepare_layout
        EXPORTING
          es_layout  TYPE slis_layout_alv
          es_variant TYPE disvariant.
ENDCLASS.

Now for the implementation of the classed I will paste the Include ZFI_PAREGGIO_CLNT_FORN_IMP in pieces. First the implementation code of the first class lcl_read_data:

CLASS lcl_read_data IMPLEMENTATION.

  METHOD read_data.

    read_data_from_bsik( ).
    read_data_from_bsid( ).

  ENDMETHOD.

  METHOD read_data_from_bsik.

    SELECT bukrs, lifnr, waers, zuonr, xref2,
           xblnr, belnr, gjahr, blart, bldat,
           budat, wrbtr, shkzg
      FROM bsik_view
      INTO TABLE @mt_bsik
      WHERE bukrs IN @s_bukrs AND waers = @s_waers
        AND belnr IN @s_belnr AND gjahr IN @s_gjahr
        AND budat IN @s_budat AND bldat IN @s_bldat
        AND blart IN @s_blart AND lifnr IN @s_bp.

  ENDMETHOD.

  METHOD read_data_from_bsid.

    SELECT bukrs, kunnr, waers, zuonr, xref2,
           xblnr, belnr, gjahr, blart, bldat,
           budat, wrbtr, shkzg
      FROM bsid_view
      INTO TABLE @mt_bsid
      FOR ALL ENTRIES IN @mt_bsik
      WHERE bukrs = @mt_bsik-bukrs AND waers = @mt_bsik-waers
        AND gjahr = @mt_bsik-gjahr AND kunnr = @mt_bsik-lifnr
        AND belnr IN @s_belnr AND budat IN @s_budat
        AND bldat IN @s_bldat AND blart IN @s_blart.

  ENDMETHOD.

ENDCLASS.

I can’t say much about the code, except the fact that if you do a code inspection it will tell you that a join would have been a better idea, but that’s the way it was requested to me by the client.

The second class is the longest and I will stop more here after I have given the code to you:

CLASS lcl_process_data IMPLEMENTATION.

  METHOD constructor.

    mr_read_data = io_read_data.

  ENDMETHOD.

  METHOD combine_data.

    DATA ls_total TYPE ty_total.

    LOOP AT mr_read_data->mt_bsik ASSIGNING FIELD-SYMBOL(<fs_bsik>).
      ls_total-bukrs = <fs_bsik>-bukrs.
      ls_total-bp    = <fs_bsik>-lifnr.
      ls_total-waers = <fs_bsik>-waers.

      ls_total-zuonr = <fs_bsik>-zuonr.
      ls_total-xref2 = <fs_bsik>-xref2.
      ls_total-xblnr = <fs_bsik>-xblnr.

      ls_total-koart = 'K'.
      ls_total-belnr = <fs_bsik>-belnr.
      ls_total-gjahr = <fs_bsik>-gjahr.
      ls_total-blart = <fs_bsik>-blart.
      ls_total-bldat = <fs_bsik>-bldat.
      ls_total-budat = <fs_bsik>-budat.

      IF <fs_bsik>-shkzg = 'H'.
        ls_total-wrbtr = -1 * <fs_bsik>-wrbtr.
      ELSE.
        ls_total-wrbtr = <fs_bsik>-wrbtr.
      ENDIF.

      APPEND ls_total TO mt_total.
    ENDLOOP.

    LOOP AT mr_read_data->mt_bsid ASSIGNING FIELD-SYMBOL(<fs_bsid>).
      ls_total-bukrs = <fs_bsid>-bukrs.
      ls_total-bp    = <fs_bsid>-kunnr.
      ls_total-waers = <fs_bsid>-waers.

      ls_total-zuonr = <fs_bsid>-zuonr.
      ls_total-xref2 = <fs_bsid>-xref2.
      ls_total-xblnr = <fs_bsid>-xblnr.

      ls_total-koart = 'D'.
      ls_total-belnr = <fs_bsid>-belnr.
      ls_total-gjahr = <fs_bsid>-gjahr.
      ls_total-blart = <fs_bsid>-blart.
      ls_total-bldat = <fs_bsid>-bldat.
      ls_total-budat = <fs_bsid>-budat.

      IF <fs_bsid>-shkzg = 'H'.
        ls_total-wrbtr = -1 * <fs_bsid>-wrbtr.
      ELSE.
        ls_total-wrbtr = <fs_bsid>-wrbtr.
      ENDIF.

      APPEND ls_total TO mt_total.
    ENDLOOP.

  ENDMETHOD.

  METHOD sort_data.

    mt_sort_criteria =
      VALUE #( ( name = 'BUKRS' )
               ( name = 'BP'    )
               ( name = 'WAERS' ) ).

    CASE z_par.
      WHEN 'ZUONR'.
        mt_sort_criteria = VALUE #( BASE mt_sort_criteria
          ( name = 'ZUONR' ) ).
      WHEN 'XREF2'.
        mt_sort_criteria = VALUE #( BASE mt_sort_criteria
          ( name = 'XREF2' ) ).
      WHEN 'XBLNR'.
        mt_sort_criteria = VALUE #( BASE mt_sort_criteria
          ( name = 'XREF2' ) ).
    ENDCASE.

    SORT mt_total BY (mt_sort_criteria).

  ENDMETHOD.

  METHOD evaluate_data.

    "Internal tables used to calculate traffic light column
    DATA: lt_group_calc TYPE tt_total,
          lt_total_calc TYPE tt_total.

    DATA ls_total TYPE ty_total.

    "Values build upon sort criteria
    DATA: current_value    TYPE string,
          comparison_value TYPE string.

    "Account type
    DATA: comparison_acc_type TYPE bseg-koart.

    "Variables to check if records should be cleared
    DATA: found_different_acc_type TYPE c,
          sum_rows                 TYPE bsik-wrbtr,
          flag_correct             TYPE c.

    CHECK mt_total IS NOT INITIAL.

    ls_total = mt_total[ 1 ].

    comparison_value =
      build_value(
        EXPORTING
          is_total     = ls_total
          it_sort_crit = mt_sort_criteria ).

    comparison_acc_type = ls_total-koart.

    LOOP AT mt_total INTO ls_total.
      current_value =
        build_value(
          EXPORTING
            is_total     = ls_total
            it_sort_crit = mt_sort_criteria ).

      IF current_value = comparison_value.
        APPEND ls_total TO lt_group_calc.
        sum_rows = sum_rows + ls_total-wrbtr.

        IF ls_total-koart <> comparison_acc_type. "The rows correspond to different account types
          found_different_acc_type = 'X'.
        ENDIF.
      ELSE.
        modify_traffic_lights(
          EXPORTING
            iv_found_diff_acc_type = found_different_acc_type
            iv_sum_rows            = sum_rows
          IMPORTING
            ev_flag_correct        = flag_correct
          CHANGING
            ct_group_calc          = lt_group_calc ).

        IF flag_correct = 'X' AND test <> 'X'.
          clear_accounting_docs( lt_group_calc ).
          check_cleared_items(
            CHANGING
              ct_group_calc = lt_group_calc ).
        ENDIF.

        APPEND LINES OF lt_group_calc TO lt_total_calc.

        "Do the neccesary calculations for the next iteration
        CLEAR lt_group_calc.
        comparison_value = current_value.
        comparison_acc_type = ls_total-koart.
        CLEAR: sum_rows, found_different_acc_type, flag_correct.

        APPEND ls_total TO lt_group_calc.
        sum_rows = sum_rows + ls_total-wrbtr.

      ENDIF.
    ENDLOOP.

    modify_traffic_lights(
      EXPORTING
        iv_found_diff_acc_type = found_different_acc_type
        iv_sum_rows            = sum_rows
      IMPORTING
        ev_flag_correct        = flag_correct
      CHANGING
        ct_group_calc          = lt_group_calc ).

    IF flag_correct = 'X' AND test <> 'X'.
      clear_accounting_docs( lt_group_calc ).
      check_cleared_items(
        CHANGING
          ct_group_calc = lt_group_calc ).
    ENDIF.

    APPEND LINES OF lt_group_calc TO lt_total_calc.

    "Save the changes of traffic lights to the attribute table
    mt_total = lt_total_calc.

  ENDMETHOD.

  METHOD build_value.

    FIELD-SYMBOLS <field> TYPE any.

    LOOP AT it_sort_crit INTO DATA(ls_sort_crit).
      ASSIGN COMPONENT ls_sort_crit-name OF STRUCTURE is_total
        TO <field>.
      IF <field> IS ASSIGNED.
        IF rv_value IS INITIAL.
          rv_value = |{ <field> }|.
        ELSE.
          rv_value = |{ rv_value };{ <field> }|.
        ENDIF.
      ENDIF.
    ENDLOOP.

  ENDMETHOD.

  METHOD process_data.

    combine_data( ).
    sort_data( ).
    evaluate_data( ).

  ENDMETHOD.

  METHOD modify_traffic_lights.

    FIELD-SYMBOLS <fs_grp_calc> TYPE ty_total.

    SORT ct_group_calc BY koart belnr. "First Customer and than Vendor documents

    "Modify the traffic light
    IF iv_found_diff_acc_type = 'X' AND iv_sum_rows IS INITIAL.
      LOOP AT ct_group_calc ASSIGNING <fs_grp_calc>.
        <fs_grp_calc>-z_ind = icon_green_light.
      ENDLOOP.
      ev_flag_correct = 'X'.
    ELSE.
      LOOP AT ct_group_calc ASSIGNING <fs_grp_calc>.
        <fs_grp_calc>-z_ind = icon_red_light.
      ENDLOOP.
    ENDIF.

  ENDMETHOD.

  METHOD clear_accounting_docs.

    interface_start( ).
    interface_clearing( it_group_calc ).
    interface_end( ).

  ENDMETHOD.

  METHOD interface_start.

    DATA: lv_function TYPE rfipi-funct    VALUE 'C', "B = BDC, C = Call Transaction
          lv_mode     TYPE rfpdo-allgazmd VALUE 'N',
          lv_update   TYPE rfpdo-allgvbmd VALUE 'S'.

    CALL FUNCTION 'POSTING_INTERFACE_START'
      EXPORTING
        i_client           = sy-mandt
        i_function         = lv_function
        i_mode             = lv_mode
        i_update           = lv_update
        i_user             = sy-uname
      EXCEPTIONS
        client_incorrect   = 1
        function_invalid   = 2
        group_name_missing = 3
        mode_invalid       = 4
        update_invalid     = 5
        OTHERS             = 6.

    IF sy-subrc <> 0.
      MESSAGE ID sy-msgid TYPE sy-msgty NUMBER sy-msgno
        WITH sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4.
    ENDIF.

  ENDMETHOD.

  METHOD interface_clearing.

    DATA: lt_blntab TYPE TABLE OF blntab,
          lt_fttax  TYPE TABLE OF fttax.

    prepare_ftpost(
      EXPORTING
        it_group_calc = it_group_calc
      IMPORTING
        et_ftpost     = DATA(lt_ftpost) ).

    prepare_ftclear(
      EXPORTING
        it_group_calc = it_group_calc
      IMPORTING
        et_ftclear    = DATA(lt_ftclear) ).

    CALL FUNCTION 'POSTING_INTERFACE_CLEARING'
      EXPORTING
        i_auglv                    = 'GUTSCHRI'
        i_tcode                    = 'FB05'
        i_sgfunct                  = 'C'
      TABLES
        t_blntab                   = lt_blntab
        t_ftclear                  = lt_ftclear
        t_ftpost                   = lt_ftpost
        t_fttax                    = lt_fttax
      EXCEPTIONS
        clearing_procedure_invalid = 1
        clearing_procedure_missing = 2
        table_t041a_empty          = 3
        transaction_code_invalid   = 4
        amount_format_error        = 5
        too_many_line_items        = 6
        company_code_invalid       = 7
        screen_not_found           = 8
        no_authorization           = 9
        OTHERS                     = 10.

    IF sy-subrc <> 0.
      MESSAGE ID sy-msgid TYPE sy-msgty NUMBER sy-msgno
            WITH sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4.
    ENDIF.

  ENDMETHOD.

  METHOD interface_end.

    CALL FUNCTION 'POSTING_INTERFACE_END'
      EXPORTING
        i_bdcimmed              = 'X'
      EXCEPTIONS
        session_not_processable = 1
        OTHERS                  = 2.

    IF sy-subrc <> 0.
      MESSAGE ID sy-msgid TYPE sy-msgty NUMBER sy-msgno
            WITH sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4.
    ENDIF.

  ENDMETHOD.

  METHOD prepare_ftpost.

    DATA lv_count TYPE count_pi VALUE '001'.
    DATA(ls_group_calc) = it_group_calc[ 1 ].

    et_ftpost = VALUE #(
      ( stype = 'K'
        count = lv_count
        fnam  = 'BKPF-BLDAT' fval = |{ r_bldat+6(2) }.{ r_bldat+4(2) }.{ r_bldat(4) }| )
      ( stype = 'K'
        count = lv_count
        fnam  = 'BKPF-BLART' fval = r_blart )
      ( stype = 'K'
        count = lv_count
        fnam  = 'BKPF-BUKRS' fval = ls_group_calc-bukrs )
      ( stype = 'K'
        count = lv_count
        fnam  = 'BKPF-BUDAT' fval = |{ r_budat+6(2) }.{ r_budat+4(2) }.{ r_budat(4) }| )
      ( stype = 'K'
        count = lv_count
        fnam  = 'BKPF-WAERS' fval = ls_group_calc-waers )
      ( stype = 'K'
        count = lv_count
        fnam  = 'BKPF-BKTXT' fval = r_bktxt ) ).

  ENDMETHOD.

  METHOD prepare_ftclear.

    LOOP AT it_group_calc INTO DATA(ls_group_calc).

      et_ftclear = VALUE #( BASE et_ftclear (
        agkoa = ls_group_calc-koart  "Account Type
        agkon = ls_group_calc-bp     "BP number
        agbuk = ls_group_calc-bukrs  "Company code
        xnops = 'X'                  "Indicator: Select Only Open Items Which Are Not Special G/L?
        selfd = 'BELNR'              "Selection Field
        selvon = ls_group_calc-belnr "Lower limit
        selbis = ls_group_calc-belnr "Upper limit
        ) ).

    ENDLOOP.

  ENDMETHOD.

  METHOD check_cleared_items.

    "Check if the items where cleared in tables BSAD and BSAK
    LOOP AT ct_group_calc ASSIGNING FIELD-SYMBOL(<fs_group_calc>).
      CASE <fs_group_calc>-koart.
        WHEN 'D'.
          SELECT SINGLE augbl, auggj FROM bsad_view
            INTO @DATA(clearing_customer_item)
            WHERE bukrs = @<fs_group_calc>-bukrs
              AND kunnr = @<fs_group_calc>-bp
              AND belnr = @<fs_group_calc>-belnr
              AND gjahr = @<fs_group_calc>-gjahr.
          IF sy-subrc = 0.
            <fs_group_calc>-clear_belnr = clearing_customer_item-augbl.
            <fs_group_calc>-clear_gjahr = clearing_customer_item-auggj.
          ENDIF.
        WHEN 'K'.
          SELECT SINGLE augbl, auggj FROM bsak_view
            INTO @DATA(clearing_vendor_item)
            WHERE bukrs = @<fs_group_calc>-bukrs
              AND lifnr = @<fs_group_calc>-bp
              AND belnr = @<fs_group_calc>-belnr
              AND gjahr = @<fs_group_calc>-gjahr.
          IF sy-subrc = 0.
            <fs_group_calc>-clear_belnr = clearing_vendor_item-augbl.
            <fs_group_calc>-clear_gjahr = clearing_vendor_item-auggj.
          ENDIF.
      ENDCASE.
    ENDLOOP.

  ENDMETHOD.

ENDCLASS.

So these are the methods of our class in the order they appear above:

  • constructor – We pass the reference to the first object go_read_data to instance attribute mr_read_data when we create the new object go_process_data.
  • combine_data – As the name says it combines the data of the customers and vendors into the instance attribute mt_total.
  • sort_data – It sorts the mt_total internal table by BUKRS – Company Code, BP – Business Partner, WAERS – Currency and one of the selection criteria (ZUONR, XREF2, XBLNR).
  • evaluate_data – does all the heavy work and makes use of other useful methods to perform the required logic
    1. build_value – Helps to build a concatenated value of the columns that were used for sorting and that will help to understand when we are processing a new row from mt_total with a different combination of these four fields.
    2. modify_traffic_lights – This method is called inside the loop on the internal table mt_total and directly after the loop. Inside the loop the method it is called if the current value returned by the build_value method is different from the comparison value. Inside the method we modify the traffic lights to green or red and fill the variable flag_correct with the value ‘X’ or empty.
    3. After the execution of the previous method if the flag_correct has the value ‘X’ and we are not performing a test run we execute the method clear_accounting_docs. I will explain this method separately as it is important to understand how we execute the transaction FB05.
    4. check_cleared_items is executed to retrieve the value of the clearing documents if all went good.
  • process_data – Serves as a container for calling the three previous mentioned method.

Next I will concentrate on the fun part, or at least from my point of view :). The method clear_accounting_docs does the call of the transaction FB05 to clear the identified open items.

Now our method calls in itself the following methods:

  • interface_start – Starts the interface through the function module POSTING_INTERFACE_START.
  • interface_clearing – Calls the BAPI POSTING_INTERFACE_CLEARING to clear the items, but before that prepares the data for the header and the items respectively with the methods prepare_ftpost and prepare_ftclear.
  • interface_end – Closes the interface by calling the function module POSTING_INTERFACE_END.

Now I am pasting below the code for the third and last class:

CLASS lcl_display_data IMPLEMENTATION.

  METHOD constructor.

    mr_process_data = io_process_data.

  ENDMETHOD.

  METHOD prepare_fieldcatalog.

    et_fcat = VALUE #(
      ( fieldname = 'Z_IND'
        seltext_s = TEXT-s01
        seltext_m = TEXT-m01
        seltext_l = TEXT-l01
        col_pos   = 1
        icon      = 'X'           )
      ( fieldname = 'BUKRS'
        seltext_s = TEXT-s02
        seltext_m = TEXT-m02
        seltext_l = TEXT-l02
        col_pos   = 2             )
      ( fieldname = 'BP'
        seltext_s = TEXT-s03
        seltext_m = TEXT-m03
        seltext_l = TEXT-l03
        col_pos   = 3             )
      ( fieldname = 'WAERS'
        seltext_s = TEXT-s04
        seltext_m = TEXT-m04
        seltext_l = TEXT-l04
        col_pos   = 4             )
      ( fieldname = 'ZUONR'
        seltext_s = TEXT-s05
        seltext_m = TEXT-m05
        seltext_l = TEXT-l05
        col_pos   = 5             )
      ( fieldname = 'XREF2'
        seltext_s = TEXT-s06
        seltext_m = TEXT-m06
        seltext_l = TEXT-l06
        col_pos   = 6             )
      ( fieldname = 'XBLNR'
        seltext_s = TEXT-s07
        seltext_m = TEXT-m07
        seltext_l = TEXT-l07
        col_pos   = 7             )
      ( fieldname = 'KOART'
        seltext_s = TEXT-s08
        seltext_m = TEXT-m08
        seltext_l = TEXT-l08
        col_pos   = 8             )
      ( fieldname = 'BELNR'
        seltext_s = TEXT-s09
        seltext_m = TEXT-m09
        seltext_l = TEXT-l09
        col_pos   = 9
        hotspot   = 'X'           )
      ( fieldname = 'GJAHR'
        seltext_s = TEXT-s10
        seltext_m = TEXT-m10
        seltext_l = TEXT-l10
        col_pos   = 10            )
      ( fieldname = 'BLART'
        seltext_s = TEXT-s11
        seltext_m = TEXT-m11
        seltext_l = TEXT-l11
        col_pos   = 11            )
      ( fieldname = 'BLDAT'
        seltext_s = TEXT-s12
        seltext_m = TEXT-m12
        seltext_l = TEXT-l12
        col_pos   = 12            )
      ( fieldname = 'BUDAT'
        seltext_s = TEXT-s13
        seltext_m = TEXT-m13
        seltext_l = TEXT-l13
        col_pos   = 13            )
      ( fieldname = 'WRBTR'
        seltext_s = TEXT-s14
        seltext_m = TEXT-m14
        seltext_l = TEXT-l14
        col_pos   = 14            )
      ( fieldname = 'CLEAR_BELNR'
        seltext_s = TEXT-s15
        seltext_m = TEXT-m15
        seltext_l = TEXT-l15
        col_pos   = 15
        hotspot   = 'X'           )
      ( fieldname = 'CLEAR_GJAHR'
        seltext_s = TEXT-s16
        seltext_m = TEXT-m16
        seltext_l = TEXT-l16
        col_pos   = 16            ) ).

  ENDMETHOD.

  METHOD prepare_layout.

    es_layout = VALUE #(
      no_input          = 'X'
      colwidth_optimize = 'X'
      zebra             = 'X' ).

    es_variant = VALUE #(
      report  = sy-repid
      variant = SWITCH #( z_par
                  WHEN 'ZUONR' THEN '/LAYOUT1'
                  WHEN 'XREF2' THEN '/LAYOUT2'
                  WHEN 'XBLNR' THEN '/LAYOUT3' ) ).

  ENDMETHOD.

  METHOD display_data.

    prepare_fieldcatalog(
      IMPORTING
        et_fcat = DATA(lt_fcat) ).

    prepare_layout(
      IMPORTING
        es_layout  = DATA(ls_layout)
        es_variant = DATA(ls_variant) ).

    CALL FUNCTION 'REUSE_ALV_GRID_DISPLAY'
      EXPORTING
        i_buffer_active         = space
        i_callback_program      = sy-repid
        i_callback_user_command = 'USER_COMMAND'
        is_layout               = ls_layout
        it_fieldcat             = lt_fcat
        i_save                  = 'X'
        is_variant              = ls_variant
      TABLES
        t_outtab                = mr_process_data->mt_total
      EXCEPTIONS
        program_error           = 1
        OTHERS                  = 2.

  ENDMETHOD.

ENDCLASS.

Now the code in this class does the usual stuff for displaying the data in an ALV. The data as you guessed was provided form the previous class in the constructor. I apologize for using the good old REUSE_ALV_GRID_DISPLAY for displaying the ALV and not the  CL_SALV_TABLE, but consider it as a not so good practise of mixing the old ways with the new recommended ones.

The last include adds insult to the injury and helps to call the transaction FB03 to display the accounting documents from the hotspot columns.

*&---------------------------------------------------------------------*
*& Include ZFI_PAREGGIO_CLNT_FORN_FORM
*&---------------------------------------------------------------------*
FORM user_command USING r_ucomm     TYPE sy-ucomm
                        rs_selfield TYPE slis_selfield.

  IF r_ucomm = '&IC1'.
    READ TABLE go_disp_data->mr_process_data->mt_total
      INDEX rs_selfield-tabindex
      INTO DATA(ls_selected_row).
    IF sy-subrc = 0.
      SET PARAMETER ID 'BUK' FIELD ls_selected_row-bukrs.

      CASE rs_selfield-fieldname.
        WHEN 'BELNR'.
          SET PARAMETER ID 'BLN' FIELD ls_selected_row-belnr.
          SET PARAMETER ID 'GJR' FIELD ls_selected_row-gjahr.
        WHEN 'CLEAR_BELNR'.
          SET PARAMETER ID 'BLN' FIELD ls_selected_row-clear_belnr.
          SET PARAMETER ID 'GJR' FIELD ls_selected_row-clear_gjahr.
      ENDCASE.

      CALL TRANSACTION 'FB03' AND SKIP FIRST SCREEN.
    ENDIF.
  ENDIF.

ENDFORM.

The last thing that I would like to note is that I have created three layouts that change only by the criteria used for the clearing:

/LAYOUT1 – Layout ZUONR
/LAYOUT2 – Layout XREF2
/LAYOUT3 – Layout XBLNR

As is the custom after such a long presentation I would like to thank you for the attention and I really hope that it was informative for you or that you learned something new from it. Don’t hesitate to give me any useful comment of what could have been done better or how you would have solved such a problem in a different way.

I would like to encourage you to follow the ABAP Development page https://community.sap.com/topics/abap and its blog feed https://blogs.sap.com/tags/833755570260738661924709785639136. Also don’t hesitate to ask and answer questions in the Q&A area https://answers.sap.com/tags/833755570260738661924709785639136. Till next time!

 

 

Assigned Tags

      9 Comments
      You must be Logged on to comment or reply to a post.
      Author's profile photo Matthew Billingham
      Matthew Billingham

      A few points. You can store code in using github and abapgit, that enable people to easily download your examples.

      I'd advise not using includes - it doesn't work well in Eclipse and it isn't necessary. Instead use global classes.

      If you really want to do OO design, learn design patterns, always programming to an interface, never a concrete class, dependency injection, and unit testing. A good start is the ABAP unit testing class on openSAP.com. https://open.sap.com/courses/wtc1

      I highly recommend it. You will learn what interfaces are about and dependency injection. Also check out the Clean Code project and apply those principles.https://github.com/SAP/styleguides/blob/main/clean-abap/CleanABAP.

      Do try to use CL_SALV_TABLE instead of REUSE_ALV_GRID_DISPLAY. You won't regret it! 

      I'm currently developing a new report. I use one local class in the report program (lcl_main) that handles all the selection screen operations, and eventually calls the screen the output will be one. There is a global class for the controller of that screen (setting up the CL_SALV_TABLE etc.), another one of handling the business data (model) and another (the DAO  - Data Access Object) for handling any db access, FM calls. These three classes are all implementations of interfaces.

      What this means is that in my unit tests, I can define a local test double implementing e.g. the DAO, and inject an instance of that into the model class, so that I can run tests on my code without needing to set up any real data.

      What I'm not doing is creating factory classes to provide my instances (these would also be implementions of interfaces). I have done this in the past, as it provides yet more flexibility. At the moment I'm trying a few different techniques, as I'm still learning my way through (even after 20+ years ABAP OO), and figuring out the best way to apply SMART principles.

      But hey - kudos for moving from procedural to oo! 

      Author's profile photo Elson Meço
      Elson Meço
      Blog Post Author

      I read carefully all your suggestions and I really appreciate the points you have suggested me for improvement.

      The curious thing is that I started a couple of days ago the course you mentioned for ABAP Unit Tests. So far I am really enjoying it and I have found all the concepts really inspiring. I'm only halfway through it and will try to apply these fresh insights on my next developments.

      I am very aware of the fact that working on global classes is the best way to go in terms of reusability and code centralization. Also, I really enjoy the capabilities offered by the CL_SALV_TABLE class and have used them in different other implementations of mine.

      I stated that my program doesn't abide to the MVC pattern fully and my classes were intended to show the separation of concerns. Personally, I have read some pretty good SAP Press books about the OO Patterns and find them really fascinating.

      Aside from the SMART principles I always remember myself of the KISS one (Keep it simple, stupid!). It's such a blessing to now that you have such good principles on which you can base your developments and do the necessary cross-checks. The greatest gift finally of being a developer nowadays is that you have such a great community that can share with you their experience and recommendations :).

      PS: As I way to show that I have really followed the openSAP course https://open.sap.com/courses/wtc1, I would like to stop a moment on their lesson on Pair Programming. I liked their expression "You are not your code" and what that really means is that any suggestion isn't never meant personally, but to the code itself. And to close it: "Be hard on the code, but not on the developer". ✌️

      Author's profile photo Matthew Billingham
      Matthew Billingham

      Pair programming in TDD can be quite fun. There was a seminar on the subject I went to. My partner wrote the unit tests, I wrote the code to fulfill them. But I did it in an "evil" style. The absolute minimum to pass the tests.

      It resulted in quite good code in the end!

      I've been programming in ABAP objects for over twenty years. I'm still learning.

      Author's profile photo Elson Meço
      Elson Meço
      Blog Post Author

      It seems controversial at a first sight the writing the bare minimum to pass a test can lead to better code, but in fact it leads to less errors and better structure.

      You have to be a bit "lazy" I guess to learn the new concepts, because as the expression says only a lazy developer is a good developer :).

      Have you got an online repository to check any example you have done?

      Author's profile photo Matthew Billingham
      Matthew Billingham

      I'm afraid I've not put anything ABAP in my github account.

      Author's profile photo Matthew Billingham
      Matthew Billingham

      Here is an interesting article I stumbled across to day about evil TDD. As when I did it, it covers Conway's Game of Life.

      https://qntm.org/emissions

      Author's profile photo Jelena Perfiljeva
      Jelena Perfiljeva

      There seems to be an error in sort_data method: in CASE statement, it does the same thing whether it's XREF2 or XBLNR. I'm scratching my head though on why CASE is at all needed (it's notoriously prone to errors / typos just like this one), it seems you could just use Z_PAR value and be done with it.

      By the way, why is it S_... for select options but then Z_PAR and TEST for parameters? This is  confusing. In addition to what's already recommended, I'd suggest to check out Clean ABAP. It's very helpful in making the code more easy to be understood by others.

      Author's profile photo Elson Meço
      Elson Meço
      Blog Post Author

      Yes, you are absolutely right. There's a typo and it could have been easily avoided by keeping the method really short like below:

        METHOD sort_data.
      
          mt_sort_criteria =
            VALUE #( ( name = 'BUKRS' )
                     ( name = 'BP'    )
                     ( name = 'WAERS' ) 
                     ( name = z_par   ) ).
      
          SORT mt_total BY (mt_sort_criteria).
      
        ENDMETHOD.

      The names were told to me like that in the technical specification and we didn't argue much about. Anyway, it's also a pretty good point you noted out.

      Author's profile photo Jelena Perfiljeva
      Jelena Perfiljeva

      Technical specification should not dictate the variable names. Sometimes this is covered by the corporate ABAP guidelines. Those guidelines can be pretty bad too but at least they usually introduce some consistency.

      In any case, it doesn't need to be an argument. You can simply ask if there is a specific reason the naming is inconsistent. The technical spec should not even go into such level of detail to begin with (might as well just write the code then). It could contain some pseudo code if it helps to explain the approach but even that can be more harmful than useful, to be honest. Programmers should not be told literally what to do. Never a good sign, in my experience, for everyone involved.