Quantcast
Channel: ABAP Development
Viewing all 948 articles
Browse latest View live

How to properly convert SOLIX to XSTRING, without additional bytes

$
0
0

There are several use cases when you need to convert SOLIX table to XSTRING. For example, when you are using sap gateway for downloading files from SAP office, you will need to use conversion from solix to xstring.

 

You can use method cl_bcs_convert=>solix_to_xstring or FM  SCMS_BINARY_TO_XSTRING for this conversion.

 

Now, the issue: SOLIX is a table of RAW 255, that means every line of this table has 255 raw bytes. When you don't specify output length of XSTRING variable, empty raw bytes from the last line are converted as well which means that you will supply corrupted file. It is be readable in most cases, but for example Microsoft Word will show warning message that the file is corrupted, though it can be opened afterwards.

 

Solution is to set number of bytes that needs to be converted as can be seen in following example:

 

    DATA:      lt_file      TYPE solix_tab,      lv_length   TYPE i,      ls_doc_data TYPE sofolenti1.    CALL FUNCTION 'SO_DOCUMENT_READ_API1'      EXPORTING        document_id                = iv_id      IMPORTING        document_data             = ls_doc_data      TABLES        contents_hex               = lt_file      EXCEPTIONS        document_id_not_exist      = 1        operation_no_authorization = 2        x_error                    = 3        OTHERS                     = 4.    IF sy-subrc = 0.      lv_length = ls_doc_data-doc_size.      IF lt_att IS NOT INITIAL.        CALL FUNCTION 'SCMS_BINARY_TO_XSTRING'          EXPORTING            input_length = lv_length          IMPORTING            buffer       = ev_xstring_content          TABLES            binary_tab   = lt_file          EXCEPTIONS            failed       = 1            OTHERS       = 2.      ENDIF.    ENDIF.

Understanding and updating MIGO defaults

$
0
0

Recently I had a situation where I needed to update MIGOs defaults programatically. I tried to search for a solution but couldn't see it mentioned anywhere and hence decided to write it down . I hope that it'll be useful for anyone in a similar situation.

 

Background:


MIGO is a multi-purpose transaction which can be used for various activities in material management - performing goods movements, receipt of purchase order etc. It replaces many older transactions . There are others as well but to list down a few:

 

  • Post a goods receipt to a known purchase order (transaction MB01)
  • Change a material document from goods receipts (transaction MB02)
  • Display a material document from goods receipts (transaction MB03)
  • Cancel a material document from goods receipts (transaction MBST)

 

To understand why we may need to ever need to modify MIGO defaults, consider the below situation.

 

- A file arrives for processing in SAP ( say as a batch job ) .

 

- Initial goods movement is tried using a BAPI. If the processing fails , a user SM35 BDC session is created for processing which can be reprocessed by users ( the users are familiar with MIGO ). A custom transaction for reprocessing is created as the users couldn't be given access to SM35. SM35 allows you to display and potentially reprocess all sessions which can be tricky if the authorisations are a bit leaky.

 

1.png

 

 

The failed sessions can then be processed by a custom transaction - the SM35 sessions are persisted to a temporary table to be used by reprocessing transaction.

 

Problem:

 

Everything looks good in theory : all automatic postings are done in background and any errors can be handled by the custom transaction. However, while the user is performing the reprocessing the MIGO session, something interesting happens - if the user opens a parallel MIGO session and performs some other processing in parallel, the subsequent sessions start to fail in the custom transaction. Users could be processing multiple sessions sequentially and might go away and do some other movements in parallel in MIGO.

2.png

 

Why does this happen ?

 

MIGO stores user's defaults trying to optimise the usage so that the user doesn't have to set the selections - this causes the defaults to be always set whenever you use the transaction. The parallel session which the user opened has overridden the defaults and as a result, subsequent failed sessions have different default values set in the screen even though the BDC recording used in SM35 session was set correctly. User defaults is overriding BDC set values .

 

 

Looking at the below image, the BDC session has set values A07 and R10 for action and sub-selection within action.

MIGO3.png

 

However, if the user choses something else in a parallel session ( say A05 and a sub-selection ) , it overrides the action default and subsequent SM35 sessions start failing as then MIGO will start with A05 / sub-selection.

 

Solution:

 

MIGO stores user's defaults in table ESDUS and these defaults correspond to MIGO_FIRSTLINE action. Seeing the below table entries, the settings for the user are:

 

Default is action = A07

and sub-selection for A07 is R10.

 

Hence, A07 / R10 will appear for ACTION and sub-selection ( as shown in above image ) .

 

MIGO4.png

Show Me The Code:

 

Now, we know where they're stored, how to update them ?

 

Class CL_MMIM_USERDEFAULTS can be used to  read and update the parameters. It's a singleton and hence there should be only instance at a given time. Consequently, if we're using it we have to ensure the instance is destroyed . This is achieved by FLUSH_ALL method of the class. Above methods are self explanatory and the constructor requires ACTION value.

 

MIGO5.png

 

So I did the following:


- Instantiate the class using ACTION as "MIGO_FIRSTLINE" and set the instance values.


- Set the values:


o_migo_defaults->set

                                         ( i_element = 'ACTION'
                      i_active
= lv_action      ).

- Flush the value to dB and destroy the instance


o_migo_defaults->flush( ). "Save values to dB
o_migo_defaults
->flush_all( ). "Destroy this instance as others instance will start producing errors


The table has other values used in MIGO defaults ( e.g. default movement type for an action ) and can be similarly updated.

What Would a Developer-Friendly Application Log Look Like?

$
0
0

Do you ever have this problem?  You're in the middle of writing a fantastic ABAP program.  It hums, it purrs, it looks reeeeally good.  The code is so clean uncle Bob would be proud.  You use the best design patterns and your application is modularized well.  Then you realize you need to log a few lines of text to view later in SLG1.  Your heart sinks.  Your robust, clean application is about to become polluted by countless 10-line calls to FM 'BAL_LOG_CREATE' and its fellow hoodlums, all because you wanted to save the output of a BAPI you called.  First world problems, AMIRITE?  Anyone?


The obvious solution is to put an object-oriented wrapper around these functions.  This solution is so obvious, in fact, that every department in SAP has written their own verison of it, and my company has added two more.  This is consistent with SAP's approach to developer tools: Quantity Guaranteed.


So why do we need another OO wrapper?  Well, because I believe in the power of collaboration, for one.  None of the other logs have been designed by the developer community.  Another reason is that logs have come a long way since 1999, when SAP released their function modules for the application log, and developers are used to a more concise syntax.  For instance, if I want to write to the console in javascript, it's:


console.log('The System is Down');


but in SAP, I have to declare 2 variables, then spend a page of code calling function modules BAL_LOG_CREATE, BAL_LOG_MSG_ADD, and BAL_DB_SAVE.  Part of the reason is that SAP has multiple logs, while a web browser only logs messages in one console.  So when you log anything in SAP, it must go to a specific application log object and sub-object.  Android (java) also writes to multiple logs.  Its logs are called with two arguments, tag and message, like:


Log.e("SystemMsgs", "The system is Down");


If logging in ABAP was going to be just as awesome (and I know it can be), what would it look like?  Please post your ideas and discuss!  Once enough good ideas are gathered, I'd like to start building something on github.  Anyone is welcome to contribute code or just ideas and insights.  Here's an example what I think would make a good application log API:


log = zcl_app_log=>get( object = 'ZFICO' subobject = 'INTERFACES' ).

TRY.

    zcl_accounting=>do_some_important_upload( ).

    log->add( 'The interface has finished' ).

  CATCH zcx_upload_failed INTO err.

    log->add( err ).

ENDTRY.

Placing a long-text on an SAP CS Order

$
0
0

Currently i'm working in SAP CS implementing a mobile solution for Quality Assurance based on SAP CS Workorders.

 

One of my requirements was to store information on the order header of this CS order.

 

Looking at the function module BAPI_ALM_ORDER_MAINTAIN it has importing parameters allowing you to put in text:

 

text-tables.png

But soon I discovered that these text weren't showing up in the actual order header nor in the operations linked to the order.

 

So I needed a different approach, luckily SAP also provides us with SAVE_TEXT and READ_TEXT function modules.

 

I implemented a method saving the text for me:

 

method.png

 

Soon I discovered that the text I was changing using this function module turned up to be in the table STXH but still not showing up in IW33.

 

Therefore I debugged the logic behind the transaction itself and came to the conclusion that the longtext indicator on the order header (AUFK-LTEXT) wasn't properly used.

 

When looking at the domain value of the longtext it only allows true or false (X or blank):

 

longtext yes-no.png

But I found that iw32 was updating the field with the language key:

 

longtext N.png

 

Therefore, against everything I've been taught I added an additional line of code updating the longtext indicator directly:

 

update dbtab.png

 

This actually shows the longtext in iw33.

 

order.png

Why object oriented? Import parameters

$
0
0

There is a lot of discussion about why we should use object oriented programming instead of plain old procedural. I have to admit it is sometimes difficult to come up with good reasons, besides the "Try it and will find out", so I'll blog about the reasons as I come upon them starting with: import parameters.

 

Last week I wanted to reuse a custom method that creates an inventory in WM, and the definition was something like this (zwm_linv_t is at table type of LINV):

 

Methods Create_Inventory            importing !I_Link as LINK                            !IT_Linv as zwm_linv_t


Why is this less then optimum design?

 

Side Note: Before you say, "Wait, that already uses OO ABAP", remember that using classes doesn't make the code object oriented. Object orientation is about design principles, not whether you use function modules or classes.

 

With that out of the way, why could this be improved upon?

  1. From the interface I get very little information about exactly what parameters are required since LINK has a lot of fields,
  2. I can't use "Mandatory" keyword because it applies to the entire LINK structure;
  3. I have to look inside the code to know which fields to fill (which in this case were only 3: date, warehouse number and storage type).

 

A better design would be (ignoring LINV):

 

Methods Create_Inventory            importing !LGNUM TYPE LGNUM                            !LGTYP TYPE LGTYP                            !DATE TYPE DATUM

 

Again you might say "There are no objects there, that's not object oriented!"*. Well you could, but you would be missing the point. The main difference between procedural and pure OO is that in pure OO there are no structures, everything is a class. The fact that there no structures prevents you from doing stuff like the first example (and I've seen many people do that).

 

The main difference between structures and classes is that classes are responsible for their own consistency, while structures aren't very smart. In a class, the public and therefore "mandatory" attributes are filled in the constructor which makes sure that, IF an object (instance of a class) exists, those attributes have a value. With structures that doesn't happen, there's nothing to ensure it.

 

Let's make that clearer by replacing the method interface with one that uses an object with the attributes of LINK

 

Methods Create_Inventory            importing !I_INVENTORY TYPE REF TO ZWM_INVENTORY.


When I want to create an instance of the import parameter I need to use the constructor, which will give a clear indication of the mandatory parameters (lack of polymorphism makes this trickier but...moving along). Unlike the first example I don't need to guess, I don't need to look into the code, I just call a method with explicit parameters, and in return I get a "bullet proof" import param.

 

If you ever used a BAPI and had to look in the code or debug to know exactly which fields to fill, you've come across the reason why structures allow very bad design, and shouldn't be used in APIs. Saves some one time effort on the side of the programmer, by repeatedly passing it along to whoever uses the API.

 

 

*Actually in pure OO (Java, C#, Objective-C) LGNUM, LGTYP and DATUM would be classes, but let's take it one step at a time.

ABAP to XML and vice versa

$
0
0

I'm not entirely sure if a similar solution exists; I've tried searching through blog posts in the ABAP Development space and couldn't find any match (although there are over 1000 results for "abap xml").

 

Basically, more out of boredom and willing to learn more about the capabilities of the iXML Library, I made an ABAP/4 class that can transform any data (simple data, structure, table type) at any depth into XML format and vice versa. Even though, bringing XML to ABAP/4 only matches elements with the same name, ignoring extra data.

 

class.png

 

For example:

Given the following nested-structured local transparent table:

 

DATA :
  BEGIN OF ls_data.
        INCLUDE STRUCTURE spfli.
DATA :
      scarr   TYPE scarr,
      sflight TYPE sflight_tab1,
    END OF ls_data,
  lt_data LIKE TABLE OF ls_data.

 

Filled with test data:

 

SELECT *
       UP TO 10 ROWS
       FROM spfli
       INTO CORRESPONDING FIELDS OF TABLE lt_data.

FIELD-SYMBOLS : <fs_data> LIKE LINE OF lt_data.
LOOP AT lt_data ASSIGNING <fs_data>.
  SELECT SINGLE *
                FROM scarr
                INTO CORRESPONDING FIELDS OF <fs_data>-scarr
                WHERE carrid = <fs_data>-carrid.

  SELECT *
         FROM sflight
         INTO CORRESPONDING FIELDS OF TABLE <fs_data>-sflight
         WHERE carrid = <fs_data>-carrid
           AND connid = <fs_data>-connid.
ENDLOOP.

 

The result is:

data.png

The class looks as follows (I will also attach it as text at the bottom together with the demo report):

 

class ZCL_XML_UTIL definition
  public
  create public .

public section.
*"* public components of class ZCL_XML_UTIL
*"* do not include other source files here!!!

  methods CONSTRUCTOR .
  methods ABAP_TO_XML
    importing
      !IM_DATA type ANY
    exporting
      !EX_CONTENT type XSTRING
    raising
      ZCX_TYPE_NOT_SUPPORTED .
  methods XML_TO_ABAP
    importing
      !IM_CONTENT type XSTRING
    exporting
      !EX_DATA type ANY
    raising
      ZCX_TYPE_NOT_SUPPORTED .
protected section.
*"* protected components of class ZCL_XML_UTIL
*"* do not include other source files here!!!

  data MO_IXML type ref to IF_IXML .
  data MO_DOCUMENT type ref to IF_IXML_DOCUMENT .
private section.
*"* private components of class ZCL_XML_UTIL
*"* do not include other source files here!!!

  methods PROCESS
    importing
      !IM_NODE type ref to IF_IXML_NODE
      !IM_NAME type STRING
      !IM_DATA type ANY
    raising
      ZCX_TYPE_NOT_SUPPORTED .
  methods CREATE_ELEMENT
    importing
      !IM_NAME type STRING
      !IM_VALUE type STRING optional
    returning
      value(RE_ELEMENT) type ref to IF_IXML_ELEMENT .
  methods TRAVERSE
    importing
      !IM_CURRENT_NODE type ref to IF_IXML_NODE
    changing
      !CH_DATA type ANY
    raising
      ZCX_TYPE_NOT_SUPPORTED .
  class-methods AS_STRING
    importing
      !IM_DATA type ANY
    returning
      value(RE_DATA_STRING) type STRING .
ENDCLASS.



CLASS ZCL_XML_UTIL IMPLEMENTATION.


* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Public Method ZCL_XML_UTIL->ABAP_TO_XML
* +-------------------------------------------------------------------------------------------------+
* | [--->] IM_DATA                        TYPE        ANY
* | [<---] EX_CONTENT                     TYPE        XSTRING
* | [!CX!] ZCX_TYPE_NOT_SUPPORTED
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD abap_to_xml.

  CLEAR : ex_content.

* create .xml document
  mo_document = mo_ixml->create_document( ).

* set encoding to UTF-8 (Unicode Transformation Format)
* 8-bit variable-width encoding maximizes compatibility with ASCII
  mo_document->set_encoding( mo_ixml->create_encoding(
      byte_order    = 0
      character_set = 'UTF-8' ) ).

  IF NOT im_data IS INITIAL.
*   use mo_document as root
    process( im_node = mo_document
             im_name = 'data'
             im_data = im_data ).
  ENDIF.

* render .xml document with output stream
  mo_document->render(
    ostream = mo_ixml->create_stream_factory( )->create_ostream_xstring(
      string = ex_content ) ).
  FREE : mo_document.

ENDMETHOD.


* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Static Private Method ZCL_XML_UTIL=>AS_STRING
* +-------------------------------------------------------------------------------------------------+
* | [--->] IM_DATA                        TYPE        ANY
* | [<-()] RE_DATA_STRING                 TYPE        STRING
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD as_string.

  MOVE im_data TO re_data_string.

ENDMETHOD.


* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Public Method ZCL_XML_UTIL->CONSTRUCTOR
* +-------------------------------------------------------------------------------------------------+
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD CONSTRUCTOR.

* get iXML library instance
  mo_ixml = cl_ixml=>create( ).

ENDMETHOD.


* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Private Method ZCL_XML_UTIL->CREATE_ELEMENT
* +-------------------------------------------------------------------------------------------------+
* | [--->] IM_NAME                        TYPE        STRING
* | [--->] IM_VALUE                       TYPE        STRING(optional)
* | [<-()] RE_ELEMENT                     TYPE REF TO IF_IXML_ELEMENT
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD CREATE_ELEMENT.

  DATA :
    lv_name TYPE string VALUE IS INITIAL.

* element names look cooler in lower case
  lv_name = to_lower( im_name ).

* create element with given name
  re_element = mo_document->create_element( name = lv_name ).

* if element is leaf, set corresponding value
  IF im_value IS SUPPLIED.
    re_element->set_value( im_value ).
  ENDIF.

ENDMETHOD.


* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Private Method ZCL_XML_UTIL->PROCESS
* +-------------------------------------------------------------------------------------------------+
* | [--->] IM_NODE                        TYPE REF TO IF_IXML_NODE
* | [--->] IM_NAME                        TYPE        STRING
* | [--->] IM_DATA                        TYPE        ANY
* | [!CX!] ZCX_TYPE_NOT_SUPPORTED
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD process.

  DATA :
    lo_type    TYPE REF TO cl_abap_typedescr,
    lo_struct  TYPE REF TO cl_abap_structdescr,
    lo_table   TYPE REF TO cl_abap_tabledescr,
    lo_data    TYPE REF TO data,
    lo_element TYPE REF TO if_ixml_element,
    lv_name    TYPE string VALUE IS INITIAL.

  FIELD-SYMBOLS :
    <ft_data>      TYPE STANDARD TABLE,
    <fs_component> TYPE abap_compdescr,
    <fs_data>      TYPE any,
    <fv_value>     TYPE any.

* determine ABAP/4 data type, support only:
* - simple type
* - structure
* - table type
  lo_type = cl_abap_typedescr=>describe_by_data( im_data ).

  CASE lo_type->kind.
    WHEN cl_abap_typedescr=>kind_elem.
*     create element
      lo_element = create_element(
          im_name  = im_name
          im_value = as_string( im_data ) ).

    WHEN cl_abap_typedescr=>kind_struct.
*     create parent element
      lo_element = create_element( im_name = im_name ).

*     process each structure component independently
      lo_struct ?= cl_abap_structdescr=>describe_by_data( im_data ).
      LOOP AT lo_struct->components ASSIGNING <fs_component>.
        ASSIGN COMPONENT <fs_component>-name OF STRUCTURE im_data TO <fv_value>.
        CHECK sy-subrc IS INITIAL.

        lv_name = <fs_component>-name.

        process( im_node = lo_element
                 im_name = lv_name
                 im_data = <fv_value> ).
      ENDLOOP.

    WHEN cl_abap_typedescr=>kind_table.
      lo_table ?= cl_abap_tabledescr=>describe_by_data( im_data ).
      CREATE DATA lo_data TYPE HANDLE lo_table.
      ASSIGN lo_data->* TO <ft_data>.
      <ft_data> = im_data.

*     create parent element
      lo_element = create_element( im_name = im_name ).

*     process each table line independently
      LOOP AT <ft_data> ASSIGNING <fs_data>.
       
process( im_node = lo_element
                 im_name = im_name
                 im_data = <fs_data> ).
     
ENDLOOP.

    WHEN OTHERS.
      TRY.
        RAISE EXCEPTION TYPE zcx_type_not_supported.
      ENDTRY.
  ENDCASE.

  IF lo_element IS BOUND.
    im_node->append_child( lo_element ).
  ENDIF.

ENDMETHOD.


* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Private Method ZCL_XML_UTIL->TRAVERSE
* +-------------------------------------------------------------------------------------------------+
* | [--->] IM_CURRENT_NODE                TYPE REF TO IF_IXML_NODE
* | [<-->] CH_DATA                        TYPE        ANY
* | [!CX!] ZCX_TYPE_NOT_SUPPORTED
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD traverse.

  DATA :
    lo_type      TYPE REF TO cl_abap_typedescr,
    lo_struct    TYPE REF TO cl_abap_structdescr,
    lo_table     TYPE REF TO cl_abap_tabledescr,
    lo_data      TYPE REF TO data,
    lo_iterator  TYPE REF TO if_ixml_node_iterator,
    lo_node_list TYPE REF TO if_ixml_node_list,
    lo_node      TYPE REF TO if_ixml_node,
    lv_node_name TYPE string VALUE IS INITIAL,
    lv_match     TYPE boolean.

  FIELD-SYMBOLS :
    <ft_data>      TYPE STANDARD TABLE,
    <fs_component> TYPE abap_compdescr,
    <fs_data>      TYPE any,
    <fv_value>     TYPE any.

* determine ABAP/4 data type, support only:
* - simple type
* - structure
* - table type
  lo_type = cl_abap_typedescr=>describe_by_data( ch_data ).

  CASE lo_type->kind.
    WHEN cl_abap_typedescr=>kind_elem.
      ch_data = im_current_node->get_value( ).

    WHEN cl_abap_typedescr=>kind_struct.
      lo_node_list = im_current_node->get_children( ).

*     process each structure component independently
      lo_struct ?= cl_abap_structdescr=>describe_by_data( ch_data ).
      LOOP AT lo_struct->components ASSIGNING <fs_component>.
        ASSIGN COMPONENT <fs_component>-name OF STRUCTURE ch_data TO <fv_value>.
        CHECK sy-subrc IS INITIAL.

*       create new iterator with each step
        lo_iterator = lo_node_list->create_iterator( ).
        lo_node = lo_iterator->get_next( ).

        WHILE lo_node  IS BOUND
           OR lv_match EQ abap_true.

*         xml element names can contain lower case characters
          lv_node_name = to_upper( lo_node->get_name( ) ).

          IF lv_node_name EQ <fs_component>-name.
            lv_match = abap_true.
            EXIT.
          ENDIF.

          lo_node = lo_iterator->get_next( ).
        ENDWHILE.

        CHECK lv_match EQ abap_true.
        traverse( EXPORTING im_current_node = lo_node
                  CHANGING  ch_data         = <fv_value> ).
        CLEAR : lv_match.
      ENDLOOP.

    WHEN cl_abap_typedescr=>kind_table.
      lo_table ?= cl_abap_tabledescr=>describe_by_data( ch_data ).

      CREATE DATA lo_data TYPE HANDLE lo_table.
      ASSIGN lo_data->* TO <ft_data>.

      lo_node_list = im_current_node->get_children( ).
      lo_iterator = lo_node_list->create_iterator( ).
      lo_node = lo_iterator->get_next( ).

      WHILE lo_node IS BOUND.
        APPEND INITIAL LINE TO <ft_data> ASSIGNING <fs_data>.

        traverse( EXPORTING im_current_node = lo_node
                  CHANGING  ch_data         = <fs_data> ).

        lo_node = lo_iterator->get_next( ).
      ENDWHILE.

      IF NOT <ft_data> IS INITIAL.
        ch_data = <ft_data>.
      ENDIF.

    WHEN OTHERS.
      TRY.
        RAISE EXCEPTION TYPE zcx_type_not_supported.
      ENDTRY.
  ENDCASE.

ENDMETHOD.


* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Public Method ZCL_XML_UTIL->XML_TO_ABAP
* +-------------------------------------------------------------------------------------------------+
* | [--->] IM_CONTENT                     TYPE        XSTRING
* | [<---] EX_DATA                        TYPE        ANY
* | [!CX!] ZCX_TYPE_NOT_SUPPORTED
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD xml_to_abap.

  CLEAR : ex_data.

  DATA :
    lo_istream        TYPE REF TO if_ixml_istream,
    lo_stream_factory TYPE REF TO if_ixml_stream_factory,
    lo_iterator       TYPE REF TO if_ixml_node_iterator.

  ASSERT NOT im_content IS INITIAL.

  IF NOT ex_data IS REQUESTED.
    RETURN.
  ENDIF.

* create .xml document
  mo_document = mo_ixml->create_document( ).

  lo_stream_factory = mo_ixml->create_stream_factory( ).
  lo_istream = lo_stream_factory->create_istream_xstring( im_content ).

* parse .xml document with given input stream
  IF mo_ixml->create_parser(
      document       = mo_document
      istream        = lo_istream
      stream_factory = lo_stream_factory )->parse( ) IS INITIAL.

*   create iterator
    lo_iterator = mo_document->create_iterator( 0 ).

*   skip #document element
    lo_iterator->get_next( ).

*   start with root
    traverse( EXPORTING im_current_node = lo_iterator->get_next( )
              CHANGING  ch_data         = ex_data ).
  ENDIF.
  FREE : mo_document.

ENDMETHOD.
ENDCLASS.

 

Feedback is most welcomed!

 

Tudor

Simple PPTX export with template

$
0
0

The idea behind this trivial solution is the possibility to export text data from an SAP system into PowerPoint format using a given template. In my experience, multinational companies usually have a default PowerPoint template for presentations, which includes: the big company logo, strict rules for what should the header and the footer include, and so on. These are particularly important for status update presentations in different areas.

Although I had this requirement in the PPM sector, that is, Portfolio and Project Management, I chose to post this in ABAP development as it might be used in other areas as well.

template.png

As a prerequisite, this solution only works beginning with Office 2007 as it requires Office Open XML. Therefore, the iXML Library must be available on the SAP Application Server.

 

The following steps need to be performed in order to achieve a first test:

1. Create a demo template.

It is mandatory to set a custom property for each of the slides in the presentation, with:

name = "&&slide*&&"

type = "text"

value = "slide*"

(where * represents the slide number)

Advanced Properties.png

2. Create an ABAP/4 class with the following source-code or use the one attached bellow:

 

class ZCL_PPTX definition
  public
  create public .

public section.
*"* public components of class ZCL_PPTX
*"* do not include other source files here!!!

  constants MC_SCHEMA type STRING value 'http://schemas.openxmlformats.org/drawingml/2006/main'. "#EC NOTEXT

  methods CONSTRUCTOR
    importing
      !IM_V_CONTENT type XSTRING
    raising
      ZCX_PPTX .
  methods GENERATE
    importing
      !IM_T_TEXTS type ZXFILE_T_NAME_VALUE_PAIR
    returning
      value(RE_V_FILE) type XSTRING .
protected section.
*"* protected components of class ZCL_PPTX
*"* do not include other source files here!!!

  data MO_ZIP type ref to CL_ABAP_ZIP .
  data MO_IXML type ref to IF_IXML .
  data MO_IXML_DOCPROPS_CUSTOM type ref to IF_IXML_DOCUMENT .

  methods GET_FILE
    importing
      !IM_V_FILEPATH type STRING
    returning
      value(RE_O_DOCUMENT) type ref to IF_IXML_DOCUMENT .
  methods UPDATE_FILE
    importing
      !IM_V_FILEPATH type STRING
      !IM_O_DOCUMENT type ref to IF_IXML_DOCUMENT .
  methods ADD_FILE
    importing
      !IM_V_FILEPATH type STRING
      !IM_O_DOCUMENT type ref to IF_IXML_DOCUMENT .
  methods UPDATE_TEXTS
    importing
      !IM_T_TEXTS type ZXFILE_T_NAME_VALUE_PAIR
      !IM_O_IXML_NODE type ref to IF_IXML_NODE .
  methods GET_INDICATORS
    returning
      value(RE_T_INDICATORS) type ZXFILE_T_NAME_VALUE_PAIR .
private section.
*"* private components of class ZCL_PPTX
*"* do not include other source files here!!!
ENDCLASS.



CLASS ZCL_PPTX IMPLEMENTATION.


* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Protected Method ZCL_PPTX->ADD_FILE
* +-------------------------------------------------------------------------------------------------+
* | [--->] IM_V_FILEPATH                  TYPE        STRING
* | [--->] IM_O_DOCUMENT                  TYPE REF TO IF_IXML_DOCUMENT
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD ADD_FILE.

  DATA :
    lo_ostream  TYPE REF TO if_ixml_ostream,
    lv_content  TYPE xstring.

* create output stream
  lo_ostream = mo_ixml->create_stream_factory( )->create_ostream_xstring( string = lv_content ).

* set encoding to UTF-8 (Unicode Transformation Format)
* 8-bit variable-width encoding maximizes compatibility with ASCII
  lo_ostream->set_encoding( encoding = mo_ixml->create_encoding( character_set = 'UTF-8' byte_order = 0 ) ).

* Set Pretty Print
  lo_ostream->set_pretty_print( abap_true ).

* render document
  mo_ixml->create_renderer( ostream = lo_ostream document = im_o_document )->render( ).

* add file
  mo_zip->add( name = im_v_filepath content = lv_content ).

ENDMETHOD.


* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Public Method ZCL_PPTX->CONSTRUCTOR
* +-------------------------------------------------------------------------------------------------+
* | [--->] IM_V_CONTENT                  TYPE        XSTRING
* | [!CX!] ZCX_PPTX
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD constructor.

  IF im_v_content IS INITIAL.
    TRY.
      RAISE EXCEPTION TYPE zcx_pptx.
    ENDTRY.
  ENDIF.

* get iXML library instance
  mo_ixml = cl_ixml=>create( ).

* load OpenXML document
  CREATE OBJECT mo_zip.
  mo_zip->load(
    EXPORTING
      zip            = im_v_content
    EXCEPTIONS
      zip_parse_error = 1
      OTHERS          = 2 ).

  IF NOT sy-subrc IS INITIAL.
    MESSAGE ID sy-msgid TYPE sy-msgty NUMBER sy-msgno
              WITH sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4.
  ENDIF.

  mo_ixml_docprops_custom = get_file( im_v_filepath = 'docProps/custom.xml' ).

ENDMETHOD.


* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Public Method ZCL_PPTX->GENERATE
* +-------------------------------------------------------------------------------------------------+
* | [--->] IM_T_TEXTS                    TYPE        ZXFILE_T_NAME_VALUE_PAIR
* | [<-()] RE_V_FILE                      TYPE        XSTRING
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD generate.

  CLEAR : re_v_file.

  DATA :
    lo_ixml_document      TYPE REF TO if_ixml_document,
    lo_ixml_document_rels TYPE REF TO if_ixml_document,
    lt_indicators        TYPE zxfile_t_name_value_pair,
    lt_files              TYPE cl_abap_zip=>t_files,
    lv_dummy              TYPE string,                      "#EC NEEDED
    lv_slide              TYPE string,
    lv_value              TYPE string,
    lv_filename          TYPE string,
    lv_filename_rels      TYPE string.

  lt_indicators[] = get_indicators( ).
  lt_files[]      = mo_zip->files[].

  FIELD-SYMBOLS : <fs_file> TYPE cl_abap_zip=>t_file.
  LOOP AT lt_files[] ASSIGNING <fs_file>
                    WHERE name CP 'ppt/slides/slide*.xml'.
    CLEAR:
      lv_dummy,
      lv_slide,
      lv_value,
      lv_filename,
      lv_filename_rels.

    SPLIT <fs_file>-name AT 'ppt/slides/' INTO lv_dummy lv_slide.
    SPLIT lv_slide      AT '.'          INTO lv_value lv_dummy.

    FIELD-SYMBOLS : <fs_indicator> TYPE zxfile_s_name_value_pair.
    READ TABLE lt_indicators[] ASSIGNING <fs_indicator>
                              WITH KEY value = lv_value.
    IF NOT sy-subrc IS INITIAL.
*    file not relevant, process next
      CONTINUE.
    ENDIF.

*  get .xml file
    lo_ixml_document = get_file( im_v_filepath = <fs_file>-name ).

*  get .rels file
    CONCATENATE 'ppt/slides/_rels/' lv_value '.xml.rels' INTO lv_filename_rels.
    lo_ixml_document_rels = get_file( im_v_filepath = lv_filename_rels ).

*  check indicators
    IF NOT <fs_indicator>-name IS INITIAL.
      update_texts( EXPORTING im_t_texts    = im_t_texts[]
                              im_o_ixml_node = lo_ixml_document ).

    ENDIF.

*  trigger update
    lv_filename = <fs_file>-name.
    update_file( im_v_filepath = lv_filename      im_o_document = lo_ixml_document ).
    update_file( im_v_filepath = lv_filename_rels im_o_document = lo_ixml_document_rels ).
  ENDLOOP.

  re_v_file = mo_zip->save( ).

ENDMETHOD.


* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Protected Method ZCL_PPTX->GET_FILE
* +-------------------------------------------------------------------------------------------------+
* | [--->] IM_V_FILEPATH                  TYPE        STRING
* | [<-()] RE_O_DOCUMENT                  TYPE REF TO IF_IXML_DOCUMENT
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD get_file.

  DATA :
    lo_stream_factory TYPE REF TO if_ixml_stream_factory,
    lo_istream        TYPE REF TO if_ixml_istream,
    lv_content        TYPE xstring.

  mo_zip->get( EXPORTING  name                    = im_v_filepath
              IMPORTING  content                = lv_content
              EXCEPTIONS zip_index_error        = 1
                          zip_decompression_error = 2
                          OTHERS                  = 3 ).

  IF NOT sy-subrc IS INITIAL.
    RETURN.
  ENDIF.

* create the document
  re_o_document = mo_ixml->create_document( ).

* create the stream factory
  lo_stream_factory = mo_ixml->create_stream_factory( ).

* create the input stream
  lo_istream = lo_stream_factory->create_istream_xstring( lv_content ).

* parse document
  IF NOT mo_ixml->create_parser( document      = re_o_document
                                istream        = lo_istream
                                stream_factory = lo_stream_factory )->parse( ) is INITIAL.
    CLEAR : re_o_document.
  ENDIF.

ENDMETHOD.


* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Protected Method ZCL_PPTX->GET_INDICATORS
* +-------------------------------------------------------------------------------------------------+
* | [<-()] RE_T_INDICATORS                TYPE        ZXFILE_T_NAME_VALUE_PAIR
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD GET_INDICATORS.

  REFRESH : re_t_indicators[].

  DATA :
    lo_ixml_iterator  TYPE REF TO if_ixml_node_iterator,
    lo_ixml_element    TYPE REF TO if_ixml_element,
    ls_indicator      TYPE zxfile_s_name_value_pair.

* get the corresponding entries in the custom .xml to control the generation
  lo_ixml_iterator  = mo_ixml_docprops_custom->get_elements_by_tag_name( name = 'property' )->create_iterator( ).

* get the first element
  lo_ixml_element ?= lo_ixml_iterator->get_next( ).
  WHILE lo_ixml_element IS BOUND.
    CLEAR ls_indicator.
*  get name
    ls_indicator-name = condense( lo_ixml_element->get_attribute( name = 'name' ) ).

    IF ls_indicator-name CP '&&*&&'.
*    get value
      lo_ixml_element    = lo_ixml_element->find_from_name( name = 'lpwstr' namespace = 'vt' ).
      ls_indicator-value = condense( val = lo_ixml_element->get_value( ) ).

*    insert indicator
      INSERT ls_indicator INTO TABLE re_t_indicators[].
    ENDIF.

*  get next element
    lo_ixml_element ?= lo_ixml_iterator->get_next( ).
  ENDWHILE.

ENDMETHOD.


* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Protected Method ZCL_PPTX->UPDATE_FILE
* +-------------------------------------------------------------------------------------------------+
* | [--->] IM_V_FILEPATH                  TYPE        STRING
* | [--->] IM_O_DOCUMENT                  TYPE REF TO IF_IXML_DOCUMENT
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD UPDATE_FILE.

  mo_zip->delete( EXPORTING  name            = im_v_filepath
                  EXCEPTIONS zip_index_error = 1
                            OTHERS          = 2 ).

  IF NOT sy-subrc IS INITIAL.
    MESSAGE ID sy-msgid TYPE sy-msgty NUMBER sy-msgno
              WITH sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4.
  ENDIF.

  IF im_o_document IS BOUND.
    add_file( im_v_filepath = im_v_filepath im_o_document = im_o_document ).
  ENDIF.

ENDMETHOD.


* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Protected Method ZCL_PPTX->UPDATE_TEXTS
* +-------------------------------------------------------------------------------------------------+
* | [--->] IM_T_TEXTS                    TYPE        ZXFILE_T_NAME_VALUE_PAIR
* | [--->] IM_O_IXML_NODE                TYPE REF TO IF_IXML_NODE
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD UPDATE_TEXTS.

  DATA :
    lo_classdescr    TYPE REF TO cl_abap_classdescr,
    lo_exception    TYPE REF TO cx_sy_ref_is_initial,
    lo_ixml_texts    TYPE REF TO if_ixml_node_collection,
    lo_ixml_iterator TYPE REF TO if_ixml_node_iterator,
    lo_ixml_document TYPE REF TO if_ixml_document,
    lo_ixml_element  TYPE REF TO if_ixml_element,
    lo_ixml_node    TYPE REF TO if_ixml_node,
    lv_message      TYPE string,
    lv_text          TYPE string,
    lv_result        TYPE string.

  TRY.
*    determine all texts of the corresponding node
      lo_classdescr ?= cl_abap_classdescr=>describe_by_object_ref( im_o_ixml_node ).
      READ TABLE lo_classdescr->interfaces WITH KEY name = 'IF_IXML_DOCUMENT'
                                          TRANSPORTING NO FIELDS.
      IF sy-subrc IS INITIAL.
*      document
        lo_ixml_document ?= im_o_ixml_node.
        lo_ixml_texts    = lo_ixml_document->get_elements_by_tag_name_ns(
                              name = 't'
                              uri  = mc_schema ).

      ELSE.
        READ TABLE lo_classdescr->interfaces WITH KEY name = 'IF_IXML_ELEMENT'
                                            TRANSPORTING NO FIELDS.
        IF sy-subrc IS INITIAL.
*        element
          lo_ixml_element ?= im_o_ixml_node.
          lo_ixml_texts    = lo_ixml_element->get_elements_by_tag_name_ns(
                              name = 't'
                              uri  = mc_schema ).
        ELSE.
*        current object not supported
          RETURN.
        ENDIF.
      ENDIF.

*    get iterator
      lo_ixml_iterator = lo_ixml_texts->create_iterator( ).

*    get first node
      lo_ixml_node = lo_ixml_iterator->get_next( ).

      WHILE lo_ixml_node IS BOUND.
*      update slide
        lv_text = lo_ixml_node->get_value( ).

*      replace the corresponding text
        FIELD-SYMBOLS : <fs_text> TYPE zxfile_s_name_value_pair.
        LOOP AT im_t_texts[] ASSIGNING <fs_text>.
*        update component
          CONCATENATE '&&' <fs_text>-name '&&' INTO lv_result.
          REPLACE lv_result IN lv_text WITH <fs_text>-value.
        ENDLOOP.

*      set updated text
        lo_ixml_node->set_value( lv_text ).

*      get next node
        lo_ixml_node = lo_ixml_iterator->get_next( ).
      ENDWHILE.
    CATCH cx_sy_ref_is_initial INTO lo_exception.
      IF lo_exception->is_resumable EQ abap_false.
        lv_message = lo_exception->get_text( ).
        MESSAGE lv_message TYPE 'X'.
      ENDIF.
  ENDTRY.

ENDMETHOD.
ENDCLASS.

 

3. Create a demo report with the following source-code or use the one attached bellow:

 

REPORT zdemo_pptx.

DATA :
  gv_data TYPE xstring.

PARAMETERS :
  p_file TYPE localfile.

AT SELECTION-SCREEN ON VALUE-REQUEST FOR p_file.
  PERFORM open.

START-OF-SELECTION.
  PERFORM upload.
  PERFORM generate.
  PERFORM download.
END-OF-SELECTION.

FORM upload.
  DATA :
    lt_file      TYPE solix_tab,
    lv_filename  TYPE string,
    lv_filelength TYPE i.

  lv_filename = p_file.

  CALL METHOD cl_gui_frontend_services=>gui_upload
    EXPORTING
      filename                = lv_filename
      filetype                = 'BIN'
    IMPORTING
      filelength              = lv_filelength
    CHANGING
      data_tab                = lt_file
    EXCEPTIONS
      file_open_error        = 1
      file_read_error        = 2
      no_batch                = 3
      gui_refuse_filetransfer = 4
      invalid_type            = 5
      no_authority            = 6
      unknown_error          = 7
      bad_data_format        = 8
      header_not_allowed      = 9
      separator_not_allowed  = 10
      header_too_long        = 11
      unknown_dp_error        = 12
      access_denied          = 13
      dp_out_of_memory        = 14
      disk_full              = 15
      dp_timeout              = 16
      not_supported_by_gui    = 17
      error_no_gui            = 18
      OTHERS                  = 19.

  IF NOT sy-subrc IS INITIAL.
    MESSAGE ID sy-msgid TYPE sy-msgty NUMBER sy-msgno
              WITH sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4.
  ENDIF.

  CALL FUNCTION 'SCMS_BINARY_TO_XSTRING'
    EXPORTING
      input_length = lv_filelength
    IMPORTING
      buffer      = gv_data
    TABLES
      binary_tab  = lt_file
    EXCEPTIONS
      failed      = 1
      OTHERS      = 2.

  IF NOT sy-subrc IS INITIAL.
    MESSAGE ID sy-msgid TYPE sy-msgty NUMBER sy-msgno
              WITH sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4.
  ENDIF.
ENDFORM.

FORM generate.
  DATA :
    lo_pptx  TYPE REF TO zcl_pptx,
    lt_texts TYPE zxfile_t_name_value_pair,
    ls_text  TYPE zxfile_s_name_value_pair.

  TRY.
      CREATE OBJECT lo_pptx
        EXPORTING
          im_v_content = gv_data.
    CATCH zcx_pptx.
      MESSAGE text-001 TYPE 'E'.
  ENDTRY.

  ls_text-name  = 'TITLE'.
  ls_text-value = 'Automatically generated
PowerPoint'.
  APPEND ls_text TO lt_texts. CLEAR ls_text.

  ls_text-name  = '
SUBTITLE'.
  ls_text-value = '
using very little coding'.
  APPEND ls_text TO lt_texts. CLEAR ls_text.

  ls_text-name  = 'NAME'.
  ls_text-value = '
Your name (Ex. John Dr. Smith)'.
  APPEND ls_text TO lt_texts. CLEAR ls_text.

  ls_text-name  = 'DEPARTMENT'.
  ls_text-value = '
Your department name or abbreviation'.
  APPEND ls_text TO lt_texts. CLEAR ls_text.

  ls_text-name  = 'DATE'.
  ls_text-value =
sy-datum.
  APPEND ls_text TO lt_texts. CLEAR ls_text.


  CLEAR gv_data.
  gv_data = lo_pptx->generate( lt_texts ).
ENDFORM.

FORM download.
  DATA :
    lt_file      TYPE solix_tab,
    lv_filelength TYPE i,
    lv_filename  TYPE string,
    lv_fullpath  TYPE string,
    lv_path      TYPE string.

  CALL FUNCTION 'SCMS_XSTRING_TO_BINARY'
    EXPORTING
      buffer        = gv_data
    IMPORTING
      output_length = lv_filelength
    TABLES
      binary_tab    = lt_file.

  CALL METHOD cl_gui_frontend_services=>file_save_dialog
    CHANGING
      filename                  = lv_filename
      path                      = lv_path
      fullpath                  = lv_fullpath
    EXCEPTIONS
      cntl_error                = 1
      error_no_gui              = 2
      not_supported_by_gui      = 3
      invalid_default_file_name = 4
      OTHERS                    = 5.

  IF NOT sy-subrc IS INITIAL.
    MESSAGE ID sy-msgid TYPE sy-msgty NUMBER sy-msgno
              WITH sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4.
  ENDIF.

  TRANSLATE lv_fullpath TO UPPER CASE.
  IF lv_fullpath NS '.PPTX'.
    CONCATENATE lv_fullpath '.PPTX' INTO lv_fullpath.
  ENDIF.

  CALL METHOD cl_gui_frontend_services=>gui_download
    EXPORTING
      bin_filesize            = lv_filelength
      filename                = lv_fullpath
      filetype                = 'BIN'
    CHANGING
      data_tab                = lt_file
    EXCEPTIONS
      file_write_error        = 1
      no_batch                = 2
      gui_refuse_filetransfer = 3
      invalid_type            = 4
      no_authority            = 5
      unknown_error          = 6
      header_not_allowed      = 7
      separator_not_allowed  = 8
      filesize_not_allowed    = 9
      header_too_long        = 10
      dp_error_create        = 11
      dp_error_send          = 12
      dp_error_write          = 13
      unknown_dp_error        = 14
      access_denied          = 15
      dp_out_of_memory        = 16
      disk_full              = 17
      dp_timeout              = 18
      file_not_found          = 19
      dataprovider_exception  = 20
      control_flush_error    = 21
      not_supported_by_gui    = 22
      error_no_gui            = 23
      OTHERS                  = 24.

  IF NOT sy-subrc IS INITIAL.
    MESSAGE ID sy-msgid TYPE sy-msgty NUMBER sy-msgno
              WITH sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4.
  ENDIF.
ENDFORM.

FORM open.
  CALL FUNCTION 'F4_FILENAME'
    EXPORTING
      program_name  = syst-cprog
      dynpro_number = syst-dynnr
      field_name    = space
    IMPORTING
      file_name    = p_file.
ENDFORM.

In the future, based on new requirements, I plan to extend this class to also include tables, new slides, and images. But if you get ahead of me, please feel free to add your code on SCN and give me a hint!

 

Tudor

FitNesse - The fully integrated standalone wiki and acceptance testing framework

$
0
0

Quite a while ago I read Uncle Bob's Clean Code: A Handbook of Agile Software Craftsmanship and learned a little bit about FitNesse.

 

"It's a Collaboration Tool"

"It's a Test Tool"

"It's Open"

 

I'm not going to go in to why you should use FitNesse. You can read a bit about it at www.fitnesse.org. What I am going to talk about today is the work I've done on ABAPSlim and how you can go about setting up your own FitNesse wiki to run tests against SAP.

 

If you want to jump straight to the code you can head over to the ABAPSlim GitHub page.

 

Slim is the protocol that FitNesse uses to talk to your SUT (System Under Test). So far I'd say ABAPSlim is about 1/3rd done. It's easy to see this since I've copied FitNesse's standard set of tests for Slim and adapted them slightly for my implementation of ABAPSlim. When the following tests are all green then we will be able to use all the features of FitNesse.

 

Working tests.PNG

 

Right now, only the basic functionality is working. You can instantiate a class (zcl_fixture_division), you can call setter methods to set up data in your test fixture (numerator and denominator) and you can call getter methods and make assertions based on the result (quotient?). It's not shown here, but you can also pass in arguments to the constructor by adding columns in the first line where the class name is...

a test.PNG

 

The class zcl_fixture_division is included in the Saplink Nugget file on the ABAPSlim github page but here's what it looks like in case you're curious. Note that the columns without question marks (numerator, denominator) are setters so in the fixture class they need to be called setnumerator and setdenominator. The method with the question mark (quotient?) doesn't need the set in front of it but it does need to have a returning parameter.

CLASS zcl_fixture_division DEFINITION

PUBLIC

FINAL

CREATE PUBLIC .

 

PUBLIC SECTION.

   METHODS: setnumerator IMPORTING num TYPE S_PRICE,

                        setdenominator IMPORTING den TYPE S_PRICE,

                        quotient RETURNING VALUE(quotient) TYPE S_PRICE.

 

 

PRIVATE SECTION.

   DATA: _numerator TYPE S_PRICE,

                  _denominator TYPE S_PRICE.

ENDCLASS.

 

CLASS ZCL_FIXTURE_DIVISION IMPLEMENTATION.

  method QUOTIENT.

      quotient = _numerator / _denominator.

  endmethod.

 

  method SETDENOMINATOR.

      me->_denominator = den.

  endmethod.

 

  method SETNUMERATOR.

      me->_numerator = num.

  endmethod.

ENDCLASS.

 

So how can you get this set up and play with it yourself?

 

1. Head over to the ABAPSlim GitHub page and download everything.

2. Install the NUGG_ZSLIM.nugg Nugget using Saplink

3. Extract the Fitnesse folder to your C: drive

4. Edit the file NSP.jcoDestination and put in your own SAP system details and credentials

5. Rename the file NSP.jcoDestination and replace NSP with your SAP instance SID

6. Run fitnesse-standalone.jar (you can run it from a command line with "java -jar fitnesse-standalone.jar")

7. Browse to http://localhost and you should see the following:

 

fitnesse.PNG

 

8. The last step will be to go to the AbapSlim test suite (http://localhost/AbapSlim) and modify the Slim parameters:

abapslim.PNG

Click edit on the AbapSlim Page and edit the variables defined so they point to wherever you put the FitNesse folder and replace NSP at the end of the COMMAND_PATTERN with your SAP instance SID. Now you can run the test suite by clicking the Suite button.

 

When you create your own test suite you'll need to include the same variables in the suite page so that it knows to use ABAPSlim instead of the default Java Slim implementation:

!define TEST_SYSTEM {slim}

!define TEST_RUNNER {C:\Fitnesse\ABAPSlim.jar}

!path C:\Fitnesse\

!define COMMAND_PATTERN {java -Djava.library.path=C:\Fitnesse\ABAPSlim_lib\ -jar C:\Fitnesse\ABAPSlim.jar -sap NSP}

 

Thanks for reading and I hope you find this useful!

 

Lucas Tétreault

 

 

 

P.S.

If you're not using Windows on x64 64bit then you'll need to download the SAP JCO 3.0 for your OS + architecture from Service Marketplace and put the files in C:\Fitnesse\ABAPSlim_lib


Enabling an SM30 maintenance view for multiple users

$
0
0

Hi everybody, this is about how i got an SM30 maintenance tableview, to accept changes from multiple users at the same time.

It's my first ever blog, so don't expect too much.

 

Maybe I should start with the admittance that DDIC related development is not my field of experience – but nevertheless, I'd like to share the following, because a reasonably thorough search on SCN didn't bring any satisfying result.

 

Tableview V_T001B_COFI is a maintenace view that allows users, to assign timeslots for accepting posts, depending on various key values. The view features a subset of the table entries of table T001B.

 

V_T001B_COFI_Change View _Posting Periods_ Specify Time Intervals__ Overview.png

 

 

When the request was brought to me, that the maintenance of posting periods in tableview V_T001B_COFI should be enabled for multiple users at the same time, I started to search SCN for similar threads. That's where I came across this one.

 

The outcome however, was not satisfactory, since any number of users could have made concurrent changes, leaving the lastone to press 'SAVE'  as the winner.

 

So I started to reanalyse the problem.

 

I would need to create key specified locks.

 

My first impulse was, to create a new maintenance dialog, complete with an enqueueing object, specifically taylored to my needs – that is, locking entries keyspecific.

The maintenance dialog would then have to ensure, that no two users could maintain the same entries. Thatwas the easy part, as far as the maintenance in my own dialog would go.

 

In addition it was necessary, that any maintenance started by using the standard view (V_T001B_COFI) would still prevent any other user from editing any entry - whilst on the other hand, the key specific  lock would have to prevent the standard maintenance as well.

 

I knew that I could achieve that, by cleverly combining the use of the FMs to be generated by the enqueueing object, together with FM 'ENQUE_READ'. But still it felt clumsy.

 

So I started to reanalyse the problem.

 

I didn't need to have control over every single tableline to be locked seperately – a subset, in this case by tablefield BUKRS would be completely sufficient.

 

And there it was – the keyword subset. I remembered coming across in DDIC, when building a maintenance tableview.

 

So my new aproach started by copying tableview V_T001B_COFI to Z_T001B_COFI, with the only difference , setting the maintenance attribut (RDONLY) for viewfield BUKRS to 'S'.

Z_T001B_COFI-PERFORMANCE_ASSISTANT.png

Z_T001B_COFI_Dictionary_ Display View.png

Following that, I created a maintenance dialog without any fancy stuff.

 

When I started the tableview Z_T001B_COFI, a popup dialog prompted me to provide the variant(BUKRS) and the next thing was a maintenance screen with only those entries, matching the variant.

Z_T001B_COFI Change View _Posting Periods_ Specify Time Intervals__ Overview.png

A quick glance on the enqueued object showed the difference. Where the standard View had produced an entry, specified only by tablename, client and record type, there was now the variant as additional discriminator.

T001B_ENQUEUE ARGUMENT.png

A little bit of testing revealed, that all my preliminary enqueueing requirements where fully met. Without any additional enqueueing object or  modification, the request was fullfilled.

  Mission accomplished

 

Sometimes, it pays to think twice before starting to code and for me, it signalled that I should get myself a bit more into DDIC.

Book Review: ABAP Objects ABAP Programming in SAP NW ( SAP Press)

$
0
0

When I joined SAP Labs India in 2007 , this book was gifted to me ( as well as every ABAP developer) in the team by the manager. Initially we thought what we would do with the book ( use it as weight for exercising was also an option given the volume of the book). So it found a corner space in the book shelf and rested their comfortably. But as I moved into typical developments,  I realized the importance of the book. The book is pretty expansive in content and would touch upon mostly everything if not in detailed. The emphasis of the book as the name suggests is on ABAP Objects and the authors have done a wonderful work in that regards. Before I go into details of the book please understand This is not a book its a bible. You have to read it every day to soak in the learning.


The book has following thirteen topics

  • Introduction
  • A practical introduction to ABAP
  • Basic Principles to ABAP
  • Classes and Objects
  • Basic ABAP Language Elements
  • Advanced Concepts in ABAP Objects
  • Classic ABAP- Events and Procedures
  • Error Handling
  • GUI Programming with ABAP
  • Working with Persistent Data
  • Dynamic Programming
  • External Interfaces
  • Testing and Analysis Tools


So if you see the content, you would agree with my previous statement that the content is expansive. If you are new to ABAP objects this book is a treatise on everything on ABAP objects and explains every concept in detail with examples. I have gone back and read this stuff so many times that some of the examples are always in my mind. Since I was into developing frameworks there were some topics such as Events in ABAP , Dynamic Programming  ( Field Symbols and Dynamic Method calls) , Testing and Analysis Tools , Shared Memory and XML were very beneficial to me. This book also has a very robust section on Error Handling which is often ignored by consultants. It is imperative that exceptions are caught in the UI and they should be raised in the frameworks and the context information be provided in exception objects.


Most of the us have learned ABAP through on job learning and in that case we know the practical usage but the theoretical knowledge of concepts is lagging. This book exposes us to those smaller things. Also sometimes in the project when we have to deal with topics such as RFCs and External Interfaces of which we have no clue. A formal knowledge on the subject would not prepare to do things on previous mentioned topics but give us a logical edge to think in the right direction.

 

If you are into any sort of ABAP development especially ABAP Objects , this book is a must have.


In the end I would summarize - this book has been my companion for more than 7 years and now when I flip the pages to read a topic which I don't use in my daily life , I am still amazed at the depth of knowledge of authors Horst Keller & Sascha Krüger and that spurs me to raise my standards.

The case of sending mail with graphs .

$
0
0

Hi,

 

Recently there was a question about sending a chart by mail from pavan kumar Mallipeddi

 

CL_GUI_CHART_ENGINE Graph As Email in Background

 

Using a hint from Jānis B I was able to generate a chart and to add it in the body of the message and also as attachment .

 

Program Y_R_EITAN_TEST_10_06 attached use cl_document_bcs and cl_igs_chart_engine .

 

cl_igs_chart_engine recieve XML documents as input so cl_ixml used .

 

In order to display the image in the body of the message cl_gbt_multirelated_service is used this allow the use of <img src="cid:<image name>"> tag .

 

FORM do_igs_chart is reponsible to return the image as xstring .

 

The program is also send a smart form as attachment. (A bonus.....)

 

screenshot_01.png

 

 

 

 

screenshot_02.png

 

screenshot_03.png

screenshot_04.png

Do you know your SE16N?

$
0
0

Probably, this does not exactly belong here, but i found no better place for it.

 

Several people i know(me included) like to use TA SE16N for a quick glance at table data or as a means of preselection e.g.  for personnel data.

Something like a query but without the hassle of building one.

 

It's really great, as long as you only need data from a single table AND all selection criteria are AND related. (Or so I thought)

 

One problem, I frequently ran into, is when you want to evaluate  a "nested" table. That is a table that has multiple  fields in its structure, basically for the same data, representing a limited table in maintenance view.

 

For example infotype tables in HR are often built like that and the problem is always, that you do not know, in which field (nnnn01, nnnn02, …) to look for a certain value.

 

Now quite I recently figured out, that SE16N can do that and I found it worth sharing.

 

Let's assume, you  need to know all employee, that have a certain date type (say 18) in infotype 41, in a specific time interval, but you have no idea, in which date type column (DAR01, DAR02,…)  it is to be found.

 

In the past, I would have run several selections, each one with a different DARxx targeted to my date type and then would have assembled the results in Excel.

 

This is how to do it now

 

 

  1. Call TA Se16N with table PA0041
  2. Fill in the first set of selection criteria (DAR01 / DAT01)
  3. Change to Technical View On (SHIFT+F11 or right-click for context menu)Technical View ON.png
  4. Select Multiple entry (Cntrl+F9 or right-click for context menu or More button in the top)
    Multiple Entry.png
    Youwill be rewarded with an additional, empty selection screen
  5. Fill in your selection criteria and then press Next Entry (F5 Or Right Arrow in the bottom)More First.png
    You get a new empty selection screen. Note the number shown in the top line, it will increase as you circle through your  value sets.More Second.png
  6. Repeat
  7. When you're through with typing in your values in every possible column, press F8 to get back to your starting selection.
    The only hint, that there are additional select criteria, is the more button in the top, now showing the filled Icon.

SE16n Final.png

 

The difference is  in the result. After executing  your selection, you will now receive all entries, that match either of your value sets. They are each linked by OR-relation.

Of course, the use of this feature is in no way limited to HR Infotype tables. It's just the most common use for me.

Wherever you need OR-related  sets of values for a SE16N Selection , this feature comes in handy.

 

Hope you'll enjoy it.







An issue caused by implicit conversion during RFC call

$
0
0


Just would like to share with you how I struggle with a bug recently. These days I am supporting a customer project which would go live very soon. We find a severe performance problem in a scenario. After fighting it for sometime, we locate the possible cause into the FM call below.


clipboard1.png

This is a RFC call from CRM to ERP system with expectation that at maximum 15 records are returned.

 

Much to our surprise, far more than 15 records are returned, finally leads to the performance issue.

clipboard2.png

Then I go to the responsible colleague for FM SLS_LORD_GET_VALUES_SAREA saying "Hey man, there is something wrong with the max entry filtering logic in your FM. Customer will go live soon and could you please help as soon as possible". My colleague soon replied to me "just checked from my side, this FM works pretty good".

 

Then I have to look into this issue once again. When debugging inside this FM, I found the importing parameter is not 15, but becomes a big number 3473457, so the max record filter logic absolutely does not work in such case.

clip_image002.jpg


I checked the signature of this FM, and found out we should pass into it with a integer, not a char ( '15' in this example ) .

clipboard4.png

Where does this magic number '3473457' come from

 

I write a small report to explore it. I deliberately pass char value ( from 1 to 15 ) to an importing parameter with INT type. 

 

DATA: lv_num_c TYPE char2.

START-OF-SELECTION.

DO 15 TIMES.

  lv_num_c = sy-index.

  TRY.

    CALL FUNCTION 'ZTEST'

      EXPORTING

         iv_in_rfc = abap_false

         iv_num = lv_num_c.

  CATCH cx_root INTO DATA(cx_root).

     WRITE: / cx_root->get_text( ).

     EXIT.

  ENDTRY.

ENDDO.

DO 15 TIMES.

  lv_num_c = sy-index.

  CALL FUNCTION 'ZTEST' DESTINATION 'NONE'

    EXPORTING

        iv_in_rfc = abap_true

        iv_num = lv_num_c.

ENDDO.

The FM ZTEST just insert what has passed in to a DB table:

 

 

FUNCTION ZTEST.

ls_rfc-in_rfc = iv_in_rfc.

ls_rfc-num = iv_num.

INSERT zrfc_Test FROM ls_rfc.

ENDFUNCTION.

 

 

Execute result:

 

The first normal FM call failed with exception:

 

The function call of ZTEST failed; a field may have been assigned to the parameter IV_NUM whose type is not compatible with this parameter

 

The second round RFC call does succeed, and check what has been written into the DB:

clipboard5.png

why we passed a '1' into the FM and it has been interpreted as 2097201??

 

in our report, we have passed '1' ( an variable with type CHAR2 ), whose Hex value is 31002000. And when it is passed into this remote-enabled FM, since the importing parameter is defined as INT in signature, the framework tries to interpret this Hex value back to an Integer, which is 2097201, since this Integer has the very Hex value 31002000.

clipboard6.png


This operation could simply be simulated to the lines below:

clipboard7.png

Summary

 

 

In normal function call, the ABAP runtime environment could help us identify some misuse on FM call, for example the non-compatible parameter type just as the first example in this blog. However when dealing with remote function call, such misuse would not lead to runtime exception, the application could keep running but not in accordance with what we expect, and sometimes it is difficult to identify in which step the deviation occurs. So we must strictly adhere to the development guideline to avoid such implicit conversion.

How to use FM : BAPI_ACT_PRICE_CHECK_AND_POST

$
0
0

Hello SAP Folks,

 

This document is an attempt to explain the use of the FM  BAPI_ACT_PRICE_CHECK_AND_POST, which can be used as an alternative of TCode KP26 for batch processing & automating the Cost Center/Activity Price Planning.

 

For instance, Lets take a requirement to automatically update the Price Planning with the currency exchange rate change at the start of every month.

A background program can be very much helpful to achieve this requirement as the manual update for multiple cost centers/activity using KP26 requires big efforts & human error is also possible.

 

Now, to achieve this requirement, I have taken the reference of Standard Table COST to get the Cost Centers/Activity Type, for which the Price Planning in KP26 is updated at least once.

 

Since the Program contains large no. of lines, I have Attached the Program here in the notepad file. For the above requirement using the subjected BAPI.


Below is the Program Selection screen which looks similar to what we see in KP26.

Capture.PNG

 

Now, Once we execute the program, the BAPI will be called to update the Price Planning for the Given Selection. Please note that ideally the Cost Center & Activity Type fields should remain empty during the Batch Job, else there is no point creating the program. Here I have entered Cost Center & Activity in selection for Demo Purpose.

 

Once the execution is completed, the program will display the processing Log in a classical ALV display, which would help us to analyze the processing & identify the failed ones.

 

If the update is successful, the program will display the log like below :

Capture2.PNG

Else, If something goes wrong during the processing or any error is found, the Error will be displayed like below :

Capture1.PNG

 

Please note, that using this BAPI you can perform the price planning for multiple entries with a single Function call, but the BAPI itself does not return any success message in return table if the update is successful & sometime may not return the Cost Center or Activity Type Value in case of some failure, so you may not get to know that out of n entries, which were updated & which were failed.

 

So to override this restriction, I have called this BAPI in a Loop to get the status of each cost center/activity combination.If you are concerned with the performance impact, it would still give faster execution time so no major impact would be observed.

 

As a result of Batch Execution, the processing log will look somewhat like below :

Capture3.PNG

 

Please refer the attached file to refer the source code.

I hope this will be helpful for you.

 

Regards,

Bharat Bajaj

Package in object name, why should you do it?

$
0
0

I know this may not be universally agreed upon, but I think it's something worthy of being discussed and so I decided to explain my reasoning behind it.

Although I started my programming career in ABAP over the years I've programmed in Java, C#, Objective-C,etc. In these languages an object has a name, and a fully qualified name:

 

  • Name: Delivery
  • Fully Qualified Name: com.sd.litigations.delivery

 

The benefit of this structure is that the "litigations" team doesn't have ownership on the usage of the "delivery" word, like a trademark. There can be another object called delivery in another package like "com.sd.whatever".

 

In ABAP you have something similar, the custom namespaces, which you may have seen in SAP developments /<application>/ but are rarely used outside SAP (I've used this once in a project). Most of the times we all share the Z or Y namespace.

 

So In my arrogance I create a package called ZLITIGATIONS, go into SE24, and claim the name ZCL_DELIVERY for myself (like I've seen people do so many times....). Why should I be encouraged to do this? Is my litigations application more important that some other package? No.

 

That's why in my projects we use some sort of package symbol in the class name, like ZLT_DELIVERY. This way someone else can create a DELIVERY object without it feeling like a "second best", besides making it easier to search for the class with intelisense (in ABAP for Eclipse).

 

PS: I know some will question why there would be two delivery classes in the first place, but let's keep that for another discussion. Think of another less widely used object, the same reasoning applies.


Proposal search for input fields

$
0
0

HI mates!

 

Actually this is my first blog in my 5+ years SAP career so don't judge me strictly.

 

So I have chosen really intersting topic: probably something that everyone thought about. I'm sure someone even tried to implement this feature by own efforts.

 

This is a proposal search feature. This is so obvious that if we type the beginning of the search term or even mask we want to see the results instantly. But not in SAP GUI. All the times we had to press F4 or at least click at search button by mouse.

 

But this morning while creating a new search help I suddenly noticed an  interesting new block in SE11 transaction.

 

pic1.PNG

 

According to F4 search help I found the following note:

 

http://service.sap.com/sap/support/notes/1861491

 

The requirements for that feature is quite high: These functions are available as of SAP NetWeaver for ABAP 7.4 SP03 and SAP GUI for Windows 7.30 patch level 5 or higher. But happily we have SP04 so I decided to go further and to try it.

 

I have selected search helps from DD30L table by AUTOSUGGEST = 'X' field. Hopefully there were results and I have chosen SDSH_SPFLI as an example to try.

From the where-used list of the following search help i found SDSH_S_SPFLI structure and created a tiny program following the instructions from the mentioned above note:

 

report ztest_autosuggest.
parameters:  p_carrid TYPE SDSH_S_SPFLI-carrier_id.
at selection-screen output.  loop at screen.    if screen-input eq 1.      cl_dsh_dynpro_properties=>enable_type_ahead(        exporting          fields    = value #( ( |{ screen-name }| ) )          overwrite = abap_false ).    endif.  endloop.

The result on the screen made me really enjoyed. Now we have it as a standard feature:

 

pic2.png

 

As I understood only since SP06 this feature is going to be activated automatically. So this cl_dsh_dynpro_properties should be called manually in lower versions.

 

Also I would mention that it's possible only search by code. The full text fuzzy search is available only for special databases like SAP HANA as well,

But in anyway that's something interesting!

 

So thanks for reading!

 

Enjoy!

Why object oriented? - Class Attributes

$
0
0

As with most OO related discussion (i.eWhy object oriented? Import parameters), the arguments get interesting really quick. In that blog, a fellow SCN user described her dislike for OO, and how it relates to procedural, as the following:

The funny thing is that this aspect I dislike most about FM's is their greatest parallel to OO concepts. Basically you can say that a function group is something similar to an object, with the FM's being its methods. Fortunately, most of the time the FM's are independent and provide a well-defined functionality that only depends on the parameters you are providing at run-time.

The point the user is making is that class attributes are a lot like global variables, which are more annoying to debug since it's harder to find out where they were actually set. I really get where he is coming from. As always, it's a matter of design and following OOP guidelines.

 

What you should do:

  • Public atributes should be set in the constructor;
  • Private attributes should be set in specific getter/setter methods (or the constructor).

 

What you shouldn't do:

  • Change an attribute directly outside of the constructor and getter/setter methods.
  • Create attributes for temporary variables that are not related to the object itself;
  • Create attributes with obscure naming like WERKS, use Plant instead. Use _ if you have to, the attribute name should always be self descriptive.

 

The rule of thumb is: If the attribute is not worthy of public or a getter/setter then it is temporary method variable and shouldn't be an attribute at all.


This explains why they aren't so bad if used well, but doesn't explain why you should use them. I think the most immediate way to summarize their power (in the context of SAP WM, and TO confirmation) is this:


lo_to->confirm( ).


Why is this powerful? Because import parameters are messy, and the more "the merrier" (joking). Translation, the more parameters, the more mistakes you can make and the more information you need to use them correctly.

This is the exact opposite of the FM with zero state, where all information must be provided by IMPORTING parameters. Lets picture the scenario where you need to confirm a TO (from SAP WM) without using OO attributes. SAP provides a FM for that:


    call function 'L_TO_CONFIRM'      exporting        i_lgnum                        = lv_lgnum        i_tanum                        = lv_tanum        i_squit                           = lv_squit        i_komim                        = lv_komim        i_tbeli                            = lv_tbeli        i_update_task                  = 'X'      tables        t_ltap_conf                    = lt_ltap_conf[]        t_ltap_conf_hu              = lt_ltap_hu[]

 

This is not an extreme example there are way more complicated FM interfaces. There are two main problems with this:

  • Filling lt_ltap_conf and lt_ltap_hu is far from trivial;
  • The FM call is completely coupled to the calling program (more on that in an instant).

 

I've used this function in RF programs which are a pain (if you ever touched one, you will know it...), and have persistency requirements: a warehouse worker can't lose his work if Wi-Fi goes down, so every item picked gets saved to the database. Now let's assume that an error is thrown at TO confirmation, and you need to find out why.

 

If you used the traditional procedural way, you would need to run the calling RF program, get to the place where you make the TO confirmation and debug (with the extra annoyance that RF is system-guided, so getting that specific TO means superuser specific code). Either that, or you could SE37 the FM and fill by hand all the parameters of the FM (remember the counterexample is a FM with not state).

 

With OO, it's as simple as SE24, create instance, insert number of TO (and warehouse number), and then press "CONFIRM". Two parameters and you are ready to debug. I cannot stress how important this decoupling becomes when you are debugging errors or making unit tests, this separation between caller and callee means you can focus on particular behaviors.

 

This is a specific situation with persistence where OO gives you a really advantage. I've found over the years that dealing with persistence is one of the greater advantages of OO since most of the data is already available. In a more general situation the CONFIRM call would be preceded by calls to ADD_ITEM, but even that is simpler than manually filling the lt_ltap_conf with absolutely no rules (check Why object oriented? Import parameters for my argumentation on that subject).

 

All this could be made using Function Groups, since this blog more about Object Oriented Programming then Classes/Methods.

Retain selection screen values on program restart for transactions with chosen selections

$
0
0

I had an interesting last week and what looked easy at the outset did my head in for quite a while. I tried various methods and the below approach was able to give me a satisfactory solution.

 

Let's have a look at the high level requirement.

 

 

- Copy a standard SAP transaction and customise it . Below picture describes the requirement

Screen Shot 2014-08-16 at 7.28.17 pm.png

 

The transaction is started with only few fields of the complete selection screen as the selection screen has too many fields and may confuse the users.

 

 

2.PNG

 

The users need to navigate back and forth between different modes of output screen. At the first display of output screen ( let's call them as levels - so the output screen is at level 0 when initially displayed and if called again goes to level 1 and so on . Similarly when the user comes back from a higher level of screen, the level will decrease : from 1 to 0 ). And of course when the program navigates back from level 0 of selection screen, it should display the selection screen.

 

 

I prototyped using a simple program using flight model.

 

 

- Selection Screen : Contains all fields.

All fields.PNG

 

However, the transaction is always run with a variant which hides the last field.

 

Selection Screen.PNG

 

- Let's test with some data.

 

 

Selection Test.PNG

 

We get the level 0 output screen.

 

Level 0.PNG

 

Now click on filter ( search icon on top right )

 

Filter.PNG

 

and we get level 1 screen.

 

Level 1.PNG

 

Looks good so far. Now, let's try going back - going back to level 0 gives the screen as anticipated. However, when we go back and see that the selection screen parameters have gone back. The selection screen has gone blank !

 

Selection Screen.PNG

 

 

Let's see what's going on.

 

As we need to keep track of different levels of screen, if the level of screen is greater than 0.

.......

ELSEIF sy-ucomm = 'EXIT'.

    IF gv_list_level > 0.

      gv_list_level = gv_list_level - 1.

      gt_flight[] = gt_master_flight[].

      CALL SCREEN 100.

    ENDIF.

 

 

When we want to go back to selection screen from screen at level 0, we use below:

 

SUBMIT zsubmit_flight

            WITH report EQ 'ZTESTFLIGHT'

            WITH variant = 'ZFLIGHT_VAR'

            WITH SELECTION-TABLE gt_seltab .


zsubmit_flight is a standard SAP report used by the report and can't be changed by us.


SUBMIT (REPORT) WITH VARIANT  = VARIANT

                    USING SELECTION-SET VARIANT

                    VIA SELECTION-SCREEN

 

Workaround:

1) Store selected values by call of RS_REFRESH_FROM_SELECTOPTIONS 

 

2) Export the selection table before doing a program restart.

EXPORT gt_seltab TO MEMORY ID gc_sel_mem.

 

3)  Retrieve the selection table AT SELECTION-SCREEN OUTPUT.

 

RS_VARIANT_CONTENTS gives the parameters and select-options actually visible in the variant.

 

IMPORT gt_seltab FROM MEMORY ID gc_sel_mem.

 

  IF NOT gt_seltab[] IS INITIAL.

 

    CALL FUNCTION 'RS_VARIANT_CONTENTS'

      EXPORTING

        report              = 'ZTESTFLIGHT'

        variant              = 'ZFLIGHT_VAR'

      TABLES

        l_params            = lt_params

        l_selop              = lt_selops

        valutab              = lt_value

      EXCEPTIONS

        variant_non_existent = 1

        variant_obsolete    = 2

        OTHERS              = 3.

    IF sy-subrc <> 0.

clear:lt_value,

          lt_selops,

          lt_value.

    ENDIF.

 

* Update parameters values

 

    LOOP AT lt_params INTO lw_param.

      READ TABLE gt_seltab REFERENCE INTO lo_values WITH KEY selname = lw_param-name.

      IF sy-subrc = 0.

        lv_attr = lo_values->selname.

        TRANSLATE lv_attr TO UPPER CASE.

        ASSIGN (lv_attr) TO <fs_attr_val>.

        <fs_attr_val> = lo_values->low.

      ENDIF.

    ENDLOOP.

 

* Update select-option values

 

    LOOP AT lt_selops INTO lw_param.

      READ TABLE gt_seltab REFERENCE INTO lo_values WITH KEY selname = lw_param-name.

      IF sy-subrc = 0.

 

        CONCATENATE lo_values->selname 'SIGN' INTO lv_attr SEPARATED BY '-'.

        TRANSLATE lv_attr TO UPPER CASE.

        ASSIGN (lv_attr) TO <fs_attr_sign>.

        <fs_attr_sign> = lo_values->sign.

 

 

        CONCATENATE lo_values->selname 'OPTION' INTO lv_attr SEPARATED BY '-'.

        TRANSLATE lv_attr TO UPPER CASE.

        ASSIGN (lv_attr) TO <fs_attr_option>.

        <fs_attr_option> = lo_values->option.

 

        CONCATENATE lo_values->selname 'LOW' INTO lv_attr SEPARATED BY '-'.

        TRANSLATE lv_attr TO UPPER CASE.

        ASSIGN (lv_attr) TO <fs_attr_low>.

        <fs_attr_low> = lo_values->low.

 

        CONCATENATE lo_values->selname 'HIGH' INTO lv_attr SEPARATED BY '-'.

        TRANSLATE lv_attr TO UPPER CASE.

        ASSIGN (lv_attr) TO <fs_attr_high>.

        <fs_attr_high> = lo_values->high.

 

        lv_attr = lo_values->selname.

        TRANSLATE lv_attr TO UPPER CASE.

        ASSIGN (lv_attr) TO <fs_attr_main>.

 

        CONCATENATE lv_attr '[]' INTO lv_attr_tab.

        ASSIGN (lv_attr_tab) TO <fs_attr_tab>.

 

        IF lo_values->low IS NOT INITIAL OR lo_values->high IS NOT INITIAL.

          REFRESH <fs_attr_tab>.

          APPEND <fs_attr_main> TO <fs_attr_tab>.

        ENDIF.

      ENDIF.

    ENDLOOP.

 

  ENDIF.

 

 

- Create a transaction 'ZFLIGHT' with program ZTESTFLIGHT , variant ZFLIGHT_VAR.

 

 

The code can be referred here:

 

ZSUBMIT_FLIGHT

https://github.com/viksingh/ABAP_Demo/blob/master/ZSUBMIT_FLIGHT

 

Program ZTESTFLIGHT with issues:

https://github.com/viksingh/ABAP_Demo/blob/master/ZTESTFLIGHT_1

 

Program  ZTESTFLIGHT with corrections:

ABAP_Demo/ZTESTFLIGHT_2 at master · viksingh/ABAP_Demo · GitHub

 

The key here is function module RS_VARIANT_CONTENTS and the dynamic update of selection screen after restart of a transaction.

Switching From Java/C++ to ABAP

$
0
0

This article probably isn't required, as most of us here are already ABAP developers, but, I thought it would be good experience to share my thoughts what it actually felt like to switch to ABAP.

 

Unlike Java or C++ , ABAP isn't a language for development of applications on Windows or any other operating system for that matter. The purpose it actually serves  is to cater to the needs of Industries who are using SAP. So the major difference between working  ABAP and Java/C++ is you have to utilize most of your talents to study the functions already provided by SAP team, as generally it optimizes the program whereas it generally is vice-versa on other language as it increases dependency on Platform.

 

ABAP can be considered a more specific language in terms of application. By this I mean that while in other languages you have freedom to present end result of your program but in ABAP you have to optimally utilize SAP GUI to present results.

 

When working on Java I was really sick of long syntax's . To be honest it really felt more like article writing rather than coding(and by now u might  have  figured out how good I'm in article writing ). I would clearly like to state here I am not judging the capabilities of any programming language, it's just my perspective on things. When I switched from Java to ABAP , firstly I thought why do companies even require ABAP coders at first place(though I clearly understand now 'why').

The simplicity it offered at the beginning made me feel like as if they had designed a function for each and everything you wanted to do, thus rendering you useless again.

 

So it can be concluded that if you are good in any other Programming language , don't get scared to switch to ABAP. It simply is the simplest language you can find. So far, while working on ABAP , I felt like as if ABAP was designed to comfort the Developers (I really mean that). Coding on ABAP really is a piece of cake(some may disagree). You just have to understand the business scenarios a bit better than your fellow counterparts working on other technologies.

I am really glad that I made the switch to ABAP when I had chance.

Why you should have your own style(guide)!

$
0
0

Hi all,

 

Today's blog is about something I’m thinking quite a while how to create it.

 

We, as ABAP-Developer , didn't have such a problem or better, not in the same way a lot of others programmers had to face it.

 

What I want to say, is when you have a look at a SAP-Transaction everybody of us know normally how to navigate. There are the well known Navigationbar, Execute and all the Standard-Buttons above an ALV.

I think, you know what I’m talking about. So what is this blog for? I recognize that developing in ABAP make a huge change at the moment (ok, it is more a process and yes it's more a general thing). That’s why we face problems, we didn't know before and one is the Design-Styleguide.

 

I just want to collect some facts why all of us should have a Design-styleguide as an additional document to the coding-guidelines.

 

Everybody of us know the Coding-Guidelines and I’m pretty sure, I will not find one person around here saying it is not necessary to have a document like this. Of course, some are a bit over the top, but all in all it is a great instrument to lead all people into the same direction.

 

Now, when I would say that a Design-Styleguide is also necessary a lot people saying to me, that it’s not important.

 

Why do I get this reaction? I mean, of course years before, when just the dynpro was developed by SAP you just could choose between a hand full controls and a predefined icons to create your screen. That was awesome, because all of the programs had the same look and feel. I think that was one of the success-factors of SAP. Keep it simple and similar might be the thinking behind.

 

But developing has changed and of course the elements we can use today are a lot more. I don't know where here on SCN I have read the sentence “A 23 year old employee don't want to use a 20 year old software” and that is so true. Additional to that, I would say that also the rules of developing has changed and so today also we, the SAP developers may need more than just a coding-guidelines.

 

Now, how to start…what to do? This is how I want to start and hey, miss the counting in my blog? Me too

 

1st point Use existing style guides

 

This is a very important point. Before to start with the own one work through other styleguides. All the companies out there especially in the mobile sector have one and I’m pretty sure SAP also got one, so if anybody knows where it is located, I would love to have a link in the comments

 

2nd point Keep it short and simple

 

I read a lot of different blogs about this topic and I decided for me, that a good styleguide should not have more than 4 to 6 pages. In my opinion I’m pretty sure, that a lot of people saying that it is not necessary (as mentioned in the beginning) and out of that I’m pretty sure that it is a absolutely success factor that the guide is as simple as possible. Don't add tons of links, keep it simple, we all are creative people and I if something is not clear, make it easy to ask.

 

3rd point Example-package

 

Provide a package of examples that leads through the different designs. That’s a way you can make similar feeling and by the way after investing some money you can save it afterwards with a multiplier

 

 

 

 

4th point Easier to test (or 3.1)

 

What?  A styleguide should make my programs easier to test? Yes, it does. People will feel comfortable with the handling and so it will be easier to hand over some testing to another. Also keep in mind that the other worlds (UI5 / River / POWL) swap on our desk and it is not just a trend. It’s the future.

 

5th point Easier to develop and to prepare (or 3.2)

 

A styleguide also helps the responsible persons to prepare the documents for developing. All people in the chain got a similar expectation of the program from the first sentence in the whole process. This will take time in the beginning, but I’m pretty sure in the end it will result in lower bugs and lower maintenance cost on each side.

 

6th point Shared vocabulary (or 3.3)

 

This is a point, I humble sometimes over. I see 3 different programs with similar (custom)data in it and there are also three different data-elements used. I know, this won't be fixed in summary by an styleguide as well, but I’m pretty sure, that a active styleguide in an company leads to more sensitive searching before creating new elements.

 

7th point Invest time

 

That isn't just a phrase here. I’m pretty sure that it will take more than two meetings to complete the process. I think it needs a lot of time to walk through the company and ask a lot of people how they define it and collect just facts. Afterwards pin all facts at a whiteboard and go on to point number eight. Of course, it also will take a lot of time to create the sample packages (Webdynpro, BSP, UI5), but this is also a nice doing to get in touch with all the things.

 

8th point Onboard young employees

This document should be created by the young employees. They may add their points without having the company glasses on. No need to argue, but I think the “older” employees should have the second check on the document, so everybody has the chance to add something but in this case all voices are at the same level and if there are arguments, it will be early enough to add these to the document.

 

9th point Catch the feedback

 

In my opinion I think it need a period to have an “beta-test” on the document. After the examples (or during) are created it should have a group of people to try the handling, if possible people without any experience of programming. They will deliver clear voices if they think all the different examples give the feeling to be always on the same highway. I think you know what I mean.

 

10th point Love it or leave it

 

 

If you have worked through the other 9 points and ended here I just have to add, that I do not know anything other to add.  I know that this will end in a lot of discussions and it might not be easy to deliver such document. So I think you have to love the idea behind and see the necessity. And I hope I delivered that point with this blog

 

 

Conclusion:

 

Have a look around in your company and I’m pretty sure there are always people trying to put things in a row, let documents look the same and try to publish the brand in the same way. So this is exact the same what a styleguide tries. All we have to learn is that a styleguide today is normal and when I talk to people with other software in the back, it is usually a must have, except if I talk to SAP-guys.

 

I think it is time to leave the leaf or do I see things wrong and most of you got one available?

 

(Comic comes from here: xkcd: Donald Knuthhttp://xkcd.com/163/)

 

Thank you for reading to the end. Feel free to add something I missed or just to leave a comment.

 

Cheers

Florian

Viewing all 948 articles
Browse latest View live


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>