Friday, 8 January 2021

ALV Tree Report – Order Combination – continued

Requirement and why I used ALV tree?

The requirement was to combine multiple orders (belonging to a single ship-to party) into a single delivery note. ALV tree seemed to be the perfect option for the hierarchy with the Ship-to Party being the Parent node and the orders with the same Ship-to Party to be the children nodes.

This post is in continuation to my previous post. If you wish to see a simple ALV tree report, you can refer to my earlier post. In this post, I have tried to show how some small code changes enhances the ALV tree report and makes it more user friendly.

Below are some enhancements I made:

◉ Expand/ Collapse, Select/ Unselect all nodes.

◉ Navigate to created delivery/ show error log in case no delivery is created

◉ Have a reset button in case of some error

1. Option to Expand/ Collapse all nodes and to Select/ Unselect all nodes

Created a custom GUI status (ZSTATUS) with the following buttons

SAP ABAP Tutorial and Material, SAP ABAP Exam Prep, SAP ABAP Certifications, SAP ABAP Learning

Create the instance of ALV tree (go_tree). The table (gt_final1) needs to be empty at this point.

DATA: gt_final1 TYPE TABLE OF ty_final,
      go_tree  TYPE REF TO cl_salv_tree. 

* Create ALV tree 
    TRY.
        cl_salv_tree=>factory(
          IMPORTING
            r_salv_tree = go_tree
          CHANGING
            t_table     = gt_final1 ).
      CATCH cx_salv_error. 
        LEAVE LIST-PROCESSING.
    ENDTRY.

* Set GUI STATUS
    go_tree->set_screen_status( pfstatus      =  'ZSTATUS'
                                report        =  sy-repid
                                set_functions =  go_tree->c_functions_all ).

The Expand and Collapse buttons trigger the event AFTER_SALV_FUNCTION.

The Select and Unselect buttons trigger the event ADDED_FUNCTION

We have created two methods for them (along with our previously created ON_LINK_CLICK method)

METHODS: on_link_click FOR EVENT link_click
OF cl_salv_events_tree
IMPORTING columnname
node_key,

on_user_command FOR EVENT added_function
OF cl_salv_events
IMPORTING e_salv_function,

reshape_tree FOR EVENT after_salv_function
OF cl_salv_events
IMPORTING e_salv_function.

We now need to register these events

DATA: lo_events TYPE REF TO cl_salv_events_tree.

    lo_events = go_tree->get_event( ).

    SET HANDLER go_create->on_link_click   FOR lo_events.
    SET HANDLER go_create->on_user_command FOR lo_events.
    SET HANDLER go_create->reshape_tree    FOR lo_events.
 
When user clicks on SELECT / DESELECT all buttons, the method ON_USER_COMMAND is triggered. The idea is to loop at all nodes and find the one that are checkboxes and then select/ unselect them. You can find that the value for checkbox is 3 in the Attributes of class CL_SALV_ITEM

METHOD on_user_command.
*---------------------------------------------------------
* Select/ Deselect all checkboxes
*---------------------------------------------------------

    DATA: lo_nodes TYPE REF TO cl_salv_nodes,
          lt_nodes TYPE salv_t_nodes,
          lo_item  TYPE REF TO cl_salv_item,
          lv_set   TYPE xfeld,
          lv_value TYPE salv_de_constant. 

    FIELD-SYMBOLS: <ls_nodes> TYPE salv_s_nodes.

* Get reference of the nodes of the tree
    lo_nodes = go_tree->get_nodes( ).

* Get all the nodes of the tree
    TRY.
        lt_nodes = lo_nodes->get_all_nodes( ).
      CATCH cx_salv_msg.
        EXIT.
    ENDTRY.

    CASE sy-ucomm.
      WHEN '&ALL'.
        lv_set = abap_true.       "check
      WHEN '&NONE'.
        lv_set = abap_false.      "uncheck
    ENDCASE.

* Loop at the nodes where the type is a checkbox (3) 
    LOOP AT lt_nodes ASSIGNING <ls_nodes>.
      lo_item = <ls_nodes>-node->get_hierarchy_item( ).
      lv_value = lo_item->get_type( ).
      IF lo_item->is_enabled( ) = abap_true AND lv_value = 3.  "Checkbox
        lo_item->set_checked( lv_set ).
      ENDIF.
    ENDLOOP.

  ENDMETHOD.                    "on_user_command

When the user clicks on EXPAND/ COLLAPSE buttons, the method RESHAPE_TREE gets triggered.

METHOD reshape_tree.
*---------------------------------------------------------
* Expand/ Collapse all nodes
*---------------------------------------------------------

    DATA: lo_nodes   TYPE REF TO cl_salv_nodes,
          lo_columns TYPE REF TO cl_salv_columns. 

* Get reference of the nodes of the tree
    lo_nodes = go_tree->get_nodes( ).

    CASE sy-ucomm.
      WHEN '&EXPAND'.
        lo_nodes->expand_all( ).
      WHEN '&COLLAPSE'.
        lo_nodes->collapse_all( ). 
    ENDCASE.

  ENDMETHOD.                    "reshape_tree

This is how the Order Combination screen looks

SAP ABAP Tutorial and Material, SAP ABAP Exam Prep, SAP ABAP Certifications, SAP ABAP Learning

NOTE: to know how to add heading and how the nodes were added to give a hierarchical display, you can refer to step 2 and step 4 from this post.

Once the user clicks on Collapse button, we can see the value 0,000 for the column Order Qty as below (because Order quantity is of type QUAN and the initial value is 0,000)

SAP ABAP Tutorial and Material, SAP ABAP Exam Prep, SAP ABAP Certifications, SAP ABAP Learning

To solve this, we can add one more field of a character type to our final structure and pass the value from the Order Qty actual field to the character field using a WRITE statement.

Next we need to hide the actual Order Qty column, rename the new character column as ‘Order Qty’ and align the values in it to the right. For this we can make use of following methods of class CL_SALV_COLUMNS:

◉ SET_VISIBLE
◉ SET_TECHNICAL
◉ SET_MEDIUM_TEXT
◉ SET_LONG_TEXT
◉ SET_ALIGNMENT

2. Navigate to the created delivery and display error log if the delivery is not created


In order to create the delivery, we are using BAPI_OUTB_DELIVERY_CREATE_SLS

Issues and work-around for error log display

◉ The return table from BAPI

The return table that the above-mentioned BAPI sends back to the custom report, has a bunch of messages (It can have the error message plus the success message of the delivery created). However, it is not possible to know just reading the table, that which message corresponds to which order.

This is how the return table looks in debug.

SAP ABAP Tutorial and Material, SAP ABAP Exam Prep, SAP ABAP Certifications, SAP ABAP Learning

In order to display the error log we need to know the Error message and the corresponding order. To solve this, we implemented a BAdI and used the Extension out structure of the BAPI.

◉ Debugging the BAPI

A bit of debug inside the BAPI and it was clear that FM SHP_DELIVERY_CREATE_FROM_SLS was being called to create the delivery and this FM exported two tables:

LT_ITEMS_OUT — list of orders for which deliveries created

LT_VBFS — list of orders for which delivery is not created and the error message class and number

SAP ABAP Tutorial and Material, SAP ABAP Exam Prep, SAP ABAP Certifications, SAP ABAP Learning

◉ Implementing BAdI and using Extension Out

SAP ABAP Tutorial and Material, SAP ABAP Exam Prep, SAP ABAP Certifications, SAP ABAP Learning

We implemented the BAdI and filled Extension out structure from LT_VBFS

Code inside the BAdI:

METHOD if_dlv_create_extout~additional_output.

  DATA: lt_callstack TYPE abap_callstack.

  FIELD-SYMBOLS: <ls_extension_out> TYPE bapiparex,
                 <ls_messages>      TYPE vbfs.

* Read the stack
  CALL FUNCTION 'SYSTEM_CALLSTACK'
    IMPORTING
      callstack = lt_callstack.

  SORT lt_callstack BY mainprogram.

* Continue if it is Order Combination report
  READ TABLE lt_callstack TRANSPORTING NO FIELDS
    WITH KEY mainprogram = 'ZORDER_COMBINATION'  "our report name
    BINARY SEARCH.
  IF sy-subrc EQ 0.
* Fill the Extension Out with VBFS table
    LOOP AT it_messages ASSIGNING <ls_messages>.
      APPEND INITIAL LINE TO ct_extension_out ASSIGNING <ls_extension_out>.
      <ls_extension_out>-structure  = 'VBFS'.
      <ls_extension_out>-valuepart1 = <ls_messages>.
      <ls_extension_out>-valuepart2 = <ls_messages>+240(20).
    ENDLOOP.
  ENDIF.

ENDMETHOD.

◉ In the report, we now have the error and the order no. using the Extension out structure
Delivery Creation method (Only a few changes made in the method in the previous post)

If delivery is created for an order, we set the node to green color and display the delivery. On clicking the delivery, we navigate to the delivery.

If no delivery is created, we set the node to red color and give a button to show error log.

  METHOD create_delivery.
*---------------------------------------------------------
* Create the delivery from the selected Orders
*---------------------------------------------------------

    DATA: lt_nodes         TYPE salv_t_nodes,
          lo_item          TYPE REF TO cl_salv_item,
          lo_data          TYPE REF TO data,
          lv_delivery      TYPE vbeln_vl,
          lv_number        TYPE vbnum,
          lt_sales_orders  TYPE STANDARD TABLE OF bapidlvreftosalesorder,
          lt_created_items TYPE STANDARD TABLE OF bapidlvitemcreated,
          lt_return        TYPE STANDARD TABLE OF bapiret2,
          lt_extension_out TYPE STANDARD TABLE OF bapiparex,
          lo_item_delivery TYPE REF TO cl_salv_item,
          lt_vbfs          TYPE STANDARD TABLE OF vbfs.

    FIELD-SYMBOLS: <ls_nodes>  TYPE salv_s_nodes,
                   <ls_data>   TYPE ty_final,
                   <ls_orders> TYPE bapidlvreftosalesorder,
                   <ls_items>  TYPE bapidlvitemcreated,
                   <ls_vbfs>   TYPE vbfs,
                   <ls_ext_out> TYPE bapiparex.

* Get the entire subtree
    TRY.
        lt_nodes = io_node->get_subtree( ).
      CATCH cx_salv_msg.
        EXIT.
    ENDTRY.

* Here we will loop at all the nodes, find the ones that are selected
* and pass those orders to BAPI for Delivery Creation
    LOOP AT lt_nodes ASSIGNING <ls_nodes>.

* Get the item details
      lo_item = <ls_nodes>-node->get_hierarchy_item( ).

* Get the node data if it is checked
      IF lo_item->is_enabled( ) = abap_true
      AND lo_item->is_checked( ) = abap_true.

        lo_data = <ls_nodes>-node->get_data_row( ).
        ASSIGN lo_data->* TO <ls_data>.
        IF sy-subrc EQ 0.

* Get the sales order and item
          APPEND INITIAL LINE TO lt_sales_orders ASSIGNING <ls_orders>.
          <ls_orders>-ref_doc  = <ls_data>-vbeln.
          <ls_orders>-ref_item = <ls_data>-posnr.

        ENDIF.
      ENDIF.

    ENDLOOP.

*Call the BAPI for delivery creation
    CALL FUNCTION 'BAPI_OUTB_DELIVERY_CREATE_SLS'
      IMPORTING
        delivery          = lv_delivery
        num_deliveries    = lv_number
      TABLES
        sales_order_items = lt_sales_orders
        created_items     = lt_created_items
        extension_out     = lt_extension_out
        return            = lt_return.

* FIll VBFS from Extension out table
    LOOP AT lt_extension_out ASSIGNING <ls_ext_out>.
      APPEND INITIAL LINE TO lt_vbfs ASSIGNING <ls_vbfs>.
      <ls_vbfs>+0(240)  = <ls_ext_out>-valuepart1.
      <ls_vbfs>+240(20) = <ls_ext_out>-valuepart2.
    ENDLOOP.

    SORT lt_vbfs BY vbeln.

    SORT lt_created_items BY ref_doc ref_item.

    IF lv_delivery IS NOT INITIAL.
      CALL FUNCTION 'BAPI_TRANSACTION_COMMIT'.
    ELSE.
      CALL FUNCTION 'BAPI_TRANSACTION_ROLLBACK'.
    ENDIF.

* Next, we loop at the nodes and display the delivery

    LOOP AT lt_nodes ASSIGNING <ls_nodes>.
      lo_item = <ls_nodes>-node->get_hierarchy_item( ).

      TRY.
          lo_item_delivery = <ls_nodes>-node->get_item( 'DELIVERY' ).
        CATCH cx_salv_msg.
          EXIT.
      ENDTRY.

      IF lo_item->is_checked( ) = abap_true.
        lo_item->set_checked( abap_false ).
        lo_data = <ls_nodes>-node->get_data_row( ).
        ASSIGN lo_data->* TO <ls_data>.
        IF sy-subrc EQ 0.
* Set the delivery
          READ TABLE lt_created_items ASSIGNING <ls_items>
            WITH KEY ref_doc  = <ls_data>-vbeln
                     ref_item = <ls_data>-posnr
                     BINARY SEARCH.
          IF sy-subrc EQ 0.
            <ls_data>-delivery = <ls_items>-deliv_numb.
            <ls_nodes>-node->set_data_row( <ls_data> ).
          ELSE.
            READ TABLE lt_vbfs ASSIGNING <ls_vbfs>
                      WITH KEY vbeln = <ls_data>-vbeln
                      BINARY SEARCH.
            IF sy-subrc EQ 0.
              MESSAGE ID <ls_vbfs>-msgid TYPE <ls_vbfs>-msgty 
              NUMBER <ls_vbfs>-msgno
              WITH <ls_vbfs>-msgv1 <ls_vbfs>-msgv2 
              <ls_vbfs>-msgv3 <ls_vbfs>-msgv4
              INTO <ls_data>-logs.
            ENDIF.
          ENDIF.
        ENDIF.

* Disable the checkbox if the delivery is created,
        IF <ls_data>-delivery IS NOT INITIAL.
          lo_item->set_enabled( abap_false ).
          lo_item->set_icon( '@0V\QOK@' ).      "OK
* Set link-click for the delivery > Now you can navigate to delivery
          lo_item_delivery->set_type( if_salv_c_item_type=>link ).
* Set the node to green color
          <ls_nodes>-node->set_row_style( if_salv_c_tree_style=>emphasized_positive ).
        ELSE.
* Set button for error log
          lo_item_delivery->set_type( if_salv_c_item_type=>button ).
          lo_item_delivery->set_value( 'Display Error Log' ).
* Set the node to red color
          <ls_nodes>-node->set_row_style( if_salv_c_tree_style=>emphasized_negative ).
        ENDIF.
      ENDIF.
    ENDLOOP.

  ENDMETHOD.                    "create_delivery

For the method ON_LINK_CLICK, we need to now check what the user has clicked on Delivery Creation button, Error Log button or the Delivery itself.

  METHOD on_link_click.
*---------------------------------------------------------
* Method when the user clicks on Link or button
*---------------------------------------------------------

    DATA: lo_nodes TYPE REF TO cl_salv_nodes,
          lo_node  TYPE REF TO cl_salv_node,
          lo_data  TYPE REF TO data.

    FIELD-SYMBOLS: <ls_data> TYPE ty_final.

    lo_nodes = go_tree->get_nodes( ).

* Get the parent node
    TRY.
        lo_node = lo_nodes->get_node( node_key ).
      CATCH cx_salv_msg.
        EXIT.
    ENDTRY.

    lo_data = lo_node->get_data_row( ).
    ASSIGN lo_data->* TO <ls_data>.
    IF sy-subrc EQ 0.
* Check what the user has clicked
      CASE <ls_data>-delivery.
        WHEN 'Delivery Creation'.
          go_create->create_delivery( lo_node ).
        WHEN 'Display Error Log'.
          MESSAGE <ls_data>-logs TYPE 'I'.
        WHEN OTHERS.
* Open the delivery in VL03N
          SET PARAMETER ID 'VL' FIELD <ls_data>-delivery.

          CALL FUNCTION 'ABAP4_CALL_TRANSACTION'
            EXPORTING
              tcode                   = 'VL03N'
              skip_screen             = abap_true
            EXCEPTIONS
              call_transaction_denied = 1
              tcode_invalid           = 2
              OTHERS                  = 3.
          IF sy-subrc <> 0.
            MESSAGE ID sy-msgid TYPE sy-msgty NUMBER sy-msgno
             WITH sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4.
          ENDIF.

          EXIT.
      ENDCASE.
    ENDIF.

  ENDMETHOD.                    "on_link_click

Working example of the report:

There is some issue with Item 30 of Order 1000000002, so it does not get delivered

Step 1: User clicks on Delivery Creation button

Step 2: Output. If you click on Delivery 2000000001, you’ll navigate to the delivery note (VL03N)

SAP ABAP Tutorial and Material, SAP ABAP Exam Prep, SAP ABAP Certifications, SAP ABAP Learning

Step 3: Click on Display Error Log

SAP ABAP Tutorial and Material, SAP ABAP Exam Prep, SAP ABAP Certifications, SAP ABAP Learning

3. Ability to reset in case of some error, and resolve the error inside the order


Along with the changes done above, I tried to make this report a bit more user-friendly. Below is the summary of the changes (without any code snippets- I leave it as an exercise for the reader).

◉ Instead of Delivery Creation, the button is called Simulation. When this button is clicked, its name changes to Delivery Creation. The BAPI creates the delivery but we don’t do a COMMIT WORK and don’t display the Delivery in the output.

◉ In case of an error, we still show the error log. We also show a Reset button at Ship-to node in the Sales Order Column.

◉ In case of no error, there would be no Reset button

SAP ABAP Tutorial and Material, SAP ABAP Exam Prep, SAP ABAP Certifications, SAP ABAP Learning

The user now has 2 options :-

1. Click on Delivery Creation button to continue with Delivery Creation of the possible orders. (In this case, all yellow nodes become green nodes and the delivery number gets displayed.)

2. Click on Reset button to do a ROLLBACK and reset everything. For the order with error, the user can now also navigate to the order, remove the error and try with the Simulation again.

The purpose of this post was to show that just with a few lines of code, the report output seems much nicer than the previous version.

No comments:

Post a Comment